update
This commit is contained in:
@@ -70,6 +70,60 @@ export class DualAgentOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a progress event if callback is configured
|
||||
*/
|
||||
private emitProgress(event: Omit<interfaces.IProgressEvent, 'timestamp' | 'logLevel' | 'logMessage'>): void {
|
||||
if (this.options.onProgress) {
|
||||
const prefix = this.options.logPrefix ? `${this.options.logPrefix} ` : '';
|
||||
const { logLevel, logMessage } = this.formatProgressEvent(event, prefix);
|
||||
|
||||
this.options.onProgress({
|
||||
...event,
|
||||
timestamp: new Date(),
|
||||
logLevel,
|
||||
logMessage,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a progress event into a log level and message
|
||||
*/
|
||||
private formatProgressEvent(
|
||||
event: Omit<interfaces.IProgressEvent, 'timestamp' | 'logLevel' | 'logMessage'>,
|
||||
prefix: string
|
||||
): { logLevel: interfaces.TLogLevel; logMessage: string } {
|
||||
switch (event.type) {
|
||||
case 'task_started':
|
||||
return { logLevel: 'info', logMessage: `${prefix}Task started` };
|
||||
case 'iteration_started':
|
||||
return { logLevel: 'info', logMessage: `${prefix}Iteration ${event.iteration}/${event.maxIterations}` };
|
||||
case 'tool_proposed':
|
||||
return { logLevel: 'info', logMessage: `${prefix} → Proposing: ${event.toolName}.${event.action}` };
|
||||
case 'guardian_evaluating':
|
||||
return { logLevel: 'info', logMessage: `${prefix} ⏳ Guardian evaluating...` };
|
||||
case 'tool_approved':
|
||||
return { logLevel: 'info', logMessage: `${prefix} ✓ Approved: ${event.toolName}.${event.action}` };
|
||||
case 'tool_rejected':
|
||||
return { logLevel: 'warn', logMessage: `${prefix} ✗ Rejected: ${event.toolName}.${event.action} - ${event.reason}` };
|
||||
case 'tool_executing':
|
||||
return { logLevel: 'info', logMessage: `${prefix} ⚡ Executing: ${event.toolName}.${event.action}...` };
|
||||
case 'tool_completed':
|
||||
return { logLevel: 'info', logMessage: `${prefix} ✓ Completed: ${event.message}` };
|
||||
case 'task_completed':
|
||||
return { logLevel: 'success', logMessage: `${prefix}Task completed in ${event.iteration} iterations` };
|
||||
case 'clarification_needed':
|
||||
return { logLevel: 'warn', logMessage: `${prefix}Clarification needed from user` };
|
||||
case 'max_iterations':
|
||||
return { logLevel: 'error', logMessage: `${prefix}${event.message}` };
|
||||
case 'max_rejections':
|
||||
return { logLevel: 'error', logMessage: `${prefix}${event.message}` };
|
||||
default:
|
||||
return { logLevel: 'info', logMessage: `${prefix}${event.type}` };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom tool
|
||||
*/
|
||||
@@ -126,7 +180,10 @@ export class DualAgentOrchestrator {
|
||||
: this.driverProvider;
|
||||
|
||||
// NOW create agents with initialized providers
|
||||
this.driver = new DriverAgent(this.driverProvider, this.options.driverSystemMessage);
|
||||
this.driver = new DriverAgent(this.driverProvider, {
|
||||
systemMessage: this.options.driverSystemMessage,
|
||||
maxHistoryMessages: this.options.maxHistoryMessages,
|
||||
});
|
||||
this.guardian = new GuardianAgent(this.guardianProvider, this.options.guardianPolicyPrompt);
|
||||
|
||||
// Register any tools that were added before start() with the agents
|
||||
@@ -192,6 +249,12 @@ export class DualAgentOrchestrator {
|
||||
let driverResponse = await this.driver.startTask(task);
|
||||
this.conversationHistory.push(driverResponse);
|
||||
|
||||
// Emit task started event
|
||||
this.emitProgress({
|
||||
type: 'task_started',
|
||||
message: task.length > 100 ? task.substring(0, 100) + '...' : task,
|
||||
});
|
||||
|
||||
while (
|
||||
iterations < this.options.maxIterations! &&
|
||||
consecutiveRejections < this.options.maxConsecutiveRejections! &&
|
||||
@@ -199,15 +262,36 @@ export class DualAgentOrchestrator {
|
||||
) {
|
||||
iterations++;
|
||||
|
||||
// Emit iteration started event
|
||||
this.emitProgress({
|
||||
type: 'iteration_started',
|
||||
iteration: iterations,
|
||||
maxIterations: this.options.maxIterations,
|
||||
});
|
||||
|
||||
// Check if task is complete
|
||||
if (this.driver.isTaskComplete(driverResponse.content)) {
|
||||
completed = true;
|
||||
finalResult = this.driver.extractTaskResult(driverResponse.content) || driverResponse.content;
|
||||
|
||||
// Emit task completed event
|
||||
this.emitProgress({
|
||||
type: 'task_completed',
|
||||
iteration: iterations,
|
||||
message: 'Task completed successfully',
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if driver needs clarification
|
||||
if (this.driver.needsClarification(driverResponse.content)) {
|
||||
// Emit clarification needed event
|
||||
this.emitProgress({
|
||||
type: 'clarification_needed',
|
||||
iteration: iterations,
|
||||
message: 'Driver needs clarification from user',
|
||||
});
|
||||
|
||||
// Return with clarification needed status
|
||||
return {
|
||||
success: false,
|
||||
@@ -234,6 +318,15 @@ export class DualAgentOrchestrator {
|
||||
// Process the first proposal (one at a time)
|
||||
const proposal = proposals[0];
|
||||
|
||||
// Emit tool proposed event
|
||||
this.emitProgress({
|
||||
type: 'tool_proposed',
|
||||
iteration: iterations,
|
||||
toolName: proposal.toolName,
|
||||
action: proposal.action,
|
||||
message: `${proposal.toolName}.${proposal.action}`,
|
||||
});
|
||||
|
||||
// Quick validation first
|
||||
const quickDecision = this.guardian.quickValidate(proposal);
|
||||
let decision: interfaces.IGuardianDecision;
|
||||
@@ -241,6 +334,14 @@ export class DualAgentOrchestrator {
|
||||
if (quickDecision) {
|
||||
decision = quickDecision;
|
||||
} else {
|
||||
// Emit guardian evaluating event
|
||||
this.emitProgress({
|
||||
type: 'guardian_evaluating',
|
||||
iteration: iterations,
|
||||
toolName: proposal.toolName,
|
||||
action: proposal.action,
|
||||
});
|
||||
|
||||
// Full AI evaluation
|
||||
decision = await this.guardian.evaluate(proposal, task);
|
||||
}
|
||||
@@ -248,6 +349,14 @@ export class DualAgentOrchestrator {
|
||||
if (decision.decision === 'approve') {
|
||||
consecutiveRejections = 0;
|
||||
|
||||
// Emit tool approved event
|
||||
this.emitProgress({
|
||||
type: 'tool_approved',
|
||||
iteration: iterations,
|
||||
toolName: proposal.toolName,
|
||||
action: proposal.action,
|
||||
});
|
||||
|
||||
// Execute the tool
|
||||
const tool = this.tools.get(proposal.toolName);
|
||||
if (!tool) {
|
||||
@@ -260,8 +369,25 @@ export class DualAgentOrchestrator {
|
||||
}
|
||||
|
||||
try {
|
||||
// Emit tool executing event
|
||||
this.emitProgress({
|
||||
type: 'tool_executing',
|
||||
iteration: iterations,
|
||||
toolName: proposal.toolName,
|
||||
action: proposal.action,
|
||||
});
|
||||
|
||||
const result = await tool.execute(proposal.action, proposal.params);
|
||||
|
||||
// Emit tool completed event
|
||||
this.emitProgress({
|
||||
type: 'tool_completed',
|
||||
iteration: iterations,
|
||||
toolName: proposal.toolName,
|
||||
action: proposal.action,
|
||||
message: result.success ? 'success' : result.error,
|
||||
});
|
||||
|
||||
// Build result message (prefer summary if provided, otherwise stringify result)
|
||||
let resultMessage: string;
|
||||
if (result.success) {
|
||||
@@ -306,6 +432,15 @@ export class DualAgentOrchestrator {
|
||||
// Rejected
|
||||
consecutiveRejections++;
|
||||
|
||||
// Emit tool rejected event
|
||||
this.emitProgress({
|
||||
type: 'tool_rejected',
|
||||
iteration: iterations,
|
||||
toolName: proposal.toolName,
|
||||
action: proposal.action,
|
||||
reason: decision.reason,
|
||||
});
|
||||
|
||||
// Build rejection feedback
|
||||
let feedback = `TOOL CALL REJECTED by Guardian:\n`;
|
||||
feedback += `- Reason: ${decision.reason}\n`;
|
||||
@@ -337,8 +472,21 @@ export class DualAgentOrchestrator {
|
||||
if (!completed) {
|
||||
if (iterations >= this.options.maxIterations!) {
|
||||
status = 'max_iterations_reached';
|
||||
// Emit max iterations event
|
||||
this.emitProgress({
|
||||
type: 'max_iterations',
|
||||
iteration: iterations,
|
||||
maxIterations: this.options.maxIterations,
|
||||
message: `Maximum iterations (${this.options.maxIterations}) reached`,
|
||||
});
|
||||
} else if (consecutiveRejections >= this.options.maxConsecutiveRejections!) {
|
||||
status = 'max_rejections_reached';
|
||||
// Emit max rejections event
|
||||
this.emitProgress({
|
||||
type: 'max_rejections',
|
||||
iteration: iterations,
|
||||
message: `Maximum consecutive rejections (${this.options.maxConsecutiveRejections}) reached`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ export interface IDualAgentOptions extends plugins.smartai.ISmartAiOptions {
|
||||
maxResultChars?: number;
|
||||
/** Maximum history messages to pass to API (default: 20). Set to 0 for unlimited. */
|
||||
maxHistoryMessages?: number;
|
||||
/** Optional callback for live progress updates during execution */
|
||||
onProgress?: (event: IProgressEvent) => void;
|
||||
/** Prefix for log messages (e.g., "[README]", "[Commit]"). Default: empty */
|
||||
logPrefix?: string;
|
||||
}
|
||||
|
||||
// ================================
|
||||
@@ -201,6 +205,58 @@ export interface IDualAgentRunResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Progress Event Interfaces
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Progress event types for live feedback during agent execution
|
||||
*/
|
||||
export type TProgressEventType =
|
||||
| 'task_started'
|
||||
| 'iteration_started'
|
||||
| 'tool_proposed'
|
||||
| 'guardian_evaluating'
|
||||
| 'tool_approved'
|
||||
| 'tool_rejected'
|
||||
| 'tool_executing'
|
||||
| 'tool_completed'
|
||||
| 'task_completed'
|
||||
| 'clarification_needed'
|
||||
| 'max_iterations'
|
||||
| 'max_rejections';
|
||||
|
||||
/**
|
||||
* Log level for progress events
|
||||
*/
|
||||
export type TLogLevel = 'info' | 'warn' | 'error' | 'success';
|
||||
|
||||
/**
|
||||
* Progress event for live feedback during agent execution
|
||||
*/
|
||||
export interface IProgressEvent {
|
||||
/** Type of progress event */
|
||||
type: TProgressEventType;
|
||||
/** Current iteration number */
|
||||
iteration?: number;
|
||||
/** Maximum iterations configured */
|
||||
maxIterations?: number;
|
||||
/** Name of the tool being used */
|
||||
toolName?: string;
|
||||
/** Action being performed */
|
||||
action?: string;
|
||||
/** Reason for rejection or other explanation */
|
||||
reason?: string;
|
||||
/** Human-readable message about the event */
|
||||
message?: string;
|
||||
/** Timestamp of the event */
|
||||
timestamp: Date;
|
||||
/** Log level for this event (info, warn, error, success) */
|
||||
logLevel: TLogLevel;
|
||||
/** Pre-formatted log message ready for output */
|
||||
logMessage: string;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Utility Types
|
||||
// ================================
|
||||
|
||||
@@ -494,32 +494,27 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
const items = await dir.list();
|
||||
|
||||
for (const item of items) {
|
||||
const itemPath = plugins.path.join(dirPath, item);
|
||||
const itemRelPath = relativePath ? `${relativePath}/${item}` : item;
|
||||
// item is IDirectoryEntry with name, path, isFile, isDirectory properties
|
||||
const itemPath = item.path;
|
||||
const itemRelPath = relativePath ? `${relativePath}/${item.name}` : item.name;
|
||||
const isDir = item.isDirectory;
|
||||
|
||||
try {
|
||||
const stats = await this.smartfs.file(itemPath).stat();
|
||||
const isDir = stats.isDirectory;
|
||||
const entry: ITreeEntry = {
|
||||
path: itemPath,
|
||||
relativePath: itemRelPath,
|
||||
isDir,
|
||||
depth,
|
||||
};
|
||||
|
||||
const entry: ITreeEntry = {
|
||||
path: itemPath,
|
||||
relativePath: itemRelPath,
|
||||
isDir,
|
||||
depth,
|
||||
};
|
||||
if (showSizes && !isDir && item.stats) {
|
||||
entry.size = item.stats.size;
|
||||
}
|
||||
|
||||
if (showSizes && !isDir) {
|
||||
entry.size = stats.size;
|
||||
}
|
||||
entries.push(entry);
|
||||
|
||||
entries.push(entry);
|
||||
|
||||
// Recurse into directories
|
||||
if (isDir && depth < maxDepth) {
|
||||
await collectEntries(itemPath, depth + 1, itemRelPath);
|
||||
}
|
||||
} catch {
|
||||
// Skip items we can't stat
|
||||
// Recurse into directories
|
||||
if (isDir && depth < maxDepth) {
|
||||
await collectEntries(itemPath, depth + 1, itemRelPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -607,13 +602,20 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
const dir = this.smartfs.directory(basePath).recursive().filter(pattern);
|
||||
const matches = await dir.list();
|
||||
|
||||
// Return file paths relative to base path for readability
|
||||
const files = matches.map((entry) => ({
|
||||
path: entry.path,
|
||||
relativePath: plugins.path.relative(basePath, entry.path),
|
||||
isDirectory: entry.isDirectory,
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
pattern,
|
||||
basePath,
|
||||
matches,
|
||||
count: matches.length,
|
||||
files,
|
||||
count: files.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user