feat(deno): Add Deno tool and smartdeno integration; export and register DenoTool; update docs and tests

This commit is contained in:
2025-12-02 12:11:31 +00:00
parent b9e8174f23
commit cf6d7163be
10 changed files with 373 additions and 21 deletions

View File

@@ -1,5 +1,16 @@
# Changelog
## 2025-12-02 - 1.1.0 - feat(deno)
Add Deno tool and smartdeno integration; export and register DenoTool; update docs and tests
- Introduce DenoTool wrapper (ts/smartagent.tools.deno.ts) to run TypeScript/JavaScript in a sandboxed Deno environment with permission controls
- Add @push.rocks/smartdeno dependency to package.json
- Import and re-export smartdeno in ts/plugins.ts
- Export DenoTool and TDenoPermission from ts/index.ts
- Register DenoTool in DualAgentOrchestrator.registerStandardTools() so it's available as a standard tool
- Update README architecture diagram and docs to include Deno as a standard tool
- Add tests for DenoTool in test/test.ts (exports, instantiation, call summary, permission display)
## 2025-12-02 - 1.0.2 - fix(core)
Bump version to 1.0.2 (patch release)

View File

@@ -23,6 +23,7 @@
"dependencies": {
"@push.rocks/smartai": "^0.8.0",
"@push.rocks/smartbrowser": "^2.0.8",
"@push.rocks/smartdeno": "^1.2.0",
"@push.rocks/smartfs": "^1.2.0",
"@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartshell": "^3.3.0"

41
pnpm-lock.yaml generated
View File

@@ -14,6 +14,9 @@ importers:
'@push.rocks/smartbrowser':
specifier: ^2.0.8
version: 2.0.8(typescript@5.9.3)
'@push.rocks/smartdeno':
specifier: ^1.2.0
version: 1.2.0
'@push.rocks/smartfs':
specifier: ^1.2.0
version: 1.2.0
@@ -831,6 +834,9 @@ packages:
'@push.rocks/smartarchive@4.2.4':
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
'@push.rocks/smartarchive@5.0.1':
resolution: {integrity: sha512-x4bie9IIdL9BZqBZLc8Pemp8xZOJGa6mXSVgKJRL4/Rw+E5N4rVHjQOYGRV75nC2mAMJh9GIbixuxLnWjj77ag==}
'@push.rocks/smartarray@1.1.0':
resolution: {integrity: sha512-b5YgBmUdglOJH8zeUf2ZWdPCoqySgwvkycRi2BhA9zVZHkpASh39Ej0q0fxFJetlUVyYqGfVoMVjbVrLFfFV7g==}
@@ -868,6 +874,9 @@ packages:
'@push.rocks/smartdelay@3.0.5':
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
'@push.rocks/smartdeno@1.2.0':
resolution: {integrity: sha512-6S1plCaMUVOZiRSflfoz9Fqk9phACCuKmc7Z6SfTvfl+p9VcPUmewKgaa/0QiLOpiI6ksfxdfmkS5Rw5HpYeIA==}
'@push.rocks/smartdns@7.6.1':
resolution: {integrity: sha512-nnP5+A2GOt0WsHrYhtKERmjdEHUchc+QbCCBEqlyeQTn+mNfx2WZvKVI1DFRJt8lamvzxP6Hr/BSe3WHdh4Snw==}
@@ -5161,6 +5170,26 @@ snapshots:
- react-native-b4a
- supports-color
'@push.rocks/smartarchive@5.0.1':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartfile': 13.1.0
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrequest': 4.4.2
'@push.rocks/smartrx': 3.0.10
'@push.rocks/smartstream': 3.2.5
'@push.rocks/smartunique': 3.0.9
'@push.rocks/smarturl': 3.1.0
'@types/tar-stream': 3.1.4
fflate: 0.8.2
file-type: 21.1.1
tar-stream: 3.1.7
transitivePeerDependencies:
- bare-abort-controller
- react-native-b4a
- supports-color
'@push.rocks/smartarray@1.1.0': {}
'@push.rocks/smartbrowser@2.0.8(typescript@5.9.3)':
@@ -5284,6 +5313,18 @@ snapshots:
dependencies:
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartdeno@1.2.0':
dependencies:
'@push.rocks/smartarchive': 5.0.1
'@push.rocks/smartfs': 1.2.0
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartshell': 3.3.0
'@push.rocks/smartunique': 3.0.9
transitivePeerDependencies:
- bare-abort-controller
- react-native-b4a
- supports-color
'@push.rocks/smartdns@7.6.1':
dependencies:
'@push.rocks/smartdelay': 3.0.5

108
readme.md
View File

@@ -19,26 +19,33 @@ This design ensures safe tool use through AI-based policy evaluation rather than
## Architecture
```
User Task + Guardian Policy Prompt
|
+---------------------------------------+
| DualAgentOrchestrator |
| |
| +--------+ +------------+ |
| | Driver |-------> | Guardian | |
| | Agent | tool | Agent | |
| | | call | | |
| | Reason |<--------| Evaluate | |
| | + Plan | approve | against | |
| +--------+ /reject | policy | |
| | +feedback+-----------+ |
| v (if approved) |
| +-----------------------------------+|
| | Standard Tools ||
| | Filesystem | HTTP | Shell | Browser|
| +-----------------------------------+|
+---------------------------------------+
```mermaid
flowchart TB
subgraph Input
Task["User Task"]
Policy["Guardian Policy Prompt"]
end
subgraph Orchestrator["DualAgentOrchestrator"]
Driver["Driver Agent<br/><i>Reason + Plan</i>"]
Guardian["Guardian Agent<br/><i>Evaluate against policy</i>"]
Driver -->|"tool call proposal"| Guardian
Guardian -->|"approve / reject + feedback"| Driver
end
subgraph Tools["Standard Tools"]
FS["Filesystem"]
HTTP["HTTP"]
Shell["Shell"]
Browser["Browser"]
Deno["Deno"]
end
Task --> Orchestrator
Policy --> Guardian
Driver -->|"execute<br/>(if approved)"| Tools
Tools -->|"result"| Driver
```
## Quick Start
@@ -139,6 +146,46 @@ Web page interaction using `@push.rocks/smartbrowser` (Puppeteer-based).
</tool_call>
```
### DenoTool
Execute TypeScript/JavaScript code in a sandboxed Deno environment using `@push.rocks/smartdeno`.
**Actions**: `execute`, `executeWithResult`
**Permissions**: `all`, `env`, `ffi`, `hrtime`, `net`, `read`, `run`, `sys`, `write`
By default, code runs fully sandboxed with no permissions. Permissions must be explicitly requested.
```typescript
// Simple code execution
<tool_call>
<tool>deno</tool>
<action>execute</action>
<params>{"code": "console.log('Hello from Deno!')"}</params>
<reasoning>Running a simple script to verify the environment</reasoning>
</tool_call>
// Code with network permission
<tool_call>
<tool>deno</tool>
<action>execute</action>
<params>{
"code": "const resp = await fetch('https://api.example.com/data'); console.log(await resp.json());",
"permissions": ["net"]
}</params>
<reasoning>Fetching data from API using Deno's fetch</reasoning>
</tool_call>
// Execute and parse JSON result
<tool_call>
<tool>deno</tool>
<action>executeWithResult</action>
<params>{
"code": "const result = { sum: 2 + 2, date: new Date().toISOString() }; console.log(JSON.stringify(result));"
}</params>
<reasoning>Computing values and returning structured data</reasoning>
</tool_call>
```
## Guardian Policy Examples
### Strict Security Policy
@@ -174,6 +221,27 @@ Always verify:
`;
```
### Deno Code Execution Policy
```typescript
const denoPolicy = `
DENO CODE EXECUTION POLICY:
- ONLY allow 'read' permission for files within the workspace
- REJECT 'all' permission unless explicitly justified for the task
- REJECT 'run' permission (subprocess execution) without specific justification
- REJECT code that attempts to:
- Access credentials or environment secrets (even with 'env' permission)
- Make network requests to internal/private IP ranges
- Write to system directories
- FLAG obfuscated or encoded code (base64, eval with dynamic strings)
- Prefer sandboxed execution (no permissions) when possible
When evaluating code:
- Review the actual code content, not just permissions
- Consider what data the code could exfiltrate
- Verify network endpoints are legitimate public APIs
`;
```
## Configuration Options
```typescript

View File

@@ -35,6 +35,10 @@ tap.test('should export BrowserTool class', async () => {
expect(smartagent.BrowserTool).toBeTypeOf('function');
});
tap.test('should export DenoTool class', async () => {
expect(smartagent.DenoTool).toBeTypeOf('function');
});
// Test tool instantiation
tap.test('should be able to instantiate FilesystemTool', async () => {
const fsTool = new smartagent.FilesystemTool();
@@ -61,6 +65,12 @@ tap.test('should be able to instantiate BrowserTool', async () => {
expect(browserTool.actions).toBeTypeOf('object');
});
tap.test('should be able to instantiate DenoTool', async () => {
const denoTool = new smartagent.DenoTool();
expect(denoTool.name).toEqual('deno');
expect(denoTool.actions).toBeTypeOf('object');
});
// Test tool descriptions
tap.test('FilesystemTool should have required actions', async () => {
const fsTool = new smartagent.FilesystemTool();
@@ -97,6 +107,13 @@ tap.test('BrowserTool should have required actions', async () => {
expect(actionNames).toContain('getPageContent');
});
tap.test('DenoTool should have required actions', async () => {
const denoTool = new smartagent.DenoTool();
const actionNames = denoTool.actions.map((a) => a.name);
expect(actionNames).toContain('execute');
expect(actionNames).toContain('executeWithResult');
});
// Test getCallSummary
tap.test('FilesystemTool should generate call summaries', async () => {
const fsTool = new smartagent.FilesystemTool();
@@ -112,4 +129,22 @@ tap.test('HttpTool should generate call summaries', async () => {
expect(summary).toInclude('example.com');
});
tap.test('DenoTool should generate call summaries', async () => {
const denoTool = new smartagent.DenoTool();
const summary = denoTool.getCallSummary('execute', { code: 'console.log("hello");' });
expect(summary).toBeTypeOf('string');
expect(summary).toInclude('sandboxed');
});
tap.test('DenoTool should show permissions in call summary', async () => {
const denoTool = new smartagent.DenoTool();
const summary = denoTool.getCallSummary('execute', {
code: 'console.log("hello");',
permissions: ['net', 'read']
});
expect(summary).toBeTypeOf('string');
expect(summary).toInclude('permissions');
expect(summary).toInclude('net');
});
export default tap.start();

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartagent',
version: '1.0.2',
version: '1.1.0',
description: 'an agentic framework built on top of @push.rocks/smartai'
}

View File

@@ -15,6 +15,7 @@ export { FilesystemTool } 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';
export { DenoTool, type TDenoPermission } from './smartagent.tools.deno.js';
// Export all interfaces
export * from './smartagent.interfaces.js';

View File

@@ -1,5 +1,6 @@
// @push.rocks scope
import * as smartai from '@push.rocks/smartai';
import * as smartdeno from '@push.rocks/smartdeno';
import * as smartfs from '@push.rocks/smartfs';
import * as smartrequest from '@push.rocks/smartrequest';
import * as smartbrowser from '@push.rocks/smartbrowser';
@@ -7,6 +8,7 @@ import * as smartshell from '@push.rocks/smartshell';
export {
smartai,
smartdeno,
smartfs,
smartrequest,
smartbrowser,

View File

@@ -7,6 +7,7 @@ import { FilesystemTool } from './smartagent.tools.filesystem.js';
import { HttpTool } from './smartagent.tools.http.js';
import { ShellTool } from './smartagent.tools.shell.js';
import { BrowserTool } from './smartagent.tools.browser.js';
import { DenoTool } from './smartagent.tools.deno.js';
/**
* DualAgentOrchestrator - Coordinates Driver and Guardian agents
@@ -87,6 +88,7 @@ export class DualAgentOrchestrator {
new HttpTool(),
new ShellTool(),
new BrowserTool(),
new DenoTool(),
];
for (const tool of standardTools) {

191
ts/smartagent.tools.deno.ts Normal file
View File

@@ -0,0 +1,191 @@
import * as plugins from './plugins.js';
import * as interfaces from './smartagent.interfaces.js';
import { BaseToolWrapper } from './smartagent.tools.base.js';
/**
* Deno permission types for sandboxed code execution
*/
export type TDenoPermission =
| 'all'
| 'env'
| 'ffi'
| 'hrtime'
| 'net'
| 'read'
| 'run'
| 'sys'
| 'write';
/**
* Deno tool for executing TypeScript/JavaScript code in a sandboxed environment
* Wraps @push.rocks/smartdeno
*/
export class DenoTool extends BaseToolWrapper {
public name = 'deno';
public description =
'Execute TypeScript/JavaScript code in a sandboxed Deno environment with fine-grained permission control';
public actions: interfaces.IToolAction[] = [
{
name: 'execute',
description:
'Execute TypeScript/JavaScript code and return stdout/stderr. Code runs in Deno sandbox with specified permissions.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'TypeScript/JavaScript code to execute',
},
permissions: {
type: 'array',
items: {
type: 'string',
enum: ['all', 'env', 'ffi', 'hrtime', 'net', 'read', 'run', 'sys', 'write'],
},
description:
'Deno permissions to grant. Default: none (fully sandboxed). Options: all, env, net, read, write, run, sys, ffi, hrtime',
},
},
required: ['code'],
},
},
{
name: 'executeWithResult',
description:
'Execute code that outputs JSON on the last line of stdout. The JSON is parsed and returned as the result.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description:
'Code that console.logs a JSON value on the final line. This JSON will be parsed and returned.',
},
permissions: {
type: 'array',
items: {
type: 'string',
enum: ['all', 'env', 'ffi', 'hrtime', 'net', 'read', 'run', 'sys', 'write'],
},
description: 'Deno permissions to grant',
},
},
required: ['code'],
},
},
];
private smartdeno!: plugins.smartdeno.SmartDeno;
public async initialize(): Promise<void> {
this.smartdeno = new plugins.smartdeno.SmartDeno();
await this.smartdeno.start();
this.isInitialized = true;
}
public async cleanup(): Promise<void> {
if (this.smartdeno) {
await this.smartdeno.stop();
}
this.isInitialized = false;
}
public async execute(
action: string,
params: Record<string, unknown>
): Promise<interfaces.IToolExecutionResult> {
this.validateAction(action);
this.ensureInitialized();
try {
const code = params.code as string;
const permissions = (params.permissions as TDenoPermission[]) || [];
// Execute the script
const result = await this.smartdeno.executeScript(code, {
permissions,
});
switch (action) {
case 'execute': {
return {
success: result.exitCode === 0,
result: {
exitCode: result.exitCode,
stdout: result.stdout,
stderr: result.stderr,
permissions,
},
};
}
case 'executeWithResult': {
if (result.exitCode !== 0) {
return {
success: false,
error: `Script failed with exit code ${result.exitCode}: ${result.stderr}`,
};
}
// Parse the last line of stdout as JSON
const lines = result.stdout.trim().split('\n');
const lastLine = lines[lines.length - 1];
try {
const parsedResult = JSON.parse(lastLine);
return {
success: true,
result: {
parsed: parsedResult,
stdout: result.stdout,
stderr: result.stderr,
},
};
} catch (parseError) {
return {
success: false,
error: `Failed to parse JSON from last line of output: ${lastLine}`,
};
}
}
default:
return {
success: false,
error: `Unknown action: ${action}`,
};
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
public getCallSummary(action: string, params: Record<string, unknown>): string {
const code = params.code as string;
const permissions = (params.permissions as string[]) || [];
// Create a preview of the code (first 100 chars)
const codePreview = code.length > 100 ? code.substring(0, 100) + '...' : code;
// Escape newlines for single-line display
const cleanPreview = codePreview.replace(/\n/g, '\\n');
const permissionInfo = permissions.length > 0
? ` [permissions: ${permissions.join(', ')}]`
: ' [sandboxed - no permissions]';
switch (action) {
case 'execute':
return `Execute Deno code${permissionInfo}: "${cleanPreview}"`;
case 'executeWithResult':
return `Execute Deno code and parse JSON result${permissionInfo}: "${cleanPreview}"`;
default:
return `Unknown action: ${action}`;
}
}
}