agents-library 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/dist/base-agent.d.ts +10 -8
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +30 -26
- package/dist/base-agent.js.map +1 -1
- package/dist/base-bot.d.ts +0 -0
- package/dist/base-bot.d.ts.map +0 -0
- package/dist/base-bot.js +0 -0
- package/dist/base-bot.js.map +0 -0
- package/dist/common/result.d.ts +0 -0
- package/dist/common/result.d.ts.map +0 -0
- package/dist/common/result.js +0 -0
- package/dist/common/result.js.map +0 -0
- package/dist/common/types.d.ts +0 -0
- package/dist/common/types.d.ts.map +0 -0
- package/dist/common/types.js +0 -0
- package/dist/common/types.js.map +0 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/kadi-event-publisher.d.ts +0 -0
- package/dist/kadi-event-publisher.d.ts.map +0 -0
- package/dist/kadi-event-publisher.js +0 -0
- package/dist/kadi-event-publisher.js.map +0 -0
- package/dist/memory/arcadedb-adapter.d.ts +8 -0
- package/dist/memory/arcadedb-adapter.d.ts.map +1 -1
- package/dist/memory/arcadedb-adapter.js +8 -0
- package/dist/memory/arcadedb-adapter.js.map +1 -1
- package/dist/memory/entity-extractor.d.ts +110 -0
- package/dist/memory/entity-extractor.d.ts.map +1 -0
- package/dist/memory/entity-extractor.js +259 -0
- package/dist/memory/entity-extractor.js.map +1 -0
- package/dist/memory/file-storage-adapter.d.ts +0 -0
- package/dist/memory/file-storage-adapter.d.ts.map +0 -0
- package/dist/memory/file-storage-adapter.js +0 -0
- package/dist/memory/file-storage-adapter.js.map +0 -0
- package/dist/memory/memory-service.d.ts +123 -13
- package/dist/memory/memory-service.d.ts.map +1 -1
- package/dist/memory/memory-service.js +428 -72
- package/dist/memory/memory-service.js.map +1 -1
- package/dist/memory/types.d.ts +0 -0
- package/dist/memory/types.d.ts.map +0 -0
- package/dist/memory/types.js +0 -0
- package/dist/memory/types.js.map +0 -0
- package/dist/producer-tool-utils.d.ts +0 -0
- package/dist/producer-tool-utils.d.ts.map +0 -0
- package/dist/producer-tool-utils.js +0 -0
- package/dist/producer-tool-utils.js.map +0 -0
- package/dist/providers/anthropic-provider.d.ts +0 -0
- package/dist/providers/anthropic-provider.d.ts.map +0 -0
- package/dist/providers/anthropic-provider.js +0 -0
- package/dist/providers/anthropic-provider.js.map +0 -0
- package/dist/providers/model-manager-provider.d.ts +0 -0
- package/dist/providers/model-manager-provider.d.ts.map +0 -0
- package/dist/providers/model-manager-provider.js +0 -0
- package/dist/providers/model-manager-provider.js.map +0 -0
- package/dist/providers/provider-manager.d.ts +0 -0
- package/dist/providers/provider-manager.d.ts.map +1 -1
- package/dist/providers/provider-manager.js +6 -2
- package/dist/providers/provider-manager.js.map +1 -1
- package/dist/providers/types.d.ts +0 -0
- package/dist/providers/types.d.ts.map +0 -0
- package/dist/providers/types.js +0 -0
- package/dist/providers/types.js.map +0 -0
- package/dist/shadow-agent-factory.d.ts +12 -96
- package/dist/shadow-agent-factory.d.ts.map +1 -1
- package/dist/shadow-agent-factory.js +73 -280
- package/dist/shadow-agent-factory.js.map +1 -1
- package/dist/types/agent-config.d.ts +62 -1
- package/dist/types/agent-config.d.ts.map +1 -1
- package/dist/types/agent-config.js +0 -0
- package/dist/types/agent-config.js.map +0 -0
- package/dist/types/event-schemas.d.ts +194 -0
- package/dist/types/event-schemas.d.ts.map +1 -1
- package/dist/types/event-schemas.js +77 -2
- package/dist/types/event-schemas.js.map +1 -1
- package/dist/types/tool-schemas.d.ts +0 -0
- package/dist/types/tool-schemas.d.ts.map +0 -0
- package/dist/types/tool-schemas.js +0 -0
- package/dist/types/tool-schemas.js.map +0 -0
- package/dist/utils/config.d.ts +48 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +163 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +26 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/path-utils.d.ts +22 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +51 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/dist/utils/read-config.d.ts +43 -0
- package/dist/utils/read-config.d.ts.map +1 -0
- package/dist/utils/read-config.js +97 -0
- package/dist/utils/read-config.js.map +1 -0
- package/dist/utils/timer.d.ts +0 -0
- package/dist/utils/timer.d.ts.map +0 -0
- package/dist/utils/timer.js +0 -0
- package/dist/utils/timer.js.map +0 -0
- package/dist/utils/vault.d.ts +29 -0
- package/dist/utils/vault.d.ts.map +1 -0
- package/dist/utils/vault.js +79 -0
- package/dist/utils/vault.js.map +1 -0
- package/dist/worker-agent-factory.d.ts +37 -38
- package/dist/worker-agent-factory.d.ts.map +1 -1
- package/dist/worker-agent-factory.js +314 -171
- package/dist/worker-agent-factory.js.map +1 -1
- package/package.json +5 -3
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module worker-agent-factory
|
|
26
26
|
*/
|
|
27
|
-
import {
|
|
27
|
+
import { z } from '@kadi.build/core';
|
|
28
28
|
import { BaseBot } from './base-bot.js';
|
|
29
|
-
import {
|
|
29
|
+
import { BaseAgent } from './base-agent.js';
|
|
30
|
+
import { TaskAssignedEventSchema, } from './types/event-schemas.js';
|
|
30
31
|
import { logger, MODULE_AGENT } from './utils/logger.js';
|
|
31
32
|
import { timer } from './utils/timer.js';
|
|
33
|
+
import { formatMemoryContext } from './memory/memory-service.js';
|
|
32
34
|
// ============================================================================
|
|
33
35
|
// Configuration Validation Schema
|
|
34
36
|
// ============================================================================
|
|
@@ -56,6 +58,7 @@ import { timer } from './utils/timer.js';
|
|
|
56
58
|
* ```
|
|
57
59
|
*/
|
|
58
60
|
const WorkerAgentConfigSchema = z.object({
|
|
61
|
+
agentId: z.string().optional(),
|
|
59
62
|
role: z.enum(['artist', 'designer', 'programmer']),
|
|
60
63
|
worktreePath: z.string().min(1, 'Worktree path is required'),
|
|
61
64
|
brokerUrl: z.string()
|
|
@@ -107,30 +110,11 @@ const WorkerAgentConfigSchema = z.object({
|
|
|
107
110
|
* // Agent now listens for {role}.task.assigned events and executes tasks
|
|
108
111
|
* ```
|
|
109
112
|
*/
|
|
110
|
-
export class BaseWorkerAgent {
|
|
113
|
+
export class BaseWorkerAgent extends BaseAgent {
|
|
111
114
|
// ============================================================================
|
|
112
115
|
// Protected Properties (accessible to subclasses/extensions)
|
|
113
116
|
// ============================================================================
|
|
114
|
-
|
|
115
|
-
* KĀDI client for broker communication
|
|
116
|
-
*
|
|
117
|
-
* Used for:
|
|
118
|
-
* - Subscribing to events
|
|
119
|
-
* - Publishing completion/failure events
|
|
120
|
-
* - Invoking remote MCP tools (git, file management)
|
|
121
|
-
*/
|
|
122
|
-
client;
|
|
123
|
-
/**
|
|
124
|
-
* LLM provider manager for model selection and chat
|
|
125
|
-
*
|
|
126
|
-
* Replaces direct Anthropic SDK usage. Provides:
|
|
127
|
-
* - Model-based routing (claude→Anthropic, gpt→Model Manager)
|
|
128
|
-
* - Automatic fallback on provider failure
|
|
129
|
-
* - Tool-calling via ChatOptions.tools
|
|
130
|
-
*
|
|
131
|
-
* Optional — if null, agent cannot execute tasks requiring LLM.
|
|
132
|
-
*/
|
|
133
|
-
providerManager = null;
|
|
117
|
+
// NOTE: client, providerManager, memoryService are inherited from BaseAgent
|
|
134
118
|
/**
|
|
135
119
|
* Agent role (artist, designer, programmer)
|
|
136
120
|
*
|
|
@@ -218,17 +202,27 @@ export class BaseWorkerAgent {
|
|
|
218
202
|
*/
|
|
219
203
|
baseBot = null;
|
|
220
204
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* Stored for reference and potential reconfiguration.
|
|
205
|
+
* Worker-specific configuration (role, worktree, model, etc.)
|
|
224
206
|
*/
|
|
225
|
-
|
|
207
|
+
workerConfig;
|
|
226
208
|
/**
|
|
227
209
|
* Set of task IDs that have been processed or are currently in-flight.
|
|
228
210
|
* Prevents duplicate execution when the same task.assigned event arrives
|
|
229
211
|
* multiple times (e.g., retry re-publish while first execution is still running).
|
|
230
212
|
*/
|
|
231
213
|
processedTaskIds = new Set();
|
|
214
|
+
/**
|
|
215
|
+
* Natively loaded ability-file-local for zero-latency file operations.
|
|
216
|
+
* Loaded in initializeClient(), null if not installed or load fails.
|
|
217
|
+
*/
|
|
218
|
+
nativeFileLocal = null;
|
|
219
|
+
/** Tool names provided by ability-file-local (used for routing) */
|
|
220
|
+
static FILE_LOCAL_TOOLS = new Set([
|
|
221
|
+
'list_files_and_folders', 'move_and_rename', 'copy_file',
|
|
222
|
+
'delete_file_or_folder', 'create_folder', 'create_file',
|
|
223
|
+
'watch_folder', 'unwatch_folder', 'compress_file', 'decompress_file',
|
|
224
|
+
'compress_multiple_files', 'decompress_multiple_files', 'search_files',
|
|
225
|
+
]);
|
|
232
226
|
// ============================================================================
|
|
233
227
|
// Constructor
|
|
234
228
|
// ============================================================================
|
|
@@ -257,33 +251,68 @@ export class BaseWorkerAgent {
|
|
|
257
251
|
* ```
|
|
258
252
|
*/
|
|
259
253
|
constructor(config) {
|
|
254
|
+
// If WorkerAgentFullConfig (has agentId at top level), use BaseAgent inheritance
|
|
255
|
+
const isFullConfig = 'agentId' in config && 'brokerUrl' in config && 'role' in config && 'agentRole' in config;
|
|
256
|
+
if (isFullConfig) {
|
|
257
|
+
// WorkerAgentFullConfig path: BaseAgent handles client, providers, memory
|
|
258
|
+
const fullConfig = config;
|
|
259
|
+
super({
|
|
260
|
+
agentId: fullConfig.agentId,
|
|
261
|
+
agentRole: fullConfig.agentRole,
|
|
262
|
+
version: fullConfig.version,
|
|
263
|
+
brokerUrl: fullConfig.brokerUrl,
|
|
264
|
+
networks: fullConfig.networks,
|
|
265
|
+
additionalBrokers: fullConfig.additionalBrokers,
|
|
266
|
+
provider: fullConfig.provider,
|
|
267
|
+
memory: fullConfig.memory,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Legacy WorkerAgentConfig path: create minimal BaseAgent config
|
|
272
|
+
const legacyConfig = config;
|
|
273
|
+
super({
|
|
274
|
+
agentId: legacyConfig.agentId || `agent-${legacyConfig.role}`,
|
|
275
|
+
agentRole: legacyConfig.role,
|
|
276
|
+
brokerUrl: legacyConfig.brokerUrl,
|
|
277
|
+
networks: legacyConfig.networks,
|
|
278
|
+
provider: legacyConfig.anthropicApiKey ? {
|
|
279
|
+
anthropicApiKey: legacyConfig.anthropicApiKey,
|
|
280
|
+
} : undefined,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
260
283
|
// Start factory timer for lifetime tracking
|
|
261
284
|
timer.start('factory');
|
|
262
|
-
// Store
|
|
263
|
-
this.
|
|
285
|
+
// Store worker-specific configuration
|
|
286
|
+
this.workerConfig = 'anthropicApiKey' in config
|
|
287
|
+
? config
|
|
288
|
+
: {
|
|
289
|
+
// Convert WorkerAgentFullConfig to WorkerAgentConfig for internal use
|
|
290
|
+
role: config.role,
|
|
291
|
+
worktreePath: config.worktreePath,
|
|
292
|
+
brokerUrl: config.brokerUrl,
|
|
293
|
+
networks: config.networks,
|
|
294
|
+
anthropicApiKey: config.provider?.anthropicApiKey || '',
|
|
295
|
+
agentId: config.agentId,
|
|
296
|
+
claudeModel: config.claudeModel,
|
|
297
|
+
capabilities: config.capabilities,
|
|
298
|
+
customBehaviors: config.customBehaviors,
|
|
299
|
+
};
|
|
264
300
|
// Extract and store individual config properties
|
|
265
|
-
this.role = config.role;
|
|
266
|
-
this.worktreePath = config.worktreePath;
|
|
301
|
+
this.role = 'role' in config ? config.role : config.role;
|
|
302
|
+
this.worktreePath = 'worktreePath' in config ? config.worktreePath : '';
|
|
267
303
|
this.networks = config.networks;
|
|
268
304
|
this.claudeModel = config.claudeModel || 'claude-sonnet-4-20250514';
|
|
269
305
|
this.capabilities = config.capabilities || [];
|
|
270
306
|
this.toolPrefixes = [];
|
|
271
|
-
// Initialize KĀDI client
|
|
272
|
-
this.client = new KadiClient({
|
|
273
|
-
name: `agent-${config.role}`,
|
|
274
|
-
version: '1.0.0',
|
|
275
|
-
brokers: {
|
|
276
|
-
default: { url: config.brokerUrl, networks: config.networks }
|
|
277
|
-
},
|
|
278
|
-
defaultBroker: 'default',
|
|
279
|
-
});
|
|
280
307
|
// COMPOSITION: Create BaseBot instance for circuit breaker and retry logic
|
|
281
|
-
|
|
282
|
-
|
|
308
|
+
const apiKey = 'anthropicApiKey' in config
|
|
309
|
+
? config.anthropicApiKey
|
|
310
|
+
: config.provider?.anthropicApiKey;
|
|
311
|
+
if (apiKey) {
|
|
283
312
|
const baseBotConfig = {
|
|
284
|
-
client: this.client,
|
|
285
|
-
anthropicApiKey:
|
|
286
|
-
botUserId: `agent-${
|
|
313
|
+
client: this.client, // inherited from BaseAgent
|
|
314
|
+
anthropicApiKey: apiKey,
|
|
315
|
+
botUserId: this.workerConfig.agentId || `agent-${this.role}`,
|
|
287
316
|
};
|
|
288
317
|
this.baseBot = new (class extends BaseBot {
|
|
289
318
|
async handleMention(_event) { }
|
|
@@ -295,6 +324,20 @@ export class BaseWorkerAgent {
|
|
|
295
324
|
// ============================================================================
|
|
296
325
|
// Protected Initialization Methods
|
|
297
326
|
// ============================================================================
|
|
327
|
+
/**
|
|
328
|
+
* Override BaseAgent.connect() to load native abilities after broker connection.
|
|
329
|
+
*/
|
|
330
|
+
async connect() {
|
|
331
|
+
await super.connect();
|
|
332
|
+
// Load ability-file-local natively (zero-latency file ops, in-process)
|
|
333
|
+
try {
|
|
334
|
+
this.nativeFileLocal = await this.client.loadNative('ability-file-local');
|
|
335
|
+
logger.info(MODULE_AGENT, 'Loaded ability-file-local natively', timer.elapsed('factory'));
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
logger.warn(MODULE_AGENT, `Could not load ability-file-local natively: ${err.message} — will use broker`, timer.elapsed('factory'));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
298
341
|
/**
|
|
299
342
|
* Initialize KĀDI client and connect to broker
|
|
300
343
|
*
|
|
@@ -316,14 +359,12 @@ export class BaseWorkerAgent {
|
|
|
316
359
|
* ```
|
|
317
360
|
*/
|
|
318
361
|
async initializeClient() {
|
|
319
|
-
logger.
|
|
320
|
-
logger.info(MODULE_AGENT, '🔌 Initializing KĀDI client...', timer.elapsed('factory'));
|
|
362
|
+
logger.debug(MODULE_AGENT, 'Initializing KADI client...', timer.elapsed('factory'));
|
|
321
363
|
try {
|
|
322
364
|
// Step 1: Start client connection to broker
|
|
323
|
-
logger.info(MODULE_AGENT, ' → Connecting to broker...', timer.elapsed('factory'));
|
|
324
365
|
try {
|
|
325
366
|
await this.client.connect();
|
|
326
|
-
logger.
|
|
367
|
+
logger.debug(MODULE_AGENT, 'Connected to broker', timer.elapsed('factory'));
|
|
327
368
|
}
|
|
328
369
|
catch (error) {
|
|
329
370
|
logger.error(MODULE_AGENT, `Client connection error: ${error.message || String(error)}`, timer.elapsed('factory'), error);
|
|
@@ -331,23 +372,15 @@ export class BaseWorkerAgent {
|
|
|
331
372
|
}
|
|
332
373
|
// Step 2: Initialize ability response subscription (if BaseBot available)
|
|
333
374
|
if (this.baseBot) {
|
|
334
|
-
logger.info(MODULE_AGENT, ' → Initializing ability response subscription...', timer.elapsed('factory'));
|
|
335
375
|
await this.baseBot['initializeAbilityResponseSubscription']();
|
|
336
|
-
logger.
|
|
376
|
+
logger.debug(MODULE_AGENT, 'Ability response subscription initialized', timer.elapsed('factory'));
|
|
337
377
|
}
|
|
338
|
-
logger.
|
|
339
|
-
logger.info(MODULE_AGENT, '✅ KĀDI client initialized successfully', timer.elapsed('factory'));
|
|
340
|
-
logger.info(MODULE_AGENT, ` Networks: ${this.networks.join(', ')}`, timer.elapsed('factory'));
|
|
341
|
-
logger.info(MODULE_AGENT, ` Protocol: Ready`, timer.elapsed('factory'));
|
|
342
|
-
logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
378
|
+
logger.debug(MODULE_AGENT, `KADI client ready (networks: ${this.networks.join(', ')})`, timer.elapsed('factory'));
|
|
343
379
|
// Note: client.serve() continues running in background to handle incoming requests
|
|
344
380
|
// We don't await it because it never resolves (blocks indefinitely)
|
|
345
381
|
}
|
|
346
382
|
catch (error) {
|
|
347
|
-
logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
348
|
-
logger.error(MODULE_AGENT, 'Failed to initialize KĀDI client', timer.elapsed('factory'), error);
|
|
349
|
-
logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
|
|
350
|
-
logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
383
|
+
logger.error(MODULE_AGENT, 'Failed to initialize KADI client', timer.elapsed('factory'), error);
|
|
351
384
|
throw error;
|
|
352
385
|
}
|
|
353
386
|
}
|
|
@@ -377,22 +410,15 @@ export class BaseWorkerAgent {
|
|
|
377
410
|
async subscribeToTaskAssignments() {
|
|
378
411
|
// Subscribe to generic task.assigned topic (role filtering done in handler)
|
|
379
412
|
const topic = `task.assigned`;
|
|
380
|
-
logger.
|
|
381
|
-
logger.info(MODULE_AGENT, `📡 Subscribing to task assignments...`, timer.elapsed('factory'));
|
|
382
|
-
logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
|
|
413
|
+
logger.debug(MODULE_AGENT, `Subscribing to task assignments (topic: ${topic})...`, timer.elapsed('factory'));
|
|
383
414
|
try {
|
|
384
415
|
// Subscribe to event topic with bound callback
|
|
385
416
|
// Using .bind(this) to preserve instance context in callback
|
|
386
417
|
await this.client.subscribe(topic, this.handleTaskAssignment.bind(this), { broker: 'default' });
|
|
387
|
-
logger.
|
|
388
|
-
logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
418
|
+
logger.debug(MODULE_AGENT, `Subscribed to ${topic}`, timer.elapsed('factory'));
|
|
389
419
|
}
|
|
390
420
|
catch (error) {
|
|
391
|
-
logger.error(MODULE_AGENT,
|
|
392
|
-
logger.error(MODULE_AGENT, `Failed to subscribe to task assignments`, timer.elapsed('factory'), error);
|
|
393
|
-
logger.error(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
|
|
394
|
-
logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
|
|
395
|
-
logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
421
|
+
logger.error(MODULE_AGENT, `Failed to subscribe to task assignments (topic: ${topic})`, timer.elapsed('factory'), error);
|
|
396
422
|
throw error;
|
|
397
423
|
}
|
|
398
424
|
}
|
|
@@ -466,8 +492,9 @@ export class BaseWorkerAgent {
|
|
|
466
492
|
}
|
|
467
493
|
// Mark task as in-flight before execution
|
|
468
494
|
this.processedTaskIds.add(validatedEvent.taskId);
|
|
469
|
-
// Capability validation:
|
|
470
|
-
|
|
495
|
+
// Capability validation: skip if task role matches this agent's role (role-based routing
|
|
496
|
+
// already ensures correct assignment). Only validate when roles don't match or are missing.
|
|
497
|
+
if (this.capabilities.length > 0 && validatedEvent.role !== this.role) {
|
|
471
498
|
const rejectionReason = this.validateTaskCapability(validatedEvent);
|
|
472
499
|
if (rejectionReason) {
|
|
473
500
|
logger.warn(MODULE_AGENT, ` ⚠️ Task capability mismatch`, timer.elapsed('factory'));
|
|
@@ -479,6 +506,9 @@ export class BaseWorkerAgent {
|
|
|
479
506
|
}
|
|
480
507
|
logger.info(MODULE_AGENT, ` ✅ Capability check passed`, timer.elapsed('factory'));
|
|
481
508
|
}
|
|
509
|
+
else if (validatedEvent.role === this.role) {
|
|
510
|
+
logger.info(MODULE_AGENT, ` ✅ Role match (${this.role}) — skipping capability check`, timer.elapsed('factory'));
|
|
511
|
+
}
|
|
482
512
|
// Worktree scope validation: soft warning only (worker always operates within its worktree directory)
|
|
483
513
|
const outOfScopeReason = this.validateTaskScope(validatedEvent);
|
|
484
514
|
if (outOfScopeReason) {
|
|
@@ -579,9 +609,23 @@ export class BaseWorkerAgent {
|
|
|
579
609
|
}
|
|
580
610
|
// Step 4: Build tool definitions — local tools + dynamic discovery from broker
|
|
581
611
|
const tools = await this.buildToolDefinitionsAsync();
|
|
582
|
-
logger.info(MODULE_AGENT, `🔧 Available tools: ${tools.length}
|
|
612
|
+
logger.info(MODULE_AGENT, `🔧 Available tools: ${tools.length}`, timer.elapsed('factory'));
|
|
613
|
+
// Step 4.5: Recall relevant past experience (non-blocking, best-effort)
|
|
614
|
+
let memoryContext = '';
|
|
615
|
+
if (this.memoryService) {
|
|
616
|
+
try {
|
|
617
|
+
const recallResult = await this.memoryService.recallRelevant(this.role, task.description, this.role, 3, ['*']);
|
|
618
|
+
if (recallResult.success && recallResult.data.length > 0) {
|
|
619
|
+
memoryContext = formatMemoryContext(recallResult.data);
|
|
620
|
+
logger.info(MODULE_AGENT, `Recalled ${recallResult.data.length} past patterns`, timer.elapsed('factory'));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
logger.warn(MODULE_AGENT, `Memory recall failed (non-fatal): ${err.message}`, timer.elapsed('factory'));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
583
627
|
// Step 5: Build initial system prompt
|
|
584
|
-
const systemPrompt = this.buildTaskSystemPrompt(task, implementationGuide, verificationCriteria);
|
|
628
|
+
const systemPrompt = this.buildTaskSystemPrompt(task, implementationGuide, verificationCriteria, memoryContext);
|
|
585
629
|
// Step 6: Enter tool-calling agent loop
|
|
586
630
|
const messages = [
|
|
587
631
|
{ role: 'user', content: systemPrompt }
|
|
@@ -598,6 +642,7 @@ export class BaseWorkerAgent {
|
|
|
598
642
|
const filesModified = [];
|
|
599
643
|
let commitSha = 'unknown';
|
|
600
644
|
let finalResponse = '';
|
|
645
|
+
let consecutiveFailures = 0;
|
|
601
646
|
for (let iteration = 0; iteration < BaseWorkerAgent.MAX_TOOL_LOOP_ITERATIONS; iteration++) {
|
|
602
647
|
logger.info(MODULE_AGENT, `🔄 Agent loop iteration ${iteration + 1}/${BaseWorkerAgent.MAX_TOOL_LOOP_ITERATIONS}`, timer.elapsed('factory'));
|
|
603
648
|
const result = await this.providerManager.chat(messages, chatOptions);
|
|
@@ -641,9 +686,11 @@ export class BaseWorkerAgent {
|
|
|
641
686
|
content: resultText,
|
|
642
687
|
tool_call_id: toolCall.id
|
|
643
688
|
});
|
|
689
|
+
consecutiveFailures = 0;
|
|
644
690
|
logger.info(MODULE_AGENT, ` ✅ Tool ${toolName} succeeded`, timer.elapsed('factory'));
|
|
645
691
|
}
|
|
646
692
|
catch (error) {
|
|
693
|
+
consecutiveFailures++;
|
|
647
694
|
const errorMsg = `Tool ${toolName} failed: ${error.message || String(error)}`;
|
|
648
695
|
logger.error(MODULE_AGENT, ` ❌ ${errorMsg}`, timer.elapsed('factory'));
|
|
649
696
|
messages.push({
|
|
@@ -653,6 +700,14 @@ export class BaseWorkerAgent {
|
|
|
653
700
|
});
|
|
654
701
|
}
|
|
655
702
|
}
|
|
703
|
+
// Circuit breaker: if 3+ consecutive tool failures, tell LLM to stop retrying
|
|
704
|
+
if (consecutiveFailures >= 3) {
|
|
705
|
+
logger.warn(MODULE_AGENT, `⚠️ Circuit breaker: ${consecutiveFailures} consecutive tool failures — instructing LLM to wrap up`, timer.elapsed('factory'));
|
|
706
|
+
messages.push({
|
|
707
|
+
role: 'user',
|
|
708
|
+
content: 'SYSTEM: Multiple consecutive tool failures detected. Stop retrying failed tools and provide your final response summarizing what you accomplished so far.'
|
|
709
|
+
});
|
|
710
|
+
}
|
|
656
711
|
// Continue loop — LLM will process tool results
|
|
657
712
|
}
|
|
658
713
|
else {
|
|
@@ -662,18 +717,49 @@ export class BaseWorkerAgent {
|
|
|
662
717
|
break;
|
|
663
718
|
}
|
|
664
719
|
}
|
|
665
|
-
// Step 7:
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
720
|
+
// Step 7: Safety net — auto-commit if LLM forgot to commit
|
|
721
|
+
if (commitSha === 'unknown' && (filesCreated.length > 0 || filesModified.length > 0)) {
|
|
722
|
+
logger.warn(MODULE_AGENT, `⚠️ LLM did not commit — auto-committing staged changes`, timer.elapsed('factory'));
|
|
723
|
+
try {
|
|
724
|
+
const commitMsg = this.commitFormat
|
|
725
|
+
? this.commitFormat.replace('{taskId}', task.taskId)
|
|
726
|
+
: `feat(${this.role}): task ${task.taskId}`;
|
|
727
|
+
const commitResult = await this.executeRemoteTool('git_git_commit', { path: this.worktreePath, message: commitMsg });
|
|
728
|
+
const sha = this.extractCommitSha(commitResult);
|
|
729
|
+
if (sha) {
|
|
730
|
+
commitSha = sha;
|
|
731
|
+
logger.info(MODULE_AGENT, ` ✅ Auto-commit succeeded: ${sha.substring(0, 7)}`, timer.elapsed('factory'));
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
logger.warn(MODULE_AGENT, ` ⚠️ Auto-commit returned no SHA`, timer.elapsed('factory'));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
catch (err) {
|
|
738
|
+
logger.warn(MODULE_AGENT, ` ⚠️ Auto-commit failed: ${err.message}`, timer.elapsed('factory'));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// Step 8: Publish completion or failure event
|
|
742
|
+
if (!commitSha || commitSha === 'unknown') {
|
|
743
|
+
logger.warn(MODULE_AGENT, `⚠️ No valid commit SHA — publishing task.failed instead of review request`, timer.elapsed('factory'));
|
|
744
|
+
// Clear dedup so agent-lead retries are accepted
|
|
745
|
+
this.processedTaskIds.delete(task.taskId);
|
|
746
|
+
await this.publishFailure(task.taskId, new Error('Git commit failed — no valid commit SHA available. Files may have been written but were not committed.'), task.questId, task.retryAttempt);
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
const contentSummary = finalResponse.length > 500
|
|
750
|
+
? finalResponse.substring(0, 500) + `... (${finalResponse.length} chars total)`
|
|
751
|
+
: finalResponse;
|
|
752
|
+
await this.publishCompletion(task.taskId, task.questId, filesCreated, filesModified, commitSha, contentSummary, task.retryAttempt);
|
|
753
|
+
}
|
|
670
754
|
logger.info(MODULE_AGENT, `✅ Task ${task.taskId} execution completed`, timer.elapsed('factory'));
|
|
671
755
|
}
|
|
672
756
|
catch (error) {
|
|
673
757
|
logger.error(MODULE_AGENT, `Failed to execute task ${task.taskId}`, timer.elapsed('factory'), error);
|
|
674
758
|
logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
|
|
675
759
|
logger.error(MODULE_AGENT, ` Stack: ${error.stack || 'No stack trace'}`, timer.elapsed('factory'));
|
|
676
|
-
|
|
760
|
+
// Clear dedup so agent-lead retries are accepted
|
|
761
|
+
this.processedTaskIds.delete(task.taskId);
|
|
762
|
+
await this.publishFailure(task.taskId, error, task.questId, task.retryAttempt);
|
|
677
763
|
throw error;
|
|
678
764
|
}
|
|
679
765
|
}
|
|
@@ -686,10 +772,13 @@ export class BaseWorkerAgent {
|
|
|
686
772
|
* Includes task context, role identity, worktree path, and instructions
|
|
687
773
|
* for using available tools (git, file operations).
|
|
688
774
|
*/
|
|
689
|
-
buildTaskSystemPrompt(task, implementationGuide, verificationCriteria) {
|
|
775
|
+
buildTaskSystemPrompt(task, implementationGuide, verificationCriteria, memoryContext) {
|
|
690
776
|
const retryContext = task.feedback
|
|
691
777
|
? `\n⚠️ REVISION REQUIRED (Attempt #${task.retryAttempt || 1})\nPrevious attempt was rejected. Feedback:\n${task.feedback}\nPlease carefully address the feedback above.\n`
|
|
692
778
|
: '';
|
|
779
|
+
const predecessorContext = task.predecessors && task.predecessors.length > 0
|
|
780
|
+
? `\n## Predecessor Tasks\nThe following tasks were completed before yours. You can read their output files using git_git_show with the branch name:\n${task.predecessors.map(p => `- **${p.role}** task (${p.taskId}): branch \`${p.branch}\`${p.commitHash ? ` commit \`${p.commitHash}\`` : ''}\n To read a file: git_git_show with path="${this.worktreePath}" and object="${p.branch}:<filename>"`).join('\n')}\n`
|
|
781
|
+
: '';
|
|
693
782
|
return `You are a ${this.role} agent working in the KĀDI multi-agent system.
|
|
694
783
|
Your worktree directory is: ${this.worktreePath}
|
|
695
784
|
|
|
@@ -697,28 +786,30 @@ Task ID: ${task.taskId}
|
|
|
697
786
|
Description: ${task.description}
|
|
698
787
|
Implementation Guide: ${implementationGuide}
|
|
699
788
|
${verificationCriteria ? `Verification Criteria: ${verificationCriteria}` : ''}
|
|
700
|
-
${retryContext}
|
|
789
|
+
${retryContext}${predecessorContext}
|
|
790
|
+
${memoryContext ? `\n## Past Experience\nThe following are relevant outcomes from past similar tasks. Use these to avoid known mistakes and apply proven patterns:\n${memoryContext}` : ''}
|
|
701
791
|
|
|
702
792
|
Instructions:
|
|
703
793
|
1. Analyze the task requirements carefully
|
|
704
794
|
2. Create the necessary files in the worktree using available tools
|
|
705
|
-
3. Stage
|
|
706
|
-
4.
|
|
795
|
+
3. Stage ALL changed files with git_git_add (pass path: "${this.worktreePath}")
|
|
796
|
+
4. CRITICAL: You MUST call git_git_commit with path: "${this.worktreePath}" to commit your staged changes. Do NOT return a summary until you have committed.
|
|
797
|
+
5. When done, provide a brief summary of what you created
|
|
707
798
|
|
|
708
799
|
Important:
|
|
709
800
|
- All file operations must be within the worktree: ${this.worktreePath}
|
|
710
|
-
-
|
|
801
|
+
- For ALL git tools (git_git_add, git_git_commit, etc.), you MUST pass path: "${this.worktreePath}" as a parameter.
|
|
802
|
+
- You MUST complete the full cycle: write files → git_git_add → git_git_commit. Skipping the commit is a failure.
|
|
711
803
|
- If the task description specifies an exact commit message, you MUST use that exact message
|
|
712
804
|
- Default commit message format (use ONLY when no commit message is specified in the task): "${this.commitFormat ? this.commitFormat.replace('{taskId}', task.taskId) : `feat(${this.role}): <description> [${task.taskId}]`}"
|
|
713
|
-
- Focus on ${this.role === 'artist' ? 'creative and artistic elements' : this.role === 'designer' ? 'design principles and aesthetics' : 'code quality and best practices'}`;
|
|
805
|
+
- Focus on ${this.role === 'artist' ? 'creative and artistic elements. IMPORTANT: You cannot generate binary image files (PNG, GIF, JPG). Instead, generate all pixel art and graphics as SVG files using <rect> elements on a grid. Use a limited palette (<=16 colors). Never create placeholder or fake image files — always produce real, renderable SVG that displays the intended artwork when opened in a browser.' : this.role === 'designer' ? 'design principles and aesthetics' : 'code quality and best practices'}`;
|
|
714
806
|
}
|
|
715
807
|
/**
|
|
716
808
|
* Build tool definitions: local tools + dynamic discovery from KĀDI broker
|
|
717
809
|
*
|
|
718
810
|
* 1. Always includes local tools (write_file, read_file)
|
|
719
|
-
* 2. Discovers network tools from broker via kadi.ability.list
|
|
720
|
-
* 3.
|
|
721
|
-
* 4. Converts to OpenAI-compatible ToolDefinition format
|
|
811
|
+
* 2. Discovers all network tools from broker via kadi.ability.list
|
|
812
|
+
* 3. Converts to OpenAI-compatible ToolDefinition format
|
|
722
813
|
*/
|
|
723
814
|
async buildToolDefinitionsAsync() {
|
|
724
815
|
const tools = [];
|
|
@@ -752,29 +843,53 @@ Important:
|
|
|
752
843
|
}
|
|
753
844
|
}
|
|
754
845
|
});
|
|
755
|
-
//
|
|
756
|
-
if (this.
|
|
846
|
+
// Include tools from natively loaded ability-file-local (available even without broker)
|
|
847
|
+
if (this.nativeFileLocal) {
|
|
848
|
+
const nativeTools = this.nativeFileLocal.getTools();
|
|
849
|
+
for (const tool of nativeTools) {
|
|
850
|
+
tools.push({
|
|
851
|
+
type: 'function',
|
|
852
|
+
function: {
|
|
853
|
+
name: tool.name,
|
|
854
|
+
description: tool.description || '',
|
|
855
|
+
parameters: tool.inputSchema || {
|
|
856
|
+
type: 'object',
|
|
857
|
+
properties: {},
|
|
858
|
+
required: []
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
logger.info(MODULE_AGENT, `Added ${nativeTools.length} tools from native ability-file-local`, timer.elapsed('factory'));
|
|
864
|
+
}
|
|
865
|
+
// Discover network tools from broker, filtered by toolPrefixes if configured
|
|
866
|
+
if (this.client.isConnected()) {
|
|
757
867
|
try {
|
|
758
868
|
const response = await this.client.invokeRemote('kadi.ability.list', { includeProviders: false });
|
|
759
869
|
if (response?.tools && Array.isArray(response.tools)) {
|
|
760
870
|
for (const tool of response.tools) {
|
|
761
|
-
//
|
|
762
|
-
if (this.
|
|
763
|
-
|
|
764
|
-
type: 'function',
|
|
765
|
-
function: {
|
|
766
|
-
name: tool.name,
|
|
767
|
-
description: tool.description || '',
|
|
768
|
-
parameters: tool.inputSchema || {
|
|
769
|
-
type: 'object',
|
|
770
|
-
properties: {},
|
|
771
|
-
required: []
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
});
|
|
871
|
+
// Skip tools already loaded natively (avoid duplicates)
|
|
872
|
+
if (this.nativeFileLocal && BaseWorkerAgent.FILE_LOCAL_TOOLS.has(tool.name)) {
|
|
873
|
+
continue;
|
|
775
874
|
}
|
|
875
|
+
// If toolPrefixes configured, filter by prefix; otherwise include all
|
|
876
|
+
if (this.toolPrefixes.length > 0 && !this.toolPrefixes.some(prefix => tool.name.startsWith(prefix))) {
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
tools.push({
|
|
880
|
+
type: 'function',
|
|
881
|
+
function: {
|
|
882
|
+
name: tool.name,
|
|
883
|
+
description: tool.description || '',
|
|
884
|
+
parameters: tool.inputSchema || {
|
|
885
|
+
type: 'object',
|
|
886
|
+
properties: {},
|
|
887
|
+
required: []
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
});
|
|
776
891
|
}
|
|
777
|
-
logger.info(MODULE_AGENT, `Discovered ${tools.length - 2} network tools from broker (filtered by prefixes: ${this.toolPrefixes.join(', ')})`, timer.elapsed('factory'));
|
|
892
|
+
logger.info(MODULE_AGENT, `Discovered ${tools.length - 2} network tools from broker${this.toolPrefixes.length > 0 ? ` (filtered by prefixes: ${this.toolPrefixes.join(', ')})` : ''}`, timer.elapsed('factory'));
|
|
778
893
|
}
|
|
779
894
|
}
|
|
780
895
|
catch (error) {
|
|
@@ -783,7 +898,7 @@ Important:
|
|
|
783
898
|
this.appendHardcodedGitTools(tools);
|
|
784
899
|
}
|
|
785
900
|
}
|
|
786
|
-
else
|
|
901
|
+
else {
|
|
787
902
|
// Not connected to broker — use hardcoded fallback
|
|
788
903
|
logger.warn(MODULE_AGENT, 'Not connected to broker — using hardcoded tool definitions', timer.elapsed('factory'));
|
|
789
904
|
this.appendHardcodedGitTools(tools);
|
|
@@ -794,7 +909,7 @@ Important:
|
|
|
794
909
|
* Fallback: append hardcoded git tool definitions when broker discovery fails
|
|
795
910
|
*/
|
|
796
911
|
appendHardcodedGitTools(tools) {
|
|
797
|
-
if (!this.toolPrefixes.some(p => p.startsWith('git_git_')))
|
|
912
|
+
if (this.toolPrefixes.length > 0 && !this.toolPrefixes.some(p => p.startsWith('git_git_')))
|
|
798
913
|
return;
|
|
799
914
|
tools.push({
|
|
800
915
|
type: 'function',
|
|
@@ -908,8 +1023,17 @@ Important:
|
|
|
908
1023
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
909
1024
|
return { success: true, content };
|
|
910
1025
|
}
|
|
1026
|
+
// Route to native ability-file-local if loaded (zero-latency, in-process)
|
|
1027
|
+
if (this.nativeFileLocal && BaseWorkerAgent.FILE_LOCAL_TOOLS.has(toolName)) {
|
|
1028
|
+
return await this.nativeFileLocal.invoke(toolName, toolArgs);
|
|
1029
|
+
}
|
|
911
1030
|
// Route to MCP tool via KĀDI broker
|
|
912
1031
|
const result = await this.client.invokeRemote(toolName, toolArgs);
|
|
1032
|
+
// Check for error responses from the broker/tool
|
|
1033
|
+
if (result?.isError) {
|
|
1034
|
+
const errorText = result?.content?.[0]?.text ?? JSON.stringify(result);
|
|
1035
|
+
throw new Error(`Remote tool ${toolName} returned error: ${errorText}`);
|
|
1036
|
+
}
|
|
913
1037
|
// invokeRemote returns { content: [{ type, text }] } — extract text
|
|
914
1038
|
if (result?.content?.[0]?.text) {
|
|
915
1039
|
try {
|
|
@@ -982,8 +1106,8 @@ Important:
|
|
|
982
1106
|
* @returns Formatted commit message
|
|
983
1107
|
*/
|
|
984
1108
|
formatCommitMessage(taskId, files, taskDescription) {
|
|
985
|
-
if (this.
|
|
986
|
-
return this.
|
|
1109
|
+
if (this.workerConfig.customBehaviors?.formatCommitMessage) {
|
|
1110
|
+
return this.workerConfig.customBehaviors.formatCommitMessage(taskId, files);
|
|
987
1111
|
}
|
|
988
1112
|
if (taskDescription) {
|
|
989
1113
|
const commitMsgMatch = taskDescription.match(/(?:commit.*?(?:with\s+)?message|commit\s+message)[\s:]*['"`]([^'"`]+)['"`]/i);
|
|
@@ -996,8 +1120,8 @@ Important:
|
|
|
996
1120
|
/**
|
|
997
1121
|
* Publish task completion event
|
|
998
1122
|
*
|
|
999
|
-
* Publishes
|
|
1000
|
-
*
|
|
1123
|
+
* Publishes TaskReviewRequestedPayload to KĀDI broker on the qa network.
|
|
1124
|
+
* Per QUEST_WORKFLOW_V2: worker → task.review_requested → agent-qa for validation.
|
|
1001
1125
|
*
|
|
1002
1126
|
* Publishing failures are handled gracefully - errors are logged but do not throw.
|
|
1003
1127
|
* This ensures task execution completes even if event publishing fails.
|
|
@@ -1015,42 +1139,53 @@ Important:
|
|
|
1015
1139
|
* [],
|
|
1016
1140
|
* 'a1b2c3d4e5f6g7h8i9j0'
|
|
1017
1141
|
* );
|
|
1018
|
-
* // Publishes to: task.
|
|
1142
|
+
* // Publishes to: task.review_requested (→ agent-qa)
|
|
1019
1143
|
* ```
|
|
1020
1144
|
*/
|
|
1021
|
-
async publishCompletion(taskId, questId,
|
|
1022
|
-
//
|
|
1023
|
-
const
|
|
1024
|
-
//
|
|
1025
|
-
const topic = `task.completed`;
|
|
1026
|
-
// Create payload matching TaskCompletedEvent schema
|
|
1145
|
+
async publishCompletion(taskId, questId, _filesCreated, _filesModified, commitSha, _contentSummary, retryAttempt) {
|
|
1146
|
+
// Per QUEST_WORKFLOW_V2: worker publishes task.review_requested → agent-qa for validation
|
|
1147
|
+
const topic = `task.review_requested`;
|
|
1148
|
+
// Create payload matching TaskReviewRequestedPayload schema
|
|
1027
1149
|
const payload = {
|
|
1028
1150
|
taskId,
|
|
1029
|
-
questId,
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
filesModified,
|
|
1034
|
-
commitSha,
|
|
1035
|
-
timestamp: new Date().toISOString(),
|
|
1036
|
-
agent: `agent-${this.role}`,
|
|
1037
|
-
...(contentSummary ? { contentSummary } : {}),
|
|
1038
|
-
...(worktreePath ? { worktreePath } : {})
|
|
1151
|
+
questId: questId || '',
|
|
1152
|
+
branch: this.worktreePath,
|
|
1153
|
+
commitHash: commitSha,
|
|
1154
|
+
...(retryAttempt && { revisionCount: retryAttempt }),
|
|
1039
1155
|
};
|
|
1040
1156
|
try {
|
|
1041
|
-
logger.info(MODULE_AGENT, `📢 Publishing
|
|
1157
|
+
logger.info(MODULE_AGENT, `📢 Publishing review request event`, timer.elapsed('factory'));
|
|
1042
1158
|
logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
|
|
1043
1159
|
logger.info(MODULE_AGENT, ` Task ID: ${taskId}`, timer.elapsed('factory'));
|
|
1044
1160
|
if (questId) {
|
|
1045
1161
|
logger.info(MODULE_AGENT, ` Quest ID: ${questId}`, timer.elapsed('factory'));
|
|
1046
1162
|
}
|
|
1047
1163
|
logger.info(MODULE_AGENT, ` Commit SHA: ${commitSha.substring(0, 7)}`, timer.elapsed('factory'));
|
|
1048
|
-
await this.client.publish(topic, payload, { broker: 'default', network: '
|
|
1049
|
-
logger.info(MODULE_AGENT, ` ✅
|
|
1164
|
+
await this.client.publish(topic, payload, { broker: 'default', network: 'qa' });
|
|
1165
|
+
logger.info(MODULE_AGENT, ` ✅ Review request published to qa network`, timer.elapsed('factory'));
|
|
1166
|
+
// Store task memory (fire-and-forget) for learning from past outcomes
|
|
1167
|
+
if (this.memoryService) {
|
|
1168
|
+
this.memoryService.storeTaskMemory({
|
|
1169
|
+
taskId,
|
|
1170
|
+
questId: questId || '',
|
|
1171
|
+
agentId: this.client.readAgentJson().name || `agent-worker-${this.role}`,
|
|
1172
|
+
agentRole: this.role,
|
|
1173
|
+
taskType: this.role,
|
|
1174
|
+
description: _contentSummary || `Task ${taskId} completed`,
|
|
1175
|
+
outcome: 'success',
|
|
1176
|
+
context: `worktree: ${this.worktreePath}`,
|
|
1177
|
+
result: `commit: ${commitSha}`,
|
|
1178
|
+
entities: [],
|
|
1179
|
+
duration: 0,
|
|
1180
|
+
timestamp: Date.now(),
|
|
1181
|
+
}).catch((err) => {
|
|
1182
|
+
logger.warn(MODULE_AGENT, `Failed to store task memory (non-fatal): ${err.message || String(err)}`, timer.elapsed('factory'));
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1050
1185
|
}
|
|
1051
1186
|
catch (error) {
|
|
1052
1187
|
// Handle publishing failures gracefully - don't throw
|
|
1053
|
-
logger.error(MODULE_AGENT, `Failed to publish
|
|
1188
|
+
logger.error(MODULE_AGENT, `Failed to publish review request event (non-fatal)`, timer.elapsed('factory'), error);
|
|
1054
1189
|
logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
|
|
1055
1190
|
logger.error(MODULE_AGENT, ` Task execution succeeded despite event publishing failure`, timer.elapsed('factory'));
|
|
1056
1191
|
// Don't throw - event publishing failure should not fail the task
|
|
@@ -1078,7 +1213,7 @@ Important:
|
|
|
1078
1213
|
* }
|
|
1079
1214
|
* ```
|
|
1080
1215
|
*/
|
|
1081
|
-
async publishFailure(taskId, error, questId) {
|
|
1216
|
+
async publishFailure(taskId, error, questId, retryAttempt) {
|
|
1082
1217
|
// Generic topic — agent identity is in the payload (agent, role fields)
|
|
1083
1218
|
const topic = `task.failed`;
|
|
1084
1219
|
// Create payload matching TaskFailedEvent schema
|
|
@@ -1088,14 +1223,15 @@ Important:
|
|
|
1088
1223
|
role: this.role,
|
|
1089
1224
|
error: error.message || String(error),
|
|
1090
1225
|
timestamp: new Date().toISOString(),
|
|
1091
|
-
agent: `agent-${this.role}
|
|
1226
|
+
agent: `agent-${this.role}`,
|
|
1227
|
+
retryAttempt: retryAttempt ?? 0
|
|
1092
1228
|
};
|
|
1093
1229
|
try {
|
|
1094
1230
|
logger.info(MODULE_AGENT, `📢 Publishing failure event`, timer.elapsed('factory'));
|
|
1095
1231
|
logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
|
|
1096
1232
|
logger.info(MODULE_AGENT, ` Task ID: ${taskId}`, timer.elapsed('factory'));
|
|
1097
1233
|
logger.info(MODULE_AGENT, ` Error: ${error.message.substring(0, 100)}${error.message.length > 100 ? '...' : ''}`, timer.elapsed('factory'));
|
|
1098
|
-
await this.client.publish(topic, payload, { broker: 'default', network:
|
|
1234
|
+
await this.client.publish(topic, payload, { broker: 'default', network: this.role });
|
|
1099
1235
|
logger.info(MODULE_AGENT, ` ✅ Failure event published`, timer.elapsed('factory'));
|
|
1100
1236
|
}
|
|
1101
1237
|
catch (publishError) {
|
|
@@ -1188,7 +1324,7 @@ Important:
|
|
|
1188
1324
|
logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
|
|
1189
1325
|
logger.info(MODULE_AGENT, ` Task ID: ${taskId}`, timer.elapsed('factory'));
|
|
1190
1326
|
logger.info(MODULE_AGENT, ` Reason: ${reason.substring(0, 100)}${reason.length > 100 ? '...' : ''}`, timer.elapsed('factory'));
|
|
1191
|
-
await this.client.publish(topic, payload, { broker: 'default', network:
|
|
1327
|
+
await this.client.publish(topic, payload, { broker: 'default', network: this.role });
|
|
1192
1328
|
logger.info(MODULE_AGENT, ` ✅ Rejection event published`, timer.elapsed('factory'));
|
|
1193
1329
|
}
|
|
1194
1330
|
catch (error) {
|
|
@@ -1222,20 +1358,24 @@ Important:
|
|
|
1222
1358
|
* ```
|
|
1223
1359
|
*/
|
|
1224
1360
|
async start() {
|
|
1225
|
-
logger.
|
|
1226
|
-
logger.
|
|
1227
|
-
logger.
|
|
1228
|
-
logger.
|
|
1229
|
-
logger.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1361
|
+
logger.debug(MODULE_AGENT, `Starting Worker Agent: ${this.role}`, timer.elapsed('factory'));
|
|
1362
|
+
logger.debug(MODULE_AGENT, `Broker URL: ${this.workerConfig.brokerUrl}`, timer.elapsed('factory'));
|
|
1363
|
+
logger.debug(MODULE_AGENT, `Networks: ${this.networks.join(', ')}`, timer.elapsed('factory'));
|
|
1364
|
+
logger.debug(MODULE_AGENT, `Worktree Path: ${this.worktreePath}`, timer.elapsed('factory'));
|
|
1365
|
+
logger.debug(MODULE_AGENT, `LLM Model: ${this.claudeModel}`, timer.elapsed('factory'));
|
|
1366
|
+
// If already connected (via BaseAgent.connect()), skip client initialization
|
|
1367
|
+
if (!this.connected) {
|
|
1368
|
+
await this.initializeClient();
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
// Still need ability response subscription even if already connected
|
|
1372
|
+
if (this.baseBot) {
|
|
1373
|
+
await this.baseBot['initializeAbilityResponseSubscription']();
|
|
1374
|
+
logger.debug(MODULE_AGENT, 'Ability response subscription initialized', timer.elapsed('factory'));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1235
1377
|
// Subscribe to task assignment events
|
|
1236
1378
|
await this.subscribeToTaskAssignments();
|
|
1237
|
-
// TODO: Implement remaining start logic in next tasks
|
|
1238
|
-
// 1. Register tools (if any)
|
|
1239
1379
|
}
|
|
1240
1380
|
/**
|
|
1241
1381
|
* Stop the worker agent
|
|
@@ -1255,26 +1395,17 @@ Important:
|
|
|
1255
1395
|
* ```
|
|
1256
1396
|
*/
|
|
1257
1397
|
async stop() {
|
|
1258
|
-
logger.
|
|
1259
|
-
logger.info(MODULE_AGENT, `Stopping Worker Agent: ${this.role}`, timer.elapsed('factory'));
|
|
1260
|
-
logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
|
|
1398
|
+
logger.debug(MODULE_AGENT, `Stopping Worker Agent: ${this.role}`, timer.elapsed('factory'));
|
|
1261
1399
|
try {
|
|
1262
|
-
//
|
|
1263
|
-
//
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
this.providerManager = null;
|
|
1269
|
-
logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
1270
|
-
logger.info(MODULE_AGENT, '✅ Worker agent stopped successfully', timer.elapsed('factory'));
|
|
1271
|
-
logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
|
|
1400
|
+
// Delegate to BaseAgent.shutdown() which handles:
|
|
1401
|
+
// - ProviderManager disposal
|
|
1402
|
+
// - MemoryService disposal
|
|
1403
|
+
// - Client disconnect
|
|
1404
|
+
await this.shutdown();
|
|
1405
|
+
logger.debug(MODULE_AGENT, 'Worker agent stopped', timer.elapsed('factory'));
|
|
1272
1406
|
}
|
|
1273
1407
|
catch (error) {
|
|
1274
|
-
logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
1275
1408
|
logger.error(MODULE_AGENT, 'Error during shutdown', timer.elapsed('factory'), error);
|
|
1276
|
-
logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
|
|
1277
|
-
logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
|
|
1278
1409
|
// Don't throw - best effort cleanup
|
|
1279
1410
|
}
|
|
1280
1411
|
}
|
|
@@ -1332,6 +1463,17 @@ Important:
|
|
|
1332
1463
|
setProviderManager(pm) {
|
|
1333
1464
|
this.providerManager = pm;
|
|
1334
1465
|
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Set the MemoryService for task memory storage and recall
|
|
1468
|
+
*
|
|
1469
|
+
* Called by the agent entry point after constructing BaseAgent
|
|
1470
|
+
* (which creates the MemoryService).
|
|
1471
|
+
*
|
|
1472
|
+
* @param ms - MemoryService instance from BaseAgent
|
|
1473
|
+
*/
|
|
1474
|
+
setMemoryService(ms) {
|
|
1475
|
+
this.memoryService = ms;
|
|
1476
|
+
}
|
|
1335
1477
|
/**
|
|
1336
1478
|
* Configure role-specific settings from a loaded RoleConfig
|
|
1337
1479
|
*
|
|
@@ -1471,17 +1613,18 @@ export class WorkerAgentFactory {
|
|
|
1471
1613
|
*/
|
|
1472
1614
|
static createAgent(config) {
|
|
1473
1615
|
try {
|
|
1474
|
-
//
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1616
|
+
// Only validate with Zod schema for legacy WorkerAgentConfig
|
|
1617
|
+
if ('anthropicApiKey' in config) {
|
|
1618
|
+
const validatedConfig = WorkerAgentConfigSchema.parse(config);
|
|
1619
|
+
return new BaseWorkerAgent(validatedConfig);
|
|
1620
|
+
}
|
|
1621
|
+
// WorkerAgentFullConfig — skip Zod validation (BaseAgent handles its own)
|
|
1622
|
+
return new BaseWorkerAgent(config);
|
|
1479
1623
|
}
|
|
1480
1624
|
catch (error) {
|
|
1481
1625
|
// Re-throw Zod validation errors with context
|
|
1482
1626
|
if (error.name === 'ZodError') {
|
|
1483
1627
|
logger.error(MODULE_AGENT, 'Worker agent configuration validation failed', timer.elapsed('factory'), error);
|
|
1484
|
-
logger.error(MODULE_AGENT, ' Validation errors:', timer.elapsed('factory'));
|
|
1485
1628
|
for (const issue of error.issues || []) {
|
|
1486
1629
|
logger.error(MODULE_AGENT, ` - ${issue.path.join('.')}: ${issue.message}`, timer.elapsed('factory'));
|
|
1487
1630
|
}
|