@aigne/agent-library 1.23.0-beta.6 → 1.23.0-beta.8
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/CHANGELOG.md +22 -0
- package/README.md +3 -0
- package/lib/cjs/bash/index.d.ts +124 -0
- package/lib/cjs/bash/index.js +359 -0
- package/lib/cjs/orchestrator/index.d.ts +3 -3
- package/lib/cjs/orchestrator/index.js +20 -7
- package/lib/cjs/orchestrator/type.d.ts +40 -139
- package/lib/cjs/orchestrator/type.js +7 -7
- package/lib/cjs/utils/mutex.d.ts +6 -0
- package/lib/cjs/utils/mutex.js +28 -0
- package/lib/dts/bash/index.d.ts +124 -0
- package/lib/dts/orchestrator/index.d.ts +3 -3
- package/lib/dts/orchestrator/type.d.ts +40 -139
- package/lib/dts/utils/mutex.d.ts +6 -0
- package/lib/esm/bash/index.d.ts +124 -0
- package/lib/esm/bash/index.js +322 -0
- package/lib/esm/orchestrator/index.d.ts +3 -3
- package/lib/esm/orchestrator/index.js +21 -8
- package/lib/esm/orchestrator/type.d.ts +40 -139
- package/lib/esm/orchestrator/type.js +7 -7
- package/lib/esm/utils/mutex.d.ts +6 -0
- package/lib/esm/utils/mutex.js +24 -0
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@
|
|
|
7
7
|
* @aigne/core bumped to 1.22.0
|
|
8
8
|
* @aigne/openai bumped to 0.3.4
|
|
9
9
|
|
|
10
|
+
## [1.23.0-beta.8](https://github.com/AIGNE-io/aigne-framework/compare/agent-library-v1.23.0-beta.7...agent-library-v1.23.0-beta.8) (2025-12-12)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **agent-library:** add BashAgent with sandbox support ([#816](https://github.com/AIGNE-io/aigne-framework/issues/816)) ([0d4feee](https://github.com/AIGNE-io/aigne-framework/commit/0d4feeeac2b71df1c4d725adeee76c9318ce8e02))
|
|
16
|
+
|
|
17
|
+
## [1.23.0-beta.7](https://github.com/AIGNE-io/aigne-framework/compare/agent-library-v1.23.0-beta.6...agent-library-v1.23.0-beta.7) (2025-12-11)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **orchestrator:** support custom input schema for planner/worker/completer ([#823](https://github.com/AIGNE-io/aigne-framework/issues/823)) ([3d26f8b](https://github.com/AIGNE-io/aigne-framework/commit/3d26f8bac8b679010f25d9e4eb59fc6e80afda4c))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Dependencies
|
|
26
|
+
|
|
27
|
+
* The following workspace dependencies were updated
|
|
28
|
+
* dependencies
|
|
29
|
+
* @aigne/core bumped to 1.71.0-beta.6
|
|
30
|
+
* @aigne/openai bumped to 0.16.15-beta.6
|
|
31
|
+
|
|
10
32
|
## [1.23.0-beta.6](https://github.com/AIGNE-io/aigne-framework/compare/agent-library-v1.23.0-beta.5...agent-library-v1.23.0-beta.6) (2025-12-11)
|
|
11
33
|
|
|
12
34
|
|
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Collection of agent libraries for [AIGNE Framework](https://github.com/AIGNE-io/
|
|
|
29
29
|
## Features
|
|
30
30
|
|
|
31
31
|
* **Orchestrator Agent**: Provides OrchestratorAgent implementation for coordinating workflows between multiple agents
|
|
32
|
+
* **Bash Agent**: Secure execution of bash scripts with sandboxed environment and comprehensive output handling
|
|
32
33
|
* **Task Concurrency**: Supports parallel execution of multiple tasks to improve processing efficiency
|
|
33
34
|
* **Planning & Execution**: Automatically generates execution plans and executes them step by step
|
|
34
35
|
* **Result Synthesis**: Intelligently synthesizes results from multiple steps and tasks
|
|
@@ -62,6 +63,8 @@ The library provides the following components:
|
|
|
62
63
|
|
|
63
64
|
* **[Orchestrator Agent](src/orchestrator/README.md)**: A sophisticated agent pattern that enables autonomous task planning and execution through a three-phase architecture: Planner → Worker → Completer. It breaks down complex objectives into manageable tasks, executes them iteratively, and synthesizes the final results. Perfect for coordinating complex workflows and multi-step tasks.
|
|
64
65
|
|
|
66
|
+
* **[Bash Agent](src/bash/README.md)**: Enables secure execution of bash scripts within a sandboxed environment. Provides controlled access to system commands, network resources, and the filesystem while returning comprehensive execution results including stdout, stderr, and exit codes. Ideal for running system commands, interacting with CLI tools, and executing shell scripts safely.
|
|
67
|
+
|
|
65
68
|
## License
|
|
66
69
|
|
|
67
70
|
Elastic-2.0
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { type SpawnOptions } from "node:child_process";
|
|
2
|
+
import { Agent, type AgentInvokeOptions, type AgentOptions, type AgentResponseStream, type Message } from "@aigne/core";
|
|
3
|
+
import { type NestAgentSchema } from "@aigne/core/loader/agent-yaml.js";
|
|
4
|
+
import { type LoadOptions } from "@aigne/core/loader/index.js";
|
|
5
|
+
import { type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
|
|
6
|
+
export interface BashAgentOptions extends AgentOptions<BashAgentInput, BashAgentOutput> {
|
|
7
|
+
sandbox?: Partial<{
|
|
8
|
+
[K in keyof SandboxRuntimeConfig]: Partial<SandboxRuntimeConfig[K]>;
|
|
9
|
+
}> | boolean;
|
|
10
|
+
inputKey?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional timeout for script execution in milliseconds
|
|
13
|
+
* @default 60000 (60 seconds)
|
|
14
|
+
*/
|
|
15
|
+
timeout?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Optional permissions configuration for command execution control
|
|
18
|
+
* Inspired by Claude Code's permission system
|
|
19
|
+
*/
|
|
20
|
+
permissions?: {
|
|
21
|
+
/**
|
|
22
|
+
* Whitelist: Commands that are allowed to execute without approval
|
|
23
|
+
* Supports exact match or prefix match with ':*' wildcard
|
|
24
|
+
* Examples: ['npm run test:*', 'git status', 'ls:*']
|
|
25
|
+
*/
|
|
26
|
+
allow?: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Blacklist: Commands that are completely forbidden
|
|
29
|
+
* Takes highest priority over allow and defaultMode
|
|
30
|
+
* Examples: ['rm:*', 'sudo:*', 'curl:*']
|
|
31
|
+
*/
|
|
32
|
+
deny?: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Default permission mode when command doesn't match allow/deny lists
|
|
35
|
+
* @default 'allow'
|
|
36
|
+
*/
|
|
37
|
+
defaultMode?: "allow" | "ask" | "deny";
|
|
38
|
+
/**
|
|
39
|
+
* Callback function invoked when a command requires user approval (ask mode)
|
|
40
|
+
* Return true to approve, false to reject
|
|
41
|
+
* @param script - The script that requires approval
|
|
42
|
+
* @returns Promise resolving to approval decision
|
|
43
|
+
*/
|
|
44
|
+
guard?: BashAgent["guard"];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface LoadBashAgentOptions extends Omit<BashAgentOptions, "permissions"> {
|
|
48
|
+
permissions?: Omit<NonNullable<BashAgentOptions["permissions"]>, "guard"> & {
|
|
49
|
+
guard?: NestAgentSchema;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export interface BashAgentInput extends Message {
|
|
53
|
+
script?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface BashAgentOutput extends Message {
|
|
56
|
+
stdout?: string;
|
|
57
|
+
stderr?: string;
|
|
58
|
+
exitCode?: number;
|
|
59
|
+
}
|
|
60
|
+
export declare class BashAgent extends Agent<BashAgentInput, BashAgentOutput> {
|
|
61
|
+
options: BashAgentOptions;
|
|
62
|
+
static load(options: {
|
|
63
|
+
filepath: string;
|
|
64
|
+
parsed: LoadBashAgentOptions;
|
|
65
|
+
options?: LoadOptions;
|
|
66
|
+
}): Promise<Agent<any, any>>;
|
|
67
|
+
constructor(options: BashAgentOptions);
|
|
68
|
+
inputKey?: string;
|
|
69
|
+
guard?: Agent<{
|
|
70
|
+
script?: string;
|
|
71
|
+
}, {
|
|
72
|
+
approved: boolean;
|
|
73
|
+
reason?: string;
|
|
74
|
+
}>;
|
|
75
|
+
process(input: BashAgentInput, options: AgentInvokeOptions): Promise<AgentResponseStream<BashAgentOutput>>;
|
|
76
|
+
spawn(command: string, args?: string[], options?: SpawnOptions): Promise<AgentResponseStream<BashAgentOutput>>;
|
|
77
|
+
runInSandbox<T>(config: Exclude<BashAgentOptions["sandbox"], boolean>, script: string, task: (script: string) => Promise<T>): Promise<T>;
|
|
78
|
+
/**
|
|
79
|
+
* Check permission for executing a script
|
|
80
|
+
* Permission priority: deny > allow > defaultMode
|
|
81
|
+
*
|
|
82
|
+
* For complex commands (with pipes, chaining, etc.), each sub-command
|
|
83
|
+
* is validated separately. All sub-commands must pass permission checks.
|
|
84
|
+
*
|
|
85
|
+
* @param script - The script to check permission for
|
|
86
|
+
* @returns Permission decision: 'allow', 'ask', or 'deny'
|
|
87
|
+
*/
|
|
88
|
+
checkPermission(script: string): Promise<"allow" | "ask" | "deny">;
|
|
89
|
+
/**
|
|
90
|
+
* Split a script into individual commands by pipes, command chaining, etc.
|
|
91
|
+
* Separators: | (pipe), && (AND), || (OR), & (background), ; (sequential), \n (newline)
|
|
92
|
+
*
|
|
93
|
+
* Note: Redirection operators (>, >>, <, <<, &>, 2>, etc.) are treated as part of
|
|
94
|
+
* the command, not as separators.
|
|
95
|
+
*
|
|
96
|
+
* @param script - The script to split
|
|
97
|
+
* @returns Array of individual commands
|
|
98
|
+
*/
|
|
99
|
+
private splitCommands;
|
|
100
|
+
/**
|
|
101
|
+
* Check permission for a single command
|
|
102
|
+
* @param command - The command to check
|
|
103
|
+
* @param permissions - Permission configuration
|
|
104
|
+
* @returns Permission decision for this command
|
|
105
|
+
*/
|
|
106
|
+
private checkSingleCommandPermission;
|
|
107
|
+
/**
|
|
108
|
+
* Match a single command against a permission pattern
|
|
109
|
+
* Supports exact match and prefix match with ':*' wildcard
|
|
110
|
+
*
|
|
111
|
+
* Note: This method is called for individual commands after splitting,
|
|
112
|
+
* so it doesn't need to handle complex command chaining.
|
|
113
|
+
*
|
|
114
|
+
* Examples:
|
|
115
|
+
* - "ls:*" matches "ls", "ls -la", "ls:option"
|
|
116
|
+
* - "npm run test:*" matches "npm run test", "npm run test:unit", "npm run test arg"
|
|
117
|
+
*
|
|
118
|
+
* @param command - The command to match (should be a single command)
|
|
119
|
+
* @param pattern - The pattern to match against
|
|
120
|
+
* @returns true if command matches pattern
|
|
121
|
+
*/
|
|
122
|
+
private matchPattern;
|
|
123
|
+
}
|
|
124
|
+
export default BashAgent;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.BashAgent = void 0;
|
|
37
|
+
const node_child_process_1 = require("node:child_process");
|
|
38
|
+
const core_1 = require("@aigne/core");
|
|
39
|
+
const agent_yaml_js_1 = require("@aigne/core/loader/agent-yaml.js");
|
|
40
|
+
const index_js_1 = require("@aigne/core/loader/index.js");
|
|
41
|
+
const schema_js_1 = require("@aigne/core/loader/schema.js");
|
|
42
|
+
const sandbox_runtime_1 = require("@anthropic-ai/sandbox-runtime");
|
|
43
|
+
const ripgrep_1 = require("@vscode/ripgrep");
|
|
44
|
+
const zod_1 = __importStar(require("zod"));
|
|
45
|
+
const mutex_js_1 = require("../utils/mutex.js");
|
|
46
|
+
const DEFAULT_TIMEOUT = 60e3; // 60 seconds
|
|
47
|
+
let sandboxInitialization;
|
|
48
|
+
const mutex = new mutex_js_1.Mutex();
|
|
49
|
+
class BashAgent extends core_1.Agent {
|
|
50
|
+
options;
|
|
51
|
+
static async load(options) {
|
|
52
|
+
const schema = getBashAgentSchema({ filepath: options.filepath });
|
|
53
|
+
const parsed = await schema.parseAsync(options.parsed);
|
|
54
|
+
return new BashAgent({
|
|
55
|
+
...parsed,
|
|
56
|
+
permissions: {
|
|
57
|
+
...parsed.permissions,
|
|
58
|
+
guard: parsed.permissions?.guard
|
|
59
|
+
? await (0, index_js_1.loadNestAgent)(options.filepath, parsed.permissions.guard, options.options ?? {}, {
|
|
60
|
+
outputSchema: zod_1.default.object({
|
|
61
|
+
approved: zod_1.default.boolean().describe("Whether the command is approved by the user."),
|
|
62
|
+
reason: zod_1.default.string().describe("Optional reason for rejection.").optional(),
|
|
63
|
+
}),
|
|
64
|
+
})
|
|
65
|
+
: undefined,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
constructor(options) {
|
|
70
|
+
super({
|
|
71
|
+
name: "Bash",
|
|
72
|
+
description: `\
|
|
73
|
+
Execute bash scripts and return stdout and stderr output.
|
|
74
|
+
|
|
75
|
+
When to use:
|
|
76
|
+
- Running system commands or bash scripts
|
|
77
|
+
- Interacting with command-line tools
|
|
78
|
+
`,
|
|
79
|
+
...options,
|
|
80
|
+
inputSchema: zod_1.default.object({
|
|
81
|
+
[options.inputKey || "script"]: zod_1.default.string().describe("The bash script to execute."),
|
|
82
|
+
}),
|
|
83
|
+
outputSchema: zod_1.default.object({
|
|
84
|
+
stdout: zod_1.default.string().describe("The standard output from the bash script.").optional(),
|
|
85
|
+
stderr: zod_1.default.string().describe("The standard error output from the bash script.").optional(),
|
|
86
|
+
exitCode: zod_1.default.number().describe("The exit code of the bash script execution.").optional(),
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
this.options = options;
|
|
90
|
+
this.guard = this.options.permissions?.guard;
|
|
91
|
+
this.inputKey = this.options.inputKey;
|
|
92
|
+
}
|
|
93
|
+
inputKey;
|
|
94
|
+
guard;
|
|
95
|
+
async process(input, options) {
|
|
96
|
+
const script = input[this.inputKey || "script"];
|
|
97
|
+
if (typeof script !== "string")
|
|
98
|
+
throw new Error(`Invalid or missing script input: ${this.inputKey || "script"}`);
|
|
99
|
+
// Permission check
|
|
100
|
+
const permission = await this.checkPermission(script);
|
|
101
|
+
if (permission === "deny") {
|
|
102
|
+
throw new Error(`Command blocked by permissions: ${script}`);
|
|
103
|
+
}
|
|
104
|
+
if (permission === "ask") {
|
|
105
|
+
if (!this.guard) {
|
|
106
|
+
throw new Error(`No guard agent configured for permission 'ask'`);
|
|
107
|
+
}
|
|
108
|
+
const { approved, reason } = await this.invokeChildAgent(this.guard, input, {
|
|
109
|
+
...options,
|
|
110
|
+
streaming: false,
|
|
111
|
+
});
|
|
112
|
+
if (!approved) {
|
|
113
|
+
throw new Error(`Command rejected by guard agent (${this.guard.name}): ${script}, reason: ${reason || "no reason provided"}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const platform = {
|
|
117
|
+
win32: "windows",
|
|
118
|
+
darwin: "macos",
|
|
119
|
+
linux: "linux",
|
|
120
|
+
}[globalThis.process.platform] || "unknown";
|
|
121
|
+
if (this.options.sandbox === false) {
|
|
122
|
+
return this.spawn("bash", ["-c", script]);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
if (!sandbox_runtime_1.SandboxManager.isSupportedPlatform(platform)) {
|
|
126
|
+
throw new Error(`Sandboxed execution is not supported on this platform ${platform}`);
|
|
127
|
+
}
|
|
128
|
+
return await this.runInSandbox(typeof this.options.sandbox === "boolean" ? {} : this.options.sandbox, script, async (sandboxedCommand) => {
|
|
129
|
+
return this.spawn(sandboxedCommand, undefined, {
|
|
130
|
+
shell: true,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async spawn(command, args, options) {
|
|
136
|
+
return new ReadableStream({
|
|
137
|
+
start: (controller) => {
|
|
138
|
+
try {
|
|
139
|
+
const timeout = this.options.timeout ?? DEFAULT_TIMEOUT;
|
|
140
|
+
const child = (0, node_child_process_1.spawn)(command, args, {
|
|
141
|
+
...options,
|
|
142
|
+
stdio: "pipe",
|
|
143
|
+
timeout,
|
|
144
|
+
});
|
|
145
|
+
let stderr = "";
|
|
146
|
+
child.stdout.on("data", (chunk) => {
|
|
147
|
+
controller.enqueue({ delta: { text: { stdout: chunk.toString() } } });
|
|
148
|
+
});
|
|
149
|
+
child.stderr.on("data", (chunk) => {
|
|
150
|
+
controller.enqueue({ delta: { text: { stderr: chunk.toString() } } });
|
|
151
|
+
stderr += chunk.toString();
|
|
152
|
+
});
|
|
153
|
+
child.on("error", (error) => {
|
|
154
|
+
controller.error(error);
|
|
155
|
+
});
|
|
156
|
+
child.on("close", (code, signal) => {
|
|
157
|
+
// Handle timeout or killed by signal
|
|
158
|
+
if (signal) {
|
|
159
|
+
const timeoutHint = signal === "SIGTERM" ? ` (likely timeout ${timeout})` : "";
|
|
160
|
+
controller.error(new Error(`Bash script killed by signal ${signal}${timeoutHint}: ${stderr}`));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Handle normal exit
|
|
164
|
+
if (typeof code === "number") {
|
|
165
|
+
if (code === 0) {
|
|
166
|
+
controller.enqueue({ delta: { json: { exitCode: code } } });
|
|
167
|
+
controller.close();
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
controller.error(new Error(`Bash script exited with code ${code}: ${stderr}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Unexpected case: no code and no signal
|
|
175
|
+
controller.error(new Error(`Bash script closed unexpectedly: ${stderr}`));
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
controller.error(error);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async runInSandbox(config, script, task) {
|
|
186
|
+
return await mutex.runExclusive(async () => {
|
|
187
|
+
sandboxInitialization ??= sandbox_runtime_1.SandboxManager.initialize({
|
|
188
|
+
network: {
|
|
189
|
+
allowedDomains: [],
|
|
190
|
+
deniedDomains: [],
|
|
191
|
+
},
|
|
192
|
+
filesystem: {
|
|
193
|
+
denyRead: [],
|
|
194
|
+
denyWrite: [],
|
|
195
|
+
allowWrite: [],
|
|
196
|
+
},
|
|
197
|
+
ripgrep: {
|
|
198
|
+
command: ripgrep_1.rgPath,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
await sandboxInitialization;
|
|
202
|
+
sandbox_runtime_1.SandboxManager.updateConfig({
|
|
203
|
+
...config,
|
|
204
|
+
network: {
|
|
205
|
+
...config?.network,
|
|
206
|
+
allowedDomains: config?.network?.allowedDomains || [],
|
|
207
|
+
deniedDomains: config?.network?.deniedDomains || [],
|
|
208
|
+
},
|
|
209
|
+
filesystem: {
|
|
210
|
+
...config?.filesystem,
|
|
211
|
+
denyRead: config?.filesystem?.denyRead || [],
|
|
212
|
+
denyWrite: config?.filesystem?.denyWrite || [],
|
|
213
|
+
allowWrite: config?.filesystem?.allowWrite || [],
|
|
214
|
+
},
|
|
215
|
+
ripgrep: {
|
|
216
|
+
command: ripgrep_1.rgPath,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const sandboxedCommand = await sandbox_runtime_1.SandboxManager.wrapWithSandbox(script);
|
|
220
|
+
return await task(sandboxedCommand);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check permission for executing a script
|
|
225
|
+
* Permission priority: deny > allow > defaultMode
|
|
226
|
+
*
|
|
227
|
+
* For complex commands (with pipes, chaining, etc.), each sub-command
|
|
228
|
+
* is validated separately. All sub-commands must pass permission checks.
|
|
229
|
+
*
|
|
230
|
+
* @param script - The script to check permission for
|
|
231
|
+
* @returns Permission decision: 'allow', 'ask', or 'deny'
|
|
232
|
+
*/
|
|
233
|
+
async checkPermission(script) {
|
|
234
|
+
const { permissions } = this.options;
|
|
235
|
+
if (!permissions) {
|
|
236
|
+
return "allow"; // No permissions configured, default to allow
|
|
237
|
+
}
|
|
238
|
+
// Split complex commands into individual commands
|
|
239
|
+
const commands = this.splitCommands(script);
|
|
240
|
+
// Check permission for each command
|
|
241
|
+
for (const command of commands) {
|
|
242
|
+
const commandPermission = this.checkSingleCommandPermission(command, permissions);
|
|
243
|
+
// If any command is denied, deny the whole script
|
|
244
|
+
if (commandPermission === "deny") {
|
|
245
|
+
return "deny";
|
|
246
|
+
}
|
|
247
|
+
// If any command requires asking, the whole script requires asking
|
|
248
|
+
if (commandPermission === "ask") {
|
|
249
|
+
return "ask";
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// All commands are allowed
|
|
253
|
+
return "allow";
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Split a script into individual commands by pipes, command chaining, etc.
|
|
257
|
+
* Separators: | (pipe), && (AND), || (OR), & (background), ; (sequential), \n (newline)
|
|
258
|
+
*
|
|
259
|
+
* Note: Redirection operators (>, >>, <, <<, &>, 2>, etc.) are treated as part of
|
|
260
|
+
* the command, not as separators.
|
|
261
|
+
*
|
|
262
|
+
* @param script - The script to split
|
|
263
|
+
* @returns Array of individual commands
|
|
264
|
+
*/
|
|
265
|
+
splitCommands(script) {
|
|
266
|
+
// Split by pipes, &&, ||, &, ;, and newlines
|
|
267
|
+
// IMPORTANT: Match longer patterns first to avoid incorrect splitting:
|
|
268
|
+
// - && and || before single & and |
|
|
269
|
+
// - Must use negative lookbehind/lookahead to avoid matching &> (redirect)
|
|
270
|
+
// Pattern explanation: (?<!&) means "not preceded by &", (?!>) means "not followed by >"
|
|
271
|
+
const parts = script.split(/(\||&&|\|\||(?<!&)&(?!>)|;|\n)/);
|
|
272
|
+
const commands = [];
|
|
273
|
+
for (let i = 0; i < parts.length; i++) {
|
|
274
|
+
const part = parts[i];
|
|
275
|
+
if (!part)
|
|
276
|
+
continue;
|
|
277
|
+
const trimmedPart = part.trim();
|
|
278
|
+
// Skip empty parts and separator tokens
|
|
279
|
+
if (trimmedPart && !["|", "&&", "||", "&", ";"].includes(trimmedPart)) {
|
|
280
|
+
commands.push(trimmedPart);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return commands.length > 0 ? commands : [script];
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Check permission for a single command
|
|
287
|
+
* @param command - The command to check
|
|
288
|
+
* @param permissions - Permission configuration
|
|
289
|
+
* @returns Permission decision for this command
|
|
290
|
+
*/
|
|
291
|
+
checkSingleCommandPermission(command, permissions) {
|
|
292
|
+
// Priority 1: Check deny list (highest priority)
|
|
293
|
+
if (permissions.deny?.some((pattern) => this.matchPattern(command, pattern))) {
|
|
294
|
+
return "deny";
|
|
295
|
+
}
|
|
296
|
+
// Priority 2: Check allow list
|
|
297
|
+
if (permissions.allow?.some((pattern) => this.matchPattern(command, pattern))) {
|
|
298
|
+
return "allow";
|
|
299
|
+
}
|
|
300
|
+
// Priority 3: Apply default mode
|
|
301
|
+
return permissions.defaultMode || "allow";
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Match a single command against a permission pattern
|
|
305
|
+
* Supports exact match and prefix match with ':*' wildcard
|
|
306
|
+
*
|
|
307
|
+
* Note: This method is called for individual commands after splitting,
|
|
308
|
+
* so it doesn't need to handle complex command chaining.
|
|
309
|
+
*
|
|
310
|
+
* Examples:
|
|
311
|
+
* - "ls:*" matches "ls", "ls -la", "ls:option"
|
|
312
|
+
* - "npm run test:*" matches "npm run test", "npm run test:unit", "npm run test arg"
|
|
313
|
+
*
|
|
314
|
+
* @param command - The command to match (should be a single command)
|
|
315
|
+
* @param pattern - The pattern to match against
|
|
316
|
+
* @returns true if command matches pattern
|
|
317
|
+
*/
|
|
318
|
+
matchPattern(command, pattern) {
|
|
319
|
+
// Trim whitespace
|
|
320
|
+
command = command.trim();
|
|
321
|
+
pattern = pattern.trim();
|
|
322
|
+
// Exact match
|
|
323
|
+
if (pattern === command) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
// Prefix match with ':*' wildcard
|
|
327
|
+
if (pattern.endsWith(":*")) {
|
|
328
|
+
const prefix = pattern.slice(0, -2).trim();
|
|
329
|
+
// Match if command equals prefix or starts with prefix followed by space or colon
|
|
330
|
+
return command === prefix || command.startsWith(prefix) || command.startsWith(`${prefix}:`);
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
exports.BashAgent = BashAgent;
|
|
336
|
+
exports.default = BashAgent;
|
|
337
|
+
function getBashAgentSchema({ filepath }) {
|
|
338
|
+
const nestAgentSchema = (0, agent_yaml_js_1.getNestAgentSchema)({ filepath });
|
|
339
|
+
return (0, schema_js_1.camelizeSchema)(zod_1.default.object({
|
|
340
|
+
sandbox: (0, schema_js_1.optionalize)(zod_1.default.union([makeShapePropertiesOptions(sandbox_runtime_1.SandboxRuntimeConfigSchema, 2), zod_1.default.boolean()])),
|
|
341
|
+
inputKey: (0, schema_js_1.optionalize)(zod_1.default.string().describe("The input key for the bash script.")),
|
|
342
|
+
timeout: (0, schema_js_1.optionalize)(zod_1.default.number().describe("Timeout for script execution in milliseconds.")),
|
|
343
|
+
permissions: (0, schema_js_1.optionalize)((0, schema_js_1.camelizeSchema)(zod_1.default.object({
|
|
344
|
+
allow: (0, schema_js_1.optionalize)(zod_1.default.array(zod_1.default.string())),
|
|
345
|
+
deny: (0, schema_js_1.optionalize)(zod_1.default.array(zod_1.default.string())),
|
|
346
|
+
defaultMode: (0, schema_js_1.optionalize)(zod_1.default.enum(["allow", "ask", "deny"])),
|
|
347
|
+
guard: (0, schema_js_1.optionalize)(nestAgentSchema),
|
|
348
|
+
}))),
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
function makeShapePropertiesOptions(schema, depth = 1) {
|
|
352
|
+
return zod_1.default.object(Object.fromEntries(Object.entries(schema.shape).map(([key, value]) => {
|
|
353
|
+
const isObject = value instanceof zod_1.ZodObject;
|
|
354
|
+
if (isObject && depth > 1) {
|
|
355
|
+
return [key, (0, schema_js_1.optionalize)(makeShapePropertiesOptions(value, depth - 1))];
|
|
356
|
+
}
|
|
357
|
+
return [key, (0, schema_js_1.optionalize)(value)];
|
|
358
|
+
})));
|
|
359
|
+
}
|
|
@@ -6,7 +6,7 @@ import { type StateManagementOptions } from "./type.js";
|
|
|
6
6
|
* Configuration options for the Orchestrator Agent
|
|
7
7
|
*/
|
|
8
8
|
export interface OrchestratorAgentOptions<I extends Message = Message, O extends Message = Message> extends Omit<AIAgentOptions<I, O>, "instructions"> {
|
|
9
|
-
objective
|
|
9
|
+
objective?: PromptBuilder;
|
|
10
10
|
planner?: OrchestratorAgent<I, O>["planner"];
|
|
11
11
|
worker?: OrchestratorAgent<I, O>["worker"];
|
|
12
12
|
completer?: OrchestratorAgent<I, O>["completer"];
|
|
@@ -17,7 +17,7 @@ export interface OrchestratorAgentOptions<I extends Message = Message, O extends
|
|
|
17
17
|
stateManagement?: StateManagementOptions;
|
|
18
18
|
}
|
|
19
19
|
export interface LoadOrchestratorAgentOptions<I extends Message = Message, O extends Message = Message> extends Omit<AIAgentOptions<I, O>, "instructions"> {
|
|
20
|
-
objective
|
|
20
|
+
objective?: string | PromptBuilder | Instructions;
|
|
21
21
|
planner?: NestAgentSchema | {
|
|
22
22
|
instructions?: string | PromptBuilder | Instructions;
|
|
23
23
|
};
|
|
@@ -62,7 +62,7 @@ export declare class OrchestratorAgent<I extends Message = Message, O extends Me
|
|
|
62
62
|
* @param options - Configuration options for the Orchestrator Agent
|
|
63
63
|
*/
|
|
64
64
|
constructor(options: OrchestratorAgentOptions<I, O>);
|
|
65
|
-
private objective
|
|
65
|
+
private objective?;
|
|
66
66
|
private planner;
|
|
67
67
|
private worker;
|
|
68
68
|
private completer;
|
|
@@ -60,7 +60,7 @@ class OrchestratorAgent extends core_1.AIAgent {
|
|
|
60
60
|
const valid = await schema.parseAsync(parsed);
|
|
61
61
|
return new OrchestratorAgent({
|
|
62
62
|
...parsed,
|
|
63
|
-
objective: (0, index_js_1.instructionsToPromptBuilder)(valid.objective),
|
|
63
|
+
objective: valid.objective && (0, index_js_1.instructionsToPromptBuilder)(valid.objective),
|
|
64
64
|
planner: valid.planner
|
|
65
65
|
? (await (0, index_js_1.loadNestAgent)(filepath, valid.planner, options, {
|
|
66
66
|
...defaultPlannerOptions,
|
|
@@ -170,10 +170,10 @@ class OrchestratorAgent extends core_1.AIAgent {
|
|
|
170
170
|
const model = this.model || options.model || options.context.model;
|
|
171
171
|
if (!model)
|
|
172
172
|
throw new Error("model is required to run OrchestratorAgent");
|
|
173
|
-
const
|
|
173
|
+
const objective = (await this.objective?.buildPrompt({
|
|
174
174
|
input,
|
|
175
175
|
context: options.context,
|
|
176
|
-
});
|
|
176
|
+
}))?.prompt;
|
|
177
177
|
const executionState = { tasks: [] };
|
|
178
178
|
let iterationCount = 0;
|
|
179
179
|
const maxIterations = this.stateManagement?.maxIterations ?? type_js_1.DEFAULT_MAX_ITERATIONS;
|
|
@@ -193,13 +193,22 @@ class OrchestratorAgent extends core_1.AIAgent {
|
|
|
193
193
|
iterationCount++;
|
|
194
194
|
// Compress state for planner input if needed
|
|
195
195
|
const compressedState = this.compressState(executionState);
|
|
196
|
-
const plan = await this.invokeChildAgent(this.planner, {
|
|
196
|
+
const plan = await this.invokeChildAgent(this.planner, {
|
|
197
|
+
objective,
|
|
198
|
+
executionState: compressedState,
|
|
199
|
+
...(0, type_utils_js_1.pick)(input, this.planner.inputKeys),
|
|
200
|
+
}, { ...options, model, streaming: false });
|
|
197
201
|
if (plan.finished || !plan.nextTask) {
|
|
198
202
|
break;
|
|
199
203
|
}
|
|
200
204
|
const task = plan.nextTask;
|
|
201
205
|
const createdAt = Date.now();
|
|
202
|
-
const taskResult = await this.invokeChildAgent(this.worker, {
|
|
206
|
+
const taskResult = await this.invokeChildAgent(this.worker, {
|
|
207
|
+
objective,
|
|
208
|
+
executionState: compressedState,
|
|
209
|
+
task,
|
|
210
|
+
...(0, type_utils_js_1.pick)(input, this.worker.inputKeys),
|
|
211
|
+
}, { ...options, model, streaming: false })
|
|
203
212
|
.then((res) => {
|
|
204
213
|
if (res.error || res.success === false) {
|
|
205
214
|
return { status: "failed", result: res.result, error: res.error };
|
|
@@ -219,7 +228,11 @@ class OrchestratorAgent extends core_1.AIAgent {
|
|
|
219
228
|
}
|
|
220
229
|
// Compress state for completer input if needed
|
|
221
230
|
const compressedState = this.compressState(executionState);
|
|
222
|
-
yield* await this.invokeChildAgent(this.completer, {
|
|
231
|
+
yield* await this.invokeChildAgent(this.completer, {
|
|
232
|
+
objective,
|
|
233
|
+
executionState: compressedState,
|
|
234
|
+
...(0, type_utils_js_1.pick)(input, this.completer.inputKeys),
|
|
235
|
+
}, { ...options, model, streaming: true });
|
|
223
236
|
}
|
|
224
237
|
}
|
|
225
238
|
exports.OrchestratorAgent = OrchestratorAgent;
|
|
@@ -228,7 +241,7 @@ function getOrchestratorAgentSchema({ filepath }) {
|
|
|
228
241
|
const nestAgentSchema = (0, agent_yaml_js_1.getNestAgentSchema)({ filepath });
|
|
229
242
|
const instructionsSchema = (0, agent_yaml_js_1.getInstructionsSchema)({ filepath });
|
|
230
243
|
return (0, schema_js_1.camelizeSchema)(zod_1.z.object({
|
|
231
|
-
objective: instructionsSchema,
|
|
244
|
+
objective: (0, schema_js_1.optionalize)(instructionsSchema),
|
|
232
245
|
planner: (0, schema_js_1.optionalize)(nestAgentSchema),
|
|
233
246
|
worker: (0, schema_js_1.optionalize)(nestAgentSchema),
|
|
234
247
|
completer: (0, schema_js_1.optionalize)(nestAgentSchema),
|