@fickydev/pigent 0.1.9 → 0.1.11
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 +13 -0
- package/PLAN.md +29 -18
- package/TODO.md +19 -12
- package/agents/assistant/agent.yaml +0 -5
- package/drizzle/migrations/0002_bouncy_leper_queen.sql +13 -0
- package/drizzle/migrations/0003_secret_stone_men.sql +11 -0
- package/drizzle/migrations/meta/0002_snapshot.json +606 -0
- package/drizzle/migrations/meta/0003_snapshot.json +681 -0
- package/drizzle/migrations/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/pigent.yaml +16 -0
- package/src/agents/AgentRunner.ts +46 -48
- package/src/agents/BotCommandHandler.ts +109 -15
- package/src/channels/types.ts +3 -0
- package/src/config/loadConfig.ts +1 -0
- package/src/config/schemas.ts +14 -8
- package/src/daemon/AgentDaemon.ts +9 -2
- package/src/daemon/Scheduler.ts +223 -0
- package/src/daemon/taskDue.ts +4 -0
- package/src/db/repositories/TaskConfigRepository.ts +63 -0
- package/src/db/repositories/TaskRepository.ts +129 -0
- package/src/db/repositories/index.ts +4 -0
- package/src/db/schema.ts +30 -0
package/pigent.yaml
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
telegramChats: []
|
|
2
2
|
modelChoices: []
|
|
3
|
+
scheduler:
|
|
4
|
+
tickIntervalMs: 10000
|
|
5
|
+
tasks: []
|
|
3
6
|
|
|
4
7
|
# Optional. If empty, /model uses Pi's currently available models automatically.
|
|
5
8
|
# Add choices to curate or rename buttons for /model.
|
|
@@ -20,3 +23,16 @@ modelChoices: []
|
|
|
20
23
|
# - assistant
|
|
21
24
|
# instructions: |
|
|
22
25
|
# Be concise.
|
|
26
|
+
#
|
|
27
|
+
# Scheduled tasks. Each task runs on interval and sends output to channel.
|
|
28
|
+
# If the agent returns "NOOP", nothing is sent.
|
|
29
|
+
#
|
|
30
|
+
# scheduler:
|
|
31
|
+
# tickIntervalMs: 10000
|
|
32
|
+
# tasks:
|
|
33
|
+
# - id: monitor-build
|
|
34
|
+
# agent: assistant
|
|
35
|
+
# intervalMs: 30000
|
|
36
|
+
# prompt: Check the build pipeline. NOOP if nothing to report.
|
|
37
|
+
# channel: telegram
|
|
38
|
+
# chatId: "-100123456"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { InboundMessage } from "../channels/types";
|
|
2
2
|
import type { Repositories } from "../db/repositories";
|
|
3
|
-
import type { AgentSessionRow } from "../db/schema";
|
|
4
3
|
import { logger } from "../logging/logger";
|
|
5
4
|
import { PiAgentRunner } from "../pi/PiAgentRunner";
|
|
6
5
|
import type { AgentRegistry } from "./AgentRegistry";
|
|
@@ -11,17 +10,12 @@ export type AgentRunInput = {
|
|
|
11
10
|
message: InboundMessage;
|
|
12
11
|
};
|
|
13
12
|
|
|
14
|
-
type AgentRunResponse = {
|
|
15
|
-
text: string;
|
|
16
|
-
error?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
13
|
function sessionLockKey(input: AgentRunInput): string {
|
|
20
14
|
return [input.agentId, input.message.channel, input.message.chatId, input.message.threadId ?? ""].join(":");
|
|
21
15
|
}
|
|
22
16
|
|
|
23
17
|
export class AgentRunner {
|
|
24
|
-
|
|
18
|
+
readonly piRunner = new PiAgentRunner();
|
|
25
19
|
private readonly sessionLocks = new Map<string, Promise<void>>();
|
|
26
20
|
|
|
27
21
|
constructor(
|
|
@@ -41,42 +35,38 @@ export class AgentRunner {
|
|
|
41
35
|
threadId: input.message.threadId,
|
|
42
36
|
});
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
sessionId: session.id,
|
|
47
|
-
channel: input.message.channel,
|
|
48
|
-
direction: "inbound",
|
|
49
|
-
senderId: input.message.senderId,
|
|
50
|
-
chatId: input.message.chatId,
|
|
51
|
-
threadId: input.message.threadId,
|
|
52
|
-
content: input.text,
|
|
53
|
-
rawJson: input.message.raw,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const chatInstructions = await this.chatInstructions(input);
|
|
57
|
-
const response = await this.createResponse(input, session, chatInstructions);
|
|
58
|
-
|
|
59
|
-
if (response.error) {
|
|
38
|
+
// Skip DB persistence for task-triggered runs (Scheduler handles its own tracking)
|
|
39
|
+
if (input.message.source !== "task") {
|
|
60
40
|
await this.repositories.messages.create({
|
|
61
41
|
agentId: input.agentId,
|
|
62
42
|
sessionId: session.id,
|
|
63
43
|
channel: input.message.channel,
|
|
64
|
-
direction: "
|
|
44
|
+
direction: "inbound",
|
|
45
|
+
senderId: input.message.senderId,
|
|
65
46
|
chatId: input.message.chatId,
|
|
66
47
|
threadId: input.message.threadId,
|
|
67
|
-
content:
|
|
48
|
+
content: input.text,
|
|
49
|
+
rawJson: input.message.raw,
|
|
68
50
|
});
|
|
69
51
|
}
|
|
70
52
|
|
|
71
|
-
await this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
53
|
+
const chatInstructions = await this.chatInstructions(input);
|
|
54
|
+
const response = await this.createResponse(input, session, chatInstructions);
|
|
55
|
+
|
|
56
|
+
if (!response.handlerError) {
|
|
57
|
+
// Task runs persist their own messages via TaskRepository
|
|
58
|
+
if (input.message.source !== "task") {
|
|
59
|
+
await this.repositories.messages.create({
|
|
60
|
+
agentId: input.agentId,
|
|
61
|
+
sessionId: session.id,
|
|
62
|
+
channel: input.message.channel,
|
|
63
|
+
direction: "outbound",
|
|
64
|
+
chatId: input.message.chatId,
|
|
65
|
+
threadId: input.message.threadId,
|
|
66
|
+
content: response.text,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
80
70
|
|
|
81
71
|
return response.text;
|
|
82
72
|
}
|
|
@@ -103,9 +93,9 @@ export class AgentRunner {
|
|
|
103
93
|
|
|
104
94
|
private async createResponse(
|
|
105
95
|
input: AgentRunInput,
|
|
106
|
-
session:
|
|
96
|
+
session: any,
|
|
107
97
|
chatInstructions: string,
|
|
108
|
-
): Promise<
|
|
98
|
+
): Promise<{ text: string; handlerError?: string }> {
|
|
109
99
|
if (process.env.PIGENT_FAKE_AGENT === "1") {
|
|
110
100
|
return { text: this.fakeResponse(input.agentId, input.text) };
|
|
111
101
|
}
|
|
@@ -131,26 +121,34 @@ export class AgentRunner {
|
|
|
131
121
|
});
|
|
132
122
|
|
|
133
123
|
if (process.env.PIGENT_FALLBACK_FAKE_AGENT === "1") {
|
|
134
|
-
return { text: this.fakeResponse(input.agentId, input.text),
|
|
124
|
+
return { text: this.fakeResponse(input.agentId, input.text), handlerError: errorMessage };
|
|
135
125
|
}
|
|
136
126
|
|
|
137
|
-
return { text: "Agent failed to respond. Please try again later.",
|
|
127
|
+
return { text: "Agent failed to respond. Please try again later.", handlerError: errorMessage };
|
|
138
128
|
}
|
|
139
129
|
}
|
|
140
130
|
|
|
141
131
|
private composePrompt(input: AgentRunInput, chatInstructions: string): string {
|
|
142
|
-
|
|
132
|
+
const parts: string[] = [
|
|
143
133
|
`[Channel]\n${input.message.channel}`,
|
|
144
134
|
`[Chat]\n${input.message.chatId}`,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
.
|
|
153
|
-
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
if (input.message.threadId) {
|
|
138
|
+
parts.push(`[Thread]\n${input.message.threadId}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (input.message.senderName || input.message.senderId) {
|
|
142
|
+
parts.push(`[User]\n${input.message.senderName ?? input.message.senderId}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (chatInstructions) {
|
|
146
|
+
parts.push(`[Chat Instructions]\n${chatInstructions}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
parts.push(`[Message]\n${input.text}`);
|
|
150
|
+
|
|
151
|
+
return parts.join("\n\n");
|
|
154
152
|
}
|
|
155
153
|
|
|
156
154
|
private async chatInstructions(input: AgentRunInput): Promise<string> {
|
|
@@ -4,6 +4,7 @@ import type { Repositories } from "../db/repositories";
|
|
|
4
4
|
import { getAvailableModelChoices } from "../pi/PiAvailableModels";
|
|
5
5
|
import { isValidModelRef } from "../pi/PiModelResolver";
|
|
6
6
|
import type { AgentRegistry } from "./AgentRegistry";
|
|
7
|
+
import type { Scheduler } from "../daemon/Scheduler";
|
|
7
8
|
|
|
8
9
|
export type BotCommandResult =
|
|
9
10
|
| {
|
|
@@ -23,12 +24,15 @@ export class BotCommandHandler {
|
|
|
23
24
|
private readonly registry: AgentRegistry,
|
|
24
25
|
private readonly repositories: Repositories,
|
|
25
26
|
private readonly modelChoices: ModelChoiceConfig[] = [],
|
|
27
|
+
private readonly scheduler?: Scheduler,
|
|
26
28
|
) {}
|
|
27
29
|
|
|
28
30
|
async handle(message: InboundMessage): Promise<BotCommandResult> {
|
|
29
31
|
const [command] = message.text.trim().split(/\s+/, 1);
|
|
30
32
|
|
|
31
33
|
switch (command) {
|
|
34
|
+
case "/task":
|
|
35
|
+
return await this.taskResult(message);
|
|
32
36
|
case "/help":
|
|
33
37
|
case "/start":
|
|
34
38
|
return { handled: true, text: this.helpText() };
|
|
@@ -62,6 +66,9 @@ export class BotCommandHandler {
|
|
|
62
66
|
"/thinking - show thinking picker for this chat session",
|
|
63
67
|
"/thinking <off|low|medium|high> - set thinking level for this chat session",
|
|
64
68
|
"/thinking default - clear thinking override for this chat session",
|
|
69
|
+
"/task list - list scheduled tasks for this chat",
|
|
70
|
+
"/task create <intervalMs> <prompt> - create a scheduled task",
|
|
71
|
+
"/task remove <id> - remove a scheduled task",
|
|
65
72
|
"/agent <agentId> <message> - send message to specific agent",
|
|
66
73
|
"@agentId <message> - send message to specific agent",
|
|
67
74
|
].join("\n");
|
|
@@ -84,8 +91,8 @@ export class BotCommandHandler {
|
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
return [
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
"Default agent: " + (chat.defaultAgentId ?? "none"),
|
|
95
|
+
"Allowed agents: " + (allowedAgentIds.length > 0 ? allowedAgentIds.join(", ") : "none"),
|
|
89
96
|
].join("\n");
|
|
90
97
|
}
|
|
91
98
|
|
|
@@ -102,8 +109,8 @@ export class BotCommandHandler {
|
|
|
102
109
|
return {
|
|
103
110
|
handled: true,
|
|
104
111
|
text: [
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
"Agent: " + sessionResult.agentId,
|
|
113
|
+
"Session model: " + (sessionResult.session.model ?? "default"),
|
|
107
114
|
choices.length > 0
|
|
108
115
|
? "Choose a model:"
|
|
109
116
|
: "No available Pi models found. Configure provider auth or use /model <provider/modelId> manually.",
|
|
@@ -121,7 +128,7 @@ export class BotCommandHandler {
|
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
const session = await this.repositories.sessions.updateModel(sessionResult.session.id, value);
|
|
124
|
-
return { handled: true, text:
|
|
131
|
+
return { handled: true, text: "Session model for " + session.agentId + " set to " + session.model + "." };
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
private async thinkingResult(message: InboundMessage): Promise<BotCommandResult> {
|
|
@@ -135,8 +142,8 @@ export class BotCommandHandler {
|
|
|
135
142
|
return {
|
|
136
143
|
handled: true,
|
|
137
144
|
text: [
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
"Agent: " + sessionResult.agentId,
|
|
146
|
+
"Session thinking level: " + (sessionResult.session.thinkingLevel ?? "default"),
|
|
140
147
|
"Choose thinking level:",
|
|
141
148
|
].join("\n"),
|
|
142
149
|
inlineKeyboard: this.thinkingKeyboard(),
|
|
@@ -152,7 +159,7 @@ export class BotCommandHandler {
|
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
const session = await this.repositories.sessions.updateThinkingLevel(sessionResult.session.id, value);
|
|
155
|
-
return { handled: true, text:
|
|
162
|
+
return { handled: true, text: "Session thinking level for " + session.agentId + " set to " + session.thinkingLevel + "." };
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
private async setModelFromChoice(message: InboundMessage): Promise<string> {
|
|
@@ -167,7 +174,7 @@ export class BotCommandHandler {
|
|
|
167
174
|
|
|
168
175
|
const choice = choices[choiceIndex];
|
|
169
176
|
const session = await this.repositories.sessions.updateModel(sessionResult.session.id, choice.id);
|
|
170
|
-
return
|
|
177
|
+
return "Session model for " + session.agentId + " set to " + choice.label + " (" + session.model + ").";
|
|
171
178
|
}
|
|
172
179
|
|
|
173
180
|
private async clearModel(message: InboundMessage): Promise<string> {
|
|
@@ -175,7 +182,7 @@ export class BotCommandHandler {
|
|
|
175
182
|
if (!sessionResult.ok) return sessionResult.message;
|
|
176
183
|
|
|
177
184
|
const session = await this.repositories.sessions.updateModel(sessionResult.session.id, null);
|
|
178
|
-
return
|
|
185
|
+
return "Session model cleared for " + session.agentId + ". Current: " + (session.model ?? "default");
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
private async setThinkingFromCallback(message: InboundMessage): Promise<string> {
|
|
@@ -188,7 +195,7 @@ export class BotCommandHandler {
|
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
const session = await this.repositories.sessions.updateThinkingLevel(sessionResult.session.id, value);
|
|
191
|
-
return
|
|
198
|
+
return "Session thinking level for " + session.agentId + " set to " + session.thinkingLevel + ".";
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
private async clearThinking(message: InboundMessage): Promise<string> {
|
|
@@ -196,7 +203,94 @@ export class BotCommandHandler {
|
|
|
196
203
|
if (!sessionResult.ok) return sessionResult.message;
|
|
197
204
|
|
|
198
205
|
const session = await this.repositories.sessions.updateThinkingLevel(sessionResult.session.id, null);
|
|
199
|
-
return
|
|
206
|
+
return "Session thinking level cleared for " + session.agentId + ". Current: " + (session.thinkingLevel ?? "default");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async taskResult(message: InboundMessage): Promise<BotCommandResult> {
|
|
210
|
+
if (!this.scheduler) {
|
|
211
|
+
return { handled: true, text: "Scheduler is not available." };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const parts = message.text.trim().split(/\s+/);
|
|
215
|
+
const subcommand = parts[1];
|
|
216
|
+
|
|
217
|
+
if (subcommand === "list") {
|
|
218
|
+
return await this.taskList(message);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (subcommand === "create") {
|
|
222
|
+
return await this.taskCreate(message, parts);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (subcommand === "remove") {
|
|
226
|
+
return await this.taskRemove(parts);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
handled: true,
|
|
231
|
+
text: [
|
|
232
|
+
"Usage:",
|
|
233
|
+
"/task list - list scheduled tasks",
|
|
234
|
+
"/task create <intervalMs> <prompt> - create a task",
|
|
235
|
+
"/task remove <id> - remove a task",
|
|
236
|
+
].join("\n"),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async taskList(message: InboundMessage): Promise<BotCommandResult> {
|
|
241
|
+
const tasks = this.scheduler!.getTasks().filter((t) => t.chatId === message.chatId || !t.chatId);
|
|
242
|
+
if (tasks.length === 0) return { handled: true, text: "No scheduled tasks for this chat." };
|
|
243
|
+
|
|
244
|
+
const lines = tasks.map((t) => {
|
|
245
|
+
const truncated = t.prompt.length > 50 ? t.prompt.slice(0, 50) + "..." : t.prompt;
|
|
246
|
+
return "- " + t.id + ": " + t.agent + " every " + t.intervalMs + 'ms "' + truncated + '"';
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return { handled: true, text: "Scheduled tasks:\n" + lines.join("\n") };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private async taskCreate(message: InboundMessage, parts: string[]): Promise<BotCommandResult> {
|
|
253
|
+
const intervalMs = Number(parts[2]);
|
|
254
|
+
if (!Number.isInteger(intervalMs) || intervalMs < 1000) {
|
|
255
|
+
return { handled: true, text: "Invalid interval. Use a number >= 1000 (milliseconds)." };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const prompt = parts.slice(3).join(" ").trim();
|
|
259
|
+
if (!prompt) {
|
|
260
|
+
return { handled: true, text: "Missing prompt. Usage: /task create <intervalMs> <prompt>" };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const chat = await this.repositories.telegram.findChat(message.chatId);
|
|
264
|
+
if (!chat?.defaultAgentId) {
|
|
265
|
+
return { handled: true, text: "No default agent configured for this chat. Set one first." };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const saved = await this.scheduler!.addDynamicTask({
|
|
269
|
+
agent: chat.defaultAgentId,
|
|
270
|
+
intervalMs,
|
|
271
|
+
prompt,
|
|
272
|
+
channel: "telegram",
|
|
273
|
+
chatId: message.chatId,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const msg = "Task created: " + saved.id + "\n"
|
|
277
|
+
+ "Agent: " + saved.agent + "\n"
|
|
278
|
+
+ "Interval: " + saved.intervalMs + "ms\n"
|
|
279
|
+
+ "Prompt: " + saved.prompt;
|
|
280
|
+
|
|
281
|
+
return { handled: true, text: msg };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private async taskRemove(parts: string[]): Promise<BotCommandResult> {
|
|
285
|
+
const taskId = parts[2];
|
|
286
|
+
if (!taskId) {
|
|
287
|
+
return { handled: true, text: "Missing task id. Usage: /task remove <id>" };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const removed = await this.scheduler!.removeDynamicTask(taskId);
|
|
291
|
+
if (!removed) return { handled: true, text: "Task not found: " + taskId };
|
|
292
|
+
|
|
293
|
+
return { handled: true, text: "Task removed: " + taskId };
|
|
200
294
|
}
|
|
201
295
|
|
|
202
296
|
private async loadModelChoices(): Promise<ModelChoiceConfig[]> {
|
|
@@ -209,7 +303,7 @@ export class BotCommandHandler {
|
|
|
209
303
|
const rows = chunk(
|
|
210
304
|
choices.map((choice, index) => ({
|
|
211
305
|
text: choice.label,
|
|
212
|
-
callbackData:
|
|
306
|
+
callbackData: "model:set:" + index,
|
|
213
307
|
})),
|
|
214
308
|
1,
|
|
215
309
|
);
|
|
@@ -250,11 +344,11 @@ export class BotCommandHandler {
|
|
|
250
344
|
}
|
|
251
345
|
|
|
252
346
|
if (!this.registry.hasAgent(agentId)) {
|
|
253
|
-
return { ok: false as const, message:
|
|
347
|
+
return { ok: false as const, message: "Unknown agent: " + agentId };
|
|
254
348
|
}
|
|
255
349
|
|
|
256
350
|
if (!(await this.repositories.telegram.isAgentAllowed(message.chatId, agentId))) {
|
|
257
|
-
return { ok: false as const, message:
|
|
351
|
+
return { ok: false as const, message: "Agent is not allowed in this chat: " + agentId };
|
|
258
352
|
}
|
|
259
353
|
|
|
260
354
|
const session = await this.repositories.sessions.getOrCreate({
|
package/src/channels/types.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type ChannelId = "telegram";
|
|
2
2
|
|
|
3
|
+
export type MessageSource = "user" | "task";
|
|
4
|
+
|
|
3
5
|
export type InboundMessage = {
|
|
4
6
|
id: string;
|
|
5
7
|
channel: ChannelId;
|
|
@@ -10,6 +12,7 @@ export type InboundMessage = {
|
|
|
10
12
|
text: string;
|
|
11
13
|
raw: unknown;
|
|
12
14
|
receivedAt: Date;
|
|
15
|
+
source?: MessageSource;
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
export type InlineKeyboardButton = {
|
package/src/config/loadConfig.ts
CHANGED
package/src/config/schemas.ts
CHANGED
|
@@ -11,10 +11,18 @@ export const PermissionConfigSchema = z.object({
|
|
|
11
11
|
blockedTools: z.array(z.string()).default([]),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
export const
|
|
15
|
-
|
|
14
|
+
export const TaskConfigSchema = z.object({
|
|
15
|
+
id: z.string().min(1),
|
|
16
|
+
agent: z.string().min(1),
|
|
16
17
|
intervalMs: z.number().int().positive().default(600_000),
|
|
17
18
|
prompt: z.string().min(1).default("If no useful action is needed, reply exactly: NOOP."),
|
|
19
|
+
channel: z.string().default("telegram"),
|
|
20
|
+
chatId: z.string().optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const SchedulerConfigSchema = z.object({
|
|
24
|
+
tickIntervalMs: z.number().int().positive().default(10_000),
|
|
25
|
+
tasks: z.array(TaskConfigSchema).default([]),
|
|
18
26
|
});
|
|
19
27
|
|
|
20
28
|
export const AgentTelegramChannelConfigSchema = z.object({
|
|
@@ -36,11 +44,6 @@ export const AgentConfigSchema = z.object({
|
|
|
36
44
|
skills: z.array(z.string()).default([]),
|
|
37
45
|
extensions: z.array(z.string()).default([]),
|
|
38
46
|
channels: AgentChannelsConfigSchema.default({ telegram: { enabled: false } }),
|
|
39
|
-
heartbeat: HeartbeatConfigSchema.default({
|
|
40
|
-
enabled: false,
|
|
41
|
-
intervalMs: 600_000,
|
|
42
|
-
prompt: "If no useful action is needed, reply exactly: NOOP.",
|
|
43
|
-
}),
|
|
44
47
|
permissions: PermissionConfigSchema.default({
|
|
45
48
|
canRunShell: false,
|
|
46
49
|
canEditFiles: false,
|
|
@@ -82,10 +85,12 @@ export const TelegramChatConfigSchema = z.object({
|
|
|
82
85
|
export const RootConfigSchema = z.object({
|
|
83
86
|
telegramChats: z.array(TelegramChatConfigSchema).default([]),
|
|
84
87
|
modelChoices: z.array(ModelChoiceConfigSchema).default([]),
|
|
88
|
+
scheduler: SchedulerConfigSchema.default({ tickIntervalMs: 10_000, tasks: [] }),
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
export type PermissionConfig = z.infer<typeof PermissionConfigSchema>;
|
|
88
|
-
export type
|
|
92
|
+
export type TaskConfig = z.infer<typeof TaskConfigSchema>;
|
|
93
|
+
export type SchedulerConfig = z.infer<typeof SchedulerConfigSchema>;
|
|
89
94
|
export type AgentConfig = z.infer<typeof AgentConfigSchema>;
|
|
90
95
|
export type ProfileConfig = z.infer<typeof ProfileConfigSchema>;
|
|
91
96
|
export type TelegramChatConfig = z.infer<typeof TelegramChatConfigSchema>;
|
|
@@ -102,4 +107,5 @@ export type LoadedConfig = {
|
|
|
102
107
|
profiles: ProfileConfig[];
|
|
103
108
|
telegramChats: TelegramChatConfig[];
|
|
104
109
|
modelChoices: ModelChoiceConfig[];
|
|
110
|
+
scheduler: SchedulerConfig;
|
|
105
111
|
};
|
|
@@ -9,6 +9,7 @@ import { loadConfig } from "../config/loadConfig";
|
|
|
9
9
|
import type { LoadedConfig } from "../config/schemas";
|
|
10
10
|
import { createRepositories, type Repositories } from "../db/repositories";
|
|
11
11
|
import { logger } from "../logging/logger";
|
|
12
|
+
import { Scheduler } from "./Scheduler";
|
|
12
13
|
|
|
13
14
|
export class AgentDaemon {
|
|
14
15
|
private running = false;
|
|
@@ -17,6 +18,7 @@ export class AgentDaemon {
|
|
|
17
18
|
private readonly registry: AgentRegistry;
|
|
18
19
|
private readonly router: MessageRouter;
|
|
19
20
|
private readonly runner: AgentRunner;
|
|
21
|
+
private readonly scheduler: Scheduler;
|
|
20
22
|
|
|
21
23
|
private constructor(
|
|
22
24
|
private readonly config: LoadedConfig,
|
|
@@ -24,9 +26,10 @@ export class AgentDaemon {
|
|
|
24
26
|
) {
|
|
25
27
|
this.adapters = createAdapters(repositories);
|
|
26
28
|
this.registry = new AgentRegistry(config, repositories);
|
|
27
|
-
this.commands = new BotCommandHandler(this.registry, repositories, config.modelChoices);
|
|
28
|
-
this.router = new MessageRouter(repositories, this.registry);
|
|
29
29
|
this.runner = new AgentRunner(this.registry, repositories);
|
|
30
|
+
this.scheduler = new Scheduler(config.scheduler, this.registry, repositories, this.adapters, this.runner);
|
|
31
|
+
this.commands = new BotCommandHandler(this.registry, repositories, config.modelChoices, this.scheduler);
|
|
32
|
+
this.router = new MessageRouter(repositories, this.registry);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
static async create(): Promise<AgentDaemon> {
|
|
@@ -45,6 +48,8 @@ export class AgentDaemon {
|
|
|
45
48
|
await adapter.start((message) => this.handleInboundMessage(message));
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
await this.scheduler.start();
|
|
52
|
+
|
|
48
53
|
logger.info("pigent daemon started", {
|
|
49
54
|
agents: this.registry.listAgents().length,
|
|
50
55
|
profiles: this.registry.listProfiles().length,
|
|
@@ -58,6 +63,8 @@ export class AgentDaemon {
|
|
|
58
63
|
|
|
59
64
|
this.running = false;
|
|
60
65
|
|
|
66
|
+
this.scheduler.stop();
|
|
67
|
+
|
|
61
68
|
for (const adapter of this.adapters) {
|
|
62
69
|
await adapter.stop();
|
|
63
70
|
}
|