@push.rocks/smartagent 1.2.3 → 1.2.5
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/plugins.d.ts +2 -0
- package/dist_ts/plugins.js +4 -1
- 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 +10 -1
- package/dist_ts/smartagent.classes.dualagent.js +164 -8
- package/dist_ts/smartagent.interfaces.d.ts +43 -0
- package/dist_ts/smartagent.interfaces.js +1 -1
- package/dist_ts/smartagent.tools.filesystem.d.ts +8 -0
- package/dist_ts/smartagent.tools.filesystem.js +261 -8
- package/package.json +3 -2
- package/readme.md +154 -30
- package/ts/plugins.ts +5 -0
- package/ts/smartagent.classes.driveragent.ts +35 -4
- package/ts/smartagent.classes.dualagent.ts +177 -7
- package/ts/smartagent.interfaces.ts +62 -0
- package/ts/smartagent.tools.filesystem.ts +305 -7
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
|
-
Task Venture Capital GmbH
|
|
363
|
-
Registered at District
|
|
486
|
+
Task Venture Capital GmbH
|
|
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.
|
package/ts/plugins.ts
CHANGED
|
@@ -3,6 +3,11 @@ import * as path from 'path';
|
|
|
3
3
|
|
|
4
4
|
export { path };
|
|
5
5
|
|
|
6
|
+
// third party
|
|
7
|
+
import { minimatch } from 'minimatch';
|
|
8
|
+
|
|
9
|
+
export { minimatch };
|
|
10
|
+
|
|
6
11
|
// @push.rocks scope
|
|
7
12
|
import * as smartai from '@push.rocks/smartai';
|
|
8
13
|
import * as smartdeno from '@push.rocks/smartdeno';
|
|
@@ -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
|
*/
|
|
@@ -102,9 +158,10 @@ export class DualAgentOrchestrator {
|
|
|
102
158
|
/**
|
|
103
159
|
* Register a scoped filesystem tool that can only access files within the specified directory
|
|
104
160
|
* @param basePath The directory to scope filesystem operations to
|
|
161
|
+
* @param excludePatterns Optional glob patterns to exclude from listings (e.g., ['.nogit/**', 'node_modules/**'])
|
|
105
162
|
*/
|
|
106
|
-
public registerScopedFilesystemTool(basePath: string): void {
|
|
107
|
-
const scopedTool = new FilesystemTool({ basePath });
|
|
163
|
+
public registerScopedFilesystemTool(basePath: string, excludePatterns?: string[]): void {
|
|
164
|
+
const scopedTool = new FilesystemTool({ basePath, excludePatterns });
|
|
108
165
|
this.registerTool(scopedTool);
|
|
109
166
|
}
|
|
110
167
|
|
|
@@ -124,7 +181,10 @@ export class DualAgentOrchestrator {
|
|
|
124
181
|
: this.driverProvider;
|
|
125
182
|
|
|
126
183
|
// NOW create agents with initialized providers
|
|
127
|
-
this.driver = new DriverAgent(this.driverProvider,
|
|
184
|
+
this.driver = new DriverAgent(this.driverProvider, {
|
|
185
|
+
systemMessage: this.options.driverSystemMessage,
|
|
186
|
+
maxHistoryMessages: this.options.maxHistoryMessages,
|
|
187
|
+
});
|
|
128
188
|
this.guardian = new GuardianAgent(this.guardianProvider, this.options.guardianPolicyPrompt);
|
|
129
189
|
|
|
130
190
|
// Register any tools that were added before start() with the agents
|
|
@@ -190,6 +250,12 @@ export class DualAgentOrchestrator {
|
|
|
190
250
|
let driverResponse = await this.driver.startTask(task);
|
|
191
251
|
this.conversationHistory.push(driverResponse);
|
|
192
252
|
|
|
253
|
+
// Emit task started event
|
|
254
|
+
this.emitProgress({
|
|
255
|
+
type: 'task_started',
|
|
256
|
+
message: task.length > 100 ? task.substring(0, 100) + '...' : task,
|
|
257
|
+
});
|
|
258
|
+
|
|
193
259
|
while (
|
|
194
260
|
iterations < this.options.maxIterations! &&
|
|
195
261
|
consecutiveRejections < this.options.maxConsecutiveRejections! &&
|
|
@@ -197,15 +263,36 @@ export class DualAgentOrchestrator {
|
|
|
197
263
|
) {
|
|
198
264
|
iterations++;
|
|
199
265
|
|
|
266
|
+
// Emit iteration started event
|
|
267
|
+
this.emitProgress({
|
|
268
|
+
type: 'iteration_started',
|
|
269
|
+
iteration: iterations,
|
|
270
|
+
maxIterations: this.options.maxIterations,
|
|
271
|
+
});
|
|
272
|
+
|
|
200
273
|
// Check if task is complete
|
|
201
274
|
if (this.driver.isTaskComplete(driverResponse.content)) {
|
|
202
275
|
completed = true;
|
|
203
276
|
finalResult = this.driver.extractTaskResult(driverResponse.content) || driverResponse.content;
|
|
277
|
+
|
|
278
|
+
// Emit task completed event
|
|
279
|
+
this.emitProgress({
|
|
280
|
+
type: 'task_completed',
|
|
281
|
+
iteration: iterations,
|
|
282
|
+
message: 'Task completed successfully',
|
|
283
|
+
});
|
|
204
284
|
break;
|
|
205
285
|
}
|
|
206
286
|
|
|
207
287
|
// Check if driver needs clarification
|
|
208
288
|
if (this.driver.needsClarification(driverResponse.content)) {
|
|
289
|
+
// Emit clarification needed event
|
|
290
|
+
this.emitProgress({
|
|
291
|
+
type: 'clarification_needed',
|
|
292
|
+
iteration: iterations,
|
|
293
|
+
message: 'Driver needs clarification from user',
|
|
294
|
+
});
|
|
295
|
+
|
|
209
296
|
// Return with clarification needed status
|
|
210
297
|
return {
|
|
211
298
|
success: false,
|
|
@@ -232,6 +319,15 @@ export class DualAgentOrchestrator {
|
|
|
232
319
|
// Process the first proposal (one at a time)
|
|
233
320
|
const proposal = proposals[0];
|
|
234
321
|
|
|
322
|
+
// Emit tool proposed event
|
|
323
|
+
this.emitProgress({
|
|
324
|
+
type: 'tool_proposed',
|
|
325
|
+
iteration: iterations,
|
|
326
|
+
toolName: proposal.toolName,
|
|
327
|
+
action: proposal.action,
|
|
328
|
+
message: `${proposal.toolName}.${proposal.action}`,
|
|
329
|
+
});
|
|
330
|
+
|
|
235
331
|
// Quick validation first
|
|
236
332
|
const quickDecision = this.guardian.quickValidate(proposal);
|
|
237
333
|
let decision: interfaces.IGuardianDecision;
|
|
@@ -239,6 +335,14 @@ export class DualAgentOrchestrator {
|
|
|
239
335
|
if (quickDecision) {
|
|
240
336
|
decision = quickDecision;
|
|
241
337
|
} else {
|
|
338
|
+
// Emit guardian evaluating event
|
|
339
|
+
this.emitProgress({
|
|
340
|
+
type: 'guardian_evaluating',
|
|
341
|
+
iteration: iterations,
|
|
342
|
+
toolName: proposal.toolName,
|
|
343
|
+
action: proposal.action,
|
|
344
|
+
});
|
|
345
|
+
|
|
242
346
|
// Full AI evaluation
|
|
243
347
|
decision = await this.guardian.evaluate(proposal, task);
|
|
244
348
|
}
|
|
@@ -246,6 +350,14 @@ export class DualAgentOrchestrator {
|
|
|
246
350
|
if (decision.decision === 'approve') {
|
|
247
351
|
consecutiveRejections = 0;
|
|
248
352
|
|
|
353
|
+
// Emit tool approved event
|
|
354
|
+
this.emitProgress({
|
|
355
|
+
type: 'tool_approved',
|
|
356
|
+
iteration: iterations,
|
|
357
|
+
toolName: proposal.toolName,
|
|
358
|
+
action: proposal.action,
|
|
359
|
+
});
|
|
360
|
+
|
|
249
361
|
// Execute the tool
|
|
250
362
|
const tool = this.tools.get(proposal.toolName);
|
|
251
363
|
if (!tool) {
|
|
@@ -258,12 +370,48 @@ export class DualAgentOrchestrator {
|
|
|
258
370
|
}
|
|
259
371
|
|
|
260
372
|
try {
|
|
373
|
+
// Emit tool executing event
|
|
374
|
+
this.emitProgress({
|
|
375
|
+
type: 'tool_executing',
|
|
376
|
+
iteration: iterations,
|
|
377
|
+
toolName: proposal.toolName,
|
|
378
|
+
action: proposal.action,
|
|
379
|
+
});
|
|
380
|
+
|
|
261
381
|
const result = await tool.execute(proposal.action, proposal.params);
|
|
262
382
|
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
:
|
|
383
|
+
// Emit tool completed event
|
|
384
|
+
this.emitProgress({
|
|
385
|
+
type: 'tool_completed',
|
|
386
|
+
iteration: iterations,
|
|
387
|
+
toolName: proposal.toolName,
|
|
388
|
+
action: proposal.action,
|
|
389
|
+
message: result.success ? 'success' : result.error,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Build result message (prefer summary if provided, otherwise stringify result)
|
|
393
|
+
let resultMessage: string;
|
|
394
|
+
if (result.success) {
|
|
395
|
+
if (result.summary) {
|
|
396
|
+
// Use tool-provided summary
|
|
397
|
+
resultMessage = `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${result.summary}`;
|
|
398
|
+
} else {
|
|
399
|
+
// Stringify and potentially truncate
|
|
400
|
+
const resultStr = JSON.stringify(result.result, null, 2);
|
|
401
|
+
const maxChars = this.options.maxResultChars ?? 15000;
|
|
402
|
+
|
|
403
|
+
if (maxChars > 0 && resultStr.length > maxChars) {
|
|
404
|
+
// Truncate the result
|
|
405
|
+
const truncated = resultStr.substring(0, maxChars);
|
|
406
|
+
const omittedTokens = Math.round((resultStr.length - maxChars) / 4);
|
|
407
|
+
resultMessage = `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${truncated}\n\n[... output truncated, ~${omittedTokens} tokens omitted. Use more specific parameters to reduce output size.]`;
|
|
408
|
+
} else {
|
|
409
|
+
resultMessage = `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${resultStr}`;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
resultMessage = `TOOL ERROR (${proposal.toolName}.${proposal.action}):\n${result.error}`;
|
|
414
|
+
}
|
|
267
415
|
|
|
268
416
|
this.conversationHistory.push({
|
|
269
417
|
role: 'system',
|
|
@@ -285,6 +433,15 @@ export class DualAgentOrchestrator {
|
|
|
285
433
|
// Rejected
|
|
286
434
|
consecutiveRejections++;
|
|
287
435
|
|
|
436
|
+
// Emit tool rejected event
|
|
437
|
+
this.emitProgress({
|
|
438
|
+
type: 'tool_rejected',
|
|
439
|
+
iteration: iterations,
|
|
440
|
+
toolName: proposal.toolName,
|
|
441
|
+
action: proposal.action,
|
|
442
|
+
reason: decision.reason,
|
|
443
|
+
});
|
|
444
|
+
|
|
288
445
|
// Build rejection feedback
|
|
289
446
|
let feedback = `TOOL CALL REJECTED by Guardian:\n`;
|
|
290
447
|
feedback += `- Reason: ${decision.reason}\n`;
|
|
@@ -316,8 +473,21 @@ export class DualAgentOrchestrator {
|
|
|
316
473
|
if (!completed) {
|
|
317
474
|
if (iterations >= this.options.maxIterations!) {
|
|
318
475
|
status = 'max_iterations_reached';
|
|
476
|
+
// Emit max iterations event
|
|
477
|
+
this.emitProgress({
|
|
478
|
+
type: 'max_iterations',
|
|
479
|
+
iteration: iterations,
|
|
480
|
+
maxIterations: this.options.maxIterations,
|
|
481
|
+
message: `Maximum iterations (${this.options.maxIterations}) reached`,
|
|
482
|
+
});
|
|
319
483
|
} else if (consecutiveRejections >= this.options.maxConsecutiveRejections!) {
|
|
320
484
|
status = 'max_rejections_reached';
|
|
485
|
+
// Emit max rejections event
|
|
486
|
+
this.emitProgress({
|
|
487
|
+
type: 'max_rejections',
|
|
488
|
+
iteration: iterations,
|
|
489
|
+
message: `Maximum consecutive rejections (${this.options.maxConsecutiveRejections}) reached`,
|
|
490
|
+
});
|
|
321
491
|
}
|
|
322
492
|
}
|
|
323
493
|
|