@intent-systems/nexus 2026.1.5-3 → 2026.1.5-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/capabilities/detector.js +214 -0
- package/dist/capabilities/registry.js +98 -0
- package/dist/channels/location.js +44 -0
- package/dist/channels/web/index.js +2 -0
- package/dist/control-plane/broker/broker.js +969 -0
- package/dist/control-plane/compaction.js +284 -0
- package/dist/control-plane/factory.js +31 -0
- package/dist/control-plane/index.js +10 -0
- package/dist/control-plane/odu/agents.js +187 -0
- package/dist/control-plane/odu/interaction-tools.js +196 -0
- package/dist/control-plane/odu/prompt-loader.js +95 -0
- package/dist/control-plane/odu/runtime.js +467 -0
- package/dist/control-plane/odu/types.js +6 -0
- package/dist/control-plane/odu-control-plane.js +314 -0
- package/dist/control-plane/single-agent.js +249 -0
- package/dist/control-plane/types.js +11 -0
- package/dist/credentials/store.js +323 -0
- package/dist/logging/redact.js +109 -0
- package/dist/markdown/fences.js +58 -0
- package/dist/memory/embeddings.js +146 -0
- package/dist/memory/index.js +382 -0
- package/dist/memory/internal.js +163 -0
- package/dist/pairing/pairing-store.js +194 -0
- package/dist/plugins/cli.js +42 -0
- package/dist/plugins/discovery.js +253 -0
- package/dist/plugins/install.js +181 -0
- package/dist/plugins/loader.js +290 -0
- package/dist/plugins/registry.js +105 -0
- package/dist/plugins/status.js +29 -0
- package/dist/plugins/tools.js +39 -0
- package/dist/plugins/types.js +1 -0
- package/dist/routing/resolve-route.js +144 -0
- package/dist/routing/session-key.js +63 -0
- package/dist/utils/provider-utils.js +28 -0
- package/package.json +4 -29
- package/patches/@mariozechner__pi-ai.patch +215 -0
- package/patches/playwright-core@1.57.0.patch +13 -0
- package/patches/qrcode-terminal.patch +12 -0
- package/scripts/postinstall.js +202 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified ODU Runtime
|
|
3
|
+
*
|
|
4
|
+
* Convention-driven agent runtime that works for any ODU.
|
|
5
|
+
* Adapted from magic-toolbox for Nexus.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { InteractionAgent, ExecutionAgent } from './agents.js';
|
|
9
|
+
import { loadHistory, appendToHistory, } from '../../config/sessions.js';
|
|
10
|
+
import { resolveUserPath } from '../../utils.js';
|
|
11
|
+
import { DEFAULT_AGENT_ID } from '../../routing/session-key.js';
|
|
12
|
+
import { loadPrompt } from './prompt-loader.js';
|
|
13
|
+
import { DEFAULT_MODEL } from '../../agents/defaults.js';
|
|
14
|
+
/**
|
|
15
|
+
* Unified Interaction Agent Runtime
|
|
16
|
+
*
|
|
17
|
+
* Convention-driven IA that works for any ODU.
|
|
18
|
+
* Wraps Nexus embedded agent system.
|
|
19
|
+
*/
|
|
20
|
+
export class ODUInteractionAgent extends InteractionAgent {
|
|
21
|
+
// Static registry of all ODU IA singletons (fractal architecture support)
|
|
22
|
+
static instances = new Map();
|
|
23
|
+
conversationHistory = [];
|
|
24
|
+
broker;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
// Singleton key: oduPath + userId (supports nested ODUs)
|
|
27
|
+
const key = `${config.oduPath}:${config.userId}`;
|
|
28
|
+
// Return existing instance if already created
|
|
29
|
+
const existing = ODUInteractionAgent.instances.get(key);
|
|
30
|
+
if (existing) {
|
|
31
|
+
return existing;
|
|
32
|
+
}
|
|
33
|
+
// Create new instance
|
|
34
|
+
super(config);
|
|
35
|
+
this.oduConfig = config.oduConfig;
|
|
36
|
+
this.broker = config.broker;
|
|
37
|
+
// Register this singleton
|
|
38
|
+
ODUInteractionAgent.instances.set(key, this);
|
|
39
|
+
// Load conversation history asynchronously
|
|
40
|
+
// (constructors can't be async, so we do this in the background)
|
|
41
|
+
if (config.sessionId) {
|
|
42
|
+
this.loadConversationHistory(config.sessionId).catch((error) => {
|
|
43
|
+
this.log.warn('Failed to load conversation history', { error });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
// Register ODU with broker
|
|
47
|
+
const agentFactory = (agentId, taskDescription, history) => {
|
|
48
|
+
return new ODUExecutionAgent({
|
|
49
|
+
agentId,
|
|
50
|
+
userId: this.userId,
|
|
51
|
+
task: {
|
|
52
|
+
userId: this.userId,
|
|
53
|
+
type: this.oduConfig.name,
|
|
54
|
+
description: taskDescription,
|
|
55
|
+
taskName: agentId,
|
|
56
|
+
},
|
|
57
|
+
oduPath: this.oduPath,
|
|
58
|
+
config: this.config,
|
|
59
|
+
history,
|
|
60
|
+
broker: this.broker,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
// Register ODU (EA factory) with broker
|
|
64
|
+
this.broker.registerODU(this.oduConfig.name, path.join(resolveUserPath(this.oduPath), '../state'), agentFactory);
|
|
65
|
+
// Register THIS IA with broker so it can receive messages
|
|
66
|
+
const iaId = `${this.oduConfig.name}-ia`;
|
|
67
|
+
this.broker.registerIA(iaId, this);
|
|
68
|
+
this.log.info('IA registered with broker', { iaId });
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the model to use for IA from config
|
|
72
|
+
* Priority: controlPlane.odu.iaModel.primary > agent.model.primary > DEFAULT_MODEL
|
|
73
|
+
*/
|
|
74
|
+
resolveIAModel() {
|
|
75
|
+
const oduConfig = this.config?.controlPlane?.odu;
|
|
76
|
+
const agentConfig = this.config?.agent;
|
|
77
|
+
// Check ODU-specific IA model first
|
|
78
|
+
if (oduConfig?.iaModel?.primary) {
|
|
79
|
+
// Extract just the model name from provider/model format
|
|
80
|
+
const parts = oduConfig.iaModel.primary.split('/');
|
|
81
|
+
return parts.length > 1 ? parts[1] : parts[0];
|
|
82
|
+
}
|
|
83
|
+
// Fall back to agent.model
|
|
84
|
+
if (agentConfig?.model?.primary) {
|
|
85
|
+
const parts = agentConfig.model.primary.split('/');
|
|
86
|
+
return parts.length > 1 ? parts[1] : parts[0];
|
|
87
|
+
}
|
|
88
|
+
// Default to opus
|
|
89
|
+
return DEFAULT_MODEL;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Load conversation history from new session format
|
|
93
|
+
*/
|
|
94
|
+
async loadConversationHistory(sessionId) {
|
|
95
|
+
try {
|
|
96
|
+
// Use DEFAULT_AGENT_ID and sessionId as the sessionKey
|
|
97
|
+
const agentId = DEFAULT_AGENT_ID;
|
|
98
|
+
const sessionKey = sessionId;
|
|
99
|
+
// Load history from new format
|
|
100
|
+
const history = await loadHistory(agentId, sessionKey);
|
|
101
|
+
if (history && history.length > 0) {
|
|
102
|
+
// Convert HistoryTurn[] to conversation history format
|
|
103
|
+
this.conversationHistory = history.map((turn) => ({
|
|
104
|
+
role: turn.role,
|
|
105
|
+
content: turn.content,
|
|
106
|
+
tool_calls: turn.tool_calls,
|
|
107
|
+
tool_call_id: turn.tool_call_id,
|
|
108
|
+
timestamp: turn.timestamp,
|
|
109
|
+
}));
|
|
110
|
+
this.log.info('Loaded conversation history', {
|
|
111
|
+
turns: this.conversationHistory.length,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
this.log.error('Failed to load conversation history', { error });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Handle a user message and return complete response
|
|
121
|
+
*/
|
|
122
|
+
async chatSync(userMessage) {
|
|
123
|
+
// ALWAYS interrupt if currently streaming
|
|
124
|
+
if (this.isStreaming) {
|
|
125
|
+
this.log.info('New message received while streaming, interrupting');
|
|
126
|
+
this.interrupt();
|
|
127
|
+
// Wait a tiny bit for stream to stop
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
129
|
+
}
|
|
130
|
+
// Add message to queue
|
|
131
|
+
this.queueMessage(userMessage);
|
|
132
|
+
// If already processing (but not streaming), return queued message
|
|
133
|
+
if (this.isProcessing && !this.isStreaming) {
|
|
134
|
+
return 'Message queued. Processing...';
|
|
135
|
+
}
|
|
136
|
+
this.isProcessing = true;
|
|
137
|
+
try {
|
|
138
|
+
let finalResponse = '';
|
|
139
|
+
// Process all queued messages
|
|
140
|
+
while (this.messageQueue.length > 0) {
|
|
141
|
+
// Dequeue all current messages
|
|
142
|
+
const messages = this.messageQueue.splice(0);
|
|
143
|
+
// Sort by priority (high priority first)
|
|
144
|
+
messages.sort((a, b) => {
|
|
145
|
+
const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };
|
|
146
|
+
const aPriority = priorityOrder[a.priority] || 2;
|
|
147
|
+
const bPriority = priorityOrder[b.priority] || 2;
|
|
148
|
+
if (aPriority !== bPriority)
|
|
149
|
+
return aPriority - bPriority;
|
|
150
|
+
return a.timestamp - b.timestamp;
|
|
151
|
+
});
|
|
152
|
+
// Extract sender from first message
|
|
153
|
+
const from = messages[0].from;
|
|
154
|
+
// Combine messages into single prompt
|
|
155
|
+
const combinedMessage = messages.length === 1
|
|
156
|
+
? messages[0].content
|
|
157
|
+
: messages.map((m, i) => `Message ${i + 1}:\n${m.content}`).join('\n\n---\n\n');
|
|
158
|
+
// Reset interrupt flag for new processing
|
|
159
|
+
this.shouldInterrupt = false;
|
|
160
|
+
// Process the combined message with sender context (with streaming + interrupt support)
|
|
161
|
+
finalResponse = await this.processSingleMessage(combinedMessage, from);
|
|
162
|
+
// If interrupted, accumulated response is preserved
|
|
163
|
+
// New messages will be in the queue for next iteration
|
|
164
|
+
}
|
|
165
|
+
return finalResponse;
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
this.isProcessing = false;
|
|
169
|
+
this.isStreaming = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Process a single message (extracted for queue handling)
|
|
174
|
+
*/
|
|
175
|
+
async processSingleMessage(userMessage, from) {
|
|
176
|
+
// If message from another agent, prepend context so IA knows who to respond to
|
|
177
|
+
if (from && from !== 'user' && from !== 'system') {
|
|
178
|
+
userMessage = `[Message from agent: ${from}]\n\n${userMessage}`;
|
|
179
|
+
}
|
|
180
|
+
this.log.info('Processing message', {
|
|
181
|
+
messageLength: userMessage.length,
|
|
182
|
+
from,
|
|
183
|
+
});
|
|
184
|
+
// Import dependencies dynamically
|
|
185
|
+
const { default: Anthropic } = await import('@anthropic-ai/sdk');
|
|
186
|
+
const { createInteractionTools, handleToolInvocation } = await import('./interaction-tools.js');
|
|
187
|
+
// Get API key from environment
|
|
188
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
189
|
+
if (!apiKey) {
|
|
190
|
+
throw new Error('ANTHROPIC_API_KEY environment variable is required for ODU interaction agent');
|
|
191
|
+
}
|
|
192
|
+
// Create Anthropic client
|
|
193
|
+
const client = new Anthropic({ apiKey });
|
|
194
|
+
// Build system prompt
|
|
195
|
+
const systemPrompt = await this.buildSystemPrompt();
|
|
196
|
+
// Create interaction tools context
|
|
197
|
+
const toolsContext = {
|
|
198
|
+
broker: this.broker,
|
|
199
|
+
userId: this.userId,
|
|
200
|
+
oduName: this.oduConfig.name,
|
|
201
|
+
oduPurpose: this.oduConfig.purpose,
|
|
202
|
+
agentId: `${this.oduConfig.name}-ia`,
|
|
203
|
+
};
|
|
204
|
+
// Get tool definitions
|
|
205
|
+
const tools = createInteractionTools(toolsContext);
|
|
206
|
+
// Build conversation history in Anthropic format
|
|
207
|
+
const messages = [];
|
|
208
|
+
// Add conversation history
|
|
209
|
+
for (const turn of this.conversationHistory) {
|
|
210
|
+
if (turn.role === 'user') {
|
|
211
|
+
messages.push({ role: 'user', content: turn.content });
|
|
212
|
+
}
|
|
213
|
+
else if (turn.role === 'assistant') {
|
|
214
|
+
// Build assistant message with tool calls if present
|
|
215
|
+
const content = [];
|
|
216
|
+
if (turn.content) {
|
|
217
|
+
content.push({ type: 'text', text: turn.content });
|
|
218
|
+
}
|
|
219
|
+
if (turn.tool_calls && turn.tool_calls.length > 0) {
|
|
220
|
+
for (const toolCall of turn.tool_calls) {
|
|
221
|
+
content.push({
|
|
222
|
+
type: 'tool_use',
|
|
223
|
+
id: toolCall.id,
|
|
224
|
+
name: toolCall.function.name,
|
|
225
|
+
input: JSON.parse(toolCall.function.arguments),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
messages.push({ role: 'assistant', content });
|
|
230
|
+
}
|
|
231
|
+
else if (turn.role === 'tool') {
|
|
232
|
+
// Tool results must be in the previous assistant message's content
|
|
233
|
+
// For now, we'll skip them as they should already be recorded
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Add current user message
|
|
238
|
+
messages.push({ role: 'user', content: userMessage });
|
|
239
|
+
// Resolve model from config (IA uses controlPlane.odu.iaModel or agent.model)
|
|
240
|
+
const iaModel = this.resolveIAModel();
|
|
241
|
+
// Call Claude API with tool support
|
|
242
|
+
let response = await client.messages.create({
|
|
243
|
+
model: iaModel,
|
|
244
|
+
max_tokens: 4096,
|
|
245
|
+
system: systemPrompt,
|
|
246
|
+
messages,
|
|
247
|
+
tools,
|
|
248
|
+
});
|
|
249
|
+
// Handle tool calls in a loop (agent may make multiple tool calls)
|
|
250
|
+
let iterationCount = 0;
|
|
251
|
+
const maxIterations = 10; // Prevent infinite loops
|
|
252
|
+
while (response.stop_reason === 'tool_use' && iterationCount < maxIterations) {
|
|
253
|
+
iterationCount++;
|
|
254
|
+
// Extract tool calls from response
|
|
255
|
+
const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
|
|
256
|
+
// Execute each tool and collect results
|
|
257
|
+
const toolResults = [];
|
|
258
|
+
for (const toolBlock of toolUseBlocks) {
|
|
259
|
+
try {
|
|
260
|
+
const result = await handleToolInvocation(toolBlock.name, toolBlock.input, toolsContext);
|
|
261
|
+
toolResults.push({
|
|
262
|
+
type: 'tool_result',
|
|
263
|
+
tool_use_id: toolBlock.id,
|
|
264
|
+
content: result,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
269
|
+
toolResults.push({
|
|
270
|
+
type: 'tool_result',
|
|
271
|
+
tool_use_id: toolBlock.id,
|
|
272
|
+
content: `Error: ${errorMessage}`,
|
|
273
|
+
is_error: true,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Add assistant response to messages
|
|
278
|
+
messages.push({ role: 'assistant', content: response.content });
|
|
279
|
+
// Add tool results as user message
|
|
280
|
+
messages.push({ role: 'user', content: toolResults });
|
|
281
|
+
// Continue conversation
|
|
282
|
+
response = await client.messages.create({
|
|
283
|
+
model: 'claude-sonnet-4-20250514',
|
|
284
|
+
max_tokens: 4096,
|
|
285
|
+
system: systemPrompt,
|
|
286
|
+
messages,
|
|
287
|
+
tools,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// Extract final text response
|
|
291
|
+
const textBlocks = response.content.filter((block) => block.type === 'text');
|
|
292
|
+
const finalResponse = textBlocks.map((block) => block.text).join('\n');
|
|
293
|
+
// Save conversation history (both in-memory and to disk)
|
|
294
|
+
const userTurn = {
|
|
295
|
+
turn_id: `turn-${Date.now()}-user`,
|
|
296
|
+
role: 'user',
|
|
297
|
+
content: userMessage,
|
|
298
|
+
timestamp: new Date().toISOString(),
|
|
299
|
+
};
|
|
300
|
+
const assistantTurn = {
|
|
301
|
+
turn_id: `turn-${Date.now()}-assistant`,
|
|
302
|
+
role: 'assistant',
|
|
303
|
+
content: finalResponse,
|
|
304
|
+
timestamp: new Date().toISOString(),
|
|
305
|
+
};
|
|
306
|
+
// Update in-memory history
|
|
307
|
+
this.conversationHistory.push(userTurn);
|
|
308
|
+
this.conversationHistory.push(assistantTurn);
|
|
309
|
+
// Persist to disk using the IA's session
|
|
310
|
+
const agentId = DEFAULT_AGENT_ID;
|
|
311
|
+
const sessionKey = this.sessionId;
|
|
312
|
+
try {
|
|
313
|
+
await appendToHistory(agentId, sessionKey, userTurn);
|
|
314
|
+
await appendToHistory(agentId, sessionKey, assistantTurn);
|
|
315
|
+
this.log.debug('Persisted conversation to history.jsonl', { sessionKey });
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
this.log.warn('Failed to persist conversation history', { error, sessionKey });
|
|
319
|
+
}
|
|
320
|
+
return finalResponse;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Build system prompt for the IA
|
|
324
|
+
*/
|
|
325
|
+
async buildSystemPrompt() {
|
|
326
|
+
// Load InteractionAgent.md prompt with template variables
|
|
327
|
+
const prompt = await loadPrompt('InteractionAgent.md', {
|
|
328
|
+
oduName: this.oduConfig.name,
|
|
329
|
+
oduPurpose: this.oduConfig.purpose,
|
|
330
|
+
oduPath: this.oduPath,
|
|
331
|
+
});
|
|
332
|
+
return prompt;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Unified Execution Agent Runtime
|
|
337
|
+
*
|
|
338
|
+
* Convention-driven EA that works for any ODU.
|
|
339
|
+
* Wraps Nexus embedded agent system with isolated workspace.
|
|
340
|
+
*/
|
|
341
|
+
export class ODUExecutionAgent extends ExecutionAgent {
|
|
342
|
+
broker;
|
|
343
|
+
constructor(config) {
|
|
344
|
+
super(config);
|
|
345
|
+
this.broker = config.broker;
|
|
346
|
+
if (config.oduConfig) {
|
|
347
|
+
this.oduConfig = config.oduConfig;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// Derive ODU name from path
|
|
351
|
+
const oduName = path.basename(resolveUserPath(config.oduPath || '~/nexus/home'));
|
|
352
|
+
this.oduConfig = {
|
|
353
|
+
name: oduName,
|
|
354
|
+
purpose: `Execute tasks for ${oduName} ODU`,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Execute the task using Nexus embedded agent
|
|
360
|
+
*/
|
|
361
|
+
async execute() {
|
|
362
|
+
this.log.info('Starting EA execution', {
|
|
363
|
+
taskType: this.task.type,
|
|
364
|
+
taskDescription: (this.task.description || '').substring(0, 200),
|
|
365
|
+
historyLength: this.history.length,
|
|
366
|
+
});
|
|
367
|
+
try {
|
|
368
|
+
// Import runEmbeddedPiAgent dynamically
|
|
369
|
+
const { runEmbeddedPiAgent } = await import('../../agents/pi-embedded-runner.js');
|
|
370
|
+
const { resolveNexusAgentDir } = await import('../../agents/agent-paths.js');
|
|
371
|
+
const { randomUUID } = await import('node:crypto');
|
|
372
|
+
const path = await import('node:path');
|
|
373
|
+
// Build initial task prompt
|
|
374
|
+
const taskPrompt = this.buildInitialPrompt();
|
|
375
|
+
// Resolve workspace directory for this EA
|
|
376
|
+
// Use shared workspace (~/nexus/home) - all EAs share the same workspace
|
|
377
|
+
// This aligns with Cursor and single-agent mode behavior
|
|
378
|
+
const workspaceDir = resolveUserPath(this.oduPath);
|
|
379
|
+
// Resolve session file path
|
|
380
|
+
const agentDir = resolveNexusAgentDir();
|
|
381
|
+
const sessionFile = path.join(workspaceDir, '.agent-session.json');
|
|
382
|
+
// Build system prompt with EA template
|
|
383
|
+
const systemPrompt = await this.buildSystemPrompt();
|
|
384
|
+
// Run embedded pi-agent with full coding tools
|
|
385
|
+
const result = await runEmbeddedPiAgent({
|
|
386
|
+
sessionId: this.agentId,
|
|
387
|
+
sessionKey: this.agentId,
|
|
388
|
+
sessionFile,
|
|
389
|
+
workspaceDir,
|
|
390
|
+
agentDir,
|
|
391
|
+
config: this.config,
|
|
392
|
+
prompt: taskPrompt,
|
|
393
|
+
// Use default provider/model if not configured
|
|
394
|
+
provider: undefined, // Will use defaults from config
|
|
395
|
+
model: undefined, // Will use defaults from config
|
|
396
|
+
thinkLevel: this.config?.agent?.thinkingDefault,
|
|
397
|
+
verboseLevel: this.config?.agent?.verboseDefault,
|
|
398
|
+
timeoutMs: (this.config?.agent?.timeoutSeconds || 300) * 1000,
|
|
399
|
+
runId: randomUUID(),
|
|
400
|
+
extraSystemPrompt: systemPrompt,
|
|
401
|
+
});
|
|
402
|
+
// Extract final response from payloads
|
|
403
|
+
const finalResponse = result.payloads?.map((p) => p.text).filter(Boolean).join('\n\n') || 'Task completed';
|
|
404
|
+
this.log.info('EA execution complete', {
|
|
405
|
+
responseLength: finalResponse.length,
|
|
406
|
+
usage: result.meta.agentMeta?.usage,
|
|
407
|
+
});
|
|
408
|
+
// Send result back to IA via broker
|
|
409
|
+
const oduName = this.oduConfig.name;
|
|
410
|
+
const iaId = `${oduName}-ia`;
|
|
411
|
+
await this.broker.send({
|
|
412
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
413
|
+
from: this.agentId,
|
|
414
|
+
to: iaId,
|
|
415
|
+
content: finalResponse,
|
|
416
|
+
priority: 'normal',
|
|
417
|
+
timestamp: Date.now(),
|
|
418
|
+
metadata: {
|
|
419
|
+
source: 'ea',
|
|
420
|
+
completionResult: true,
|
|
421
|
+
usage: result.meta.agentMeta?.usage,
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
return finalResponse;
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
this.log.error('EA execution failed', { error });
|
|
428
|
+
// Send error back to IA
|
|
429
|
+
const oduName = this.oduConfig.name;
|
|
430
|
+
const iaId = `${oduName}-ia`;
|
|
431
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
432
|
+
await this.broker.send({
|
|
433
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
434
|
+
from: this.agentId,
|
|
435
|
+
to: iaId,
|
|
436
|
+
content: `Task failed: ${errorMessage}`,
|
|
437
|
+
priority: 'normal',
|
|
438
|
+
timestamp: Date.now(),
|
|
439
|
+
metadata: {
|
|
440
|
+
source: 'ea',
|
|
441
|
+
completionResult: true,
|
|
442
|
+
error: true,
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Build system prompt for the EA
|
|
450
|
+
*/
|
|
451
|
+
async buildSystemPrompt() {
|
|
452
|
+
// Load ExecutionAgent.md prompt with template variables
|
|
453
|
+
const prompt = await loadPrompt('ExecutionAgent.md', {
|
|
454
|
+
oduName: this.oduConfig.name,
|
|
455
|
+
oduPurpose: this.oduConfig.purpose,
|
|
456
|
+
oduPath: this.oduPath,
|
|
457
|
+
task: this.task.description || 'Execute the assigned task',
|
|
458
|
+
taskName: this.task.taskName || 'task',
|
|
459
|
+
agentId: this.agentId,
|
|
460
|
+
// For now, leave skills and capabilities empty
|
|
461
|
+
// These will be populated when we implement skill/capability loading
|
|
462
|
+
availableSkills: '(Skills will be listed here)',
|
|
463
|
+
availableCapabilities: '(Capabilities will be listed here)',
|
|
464
|
+
});
|
|
465
|
+
return prompt;
|
|
466
|
+
}
|
|
467
|
+
}
|