@push.rocks/smartagent 1.0.2

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.
Files changed (42) hide show
  1. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  2. package/dist_ts/00_commitinfo_data.js +9 -0
  3. package/dist_ts/index.d.ts +10 -0
  4. package/dist_ts/index.js +18 -0
  5. package/dist_ts/plugins.d.ts +6 -0
  6. package/dist_ts/plugins.js +8 -0
  7. package/dist_ts/smartagent.classes.driveragent.d.ts +70 -0
  8. package/dist_ts/smartagent.classes.driveragent.js +274 -0
  9. package/dist_ts/smartagent.classes.dualagent.d.ts +62 -0
  10. package/dist_ts/smartagent.classes.dualagent.js +298 -0
  11. package/dist_ts/smartagent.classes.guardianagent.d.ts +46 -0
  12. package/dist_ts/smartagent.classes.guardianagent.js +201 -0
  13. package/dist_ts/smartagent.classes.smartagent.d.ts +123 -0
  14. package/dist_ts/smartagent.classes.smartagent.js +274 -0
  15. package/dist_ts/smartagent.interfaces.d.ts +165 -0
  16. package/dist_ts/smartagent.interfaces.js +8 -0
  17. package/dist_ts/smartagent.tools.base.d.ts +46 -0
  18. package/dist_ts/smartagent.tools.base.js +45 -0
  19. package/dist_ts/smartagent.tools.browser.d.ts +16 -0
  20. package/dist_ts/smartagent.tools.browser.js +177 -0
  21. package/dist_ts/smartagent.tools.filesystem.d.ts +16 -0
  22. package/dist_ts/smartagent.tools.filesystem.js +352 -0
  23. package/dist_ts/smartagent.tools.http.d.ts +15 -0
  24. package/dist_ts/smartagent.tools.http.js +187 -0
  25. package/dist_ts/smartagent.tools.shell.d.ts +16 -0
  26. package/dist_ts/smartagent.tools.shell.js +155 -0
  27. package/npmextra.json +18 -0
  28. package/package.json +50 -0
  29. package/readme.hints.md +16 -0
  30. package/readme.md +299 -0
  31. package/ts/00_commitinfo_data.ts +8 -0
  32. package/ts/index.ts +29 -0
  33. package/ts/plugins.ts +14 -0
  34. package/ts/smartagent.classes.driveragent.ts +321 -0
  35. package/ts/smartagent.classes.dualagent.ts +350 -0
  36. package/ts/smartagent.classes.guardianagent.ts +241 -0
  37. package/ts/smartagent.interfaces.ts +210 -0
  38. package/ts/smartagent.tools.base.ts +80 -0
  39. package/ts/smartagent.tools.browser.ts +200 -0
  40. package/ts/smartagent.tools.filesystem.ts +379 -0
  41. package/ts/smartagent.tools.http.ts +205 -0
  42. package/ts/smartagent.tools.shell.ts +182 -0
