@elizaos/plugin-agent-orchestrator 2.0.0-alpha.3 → 2.0.0-alpha.4

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/dist/index.js CHANGED
@@ -1,293 +1,556 @@
1
1
  // index.ts
2
2
  import { getSessionProviders as getSessionProviders2 } from "@elizaos/core";
3
3
 
4
- // src/actions/task-management.ts
5
- function getService(runtime) {
6
- const svc = runtime.getService("CODE_TASK");
7
- if (!svc) {
8
- throw new Error("AgentOrchestratorService not available (CODE_TASK)");
4
+ // src/actions/messaging.ts
5
+ function extractMessagingParams(message, _state) {
6
+ const params = message.content?.params;
7
+ if (!params) {
8
+ return {};
9
9
  }
10
- return svc;
11
- }
12
- function extractQuery(text) {
13
- return text.toLowerCase().replace(/\b(switch|select|go|change|search|find|pause|stop|halt|resume|restart|continue|start|run|begin|cancel|delete|remove|list|show|view)\b/g, "").replace(/\b(about|for|named|called|with|to|my|your|our|this|current)\b/g, "").replace(/\b(task|tasks|the|a|an)\b/g, "").replace(/\s+/g, " ").trim();
10
+ let target;
11
+ if (params.target) {
12
+ target = params.target;
13
+ } else {
14
+ target = {};
15
+ if (params.channel)
16
+ target.channel = params.channel;
17
+ if (params.to)
18
+ target.to = params.to;
19
+ if (params.threadId !== undefined)
20
+ target.threadId = params.threadId;
21
+ if (params.replyTo)
22
+ target.replyToMessageId = params.replyTo;
23
+ }
24
+ let content;
25
+ if (params.content) {
26
+ content = params.content;
27
+ } else {
28
+ content = {};
29
+ const text = params.text ?? message.content?.text;
30
+ if (text)
31
+ content.text = text;
32
+ }
33
+ return { target, content };
14
34
  }
15
- var createTaskAction = {
16
- name: "CREATE_TASK",
17
- similes: ["START_TASK", "SPAWN_TASK", "NEW_TASK", "BEGIN_TASK"],
18
- description: "Create an orchestrated background task to be executed by a selected agent provider.",
19
- validate: async (runtime, message) => {
20
- const svc = runtime.getService("CODE_TASK");
21
- if (!svc) {
35
+ var sendCrossPlatformMessageAction = {
36
+ name: "SEND_CROSS_PLATFORM_MESSAGE",
37
+ similes: [
38
+ "CROSS_PLATFORM_MESSAGE",
39
+ "UNIFIED_SEND",
40
+ "SEND_TO_CHANNEL",
41
+ "RELAY_MESSAGE",
42
+ "BROADCAST_MESSAGE"
43
+ ],
44
+ description: "Send a message to any supported platform (Discord, Telegram, Slack, WhatsApp, Twitch). " + "Requires specifying the target channel/platform and recipient.",
45
+ validate: async (runtime, message, _state) => {
46
+ const messagingService = runtime.getService("MESSAGING");
47
+ if (!messagingService) {
22
48
  return false;
23
49
  }
24
- const text = message.content.text?.toLowerCase() ?? "";
25
- const hasExplicit = text.includes("create task") || text.includes("new task") || text.includes("start a task");
26
- const hasIntent = text.includes("implement") || text.includes("build") || text.includes("create") || text.includes("develop") || text.includes("refactor") || text.includes("fix") || text.includes("add");
27
- return hasExplicit || hasIntent;
50
+ const params = message.content?.params;
51
+ if (!params) {
52
+ return true;
53
+ }
54
+ const hasTarget = params.target || params.channel && params.to;
55
+ const hasContent = params.content || params.text || message.content?.text;
56
+ return !!(hasTarget && hasContent);
28
57
  },
29
- handler: async (runtime, message, _state, options, callback) => {
30
- const svc = getService(runtime);
31
- const raw = message.content.text ?? "";
32
- const opts = options;
33
- const name = (opts?.title ?? raw.split(`
34
- `)[0] ?? "New Task").trim().slice(0, 100) || "New Task";
35
- const description = (opts?.description ?? raw).trim().slice(0, 4000) || name;
36
- const roomId = message.roomId;
37
- const task = await svc.createTask(name, description, roomId);
38
- const stepLines = Array.isArray(opts?.steps) ? opts?.steps : undefined;
39
- if (stepLines && stepLines.length > 0) {
40
- for (const s of stepLines) {
41
- const step = String(s).trim();
42
- if (!step)
43
- continue;
44
- await svc.addStep(task.id ?? "", step);
58
+ handler: async (runtime, message, state, _options, callback) => {
59
+ const messagingService = runtime.getService("MESSAGING");
60
+ if (!messagingService) {
61
+ if (callback) {
62
+ await callback({
63
+ text: "Messaging service is not available. Please ensure the orchestrator plugin is properly configured."
64
+ });
45
65
  }
46
- await svc.appendOutput(task.id ?? "", `Plan:
47
- ${stepLines.map((s, i) => `${i + 1}. ${s}`).join(`
48
- `)}`);
66
+ return { success: false, error: "Messaging service not available" };
49
67
  }
50
- const msg = `Created task: ${task.name}
51
- Provider: ${task.metadata.providerLabel ?? task.metadata.providerId}
52
- Starting execution…`;
53
- await callback?.({ content: { text: msg } });
54
- svc.startTaskExecution(task.id ?? "").catch((err) => {
55
- const errorMsg = err instanceof Error ? err.message : String(err);
56
- svc.appendOutput(task.id ?? "", `Execution error: ${errorMsg}`).catch(() => {});
57
- });
58
- return { success: true, text: msg, data: { taskId: task.id ?? "" } };
59
- }
60
- };
61
- var listTasksAction = {
62
- name: "LIST_TASKS",
63
- similes: ["SHOW_TASKS", "GET_TASKS", "TASKS", "VIEW_TASKS"],
64
- description: "List tasks managed by the orchestrator.",
65
- validate: async (runtime, message) => {
66
- const svc = runtime.getService("CODE_TASK");
67
- if (!svc) {
68
- return false;
68
+ const { target, content } = extractMessagingParams(message, state);
69
+ if (!target?.channel) {
70
+ if (callback) {
71
+ await callback({
72
+ text: "Please specify the target channel (discord, telegram, slack, whatsapp, twitch)."
73
+ });
74
+ }
75
+ return { success: false, error: "Missing target channel" };
69
76
  }
70
- const t = message.content.text?.toLowerCase() ?? "";
71
- return t.includes("list task") || t.includes("show task") || t === "tasks" || t.includes("my task");
72
- },
73
- handler: async (runtime, _message, _state, _options, callback) => {
74
- const svc = getService(runtime);
75
- const tasks = await svc.getRecentTasks(20);
76
- if (tasks.length === 0) {
77
- const msg2 = "No tasks.";
78
- await callback?.({ content: { text: msg2 } });
79
- return { success: true, text: msg2 };
77
+ if (!target?.to) {
78
+ if (callback) {
79
+ await callback({
80
+ text: "Please specify the recipient (channel ID, chat ID, or room ID)."
81
+ });
82
+ }
83
+ return { success: false, error: "Missing recipient" };
80
84
  }
81
- const lines = ["Tasks:"];
82
- const current = svc.getCurrentTaskId();
83
- for (const t of tasks) {
84
- const marker = t.id === current ? " (current)" : "";
85
- lines.push(`- ${t.name} — ${t.metadata.status} ${t.metadata.progress}%${marker}`);
85
+ if (!content?.text) {
86
+ if (callback) {
87
+ await callback({
88
+ text: "Please provide the message text to send."
89
+ });
90
+ }
91
+ return { success: false, error: "Missing message text" };
86
92
  }
87
- const msg = lines.join(`
88
- `);
89
- await callback?.({ content: { text: msg } });
90
- return { success: true, text: msg };
91
- }
93
+ const sendTarget = {
94
+ channel: target.channel,
95
+ to: target.to
96
+ };
97
+ if (target.threadId !== undefined)
98
+ sendTarget.threadId = target.threadId;
99
+ if (target.replyToMessageId)
100
+ sendTarget.replyToMessageId = target.replyToMessageId;
101
+ const sendContent = {
102
+ text: content.text
103
+ };
104
+ if (content.attachments)
105
+ sendContent.attachments = content.attachments;
106
+ if (content.embed)
107
+ sendContent.embed = content.embed;
108
+ if (content.buttons)
109
+ sendContent.buttons = content.buttons;
110
+ if (content.disableLinkPreview !== undefined)
111
+ sendContent.disableLinkPreview = content.disableLinkPreview;
112
+ if (content.silent !== undefined)
113
+ sendContent.silent = content.silent;
114
+ const result = await messagingService.send({
115
+ target: sendTarget,
116
+ content: sendContent
117
+ });
118
+ if (callback) {
119
+ if (result.success) {
120
+ const callbackData = {};
121
+ if (result.messageId)
122
+ callbackData.messageId = result.messageId;
123
+ if (result.sentAt)
124
+ callbackData.sentAt = result.sentAt;
125
+ await callback({
126
+ text: `Message sent successfully to ${target.channel}.`,
127
+ data: callbackData
128
+ });
129
+ } else {
130
+ await callback({
131
+ text: `Failed to send message: ${result.error}`
132
+ });
133
+ }
134
+ }
135
+ return {
136
+ success: result.success,
137
+ data: result,
138
+ ...result.error !== undefined ? { error: result.error } : {}
139
+ };
140
+ },
141
+ examples: [
142
+ [
143
+ {
144
+ name: "{{name1}}",
145
+ content: {
146
+ text: "Send 'Hello from the agent!' to Discord channel 123456789",
147
+ params: {
148
+ channel: "discord",
149
+ to: "123456789",
150
+ text: "Hello from the agent!"
151
+ }
152
+ }
153
+ },
154
+ {
155
+ name: "{{agentName}}",
156
+ content: {
157
+ text: "Message sent successfully to Discord.",
158
+ actions: ["SEND_CROSS_PLATFORM_MESSAGE"]
159
+ }
160
+ }
161
+ ],
162
+ [
163
+ {
164
+ name: "{{name1}}",
165
+ content: {
166
+ text: "Send a Telegram message",
167
+ params: {
168
+ channel: "telegram",
169
+ to: "-1001234567890",
170
+ text: "Automated notification from your AI assistant."
171
+ }
172
+ }
173
+ },
174
+ {
175
+ name: "{{agentName}}",
176
+ content: {
177
+ text: "Message sent successfully to Telegram.",
178
+ actions: ["SEND_CROSS_PLATFORM_MESSAGE"]
179
+ }
180
+ }
181
+ ]
182
+ ]
92
183
  };
93
- var switchTaskAction = {
94
- name: "SWITCH_TASK",
95
- similes: ["SELECT_TASK", "SET_TASK", "CHANGE_TASK", "GO_TO_TASK"],
96
- description: "Switch the current task context to a different task.",
97
- validate: async (runtime, message) => {
98
- const svc = runtime.getService("CODE_TASK");
99
- if (!svc) {
184
+ var sendToDeliveryContextAction = {
185
+ name: "SEND_TO_DELIVERY_CONTEXT",
186
+ similes: ["DELIVER_MESSAGE", "SEND_TO_CONTEXT", "ROUTE_MESSAGE"],
187
+ description: "Send a message using a delivery context that specifies the target channel and recipient. " + "Useful for routing messages back to the original requester or to a specific context.",
188
+ validate: async (runtime, message, _state) => {
189
+ const messagingService = runtime.getService("MESSAGING");
190
+ if (!messagingService) {
100
191
  return false;
101
192
  }
102
- const t = message.content.text?.toLowerCase() ?? "";
103
- return t.includes("switch to task") || t.includes("select task") || t.includes("task") && t.includes("switch");
193
+ const params = message.content?.params;
194
+ if (!params?.deliveryContext) {
195
+ return false;
196
+ }
197
+ const hasText = params.text || message.content?.text;
198
+ return !!hasText;
104
199
  },
105
200
  handler: async (runtime, message, _state, _options, callback) => {
106
- const svc = getService(runtime);
107
- const query = extractQuery(message.content.text ?? "");
108
- if (!query) {
109
- const msg2 = "Please specify which task to switch to (by name or id).";
110
- await callback?.({ content: { text: msg2 } });
111
- return { success: false, text: msg2 };
201
+ const messagingService = runtime.getService("MESSAGING");
202
+ if (!messagingService) {
203
+ if (callback) {
204
+ await callback({
205
+ text: "Messaging service is not available."
206
+ });
207
+ }
208
+ return { success: false, error: "Messaging service not available" };
112
209
  }
113
- const matches = await svc.searchTasks(query);
114
- const chosen = matches[0];
115
- if (!chosen?.id) {
116
- const msg2 = `No task found matching: "${query}"`;
117
- await callback?.({ content: { text: msg2 } });
118
- return { success: false, text: msg2 };
210
+ const params = message.content?.params;
211
+ const deliveryContext = params?.deliveryContext;
212
+ const text = params?.text ?? message.content?.text;
213
+ if (!deliveryContext) {
214
+ if (callback) {
215
+ await callback({
216
+ text: "Please provide a delivery context with channel and recipient information."
217
+ });
218
+ }
219
+ return { success: false, error: "Missing delivery context" };
119
220
  }
120
- svc.setCurrentTask(chosen.id);
121
- const msg = `Switched to task: ${chosen.name}`;
122
- await callback?.({ content: { text: msg } });
123
- return { success: true, text: msg, data: { taskId: chosen.id } };
124
- }
125
- };
126
- var searchTasksAction = {
127
- name: "SEARCH_TASKS",
128
- similes: ["FIND_TASK", "LOOKUP_TASK"],
129
- description: "Search tasks by query.",
130
- validate: async (runtime, message) => {
131
- const svc = runtime.getService("CODE_TASK");
132
- if (!svc) {
133
- return false;
134
- }
135
- const t = message.content.text?.toLowerCase() ?? "";
136
- return t.includes("search task") || t.includes("find task") || t.includes("look for task");
137
- },
138
- handler: async (runtime, message, _state, options, callback) => {
139
- const svc = getService(runtime);
140
- const opt = options;
141
- const query = (opt?.query ?? extractQuery(message.content.text ?? "")).trim();
142
- if (!query) {
143
- const msg2 = "What would you like to search for?";
144
- await callback?.({ content: { text: msg2 } });
145
- return { success: false, text: msg2 };
146
- }
147
- const matches = await svc.searchTasks(query);
148
- if (matches.length === 0) {
149
- const msg2 = `No tasks found matching: "${query}"`;
150
- await callback?.({ content: { text: msg2 } });
151
- return { success: true, text: msg2 };
221
+ if (!text) {
222
+ if (callback) {
223
+ await callback({
224
+ text: "Please provide the message text to send."
225
+ });
226
+ }
227
+ return { success: false, error: "Missing message text" };
152
228
  }
153
- const lines = [`Found ${matches.length} task(s) matching "${query}":`];
154
- for (const t of matches.slice(0, 10)) {
155
- lines.push(`- ${t.name} — ${t.metadata.status} ${t.metadata.progress}%`);
229
+ const result = await messagingService.sendToDeliveryContext(deliveryContext, {
230
+ text
231
+ });
232
+ if (callback) {
233
+ if (result.success) {
234
+ const callbackData = {};
235
+ if (result.messageId)
236
+ callbackData.messageId = result.messageId;
237
+ await callback({
238
+ text: `Message delivered successfully via ${result.channel}.`,
239
+ data: callbackData
240
+ });
241
+ } else {
242
+ await callback({
243
+ text: `Failed to deliver message: ${result.error}`
244
+ });
245
+ }
156
246
  }
157
- const msg = lines.join(`
158
- `);
159
- await callback?.({ content: { text: msg } });
160
- return { success: true, text: msg };
161
- }
247
+ return {
248
+ success: result.success,
249
+ data: result,
250
+ ...result.error !== undefined ? { error: result.error } : {}
251
+ };
252
+ },
253
+ examples: [
254
+ [
255
+ {
256
+ name: "{{name1}}",
257
+ content: {
258
+ text: "Send result to delivery context",
259
+ params: {
260
+ deliveryContext: {
261
+ channel: "discord",
262
+ to: "123456789"
263
+ },
264
+ text: "Task completed successfully!"
265
+ }
266
+ }
267
+ },
268
+ {
269
+ name: "{{agentName}}",
270
+ content: {
271
+ text: "Message delivered successfully via discord.",
272
+ actions: ["SEND_TO_DELIVERY_CONTEXT"]
273
+ }
274
+ }
275
+ ]
276
+ ]
162
277
  };
163
- var pauseTaskAction = {
164
- name: "PAUSE_TASK",
165
- similes: ["STOP_TASK", "HALT_TASK"],
166
- description: "Pause a running task.",
167
- validate: async (runtime, message) => {
168
- const svc = runtime.getService("CODE_TASK");
169
- if (!svc) {
278
+ var sendToRoomAction = {
279
+ name: "SEND_TO_ROOM",
280
+ similes: ["MESSAGE_ROOM", "ROOM_MESSAGE", "NOTIFY_ROOM"],
281
+ description: "Send a message to an Eliza room. The room's metadata determines which platform and recipient to use.",
282
+ validate: async (runtime, message, _state) => {
283
+ const messagingService = runtime.getService("MESSAGING");
284
+ if (!messagingService) {
170
285
  return false;
171
286
  }
172
- const t = message.content.text?.toLowerCase() ?? "";
173
- return (t.includes("pause") || t.includes("stop") || t.includes("halt")) && t.includes("task");
174
- },
175
- handler: async (runtime, message, _state, _options, callback) => {
176
- const svc = getService(runtime);
177
- const query = extractQuery(message.content.text ?? "");
178
- const task = query ? (await svc.searchTasks(query))[0] : await svc.getCurrentTask();
179
- if (!task?.id) {
180
- const msg2 = "No task to pause.";
181
- await callback?.({ content: { text: msg2 } });
182
- return { success: false, text: msg2 };
183
- }
184
- await svc.pauseTask(task.id);
185
- const msg = `Paused task: ${task.name}`;
186
- await callback?.({ content: { text: msg } });
187
- return { success: true, text: msg };
188
- }
189
- };
190
- var resumeTaskAction = {
191
- name: "RESUME_TASK",
192
- similes: ["CONTINUE_TASK", "RESTART_TASK", "RUN_TASK"],
193
- description: "Resume a paused task.",
194
- validate: async (runtime, message) => {
195
- const svc = runtime.getService("CODE_TASK");
196
- if (!svc) {
287
+ const params = message.content?.params;
288
+ if (!params?.roomId) {
197
289
  return false;
198
290
  }
199
- const t = message.content.text?.toLowerCase() ?? "";
200
- return t.includes("task") && (t.includes("resume") || t.includes("restart") || t.includes("continue"));
291
+ const hasText = params.text || message.content?.text;
292
+ return !!hasText;
201
293
  },
202
294
  handler: async (runtime, message, _state, _options, callback) => {
203
- const svc = getService(runtime);
204
- const query = extractQuery(message.content.text ?? "");
205
- const task = query ? (await svc.searchTasks(query))[0] : await svc.getCurrentTask();
206
- if (!task?.id) {
207
- const msg2 = "No task to resume.";
208
- await callback?.({ content: { text: msg2 } });
209
- return { success: false, text: msg2 };
295
+ const messagingService = runtime.getService("MESSAGING");
296
+ if (!messagingService) {
297
+ if (callback) {
298
+ await callback({
299
+ text: "Messaging service is not available."
300
+ });
301
+ }
302
+ return { success: false, error: "Messaging service not available" };
210
303
  }
211
- await svc.resumeTask(task.id);
212
- svc.startTaskExecution(task.id).catch((err) => {
213
- const errorMsg = err instanceof Error ? err.message : String(err);
214
- svc.appendOutput(task.id, `Execution error: ${errorMsg}`).catch(() => {});
215
- });
216
- const msg = `Resumed task: ${task.name}`;
217
- await callback?.({ content: { text: msg } });
218
- return { success: true, text: msg };
219
- }
304
+ const params = message.content?.params;
305
+ const roomId = params?.roomId;
306
+ const text = params?.text ?? message.content?.text;
307
+ if (!roomId) {
308
+ if (callback) {
309
+ await callback({
310
+ text: "Please specify the room ID to send the message to."
311
+ });
312
+ }
313
+ return { success: false, error: "Missing room ID" };
314
+ }
315
+ if (!text) {
316
+ if (callback) {
317
+ await callback({
318
+ text: "Please provide the message text to send."
319
+ });
320
+ }
321
+ return { success: false, error: "Missing message text" };
322
+ }
323
+ const result = await messagingService.sendToRoom(roomId, { text });
324
+ if (callback) {
325
+ if (result.success) {
326
+ const callbackData = {};
327
+ if (result.messageId)
328
+ callbackData.messageId = result.messageId;
329
+ await callback({
330
+ text: `Message sent to room via ${result.channel}.`,
331
+ data: callbackData
332
+ });
333
+ } else {
334
+ await callback({
335
+ text: `Failed to send to room: ${result.error}`
336
+ });
337
+ }
338
+ }
339
+ return {
340
+ success: result.success,
341
+ data: result,
342
+ ...result.error !== undefined ? { error: result.error } : {}
343
+ };
344
+ },
345
+ examples: [
346
+ [
347
+ {
348
+ name: "{{name1}}",
349
+ content: {
350
+ text: "Send to room",
351
+ params: {
352
+ roomId: "550e8400-e29b-41d4-a716-446655440000",
353
+ text: "Hello, room!"
354
+ }
355
+ }
356
+ },
357
+ {
358
+ name: "{{agentName}}",
359
+ content: {
360
+ text: "Message sent to room via discord.",
361
+ actions: ["SEND_TO_ROOM"]
362
+ }
363
+ }
364
+ ]
365
+ ]
220
366
  };
221
- var cancelTaskAction = {
222
- name: "CANCEL_TASK",
223
- similes: ["DELETE_TASK", "REMOVE_TASK", "ABORT_TASK"],
224
- description: "Cancel a task.",
225
- validate: async (runtime, message) => {
226
- const svc = runtime.getService("CODE_TASK");
227
- if (!svc) {
367
+ var sendToSessionMessageAction = {
368
+ name: "SEND_TO_SESSION_MESSAGE",
369
+ similes: ["SESSION_MESSAGE", "MESSAGE_SESSION", "NOTIFY_SESSION"],
370
+ description: "Send a message to a session by its session key. The session key is mapped to an Eliza room.",
371
+ validate: async (runtime, message, _state) => {
372
+ const messagingService = runtime.getService("MESSAGING");
373
+ if (!messagingService) {
228
374
  return false;
229
375
  }
230
- const t = message.content.text?.toLowerCase() ?? "";
231
- return (t.includes("cancel") || t.includes("delete") || t.includes("remove")) && t.includes("task");
376
+ const params = message.content?.params;
377
+ if (!params?.sessionKey) {
378
+ return false;
379
+ }
380
+ const hasText = params.text || message.content?.text;
381
+ return !!hasText;
232
382
  },
233
383
  handler: async (runtime, message, _state, _options, callback) => {
234
- const svc = getService(runtime);
235
- const query = extractQuery(message.content.text ?? "");
236
- const task = query ? (await svc.searchTasks(query))[0] : await svc.getCurrentTask();
237
- if (!task?.id) {
238
- const msg2 = "No task to cancel.";
239
- await callback?.({ content: { text: msg2 } });
240
- return { success: false, text: msg2 };
384
+ const messagingService = runtime.getService("MESSAGING");
385
+ if (!messagingService) {
386
+ if (callback) {
387
+ await callback({
388
+ text: "Messaging service is not available."
389
+ });
390
+ }
391
+ return { success: false, error: "Messaging service not available" };
241
392
  }
242
- await svc.cancelTask(task.id);
243
- const msg = `Cancelled task: ${task.name}`;
244
- await callback?.({ content: { text: msg } });
245
- return { success: true, text: msg };
246
- }
247
- };
248
-
249
- // src/utils/session.ts
250
- import crypto from "node:crypto";
251
- import {
252
- buildAgentMainSessionKey,
253
- buildAgentSessionKey,
254
- buildAgentPeerSessionKey,
255
- buildAcpSessionKey,
256
- buildSubagentSessionKey,
257
- parseAgentSessionKey,
258
- isAcpSessionKey,
393
+ const params = message.content?.params;
394
+ const sessionKey = params?.sessionKey;
395
+ const text = params?.text ?? message.content?.text;
396
+ if (!sessionKey) {
397
+ if (callback) {
398
+ await callback({
399
+ text: "Please specify the session key to send the message to."
400
+ });
401
+ }
402
+ return { success: false, error: "Missing session key" };
403
+ }
404
+ if (!text) {
405
+ if (callback) {
406
+ await callback({
407
+ text: "Please provide the message text to send."
408
+ });
409
+ }
410
+ return { success: false, error: "Missing message text" };
411
+ }
412
+ const result = await messagingService.sendToSession(sessionKey, { text });
413
+ if (callback) {
414
+ if (result.success) {
415
+ const callbackData = {};
416
+ if (result.messageId)
417
+ callbackData.messageId = result.messageId;
418
+ await callback({
419
+ text: `Message sent to session via ${result.channel}.`,
420
+ data: callbackData
421
+ });
422
+ } else {
423
+ await callback({
424
+ text: `Failed to send to session: ${result.error}`
425
+ });
426
+ }
427
+ }
428
+ return {
429
+ success: result.success,
430
+ data: result,
431
+ ...result.error !== undefined ? { error: result.error } : {}
432
+ };
433
+ },
434
+ examples: [
435
+ [
436
+ {
437
+ name: "{{name1}}",
438
+ content: {
439
+ text: "Send to session",
440
+ params: {
441
+ sessionKey: "agent:main:dm:user123",
442
+ text: "Update from your subagent!"
443
+ }
444
+ }
445
+ },
446
+ {
447
+ name: "{{agentName}}",
448
+ content: {
449
+ text: "Message sent to session via discord.",
450
+ actions: ["SEND_TO_SESSION_MESSAGE"]
451
+ }
452
+ }
453
+ ]
454
+ ]
455
+ };
456
+ var listMessagingChannelsAction = {
457
+ name: "LIST_MESSAGING_CHANNELS",
458
+ similes: ["AVAILABLE_CHANNELS", "GET_CHANNELS", "MESSAGING_PLATFORMS"],
459
+ description: "List all available messaging channels/platforms that the agent can send messages to.",
460
+ validate: async (runtime, _message, _state) => {
461
+ const messagingService = runtime.getService("MESSAGING");
462
+ return !!messagingService;
463
+ },
464
+ handler: async (runtime, _message, _state, _options, callback) => {
465
+ const messagingService = runtime.getService("MESSAGING");
466
+ if (!messagingService) {
467
+ if (callback) {
468
+ await callback({
469
+ text: "Messaging service is not available."
470
+ });
471
+ }
472
+ return { success: false, error: "Messaging service not available" };
473
+ }
474
+ const channels = messagingService.getAvailableChannels();
475
+ if (callback) {
476
+ if (channels.length > 0) {
477
+ await callback({
478
+ text: `Available messaging channels: ${channels.join(", ")}`,
479
+ data: { channels }
480
+ });
481
+ } else {
482
+ await callback({
483
+ text: "No messaging channels are currently available.",
484
+ data: { channels: [] }
485
+ });
486
+ }
487
+ }
488
+ return {
489
+ success: true,
490
+ data: { channels }
491
+ };
492
+ },
493
+ examples: [
494
+ [
495
+ {
496
+ name: "{{name1}}",
497
+ content: {
498
+ text: "What messaging platforms can you use?"
499
+ }
500
+ },
501
+ {
502
+ name: "{{agentName}}",
503
+ content: {
504
+ text: "Available messaging channels: discord, telegram, internal",
505
+ actions: ["LIST_MESSAGING_CHANNELS"]
506
+ }
507
+ }
508
+ ]
509
+ ]
510
+ };
511
+
512
+ // src/utils/session.ts
513
+ import crypto from "node:crypto";
514
+ import {
515
+ buildAcpSessionKey,
516
+ buildAgentMainSessionKey,
517
+ buildAgentPeerSessionKey,
518
+ buildAgentSessionKey,
519
+ buildGroupHistoryKey,
520
+ buildSubagentSessionKey,
521
+ createSendPolicyProvider,
522
+ createSessionEntry,
523
+ createSessionProvider,
524
+ createSessionSkillsProvider,
525
+ deleteSessionEntry,
526
+ extractSessionContext,
527
+ getSessionEntry,
528
+ getSessionProviders,
529
+ isAcpSessionKey,
259
530
  isSubagentSessionKey,
531
+ isValidSessionEntry,
532
+ listSessionKeys,
533
+ loadSessionStore,
534
+ mergeSessionEntry,
535
+ normalizeAccountId,
260
536
  normalizeAgentId,
261
537
  normalizeMainKey,
262
- normalizeAccountId,
263
- toAgentRequestSessionKey,
264
- toAgentStoreSessionKey,
538
+ parseAgentSessionKey,
265
539
  resolveAgentIdFromSessionKey,
266
- resolveThreadParentSessionKey,
267
- resolveThreadSessionKeys,
268
- buildGroupHistoryKey,
269
- loadSessionStore,
270
- saveSessionStore,
271
- updateSessionStore,
272
- updateSessionStoreEntry,
273
- getSessionEntry,
274
- upsertSessionEntry,
275
- deleteSessionEntry,
276
- listSessionKeys,
277
- resolveStateDir,
278
540
  resolveAgentSessionsDir,
279
541
  resolveDefaultSessionStorePath,
280
542
  resolveSessionTranscriptPath,
543
+ resolveStateDir,
281
544
  resolveStorePath,
282
- createSessionProvider,
283
- createSessionSkillsProvider,
284
- createSendPolicyProvider,
285
- getSessionProviders,
286
- extractSessionContext,
545
+ resolveThreadParentSessionKey,
546
+ resolveThreadSessionKeys,
287
547
  SessionStateManager,
288
- createSessionEntry,
289
- mergeSessionEntry,
290
- isValidSessionEntry
548
+ saveSessionStore,
549
+ toAgentRequestSessionKey,
550
+ toAgentStoreSessionKey,
551
+ updateSessionStore,
552
+ updateSessionStoreEntry,
553
+ upsertSessionEntry
291
554
  } from "@elizaos/core";
292
555
  function hashToUUID(input) {
293
556
  const hash = crypto.createHash("sha256").update(input).digest("hex");
@@ -405,25 +668,37 @@ function normalizeDeliveryContext(context) {
405
668
  }
406
669
  const result = {};
407
670
  if (context.channel && typeof context.channel === "string") {
408
- result.channel = context.channel.trim() || undefined;
671
+ const trimmed = context.channel.trim();
672
+ if (trimmed)
673
+ result.channel = trimmed;
409
674
  }
410
675
  if (context.accountId && typeof context.accountId === "string") {
411
- result.accountId = context.accountId.trim() || undefined;
676
+ const trimmed = context.accountId.trim();
677
+ if (trimmed)
678
+ result.accountId = trimmed;
412
679
  }
413
680
  if (context.to && typeof context.to === "string") {
414
- result.to = context.to.trim() || undefined;
681
+ const trimmed = context.to.trim();
682
+ if (trimmed)
683
+ result.to = trimmed;
415
684
  }
416
685
  if (context.threadId !== undefined && context.threadId !== null) {
417
686
  result.threadId = context.threadId;
418
687
  }
419
688
  if (context.groupId && typeof context.groupId === "string") {
420
- result.groupId = context.groupId.trim() || undefined;
689
+ const trimmed = context.groupId.trim();
690
+ if (trimmed)
691
+ result.groupId = trimmed;
421
692
  }
422
693
  if (context.groupChannel && typeof context.groupChannel === "string") {
423
- result.groupChannel = context.groupChannel.trim() || undefined;
694
+ const trimmed = context.groupChannel.trim();
695
+ if (trimmed)
696
+ result.groupChannel = trimmed;
424
697
  }
425
698
  if (context.groupSpace && typeof context.groupSpace === "string") {
426
- result.groupSpace = context.groupSpace.trim() || undefined;
699
+ const trimmed = context.groupSpace.trim();
700
+ if (trimmed)
701
+ result.groupSpace = trimmed;
427
702
  }
428
703
  const hasValues = Object.values(result).some((v) => v !== undefined && v !== null);
429
704
  return hasValues ? result : undefined;
@@ -438,15 +713,29 @@ function mergeDeliveryContext(primary, secondary) {
438
713
  if (!secondary) {
439
714
  return normalizeDeliveryContext(primary);
440
715
  }
441
- return normalizeDeliveryContext({
442
- channel: primary.channel || secondary.channel,
443
- accountId: primary.accountId || secondary.accountId,
444
- to: primary.to || secondary.to,
445
- threadId: primary.threadId ?? secondary.threadId,
446
- groupId: primary.groupId || secondary.groupId,
447
- groupChannel: primary.groupChannel || secondary.groupChannel,
448
- groupSpace: primary.groupSpace || secondary.groupSpace
449
- });
716
+ const merged = {};
717
+ const ch = primary.channel || secondary.channel;
718
+ if (ch)
719
+ merged.channel = ch;
720
+ const acct = primary.accountId || secondary.accountId;
721
+ if (acct)
722
+ merged.accountId = acct;
723
+ const to = primary.to || secondary.to;
724
+ if (to)
725
+ merged.to = to;
726
+ const tid = primary.threadId ?? secondary.threadId;
727
+ if (tid !== undefined)
728
+ merged.threadId = tid;
729
+ const gid = primary.groupId || secondary.groupId;
730
+ if (gid)
731
+ merged.groupId = gid;
732
+ const gch = primary.groupChannel || secondary.groupChannel;
733
+ if (gch)
734
+ merged.groupChannel = gch;
735
+ const gsp = primary.groupSpace || secondary.groupSpace;
736
+ if (gsp)
737
+ merged.groupSpace = gsp;
738
+ return normalizeDeliveryContext(merged);
450
739
  }
451
740
  function formatDurationShort(ms) {
452
741
  if (!ms || !Number.isFinite(ms) || ms <= 0) {
@@ -485,23 +774,19 @@ function getSubagentService(runtime) {
485
774
  }
486
775
  return svc;
487
776
  }
488
- function extractSessionContext2(runtime, message) {
777
+ function extractSessionContext2(_runtime, message) {
489
778
  const metadata = message.content?.metadata;
490
779
  const sessionKey = typeof metadata?.sessionKey === "string" ? metadata.sessionKey : undefined;
491
- return {
492
- sessionKey,
493
- roomId: message.roomId
494
- };
780
+ const result = {};
781
+ if (sessionKey)
782
+ result.sessionKey = sessionKey;
783
+ if (message.roomId)
784
+ result.roomId = message.roomId;
785
+ return result;
495
786
  }
496
787
  var spawnSubagentAction = {
497
788
  name: "SPAWN_SUBAGENT",
498
- similes: [
499
- "SPAWN_TASK",
500
- "BACKGROUND_TASK",
501
- "START_SUBAGENT",
502
- "SESSIONS_SPAWN",
503
- "CREATE_SUBAGENT"
504
- ],
789
+ similes: ["SPAWN_TASK", "BACKGROUND_TASK", "START_SUBAGENT", "SESSIONS_SPAWN", "CREATE_SUBAGENT"],
505
790
  description: "Spawn a background sub-agent run to execute a task asynchronously. The subagent will complete the task and announce results back.",
506
791
  validate: async (runtime, message) => {
507
792
  const svc = runtime.getService("SUBAGENT");
@@ -523,18 +808,20 @@ var spawnSubagentAction = {
523
808
  await callback?.({ content: { text: msg2 } });
524
809
  return { success: false, text: msg2 };
525
810
  }
526
- const result = await svc.spawnSubagent({
811
+ const spawnParams = {
527
812
  task,
528
- label: opts?.label,
529
- agentId: opts?.agentId,
530
- model: opts?.model,
531
- thinking: opts?.thinking,
532
813
  runTimeoutSeconds: opts?.timeoutSeconds ?? 300,
533
814
  cleanup: opts?.cleanup ?? "keep"
534
- }, {
535
- sessionKey: context.sessionKey,
536
- roomId: context.roomId
537
- });
815
+ };
816
+ if (opts?.label)
817
+ spawnParams.label = opts.label;
818
+ if (opts?.agentId)
819
+ spawnParams.agentId = opts.agentId;
820
+ if (opts?.model)
821
+ spawnParams.model = opts.model;
822
+ if (opts?.thinking)
823
+ spawnParams.thinking = opts.thinking;
824
+ const result = await svc.spawnSubagent(spawnParams, context);
538
825
  if (result.status !== "accepted") {
539
826
  const msg2 = `Failed to spawn subagent: ${result.error ?? "unknown error"}`;
540
827
  await callback?.({ content: { text: msg2 } });
@@ -582,16 +869,17 @@ var sendToSessionAction = {
582
869
  await callback?.({ content: { text: msg2 } });
583
870
  return { success: false, text: msg2 };
584
871
  }
585
- const result = await svc.sendToAgent({
586
- sessionKey: opts?.sessionKey,
587
- label: opts?.label,
588
- agentId: opts?.agentId,
872
+ const sendParams = {
589
873
  message: targetMessage,
590
874
  timeoutSeconds: opts?.timeoutSeconds ?? 30
591
- }, {
592
- sessionKey: context.sessionKey,
593
- roomId: context.roomId
594
- });
875
+ };
876
+ if (opts?.sessionKey)
877
+ sendParams.sessionKey = opts.sessionKey;
878
+ if (opts?.label)
879
+ sendParams.label = opts.label;
880
+ if (opts?.agentId)
881
+ sendParams.agentId = opts.agentId;
882
+ const result = await svc.sendToAgent(sendParams, context);
595
883
  if (result.status === "forbidden") {
596
884
  const msg2 = `Access denied: ${result.error}`;
597
885
  await callback?.({ content: { text: msg2 } });
@@ -673,7 +961,7 @@ var cancelSubagentAction = {
673
961
  const text = message.content.text?.toLowerCase() ?? "";
674
962
  return (text.includes("cancel") || text.includes("stop") || text.includes("abort")) && (text.includes("subagent") || text.includes("background"));
675
963
  },
676
- handler: async (runtime, message, _state, options, callback) => {
964
+ handler: async (runtime, _message, _state, options, callback) => {
677
965
  const svc = getSubagentService(runtime);
678
966
  const opts = options;
679
967
  const runId = opts?.runId;
@@ -705,7 +993,7 @@ var getSubagentStatusAction = {
705
993
  const text = message.content.text?.toLowerCase() ?? "";
706
994
  return text.includes("subagent status") || text.includes("task status") || text.includes("check subagent") || text.includes("subagent info");
707
995
  },
708
- handler: async (runtime, message, _state, options, callback) => {
996
+ handler: async (runtime, _message, _state, options, callback) => {
709
997
  const svc = getSubagentService(runtime);
710
998
  const opts = options;
711
999
  const runId = opts?.runId;
@@ -739,491 +1027,250 @@ var getSubagentStatusAction = {
739
1027
  }
740
1028
  };
741
1029
 
742
- // src/actions/messaging.ts
743
- function extractMessagingParams(message, state) {
744
- const params = message.content?.params;
745
- if (!params) {
746
- return {};
1030
+ // src/actions/task-management.ts
1031
+ function getService(runtime) {
1032
+ const svc = runtime.getService("CODE_TASK");
1033
+ if (!svc) {
1034
+ throw new Error("AgentOrchestratorService not available (CODE_TASK)");
747
1035
  }
748
- const target = params.target ?? {
749
- channel: params.channel,
750
- to: params.to,
751
- threadId: params.threadId,
752
- replyToMessageId: params.replyTo
753
- };
754
- const content = params.content ?? {
755
- text: params.text ?? message.content?.text
756
- };
757
- return { target, content };
1036
+ return svc;
758
1037
  }
759
- var sendCrossPlatformMessageAction = {
760
- name: "SEND_CROSS_PLATFORM_MESSAGE",
761
- similes: [
762
- "CROSS_PLATFORM_MESSAGE",
763
- "UNIFIED_SEND",
764
- "SEND_TO_CHANNEL",
765
- "RELAY_MESSAGE",
766
- "BROADCAST_MESSAGE"
767
- ],
768
- description: "Send a message to any supported platform (Discord, Telegram, Slack, WhatsApp, Twitch). " + "Requires specifying the target channel/platform and recipient.",
769
- validate: async (runtime, message, _state) => {
770
- const messagingService = runtime.getService("MESSAGING");
771
- if (!messagingService) {
1038
+ function extractQuery(text) {
1039
+ return text.toLowerCase().replace(/\b(switch|select|go|change|search|find|pause|stop|halt|resume|restart|continue|start|run|begin|cancel|delete|remove|list|show|view)\b/g, "").replace(/\b(about|for|named|called|with|to|my|your|our|this|current)\b/g, "").replace(/\b(task|tasks|the|a|an)\b/g, "").replace(/\s+/g, " ").trim();
1040
+ }
1041
+ var createTaskAction = {
1042
+ name: "CREATE_TASK",
1043
+ similes: ["START_TASK", "SPAWN_TASK", "NEW_TASK", "BEGIN_TASK"],
1044
+ description: "Create an orchestrated background task to be executed by a selected agent provider.",
1045
+ validate: async (runtime, message) => {
1046
+ const svc = runtime.getService("CODE_TASK");
1047
+ if (!svc) {
772
1048
  return false;
773
1049
  }
774
- const params = message.content?.params;
775
- if (!params) {
776
- return true;
777
- }
778
- const hasTarget = params.target || params.channel && params.to;
779
- const hasContent = params.content || params.text || message.content?.text;
780
- return !!(hasTarget && hasContent);
1050
+ const text = message.content.text?.toLowerCase() ?? "";
1051
+ const hasExplicit = text.includes("create task") || text.includes("new task") || text.includes("start a task");
1052
+ const hasIntent = text.includes("implement") || text.includes("build") || text.includes("create") || text.includes("develop") || text.includes("refactor") || text.includes("fix") || text.includes("add");
1053
+ return hasExplicit || hasIntent;
781
1054
  },
782
- handler: async (runtime, message, state, _options, callback) => {
783
- const messagingService = runtime.getService("MESSAGING");
784
- if (!messagingService) {
785
- if (callback) {
786
- await callback({
787
- text: "Messaging service is not available. Please ensure the orchestrator plugin is properly configured."
788
- });
1055
+ handler: async (runtime, message, _state, options, callback) => {
1056
+ const svc = getService(runtime);
1057
+ const raw = message.content.text ?? "";
1058
+ const opts = options;
1059
+ const name = (opts?.title ?? raw.split(`
1060
+ `)[0] ?? "New Task").trim().slice(0, 100) || "New Task";
1061
+ const description = (opts?.description ?? raw).trim().slice(0, 4000) || name;
1062
+ const roomId = message.roomId;
1063
+ const task = await svc.createTask(name, description, roomId);
1064
+ const stepLines = Array.isArray(opts?.steps) ? opts?.steps : undefined;
1065
+ if (stepLines && stepLines.length > 0) {
1066
+ for (const s of stepLines) {
1067
+ const step = String(s).trim();
1068
+ if (!step)
1069
+ continue;
1070
+ await svc.addStep(task.id ?? "", step);
789
1071
  }
790
- return { success: false, error: "Messaging service not available" };
1072
+ await svc.appendOutput(task.id ?? "", `Plan:
1073
+ ${stepLines.map((s, i) => `${i + 1}. ${s}`).join(`
1074
+ `)}`);
791
1075
  }
792
- const { target, content } = extractMessagingParams(message, state);
793
- if (!target?.channel) {
794
- if (callback) {
795
- await callback({
796
- text: "Please specify the target channel (discord, telegram, slack, whatsapp, twitch)."
797
- });
798
- }
799
- return { success: false, error: "Missing target channel" };
800
- }
801
- if (!target?.to) {
802
- if (callback) {
803
- await callback({
804
- text: "Please specify the recipient (channel ID, chat ID, or room ID)."
805
- });
806
- }
807
- return { success: false, error: "Missing recipient" };
808
- }
809
- if (!content?.text) {
810
- if (callback) {
811
- await callback({
812
- text: "Please provide the message text to send."
813
- });
814
- }
815
- return { success: false, error: "Missing message text" };
816
- }
817
- const result = await messagingService.send({
818
- target: {
819
- channel: target.channel,
820
- to: target.to,
821
- threadId: target.threadId,
822
- replyToMessageId: target.replyToMessageId
823
- },
824
- content: {
825
- text: content.text,
826
- attachments: content.attachments,
827
- embed: content.embed,
828
- buttons: content.buttons,
829
- disableLinkPreview: content.disableLinkPreview,
830
- silent: content.silent
831
- }
1076
+ const msg = `Created task: ${task.name}
1077
+ Provider: ${task.metadata.providerLabel ?? task.metadata.providerId}
1078
+ Starting execution…`;
1079
+ await callback?.({ content: { text: msg } });
1080
+ svc.startTaskExecution(task.id ?? "").catch((err) => {
1081
+ const errorMsg = err instanceof Error ? err.message : String(err);
1082
+ svc.appendOutput(task.id ?? "", `Execution error: ${errorMsg}`).catch(() => {});
832
1083
  });
833
- if (callback) {
834
- if (result.success) {
835
- await callback({
836
- text: `Message sent successfully to ${target.channel}.`,
837
- data: { messageId: result.messageId, sentAt: result.sentAt }
838
- });
839
- } else {
840
- await callback({
841
- text: `Failed to send message: ${result.error}`
842
- });
843
- }
1084
+ return { success: true, text: msg, data: { taskId: task.id ?? "" } };
1085
+ }
1086
+ };
1087
+ var listTasksAction = {
1088
+ name: "LIST_TASKS",
1089
+ similes: ["SHOW_TASKS", "GET_TASKS", "TASKS", "VIEW_TASKS"],
1090
+ description: "List tasks managed by the orchestrator.",
1091
+ validate: async (runtime, message) => {
1092
+ const svc = runtime.getService("CODE_TASK");
1093
+ if (!svc) {
1094
+ return false;
844
1095
  }
845
- return {
846
- success: result.success,
847
- data: result,
848
- error: result.error
849
- };
1096
+ const t = message.content.text?.toLowerCase() ?? "";
1097
+ return t.includes("list task") || t.includes("show task") || t === "tasks" || t.includes("my task");
850
1098
  },
851
- examples: [
852
- [
853
- {
854
- name: "{{name1}}",
855
- content: {
856
- text: "Send 'Hello from the agent!' to Discord channel 123456789",
857
- params: {
858
- channel: "discord",
859
- to: "123456789",
860
- text: "Hello from the agent!"
861
- }
862
- }
863
- },
864
- {
865
- name: "{{agentName}}",
866
- content: {
867
- text: "Message sent successfully to Discord.",
868
- actions: ["SEND_CROSS_PLATFORM_MESSAGE"]
869
- }
870
- }
871
- ],
872
- [
873
- {
874
- name: "{{name1}}",
875
- content: {
876
- text: "Send a Telegram message",
877
- params: {
878
- channel: "telegram",
879
- to: "-1001234567890",
880
- text: "Automated notification from your AI assistant."
881
- }
882
- }
883
- },
884
- {
885
- name: "{{agentName}}",
886
- content: {
887
- text: "Message sent successfully to Telegram.",
888
- actions: ["SEND_CROSS_PLATFORM_MESSAGE"]
889
- }
890
- }
891
- ]
892
- ]
1099
+ handler: async (runtime, _message, _state, _options, callback) => {
1100
+ const svc = getService(runtime);
1101
+ const tasks = await svc.getRecentTasks(20);
1102
+ if (tasks.length === 0) {
1103
+ const msg2 = "No tasks.";
1104
+ await callback?.({ content: { text: msg2 } });
1105
+ return { success: true, text: msg2 };
1106
+ }
1107
+ const lines = ["Tasks:"];
1108
+ const current = svc.getCurrentTaskId();
1109
+ for (const t of tasks) {
1110
+ const marker = t.id === current ? " (current)" : "";
1111
+ lines.push(`- ${t.name} — ${t.metadata.status} ${t.metadata.progress}%${marker}`);
1112
+ }
1113
+ const msg = lines.join(`
1114
+ `);
1115
+ await callback?.({ content: { text: msg } });
1116
+ return { success: true, text: msg };
1117
+ }
893
1118
  };
894
- var sendToDeliveryContextAction = {
895
- name: "SEND_TO_DELIVERY_CONTEXT",
896
- similes: [
897
- "DELIVER_MESSAGE",
898
- "SEND_TO_CONTEXT",
899
- "ROUTE_MESSAGE"
900
- ],
901
- description: "Send a message using a delivery context that specifies the target channel and recipient. " + "Useful for routing messages back to the original requester or to a specific context.",
902
- validate: async (runtime, message, _state) => {
903
- const messagingService = runtime.getService("MESSAGING");
904
- if (!messagingService) {
1119
+ var switchTaskAction = {
1120
+ name: "SWITCH_TASK",
1121
+ similes: ["SELECT_TASK", "SET_TASK", "CHANGE_TASK", "GO_TO_TASK"],
1122
+ description: "Switch the current task context to a different task.",
1123
+ validate: async (runtime, message) => {
1124
+ const svc = runtime.getService("CODE_TASK");
1125
+ if (!svc) {
905
1126
  return false;
906
1127
  }
907
- const params = message.content?.params;
908
- if (!params?.deliveryContext) {
1128
+ const t = message.content.text?.toLowerCase() ?? "";
1129
+ return t.includes("switch to task") || t.includes("select task") || t.includes("task") && t.includes("switch");
1130
+ },
1131
+ handler: async (runtime, message, _state, _options, callback) => {
1132
+ const svc = getService(runtime);
1133
+ const query = extractQuery(message.content.text ?? "");
1134
+ if (!query) {
1135
+ const msg2 = "Please specify which task to switch to (by name or id).";
1136
+ await callback?.({ content: { text: msg2 } });
1137
+ return { success: false, text: msg2 };
1138
+ }
1139
+ const matches = await svc.searchTasks(query);
1140
+ const chosen = matches[0];
1141
+ if (!chosen?.id) {
1142
+ const msg2 = `No task found matching: "${query}"`;
1143
+ await callback?.({ content: { text: msg2 } });
1144
+ return { success: false, text: msg2 };
1145
+ }
1146
+ svc.setCurrentTask(chosen.id);
1147
+ const msg = `Switched to task: ${chosen.name}`;
1148
+ await callback?.({ content: { text: msg } });
1149
+ return { success: true, text: msg, data: { taskId: chosen.id } };
1150
+ }
1151
+ };
1152
+ var searchTasksAction = {
1153
+ name: "SEARCH_TASKS",
1154
+ similes: ["FIND_TASK", "LOOKUP_TASK"],
1155
+ description: "Search tasks by query.",
1156
+ validate: async (runtime, message) => {
1157
+ const svc = runtime.getService("CODE_TASK");
1158
+ if (!svc) {
909
1159
  return false;
910
1160
  }
911
- const hasText = params.text || message.content?.text;
912
- return !!hasText;
1161
+ const t = message.content.text?.toLowerCase() ?? "";
1162
+ return t.includes("search task") || t.includes("find task") || t.includes("look for task");
913
1163
  },
914
- handler: async (runtime, message, state, _options, callback) => {
915
- const messagingService = runtime.getService("MESSAGING");
916
- if (!messagingService) {
917
- if (callback) {
918
- await callback({
919
- text: "Messaging service is not available."
920
- });
921
- }
922
- return { success: false, error: "Messaging service not available" };
1164
+ handler: async (runtime, message, _state, options, callback) => {
1165
+ const svc = getService(runtime);
1166
+ const opt = options;
1167
+ const query = (opt?.query ?? extractQuery(message.content.text ?? "")).trim();
1168
+ if (!query) {
1169
+ const msg2 = "What would you like to search for?";
1170
+ await callback?.({ content: { text: msg2 } });
1171
+ return { success: false, text: msg2 };
923
1172
  }
924
- const params = message.content?.params;
925
- const deliveryContext = params?.deliveryContext;
926
- const text = params?.text ?? message.content?.text;
927
- if (!deliveryContext) {
928
- if (callback) {
929
- await callback({
930
- text: "Please provide a delivery context with channel and recipient information."
931
- });
932
- }
933
- return { success: false, error: "Missing delivery context" };
1173
+ const matches = await svc.searchTasks(query);
1174
+ if (matches.length === 0) {
1175
+ const msg2 = `No tasks found matching: "${query}"`;
1176
+ await callback?.({ content: { text: msg2 } });
1177
+ return { success: true, text: msg2 };
934
1178
  }
935
- if (!text) {
936
- if (callback) {
937
- await callback({
938
- text: "Please provide the message text to send."
939
- });
940
- }
941
- return { success: false, error: "Missing message text" };
1179
+ const lines = [`Found ${matches.length} task(s) matching "${query}":`];
1180
+ for (const t of matches.slice(0, 10)) {
1181
+ lines.push(`- ${t.name} — ${t.metadata.status} ${t.metadata.progress}%`);
942
1182
  }
943
- const result = await messagingService.sendToDeliveryContext(deliveryContext, {
944
- text
945
- });
946
- if (callback) {
947
- if (result.success) {
948
- await callback({
949
- text: `Message delivered successfully via ${result.channel}.`,
950
- data: { messageId: result.messageId }
951
- });
952
- } else {
953
- await callback({
954
- text: `Failed to deliver message: ${result.error}`
955
- });
956
- }
1183
+ const msg = lines.join(`
1184
+ `);
1185
+ await callback?.({ content: { text: msg } });
1186
+ return { success: true, text: msg };
1187
+ }
1188
+ };
1189
+ var pauseTaskAction = {
1190
+ name: "PAUSE_TASK",
1191
+ similes: ["STOP_TASK", "HALT_TASK"],
1192
+ description: "Pause a running task.",
1193
+ validate: async (runtime, message) => {
1194
+ const svc = runtime.getService("CODE_TASK");
1195
+ if (!svc) {
1196
+ return false;
957
1197
  }
958
- return {
959
- success: result.success,
960
- data: result,
961
- error: result.error
962
- };
1198
+ const t = message.content.text?.toLowerCase() ?? "";
1199
+ return (t.includes("pause") || t.includes("stop") || t.includes("halt")) && t.includes("task");
963
1200
  },
964
- examples: [
965
- [
966
- {
967
- name: "{{name1}}",
968
- content: {
969
- text: "Send result to delivery context",
970
- params: {
971
- deliveryContext: {
972
- channel: "discord",
973
- to: "123456789"
974
- },
975
- text: "Task completed successfully!"
976
- }
977
- }
978
- },
979
- {
980
- name: "{{agentName}}",
981
- content: {
982
- text: "Message delivered successfully via discord.",
983
- actions: ["SEND_TO_DELIVERY_CONTEXT"]
984
- }
985
- }
986
- ]
987
- ]
988
- };
989
- var sendToRoomAction = {
990
- name: "SEND_TO_ROOM",
991
- similes: [
992
- "MESSAGE_ROOM",
993
- "ROOM_MESSAGE",
994
- "NOTIFY_ROOM"
995
- ],
996
- description: "Send a message to an Eliza room. The room's metadata determines which platform and recipient to use.",
997
- validate: async (runtime, message, _state) => {
998
- const messagingService = runtime.getService("MESSAGING");
999
- if (!messagingService) {
1000
- return false;
1201
+ handler: async (runtime, message, _state, _options, callback) => {
1202
+ const svc = getService(runtime);
1203
+ const query = extractQuery(message.content.text ?? "");
1204
+ const task = query ? (await svc.searchTasks(query))[0] : await svc.getCurrentTask();
1205
+ if (!task?.id) {
1206
+ const msg2 = "No task to pause.";
1207
+ await callback?.({ content: { text: msg2 } });
1208
+ return { success: false, text: msg2 };
1001
1209
  }
1002
- const params = message.content?.params;
1003
- if (!params?.roomId) {
1210
+ await svc.pauseTask(task.id);
1211
+ const msg = `Paused task: ${task.name}`;
1212
+ await callback?.({ content: { text: msg } });
1213
+ return { success: true, text: msg };
1214
+ }
1215
+ };
1216
+ var resumeTaskAction = {
1217
+ name: "RESUME_TASK",
1218
+ similes: ["CONTINUE_TASK", "RESTART_TASK", "RUN_TASK"],
1219
+ description: "Resume a paused task.",
1220
+ validate: async (runtime, message) => {
1221
+ const svc = runtime.getService("CODE_TASK");
1222
+ if (!svc) {
1004
1223
  return false;
1005
1224
  }
1006
- const hasText = params.text || message.content?.text;
1007
- return !!hasText;
1225
+ const t = message.content.text?.toLowerCase() ?? "";
1226
+ return t.includes("task") && (t.includes("resume") || t.includes("restart") || t.includes("continue"));
1008
1227
  },
1009
- handler: async (runtime, message, state, _options, callback) => {
1010
- const messagingService = runtime.getService("MESSAGING");
1011
- if (!messagingService) {
1012
- if (callback) {
1013
- await callback({
1014
- text: "Messaging service is not available."
1015
- });
1016
- }
1017
- return { success: false, error: "Messaging service not available" };
1018
- }
1019
- const params = message.content?.params;
1020
- const roomId = params?.roomId;
1021
- const text = params?.text ?? message.content?.text;
1022
- if (!roomId) {
1023
- if (callback) {
1024
- await callback({
1025
- text: "Please specify the room ID to send the message to."
1026
- });
1027
- }
1028
- return { success: false, error: "Missing room ID" };
1029
- }
1030
- if (!text) {
1031
- if (callback) {
1032
- await callback({
1033
- text: "Please provide the message text to send."
1034
- });
1035
- }
1036
- return { success: false, error: "Missing message text" };
1037
- }
1038
- const result = await messagingService.sendToRoom(roomId, { text });
1039
- if (callback) {
1040
- if (result.success) {
1041
- await callback({
1042
- text: `Message sent to room via ${result.channel}.`,
1043
- data: { messageId: result.messageId }
1044
- });
1045
- } else {
1046
- await callback({
1047
- text: `Failed to send to room: ${result.error}`
1048
- });
1049
- }
1228
+ handler: async (runtime, message, _state, _options, callback) => {
1229
+ const svc = getService(runtime);
1230
+ const query = extractQuery(message.content.text ?? "");
1231
+ const task = query ? (await svc.searchTasks(query))[0] : await svc.getCurrentTask();
1232
+ if (!task?.id) {
1233
+ const msg2 = "No task to resume.";
1234
+ await callback?.({ content: { text: msg2 } });
1235
+ return { success: false, text: msg2 };
1050
1236
  }
1051
- return {
1052
- success: result.success,
1053
- data: result,
1054
- error: result.error
1055
- };
1056
- },
1057
- examples: [
1058
- [
1059
- {
1060
- name: "{{name1}}",
1061
- content: {
1062
- text: "Send to room",
1063
- params: {
1064
- roomId: "550e8400-e29b-41d4-a716-446655440000",
1065
- text: "Hello, room!"
1066
- }
1067
- }
1068
- },
1069
- {
1070
- name: "{{agentName}}",
1071
- content: {
1072
- text: "Message sent to room via discord.",
1073
- actions: ["SEND_TO_ROOM"]
1074
- }
1075
- }
1076
- ]
1077
- ]
1237
+ await svc.resumeTask(task.id);
1238
+ const taskId = task.id;
1239
+ svc.startTaskExecution(taskId).catch((err) => {
1240
+ const errorMsg = err instanceof Error ? err.message : String(err);
1241
+ svc.appendOutput(taskId, `Execution error: ${errorMsg}`).catch(() => {});
1242
+ });
1243
+ const msg = `Resumed task: ${task.name}`;
1244
+ await callback?.({ content: { text: msg } });
1245
+ return { success: true, text: msg };
1246
+ }
1078
1247
  };
1079
- var sendToSessionMessageAction = {
1080
- name: "SEND_TO_SESSION_MESSAGE",
1081
- similes: [
1082
- "SESSION_MESSAGE",
1083
- "MESSAGE_SESSION",
1084
- "NOTIFY_SESSION"
1085
- ],
1086
- description: "Send a message to a session by its session key. The session key is mapped to an Eliza room.",
1087
- validate: async (runtime, message, _state) => {
1088
- const messagingService = runtime.getService("MESSAGING");
1089
- if (!messagingService) {
1090
- return false;
1091
- }
1092
- const params = message.content?.params;
1093
- if (!params?.sessionKey) {
1248
+ var cancelTaskAction = {
1249
+ name: "CANCEL_TASK",
1250
+ similes: ["DELETE_TASK", "REMOVE_TASK", "ABORT_TASK"],
1251
+ description: "Cancel a task.",
1252
+ validate: async (runtime, message) => {
1253
+ const svc = runtime.getService("CODE_TASK");
1254
+ if (!svc) {
1094
1255
  return false;
1095
1256
  }
1096
- const hasText = params.text || message.content?.text;
1097
- return !!hasText;
1257
+ const t = message.content.text?.toLowerCase() ?? "";
1258
+ return (t.includes("cancel") || t.includes("delete") || t.includes("remove")) && t.includes("task");
1098
1259
  },
1099
- handler: async (runtime, message, state, _options, callback) => {
1100
- const messagingService = runtime.getService("MESSAGING");
1101
- if (!messagingService) {
1102
- if (callback) {
1103
- await callback({
1104
- text: "Messaging service is not available."
1105
- });
1106
- }
1107
- return { success: false, error: "Messaging service not available" };
1108
- }
1109
- const params = message.content?.params;
1110
- const sessionKey = params?.sessionKey;
1111
- const text = params?.text ?? message.content?.text;
1112
- if (!sessionKey) {
1113
- if (callback) {
1114
- await callback({
1115
- text: "Please specify the session key to send the message to."
1116
- });
1117
- }
1118
- return { success: false, error: "Missing session key" };
1119
- }
1120
- if (!text) {
1121
- if (callback) {
1122
- await callback({
1123
- text: "Please provide the message text to send."
1124
- });
1125
- }
1126
- return { success: false, error: "Missing message text" };
1127
- }
1128
- const result = await messagingService.sendToSession(sessionKey, { text });
1129
- if (callback) {
1130
- if (result.success) {
1131
- await callback({
1132
- text: `Message sent to session via ${result.channel}.`,
1133
- data: { messageId: result.messageId }
1134
- });
1135
- } else {
1136
- await callback({
1137
- text: `Failed to send to session: ${result.error}`
1138
- });
1139
- }
1260
+ handler: async (runtime, message, _state, _options, callback) => {
1261
+ const svc = getService(runtime);
1262
+ const query = extractQuery(message.content.text ?? "");
1263
+ const task = query ? (await svc.searchTasks(query))[0] : await svc.getCurrentTask();
1264
+ if (!task?.id) {
1265
+ const msg2 = "No task to cancel.";
1266
+ await callback?.({ content: { text: msg2 } });
1267
+ return { success: false, text: msg2 };
1140
1268
  }
1141
- return {
1142
- success: result.success,
1143
- data: result,
1144
- error: result.error
1145
- };
1146
- },
1147
- examples: [
1148
- [
1149
- {
1150
- name: "{{name1}}",
1151
- content: {
1152
- text: "Send to session",
1153
- params: {
1154
- sessionKey: "agent:main:dm:user123",
1155
- text: "Update from your subagent!"
1156
- }
1157
- }
1158
- },
1159
- {
1160
- name: "{{agentName}}",
1161
- content: {
1162
- text: "Message sent to session via discord.",
1163
- actions: ["SEND_TO_SESSION_MESSAGE"]
1164
- }
1165
- }
1166
- ]
1167
- ]
1168
- };
1169
- var listMessagingChannelsAction = {
1170
- name: "LIST_MESSAGING_CHANNELS",
1171
- similes: [
1172
- "AVAILABLE_CHANNELS",
1173
- "GET_CHANNELS",
1174
- "MESSAGING_PLATFORMS"
1175
- ],
1176
- description: "List all available messaging channels/platforms that the agent can send messages to.",
1177
- validate: async (runtime, _message, _state) => {
1178
- const messagingService = runtime.getService("MESSAGING");
1179
- return !!messagingService;
1180
- },
1181
- handler: async (runtime, _message, _state, _options, callback) => {
1182
- const messagingService = runtime.getService("MESSAGING");
1183
- if (!messagingService) {
1184
- if (callback) {
1185
- await callback({
1186
- text: "Messaging service is not available."
1187
- });
1188
- }
1189
- return { success: false, error: "Messaging service not available" };
1190
- }
1191
- const channels = messagingService.getAvailableChannels();
1192
- if (callback) {
1193
- if (channels.length > 0) {
1194
- await callback({
1195
- text: `Available messaging channels: ${channels.join(", ")}`,
1196
- data: { channels }
1197
- });
1198
- } else {
1199
- await callback({
1200
- text: "No messaging channels are currently available.",
1201
- data: { channels: [] }
1202
- });
1203
- }
1204
- }
1205
- return {
1206
- success: true,
1207
- data: { channels }
1208
- };
1209
- },
1210
- examples: [
1211
- [
1212
- {
1213
- name: "{{name1}}",
1214
- content: {
1215
- text: "What messaging platforms can you use?"
1216
- }
1217
- },
1218
- {
1219
- name: "{{agentName}}",
1220
- content: {
1221
- text: "Available messaging channels: discord, telegram, internal",
1222
- actions: ["LIST_MESSAGING_CHANNELS"]
1223
- }
1224
- }
1225
- ]
1226
- ]
1269
+ await svc.cancelTask(task.id);
1270
+ const msg = `Cancelled task: ${task.name}`;
1271
+ await callback?.({ content: { text: msg } });
1272
+ return { success: true, text: msg };
1273
+ }
1227
1274
  };
1228
1275
 
1229
1276
  // src/config.ts
@@ -1235,61 +1282,6 @@ function getConfiguredAgentOrchestratorOptions() {
1235
1282
  return globalOptions;
1236
1283
  }
1237
1284
 
1238
- // src/providers/task-context.ts
1239
- function getService2(runtime) {
1240
- return runtime.getService("CODE_TASK");
1241
- }
1242
- function formatStatus(status) {
1243
- switch (status) {
1244
- case "pending":
1245
- return "⏳ pending";
1246
- case "running":
1247
- return "\uD83D\uDD04 running";
1248
- case "paused":
1249
- return "⏸️ paused";
1250
- case "completed":
1251
- return "✅ completed";
1252
- case "failed":
1253
- return "❌ failed";
1254
- case "cancelled":
1255
- return "\uD83D\uDED1 cancelled";
1256
- default:
1257
- return status;
1258
- }
1259
- }
1260
- var taskContextProvider = {
1261
- name: "TASK_CONTEXT",
1262
- description: "Summary of orchestrated tasks and current selection",
1263
- dynamic: true,
1264
- get: async (runtime, _message, _state) => {
1265
- const svc = getService2(runtime);
1266
- if (!svc) {
1267
- return { text: "Task service not available." };
1268
- }
1269
- const current = await svc.getCurrentTask();
1270
- const tasks = await svc.getRecentTasks(10);
1271
- if (tasks.length === 0) {
1272
- return { text: "No tasks have been created yet." };
1273
- }
1274
- const lines = [];
1275
- if (current) {
1276
- lines.push(`## Current Task`);
1277
- lines.push(`- Name: ${current.name}`);
1278
- lines.push(`- Status: ${formatStatus(current.metadata.status)}`);
1279
- lines.push(`- Progress: ${current.metadata.progress}%`);
1280
- lines.push(`- Provider: ${current.metadata.providerLabel ?? current.metadata.providerId}`);
1281
- lines.push("");
1282
- }
1283
- lines.push("## Recent Tasks");
1284
- for (const t of tasks) {
1285
- const marker = current?.id === t.id ? " (current)" : "";
1286
- lines.push(`- ${t.name} — ${formatStatus(t.metadata.status)} ${t.metadata.progress}%${marker}`);
1287
- }
1288
- return { text: lines.join(`
1289
- `).trim() };
1290
- }
1291
- };
1292
-
1293
1285
  // src/providers/orchestrator-config.ts
1294
1286
  var DEFAULT_CONFIG = {
1295
1287
  subagents: {
@@ -1316,15 +1308,18 @@ function getOrchestratorConfig(runtime) {
1316
1308
  const subagentsRaw = settings.subagents ?? {};
1317
1309
  const a2aRaw = settings.agentToAgent ?? {};
1318
1310
  const sandboxRaw = settings.sandbox ?? {};
1311
+ const subagents = {
1312
+ enabled: subagentsRaw.enabled ?? true,
1313
+ timeoutSeconds: subagentsRaw.timeoutSeconds ?? 300,
1314
+ allowAgents: subagentsRaw.allowAgents ?? [],
1315
+ archiveAfterMinutes: subagentsRaw.archiveAfterMinutes ?? 60
1316
+ };
1317
+ if (subagentsRaw.model)
1318
+ subagents.model = subagentsRaw.model;
1319
+ if (subagentsRaw.thinking)
1320
+ subagents.thinking = subagentsRaw.thinking;
1319
1321
  return {
1320
- subagents: {
1321
- enabled: subagentsRaw.enabled ?? DEFAULT_CONFIG.subagents.enabled,
1322
- model: subagentsRaw.model,
1323
- thinking: subagentsRaw.thinking,
1324
- timeoutSeconds: subagentsRaw.timeoutSeconds ?? DEFAULT_CONFIG.subagents.timeoutSeconds,
1325
- allowAgents: subagentsRaw.allowAgents ?? DEFAULT_CONFIG.subagents.allowAgents,
1326
- archiveAfterMinutes: subagentsRaw.archiveAfterMinutes ?? DEFAULT_CONFIG.subagents.archiveAfterMinutes
1327
- },
1322
+ subagents,
1328
1323
  agentToAgent: {
1329
1324
  enabled: a2aRaw.enabled ?? DEFAULT_CONFIG.agentToAgent.enabled,
1330
1325
  allow: a2aRaw.allow ?? DEFAULT_CONFIG.agentToAgent.allow
@@ -1373,7 +1368,62 @@ var orchestratorConfigProvider = {
1373
1368
  description: "Provides orchestrator configuration including subagents, A2A, and sandbox settings",
1374
1369
  get: async (runtime, _message, _state) => {
1375
1370
  const config = getOrchestratorConfig(runtime);
1376
- return formatConfigContext(config);
1371
+ return { text: formatConfigContext(config) };
1372
+ }
1373
+ };
1374
+
1375
+ // src/providers/task-context.ts
1376
+ function getService2(runtime) {
1377
+ return runtime.getService("CODE_TASK");
1378
+ }
1379
+ function formatStatus(status) {
1380
+ switch (status) {
1381
+ case "pending":
1382
+ return "⏳ pending";
1383
+ case "running":
1384
+ return "\uD83D\uDD04 running";
1385
+ case "paused":
1386
+ return "⏸️ paused";
1387
+ case "completed":
1388
+ return "✅ completed";
1389
+ case "failed":
1390
+ return "❌ failed";
1391
+ case "cancelled":
1392
+ return "\uD83D\uDED1 cancelled";
1393
+ default:
1394
+ return status;
1395
+ }
1396
+ }
1397
+ var taskContextProvider = {
1398
+ name: "TASK_CONTEXT",
1399
+ description: "Summary of orchestrated tasks and current selection",
1400
+ dynamic: true,
1401
+ get: async (runtime, _message, _state) => {
1402
+ const svc = getService2(runtime);
1403
+ if (!svc) {
1404
+ return { text: "Task service not available." };
1405
+ }
1406
+ const current = await svc.getCurrentTask();
1407
+ const tasks = await svc.getRecentTasks(10);
1408
+ if (tasks.length === 0) {
1409
+ return { text: "No tasks have been created yet." };
1410
+ }
1411
+ const lines = [];
1412
+ if (current) {
1413
+ lines.push(`## Current Task`);
1414
+ lines.push(`- Name: ${current.name}`);
1415
+ lines.push(`- Status: ${formatStatus(current.metadata.status)}`);
1416
+ lines.push(`- Progress: ${current.metadata.progress}%`);
1417
+ lines.push(`- Provider: ${current.metadata.providerLabel ?? current.metadata.providerId}`);
1418
+ lines.push("");
1419
+ }
1420
+ lines.push("## Recent Tasks");
1421
+ for (const t of tasks) {
1422
+ const marker = current?.id === t.id ? " (current)" : "";
1423
+ lines.push(`- ${t.name} — ${formatStatus(t.metadata.status)} ${t.metadata.progress}%${marker}`);
1424
+ }
1425
+ return { text: lines.join(`
1426
+ `).trim() };
1377
1427
  }
1378
1428
  };
1379
1429
 
@@ -1809,694 +1859,584 @@ Provider: ${provider.label} (${provider.id})`);
1809
1859
  const roomId = task.roomId ?? undefined;
1810
1860
  const worldId = task.worldId ?? undefined;
1811
1861
  const execCtxBase = {
1812
- runtimeAgentId: this.runtime.agentId,
1813
- workingDirectory: task.metadata.workingDirectory,
1814
- appendOutput: async (line) => this.appendOutput(taskId, line),
1815
- updateProgress: async (p) => this.updateTaskProgress(taskId, p),
1816
- updateStep: async (stepId, status, output) => this.updateStep(taskId, stepId, status, output),
1817
- isCancelled: () => this.isTaskCancelled(taskId),
1818
- isPaused: () => this.isTaskPaused(taskId)
1819
- };
1820
- const execCtx = {
1821
- ...execCtxBase,
1822
- ...roomId ? { roomId } : {},
1823
- ...worldId ? { worldId } : {}
1824
- };
1825
- const result = await provider.executeTask(task, execCtx);
1826
- await this.setTaskResult(taskId, result);
1827
- } catch (e) {
1828
- const msg = e instanceof Error ? e.message : String(e);
1829
- await this.setTaskError(taskId, msg);
1830
- } finally {
1831
- this.clearControl(taskId);
1832
- }
1833
- }
1834
- on(event, handler) {
1835
- this.emitter.on(event, handler);
1836
- }
1837
- off(event, handler) {
1838
- this.emitter.off(event, handler);
1839
- }
1840
- emit(type, taskId, data) {
1841
- const event = data ? { type, taskId, data } : { type, taskId };
1842
- this.emitter.emit(type, event);
1843
- this.emitter.emit("task", event);
1844
- }
1845
- async stop() {
1846
- this.emitter.removeAllListeners();
1847
- this.controlStates.clear();
1848
- this.executions.clear();
1849
- }
1850
- async getTaskContext() {
1851
- const current = await this.getCurrentTask();
1852
- const tasks = await this.getRecentTasks(10);
1853
- if (tasks.length === 0) {
1854
- return "No tasks have been created yet.";
1855
- }
1856
- const lines = [];
1857
- const active = current ?? tasks[0] ?? null;
1858
- if (active) {
1859
- const m = active.metadata;
1860
- lines.push(`## Current Task (selected): ${active.name}`);
1861
- lines.push(`- **Execution status**: ${m.status}`);
1862
- lines.push(`- **Progress**: ${m.progress}%`);
1863
- lines.push(`- **Provider**: ${m.providerLabel ?? m.providerId}`);
1864
- lines.push("");
1865
- if (active.description) {
1866
- lines.push("### Description");
1867
- lines.push(active.description);
1868
- lines.push("");
1869
- }
1870
- if (m.steps.length > 0) {
1871
- lines.push("### Plan / Steps");
1872
- for (const s of m.steps) {
1873
- lines.push(`- [${s.status}] ${s.description}`);
1874
- }
1875
- lines.push("");
1876
- }
1877
- if (m.output.length > 0) {
1878
- lines.push("### Task Output (history)");
1879
- lines.push("```");
1880
- lines.push(...m.output.slice(-200));
1881
- lines.push("```");
1882
- lines.push("");
1883
- }
1884
- }
1885
- return lines.join(`
1886
- `).trim();
1887
- }
1888
- }
1889
-
1890
- // src/services/subagent-service.ts
1891
- import { EventEmitter as EventEmitter2 } from "node:events";
1892
- import crypto2 from "node:crypto";
1893
- import {
1894
- EventType,
1895
- ChannelType,
1896
- Service as Service2
1897
- } from "@elizaos/core";
1898
- class SubagentService extends Service2 {
1899
- static serviceType = "SUBAGENT";
1900
- capabilityDescription = "Manages subagent spawning, lifecycle, and communication";
1901
- emitter = new EventEmitter2;
1902
- subagentRuns = new Map;
1903
- activeRuns = new Map;
1904
- sweeper = null;
1905
- initialized = false;
1906
- static async start(runtime) {
1907
- const service = new SubagentService(runtime);
1908
- await service.initialize();
1909
- return service;
1910
- }
1911
- async initialize() {
1912
- if (this.initialized) {
1913
- return;
1914
- }
1915
- this.initialized = true;
1916
- this.runtime.registerEvent(EventType.RUN_ENDED, async (payload) => {
1917
- await this.handleRunEnded(payload);
1918
- });
1919
- this.runtime.registerEvent(EventType.RUN_TIMEOUT, async (payload) => {
1920
- await this.handleRunTimeout(payload);
1921
- });
1922
- this.startSweeper();
1923
- }
1924
- getConfig() {
1925
- const settings = this.runtime.character?.settings;
1926
- const subagents = settings?.subagents ?? {};
1927
- return {
1928
- enabled: subagents.enabled !== false,
1929
- model: subagents.model,
1930
- thinking: subagents.thinking,
1931
- timeoutSeconds: subagents.timeoutSeconds ?? 300,
1932
- allowAgents: subagents.allowAgents ?? [],
1933
- archiveAfterMinutes: subagents.archiveAfterMinutes ?? 60
1934
- };
1935
- }
1936
- getAgentToAgentPolicy() {
1937
- const settings = this.runtime.character?.settings;
1938
- const a2aConfig = settings?.agentToAgent ?? {};
1939
- const enabled = a2aConfig.enabled === true;
1940
- const allowRules = (a2aConfig.allow ?? []).map((rule) => ({
1941
- source: rule.source ?? "*",
1942
- target: rule.target ?? "*"
1943
- }));
1944
- return {
1945
- enabled,
1946
- allowRules,
1947
- isAllowed: (sourceAgentId, targetAgentId) => {
1948
- if (!enabled) {
1949
- return false;
1950
- }
1951
- if (sourceAgentId === targetAgentId) {
1952
- return true;
1953
- }
1954
- const sourceNorm = normalizeAgentId2(sourceAgentId);
1955
- const targetNorm = normalizeAgentId2(targetAgentId);
1956
- for (const rule of allowRules) {
1957
- const sourceMatch = rule.source === "*" || normalizeAgentId2(rule.source) === sourceNorm;
1958
- const targetMatch = rule.target === "*" || normalizeAgentId2(rule.target) === targetNorm;
1959
- if (sourceMatch && targetMatch) {
1960
- return true;
1961
- }
1962
- }
1963
- return false;
1964
- }
1965
- };
1966
- }
1967
- async spawnSubagent(params, requesterContext) {
1968
- const config = this.getConfig();
1969
- if (!config.enabled) {
1970
- return {
1971
- status: "forbidden",
1972
- error: "Subagent spawning is disabled"
1973
- };
1974
- }
1975
- if (requesterContext.sessionKey && isSubagentSessionKey2(requesterContext.sessionKey)) {
1976
- return {
1977
- status: "forbidden",
1978
- error: "sessions_spawn is not allowed from sub-agent sessions"
1979
- };
1980
- }
1981
- const requesterAgentId = requesterContext.sessionKey ? extractAgentIdFromSessionKey(requesterContext.sessionKey) : this.runtime.character?.name ?? "unknown";
1982
- const targetAgentId = params.agentId ? normalizeAgentId2(params.agentId) : requesterAgentId;
1983
- if (targetAgentId !== requesterAgentId) {
1984
- const allowAgents = config.allowAgents ?? [];
1985
- const allowAny = allowAgents.some((v) => v.trim() === "*");
1986
- const allowSet = new Set(allowAgents.filter((v) => v.trim() && v.trim() !== "*").map((v) => normalizeAgentId2(v)));
1987
- if (!allowAny && !allowSet.has(targetAgentId)) {
1988
- return {
1989
- status: "forbidden",
1990
- error: `agentId "${targetAgentId}" is not allowed for subagent spawning`
1991
- };
1992
- }
1993
- }
1994
- const childSessionKey = createSubagentSessionKey(targetAgentId);
1995
- const runId = crypto2.randomUUID();
1996
- const childRoomId = sessionKeyToRoomId(childSessionKey, targetAgentId);
1997
- const roomMetadata = {
1998
- isSubagent: true,
1999
- sessionKey: childSessionKey,
2000
- parentRoomId: requesterContext.roomId,
2001
- parentSessionKey: requesterContext.sessionKey,
2002
- task: params.task,
2003
- label: params.label,
2004
- spawnedAt: Date.now(),
2005
- cleanup: params.cleanup ?? "keep"
2006
- };
2007
- const childRoom = {
2008
- id: childRoomId,
2009
- name: params.label || `Subagent: ${params.task.slice(0, 50)}`,
2010
- type: ChannelType.SELF,
2011
- channelId: childSessionKey,
2012
- agentId: this.runtime.agentId,
2013
- worldId: this.runtime.agentId,
2014
- metadata: roomMetadata
2015
- };
2016
- await this.runtime.ensureRoomExists(childRoom);
2017
- const now2 = Date.now();
2018
- const archiveAfterMs = config.archiveAfterMinutes ? config.archiveAfterMinutes * 60000 : undefined;
2019
- const record = {
2020
- runId,
2021
- childSessionKey,
2022
- requesterSessionKey: requesterContext.sessionKey ?? "unknown",
2023
- requesterOrigin: normalizeDeliveryContext(requesterContext.origin),
2024
- requesterDisplayKey: requesterContext.sessionKey ?? "main",
2025
- task: params.task,
2026
- cleanup: params.cleanup ?? "keep",
2027
- label: params.label,
2028
- createdAt: now2,
2029
- startedAt: now2,
2030
- archiveAtMs: archiveAfterMs ? now2 + archiveAfterMs : undefined,
2031
- cleanupHandled: false,
2032
- roomId: childRoomId,
2033
- worldId: this.runtime.agentId
2034
- };
2035
- this.subagentRuns.set(runId, record);
2036
- this.emitSubagentEvent("SUBAGENT_SPAWN_REQUESTED", {
2037
- runId,
2038
- childSessionKey,
2039
- childRoomId,
2040
- requesterSessionKey: requesterContext.sessionKey,
2041
- requesterRoomId: requesterContext.roomId,
2042
- task: params.task,
2043
- label: params.label
2044
- });
2045
- const systemPrompt = this.buildSubagentSystemPrompt({
2046
- requesterSessionKey: requesterContext.sessionKey,
2047
- requesterOrigin: requesterContext.origin,
2048
- childSessionKey,
2049
- label: params.label,
2050
- task: params.task
2051
- });
2052
- const initialMessage = {
2053
- id: hashToUUID(`${runId}-initial`),
2054
- entityId: this.runtime.agentId,
2055
- agentId: this.runtime.agentId,
2056
- roomId: childRoomId,
2057
- content: {
2058
- text: params.task,
2059
- type: "text",
2060
- metadata: {
2061
- isSubagentTask: true,
2062
- runId,
2063
- systemPromptOverride: systemPrompt,
2064
- modelOverride: params.model || config.model,
2065
- thinkingOverride: params.thinking || config.thinking
2066
- }
2067
- }
2068
- };
2069
- this.executeSubagentRun(runId, initialMessage, params.runTimeoutSeconds).catch((error) => {
2070
- this.runtime.logger.error("Subagent execution error", { runId, error });
2071
- this.handleSubagentError(runId, error);
2072
- });
2073
- return {
2074
- status: "accepted",
2075
- childSessionKey,
2076
- childRoomId,
2077
- runId,
2078
- modelApplied: !!(params.model || config.model)
2079
- };
2080
- }
2081
- async executeSubagentRun(runId, initialMessage, timeoutSeconds) {
2082
- const config = this.getConfig();
2083
- const timeout = (timeoutSeconds ?? config.timeoutSeconds ?? 300) * 1000;
2084
- const controller = new AbortController;
2085
- this.activeRuns.set(runId, controller);
2086
- const timeoutId = timeout > 0 ? setTimeout(() => {
2087
- controller.abort();
2088
- this.handleSubagentTimeout(runId);
2089
- }, timeout) : null;
2090
- try {
2091
- await this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
2092
- runtime: this.runtime,
2093
- message: initialMessage,
2094
- source: "subagent"
2095
- });
2096
- await this.waitForCompletion(runId, timeout, controller.signal);
1862
+ runtimeAgentId: this.runtime.agentId,
1863
+ workingDirectory: task.metadata.workingDirectory,
1864
+ appendOutput: async (line) => this.appendOutput(taskId, line),
1865
+ updateProgress: async (p) => this.updateTaskProgress(taskId, p),
1866
+ updateStep: async (stepId, status, output) => this.updateStep(taskId, stepId, status, output),
1867
+ isCancelled: () => this.isTaskCancelled(taskId),
1868
+ isPaused: () => this.isTaskPaused(taskId)
1869
+ };
1870
+ const execCtx = {
1871
+ ...execCtxBase,
1872
+ ...roomId ? { roomId } : {},
1873
+ ...worldId ? { worldId } : {}
1874
+ };
1875
+ const result = await provider.executeTask(task, execCtx);
1876
+ await this.setTaskResult(taskId, result);
1877
+ } catch (e) {
1878
+ const msg = e instanceof Error ? e.message : String(e);
1879
+ await this.setTaskError(taskId, msg);
2097
1880
  } finally {
2098
- if (timeoutId) {
2099
- clearTimeout(timeoutId);
2100
- }
2101
- this.activeRuns.delete(runId);
1881
+ this.clearControl(taskId);
2102
1882
  }
2103
1883
  }
2104
- async waitForCompletion(runId, timeoutMs, signal) {
2105
- const startTime = Date.now();
2106
- const pollInterval = 500;
2107
- while (!signal.aborted) {
2108
- const record = this.subagentRuns.get(runId);
2109
- if (!record) {
2110
- return;
1884
+ on(event, handler) {
1885
+ this.emitter.on(event, handler);
1886
+ }
1887
+ off(event, handler) {
1888
+ this.emitter.off(event, handler);
1889
+ }
1890
+ emit(type, taskId, data) {
1891
+ const event = data ? { type, taskId, data } : { type, taskId };
1892
+ this.emitter.emit(type, event);
1893
+ this.emitter.emit("task", event);
1894
+ }
1895
+ async stop() {
1896
+ this.emitter.removeAllListeners();
1897
+ this.controlStates.clear();
1898
+ this.executions.clear();
1899
+ }
1900
+ async getTaskContext() {
1901
+ const current = await this.getCurrentTask();
1902
+ const tasks = await this.getRecentTasks(10);
1903
+ if (tasks.length === 0) {
1904
+ return "No tasks have been created yet.";
1905
+ }
1906
+ const lines = [];
1907
+ const active = current ?? tasks[0] ?? null;
1908
+ if (active) {
1909
+ const m = active.metadata;
1910
+ lines.push(`## Current Task (selected): ${active.name}`);
1911
+ lines.push(`- **Execution status**: ${m.status}`);
1912
+ lines.push(`- **Progress**: ${m.progress}%`);
1913
+ lines.push(`- **Provider**: ${m.providerLabel ?? m.providerId}`);
1914
+ lines.push("");
1915
+ if (active.description) {
1916
+ lines.push("### Description");
1917
+ lines.push(active.description);
1918
+ lines.push("");
2111
1919
  }
2112
- if (record.endedAt) {
2113
- return;
1920
+ if (m.steps.length > 0) {
1921
+ lines.push("### Plan / Steps");
1922
+ for (const s of m.steps) {
1923
+ lines.push(`- [${s.status}] ${s.description}`);
1924
+ }
1925
+ lines.push("");
2114
1926
  }
2115
- if (Date.now() - startTime > timeoutMs + 5000) {
2116
- return;
1927
+ if (m.output.length > 0) {
1928
+ lines.push("### Task Output (history)");
1929
+ lines.push("```");
1930
+ lines.push(...m.output.slice(-200));
1931
+ lines.push("```");
1932
+ lines.push("");
2117
1933
  }
2118
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
2119
1934
  }
1935
+ return lines.join(`
1936
+ `).trim();
2120
1937
  }
2121
- handleSubagentTimeout(runId) {
2122
- const record = this.subagentRuns.get(runId);
2123
- if (!record || record.endedAt) {
1938
+ }
1939
+
1940
+ // src/services/messaging-service.ts
1941
+ import crypto2 from "node:crypto";
1942
+ import { EventEmitter as EventEmitter2 } from "node:events";
1943
+ import { EventType, Service as Service2 } from "@elizaos/core";
1944
+ class MessagingService extends Service2 {
1945
+ static serviceType = "MESSAGING";
1946
+ capabilityDescription = "Unified cross-platform messaging for sending messages to any supported channel";
1947
+ emitter = new EventEmitter2;
1948
+ adapters = new Map;
1949
+ pendingDeliveries = new Map;
1950
+ initialized = false;
1951
+ static async start(runtime) {
1952
+ const service = new MessagingService(runtime);
1953
+ await service.initialize();
1954
+ return service;
1955
+ }
1956
+ async initialize() {
1957
+ if (this.initialized) {
2124
1958
  return;
2125
1959
  }
2126
- record.endedAt = Date.now();
2127
- record.outcome = { status: "timeout" };
2128
- this.emitSubagentEvent("SUBAGENT_RUN_TIMEOUT", {
2129
- runId,
2130
- childSessionKey: record.childSessionKey,
2131
- childRoomId: record.roomId,
2132
- task: record.task,
2133
- status: "timeout"
1960
+ this.initialized = true;
1961
+ this.registerBuiltInAdapters();
1962
+ }
1963
+ registerBuiltInAdapters() {
1964
+ this.registerAdapter({
1965
+ channel: "discord",
1966
+ isAvailable: () => {
1967
+ const service = this.runtime.getService("DISCORD");
1968
+ return !!service;
1969
+ },
1970
+ send: async (params) => this.sendViaDiscord(params)
2134
1971
  });
2135
- this.announceSubagentResult(runId).catch((err) => {
2136
- this.runtime.logger.error("Failed to announce timeout", { runId, error: err });
1972
+ this.registerAdapter({
1973
+ channel: "telegram",
1974
+ isAvailable: () => {
1975
+ const service = this.runtime.getService("TELEGRAM");
1976
+ return !!service;
1977
+ },
1978
+ send: async (params) => this.sendViaTelegram(params)
2137
1979
  });
2138
- }
2139
- handleSubagentError(runId, error) {
2140
- const record = this.subagentRuns.get(runId);
2141
- if (!record) {
2142
- return;
2143
- }
2144
- record.endedAt = Date.now();
2145
- record.outcome = {
2146
- status: "error",
2147
- error: error instanceof Error ? error.message : String(error)
2148
- };
2149
- this.emitSubagentEvent("SUBAGENT_RUN_FAILED", {
2150
- runId,
2151
- childSessionKey: record.childSessionKey,
2152
- childRoomId: record.roomId,
2153
- task: record.task,
2154
- status: "error",
2155
- error: record.outcome.error
1980
+ this.registerAdapter({
1981
+ channel: "slack",
1982
+ isAvailable: () => {
1983
+ const service = this.runtime.getService("slack");
1984
+ return !!service;
1985
+ },
1986
+ send: async (params) => this.sendViaSlack(params)
2156
1987
  });
2157
- this.announceSubagentResult(runId).catch((err) => {
2158
- this.runtime.logger.error("Failed to announce error", { runId, error: err });
1988
+ this.registerAdapter({
1989
+ channel: "whatsapp",
1990
+ isAvailable: () => {
1991
+ const service = this.runtime.getService("whatsapp");
1992
+ return !!service;
1993
+ },
1994
+ send: async (params) => this.sendViaWhatsApp(params)
1995
+ });
1996
+ this.registerAdapter({
1997
+ channel: "twitch",
1998
+ isAvailable: () => {
1999
+ const service = this.runtime.getService("twitch");
2000
+ return !!service;
2001
+ },
2002
+ send: async (params) => this.sendViaTwitch(params)
2003
+ });
2004
+ this.registerAdapter({
2005
+ channel: "internal",
2006
+ isAvailable: () => true,
2007
+ send: async (params) => this.sendViaInternal(params)
2159
2008
  });
2160
2009
  }
2161
- async handleRunEnded(payload) {
2162
- const p = payload;
2163
- if (!p.roomId) {
2164
- return;
2165
- }
2166
- for (const [runId, record] of this.subagentRuns.entries()) {
2167
- if (record.roomId === p.roomId && !record.endedAt) {
2168
- record.endedAt = Date.now();
2169
- record.outcome = { status: "ok" };
2170
- this.emitSubagentEvent("SUBAGENT_RUN_COMPLETED", {
2171
- runId,
2172
- childSessionKey: record.childSessionKey,
2173
- childRoomId: record.roomId,
2174
- task: record.task,
2175
- status: "completed",
2176
- startedAt: record.startedAt,
2177
- endedAt: record.endedAt,
2178
- durationMs: record.endedAt - (record.startedAt ?? record.createdAt)
2179
- });
2180
- await this.announceSubagentResult(runId);
2181
- break;
2010
+ registerAdapter(adapter) {
2011
+ this.adapters.set(adapter.channel, adapter);
2012
+ }
2013
+ getAdapter(channel) {
2014
+ return this.adapters.get(channel);
2015
+ }
2016
+ getAvailableChannels() {
2017
+ const channels = [];
2018
+ for (const [channel, adapter] of this.adapters) {
2019
+ if (adapter.isAvailable()) {
2020
+ channels.push(channel);
2182
2021
  }
2183
2022
  }
2023
+ return channels;
2184
2024
  }
2185
- async handleRunTimeout(payload) {
2186
- const p = payload;
2187
- if (!p.roomId) {
2188
- return;
2025
+ async send(params) {
2026
+ const idempotencyKey = params.idempotencyKey ?? crypto2.randomUUID();
2027
+ const channel = params.target.channel;
2028
+ const existing = this.pendingDeliveries.get(idempotencyKey);
2029
+ if (existing) {
2030
+ const result = {
2031
+ success: existing.status.status === "sent" || existing.status.status === "delivered",
2032
+ channel,
2033
+ targetId: params.target.to
2034
+ };
2035
+ if (existing.status.messageId)
2036
+ result.messageId = existing.status.messageId;
2037
+ if (existing.status.error)
2038
+ result.error = existing.status.error;
2039
+ if (existing.status.status === "sent")
2040
+ result.sentAt = existing.status.updatedAt;
2041
+ return result;
2042
+ }
2043
+ const status = {
2044
+ status: "pending",
2045
+ updatedAt: Date.now()
2046
+ };
2047
+ this.pendingDeliveries.set(idempotencyKey, { params, status });
2048
+ this.emitMessagingEvent("MESSAGING_SEND_REQUESTED", {
2049
+ idempotencyKey,
2050
+ channel,
2051
+ targetId: params.target.to,
2052
+ status: "pending"
2053
+ });
2054
+ const adapter = this.adapters.get(channel);
2055
+ if (!adapter) {
2056
+ const errorMsg = `No adapter registered for channel: ${channel}`;
2057
+ const result = {
2058
+ success: false,
2059
+ channel,
2060
+ targetId: params.target.to,
2061
+ error: errorMsg
2062
+ };
2063
+ status.status = "failed";
2064
+ status.error = errorMsg;
2065
+ status.updatedAt = Date.now();
2066
+ this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
2067
+ idempotencyKey,
2068
+ channel,
2069
+ targetId: params.target.to,
2070
+ status: "failed",
2071
+ error: errorMsg
2072
+ });
2073
+ return result;
2074
+ }
2075
+ if (!adapter.isAvailable()) {
2076
+ const errorMsg = `${channel} service is not available`;
2077
+ const result = {
2078
+ success: false,
2079
+ channel,
2080
+ targetId: params.target.to,
2081
+ error: errorMsg
2082
+ };
2083
+ status.status = "failed";
2084
+ status.error = errorMsg;
2085
+ status.updatedAt = Date.now();
2086
+ this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
2087
+ idempotencyKey,
2088
+ channel,
2089
+ targetId: params.target.to,
2090
+ status: "failed",
2091
+ error: errorMsg
2092
+ });
2093
+ return result;
2189
2094
  }
2190
- for (const [runId, record] of this.subagentRuns.entries()) {
2191
- if (record.roomId === p.roomId && !record.endedAt) {
2192
- this.handleSubagentTimeout(runId);
2193
- break;
2095
+ try {
2096
+ const result = await adapter.send({ ...params, idempotencyKey });
2097
+ status.status = result.success ? "sent" : "failed";
2098
+ if (result.messageId)
2099
+ status.messageId = result.messageId;
2100
+ if (result.error)
2101
+ status.error = result.error;
2102
+ status.updatedAt = Date.now();
2103
+ if (result.success) {
2104
+ const sentPayload = {
2105
+ idempotencyKey,
2106
+ channel,
2107
+ targetId: params.target.to,
2108
+ status: "sent"
2109
+ };
2110
+ if (result.messageId)
2111
+ sentPayload.messageId = result.messageId;
2112
+ if (result.sentAt)
2113
+ sentPayload.sentAt = result.sentAt;
2114
+ this.emitMessagingEvent("MESSAGING_SENT", sentPayload);
2115
+ } else {
2116
+ const failedPayload = {
2117
+ idempotencyKey,
2118
+ channel,
2119
+ targetId: params.target.to,
2120
+ status: "failed"
2121
+ };
2122
+ if (result.error)
2123
+ failedPayload.error = result.error;
2124
+ this.emitMessagingEvent("MESSAGING_SEND_FAILED", failedPayload);
2194
2125
  }
2126
+ return result;
2127
+ } catch (error) {
2128
+ const errorMessage = error instanceof Error ? error.message : String(error);
2129
+ status.status = "failed";
2130
+ status.error = errorMessage;
2131
+ status.updatedAt = Date.now();
2132
+ this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
2133
+ idempotencyKey,
2134
+ channel,
2135
+ targetId: params.target.to,
2136
+ status: "failed",
2137
+ error: errorMessage
2138
+ });
2139
+ return {
2140
+ success: false,
2141
+ channel,
2142
+ targetId: params.target.to,
2143
+ error: errorMessage
2144
+ };
2195
2145
  }
2196
2146
  }
2197
- async announceSubagentResult(runId) {
2198
- const record = this.subagentRuns.get(runId);
2199
- if (!record) {
2200
- return false;
2201
- }
2202
- if (record.cleanupCompletedAt || record.cleanupHandled) {
2203
- return false;
2147
+ async sendToDeliveryContext(deliveryContext, content, options) {
2148
+ const channel = this.normalizeChannel(deliveryContext.channel);
2149
+ const to = deliveryContext.to ?? deliveryContext.accountId ?? "";
2150
+ if (!to) {
2151
+ return {
2152
+ success: false,
2153
+ channel,
2154
+ targetId: "",
2155
+ error: "No recipient specified in delivery context"
2156
+ };
2204
2157
  }
2205
- record.cleanupHandled = true;
2206
- let reply;
2207
- if (record.roomId) {
2208
- const memories = await this.runtime.getMemories({
2209
- roomId: record.roomId,
2210
- count: 10
2211
- });
2212
- const lastAssistant = memories.filter((m) => m.entityId === this.runtime.agentId).sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))[0];
2213
- reply = lastAssistant?.content?.text;
2158
+ return this.send({
2159
+ target: {
2160
+ channel,
2161
+ to,
2162
+ ...deliveryContext.accountId ? { accountId: deliveryContext.accountId } : {},
2163
+ ...deliveryContext.threadId !== undefined ? { threadId: deliveryContext.threadId } : {}
2164
+ },
2165
+ content,
2166
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {},
2167
+ ...options?.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}
2168
+ });
2169
+ }
2170
+ async sendToRoom(roomId, content, options) {
2171
+ const room = await this.runtime.getRoom(roomId);
2172
+ if (!room) {
2173
+ return {
2174
+ success: false,
2175
+ channel: "unknown",
2176
+ targetId: roomId,
2177
+ error: `Room not found: ${roomId}`
2178
+ };
2214
2179
  }
2215
- const durationMs = record.endedAt ? record.endedAt - (record.startedAt ?? record.createdAt) : undefined;
2216
- const statsLine = `Runtime: ${formatDurationShort(durationMs) ?? "n/a"} • Session: ${record.childSessionKey}`;
2217
- const outcome = record.outcome ?? { status: "unknown" };
2218
- const statusLabel = outcome.status === "ok" ? "completed successfully" : outcome.status === "timeout" ? "timed out" : outcome.status === "error" ? `failed: ${outcome.error || "unknown error"}` : "finished with unknown status";
2219
- const taskLabel = record.label || record.task || "background task";
2220
- const triggerMessage = [
2221
- `A background task "${taskLabel}" just ${statusLabel}.`,
2222
- "",
2223
- "Findings:",
2224
- reply || "(no output)",
2225
- "",
2226
- statsLine,
2227
- "",
2228
- "Summarize this naturally for the user. Keep it brief (1-2 sentences).",
2229
- "Do not mention technical details like tokens, stats, or that this was a background task.",
2230
- "You can respond with NO_REPLY if no announcement is needed."
2231
- ].join(`
2232
- `);
2233
- if (record.requesterSessionKey && record.requesterSessionKey !== "unknown") {
2234
- const requesterRoomId = sessionKeyToRoomId(record.requesterSessionKey, extractAgentIdFromSessionKey(record.requesterSessionKey));
2235
- const announceMessage = {
2236
- id: hashToUUID(`${runId}-announce`),
2237
- entityId: this.runtime.agentId,
2238
- agentId: this.runtime.agentId,
2239
- roomId: requesterRoomId,
2240
- content: {
2241
- text: triggerMessage,
2242
- type: "text",
2243
- metadata: {
2244
- isSubagentAnnouncement: true,
2245
- subagentRunId: runId,
2246
- deliveryContext: record.requesterOrigin
2247
- }
2248
- }
2180
+ const metadata = room.metadata;
2181
+ const channel = this.normalizeChannel(metadata?.messagingChannel);
2182
+ const to = metadata?.messagingTo ?? room.channelId ?? "";
2183
+ if (!to) {
2184
+ return {
2185
+ success: false,
2186
+ channel,
2187
+ targetId: roomId,
2188
+ error: "Room has no messaging target configured"
2249
2189
  };
2250
- await this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
2251
- runtime: this.runtime,
2252
- message: announceMessage,
2253
- source: "subagent_announce"
2254
- });
2255
2190
  }
2256
- this.emitSubagentEvent("SUBAGENT_ANNOUNCE_SENT", {
2257
- runId,
2258
- childSessionKey: record.childSessionKey,
2259
- requesterSessionKey: record.requesterSessionKey,
2260
- task: record.task,
2261
- outcome
2191
+ return this.send({
2192
+ target: {
2193
+ channel,
2194
+ to,
2195
+ ...metadata?.messagingAccountId ? { accountId: metadata.messagingAccountId } : {},
2196
+ ...metadata?.messagingThreadId !== undefined ? { threadId: metadata.messagingThreadId } : {}
2197
+ },
2198
+ content,
2199
+ ...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {},
2200
+ ...options?.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}
2262
2201
  });
2263
- record.cleanupCompletedAt = Date.now();
2264
- if (record.cleanup === "delete") {
2265
- this.subagentRuns.delete(runId);
2266
- }
2267
- return true;
2268
2202
  }
2269
- buildSubagentSystemPrompt(params) {
2270
- const taskText = typeof params.task === "string" && params.task.trim() ? params.task.replace(/\s+/g, " ").trim() : "{{TASK_DESCRIPTION}}";
2271
- const lines = [
2272
- "# Subagent Context",
2273
- "",
2274
- "You are a **subagent** spawned by the main agent for a specific task.",
2275
- "",
2276
- "## Your Role",
2277
- `- You were created to handle: ${taskText}`,
2278
- "- Complete this task. That's your entire purpose.",
2279
- "- You are NOT the main agent. Don't try to be.",
2280
- "",
2281
- "## Rules",
2282
- "1. **Stay focused** - Do your assigned task, nothing else",
2283
- "2. **Complete the task** - Your final message will be automatically reported to the main agent",
2284
- "3. **Don't initiate** - No heartbeats, no proactive actions, no side quests",
2285
- "4. **Be ephemeral** - You may be terminated after task completion. That's fine.",
2286
- "",
2287
- "## Output Format",
2288
- "When complete, your final response should include:",
2289
- "- What you accomplished or found",
2290
- "- Any relevant details the main agent should know",
2291
- "- Keep it concise but informative",
2292
- "",
2293
- "## What You DON'T Do",
2294
- "- NO user conversations (that's main agent's job)",
2295
- "- NO external messages unless explicitly tasked with a specific recipient",
2296
- "- NO cron jobs or persistent state",
2297
- "- NO pretending to be the main agent",
2298
- "",
2299
- "## Session Context",
2300
- params.label ? `- Label: ${params.label}` : undefined,
2301
- params.requesterSessionKey ? `- Requester session: ${params.requesterSessionKey}` : undefined,
2302
- params.requesterOrigin?.channel ? `- Requester channel: ${params.requesterOrigin.channel}` : undefined,
2303
- `- Your session: ${params.childSessionKey}`,
2304
- ""
2305
- ].filter((line) => line !== undefined);
2306
- return lines.join(`
2307
- `);
2203
+ async sendToSession(sessionKey, content, options) {
2204
+ const agentId = extractAgentIdFromSessionKey(sessionKey);
2205
+ const roomId = sessionKeyToRoomId(sessionKey, agentId);
2206
+ return this.sendToRoom(roomId, content, options);
2308
2207
  }
2309
- async sendToAgent(params, requesterContext) {
2310
- const runId = crypto2.randomUUID();
2311
- const policy = this.getAgentToAgentPolicy();
2312
- let targetSessionKey = params.sessionKey;
2313
- if (!targetSessionKey && params.label) {
2314
- const matchingRun = this.findSubagentRunByLabel(params.label, params.agentId);
2315
- if (matchingRun) {
2316
- targetSessionKey = matchingRun.childSessionKey;
2317
- } else {
2208
+ async sendViaDiscord(params) {
2209
+ const discordService = this.runtime.getService("DISCORD");
2210
+ if (!discordService?.client) {
2211
+ return {
2212
+ success: false,
2213
+ channel: "discord",
2214
+ targetId: params.target.to,
2215
+ error: "Discord service not available"
2216
+ };
2217
+ }
2218
+ try {
2219
+ const channel = await discordService.client.channels.fetch(params.target.to);
2220
+ if (!channel?.send) {
2318
2221
  return {
2319
- status: "error",
2320
- runId,
2321
- error: `No subagent found with label "${params.label}"`
2222
+ success: false,
2223
+ channel: "discord",
2224
+ targetId: params.target.to,
2225
+ error: "Channel not found or not a text channel"
2322
2226
  };
2323
2227
  }
2228
+ const message = await channel.send({
2229
+ content: params.content.text,
2230
+ ...params.target.replyToMessageId ? { reply: { messageReference: params.target.replyToMessageId } } : {}
2231
+ });
2232
+ return {
2233
+ success: true,
2234
+ messageId: message.id,
2235
+ channel: "discord",
2236
+ targetId: params.target.to,
2237
+ sentAt: Date.now()
2238
+ };
2239
+ } catch (error) {
2240
+ return {
2241
+ success: false,
2242
+ channel: "discord",
2243
+ targetId: params.target.to,
2244
+ error: error instanceof Error ? error.message : String(error)
2245
+ };
2324
2246
  }
2325
- if (!targetSessionKey) {
2247
+ }
2248
+ async sendViaTelegram(params) {
2249
+ const telegramService = this.runtime.getService("TELEGRAM");
2250
+ if (!telegramService?.bot?.telegram) {
2251
+ return {
2252
+ success: false,
2253
+ channel: "telegram",
2254
+ targetId: params.target.to,
2255
+ error: "Telegram service not available"
2256
+ };
2257
+ }
2258
+ try {
2259
+ const chatId = Number.isNaN(Number(params.target.to)) ? params.target.to : Number(params.target.to);
2260
+ const result = await telegramService.bot.telegram.sendMessage(chatId, params.content.text, {
2261
+ reply_to_message_id: params.target.replyToMessageId ? Number(params.target.replyToMessageId) : undefined,
2262
+ disable_web_page_preview: params.content.disableLinkPreview,
2263
+ disable_notification: params.content.silent
2264
+ });
2265
+ return {
2266
+ success: true,
2267
+ messageId: String(result.message_id),
2268
+ channel: "telegram",
2269
+ targetId: params.target.to,
2270
+ sentAt: Date.now()
2271
+ };
2272
+ } catch (error) {
2326
2273
  return {
2327
- status: "error",
2328
- runId,
2329
- error: "Either sessionKey or label is required"
2274
+ success: false,
2275
+ channel: "telegram",
2276
+ targetId: params.target.to,
2277
+ error: error instanceof Error ? error.message : String(error)
2330
2278
  };
2331
2279
  }
2332
- const requesterAgentId = requesterContext.sessionKey ? extractAgentIdFromSessionKey(requesterContext.sessionKey) : this.runtime.character?.name ?? "unknown";
2333
- const targetAgentId = extractAgentIdFromSessionKey(targetSessionKey);
2334
- if (requesterAgentId !== targetAgentId) {
2335
- if (!policy.enabled) {
2336
- return {
2337
- status: "forbidden",
2338
- runId,
2339
- error: "Agent-to-agent messaging is disabled. Set settings.agentToAgent.enabled=true to allow cross-agent sends."
2340
- };
2341
- }
2342
- if (!policy.isAllowed(requesterAgentId, targetAgentId)) {
2343
- return {
2344
- status: "forbidden",
2345
- runId,
2346
- error: "Agent-to-agent messaging denied by policy."
2347
- };
2348
- }
2280
+ }
2281
+ async sendViaSlack(params) {
2282
+ const slackService = this.runtime.getService("slack");
2283
+ if (!slackService?.sendMessage) {
2284
+ return {
2285
+ success: false,
2286
+ channel: "slack",
2287
+ targetId: params.target.to,
2288
+ error: "Slack service not available or sendMessage method not found"
2289
+ };
2349
2290
  }
2350
- const targetRoomId = sessionKeyToRoomId(targetSessionKey, targetAgentId);
2351
- const contextMessage = this.buildAgentToAgentContext({
2352
- requesterSessionKey: requesterContext.sessionKey,
2353
- targetSessionKey
2354
- });
2355
- const message = {
2356
- id: hashToUUID(`${runId}-a2a`),
2357
- entityId: this.runtime.agentId,
2358
- agentId: this.runtime.agentId,
2359
- roomId: targetRoomId,
2360
- content: {
2361
- text: params.message,
2362
- type: "text",
2363
- metadata: {
2364
- isAgentToAgent: true,
2365
- runId,
2366
- senderSessionKey: requesterContext.sessionKey,
2367
- senderRoomId: requesterContext.roomId,
2368
- systemPromptOverride: contextMessage
2369
- }
2370
- }
2371
- };
2372
- const timeoutMs = (params.timeoutSeconds ?? 30) * 1000;
2373
- if (params.timeoutSeconds === 0) {
2374
- this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
2375
- runtime: this.runtime,
2376
- message,
2377
- source: "a2a"
2378
- }).catch((err) => {
2379
- this.runtime.logger.error("A2A send error", { runId, error: err });
2380
- });
2381
- this.emitSubagentEvent("A2A_MESSAGE_SENT", {
2382
- runId,
2383
- childSessionKey: targetSessionKey,
2384
- requesterSessionKey: requesterContext.sessionKey,
2385
- task: params.message
2291
+ try {
2292
+ const result = await slackService.sendMessage(params.target.to, params.content.text, {
2293
+ ...params.target.threadId ? { threadTs: String(params.target.threadId) } : {},
2294
+ replyBroadcast: false
2386
2295
  });
2387
2296
  return {
2388
- status: "accepted",
2389
- runId,
2390
- sessionKey: targetSessionKey,
2391
- delivery: { status: "pending", mode: "async" }
2297
+ success: true,
2298
+ messageId: result.ts,
2299
+ channel: "slack",
2300
+ targetId: params.target.to,
2301
+ sentAt: Date.now()
2302
+ };
2303
+ } catch (error) {
2304
+ return {
2305
+ success: false,
2306
+ channel: "slack",
2307
+ targetId: params.target.to,
2308
+ error: error instanceof Error ? error.message : String(error)
2392
2309
  };
2393
2310
  }
2394
- const sentAt = Date.now();
2395
- await this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
2396
- runtime: this.runtime,
2397
- message,
2398
- source: "a2a"
2399
- });
2400
- const pollIntervalMs = 500;
2401
- const maxPolls = Math.ceil(timeoutMs / pollIntervalMs);
2402
- let lastReply;
2403
- for (let poll = 0;poll < maxPolls; poll++) {
2404
- await new Promise((r) => setTimeout(r, pollIntervalMs));
2405
- const memories = await this.runtime.getMemories({
2406
- roomId: targetRoomId,
2407
- count: 10
2408
- });
2409
- const newReplies = memories.filter((m) => m.entityId === this.runtime.agentId && m.id !== message.id && m.createdAt && m.createdAt > sentAt);
2410
- if (newReplies.length > 0) {
2411
- lastReply = newReplies.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))[0];
2412
- break;
2413
- }
2311
+ }
2312
+ async sendViaWhatsApp(params) {
2313
+ const whatsappService = this.runtime.getService("whatsapp");
2314
+ if (!whatsappService?.sendText) {
2315
+ return {
2316
+ success: false,
2317
+ channel: "whatsapp",
2318
+ targetId: params.target.to,
2319
+ error: "WhatsApp service not available or sendText method not found"
2320
+ };
2414
2321
  }
2415
- this.emitSubagentEvent("A2A_MESSAGE_SENT", {
2416
- runId,
2417
- childSessionKey: targetSessionKey,
2418
- requesterSessionKey: requesterContext.sessionKey,
2419
- task: params.message
2420
- });
2421
- if (!lastReply) {
2322
+ try {
2323
+ const result = await whatsappService.sendText(params.target.to, params.content.text);
2324
+ const messageId = result.messages?.[0]?.id;
2422
2325
  return {
2423
- status: "timeout",
2424
- runId,
2425
- sessionKey: targetSessionKey,
2426
- error: `No response received within ${params.timeoutSeconds ?? 30} seconds`,
2427
- delivery: { status: "timeout", mode: "sync" }
2326
+ success: true,
2327
+ ...messageId ? { messageId } : {},
2328
+ channel: "whatsapp",
2329
+ targetId: params.target.to,
2330
+ sentAt: Date.now()
2331
+ };
2332
+ } catch (error) {
2333
+ return {
2334
+ success: false,
2335
+ channel: "whatsapp",
2336
+ targetId: params.target.to,
2337
+ error: error instanceof Error ? error.message : String(error)
2428
2338
  };
2429
2339
  }
2430
- return {
2431
- status: "ok",
2432
- runId,
2433
- sessionKey: targetSessionKey,
2434
- reply: lastReply.content?.text,
2435
- delivery: { status: "delivered", mode: "sync" }
2436
- };
2437
- }
2438
- buildAgentToAgentContext(params) {
2439
- return [
2440
- "# Agent-to-Agent Message Context",
2441
- "",
2442
- "This message was sent by another agent session.",
2443
- params.requesterSessionKey ? `- Sender: ${params.requesterSessionKey}` : undefined,
2444
- `- Target: ${params.targetSessionKey}`,
2445
- "",
2446
- "Process this message and respond appropriately."
2447
- ].filter((l) => l !== undefined).join(`
2448
- `);
2449
- }
2450
- getSubagentRun(runId) {
2451
- return this.subagentRuns.get(runId);
2452
2340
  }
2453
- findSubagentRunByLabel(label, agentId) {
2454
- const normalizedLabel = label.toLowerCase().trim();
2455
- const normalizedAgentId = agentId ? normalizeAgentId2(agentId) : undefined;
2456
- for (const run of this.subagentRuns.values()) {
2457
- const runLabel = run.label?.toLowerCase().trim();
2458
- if (runLabel !== normalizedLabel) {
2459
- continue;
2460
- }
2461
- if (normalizedAgentId) {
2462
- const runAgentId = extractAgentIdFromSessionKey(run.childSessionKey);
2463
- if (normalizeAgentId2(runAgentId) !== normalizedAgentId) {
2464
- continue;
2465
- }
2466
- }
2467
- if (!run.endedAt) {
2468
- return run;
2469
- }
2341
+ async sendViaTwitch(params) {
2342
+ const twitchService = this.runtime.getService("twitch");
2343
+ if (!twitchService?.sendMessage) {
2344
+ return {
2345
+ success: false,
2346
+ channel: "twitch",
2347
+ targetId: params.target.to,
2348
+ error: "Twitch service not available or sendMessage method not found"
2349
+ };
2350
+ }
2351
+ try {
2352
+ const result = await twitchService.sendMessage(params.content.text, {
2353
+ channel: params.target.to,
2354
+ ...params.target.replyToMessageId ? { replyTo: params.target.replyToMessageId } : {}
2355
+ });
2356
+ return {
2357
+ success: result.success,
2358
+ ...result.messageId !== undefined ? { messageId: result.messageId } : {},
2359
+ channel: "twitch",
2360
+ targetId: params.target.to,
2361
+ sentAt: Date.now()
2362
+ };
2363
+ } catch (error) {
2364
+ return {
2365
+ success: false,
2366
+ channel: "twitch",
2367
+ targetId: params.target.to,
2368
+ error: error instanceof Error ? error.message : String(error)
2369
+ };
2470
2370
  }
2471
- const completedRuns = [...this.subagentRuns.values()].filter((run) => {
2472
- const runLabel = run.label?.toLowerCase().trim();
2473
- if (runLabel !== normalizedLabel) {
2474
- return false;
2475
- }
2476
- if (normalizedAgentId) {
2477
- const runAgentId = extractAgentIdFromSessionKey(run.childSessionKey);
2478
- if (normalizeAgentId2(runAgentId) !== normalizedAgentId) {
2479
- return false;
2480
- }
2481
- }
2482
- return true;
2483
- }).sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
2484
- return completedRuns[0];
2485
2371
  }
2486
- listSubagentRuns(requesterSessionKey) {
2487
- const runs = [...this.subagentRuns.values()];
2488
- if (!requesterSessionKey) {
2489
- return runs;
2372
+ async sendViaInternal(params) {
2373
+ const roomId = params.target.to;
2374
+ const messageId = crypto2.randomUUID();
2375
+ try {
2376
+ const memory = {
2377
+ id: messageId,
2378
+ entityId: this.runtime.agentId,
2379
+ agentId: this.runtime.agentId,
2380
+ roomId,
2381
+ content: {
2382
+ text: params.content.text,
2383
+ type: "text",
2384
+ source: "internal",
2385
+ metadata: {
2386
+ isInternalMessage: true,
2387
+ idempotencyKey: params.idempotencyKey
2388
+ }
2389
+ },
2390
+ createdAt: Date.now()
2391
+ };
2392
+ await this.runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
2393
+ runtime: this.runtime,
2394
+ message: memory,
2395
+ source: "internal_messaging"
2396
+ });
2397
+ return {
2398
+ success: true,
2399
+ messageId,
2400
+ channel: "internal",
2401
+ targetId: params.target.to,
2402
+ sentAt: Date.now()
2403
+ };
2404
+ } catch (error) {
2405
+ return {
2406
+ success: false,
2407
+ channel: "internal",
2408
+ targetId: params.target.to,
2409
+ error: error instanceof Error ? error.message : String(error)
2410
+ };
2490
2411
  }
2491
- return runs.filter((r) => r.requesterSessionKey === requesterSessionKey);
2492
2412
  }
2493
- cancelSubagentRun(runId) {
2494
- const controller = this.activeRuns.get(runId);
2495
- if (controller) {
2496
- controller.abort();
2497
- return true;
2413
+ normalizeChannel(channel) {
2414
+ if (!channel) {
2415
+ return "unknown";
2498
2416
  }
2499
- return false;
2417
+ const lower = channel.toLowerCase();
2418
+ if (lower === "discord" || lower.includes("discord")) {
2419
+ return "discord";
2420
+ }
2421
+ if (lower === "telegram" || lower.includes("telegram")) {
2422
+ return "telegram";
2423
+ }
2424
+ if (lower === "slack" || lower.includes("slack")) {
2425
+ return "slack";
2426
+ }
2427
+ if (lower === "whatsapp" || lower.includes("whatsapp")) {
2428
+ return "whatsapp";
2429
+ }
2430
+ if (lower === "twitch" || lower.includes("twitch")) {
2431
+ return "twitch";
2432
+ }
2433
+ if (lower === "google_chat" || lower.includes("google") || lower.includes("gchat")) {
2434
+ return "google_chat";
2435
+ }
2436
+ if (lower === "internal" || lower === "a2a") {
2437
+ return "internal";
2438
+ }
2439
+ return "unknown";
2500
2440
  }
2501
2441
  on(event, handler) {
2502
2442
  this.emitter.on(event, handler);
@@ -2504,46 +2444,22 @@ class SubagentService extends Service2 {
2504
2444
  off(event, handler) {
2505
2445
  this.emitter.off(event, handler);
2506
2446
  }
2507
- emitSubagentEvent(type, payload) {
2447
+ emitMessagingEvent(type, payload) {
2508
2448
  this.emitter.emit(type, payload);
2509
- this.emitter.emit("task", { type, ...payload });
2510
- }
2511
- startSweeper() {
2512
- if (this.sweeper) {
2513
- return;
2514
- }
2515
- this.sweeper = setInterval(() => {
2516
- this.sweepOldRuns();
2517
- }, 60000);
2518
- this.sweeper.unref?.();
2519
- }
2520
- sweepOldRuns() {
2521
- const now2 = Date.now();
2522
- for (const [runId, record] of this.subagentRuns.entries()) {
2523
- if (record.archiveAtMs && record.archiveAtMs <= now2) {
2524
- this.subagentRuns.delete(runId);
2525
- }
2526
- }
2449
+ this.emitter.emit("messaging", { type, ...payload });
2527
2450
  }
2528
2451
  async stop() {
2529
- if (this.sweeper) {
2530
- clearInterval(this.sweeper);
2531
- this.sweeper = null;
2532
- }
2533
- for (const controller of this.activeRuns.values()) {
2534
- controller.abort();
2535
- }
2536
- this.activeRuns.clear();
2452
+ this.pendingDeliveries.clear();
2537
2453
  this.emitter.removeAllListeners();
2538
2454
  }
2539
2455
  }
2540
2456
 
2541
2457
  // src/services/sandbox-service.ts
2542
- import { EventEmitter as EventEmitter3 } from "node:events";
2543
2458
  import { spawn } from "node:child_process";
2459
+ import { EventEmitter as EventEmitter3 } from "node:events";
2544
2460
  import fs from "node:fs/promises";
2545
- import path from "node:path";
2546
2461
  import os from "node:os";
2462
+ import path from "node:path";
2547
2463
  import { Service as Service3 } from "@elizaos/core";
2548
2464
  var DEFAULT_DOCKER_CONFIG = {
2549
2465
  image: "ubuntu:22.04",
@@ -2684,7 +2600,7 @@ class SandboxService extends Service3 {
2684
2600
  this.contexts.set(trimmedKey, context);
2685
2601
  this.emitSandboxEvent("SANDBOX_CREATED", {
2686
2602
  sessionKey: trimmedKey,
2687
- roomId: context.roomId,
2603
+ ...context.roomId !== undefined ? { roomId: context.roomId } : {},
2688
2604
  containerName
2689
2605
  });
2690
2606
  return context;
@@ -2716,7 +2632,7 @@ class SandboxService extends Service3 {
2716
2632
  this.contexts.delete(sessionKey);
2717
2633
  this.emitSandboxEvent("SANDBOX_DESTROYED", {
2718
2634
  sessionKey,
2719
- roomId: context.roomId,
2635
+ ...context.roomId !== undefined ? { roomId: context.roomId } : {},
2720
2636
  containerName: context.containerName
2721
2637
  });
2722
2638
  }
@@ -2728,7 +2644,7 @@ class SandboxService extends Service3 {
2728
2644
  const startTime = Date.now();
2729
2645
  this.emitSandboxEvent("SANDBOX_COMMAND_STARTED", {
2730
2646
  sessionKey,
2731
- roomId: context.roomId,
2647
+ ...context.roomId !== undefined ? { roomId: context.roomId } : {},
2732
2648
  containerName: context.containerName,
2733
2649
  command: params.command
2734
2650
  });
@@ -2736,7 +2652,7 @@ class SandboxService extends Service3 {
2736
2652
  const result = await this.executeInContainer(context, params);
2737
2653
  this.emitSandboxEvent("SANDBOX_COMMAND_COMPLETED", {
2738
2654
  sessionKey,
2739
- roomId: context.roomId,
2655
+ ...context.roomId !== undefined ? { roomId: context.roomId } : {},
2740
2656
  containerName: context.containerName,
2741
2657
  command: params.command,
2742
2658
  result
@@ -2755,7 +2671,7 @@ class SandboxService extends Service3 {
2755
2671
  };
2756
2672
  this.emitSandboxEvent("SANDBOX_COMMAND_FAILED", {
2757
2673
  sessionKey,
2758
- roomId: context.roomId,
2674
+ ...context.roomId !== undefined ? { roomId: context.roomId } : {},
2759
2675
  containerName: context.containerName,
2760
2676
  command: params.command,
2761
2677
  result,
@@ -2792,15 +2708,17 @@ class SandboxService extends Service3 {
2792
2708
  }
2793
2709
  child.on("close", (code) => {
2794
2710
  clearTimeout(timeoutId);
2795
- resolve({
2711
+ const result = {
2796
2712
  success: code === 0 && !timedOut,
2797
2713
  exitCode: code ?? 1,
2798
2714
  stdout: stdout.slice(0, 1e5),
2799
2715
  stderr: stderr.slice(0, 1e5),
2800
2716
  durationMs: Date.now() - startTime,
2801
- timedOut,
2802
- error: timedOut ? "Command timed out" : undefined
2803
- });
2717
+ timedOut
2718
+ };
2719
+ if (timedOut)
2720
+ result.error = "Command timed out";
2721
+ resolve(result);
2804
2722
  });
2805
2723
  child.on("error", (error) => {
2806
2724
  clearTimeout(timeoutId);
@@ -2838,15 +2756,17 @@ class SandboxService extends Service3 {
2838
2756
  });
2839
2757
  child.on("close", (code) => {
2840
2758
  clearTimeout(timeoutId);
2841
- resolve({
2759
+ const result = {
2842
2760
  success: code === 0 && !timedOut,
2843
2761
  exitCode: code ?? 1,
2844
2762
  stdout: stdout.slice(0, 1e5),
2845
2763
  stderr: stderr.slice(0, 1e5),
2846
2764
  durationMs: Date.now() - startTime,
2847
- timedOut,
2848
- error: timedOut ? "Command timed out" : undefined
2849
- });
2765
+ timedOut
2766
+ };
2767
+ if (timedOut)
2768
+ result.error = "Command timed out";
2769
+ resolve(result);
2850
2770
  });
2851
2771
  child.on("error", (error) => {
2852
2772
  clearTimeout(timeoutId);
@@ -2897,15 +2817,17 @@ class SandboxService extends Service3 {
2897
2817
  }
2898
2818
  child.on("close", (code) => {
2899
2819
  clearTimeout(timeoutId);
2900
- resolve({
2820
+ const result = {
2901
2821
  success: code === 0 && !timedOut,
2902
2822
  exitCode: code ?? 1,
2903
2823
  stdout: stdout.slice(0, 1e5),
2904
2824
  stderr: stderr.slice(0, 1e5),
2905
2825
  durationMs: Date.now() - startTime,
2906
- timedOut,
2907
- error: timedOut ? "Command timed out" : undefined
2908
- });
2826
+ timedOut
2827
+ };
2828
+ if (timedOut)
2829
+ result.error = "Command timed out";
2830
+ resolve(result);
2909
2831
  });
2910
2832
  child.on("error", (error) => {
2911
2833
  clearTimeout(timeoutId);
@@ -2950,10 +2872,7 @@ class SandboxService extends Service3 {
2950
2872
  `name=^${context.containerName}$`
2951
2873
  ]);
2952
2874
  if (checkResult.stdout.trim()) {
2953
- const startResult = await this.executeDockerCommand([
2954
- "start",
2955
- context.containerName
2956
- ]);
2875
+ const startResult = await this.executeDockerCommand(["start", context.containerName]);
2957
2876
  if (startResult.success) {
2958
2877
  this.activeContainers.add(context.containerName);
2959
2878
  return;
@@ -3020,21 +2939,21 @@ class SandboxService extends Service3 {
3020
2939
  browserArgs.push(config.browser.image);
3021
2940
  const result = await this.executeDockerCommand(browserArgs);
3022
2941
  if (!result.success) {
3023
- this.runtime.logger.error("Failed to start browser container", {
2942
+ this.runtime.logger.error({
3024
2943
  sessionKey,
3025
2944
  error: result.stderr
3026
- });
2945
+ }, "Failed to start browser container");
3027
2946
  return null;
3028
2947
  }
3029
2948
  const browserContext = {
3030
2949
  bridgeUrl: `http://localhost:${config.browser.cdpPort}`,
3031
- noVncUrl: config.browser.enableNoVnc ? `http://localhost:${config.browser.noVncPort}` : undefined,
2950
+ ...config.browser.enableNoVnc ? { noVncUrl: `http://localhost:${config.browser.noVncPort}` } : {},
3032
2951
  containerName: browserContainerName
3033
2952
  };
3034
2953
  context.browser = browserContext;
3035
2954
  this.emitSandboxEvent("SANDBOX_BROWSER_STARTED", {
3036
2955
  sessionKey,
3037
- roomId: context.roomId,
2956
+ ...context.roomId !== undefined ? { roomId: context.roomId } : {},
3038
2957
  containerName: browserContainerName
3039
2958
  });
3040
2959
  return browserContext;
@@ -3047,10 +2966,10 @@ class SandboxService extends Service3 {
3047
2966
  await this.stopContainer(context.browser.containerName);
3048
2967
  this.emitSandboxEvent("SANDBOX_BROWSER_STOPPED", {
3049
2968
  sessionKey,
3050
- roomId: context.roomId,
2969
+ ...context.roomId !== undefined ? { roomId: context.roomId } : {},
3051
2970
  containerName: context.browser.containerName
3052
2971
  });
3053
- context.browser = undefined;
2972
+ delete context.browser;
3054
2973
  }
3055
2974
  startSweeper() {
3056
2975
  if (this.sweeper) {
@@ -3073,10 +2992,10 @@ class SandboxService extends Service3 {
3073
2992
  const tooOld = now2 - context.createdAt > maxAgeMs;
3074
2993
  if (idle || tooOld) {
3075
2994
  this.destroySandbox(sessionKey).catch((err) => {
3076
- this.runtime.logger.error("Failed to destroy idle sandbox", {
2995
+ this.runtime.logger.error({
3077
2996
  sessionKey,
3078
- error: err
3079
- });
2997
+ error: err instanceof Error ? err.message : String(err)
2998
+ }, "Failed to destroy idle sandbox");
3080
2999
  });
3081
3000
  }
3082
3001
  }
@@ -3103,19 +3022,24 @@ class SandboxService extends Service3 {
3103
3022
  }
3104
3023
  }
3105
3024
 
3106
- // src/services/messaging-service.ts
3107
- import { EventEmitter as EventEmitter4 } from "node:events";
3025
+ // src/services/subagent-service.ts
3108
3026
  import crypto3 from "node:crypto";
3109
- import { Service as Service4, EventType as EventType2 } from "@elizaos/core";
3110
- class MessagingService extends Service4 {
3111
- static serviceType = "MESSAGING";
3112
- capabilityDescription = "Unified cross-platform messaging for sending messages to any supported channel";
3027
+ import { EventEmitter as EventEmitter4 } from "node:events";
3028
+ import {
3029
+ ChannelType,
3030
+ EventType as EventType2,
3031
+ Service as Service4
3032
+ } from "@elizaos/core";
3033
+ class SubagentService extends Service4 {
3034
+ static serviceType = "SUBAGENT";
3035
+ capabilityDescription = "Manages subagent spawning, lifecycle, and communication";
3113
3036
  emitter = new EventEmitter4;
3114
- adapters = new Map;
3115
- pendingDeliveries = new Map;
3037
+ subagentRuns = new Map;
3038
+ activeRuns = new Map;
3039
+ sweeper = null;
3116
3040
  initialized = false;
3117
3041
  static async start(runtime) {
3118
- const service = new MessagingService(runtime);
3042
+ const service = new SubagentService(runtime);
3119
3043
  await service.initialize();
3120
3044
  return service;
3121
3045
  }
@@ -3124,472 +3048,636 @@ class MessagingService extends Service4 {
3124
3048
  return;
3125
3049
  }
3126
3050
  this.initialized = true;
3127
- this.registerBuiltInAdapters();
3128
- }
3129
- registerBuiltInAdapters() {
3130
- this.registerAdapter({
3131
- channel: "discord",
3132
- isAvailable: () => {
3133
- const service = this.runtime.getService("DISCORD");
3134
- return !!service;
3135
- },
3136
- send: async (params) => this.sendViaDiscord(params)
3137
- });
3138
- this.registerAdapter({
3139
- channel: "telegram",
3140
- isAvailable: () => {
3141
- const service = this.runtime.getService("TELEGRAM");
3142
- return !!service;
3143
- },
3144
- send: async (params) => this.sendViaTelegram(params)
3145
- });
3146
- this.registerAdapter({
3147
- channel: "slack",
3148
- isAvailable: () => {
3149
- const service = this.runtime.getService("slack");
3150
- return !!service;
3151
- },
3152
- send: async (params) => this.sendViaSlack(params)
3153
- });
3154
- this.registerAdapter({
3155
- channel: "whatsapp",
3156
- isAvailable: () => {
3157
- const service = this.runtime.getService("whatsapp");
3158
- return !!service;
3159
- },
3160
- send: async (params) => this.sendViaWhatsApp(params)
3161
- });
3162
- this.registerAdapter({
3163
- channel: "twitch",
3164
- isAvailable: () => {
3165
- const service = this.runtime.getService("twitch");
3166
- return !!service;
3167
- },
3168
- send: async (params) => this.sendViaTwitch(params)
3051
+ this.runtime.registerEvent(EventType2.RUN_ENDED, async (payload) => {
3052
+ await this.handleRunEnded(payload);
3169
3053
  });
3170
- this.registerAdapter({
3171
- channel: "internal",
3172
- isAvailable: () => true,
3173
- send: async (params) => this.sendViaInternal(params)
3054
+ this.runtime.registerEvent(EventType2.RUN_TIMEOUT, async (payload) => {
3055
+ await this.handleRunTimeout(payload);
3174
3056
  });
3057
+ this.startSweeper();
3175
3058
  }
3176
- registerAdapter(adapter) {
3177
- this.adapters.set(adapter.channel, adapter);
3178
- }
3179
- getAdapter(channel) {
3180
- return this.adapters.get(channel);
3181
- }
3182
- getAvailableChannels() {
3183
- const channels = [];
3184
- for (const [channel, adapter] of this.adapters) {
3185
- if (adapter.isAvailable()) {
3186
- channels.push(channel);
3187
- }
3188
- }
3189
- return channels;
3190
- }
3191
- async send(params) {
3192
- const idempotencyKey = params.idempotencyKey ?? crypto3.randomUUID();
3193
- const channel = params.target.channel;
3194
- const existing = this.pendingDeliveries.get(idempotencyKey);
3195
- if (existing) {
3196
- return {
3197
- success: existing.status.status === "sent" || existing.status.status === "delivered",
3198
- messageId: existing.status.messageId,
3199
- channel,
3200
- targetId: params.target.to,
3201
- error: existing.status.error,
3202
- sentAt: existing.status.status === "sent" ? existing.status.updatedAt : undefined
3203
- };
3204
- }
3205
- const status = {
3206
- status: "pending",
3207
- updatedAt: Date.now()
3059
+ getConfig() {
3060
+ const settings = this.runtime.character?.settings;
3061
+ const subagents = settings?.subagents ?? {};
3062
+ return {
3063
+ enabled: subagents.enabled !== false,
3064
+ ...subagents.model !== undefined ? { model: subagents.model } : {},
3065
+ ...subagents.thinking !== undefined ? { thinking: subagents.thinking } : {},
3066
+ timeoutSeconds: subagents.timeoutSeconds ?? 300,
3067
+ allowAgents: subagents.allowAgents ?? [],
3068
+ archiveAfterMinutes: subagents.archiveAfterMinutes ?? 60
3208
3069
  };
3209
- this.pendingDeliveries.set(idempotencyKey, { params, status });
3210
- this.emitMessagingEvent("MESSAGING_SEND_REQUESTED", {
3211
- idempotencyKey,
3212
- channel,
3213
- targetId: params.target.to,
3214
- status: "pending"
3215
- });
3216
- const adapter = this.adapters.get(channel);
3217
- if (!adapter) {
3218
- const result = {
3219
- success: false,
3220
- channel,
3221
- targetId: params.target.to,
3222
- error: `No adapter registered for channel: ${channel}`
3223
- };
3224
- status.status = "failed";
3225
- status.error = result.error;
3226
- status.updatedAt = Date.now();
3227
- this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
3228
- idempotencyKey,
3229
- channel,
3230
- targetId: params.target.to,
3231
- status: "failed",
3232
- error: result.error
3233
- });
3234
- return result;
3235
- }
3236
- if (!adapter.isAvailable()) {
3237
- const result = {
3238
- success: false,
3239
- channel,
3240
- targetId: params.target.to,
3241
- error: `${channel} service is not available`
3242
- };
3243
- status.status = "failed";
3244
- status.error = result.error;
3245
- status.updatedAt = Date.now();
3246
- this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
3247
- idempotencyKey,
3248
- channel,
3249
- targetId: params.target.to,
3250
- status: "failed",
3251
- error: result.error
3252
- });
3253
- return result;
3254
- }
3255
- try {
3256
- const result = await adapter.send({ ...params, idempotencyKey });
3257
- status.status = result.success ? "sent" : "failed";
3258
- status.messageId = result.messageId;
3259
- status.error = result.error;
3260
- status.updatedAt = Date.now();
3261
- if (result.success) {
3262
- this.emitMessagingEvent("MESSAGING_SENT", {
3263
- idempotencyKey,
3264
- messageId: result.messageId,
3265
- channel,
3266
- targetId: params.target.to,
3267
- status: "sent",
3268
- sentAt: result.sentAt
3269
- });
3270
- } else {
3271
- this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
3272
- idempotencyKey,
3273
- channel,
3274
- targetId: params.target.to,
3275
- status: "failed",
3276
- error: result.error
3277
- });
3070
+ }
3071
+ getAgentToAgentPolicy() {
3072
+ const settings = this.runtime.character?.settings;
3073
+ const a2aConfig = settings?.agentToAgent ?? {};
3074
+ const enabled = a2aConfig.enabled === true;
3075
+ const allowRules = (a2aConfig.allow ?? []).map((rule) => ({
3076
+ source: rule.source ?? "*",
3077
+ target: rule.target ?? "*"
3078
+ }));
3079
+ return {
3080
+ enabled,
3081
+ allowRules,
3082
+ isAllowed: (sourceAgentId, targetAgentId) => {
3083
+ if (!enabled) {
3084
+ return false;
3085
+ }
3086
+ if (sourceAgentId === targetAgentId) {
3087
+ return true;
3088
+ }
3089
+ const sourceNorm = normalizeAgentId2(sourceAgentId);
3090
+ const targetNorm = normalizeAgentId2(targetAgentId);
3091
+ for (const rule of allowRules) {
3092
+ const sourceMatch = rule.source === "*" || normalizeAgentId2(rule.source) === sourceNorm;
3093
+ const targetMatch = rule.target === "*" || normalizeAgentId2(rule.target) === targetNorm;
3094
+ if (sourceMatch && targetMatch) {
3095
+ return true;
3096
+ }
3097
+ }
3098
+ return false;
3278
3099
  }
3279
- return result;
3280
- } catch (error) {
3281
- const errorMessage = error instanceof Error ? error.message : String(error);
3282
- status.status = "failed";
3283
- status.error = errorMessage;
3284
- status.updatedAt = Date.now();
3285
- this.emitMessagingEvent("MESSAGING_SEND_FAILED", {
3286
- idempotencyKey,
3287
- channel,
3288
- targetId: params.target.to,
3289
- status: "failed",
3290
- error: errorMessage
3291
- });
3100
+ };
3101
+ }
3102
+ async spawnSubagent(params, requesterContext) {
3103
+ const config = this.getConfig();
3104
+ if (!config.enabled) {
3292
3105
  return {
3293
- success: false,
3294
- channel,
3295
- targetId: params.target.to,
3296
- error: errorMessage
3106
+ status: "forbidden",
3107
+ error: "Subagent spawning is disabled"
3297
3108
  };
3298
3109
  }
3299
- }
3300
- async sendToDeliveryContext(deliveryContext, content, options) {
3301
- const channel = this.normalizeChannel(deliveryContext.channel);
3302
- const to = deliveryContext.to ?? deliveryContext.accountId ?? "";
3303
- if (!to) {
3110
+ if (requesterContext.sessionKey && isSubagentSessionKey2(requesterContext.sessionKey)) {
3304
3111
  return {
3305
- success: false,
3306
- channel,
3307
- targetId: "",
3308
- error: "No recipient specified in delivery context"
3112
+ status: "forbidden",
3113
+ error: "sessions_spawn is not allowed from sub-agent sessions"
3309
3114
  };
3310
3115
  }
3311
- return this.send({
3312
- target: {
3313
- channel,
3314
- to,
3315
- accountId: deliveryContext.accountId,
3316
- threadId: deliveryContext.threadId
3317
- },
3318
- content,
3319
- idempotencyKey: options?.idempotencyKey,
3320
- timeoutMs: options?.timeoutMs
3116
+ const requesterAgentId = requesterContext.sessionKey ? extractAgentIdFromSessionKey(requesterContext.sessionKey) : this.runtime.character?.name ?? "unknown";
3117
+ const targetAgentId = params.agentId ? normalizeAgentId2(params.agentId) : requesterAgentId;
3118
+ if (targetAgentId !== requesterAgentId) {
3119
+ const allowAgents = config.allowAgents ?? [];
3120
+ const allowAny = allowAgents.some((v) => v.trim() === "*");
3121
+ const allowSet = new Set(allowAgents.filter((v) => v.trim() && v.trim() !== "*").map((v) => normalizeAgentId2(v)));
3122
+ if (!allowAny && !allowSet.has(targetAgentId)) {
3123
+ return {
3124
+ status: "forbidden",
3125
+ error: `agentId "${targetAgentId}" is not allowed for subagent spawning`
3126
+ };
3127
+ }
3128
+ }
3129
+ const childSessionKey = createSubagentSessionKey(targetAgentId);
3130
+ const runId = crypto3.randomUUID();
3131
+ const childRoomId = sessionKeyToRoomId(childSessionKey, targetAgentId);
3132
+ const roomMetadata = {
3133
+ isSubagent: true,
3134
+ sessionKey: childSessionKey,
3135
+ task: params.task,
3136
+ spawnedAt: Date.now(),
3137
+ cleanup: params.cleanup ?? "keep"
3138
+ };
3139
+ if (requesterContext.roomId)
3140
+ roomMetadata.parentRoomId = requesterContext.roomId;
3141
+ if (requesterContext.sessionKey)
3142
+ roomMetadata.parentSessionKey = requesterContext.sessionKey;
3143
+ if (params.label)
3144
+ roomMetadata.label = params.label;
3145
+ const childRoom = {
3146
+ id: childRoomId,
3147
+ name: params.label || `Subagent: ${params.task.slice(0, 50)}`,
3148
+ source: "subagent",
3149
+ type: ChannelType.SELF,
3150
+ channelId: childSessionKey,
3151
+ agentId: this.runtime.agentId,
3152
+ worldId: this.runtime.agentId,
3153
+ metadata: roomMetadata
3154
+ };
3155
+ await this.runtime.ensureRoomExists(childRoom);
3156
+ const now2 = Date.now();
3157
+ const archiveAfterMs = config.archiveAfterMinutes ? config.archiveAfterMinutes * 60000 : undefined;
3158
+ const record = {
3159
+ runId,
3160
+ childSessionKey,
3161
+ requesterSessionKey: requesterContext.sessionKey ?? "unknown",
3162
+ requesterDisplayKey: requesterContext.sessionKey ?? "main",
3163
+ task: params.task,
3164
+ cleanup: params.cleanup ?? "keep",
3165
+ createdAt: now2,
3166
+ startedAt: now2,
3167
+ cleanupHandled: false,
3168
+ roomId: childRoomId,
3169
+ worldId: this.runtime.agentId
3170
+ };
3171
+ const normalizedOrigin = normalizeDeliveryContext(requesterContext.origin);
3172
+ if (normalizedOrigin)
3173
+ record.requesterOrigin = normalizedOrigin;
3174
+ if (params.label)
3175
+ record.label = params.label;
3176
+ if (archiveAfterMs)
3177
+ record.archiveAtMs = now2 + archiveAfterMs;
3178
+ this.subagentRuns.set(runId, record);
3179
+ const spawnPayload = {
3180
+ runId,
3181
+ childSessionKey,
3182
+ childRoomId,
3183
+ task: params.task
3184
+ };
3185
+ if (requesterContext.sessionKey)
3186
+ spawnPayload.requesterSessionKey = requesterContext.sessionKey;
3187
+ if (requesterContext.roomId)
3188
+ spawnPayload.requesterRoomId = requesterContext.roomId;
3189
+ if (params.label)
3190
+ spawnPayload.label = params.label;
3191
+ this.emitSubagentEvent("SUBAGENT_SPAWN_REQUESTED", spawnPayload);
3192
+ const systemPromptParams = {
3193
+ childSessionKey,
3194
+ task: params.task
3195
+ };
3196
+ if (requesterContext.sessionKey)
3197
+ systemPromptParams.requesterSessionKey = requesterContext.sessionKey;
3198
+ if (requesterContext.origin)
3199
+ systemPromptParams.requesterOrigin = requesterContext.origin;
3200
+ if (params.label)
3201
+ systemPromptParams.label = params.label;
3202
+ const systemPrompt = this.buildSubagentSystemPrompt(systemPromptParams);
3203
+ const taskMetadata = {
3204
+ isSubagentTask: true,
3205
+ runId,
3206
+ systemPromptOverride: systemPrompt
3207
+ };
3208
+ const modelOverride = params.model || config.model;
3209
+ const thinkingOverride = params.thinking || config.thinking;
3210
+ if (modelOverride)
3211
+ taskMetadata.modelOverride = modelOverride;
3212
+ if (thinkingOverride)
3213
+ taskMetadata.thinkingOverride = thinkingOverride;
3214
+ const initialMessage = {
3215
+ id: hashToUUID(`${runId}-initial`),
3216
+ entityId: this.runtime.agentId,
3217
+ agentId: this.runtime.agentId,
3218
+ roomId: childRoomId,
3219
+ content: {
3220
+ text: params.task,
3221
+ type: "text",
3222
+ metadata: taskMetadata
3223
+ }
3224
+ };
3225
+ this.executeSubagentRun(runId, initialMessage, params.runTimeoutSeconds).catch((error) => {
3226
+ this.runtime.logger.error(`Subagent execution error [runId=${runId}]: ${error}`);
3227
+ this.handleSubagentError(runId, error);
3321
3228
  });
3229
+ return {
3230
+ status: "accepted",
3231
+ childSessionKey,
3232
+ childRoomId,
3233
+ runId,
3234
+ modelApplied: !!(params.model || config.model)
3235
+ };
3322
3236
  }
3323
- async sendToRoom(roomId, content, options) {
3324
- const room = await this.runtime.getRoom(roomId);
3325
- if (!room) {
3326
- return {
3327
- success: false,
3328
- channel: "unknown",
3329
- targetId: roomId,
3330
- error: `Room not found: ${roomId}`
3331
- };
3237
+ async executeSubagentRun(runId, initialMessage, timeoutSeconds) {
3238
+ const config = this.getConfig();
3239
+ const timeout = (timeoutSeconds ?? config.timeoutSeconds ?? 300) * 1000;
3240
+ const controller = new AbortController;
3241
+ this.activeRuns.set(runId, controller);
3242
+ const timeoutId = timeout > 0 ? setTimeout(() => {
3243
+ controller.abort();
3244
+ this.handleSubagentTimeout(runId);
3245
+ }, timeout) : null;
3246
+ try {
3247
+ await this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
3248
+ runtime: this.runtime,
3249
+ message: initialMessage,
3250
+ source: "subagent"
3251
+ });
3252
+ await this.waitForCompletion(runId, timeout, controller.signal);
3253
+ } finally {
3254
+ if (timeoutId) {
3255
+ clearTimeout(timeoutId);
3256
+ }
3257
+ this.activeRuns.delete(runId);
3332
3258
  }
3333
- const metadata = room.metadata;
3334
- const channel = this.normalizeChannel(metadata?.messagingChannel);
3335
- const to = metadata?.messagingTo ?? room.channelId ?? "";
3336
- if (!to) {
3337
- return {
3338
- success: false,
3339
- channel,
3340
- targetId: roomId,
3341
- error: "Room has no messaging target configured"
3342
- };
3259
+ }
3260
+ async waitForCompletion(runId, timeoutMs, signal) {
3261
+ const startTime = Date.now();
3262
+ const pollInterval = 500;
3263
+ while (!signal.aborted) {
3264
+ const record = this.subagentRuns.get(runId);
3265
+ if (!record) {
3266
+ return;
3267
+ }
3268
+ if (record.endedAt) {
3269
+ return;
3270
+ }
3271
+ if (Date.now() - startTime > timeoutMs + 5000) {
3272
+ return;
3273
+ }
3274
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
3343
3275
  }
3344
- return this.send({
3345
- target: {
3346
- channel,
3347
- to,
3348
- accountId: metadata?.messagingAccountId,
3349
- threadId: metadata?.messagingThreadId
3350
- },
3351
- content,
3352
- idempotencyKey: options?.idempotencyKey,
3353
- timeoutMs: options?.timeoutMs
3276
+ }
3277
+ handleSubagentTimeout(runId) {
3278
+ const record = this.subagentRuns.get(runId);
3279
+ if (!record || record.endedAt) {
3280
+ return;
3281
+ }
3282
+ record.endedAt = Date.now();
3283
+ record.outcome = { status: "timeout" };
3284
+ const timeoutPayload = {
3285
+ runId,
3286
+ childSessionKey: record.childSessionKey,
3287
+ task: record.task,
3288
+ status: "timeout"
3289
+ };
3290
+ if (record.roomId)
3291
+ timeoutPayload.childRoomId = record.roomId;
3292
+ this.emitSubagentEvent("SUBAGENT_RUN_TIMEOUT", timeoutPayload);
3293
+ this.announceSubagentResult(runId).catch((err) => {
3294
+ this.runtime.logger.error(`Failed to announce timeout [runId=${runId}]: ${err}`);
3354
3295
  });
3355
3296
  }
3356
- async sendToSession(sessionKey, content, options) {
3357
- const agentId = extractAgentIdFromSessionKey(sessionKey);
3358
- const roomId = sessionKeyToRoomId(sessionKey, agentId);
3359
- return this.sendToRoom(roomId, content, options);
3297
+ handleSubagentError(runId, error) {
3298
+ const record = this.subagentRuns.get(runId);
3299
+ if (!record) {
3300
+ return;
3301
+ }
3302
+ record.endedAt = Date.now();
3303
+ record.outcome = {
3304
+ status: "error",
3305
+ error: error instanceof Error ? error.message : String(error)
3306
+ };
3307
+ const errorPayload = {
3308
+ runId,
3309
+ childSessionKey: record.childSessionKey,
3310
+ task: record.task,
3311
+ status: "error"
3312
+ };
3313
+ if (record.outcome.error)
3314
+ errorPayload.error = record.outcome.error;
3315
+ if (record.roomId)
3316
+ errorPayload.childRoomId = record.roomId;
3317
+ this.emitSubagentEvent("SUBAGENT_RUN_FAILED", errorPayload);
3318
+ this.announceSubagentResult(runId).catch((err) => {
3319
+ this.runtime.logger.error(`Failed to announce error [runId=${runId}]: ${err}`);
3320
+ });
3360
3321
  }
3361
- async sendViaDiscord(params) {
3362
- const discordService = this.runtime.getService("DISCORD");
3363
- if (!discordService?.client) {
3364
- return {
3365
- success: false,
3366
- channel: "discord",
3367
- targetId: params.target.to,
3368
- error: "Discord service not available"
3369
- };
3322
+ async handleRunEnded(payload) {
3323
+ const p = payload;
3324
+ if (!p.roomId) {
3325
+ return;
3370
3326
  }
3371
- try {
3372
- const channel = await discordService.client.channels.fetch(params.target.to);
3373
- if (!channel?.send) {
3374
- return {
3375
- success: false,
3376
- channel: "discord",
3377
- targetId: params.target.to,
3378
- error: "Channel not found or not a text channel"
3327
+ for (const [runId, record] of this.subagentRuns.entries()) {
3328
+ if (record.roomId === p.roomId && !record.endedAt) {
3329
+ record.endedAt = Date.now();
3330
+ record.outcome = { status: "ok" };
3331
+ const completedPayload = {
3332
+ runId,
3333
+ childSessionKey: record.childSessionKey,
3334
+ childRoomId: record.roomId,
3335
+ task: record.task,
3336
+ status: "completed",
3337
+ endedAt: record.endedAt,
3338
+ durationMs: record.endedAt - (record.startedAt ?? record.createdAt)
3379
3339
  };
3340
+ if (record.startedAt)
3341
+ completedPayload.startedAt = record.startedAt;
3342
+ this.emitSubagentEvent("SUBAGENT_RUN_COMPLETED", completedPayload);
3343
+ await this.announceSubagentResult(runId);
3344
+ break;
3380
3345
  }
3381
- const message = await channel.send({
3382
- content: params.content.text,
3383
- ...params.target.replyToMessageId ? { reply: { messageReference: params.target.replyToMessageId } } : {}
3384
- });
3385
- return {
3386
- success: true,
3387
- messageId: message.id,
3388
- channel: "discord",
3389
- targetId: params.target.to,
3390
- sentAt: Date.now()
3391
- };
3392
- } catch (error) {
3393
- return {
3394
- success: false,
3395
- channel: "discord",
3396
- targetId: params.target.to,
3397
- error: error instanceof Error ? error.message : String(error)
3398
- };
3399
3346
  }
3400
3347
  }
3401
- async sendViaTelegram(params) {
3402
- const telegramService = this.runtime.getService("TELEGRAM");
3403
- if (!telegramService?.bot?.telegram) {
3404
- return {
3405
- success: false,
3406
- channel: "telegram",
3407
- targetId: params.target.to,
3408
- error: "Telegram service not available"
3409
- };
3348
+ async handleRunTimeout(payload) {
3349
+ const p = payload;
3350
+ if (!p.roomId) {
3351
+ return;
3410
3352
  }
3411
- try {
3412
- const chatId = Number.isNaN(Number(params.target.to)) ? params.target.to : Number(params.target.to);
3413
- const result = await telegramService.bot.telegram.sendMessage(chatId, params.content.text, {
3414
- reply_to_message_id: params.target.replyToMessageId ? Number(params.target.replyToMessageId) : undefined,
3415
- disable_web_page_preview: params.content.disableLinkPreview,
3416
- disable_notification: params.content.silent
3417
- });
3418
- return {
3419
- success: true,
3420
- messageId: String(result.message_id),
3421
- channel: "telegram",
3422
- targetId: params.target.to,
3423
- sentAt: Date.now()
3424
- };
3425
- } catch (error) {
3426
- return {
3427
- success: false,
3428
- channel: "telegram",
3429
- targetId: params.target.to,
3430
- error: error instanceof Error ? error.message : String(error)
3431
- };
3353
+ for (const [runId, record] of this.subagentRuns.entries()) {
3354
+ if (record.roomId === p.roomId && !record.endedAt) {
3355
+ this.handleSubagentTimeout(runId);
3356
+ break;
3357
+ }
3432
3358
  }
3433
3359
  }
3434
- async sendViaSlack(params) {
3435
- const slackService = this.runtime.getService("slack");
3436
- if (!slackService?.sendMessage) {
3437
- return {
3438
- success: false,
3439
- channel: "slack",
3440
- targetId: params.target.to,
3441
- error: "Slack service not available or sendMessage method not found"
3442
- };
3360
+ async announceSubagentResult(runId) {
3361
+ const record = this.subagentRuns.get(runId);
3362
+ if (!record) {
3363
+ return false;
3443
3364
  }
3444
- try {
3445
- const result = await slackService.sendMessage(params.target.to, params.content.text, {
3446
- threadTs: params.target.threadId ? String(params.target.threadId) : undefined,
3447
- replyBroadcast: false
3448
- });
3449
- return {
3450
- success: true,
3451
- messageId: result.ts,
3452
- channel: "slack",
3453
- targetId: params.target.to,
3454
- sentAt: Date.now()
3455
- };
3456
- } catch (error) {
3457
- return {
3458
- success: false,
3459
- channel: "slack",
3460
- targetId: params.target.to,
3461
- error: error instanceof Error ? error.message : String(error)
3462
- };
3365
+ if (record.cleanupCompletedAt || record.cleanupHandled) {
3366
+ return false;
3463
3367
  }
3464
- }
3465
- async sendViaWhatsApp(params) {
3466
- const whatsappService = this.runtime.getService("whatsapp");
3467
- if (!whatsappService?.sendText) {
3468
- return {
3469
- success: false,
3470
- channel: "whatsapp",
3471
- targetId: params.target.to,
3472
- error: "WhatsApp service not available or sendText method not found"
3473
- };
3368
+ record.cleanupHandled = true;
3369
+ let reply;
3370
+ if (record.roomId) {
3371
+ const memories = await this.runtime.getMemories({
3372
+ tableName: "messages",
3373
+ roomId: record.roomId,
3374
+ count: 10
3375
+ });
3376
+ const lastAssistant = memories.filter((m) => m.entityId === this.runtime.agentId).sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))[0];
3377
+ reply = lastAssistant?.content?.text;
3474
3378
  }
3475
- try {
3476
- const result = await whatsappService.sendText(params.target.to, params.content.text);
3477
- const messageId = result.messages?.[0]?.id;
3478
- return {
3479
- success: true,
3480
- messageId,
3481
- channel: "whatsapp",
3482
- targetId: params.target.to,
3483
- sentAt: Date.now()
3379
+ const durationMs = record.endedAt ? record.endedAt - (record.startedAt ?? record.createdAt) : undefined;
3380
+ const statsLine = `Runtime: ${formatDurationShort(durationMs) ?? "n/a"} • Session: ${record.childSessionKey}`;
3381
+ const outcome = record.outcome ?? { status: "unknown" };
3382
+ const statusLabel = outcome.status === "ok" ? "completed successfully" : outcome.status === "timeout" ? "timed out" : outcome.status === "error" ? `failed: ${outcome.error || "unknown error"}` : "finished with unknown status";
3383
+ const taskLabel = record.label || record.task || "background task";
3384
+ const triggerMessage = [
3385
+ `A background task "${taskLabel}" just ${statusLabel}.`,
3386
+ "",
3387
+ "Findings:",
3388
+ reply || "(no output)",
3389
+ "",
3390
+ statsLine,
3391
+ "",
3392
+ "Summarize this naturally for the user. Keep it brief (1-2 sentences).",
3393
+ "Do not mention technical details like tokens, stats, or that this was a background task.",
3394
+ "You can respond with NO_REPLY if no announcement is needed."
3395
+ ].join(`
3396
+ `);
3397
+ if (record.requesterSessionKey && record.requesterSessionKey !== "unknown") {
3398
+ const requesterRoomId = sessionKeyToRoomId(record.requesterSessionKey, extractAgentIdFromSessionKey(record.requesterSessionKey));
3399
+ const metadata = {
3400
+ isSubagentAnnouncement: true,
3401
+ subagentRunId: runId
3484
3402
  };
3485
- } catch (error) {
3486
- return {
3487
- success: false,
3488
- channel: "whatsapp",
3489
- targetId: params.target.to,
3490
- error: error instanceof Error ? error.message : String(error)
3403
+ if (record.requesterOrigin) {
3404
+ metadata.deliveryContext = record.requesterOrigin;
3405
+ }
3406
+ const announceMessage = {
3407
+ id: hashToUUID(`${runId}-announce`),
3408
+ entityId: this.runtime.agentId,
3409
+ agentId: this.runtime.agentId,
3410
+ roomId: requesterRoomId,
3411
+ content: {
3412
+ text: triggerMessage,
3413
+ type: "text",
3414
+ metadata
3415
+ }
3491
3416
  };
3417
+ await this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
3418
+ runtime: this.runtime,
3419
+ message: announceMessage,
3420
+ source: "subagent_announce"
3421
+ });
3492
3422
  }
3423
+ const announcePayload = {
3424
+ runId,
3425
+ childSessionKey: record.childSessionKey,
3426
+ task: record.task,
3427
+ outcome
3428
+ };
3429
+ if (record.requesterSessionKey)
3430
+ announcePayload.requesterSessionKey = record.requesterSessionKey;
3431
+ this.emitSubagentEvent("SUBAGENT_ANNOUNCE_SENT", announcePayload);
3432
+ record.cleanupCompletedAt = Date.now();
3433
+ if (record.cleanup === "delete") {
3434
+ this.subagentRuns.delete(runId);
3435
+ }
3436
+ return true;
3493
3437
  }
3494
- async sendViaTwitch(params) {
3495
- const twitchService = this.runtime.getService("twitch");
3496
- if (!twitchService?.sendMessage) {
3438
+ buildSubagentSystemPrompt(params) {
3439
+ const taskText = typeof params.task === "string" && params.task.trim() ? params.task.replace(/\s+/g, " ").trim() : "{{TASK_DESCRIPTION}}";
3440
+ const lines = [
3441
+ "# Subagent Context",
3442
+ "",
3443
+ "You are a **subagent** spawned by the main agent for a specific task.",
3444
+ "",
3445
+ "## Your Role",
3446
+ `- You were created to handle: ${taskText}`,
3447
+ "- Complete this task. That's your entire purpose.",
3448
+ "- You are NOT the main agent. Don't try to be.",
3449
+ "",
3450
+ "## Rules",
3451
+ "1. **Stay focused** - Do your assigned task, nothing else",
3452
+ "2. **Complete the task** - Your final message will be automatically reported to the main agent",
3453
+ "3. **Don't initiate** - No heartbeats, no proactive actions, no side quests",
3454
+ "4. **Be ephemeral** - You may be terminated after task completion. That's fine.",
3455
+ "",
3456
+ "## Output Format",
3457
+ "When complete, your final response should include:",
3458
+ "- What you accomplished or found",
3459
+ "- Any relevant details the main agent should know",
3460
+ "- Keep it concise but informative",
3461
+ "",
3462
+ "## What You DON'T Do",
3463
+ "- NO user conversations (that's main agent's job)",
3464
+ "- NO external messages unless explicitly tasked with a specific recipient",
3465
+ "- NO cron jobs or persistent state",
3466
+ "- NO pretending to be the main agent",
3467
+ "",
3468
+ "## Session Context",
3469
+ params.label ? `- Label: ${params.label}` : undefined,
3470
+ params.requesterSessionKey ? `- Requester session: ${params.requesterSessionKey}` : undefined,
3471
+ params.requesterOrigin?.channel ? `- Requester channel: ${params.requesterOrigin.channel}` : undefined,
3472
+ `- Your session: ${params.childSessionKey}`,
3473
+ ""
3474
+ ].filter((line) => line !== undefined);
3475
+ return lines.join(`
3476
+ `);
3477
+ }
3478
+ async sendToAgent(params, requesterContext) {
3479
+ const runId = crypto3.randomUUID();
3480
+ const policy = this.getAgentToAgentPolicy();
3481
+ let targetSessionKey = params.sessionKey;
3482
+ if (!targetSessionKey && params.label) {
3483
+ const matchingRun = this.findSubagentRunByLabel(params.label, params.agentId);
3484
+ if (matchingRun) {
3485
+ targetSessionKey = matchingRun.childSessionKey;
3486
+ } else {
3487
+ return {
3488
+ status: "error",
3489
+ runId,
3490
+ error: `No subagent found with label "${params.label}"`
3491
+ };
3492
+ }
3493
+ }
3494
+ if (!targetSessionKey) {
3497
3495
  return {
3498
- success: false,
3499
- channel: "twitch",
3500
- targetId: params.target.to,
3501
- error: "Twitch service not available or sendMessage method not found"
3496
+ status: "error",
3497
+ runId,
3498
+ error: "Either sessionKey or label is required"
3502
3499
  };
3503
3500
  }
3504
- try {
3505
- const result = await twitchService.sendMessage(params.content.text, {
3506
- channel: params.target.to,
3507
- replyTo: params.target.replyToMessageId
3501
+ const requesterAgentId = requesterContext.sessionKey ? extractAgentIdFromSessionKey(requesterContext.sessionKey) : this.runtime.character?.name ?? "unknown";
3502
+ const targetAgentId = extractAgentIdFromSessionKey(targetSessionKey);
3503
+ if (requesterAgentId !== targetAgentId) {
3504
+ if (!policy.enabled) {
3505
+ return {
3506
+ status: "forbidden",
3507
+ runId,
3508
+ error: "Agent-to-agent messaging is disabled. Set settings.agentToAgent.enabled=true to allow cross-agent sends."
3509
+ };
3510
+ }
3511
+ if (!policy.isAllowed(requesterAgentId, targetAgentId)) {
3512
+ return {
3513
+ status: "forbidden",
3514
+ runId,
3515
+ error: "Agent-to-agent messaging denied by policy."
3516
+ };
3517
+ }
3518
+ }
3519
+ const targetRoomId = sessionKeyToRoomId(targetSessionKey, targetAgentId);
3520
+ const a2aContextParams = {
3521
+ targetSessionKey
3522
+ };
3523
+ if (requesterContext.sessionKey)
3524
+ a2aContextParams.requesterSessionKey = requesterContext.sessionKey;
3525
+ const contextMessage = this.buildAgentToAgentContext(a2aContextParams);
3526
+ const a2aMetadata = {
3527
+ isAgentToAgent: true,
3528
+ runId,
3529
+ systemPromptOverride: contextMessage
3530
+ };
3531
+ if (requesterContext.sessionKey)
3532
+ a2aMetadata.senderSessionKey = requesterContext.sessionKey;
3533
+ if (requesterContext.roomId)
3534
+ a2aMetadata.senderRoomId = requesterContext.roomId;
3535
+ const message = {
3536
+ id: hashToUUID(`${runId}-a2a`),
3537
+ entityId: this.runtime.agentId,
3538
+ agentId: this.runtime.agentId,
3539
+ roomId: targetRoomId,
3540
+ content: {
3541
+ text: params.message,
3542
+ type: "text",
3543
+ metadata: a2aMetadata
3544
+ }
3545
+ };
3546
+ const timeoutMs = (params.timeoutSeconds ?? 30) * 1000;
3547
+ if (params.timeoutSeconds === 0) {
3548
+ this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
3549
+ runtime: this.runtime,
3550
+ message,
3551
+ source: "a2a"
3552
+ }).catch((err) => {
3553
+ this.runtime.logger.error(`A2A send error [runId=${runId}]: ${err}`);
3508
3554
  });
3509
- return {
3510
- success: result.success,
3511
- messageId: result.messageId,
3512
- channel: "twitch",
3513
- targetId: params.target.to,
3514
- sentAt: Date.now()
3555
+ const asyncPayload = {
3556
+ runId,
3557
+ childSessionKey: targetSessionKey,
3558
+ task: params.message
3515
3559
  };
3516
- } catch (error) {
3560
+ if (requesterContext.sessionKey)
3561
+ asyncPayload.requesterSessionKey = requesterContext.sessionKey;
3562
+ this.emitSubagentEvent("A2A_MESSAGE_SENT", asyncPayload);
3517
3563
  return {
3518
- success: false,
3519
- channel: "twitch",
3520
- targetId: params.target.to,
3521
- error: error instanceof Error ? error.message : String(error)
3564
+ status: "accepted",
3565
+ runId,
3566
+ sessionKey: targetSessionKey,
3567
+ delivery: { status: "pending", mode: "async" }
3522
3568
  };
3523
3569
  }
3524
- }
3525
- async sendViaInternal(params) {
3526
- const roomId = params.target.to;
3527
- const messageId = crypto3.randomUUID();
3528
- try {
3529
- const memory = {
3530
- id: messageId,
3531
- entityId: this.runtime.agentId,
3532
- agentId: this.runtime.agentId,
3533
- roomId,
3534
- content: {
3535
- text: params.content.text,
3536
- type: "text",
3537
- source: "internal",
3538
- metadata: {
3539
- isInternalMessage: true,
3540
- idempotencyKey: params.idempotencyKey
3541
- }
3542
- },
3543
- createdAt: Date.now()
3544
- };
3545
- await this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
3546
- runtime: this.runtime,
3547
- message: memory,
3548
- source: "internal_messaging"
3570
+ const sentAt = Date.now();
3571
+ await this.runtime.emitEvent(EventType2.MESSAGE_RECEIVED, {
3572
+ runtime: this.runtime,
3573
+ message,
3574
+ source: "a2a"
3575
+ });
3576
+ const pollIntervalMs = 500;
3577
+ const maxPolls = Math.ceil(timeoutMs / pollIntervalMs);
3578
+ let lastReply;
3579
+ for (let poll = 0;poll < maxPolls; poll++) {
3580
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
3581
+ const memories = await this.runtime.getMemories({
3582
+ tableName: "messages",
3583
+ roomId: targetRoomId,
3584
+ count: 10
3549
3585
  });
3586
+ const newReplies = memories.filter((m) => m.entityId === this.runtime.agentId && m.id !== message.id && m.createdAt && m.createdAt > sentAt);
3587
+ if (newReplies.length > 0) {
3588
+ lastReply = newReplies.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))[0];
3589
+ break;
3590
+ }
3591
+ }
3592
+ const syncPayload = {
3593
+ runId,
3594
+ childSessionKey: targetSessionKey,
3595
+ task: params.message
3596
+ };
3597
+ if (requesterContext.sessionKey)
3598
+ syncPayload.requesterSessionKey = requesterContext.sessionKey;
3599
+ this.emitSubagentEvent("A2A_MESSAGE_SENT", syncPayload);
3600
+ if (!lastReply) {
3550
3601
  return {
3551
- success: true,
3552
- messageId,
3553
- channel: "internal",
3554
- targetId: params.target.to,
3555
- sentAt: Date.now()
3556
- };
3557
- } catch (error) {
3558
- return {
3559
- success: false,
3560
- channel: "internal",
3561
- targetId: params.target.to,
3562
- error: error instanceof Error ? error.message : String(error)
3602
+ status: "timeout",
3603
+ runId,
3604
+ sessionKey: targetSessionKey,
3605
+ error: `No response received within ${params.timeoutSeconds ?? 30} seconds`,
3606
+ delivery: { status: "timeout", mode: "sync" }
3563
3607
  };
3564
3608
  }
3609
+ const result = {
3610
+ status: "ok",
3611
+ runId,
3612
+ sessionKey: targetSessionKey,
3613
+ delivery: { status: "delivered", mode: "sync" }
3614
+ };
3615
+ if (lastReply.content?.text)
3616
+ result.reply = lastReply.content.text;
3617
+ return result;
3565
3618
  }
3566
- normalizeChannel(channel) {
3567
- if (!channel) {
3568
- return "unknown";
3569
- }
3570
- const lower = channel.toLowerCase();
3571
- if (lower === "discord" || lower.includes("discord")) {
3572
- return "discord";
3573
- }
3574
- if (lower === "telegram" || lower.includes("telegram")) {
3575
- return "telegram";
3576
- }
3577
- if (lower === "slack" || lower.includes("slack")) {
3578
- return "slack";
3579
- }
3580
- if (lower === "whatsapp" || lower.includes("whatsapp")) {
3581
- return "whatsapp";
3582
- }
3583
- if (lower === "twitch" || lower.includes("twitch")) {
3584
- return "twitch";
3619
+ buildAgentToAgentContext(params) {
3620
+ return [
3621
+ "# Agent-to-Agent Message Context",
3622
+ "",
3623
+ "This message was sent by another agent session.",
3624
+ params.requesterSessionKey ? `- Sender: ${params.requesterSessionKey}` : undefined,
3625
+ `- Target: ${params.targetSessionKey}`,
3626
+ "",
3627
+ "Process this message and respond appropriately."
3628
+ ].filter((l) => l !== undefined).join(`
3629
+ `);
3630
+ }
3631
+ getSubagentRun(runId) {
3632
+ return this.subagentRuns.get(runId);
3633
+ }
3634
+ findSubagentRunByLabel(label, agentId) {
3635
+ const normalizedLabel = label.toLowerCase().trim();
3636
+ const normalizedAgentId = agentId ? normalizeAgentId2(agentId) : undefined;
3637
+ for (const run of this.subagentRuns.values()) {
3638
+ const runLabel = run.label?.toLowerCase().trim();
3639
+ if (runLabel !== normalizedLabel) {
3640
+ continue;
3641
+ }
3642
+ if (normalizedAgentId) {
3643
+ const runAgentId = extractAgentIdFromSessionKey(run.childSessionKey);
3644
+ if (normalizeAgentId2(runAgentId) !== normalizedAgentId) {
3645
+ continue;
3646
+ }
3647
+ }
3648
+ if (!run.endedAt) {
3649
+ return run;
3650
+ }
3585
3651
  }
3586
- if (lower === "google_chat" || lower.includes("google") || lower.includes("gchat")) {
3587
- return "google_chat";
3652
+ const completedRuns = [...this.subagentRuns.values()].filter((run) => {
3653
+ const runLabel = run.label?.toLowerCase().trim();
3654
+ if (runLabel !== normalizedLabel) {
3655
+ return false;
3656
+ }
3657
+ if (normalizedAgentId) {
3658
+ const runAgentId = extractAgentIdFromSessionKey(run.childSessionKey);
3659
+ if (normalizeAgentId2(runAgentId) !== normalizedAgentId) {
3660
+ return false;
3661
+ }
3662
+ }
3663
+ return true;
3664
+ }).sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
3665
+ return completedRuns[0];
3666
+ }
3667
+ listSubagentRuns(requesterSessionKey) {
3668
+ const runs = [...this.subagentRuns.values()];
3669
+ if (!requesterSessionKey) {
3670
+ return runs;
3588
3671
  }
3589
- if (lower === "internal" || lower === "a2a") {
3590
- return "internal";
3672
+ return runs.filter((r) => r.requesterSessionKey === requesterSessionKey);
3673
+ }
3674
+ cancelSubagentRun(runId) {
3675
+ const controller = this.activeRuns.get(runId);
3676
+ if (controller) {
3677
+ controller.abort();
3678
+ return true;
3591
3679
  }
3592
- return "unknown";
3680
+ return false;
3593
3681
  }
3594
3682
  on(event, handler) {
3595
3683
  this.emitter.on(event, handler);
@@ -3597,25 +3685,46 @@ class MessagingService extends Service4 {
3597
3685
  off(event, handler) {
3598
3686
  this.emitter.off(event, handler);
3599
3687
  }
3600
- emitMessagingEvent(type, payload) {
3688
+ emitSubagentEvent(type, payload) {
3601
3689
  this.emitter.emit(type, payload);
3602
- this.emitter.emit("messaging", { type, ...payload });
3690
+ this.emitter.emit("task", { type, ...payload });
3691
+ }
3692
+ startSweeper() {
3693
+ if (this.sweeper) {
3694
+ return;
3695
+ }
3696
+ this.sweeper = setInterval(() => {
3697
+ this.sweepOldRuns();
3698
+ }, 60000);
3699
+ this.sweeper.unref?.();
3700
+ }
3701
+ sweepOldRuns() {
3702
+ const now2 = Date.now();
3703
+ for (const [runId, record] of this.subagentRuns.entries()) {
3704
+ if (record.archiveAtMs && record.archiveAtMs <= now2) {
3705
+ this.subagentRuns.delete(runId);
3706
+ }
3707
+ }
3603
3708
  }
3604
3709
  async stop() {
3605
- this.pendingDeliveries.clear();
3710
+ if (this.sweeper) {
3711
+ clearInterval(this.sweeper);
3712
+ this.sweeper = null;
3713
+ }
3714
+ for (const controller of this.activeRuns.values()) {
3715
+ controller.abort();
3716
+ }
3717
+ this.activeRuns.clear();
3606
3718
  this.emitter.removeAllListeners();
3607
3719
  }
3608
3720
  }
3609
- // src/types/subagent.ts
3610
- var SubagentEventType = {
3611
- SPAWN_REQUESTED: "SUBAGENT_SPAWN_REQUESTED",
3612
- RUN_STARTED: "SUBAGENT_RUN_STARTED",
3613
- RUN_COMPLETED: "SUBAGENT_RUN_COMPLETED",
3614
- RUN_FAILED: "SUBAGENT_RUN_FAILED",
3615
- RUN_TIMEOUT: "SUBAGENT_RUN_TIMEOUT",
3616
- ANNOUNCE_SENT: "SUBAGENT_ANNOUNCE_SENT",
3617
- A2A_MESSAGE_SENT: "A2A_MESSAGE_SENT",
3618
- A2A_MESSAGE_RECEIVED: "A2A_MESSAGE_RECEIVED"
3721
+ // src/types/messaging.ts
3722
+ var MessagingEventType = {
3723
+ SEND_REQUESTED: "MESSAGING_SEND_REQUESTED",
3724
+ SENT: "MESSAGING_SENT",
3725
+ SEND_FAILED: "MESSAGING_SEND_FAILED",
3726
+ DELIVERED: "MESSAGING_DELIVERED",
3727
+ READ: "MESSAGING_READ"
3619
3728
  };
3620
3729
  // src/types/sandbox.ts
3621
3730
  var SandboxEventType = {
@@ -3627,13 +3736,16 @@ var SandboxEventType = {
3627
3736
  BROWSER_STARTED: "SANDBOX_BROWSER_STARTED",
3628
3737
  BROWSER_STOPPED: "SANDBOX_BROWSER_STOPPED"
3629
3738
  };
3630
- // src/types/messaging.ts
3631
- var MessagingEventType = {
3632
- SEND_REQUESTED: "MESSAGING_SEND_REQUESTED",
3633
- SENT: "MESSAGING_SENT",
3634
- SEND_FAILED: "MESSAGING_SEND_FAILED",
3635
- DELIVERED: "MESSAGING_DELIVERED",
3636
- READ: "MESSAGING_READ"
3739
+ // src/types/subagent.ts
3740
+ var SubagentEventType = {
3741
+ SPAWN_REQUESTED: "SUBAGENT_SPAWN_REQUESTED",
3742
+ RUN_STARTED: "SUBAGENT_RUN_STARTED",
3743
+ RUN_COMPLETED: "SUBAGENT_RUN_COMPLETED",
3744
+ RUN_FAILED: "SUBAGENT_RUN_FAILED",
3745
+ RUN_TIMEOUT: "SUBAGENT_RUN_TIMEOUT",
3746
+ ANNOUNCE_SENT: "SUBAGENT_ANNOUNCE_SENT",
3747
+ A2A_MESSAGE_SENT: "A2A_MESSAGE_SENT",
3748
+ A2A_MESSAGE_RECEIVED: "A2A_MESSAGE_RECEIVED"
3637
3749
  };
3638
3750
  // index.ts
3639
3751
  var sessionProviders = getSessionProviders2();
@@ -3748,4 +3860,4 @@ export {
3748
3860
  AgentOrchestratorService
3749
3861
  };
3750
3862
 
3751
- //# debugId=9695C525D670EDC964756E2164756E21
3863
+ //# debugId=4AD40853FB59A54864756E2164756E21