@push.rocks/smartagent 1.2.3 → 1.2.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/readme.md CHANGED
@@ -1,21 +1,31 @@
1
1
  # @push.rocks/smartagent
2
- A dual-agent agentic framework with Driver and Guardian agents for safe, policy-controlled AI task execution.
2
+
3
+ A dual-agent agentic framework with **Driver** and **Guardian** agents for safe, policy-controlled AI task execution. 🤖🛡️
3
4
 
4
5
  ## Install
6
+
5
7
  ```bash
6
8
  npm install @push.rocks/smartagent
7
9
  # or
8
10
  pnpm install @push.rocks/smartagent
9
11
  ```
10
12
 
13
+ ## Issue Reporting and Security
14
+
15
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
16
+
11
17
  ## Overview
12
18
 
13
- SmartAgent implements a dual-agent architecture:
19
+ SmartAgent implements a **dual-agent architecture** where AI safety isn't just an afterthought—it's baked into the core design:
14
20
 
15
- - **Driver Agent**: Executes tasks, reasons about goals, and proposes tool calls
16
- - **Guardian Agent**: Evaluates tool call proposals against a policy prompt, approving or rejecting with feedback
21
+ - **🎯 Driver Agent**: The executor. Reasons about goals, plans steps, and proposes tool calls
22
+ - **🛡️ Guardian Agent**: The gatekeeper. Evaluates every tool call against your policy, approving or rejecting with feedback
17
23
 
18
- This design ensures safe tool use through AI-based policy evaluation rather than rigid programmatic rules.
24
+ This design ensures safe tool use through **AI-based policy evaluation** rather than rigid programmatic rules. The Guardian can understand context, nuance, and intent—catching dangerous operations that simple regex or allowlists would miss.
25
+
26
+ ### Why Dual-Agent?
27
+
28
+ Traditional AI agents have a fundamental problem: they're given tools and expected to use them responsibly. SmartAgent adds a second AI specifically trained to evaluate whether each action is safe and appropriate. Think of it as separation of concerns, but for AI safety.
19
29
 
20
30
  ## Architecture
21
31
 
@@ -89,8 +99,11 @@ await orchestrator.stop();
89
99
 
90
100
  ## Standard Tools
91
101
 
92
- ### FilesystemTool
93
- File and directory operations using `@push.rocks/smartfs`.
102
+ SmartAgent comes with five battle-tested tools out of the box:
103
+
104
+ ### 🗂️ FilesystemTool
105
+
106
+ File and directory operations powered by `@push.rocks/smartfs`.
94
107
 
95
108
  **Actions**: `read`, `write`, `append`, `list`, `delete`, `exists`, `stat`, `copy`, `move`, `mkdir`
96
109
 
@@ -104,7 +117,15 @@ File and directory operations using `@push.rocks/smartfs`.
104
117
  </tool_call>
105
118
  ```
106
119
 
107
- ### HttpTool
120
+ **Scoped Filesystem**: Lock file operations to a specific directory:
121
+
122
+ ```typescript
123
+ // Only allow access within a specific directory
124
+ orchestrator.registerScopedFilesystemTool('/home/user/workspace');
125
+ ```
126
+
127
+ ### 🌐 HttpTool
128
+
108
129
  HTTP requests using `@push.rocks/smartrequest`.
109
130
 
110
131
  **Actions**: `get`, `post`, `put`, `patch`, `delete`
@@ -113,13 +134,14 @@ HTTP requests using `@push.rocks/smartrequest`.
113
134
  <tool_call>
114
135
  <tool>http</tool>
115
136
  <action>get</action>
116
- <params>{"url": "https://api.example.com/data"}</params>
137
+ <params>{"url": "https://api.example.com/data", "headers": {"Authorization": "Bearer token"}}</params>
117
138
  <reasoning>Fetching data from the API endpoint</reasoning>
118
139
  </tool_call>
119
140
  ```
120
141
 
121
- ### ShellTool
122
- Secure shell command execution using `@push.rocks/smartshell` with `execSpawn` (no shell injection).
142
+ ### 💻 ShellTool
143
+
144
+ Secure shell command execution using `@push.rocks/smartshell` with `execSpawn` (no shell injection possible).
123
145
 
