@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/dist_ts/smartagent.classes.driveragent.d.ts +11 -1
- package/dist_ts/smartagent.classes.driveragent.js +26 -5
- package/dist_ts/smartagent.classes.dualagent.d.ts +8 -0
- package/dist_ts/smartagent.classes.dualagent.js +161 -6
- package/dist_ts/smartagent.interfaces.d.ts +43 -0
- package/dist_ts/smartagent.interfaces.js +1 -1
- package/dist_ts/smartagent.tools.filesystem.js +235 -7
- package/package.json +1 -1
- package/readme.md +153 -29
- package/ts/smartagent.classes.driveragent.ts +35 -4
- package/ts/smartagent.classes.dualagent.ts +174 -5
- package/ts/smartagent.interfaces.ts +62 -0
- package/ts/smartagent.tools.filesystem.ts +272 -6
package/readme.md
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
# @push.rocks/smartagent
|
|
2
|
-
|
|
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
|
-
-
|
|
16
|
-
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 |
|
|
344
|
-
| Anthropic |
|
|
345
|
-
| Perplexity |
|
|
346
|
-
| Groq |
|
|
347
|
-
| Ollama |
|
|
348
|
-
| XAI |
|
|
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
|
|
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.
|
|
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
|
|
487
|
+
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
364
488
|
|
|
365
|
-
For any legal inquiries or
|
|
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
|
-
|
|
28
|
+
options?: IDriverAgentOptions | string
|
|
18
29
|
) {
|
|
19
30
|
this.provider = provider;
|
|
20
|
-
|
|
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
|
|
109
|
-
|
|
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,
|
|
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
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
:
|
|
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
|
|