@@ -0,0 +1,350 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as interfaces from './smartagent.interfaces.js';
3
+ import { BaseToolWrapper } from './smartagent.tools.base.js';
4
+ import { DriverAgent } from './smartagent.classes.driveragent.js';
5
+ import { GuardianAgent } from './smartagent.classes.guardianagent.js';
6
+ import { FilesystemTool } from './smartagent.tools.filesystem.js';
7
+ import { HttpTool } from './smartagent.tools.http.js';
8
+ import { ShellTool } from './smartagent.tools.shell.js';
9
+ import { BrowserTool } from './smartagent.tools.browser.js';
10
+
11
+ /**
12
+ * DualAgentOrchestrator - Coordinates Driver and Guardian agents
13
+ * Manages the complete lifecycle of task execution with tool approval
14
+ */
15
+ export class DualAgentOrchestrator {
16
+ private options: interfaces.IDualAgentOptions;
17
+ private smartai: plugins.smartai.SmartAi;
18
+ private driverProvider: plugins.smartai.MultiModalModel;
19
+ private guardianProvider: plugins.smartai.MultiModalModel;
20
+ private driver: DriverAgent;
21
+ private guardian: GuardianAgent;
22
+ private tools: Map<string, BaseToolWrapper> = new Map();
23
+ private isRunning = false;
24
+ private conversationHistory: interfaces.IAgentMessage[] = [];
25
+
26
+ constructor(options: interfaces.IDualAgentOptions) {
27
+ this.options = {
28
+ maxIterations: 20,
29
+ maxConsecutiveRejections: 3,
30
+ defaultProvider: 'openai',
31
+ ...options,
32
+ };
33
+
34
+ // Create SmartAi instance
35
+ this.smartai = new plugins.smartai.SmartAi(options);
36
+
37
+ // Get providers
38
+ this.driverProvider = this.getProviderByName(this.options.defaultProvider!);
39
+ this.guardianProvider = this.options.guardianProvider
40
+ ? this.getProviderByName(this.options.guardianProvider)
41
+ : this.driverProvider;
42
+
43
+ // Create agents
44
+ this.driver = new DriverAgent(this.driverProvider, options.driverSystemMessage);
45
+ this.guardian = new GuardianAgent(this.guardianProvider, options.guardianPolicyPrompt);
46
+ }
47
+
48
+ /**
49
+ * Get provider by name
50
+ */
51
+ private getProviderByName(providerName: plugins.smartai.TProvider): plugins.smartai.MultiModalModel {
52
+ switch (providerName) {
53
+ case 'openai':
54
+ return this.smartai.openaiProvider;
55
+ case 'anthropic':
56
+ return this.smartai.anthropicProvider;
57
+ case 'perplexity':
58
+ return this.smartai.perplexityProvider;
59
+ case 'ollama':
60
+ return this.smartai.ollamaProvider;
61
+ case 'groq':
62
+ return this.smartai.groqProvider;
63
+ case 'xai':
64
+ return this.smartai.xaiProvider;
65
+ case 'exo':
66
+ return this.smartai.exoProvider;
67
+ default:
68
+ return this.smartai.openaiProvider;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Register a custom tool
74
+ */
75
+ public registerTool(tool: BaseToolWrapper): void {
76
+ this.tools.set(tool.name, tool);
77
+ this.driver.registerTool(tool);
78
+ this.guardian.registerTool(tool);
79
+ }
80
+
81
+ /**
82
+ * Register all standard tools
83
+ */
84
+ public registerStandardTools(): void {
85
+ const standardTools = [
86
+ new FilesystemTool(),
87
+ new HttpTool(),
88
+ new ShellTool(),
89
+ new BrowserTool(),
90
+ ];
91
+
92
+ for (const tool of standardTools) {
93
+ this.registerTool(tool);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Initialize all tools (eager loading)
99
+ */
100
+ public async start(): Promise<void> {
101
+ // Start smartai
102
+ await this.smartai.start();
103
+
104
+ // Initialize all tools
105
+ const initPromises: Promise<void>[] = [];
106
+ for (const tool of this.tools.values()) {
107
+ initPromises.push(tool.initialize());
108
+ }
109
+
110
+ await Promise.all(initPromises);
111
+ this.isRunning = true;
112
+ }
113
+
114
+ /**
115
+ * Cleanup all tools
116
+ */
117
+ public async stop(): Promise<void> {
118
+ const cleanupPromises: Promise<void>[] = [];
119
+
120
+ for (const tool of this.tools.values()) {
121
+ cleanupPromises.push(tool.cleanup());
122
+ }
123
+
124
+ await Promise.all(cleanupPromises);
125
+ await this.smartai.stop();
126
+ this.isRunning = false;
127
+ this.driver.reset();
128
+ }
129
+
130
+ /**
131
+ * Run a task through the dual-agent system
132
+ */
133
+ public async run(task: string): Promise<interfaces.IDualAgentRunResult> {
134
+ if (!this.isRunning) {
135
+ throw new Error('Orchestrator not started. Call start() first.');
136
+ }
137
+
138
+ this.conversationHistory = [];
139
+ let iterations = 0;
140
+ let consecutiveRejections = 0;
141
+ let completed = false;
142
+ let finalResult: string | null = null;
143
+
144
+ // Add initial task to history
145
+ this.conversationHistory.push({
146
+ role: 'user',
147
+ content: task,
148
+ });
149
+
150
+ // Start the driver with the task
151
+ let driverResponse = await this.driver.startTask(task);
152
+ this.conversationHistory.push(driverResponse);
153
+
154
+ while (
155
+ iterations < this.options.maxIterations! &&
156
+ consecutiveRejections < this.options.maxConsecutiveRejections! &&
157
+ !completed
158
+ ) {
159
+ iterations++;
160
+
161
+ // Check if task is complete
162
+ if (this.driver.isTaskComplete(driverResponse.content)) {
163
+ completed = true;
164
+ finalResult = this.driver.extractTaskResult(driverResponse.content) || driverResponse.content;
165
+ break;
166
+ }
167
+
168
+ // Check if driver needs clarification
169
+ if (this.driver.needsClarification(driverResponse.content)) {
170
+ // Return with clarification needed status
171
+ return {
172
+ success: false,
173
+ completed: false,
174
+ result: driverResponse.content,
175
+ iterations,
176
+ history: this.conversationHistory,
177
+ status: 'clarification_needed',
178
+ };
179
+ }
180
+
181
+ // Parse tool call proposals
182
+ const proposals = this.driver.parseToolCallProposals(driverResponse.content);
183
+
184
+ if (proposals.length === 0) {
185
+ // No tool calls, continue the conversation
186
+ driverResponse = await this.driver.continueWithMessage(
187
+ 'Please either use a tool to make progress on the task, or indicate that the task is complete with <task_complete>summary</task_complete>.'
188
+ );
189
+ this.conversationHistory.push(driverResponse);
190
+ continue;
191
+ }
192
+
193
+ // Process the first proposal (one at a time)
194
+ const proposal = proposals[0];
195
+
196
+ // Quick validation first
197
+ const quickDecision = this.guardian.quickValidate(proposal);
198
+ let decision: interfaces.IGuardianDecision;
199
+
200
+ if (quickDecision) {
201
+ decision = quickDecision;
202
+ } else {
203
+ // Full AI evaluation
204
+ decision = await this.guardian.evaluate(proposal, task);
205
+ }
206
+
207
+ if (decision.decision === 'approve') {
208
+ consecutiveRejections = 0;
209
+
210
+ // Execute the tool
211
+ const tool = this.tools.get(proposal.toolName);
212
+ if (!tool) {
213
+ const errorMessage = `Tool "${proposal.toolName}" not found.`;
214
+ driverResponse = await this.driver.continueWithMessage(
215
+ `TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`
216
+ );
217
+ this.conversationHistory.push(driverResponse);
218
+ continue;
219
+ }
220
+
221
+ try {
222
+ const result = await tool.execute(proposal.action, proposal.params);
223
+
224
+ // Send result to driver
225
+ const resultMessage = result.success
226
+ ? `TOOL RESULT (${proposal.toolName}.${proposal.action}):\n${JSON.stringify(result.result, null, 2)}`
227
+ : `TOOL ERROR (${proposal.toolName}.${proposal.action}):\n${result.error}`;
228
+
229
+ this.conversationHistory.push({
230
+ role: 'system',
231
+ content: resultMessage,
232
+ toolCall: proposal,
233
+ toolResult: result,
234
+ });
235
+
236
+ driverResponse = await this.driver.continueWithMessage(resultMessage);
237
+ this.conversationHistory.push(driverResponse);
238
+ } catch (error) {
239
+ const errorMessage = `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`;
240
+ driverResponse = await this.driver.continueWithMessage(
241
+ `TOOL ERROR: ${errorMessage}\n\nPlease try a different approach.`
242
+ );
243
+ this.conversationHistory.push(driverResponse);
244
+ }
245
+ } else {
246
+ // Rejected
247
+ consecutiveRejections++;
248
+
249
+ // Build rejection feedback
250
+ let feedback = `TOOL CALL REJECTED by Guardian:\n`;
251
+ feedback += `- Reason: ${decision.reason}\n`;
252
+
253
+ if (decision.concerns && decision.concerns.length > 0) {
254
+ feedback += `- Concerns:\n${decision.concerns.map(c => ` - ${c}`).join('\n')}\n`;
255
+ }
256
+
257
+ if (decision.suggestions) {
258
+ feedback += `- Suggestions: ${decision.suggestions}\n`;
259
+ }
260
+
261
+ feedback += `\nPlease adapt your approach based on this feedback.`;
262
+
263
+ this.conversationHistory.push({
264
+ role: 'system',
265
+ content: feedback,
266
+ toolCall: proposal,
267
+ guardianDecision: decision,
268
+ });
269
+
270
+ driverResponse = await this.driver.continueWithMessage(feedback);
271
+ this.conversationHistory.push(driverResponse);
272
+ }
273
+ }
274
+
275
+ // Determine final status
276
+ let status: interfaces.TDualAgentRunStatus = 'completed';
277
+ if (!completed) {
278
+ if (iterations >= this.options.maxIterations!) {
279
+ status = 'max_iterations_reached';
280
+ } else if (consecutiveRejections >= this.options.maxConsecutiveRejections!) {
281
+ status = 'max_rejections_reached';
282
+ }
283
+ }
284
+
285
+ return {
286
+ success: completed,
287
+ completed,
288
+ result: finalResult || driverResponse.content,
289
+ iterations,
290
+ history: this.conversationHistory,
291
+ status,
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Continue an existing task with user input
297
+ */
298
+ public async continueTask(userInput: string): Promise<interfaces.IDualAgentRunResult> {
299
+ if (!this.isRunning) {
300
+ throw new Error('Orchestrator not started. Call start() first.');
301
+ }
302
+
303
+ this.conversationHistory.push({
304
+ role: 'user',
305
+ content: userInput,
306
+ });
307
+
308
+ const driverResponse = await this.driver.continueWithMessage(userInput);
309
+ this.conversationHistory.push(driverResponse);
310
+
311
+ // Continue the run loop
312
+ // For simplicity, we return the current state - full continuation would need refactoring
313
+ return {
314
+ success: false,
315
+ completed: false,
316
+ result: driverResponse.content,
317
+ iterations: 1,
318
+ history: this.conversationHistory,
319
+ status: 'in_progress',
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Get the conversation history
325
+ */
326
+ public getHistory(): interfaces.IAgentMessage[] {
327
+ return [...this.conversationHistory];
328
+ }
329
+
330
+ /**
331
+ * Update the guardian policy
332
+ */
333
+ public setGuardianPolicy(policyPrompt: string): void {
334
+ this.guardian.setPolicy(policyPrompt);
335
+ }
336
+
337
+ /**
338
+ * Check if orchestrator is running
339
+ */
340
+ public isActive(): boolean {
341
+ return this.isRunning;
342
+ }
343
+
344
+ /**
345
+ * Get registered tool names
346
+ */
347
+ public getToolNames(): string[] {
348
+ return Array.from(this.tools.keys());
349
+ }
350
+ }
@@ -0,0 +1,241 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as interfaces from './smartagent.interfaces.js';
3
+ import type { BaseToolWrapper } from './smartagent.tools.base.js';
4
+
5
+ /**
6
+ * GuardianAgent - Evaluates tool call proposals against a policy
7
+ * Uses AI reasoning to approve or reject tool calls
8
+ */
9
+ export class GuardianAgent {
10
+ private provider: plugins.smartai.MultiModalModel;
11
+ private policyPrompt: string;
12
+ private tools: Map<string, BaseToolWrapper> = new Map();
13
+
14
+ constructor(
15
+ provider: plugins.smartai.MultiModalModel,
16
+ policyPrompt: string
17
+ ) {
18
+ this.provider = provider;
19
+ this.policyPrompt = policyPrompt;
20
+ }
21
+
22
+ /**
23
+ * Register a tool for reference during evaluation
24
+ */
25
+ public registerTool(tool: BaseToolWrapper): void {
26
+ this.tools.set(tool.name, tool);
27
+ }
28
+
29
+ /**
30
+ * Evaluate a tool call proposal against the policy
31
+ */
32
+ public async evaluate(
33
+ proposal: interfaces.IToolCallProposal,
34
+ taskContext: string
35
+ ): Promise<interfaces.IGuardianDecision> {
36
+ // Get the tool to generate a human-readable summary
37
+ const tool = this.tools.get(proposal.toolName);
38
+ let callSummary = `${proposal.toolName}.${proposal.action}(${JSON.stringify(proposal.params)})`;
39
+
40
+ if (tool) {
41
+ try {
42
+ callSummary = tool.getCallSummary(proposal.action, proposal.params);
43
+ } catch {
44
+ // Fallback to basic summary
45
+ }
46
+ }
47
+
48
+ // Build the evaluation prompt
49
+ const evaluationPrompt = this.buildEvaluationPrompt(
50
+ proposal,
51
+ callSummary,
52
+ taskContext
53
+ );
54
+
55
+ // Get response from provider
56
+ const response = await this.provider.chat({
57
+ systemMessage: this.buildGuardianSystemMessage(),
58
+ userMessage: evaluationPrompt,
59
+ messageHistory: [],
60
+ });
61
+
62
+ // Parse the decision from the response
63
+ return this.parseDecision(response.message, proposal);
64
+ }
65
+
66
+ /**
67
+ * Build the system message for the Guardian
68
+ */
69
+ private buildGuardianSystemMessage(): string {
70
+ return `You are a Guardian AI responsible for evaluating tool call proposals.
71
+
72
+ ## Your Role
73
+ You evaluate whether proposed tool calls are safe and aligned with the policy.
74
+
75
+ ## Policy to Enforce
76
+ ${this.policyPrompt}
77
+
78
+ ## Response Format
79
+ For EVERY evaluation, respond with a decision in this exact format:
80
+
81
+ <guardian_decision>
82
+ <decision>approve OR reject</decision>
83
+ <reason>Your detailed explanation</reason>
84
+ <concerns>List any concerns, even if approving</concerns>
85
+ <suggestions>Alternative approaches if rejecting</suggestions>
86
+ </guardian_decision>
87
+
88
+ ## Guidelines
89
+ 1. Carefully analyze what the tool call will do
90
+ 2. Consider security implications
91
+ 3. Check against the policy requirements
92
+ 4. If uncertain, err on the side of caution (reject)
93
+ 5. Provide actionable feedback when rejecting`;
94
+ }
95
+
96
+ /**
97
+ * Build the evaluation prompt for a specific proposal
98
+ */
99
+ private buildEvaluationPrompt(
100
+ proposal: interfaces.IToolCallProposal,
101
+ callSummary: string,
102
+ taskContext: string
103
+ ): string {
104
+ const toolInfo = this.tools.get(proposal.toolName);
105
+ const toolDescription = toolInfo ? toolInfo.getFullDescription() : 'Unknown tool';
106
+
107
+ return `## Task Context
108
+ ${taskContext}
109
+
110
+ ## Tool Being Used
111
+ ${toolDescription}
112
+
113
+ ## Proposed Tool Call
114
+ - **Tool**: ${proposal.toolName}
115
+ - **Action**: ${proposal.action}
116
+ - **Parameters**: ${JSON.stringify(proposal.params, null, 2)}
117
+
118
+ ## Human-Readable Summary
119
+ ${callSummary}
120
+
121
+ ## Driver's Reasoning
122
+ ${proposal.reasoning || 'No reasoning provided'}
123
+
124
+ ---
125
+
126
+ Evaluate this tool call against the policy. Should it be approved or rejected?`;
127
+ }
128
+
129
+ /**
130
+ * Parse the guardian decision from the response
131
+ */
132
+ private parseDecision(
133
+ response: string,
134
+ proposal: interfaces.IToolCallProposal
135
+ ): interfaces.IGuardianDecision {
136
+ // Try to extract from XML tags
137
+ const decisionMatch = response.match(/<decision>(.*?)<\/decision>/s);
138
+ const reasonMatch = response.match(/<reason>([\s\S]*?)<\/reason>/);
139
+ const concernsMatch = response.match(/<concerns>([\s\S]*?)<\/concerns>/);
140
+ const suggestionsMatch = response.match(/<suggestions>([\s\S]*?)<\/suggestions>/);
141
+
142
+ // Determine decision
143
+ let decision: 'approve' | 'reject' = 'reject';
144
+ if (decisionMatch) {
145
+ const decisionText = decisionMatch[1].trim().toLowerCase();
146
+ decision = decisionText.includes('approve') ? 'approve' : 'reject';
147
+ } else {
148
+ // Fallback: look for approval keywords in the response
149
+ const lowerResponse = response.toLowerCase();
150
+ if (
151
+ lowerResponse.includes('approved') ||
152
+ lowerResponse.includes('i approve') ||
153
+ lowerResponse.includes('looks safe')
154
+ ) {
155
+ decision = 'approve';
156
+ }
157
+ }
158
+
159
+ // Extract reason
160
+ let reason = reasonMatch ? reasonMatch[1].trim() : '';
161
+ if (!reason) {
162
+ // Use the full response as reason if no tag found
163
+ reason = response.substring(0, 500);
164
+ }
165
+
166
+ // Extract concerns
167
+ const concerns: string[] = [];
168
+ if (concernsMatch) {
169
+ const concernsText = concernsMatch[1].trim();
170
+ if (concernsText && concernsText.toLowerCase() !== 'none') {
171
+ // Split by newlines or bullet points
172
+ const concernLines = concernsText.split(/[\n\r]+/).map(l => l.trim()).filter(l => l);
173
+ concerns.push(...concernLines);
174
+ }
175
+ }
176
+
177
+ // Extract suggestions
178
+ const suggestions = suggestionsMatch ? suggestionsMatch[1].trim() : undefined;
179
+
180
+ return {
181
+ decision,
182
+ reason,
183
+ concerns: concerns.length > 0 ? concerns : undefined,
184
+ suggestions: suggestions && suggestions.toLowerCase() !== 'none' ? suggestions : undefined,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Quick validation without AI (for obviously safe/unsafe operations)
190
+ * Returns null if AI evaluation is needed
191
+ */
192
+ public quickValidate(proposal: interfaces.IToolCallProposal): interfaces.IGuardianDecision | null {
193
+ // Check if tool exists
194
+ if (!this.tools.has(proposal.toolName)) {
195
+ return {
196
+ decision: 'reject',
197
+ reason: `Unknown tool: ${proposal.toolName}`,
198
+ };
199
+ }
200
+
201
+ // Check if action exists
202
+ const tool = this.tools.get(proposal.toolName)!;
203
+ const validAction = tool.actions.find(a => a.name === proposal.action);
204
+ if (!validAction) {
205
+ return {
206
+ decision: 'reject',
207
+ reason: `Unknown action "${proposal.action}" for tool "${proposal.toolName}". Available actions: ${tool.actions.map(a => a.name).join(', ')}`,
208
+ };
209
+ }
210
+
211
+ // Check required parameters
212
+ const schema = validAction.parameters;
213
+ if (schema && schema.required && Array.isArray(schema.required)) {
214
+ for (const requiredParam of schema.required as string[]) {
215
+ if (!(requiredParam in proposal.params)) {
216
+ return {
217
+ decision: 'reject',
218
+ reason: `Missing required parameter: ${requiredParam}`,
219
+ };
220
+ }
221
+ }
222
+ }
223
+
224
+ // Needs full AI evaluation
225
+ return null;
226
+ }
227
+
228
+ /**
229
+ * Update the policy prompt
230
+ */
231
+ public setPolicy(policyPrompt: string): void {
232
+ this.policyPrompt = policyPrompt;
233
+ }
234
+
235
+ /**
236
+ * Get current policy
237
+ */
238
+ public getPolicy(): string {
239
+ return this.policyPrompt;
240
+ }
241
+ }