124
146
  **Actions**: `execute`, `which`
125
147
 
@@ -132,7 +154,10 @@ Secure shell command execution using `@push.rocks/smartshell` with `execSpawn` (
132
154
  </tool_call>
133
155
  ```
134
156
 
135
- ### BrowserTool
157
+ > 🔒 **Security Note**: The shell tool uses `execSpawn` with `shell: false`, meaning command and arguments are passed separately. This makes shell injection attacks impossible.
158
+
159
+ ### 🌍 BrowserTool
160
+
136
161
  Web page interaction using `@push.rocks/smartbrowser` (Puppeteer-based).
137
162
 
138
163
  **Actions**: `screenshot`, `pdf`, `evaluate`, `getPageContent`
@@ -146,17 +171,18 @@ Web page interaction using `@push.rocks/smartbrowser` (Puppeteer-based).
146
171
  </tool_call>
147
172
  ```
148
173
 
149
- ### DenoTool
150
- Execute TypeScript/JavaScript code in a sandboxed Deno environment using `@push.rocks/smartdeno`.
174
+ ### 🦕 DenoTool
175
+
176
+ Execute TypeScript/JavaScript code in a **sandboxed Deno environment** with fine-grained permission control.
151
177
 
152
178
  **Actions**: `execute`, `executeWithResult`
153
179
 
154
180
  **Permissions**: `all`, `env`, `ffi`, `hrtime`, `net`, `read`, `run`, `sys`, `write`
155
181
 
156
- By default, code runs fully sandboxed with no permissions. Permissions must be explicitly requested.
182
+ By default, code runs **fully sandboxed with no permissions**. Permissions must be explicitly requested and are subject to Guardian approval.
157
183
 
158
184
  ```typescript
159
- // Simple code execution
185
+ // Simple code execution (sandboxed, no permissions)
160
186
  <tool_call>
161
187
  <tool>deno</tool>
162
188
  <action>execute</action>
@@ -188,7 +214,10 @@ By default, code runs fully sandboxed with no permissions. Permissions must be e
188
214
 
189
215
  ## Guardian Policy Examples
190
216
 
191
- ### Strict Security Policy
217
+ The Guardian's power comes from your policy. Here are battle-tested examples:
218
+
219
+ ### 🔐 Strict Security Policy
220
+
192
221
  ```typescript
193
222
  const securityPolicy = `
194
223
  SECURITY POLICY:
@@ -204,7 +233,8 @@ When rejecting, always explain:
204
233
  `;
205
234
  ```
206
235
 
207
- ### Development Environment Policy
236
+ ### 🛠️ Development Environment Policy
237
+
208
238
  ```typescript
209
239
  const devPolicy = `
210
240
  DEVELOPMENT POLICY:
@@ -221,7 +251,8 @@ Always verify:
221
251
  `;
222
252
  ```
223
253
 
224
- ### Deno Code Execution Policy
254
+ ### 🦕 Deno Code Execution Policy
255
+
225
256
  ```typescript
226
257
  const denoPolicy = `
227
258
  DENO CODE EXECUTION POLICY:
@@ -253,6 +284,9 @@ interface IDualAgentOptions {
253
284
  groqToken?: string;
254
285
  xaiToken?: string;
255
286
 
287
+ // Use existing SmartAi instance (optional - avoids duplicate providers)
288
+ smartAiInstance?: SmartAi;
289
+
256
290
  // Provider selection
257
291
  defaultProvider?: TProvider; // For both Driver and Guardian
258
292
  guardianProvider?: TProvider; // Optional: separate provider for Guardian
@@ -278,6 +312,14 @@ interface IDualAgentRunResult {
278
312
  history: IAgentMessage[]; // Full conversation history
279
313
  status: TDualAgentRunStatus; // 'completed' | 'max_iterations_reached' | etc.
280
314
  }
315
+
316
+ type TDualAgentRunStatus =
317
+ | 'completed'
318
+ | 'in_progress'
319
+ | 'max_iterations_reached'
320
+ | 'max_rejections_reached'
321
+ | 'clarification_needed'
322
+ | 'error';
281
323
  ```
282
324
 
283
325
  ## Custom Tools
@@ -306,10 +348,12 @@ class MyCustomTool extends BaseToolWrapper {
306
348
  ];
307
349
 
308
350
  public async initialize(): Promise<void> {
351
+ // Setup your tool (called when orchestrator.start() runs)
309
352
  this.isInitialized = true;
310
353
  }
311
354
 
312
355
  public async cleanup(): Promise<void> {
356
+ // Cleanup resources (called when orchestrator.stop() runs)
313
357
  this.isInitialized = false;
314
358
  }
315
359
 
@@ -327,6 +371,7 @@ class MyCustomTool extends BaseToolWrapper {
327
371
  return { success: false, error: 'Unknown action' };
328
372
  }
329
373
 
374
+ // Human-readable summary for Guardian evaluation
330
375
  public getCallSummary(action: string, params: Record<string, unknown>): string {
331
376
  return `Custom action "${action}" with input "${params.input}"`;
332
377
  }
@@ -336,32 +381,111 @@ class MyCustomTool extends BaseToolWrapper {
336
381
  orchestrator.registerTool(new MyCustomTool());
337
382
  ```
338
383
 
384
+ ## Reusing SmartAi Instances
385
+
386
+ If you already have a `@push.rocks/smartai` instance, you can share it:
387
+
388
+ ```typescript
389
+ import { SmartAi } from '@push.rocks/smartai';
390
+ import { DualAgentOrchestrator } from '@push.rocks/smartagent';
391
+
392
+ const smartai = new SmartAi({ openaiToken: 'sk-...' });
393
+ await smartai.start();
394
+
395
+ const orchestrator = new DualAgentOrchestrator({
396
+ smartAiInstance: smartai, // Reuse existing instance
397
+ guardianPolicyPrompt: '...',
398
+ });
399
+
400
+ await orchestrator.start();
401
+ // ... use orchestrator ...
402
+ await orchestrator.stop();
403
+
404
+ // SmartAi instance lifecycle is managed separately
405
+ await smartai.stop();
406
+ ```
407
+
339
408
  ## Supported Providers
340
409
 
410
+ SmartAgent supports all providers from `@push.rocks/smartai`:
411
+
341
412
  | Provider | Driver | Guardian |
342
413
  |----------|:------:|:--------:|
343
- | OpenAI | Yes | Yes |
344
- | Anthropic | Yes | Yes |
345
- | Perplexity | Yes | Yes |
346
- | Groq | Yes | Yes |
347
- | Ollama | Yes | Yes |
348
- | XAI | Yes | Yes |
414
+ | OpenAI | | |
415
+ | Anthropic | | |
416
+ | Perplexity | | |
417
+ | Groq | | |
418
+ | Ollama | | |
419
+ | XAI | | |
420
+ | Exo | ✅ | ✅ |
421
+
422
+ **💡 Pro tip**: Use a faster/cheaper model for Guardian (like Groq) and a more capable model for Driver:
423
+
424
+ ```typescript
425
+ const orchestrator = new DualAgentOrchestrator({
426
+ openaiToken: 'sk-...',
427
+ groqToken: 'gsk-...',
428
+ defaultProvider: 'openai', // Driver uses OpenAI
429
+ guardianProvider: 'groq', // Guardian uses Groq (faster, cheaper)
430
+ guardianPolicyPrompt: '...',
431
+ });
432
+ ```
433
+
434
+ ## API Reference
435
+
436
+ ### DualAgentOrchestrator
437
+
438
+ | Method | Description |
439
+ |--------|-------------|
440
+ | `start()` | Initialize all tools and AI providers |
441
+ | `stop()` | Cleanup all tools and resources |
442
+ | `run(task: string)` | Execute a task and return result |
443
+ | `continueTask(input: string)` | Continue a task with user input |
444
+ | `registerTool(tool)` | Register a custom tool |
445
+ | `registerStandardTools()` | Register all built-in tools |
446
+ | `registerScopedFilesystemTool(basePath)` | Register filesystem tool with path restriction |
447
+ | `setGuardianPolicy(policy)` | Update Guardian policy at runtime |
448
+ | `getHistory()` | Get conversation history |
449
+ | `getToolNames()` | Get list of registered tool names |
450
+ | `isActive()` | Check if orchestrator is running |
451
+
452
+ ### Exports
453
+
454
+ ```typescript
455
+ // Main classes
456
+ export { DualAgentOrchestrator } from '@push.rocks/smartagent';
457
+ export { DriverAgent } from '@push.rocks/smartagent';
458
+ export { GuardianAgent } from '@push.rocks/smartagent';
459
+
460
+ // Tools
461
+ export { BaseToolWrapper } from '@push.rocks/smartagent';
462
+ export { FilesystemTool } from '@push.rocks/smartagent';
463
+ export { HttpTool } from '@push.rocks/smartagent';
464
+ export { ShellTool } from '@push.rocks/smartagent';
465
+ export { BrowserTool } from '@push.rocks/smartagent';
466
+ export { DenoTool } from '@push.rocks/smartagent';
467
+
468
+ // Types and interfaces
469
+ export * from '@push.rocks/smartagent'; // All interfaces
470
+ ```
349
471
 
350
472
  ## License and Legal Information
351
473
 
352
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
474
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
353
475
 
354
476
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
355
477
 
356
478
  ### Trademarks
357
479
 
358
- This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
480
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
481
+
482
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
359
483
 
360
484
  ### Company Information
361
485
 
362
486
  Task Venture Capital GmbH
363
- Registered at District court Bremen HRB 35230 HB, Germany
487
+ Registered at District Court Bremen HRB 35230 HB, Germany
364
488
 
365
- For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
489
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
366
490
 
367
491
  By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -2,6 +2,16 @@ import * as plugins from './plugins.js';
2
2
  import * as interfaces from './smartagent.interfaces.js';
3
3
  import type { BaseToolWrapper } from './smartagent.tools.base.js';
4
4
 
5
+ /**
6
+ * Options for configuring the DriverAgent
7
+ */
8
+ export interface IDriverAgentOptions {
9
+ /** Custom system message for the driver */
10
+ systemMessage?: string;
11
+ /** Maximum history messages to pass to API (default: 20). Set to 0 for unlimited. */
12
+ maxHistoryMessages?: number;
13
+ }
14
+
5
15
  /**
6
16
  * DriverAgent - Executes tasks by reasoning and proposing tool calls
7
17
  * Works in conjunction with GuardianAgent for approval
@@ -9,15 +19,24 @@ import type { BaseToolWrapper } from './smartagent.tools.base.js';
9
19
  export class DriverAgent {
10
20
  private provider: plugins.smartai.MultiModalModel;
11
21
  private systemMessage: string;
22
+ private maxHistoryMessages: number;
12
23
  private messageHistory: plugins.smartai.ChatMessage[] = [];
13
24
  private tools: Map<string, BaseToolWrapper> = new Map();
14
25
 
15
26
  constructor(
16
27
  provider: plugins.smartai.MultiModalModel,
17
- systemMessage?: string
28
+ options?: IDriverAgentOptions | string
18
29
  ) {
19
30
  this.provider = provider;
20
- this.systemMessage = systemMessage || this.getDefaultSystemMessage();
31
+
32
+ // Support both legacy string systemMessage and new options object
33
+ if (typeof options === 'string') {
34
+ this.systemMessage = options || this.getDefaultSystemMessage();
35
+ this.maxHistoryMessages = 20;
36
+ } else {
37
+ this.systemMessage = options?.systemMessage || this.getDefaultSystemMessage();
38
+ this.maxHistoryMessages = options?.maxHistoryMessages ?? 20;
39
+ }
21
40
  }
22
41
 
23
42
  /**
@@ -105,8 +124,20 @@ export class DriverAgent {
105
124
  fullSystemMessage = this.getNoToolsSystemMessage();
106
125
  }
107
126
 
108
- // Get response from provider (pass all but last user message as history)
109
- const historyForChat = this.messageHistory.slice(0, -1);
127
+ // Get response from provider with history windowing
128
+ // Keep original task and most recent messages to avoid token explosion
129
+ let historyForChat: plugins.smartai.ChatMessage[];
130
+ const fullHistory = this.messageHistory.slice(0, -1); // Exclude the just-added message
131
+
132
+ if (this.maxHistoryMessages > 0 && fullHistory.length > this.maxHistoryMessages) {
133
+ // Keep the original task (first message) and most recent messages
134
+ historyForChat = [
135
+ fullHistory[0], // Original task
136
+ ...fullHistory.slice(-(this.maxHistoryMessages - 1)), // Recent messages
137
+ ];
138
+ } else {
139
+ historyForChat = fullHistory;
140
+ }
110
141
 
111
142
  const response = await this.provider.chat({
112
143
  systemMessage: fullSystemMessage,
@@ -30,6 +30,8 @@ export class DualAgentOrchestrator {
30
30
  maxIterations: 20,
31
31
  maxConsecutiveRejections: 3,
32
32
  defaultProvider: 'openai',
33
+ maxResultChars: 15000,
34
+ maxHistoryMessages: 20,
33
35
  ...options,
34
36
  };
35
37
 
@@ -68,6 +70,60 @@ export class DualAgentOrchestrator {
68
70
  }
69
71
  }
70
72
 
73
+ /**
74
+ * Emit a progress event if callback is configured
75
+ */
76
+ private emitProgress(event: Omit<interfaces.IProgressEvent, 'timestamp' | 'logLevel' | 'logMessage'>): void {
77
+ if (this.options.onProgress) {
78
+ const prefix = this.options.logPrefix ? `${this.options.logPrefix} ` : '';
79
+ const { logLevel, logMessage } = this.formatProgressEvent(event, prefix);
80
+
81
+ this.options.onProgress({
82
+ ...event,
83
+ timestamp: new Date(),
84
+ logLevel,
85
+ logMessage,
86
+ });
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Format a progress event into a log level and message
92
+ */
93
+ private formatProgressEvent(
94
+ event: Omit<interfaces.IProgressEvent, 'timestamp' | 'logLevel' | 'logMessage'>,
95
+ prefix: string
96
+ ): { logLevel: interfaces.TLogLevel; logMessage: string } {
97
+ switch (event.type) {
98
+ case 'task_started':
99
+ return { logLevel: 'info', logMessage: `${prefix}Task started` };
100
+ case 'iteration_started':
101
+ return { logLevel: 'info', logMessage: `${prefix}Iteration ${event.iteration}/${event.maxIterations}` };
102
+ case 'tool_proposed':
103
+ return { logLevel: 'info', logMessage: `${prefix} → Proposing: ${event.toolName}.${event.action}` };
104
+ case 'guardian_evaluating':
105
+ return { logLevel: 'info', logMessage: `${prefix} ⏳ Guardian evaluating...` };
106
+ case 'tool_approved':
107
+ return { logLevel: 'info', logMessage: `${prefix} ✓ Approved: ${event.toolName}.${event.action}` };
108
+ case 'tool_rejected':
109
+ return { logLevel: 'warn', logMessage: `${prefix} ✗ Rejected: ${event.toolName}.${event.action} - ${event.reason}` };
110
+ case 'tool_executing':
111
+ return { logLevel: 'info', logMessage: `${prefix} ⚡ Executing: ${event.toolName}.${event.action}...` };
112
+ case 'tool_completed':
113
+ return { logLevel: 'info', logMessage: `${prefix} ✓ Completed: ${event.message}` };
114
+ case 'task_completed':
115
+ return { logLevel: 'success', logMessage: `${prefix}Task completed in ${event.iteration} iterations` };
116
+ case 'clarification_needed':
117
+ return { logLevel: 'warn', logMessage: `${prefix}Clarification needed from user` };
118
+ case 'max_iterations':
119
+ return { logLevel: 'error', logMessage: `${prefix}${event.message}` };
120
+ case 'max_rejections':
121
+ return { logLevel: 'error', logMessage: `${prefix}${event.message}` };
122
+ default:
123
+ return { logLevel: 'info', logMessage: `${prefix}${event.type}` };
124
+ }
125
+ }
126
+
71
127
  /**
72
128
  * Register a custom tool
73
129
  */
@@ -124,7 +180,10 @@ export class DualAgentOrchestrator {
124
180
  : this.driverProvider;
125
181
 
126
182
  // NOW create agents with initialized providers
127
- this.driver = new DriverAgent(this.driverProvider, this.options.driverSystemMessage);
183
+ this.driver = new DriverAgent(this.driverProvider, {
184
+ systemMessage: this.options.driverSystemMessage,
185
+ maxHistoryMessages: this.options.maxHistoryMessages,
186
+ });
128
187
  this.guardian = new GuardianAgent(this.guardianProvider, this.options.guardianPolicyPrompt);
129
188
 
130
189
  // Register any tools that were added before start() with the agents
@@ -190,6 +249,12 @@ export class DualAgentOrchestrator {
190
249
  let driverResponse = await this.driver.startTask(task);
191
250
  this.conversationHistory.push(driverResponse);
192
251
 
252
+ // Emit task started event
253
+ this.emitProgress({
254
+ type: 'task_started',
255
+ message: task.length > 100 ? task.substring(0, 100) + '...' : task,
256
+ });
257
+
193
258
  while (
194
259
  iterations < this.options.maxIterations! &&
195
260
  consecutiveRejections < this.options.maxConsecutiveRejections! &&
@@ -197,15 +262,36 @@ export class DualAgentOrchestrator {
197
262
  ) {
198
263
  iterations++;
199
264
 
265
+ // Emit iteration started event
266
+ this.emitProgress({
267
+ type: 'iteration_started',
268
+ iteration: iterations,
269
+ maxIterations: this.options.maxIterations,
270
+ });
271
+
200
272
  // Check if task is complete
201
273
  if (this.driver.isTaskComplete(driverResponse.content)) {
202
274
  completed = true;
203
275
  finalResult = this.driver.extractTaskResult(driverResponse.content) || driverResponse.content;
276
+
277
+ // Emit task completed event
278
+ this.emitProgress({
279
+ type: 'task_completed',
280
+ iteration: iterations,
281
+ message: 'Task completed successfully',
282
+ });
204
283
  break;
205
284
  }
206
285
 
207
286
  // Check if driver needs clarification
208
287
  if (this.driver.needsClarification(driverResponse.content)) {
288
+ // Emit clarification needed event
289
+ this.emitProgress({
290
+ type: 'clarification_needed',
291
+ iteration: iterations,
292
+ message: 'Driver needs clarification from user',
293
+ });
294
+
209
295
  // Return with clarification needed status
210
296
  return {
211
297
  success: false,
@@ -232,6 +318,15 @@ export class DualAgentOrchestrator {
232
318
  // Process the first proposal (one at a time)
233
319
  const proposal = proposals[0];
234
320
 
321
+ // Emit tool proposed event
322
+ this.emitProgress({
323
+ type: 'tool_proposed',
324
+ iteration: iterations,
325
+ toolName: proposal.toolName,
326
+ action: proposal.action,
327
+ message: `${proposal.toolName}.${proposal.action}`,
328
+ });
329
+
235
330
  // Quick validation first
236
331
  const quickDecision = this.guardian.quickValidate(proposal);
237
332
  let decision: interfaces.IGuardianDecision;
@@ -239,6 +334,14 @@ export class DualAgentOrchestrator {
239
334
  if (quickDecision) {
240
335
  decision = quickDecision;
241
336
  } else {
337
+ // Emit guardian evaluating event
338
+ this.emitProgress({
339
+ type: 'guardian_evaluating',
340
+ iteration: iterations,
341
+ toolName: proposal.toolName,
342
+ action: proposal.action,
343
+ });
344
+
242
345
  // Full AI evaluation
243
346
  decision = await this.guardian.evaluate(proposal, task);
244
347
  }
@@ -246,6 +349,14 @@ export class DualAgentOrchestrator {
246
349
  if (decision.decision === 'approve') {
247
350
  consecutiveRejections = 0;
248
351
 
352
+ // Emit tool approved event
353
+ this.emitProgress({
354
+ type: 'tool_approved',
355
+ iteration: iterations,
356
+ toolName: proposal.toolName,
357
+ action: proposal.action,
358
+ });
359
+
249
360
  // Execute the tool
250
361
  const tool = this.tools.get(proposal.toolName);
251
362
  if (!tool) {
@@ -258,12 +369,48 @@ export class DualAgentOrchestrator {
258
369
  }
259
370
 
260
371
  try {
372
+ // Emit tool executing event
373
+ this.emitProgress({
374
+ type: 'tool_executing',
375
+ iteration: iterations,
376
+ toolName: proposal.toolName,
377
+ action: proposal.action,
378
+ });
379
+
261
380
  const result = await tool.execute(proposal.action, proposal.params);
262
381
 
263
- // Send result to driver
264
- const resultMessage = result.success
265
- ? `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${JSON.stringify(result.result, null, 2)}`
266
- : `TOOL ERROR (${proposal.toolName}.${proposal.action}):\n${result.error}`;
382
+ // Emit tool completed event
383
+ this.emitProgress({
384
+ type: 'tool_completed',
385
+ iteration: iterations,
386
+ toolName: proposal.toolName,
387
+ action: proposal.action,
388
+ message: result.success ? 'success' : result.error,
389
+ });
390
+
391
+ // Build result message (prefer summary if provided, otherwise stringify result)
392
+ let resultMessage: string;
393
+ if (result.success) {
394
+ if (result.summary) {
395
+ // Use tool-provided summary
396
+ resultMessage = `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${result.summary}`;
397
+ } else {
398
+ // Stringify and potentially truncate
399
+ const resultStr = JSON.stringify(result.result, null, 2);
400
+ const maxChars = this.options.maxResultChars ?? 15000;
401
+
402
+ if (maxChars > 0 && resultStr.length > maxChars) {
403
+ // Truncate the result
404
+ const truncated = resultStr.substring(0, maxChars);
405
+ const omittedTokens = Math.round((resultStr.length - maxChars) / 4);
406
+ resultMessage = `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${truncated}\n\n[... output truncated, ~${omittedTokens} tokens omitted. Use more specific parameters to reduce output size.]`;
407
+ } else {
408
+ resultMessage = `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${resultStr}`;
409
+ }
410
+ }
411
+ } else {
412
+ resultMessage = `TOOL ERROR (${proposal.toolName}.${proposal.action}):\n${result.error}`;
413
+ }
267
414
 
268
415
  this.conversationHistory.push({
269
416
  role: 'system',
@@ -285,6 +432,15 @@ export class DualAgentOrchestrator {
285
432
  // Rejected
286
433
  consecutiveRejections++;
287
434
 
435
+ // Emit tool rejected event
436
+ this.emitProgress({
437
+ type: 'tool_rejected',
438
+ iteration: iterations,
439
+ toolName: proposal.toolName,
440
+ action: proposal.action,
441
+ reason: decision.reason,
442
+ });
443
+
288
444
  // Build rejection feedback
289
445
  let feedback = `TOOL CALL REJECTED by Guardian:\n`;
290
446
  feedback += `- Reason: ${decision.reason}\n`;
@@ -316,8 +472,21 @@ export class DualAgentOrchestrator {
316
472
  if (!completed) {
317
473
  if (iterations >= this.options.maxIterations!) {
318
474
  status = 'max_iterations_reached';
475
+ // Emit max iterations event
476
+ this.emitProgress({
477
+ type: 'max_iterations',
478
+ iteration: iterations,
479
+ maxIterations: this.options.maxIterations,
480
+ message: `Maximum iterations (${this.options.maxIterations}) reached`,
481
+ });
319
482
  } else if (consecutiveRejections >= this.options.maxConsecutiveRejections!) {
320
483
  status = 'max_rejections_reached';
484
+ // Emit max rejections event
485
+ this.emitProgress({
486
+ type: 'max_rejections',
487
+ iteration: iterations,
488
+ message: `Maximum consecutive rejections (${this.options.maxConsecutiveRejections}) reached`,
489
+ });
321
490
  }
322
491
  }
323
492