Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8827a55768 | |||
| 37b6e98a81 | |||
| 35911c21de | |||
| 7403e769d0 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartagent",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.2",
|
||||
"private": false,
|
||||
"description": "an agentic framework built on top of @push.rocks/smartai",
|
||||
"main": "dist_ts/index.js",
|
||||
|
||||
@@ -11,7 +11,7 @@ export { GuardianAgent } from './smartagent.classes.guardianagent.js';
|
||||
export { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
// Export standard tools
|
||||
export { FilesystemTool } from './smartagent.tools.filesystem.js';
|
||||
export { FilesystemTool, type IFilesystemToolOptions } from './smartagent.tools.filesystem.js';
|
||||
export { HttpTool } from './smartagent.tools.http.js';
|
||||
export { ShellTool } from './smartagent.tools.shell.js';
|
||||
export { BrowserTool } from './smartagent.tools.browser.js';
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
|
||||
export { path };
|
||||
|
||||
// @push.rocks scope
|
||||
import * as smartai from '@push.rocks/smartai';
|
||||
import * as smartdeno from '@push.rocks/smartdeno';
|
||||
|
||||
@@ -41,8 +41,14 @@ export class DriverAgent {
|
||||
// Reset message history
|
||||
this.messageHistory = [];
|
||||
|
||||
// Build the user message
|
||||
const userMessage = `TASK: ${task}\n\nAnalyze this task and determine what actions are needed. If you need to use a tool, provide a tool call proposal.`;
|
||||
// Build the user message based on available tools
|
||||
const hasTools = this.tools.size > 0;
|
||||
let userMessage: string;
|
||||
if (hasTools) {
|
||||
userMessage = `TASK: ${task}\n\nAnalyze this task and determine what actions are needed. If you need to use a tool, provide a tool call proposal.`;
|
||||
} else {
|
||||
userMessage = `TASK: ${task}\n\nComplete this task directly. When done, wrap your final output in <task_complete>your output here</task_complete> tags.`;
|
||||
}
|
||||
|
||||
// Add to history
|
||||
this.messageHistory.push({
|
||||
@@ -50,9 +56,15 @@ export class DriverAgent {
|
||||
content: userMessage,
|
||||
});
|
||||
|
||||
// Build tool descriptions for the system message
|
||||
const toolDescriptions = this.buildToolDescriptions();
|
||||
const fullSystemMessage = `${this.systemMessage}\n\n## Available Tools\n${toolDescriptions}`;
|
||||
// Build the system message - adapt based on available tools
|
||||
let fullSystemMessage: string;
|
||||
if (hasTools) {
|
||||
const toolDescriptions = this.buildToolDescriptions();
|
||||
fullSystemMessage = `${this.systemMessage}\n\n## Available Tools\n${toolDescriptions}`;
|
||||
} else {
|
||||
// Use a simpler system message when no tools are available
|
||||
fullSystemMessage = this.getNoToolsSystemMessage();
|
||||
}
|
||||
|
||||
// Get response from provider
|
||||
const response = await this.provider.chat({
|
||||
@@ -83,9 +95,15 @@ export class DriverAgent {
|
||||
content: message,
|
||||
});
|
||||
|
||||
// Build tool descriptions for the system message
|
||||
const toolDescriptions = this.buildToolDescriptions();
|
||||
const fullSystemMessage = `${this.systemMessage}\n\n## Available Tools\n${toolDescriptions}`;
|
||||
// Build the system message - adapt based on available tools
|
||||
const hasTools = this.tools.size > 0;
|
||||
let fullSystemMessage: string;
|
||||
if (hasTools) {
|
||||
const toolDescriptions = this.buildToolDescriptions();
|
||||
fullSystemMessage = `${this.systemMessage}\n\n## Available Tools\n${toolDescriptions}`;
|
||||
} else {
|
||||
fullSystemMessage = this.getNoToolsSystemMessage();
|
||||
}
|
||||
|
||||
// Get response from provider (pass all but last user message as history)
|
||||
const historyForChat = this.messageHistory.slice(0, -1);
|
||||
@@ -312,6 +330,35 @@ When you need to use a tool, output a tool call proposal in this format:
|
||||
- If you need clarification, ask using <needs_clarification>your question</needs_clarification>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the system message when no tools are available
|
||||
* Used for direct task completion without tool usage
|
||||
*/
|
||||
private getNoToolsSystemMessage(): string {
|
||||
// Use custom system message if provided, otherwise use a simple default
|
||||
if (this.systemMessage && this.systemMessage !== this.getDefaultSystemMessage()) {
|
||||
return this.systemMessage;
|
||||
}
|
||||
|
||||
return `You are an AI assistant that completes tasks directly.
|
||||
|
||||
## Your Role
|
||||
You analyze tasks and provide complete, high-quality outputs.
|
||||
|
||||
## Output Format
|
||||
When you have completed the task, wrap your final output in task_complete tags:
|
||||
|
||||
<task_complete>
|
||||
Your complete output here
|
||||
</task_complete>
|
||||
|
||||
## Guidelines
|
||||
1. Analyze the task requirements carefully
|
||||
2. Provide a complete and accurate response
|
||||
3. Always wrap your final output in <task_complete></task_complete> tags
|
||||
4. If you need clarification, ask using <needs_clarification>your question</needs_clarification>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the conversation state
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ export class DualAgentOrchestrator {
|
||||
private tools: Map<string, BaseToolWrapper> = new Map();
|
||||
private isRunning = false;
|
||||
private conversationHistory: interfaces.IAgentMessage[] = [];
|
||||
private ownsSmartAi = true; // true if we created the SmartAi instance, false if it was provided
|
||||
|
||||
constructor(options: interfaces.IDualAgentOptions) {
|
||||
this.options = {
|
||||
@@ -32,18 +33,15 @@ export class DualAgentOrchestrator {
|
||||
...options,
|
||||
};
|
||||
|
||||
// Create SmartAi instance
|
||||
this.smartai = new plugins.smartai.SmartAi(options);
|
||||
|
||||
// Get providers
|
||||
this.driverProvider = this.getProviderByName(this.options.defaultProvider!);
|
||||
this.guardianProvider = this.options.guardianProvider
|
||||
? this.getProviderByName(this.options.guardianProvider)
|
||||
: this.driverProvider;
|
||||
|
||||
// Create agents
|
||||
this.driver = new DriverAgent(this.driverProvider, options.driverSystemMessage);
|
||||
this.guardian = new GuardianAgent(this.guardianProvider, options.guardianPolicyPrompt);
|
||||
// Use existing SmartAi instance if provided, otherwise create a new one
|
||||
if (options.smartAiInstance) {
|
||||
this.smartai = options.smartAiInstance;
|
||||
this.ownsSmartAi = false; // Don't manage lifecycle of provided instance
|
||||
} else {
|
||||
this.smartai = new plugins.smartai.SmartAi(options);
|
||||
this.ownsSmartAi = true;
|
||||
}
|
||||
// Note: Don't access providers here - they don't exist until start() is called
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,8 +73,13 @@ export class DualAgentOrchestrator {
|
||||
*/
|
||||
public registerTool(tool: BaseToolWrapper): void {
|
||||
this.tools.set(tool.name, tool);
|
||||
this.driver.registerTool(tool);
|
||||
this.guardian.registerTool(tool);
|
||||
// Register with agents if they exist (they're created in start())
|
||||
if (this.driver) {
|
||||
this.driver.registerTool(tool);
|
||||
}
|
||||
if (this.guardian) {
|
||||
this.guardian.registerTool(tool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,12 +99,39 @@ export class DualAgentOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a scoped filesystem tool that can only access files within the specified directory
|
||||
* @param basePath The directory to scope filesystem operations to
|
||||
*/
|
||||
public registerScopedFilesystemTool(basePath: string): void {
|
||||
const scopedTool = new FilesystemTool({ basePath });
|
||||
this.registerTool(scopedTool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all tools (eager loading)
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
// Start smartai
|
||||
await this.smartai.start();
|
||||
// Start smartai only if we created it (external instances should already be started)
|
||||
if (this.ownsSmartAi) {
|
||||
await this.smartai.start();
|
||||
}
|
||||
|
||||
// NOW get providers (after they've been initialized by smartai.start())
|
||||
this.driverProvider = this.getProviderByName(this.options.defaultProvider!);
|
||||
this.guardianProvider = this.options.guardianProvider
|
||||
? this.getProviderByName(this.options.guardianProvider)
|
||||
: this.driverProvider;
|
||||
|
||||
// NOW create agents with initialized providers
|
||||
this.driver = new DriverAgent(this.driverProvider, this.options.driverSystemMessage);
|
||||
this.guardian = new GuardianAgent(this.guardianProvider, this.options.guardianPolicyPrompt);
|
||||
|
||||
// Register any tools that were added before start() with the agents
|
||||
for (const tool of this.tools.values()) {
|
||||
this.driver.registerTool(tool);
|
||||
this.guardian.registerTool(tool);
|
||||
}
|
||||
|
||||
// Initialize all tools
|
||||
const initPromises: Promise<void>[] = [];
|
||||
@@ -124,9 +154,16 @@ export class DualAgentOrchestrator {
|
||||
}
|
||||
|
||||
await Promise.all(cleanupPromises);
|
||||
await this.smartai.stop();
|
||||
|
||||
// Only stop smartai if we created it (don't stop external instances)
|
||||
if (this.ownsSmartAi) {
|
||||
await this.smartai.stop();
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
this.driver.reset();
|
||||
if (this.driver) {
|
||||
this.driver.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as plugins from './plugins.js';
|
||||
* Configuration options for the DualAgentOrchestrator
|
||||
*/
|
||||
export interface IDualAgentOptions extends plugins.smartai.ISmartAiOptions {
|
||||
/** Existing SmartAi instance to reuse (avoids creating duplicate providers) */
|
||||
smartAiInstance?: plugins.smartai.SmartAi;
|
||||
/** Name of the agent system */
|
||||
name?: string;
|
||||
/** Default AI provider for both Driver and Guardian */
|
||||
|
||||
@@ -2,6 +2,14 @@ import * as plugins from './plugins.js';
|
||||
import * as interfaces from './smartagent.interfaces.js';
|
||||
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
/**
|
||||
* Options for FilesystemTool
|
||||
*/
|
||||
export interface IFilesystemToolOptions {
|
||||
/** Base path to scope all operations to. If set, all paths must be within this directory. */
|
||||
basePath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filesystem tool for file and directory operations
|
||||
* Wraps @push.rocks/smartfs
|
||||
@@ -10,6 +18,31 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
public name = 'filesystem';
|
||||
public description = 'Read, write, list, and delete files and directories';
|
||||
|
||||
/** Base path to scope all operations to */
|
||||
private basePath?: string;
|
||||
|
||||
constructor(options?: IFilesystemToolOptions) {
|
||||
super();
|
||||
if (options?.basePath) {
|
||||
this.basePath = plugins.path.resolve(options.basePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a path is within the allowed base path
|
||||
* @throws Error if path is outside allowed directory
|
||||
*/
|
||||
private validatePath(pathArg: string): string {
|
||||
const resolved = plugins.path.resolve(pathArg);
|
||||
if (this.basePath) {
|
||||
// Ensure the resolved path starts with the base path
|
||||
if (!resolved.startsWith(this.basePath + plugins.path.sep) && resolved !== this.basePath) {
|
||||
throw new Error(`Access denied: path "${pathArg}" is outside allowed directory "${this.basePath}"`);
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public actions: interfaces.IToolAction[] = [
|
||||
{
|
||||
name: 'read',
|
||||
@@ -172,9 +205,10 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
try {
|
||||
switch (action) {
|
||||
case 'read': {
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
const encoding = (params.encoding as string) || 'utf8';
|
||||
const content = await this.smartfs
|
||||
.file(params.path as string)
|
||||
.file(validatedPath)
|
||||
.encoding(encoding as 'utf8' | 'binary' | 'base64')
|
||||
.read();
|
||||
return {
|
||||
@@ -188,9 +222,10 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'write': {
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
const encoding = (params.encoding as string) || 'utf8';
|
||||
await this.smartfs
|
||||
.file(params.path as string)
|
||||
.file(validatedPath)
|
||||
.encoding(encoding as 'utf8' | 'binary' | 'base64')
|
||||
.write(params.content as string);
|
||||
return {
|
||||
@@ -204,7 +239,8 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'append': {
|
||||
await this.smartfs.file(params.path as string).append(params.content as string);
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
await this.smartfs.file(validatedPath).append(params.content as string);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -215,7 +251,8 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
let dir = this.smartfs.directory(params.path as string);
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
let dir = this.smartfs.directory(validatedPath);
|
||||
if (params.recursive) {
|
||||
dir = dir.recursive();
|
||||
}
|
||||
@@ -234,33 +271,34 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
const path = params.path as string;
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
// Check if it's a directory or file
|
||||
const exists = await this.smartfs.file(path).exists();
|
||||
const exists = await this.smartfs.file(validatedPath).exists();
|
||||
if (exists) {
|
||||
// Try to get stats to check if it's a directory
|
||||
try {
|
||||
const stats = await this.smartfs.file(path).stat();
|
||||
const stats = await this.smartfs.file(validatedPath).stat();
|
||||
if (stats.isDirectory && params.recursive) {
|
||||
await this.smartfs.directory(path).recursive().delete();
|
||||
await this.smartfs.directory(validatedPath).recursive().delete();
|
||||
} else {
|
||||
await this.smartfs.file(path).delete();
|
||||
await this.smartfs.file(validatedPath).delete();
|
||||
}
|
||||
} catch {
|
||||
await this.smartfs.file(path).delete();
|
||||
await this.smartfs.file(validatedPath).delete();
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
path,
|
||||
path: params.path,
|
||||
deleted: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'exists': {
|
||||
const exists = await this.smartfs.file(params.path as string).exists();
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
const exists = await this.smartfs.file(validatedPath).exists();
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -271,7 +309,8 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'stat': {
|
||||
const stats = await this.smartfs.file(params.path as string).stat();
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
const stats = await this.smartfs.file(validatedPath).stat();
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -282,7 +321,9 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'copy': {
|
||||
await this.smartfs.file(params.source as string).copy(params.destination as string);
|
||||
const validatedSource = this.validatePath(params.source as string);
|
||||
const validatedDest = this.validatePath(params.destination as string);
|
||||
await this.smartfs.file(validatedSource).copy(validatedDest);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -294,7 +335,9 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'move': {
|
||||
await this.smartfs.file(params.source as string).move(params.destination as string);
|
||||
const validatedSource = this.validatePath(params.source as string);
|
||||
const validatedDest = this.validatePath(params.destination as string);
|
||||
await this.smartfs.file(validatedSource).move(validatedDest);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -306,7 +349,8 @@ export class FilesystemTool extends BaseToolWrapper {
|
||||
}
|
||||
|
||||
case 'mkdir': {
|
||||
let dir = this.smartfs.directory(params.path as string);
|
||||
const validatedPath = this.validatePath(params.path as string);
|
||||
let dir = this.smartfs.directory(validatedPath);
|
||||
if (params.recursive !== false) {
|
||||
dir = dir.recursive();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user