openbot 0.2.2 → 0.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/README.md +1 -1
- package/dist/agents/agent-creator.js +58 -19
- package/dist/agents/os-agent.js +1 -4
- package/dist/agents/planner-agent.js +32 -0
- package/dist/agents/topic-agent.js +1 -1
- package/dist/architecture/contracts.js +1 -0
- package/dist/architecture/execution-engine.js +151 -0
- package/dist/architecture/intent-classifier.js +26 -0
- package/dist/architecture/planner.js +106 -0
- package/dist/automation-worker.js +121 -0
- package/dist/automations.js +52 -0
- package/dist/cli.js +54 -141
- package/dist/config.js +20 -0
- package/dist/core/agents.js +41 -0
- package/dist/core/delegation.js +124 -0
- package/dist/core/manager.js +73 -0
- package/dist/core/plugins.js +77 -0
- package/dist/core/router.js +40 -0
- package/dist/installers.js +170 -0
- package/dist/marketplace.js +80 -0
- package/dist/open-bot.js +34 -157
- package/dist/orchestrator.js +247 -51
- package/dist/plugins/approval/index.js +107 -3
- package/dist/plugins/brain/index.js +17 -86
- package/dist/plugins/brain/memory.js +1 -1
- package/dist/plugins/brain/prompt.js +8 -13
- package/dist/plugins/brain/types.js +0 -15
- package/dist/plugins/file-system/index.js +8 -8
- package/dist/plugins/llm/context-shaping.js +177 -0
- package/dist/plugins/llm/index.js +223 -49
- package/dist/plugins/memory/index.js +220 -0
- package/dist/plugins/memory/memory.js +122 -0
- package/dist/plugins/memory/prompt.js +55 -0
- package/dist/plugins/memory/types.js +45 -0
- package/dist/plugins/shell/index.js +3 -3
- package/dist/plugins/skills/index.js +9 -9
- package/dist/registry/index.js +1 -4
- package/dist/registry/plugin-loader.js +339 -56
- package/dist/registry/plugin-registry.js +21 -4
- package/dist/registry/ts-agent-loader.js +4 -4
- package/dist/registry/yaml-agent-loader.js +78 -20
- package/dist/runtime/execution-trace.js +41 -0
- package/dist/runtime/intent-routing.js +26 -0
- package/dist/runtime/openbot-runtime.js +354 -0
- package/dist/server.js +549 -31
- package/dist/ui/widgets/approval-card.js +22 -2
- package/dist/ui/widgets/delegation.js +29 -0
- package/dist/version.js +62 -0
- package/package.json +8 -7
package/dist/cli.js
CHANGED
|
@@ -3,168 +3,37 @@ import { Command } from "commander";
|
|
|
3
3
|
import * as readline from "node:readline/promises";
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import { execSync } from "node:child_process";
|
|
7
|
-
import { tmpdir } from "node:os";
|
|
8
6
|
import { saveConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
9
7
|
import { startServer } from "./server.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { getPluginMetadata } from "./registry/plugin-loader.js";
|
|
9
|
+
import { checkGitHubRepo, checkNpmPackage, parsePluginInstallSource, parseAgentInstallSource, installPluginFromSource, installAgentFromSource, } from "./installers.js";
|
|
12
10
|
const program = new Command();
|
|
13
11
|
program
|
|
14
12
|
.name("openbot")
|
|
15
13
|
.description("OpenBot CLI - Secure and easy configuration")
|
|
16
|
-
.version("0.2.
|
|
17
|
-
/**
|
|
18
|
-
* Check if a GitHub repository exists.
|
|
19
|
-
*/
|
|
20
|
-
function checkGitHubRepo(repo) {
|
|
21
|
-
try {
|
|
22
|
-
execSync(`git ls-remote https://github.com/${repo}.git`, { stdio: "ignore" });
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Check if an NPM package exists.
|
|
31
|
-
*/
|
|
32
|
-
function checkNpmPackage(pkg) {
|
|
33
|
-
try {
|
|
34
|
-
execSync(`npm show ${pkg} version`, { stdio: "ignore" });
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Install a plugin from a source (GitHub, local, or NPM).
|
|
43
|
-
*/
|
|
14
|
+
.version("0.2.5");
|
|
44
15
|
async function installPlugin(source, quiet = false) {
|
|
45
|
-
const isGitHub = (source.includes("/") || source.startsWith("github:")) && !source.startsWith("/") && !source.startsWith(".");
|
|
46
|
-
const isNpm = source.startsWith("@") || source.startsWith("npm:");
|
|
47
|
-
const repoUrl = isGitHub
|
|
48
|
-
? (source.startsWith("github:") ? `https://github.com/${source.slice(7)}.git` : `https://github.com/${source}.git`)
|
|
49
|
-
: source;
|
|
50
|
-
const tempDir = path.join(tmpdir(), `openbot-plugin-install-${Date.now()}`);
|
|
51
16
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// 1. Fetch to temp directory
|
|
55
|
-
if (isGitHub) {
|
|
56
|
-
execSync(`git clone --depth 1 ${repoUrl} ${tempDir}`, { stdio: quiet ? "ignore" : "inherit" });
|
|
57
|
-
}
|
|
58
|
-
else if (isNpm) {
|
|
59
|
-
const pkgName = source.startsWith("npm:") ? source.slice(4) : source;
|
|
60
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
61
|
-
execSync(`npm install ${pkgName} --prefix ${tempDir}`, { stdio: quiet ? "ignore" : "inherit" });
|
|
62
|
-
// Move from node_modules to tempDir root for consistency
|
|
63
|
-
const pkgFolder = path.join(tempDir, "node_modules", pkgName);
|
|
64
|
-
const moveTemp = path.join(tmpdir(), `openbot-npm-move-${Date.now()}`);
|
|
65
|
-
await fs.rename(pkgFolder, moveTemp);
|
|
66
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
67
|
-
await fs.rename(moveTemp, tempDir);
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
const absoluteSource = path.resolve(source);
|
|
71
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
72
|
-
execSync(`cp -R ${absoluteSource}/. ${tempDir}`, { stdio: quiet ? "ignore" : "inherit" });
|
|
73
|
-
}
|
|
74
|
-
// 2. Identify name from metadata
|
|
75
|
-
const { name } = await getPluginMetadata(tempDir);
|
|
76
|
-
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
77
|
-
const targetDir = path.join(baseDir, "plugins", name);
|
|
78
|
-
// 3. Move to target directory
|
|
79
|
-
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
|
80
|
-
if (await fs.access(targetDir).then(() => true).catch(() => false)) {
|
|
81
|
-
if (!quiet)
|
|
82
|
-
console.log(`⚠️ Plugin "${name}" already exists. Overwriting...`);
|
|
83
|
-
await fs.rm(targetDir, { recursive: true, force: true });
|
|
84
|
-
}
|
|
85
|
-
await fs.rename(tempDir, targetDir);
|
|
86
|
-
if (!quiet)
|
|
87
|
-
console.log(`✅ Moved to: ${targetDir}`);
|
|
88
|
-
// 4. Prepare
|
|
89
|
-
if (!quiet)
|
|
90
|
-
console.log(`⚙️ Preparing plugin "${name}"...`);
|
|
91
|
-
await ensurePluginReady(targetDir);
|
|
92
|
-
if (!quiet)
|
|
93
|
-
console.log(`\n🎉 Successfully installed plugin: ${name}`);
|
|
17
|
+
const parsed = parsePluginInstallSource(source);
|
|
18
|
+
const name = await installPluginFromSource(parsed, { quiet });
|
|
94
19
|
return name;
|
|
95
20
|
}
|
|
96
21
|
catch (err) {
|
|
97
22
|
if (!quiet)
|
|
98
23
|
console.error("\n❌ Plugin installation failed:", err instanceof Error ? err.message : String(err));
|
|
99
|
-
try {
|
|
100
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
101
|
-
}
|
|
102
|
-
catch { /* ignore */ }
|
|
103
24
|
if (!quiet)
|
|
104
25
|
process.exit(1);
|
|
105
26
|
throw err;
|
|
106
27
|
}
|
|
107
28
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Install an agent from a source (GitHub).
|
|
110
|
-
*/
|
|
111
29
|
async function installAgent(source) {
|
|
112
|
-
const repoUrl = source.startsWith("github:") ? `https://github.com/${source.slice(7)}.git` : `https://github.com/${source}.git`;
|
|
113
|
-
const tempDir = path.join(tmpdir(), `openbot-agent-install-${Date.now()}`);
|
|
114
30
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
execSync(`git clone --depth 1 ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
|
118
|
-
// 2. Identify name from agent.yaml
|
|
119
|
-
const config = await readAgentConfig(tempDir);
|
|
120
|
-
const name = config.name || path.basename(source.replace(".git", ""));
|
|
121
|
-
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
122
|
-
const targetDir = path.join(baseDir, "agents", name);
|
|
123
|
-
// 3. Move to target directory
|
|
124
|
-
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
|
125
|
-
if (await fs.access(targetDir).then(() => true).catch(() => false)) {
|
|
126
|
-
console.log(`⚠️ Agent "${name}" already exists. Overwriting...`);
|
|
127
|
-
await fs.rm(targetDir, { recursive: true, force: true });
|
|
128
|
-
}
|
|
129
|
-
await fs.rename(tempDir, targetDir);
|
|
130
|
-
console.log(`✅ Moved to: ${targetDir}`);
|
|
131
|
-
// 4. Recursive plugin installation
|
|
132
|
-
if (config.plugins && Array.isArray(config.plugins)) {
|
|
133
|
-
for (const pluginItem of config.plugins) {
|
|
134
|
-
const pluginName = typeof pluginItem === "string" ? pluginItem : pluginItem.name;
|
|
135
|
-
// Skip built-in plugins
|
|
136
|
-
if (["shell", "file-system"].includes(pluginName))
|
|
137
|
-
continue;
|
|
138
|
-
// Check if plugin exists locally
|
|
139
|
-
const pluginPath = path.join(baseDir, "plugins", pluginName);
|
|
140
|
-
const existsLocally = await fs.access(pluginPath).then(() => true).catch(() => false);
|
|
141
|
-
if (!existsLocally) {
|
|
142
|
-
console.log(`🔍 Agent needs plugin "${pluginName}". Searching...`);
|
|
143
|
-
// Try GitHub
|
|
144
|
-
const ghRepo = `meetopenbot/plugin-${pluginName}`;
|
|
145
|
-
if (checkGitHubRepo(ghRepo)) {
|
|
146
|
-
await installPlugin(ghRepo);
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
// Try NPM
|
|
150
|
-
const npmPkg = `@melony/plugin-${pluginName}`;
|
|
151
|
-
if (checkNpmPackage(npmPkg)) {
|
|
152
|
-
await installPlugin(npmPkg);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
console.warn(`⚠️ Could not find plugin "${pluginName}" for agent "${name}". You may need to install it manually.`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
console.log(`\n🎉 Successfully installed agent: ${name}`);
|
|
31
|
+
const parsed = parseAgentInstallSource(source);
|
|
32
|
+
const name = await installAgentFromSource(parsed);
|
|
160
33
|
return name;
|
|
161
34
|
}
|
|
162
35
|
catch (err) {
|
|
163
36
|
console.error("\n❌ Agent installation failed:", err instanceof Error ? err.message : String(err));
|
|
164
|
-
try {
|
|
165
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
166
|
-
}
|
|
167
|
-
catch { /* ignore */ }
|
|
168
37
|
process.exit(1);
|
|
169
38
|
}
|
|
170
39
|
}
|
|
@@ -239,10 +108,12 @@ program
|
|
|
239
108
|
}
|
|
240
109
|
// 2. Try as Plugin
|
|
241
110
|
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
111
|
+
const agentPath = path.join(baseDir, "agents", name);
|
|
242
112
|
const pluginPath = path.join(baseDir, "plugins", name);
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
113
|
+
const agentExists = await fs.access(agentPath).then(() => true).catch(() => false);
|
|
114
|
+
const pluginExists = await fs.access(pluginPath).then(() => true).catch(() => false);
|
|
115
|
+
if (agentExists || pluginExists) {
|
|
116
|
+
console.log(`✅ Agent or Plugin "${name}" is already installed locally.`);
|
|
246
117
|
return;
|
|
247
118
|
}
|
|
248
119
|
// Check GitHub Plugin
|
|
@@ -302,4 +173,46 @@ plugin
|
|
|
302
173
|
}
|
|
303
174
|
console.log("------------------------------------------\n");
|
|
304
175
|
});
|
|
176
|
+
const agent = program.command("agent").description("Manage OpenBot agents");
|
|
177
|
+
agent
|
|
178
|
+
.command("install <source>")
|
|
179
|
+
.description("Install a custom agent from GitHub (user/repo) or a local path")
|
|
180
|
+
.action(async (source) => {
|
|
181
|
+
await installAgent(source);
|
|
182
|
+
});
|
|
183
|
+
agent
|
|
184
|
+
.command("list")
|
|
185
|
+
.description("List all installed custom agents")
|
|
186
|
+
.action(async () => {
|
|
187
|
+
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
188
|
+
const agentsDir = path.join(baseDir, "agents");
|
|
189
|
+
try {
|
|
190
|
+
await fs.access(agentsDir);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
console.log("No custom agents found.");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
197
|
+
const agents = [];
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
if (!entry.isDirectory())
|
|
200
|
+
continue;
|
|
201
|
+
if (entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
202
|
+
continue;
|
|
203
|
+
const agentDir = path.join(agentsDir, entry.name);
|
|
204
|
+
const { name, version, description } = await getPluginMetadata(agentDir);
|
|
205
|
+
agents.push({ name, version, description });
|
|
206
|
+
}
|
|
207
|
+
if (agents.length === 0) {
|
|
208
|
+
console.log("No custom agents found.");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
console.log("\n🤖 Installed Custom Agents:");
|
|
212
|
+
console.log("------------------------------------------");
|
|
213
|
+
for (const a of agents) {
|
|
214
|
+
console.log(`${a.name.padEnd(20)} (${a.version}) - ${a.description}`);
|
|
215
|
+
}
|
|
216
|
+
console.log("------------------------------------------\n");
|
|
217
|
+
});
|
|
305
218
|
program.parse();
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
export const DEFAULT_BASE_DIR = "~/.openbot";
|
|
5
|
+
export const DEFAULT_PLUGINS_DIR = "plugins";
|
|
6
|
+
export const DEFAULT_AGENTS_DIR = "agents";
|
|
5
7
|
export function loadConfig() {
|
|
6
8
|
const configPath = path.join(os.homedir(), ".openbot", "config.json");
|
|
7
9
|
if (fs.existsSync(configPath)) {
|
|
@@ -31,3 +33,21 @@ export function isConfigured() {
|
|
|
31
33
|
export function resolvePath(p) {
|
|
32
34
|
return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : path.resolve(p);
|
|
33
35
|
}
|
|
36
|
+
export const DEFAULT_AGENT_MD = `---
|
|
37
|
+
name: Agent
|
|
38
|
+
description: A specialized AI agent
|
|
39
|
+
plugins:
|
|
40
|
+
- shell
|
|
41
|
+
- file-system
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
# Agent Profile
|
|
45
|
+
|
|
46
|
+
You are a specialized AI agent within the OpenBot system.
|
|
47
|
+
Your role is defined by your configuration and the tools you have access to.
|
|
48
|
+
|
|
49
|
+
## Persona
|
|
50
|
+
- Helpful and precise
|
|
51
|
+
- Focused on my specific domain
|
|
52
|
+
- Professional in all interactions
|
|
53
|
+
`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { osAgent } from "../agents/os-agent.js";
|
|
2
|
+
import { agentCreatorAgent } from "../agents/agent-creator.js";
|
|
3
|
+
import { plannerAgent } from "../agents/planner-agent.js";
|
|
4
|
+
import { shellToolDefinitions } from "../plugins/shell/index.js";
|
|
5
|
+
import { fileSystemToolDefinitions } from "../plugins/file-system/index.js";
|
|
6
|
+
import { AgentRegistry, discoverYamlAgents, discoverTsAgents } from "../registry/index.js";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
export async function setupAgentRegistry(resolvedBaseDir, pluginRegistry, model, options) {
|
|
9
|
+
const agentRegistry = new AgentRegistry();
|
|
10
|
+
agentRegistry.register({
|
|
11
|
+
name: "os",
|
|
12
|
+
description: "Handles shell commands and file system operations",
|
|
13
|
+
capabilities: {
|
|
14
|
+
...Object.fromEntries(Object.entries(shellToolDefinitions).map(([k, v]) => [k, v.description])),
|
|
15
|
+
...Object.fromEntries(Object.entries(fileSystemToolDefinitions).map(([k, v]) => [k, v.description])),
|
|
16
|
+
},
|
|
17
|
+
plugin: osAgent({ model }),
|
|
18
|
+
});
|
|
19
|
+
agentRegistry.register({
|
|
20
|
+
name: "agent-creator",
|
|
21
|
+
description: "Helps the user create and configure new custom OpenBot agents via natural language.",
|
|
22
|
+
capabilities: {
|
|
23
|
+
...Object.fromEntries(Object.entries(fileSystemToolDefinitions).map(([k, v]) => [k, v.description])),
|
|
24
|
+
},
|
|
25
|
+
plugin: agentCreatorAgent({ model }),
|
|
26
|
+
});
|
|
27
|
+
agentRegistry.register({
|
|
28
|
+
name: "planner-agent",
|
|
29
|
+
description: "Creates concise execution plans from user intent for OpenBot to run.",
|
|
30
|
+
plugin: plannerAgent({ model }),
|
|
31
|
+
});
|
|
32
|
+
const agentsDir = path.join(resolvedBaseDir, "agents");
|
|
33
|
+
const [yamlAgents, tsAgents] = await Promise.all([
|
|
34
|
+
discoverYamlAgents(agentsDir, pluginRegistry, model, options),
|
|
35
|
+
discoverTsAgents(agentsDir, model, options),
|
|
36
|
+
]);
|
|
37
|
+
for (const agent of [...yamlAgents, ...tsAgents]) {
|
|
38
|
+
agentRegistry.register(agent);
|
|
39
|
+
}
|
|
40
|
+
return agentRegistry;
|
|
41
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { generateId } from "melony";
|
|
2
|
+
export function setupDelegation(builder, agentRuntimes) {
|
|
3
|
+
builder.on("action:delegateTask", async function* (event, context) {
|
|
4
|
+
const { agent: agentName, toolCallId, task, attachments } = event.data;
|
|
5
|
+
const agentRuntime = agentRuntimes.get(agentName);
|
|
6
|
+
// If the agent is not found, return an error
|
|
7
|
+
if (!agentRuntime) {
|
|
8
|
+
yield {
|
|
9
|
+
type: "action:result",
|
|
10
|
+
data: {
|
|
11
|
+
action: "delegateTask",
|
|
12
|
+
result: `Error: Agent "${agentName}" not found.`,
|
|
13
|
+
toolCallId,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const delegationId = `del_${generateId()}`;
|
|
19
|
+
// Signal delegation start for UI
|
|
20
|
+
yield {
|
|
21
|
+
type: "delegation:start",
|
|
22
|
+
meta: { delegationId, agentName },
|
|
23
|
+
data: { agent: agentName, task },
|
|
24
|
+
};
|
|
25
|
+
// Initialize agent isolated state if not present
|
|
26
|
+
const state = context.state;
|
|
27
|
+
if (!state.agentStates)
|
|
28
|
+
state.agentStates = {};
|
|
29
|
+
if (!state.agentStates[agentName])
|
|
30
|
+
state.agentStates[agentName] = {};
|
|
31
|
+
const agentState = state.agentStates[agentName];
|
|
32
|
+
const agentIterator = agentRuntime.run({
|
|
33
|
+
type: "agent:input",
|
|
34
|
+
data: { content: task, attachments },
|
|
35
|
+
}, {
|
|
36
|
+
runId: delegationId,
|
|
37
|
+
state: agentState,
|
|
38
|
+
});
|
|
39
|
+
let lastAgentOutput = "";
|
|
40
|
+
try {
|
|
41
|
+
for await (const agentEvent of agentIterator) {
|
|
42
|
+
// Forward agent events to the main runtime so the user sees progress.
|
|
43
|
+
// We SKIP forwarding 'agent:input' because it triggers the manager's LLM again.
|
|
44
|
+
// Instead, we yield it as 'agent:sub-input' for logging/monitoring.
|
|
45
|
+
if (agentEvent.type === "agent:input") {
|
|
46
|
+
yield {
|
|
47
|
+
...agentEvent,
|
|
48
|
+
type: "agent:sub-input",
|
|
49
|
+
meta: { ...agentEvent.meta, delegationId, agentName },
|
|
50
|
+
};
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// We wrap sub-agent actions to avoid triggering manager handlers if they share names.
|
|
54
|
+
if (agentEvent.type.startsWith("action:") && agentEvent.type !== "action:result") {
|
|
55
|
+
yield {
|
|
56
|
+
...agentEvent,
|
|
57
|
+
type: "agent:sub-action",
|
|
58
|
+
meta: { ...agentEvent.meta, delegationId, agentName },
|
|
59
|
+
data: { ...agentEvent.data, originalType: agentEvent.type },
|
|
60
|
+
};
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// Wrap action results to avoid confusion with manager action results.
|
|
64
|
+
if (agentEvent.type === "action:result") {
|
|
65
|
+
yield {
|
|
66
|
+
...agentEvent,
|
|
67
|
+
type: "agent:sub-action-result",
|
|
68
|
+
meta: { ...agentEvent.meta, delegationId, agentName },
|
|
69
|
+
};
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Wrap usage updates to avoid confusion with manager usage.
|
|
73
|
+
if (agentEvent.type === "usage:update") {
|
|
74
|
+
yield {
|
|
75
|
+
...agentEvent,
|
|
76
|
+
type: "agent:sub-usage",
|
|
77
|
+
meta: { ...agentEvent.meta, delegationId, agentName },
|
|
78
|
+
};
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Pass through other events but tag them with delegationId and agentName in meta
|
|
82
|
+
yield {
|
|
83
|
+
...agentEvent,
|
|
84
|
+
meta: { ...agentEvent.meta, delegationId, agentName },
|
|
85
|
+
};
|
|
86
|
+
// accumulate agent output
|
|
87
|
+
if (agentEvent.type === "agent:output") {
|
|
88
|
+
const agentOutput = agentEvent.data;
|
|
89
|
+
// THIS NEEDS TO BE IMPROVED. WE NEED TO KEEP A SINGLE AGENT OUTPUT VARIABLE.
|
|
90
|
+
const value = agentOutput?.result ?? agentOutput?.content ?? agentOutput?.message;
|
|
91
|
+
if (typeof value === "string") {
|
|
92
|
+
if (lastAgentOutput)
|
|
93
|
+
lastAgentOutput += "\n\n";
|
|
94
|
+
lastAgentOutput += value;
|
|
95
|
+
}
|
|
96
|
+
else if (typeof value === "object" && value !== null) {
|
|
97
|
+
if (lastAgentOutput)
|
|
98
|
+
lastAgentOutput += "\n\n";
|
|
99
|
+
lastAgentOutput += JSON.stringify(value, null, 2);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(`[delegation] Error running agent "${agentName}":`, error);
|
|
106
|
+
lastAgentOutput = `Error executing task: ${error.message}`;
|
|
107
|
+
}
|
|
108
|
+
// Signal delegation end for UI
|
|
109
|
+
yield {
|
|
110
|
+
type: "delegation:end",
|
|
111
|
+
meta: { delegationId, agentName },
|
|
112
|
+
data: { agent: agentName, result: lastAgentOutput || "Task completed." },
|
|
113
|
+
};
|
|
114
|
+
// Feedback the result back to the manager
|
|
115
|
+
yield {
|
|
116
|
+
type: "action:result",
|
|
117
|
+
data: {
|
|
118
|
+
action: "delegateTask",
|
|
119
|
+
result: lastAgentOutput || "Task completed with no output.",
|
|
120
|
+
toolCallId,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { memoryPlugin, memoryToolDefinitions, createMemoryPromptBuilder } from "../plugins/memory/index.js";
|
|
3
|
+
import { topicAgent } from "../agents/topic-agent.js";
|
|
4
|
+
import { llmPlugin } from "../plugins/llm/index.js";
|
|
5
|
+
export function createManagerPlugin(model, resolvedModelId, resolvedBaseDir, registry) {
|
|
6
|
+
const agentNames = registry.getAgentNames();
|
|
7
|
+
const allAgents = registry.getAgents();
|
|
8
|
+
const buildMemoryPrompt = createMemoryPromptBuilder(resolvedBaseDir);
|
|
9
|
+
const agentDescriptions = allAgents
|
|
10
|
+
.map((a) => {
|
|
11
|
+
const tools = a.capabilities
|
|
12
|
+
? Object.entries(a.capabilities)
|
|
13
|
+
.map(([name, desc]) => ` - ${name}: ${desc}`)
|
|
14
|
+
.join("\n")
|
|
15
|
+
: "";
|
|
16
|
+
return `<agent name="${a.name}">
|
|
17
|
+
<description>${a.description}</description>
|
|
18
|
+
${tools ? ` <capabilities>\n${tools}\n </capabilities>` : ""}
|
|
19
|
+
</agent>`;
|
|
20
|
+
})
|
|
21
|
+
.join("\n\n");
|
|
22
|
+
return (builder) => {
|
|
23
|
+
builder
|
|
24
|
+
.use(memoryPlugin({
|
|
25
|
+
baseDir: resolvedBaseDir,
|
|
26
|
+
}))
|
|
27
|
+
.use(topicAgent({ model: model }))
|
|
28
|
+
.use(llmPlugin({
|
|
29
|
+
model: model,
|
|
30
|
+
modelId: resolvedModelId,
|
|
31
|
+
usageScope: "manager",
|
|
32
|
+
system: async (context) => {
|
|
33
|
+
const memoryPrompt = await buildMemoryPrompt(context);
|
|
34
|
+
return `
|
|
35
|
+
|
|
36
|
+
<orchestrator>
|
|
37
|
+
Your goal is to solve user requests by delegating tasks to expert sub-agents.
|
|
38
|
+
|
|
39
|
+
**Directives**:
|
|
40
|
+
1. **Delegate**: Use \`delegateTask\` for any task matching an agent's description.
|
|
41
|
+
2. **Plan**: For multi-step tasks, use \`planner-agent\` first to create a roadmap.
|
|
42
|
+
3. **Context**: Provide a clear, detailed task for the sub-agent. Pass any relevant user attachments.
|
|
43
|
+
4. **Report**: Summarize the sub-agent's work concisely for the user.
|
|
44
|
+
5. **Memory**: Use your memory tools (\`remember\`, \`recall\`) to maintain context across sessions.
|
|
45
|
+
</orchestrator>
|
|
46
|
+
|
|
47
|
+
<agents>
|
|
48
|
+
${agentDescriptions}
|
|
49
|
+
</agents>${memoryPrompt}`;
|
|
50
|
+
},
|
|
51
|
+
promptInputType: "agent:input",
|
|
52
|
+
actionResultInputType: "action:result",
|
|
53
|
+
completionEventType: "agent:output",
|
|
54
|
+
toolDefinitions: {
|
|
55
|
+
...memoryToolDefinitions,
|
|
56
|
+
delegateTask: {
|
|
57
|
+
description: `Delegate a task to a specialized expert agent.`,
|
|
58
|
+
inputSchema: z.object({
|
|
59
|
+
agent: z.enum(agentNames).describe("The name of the agent to use"),
|
|
60
|
+
task: z.string().describe("The task for the agent to perform"),
|
|
61
|
+
attachments: z.array(z.object({
|
|
62
|
+
id: z.string(),
|
|
63
|
+
name: z.string(),
|
|
64
|
+
mimeType: z.string(),
|
|
65
|
+
size: z.number(),
|
|
66
|
+
url: z.string(),
|
|
67
|
+
})).optional().describe("Attachments to pass through to the agent"),
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
}));
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { shellPlugin, shellToolDefinitions } from "../plugins/shell/index.js";
|
|
2
|
+
import { fileSystemPlugin, fileSystemToolDefinitions } from "../plugins/file-system/index.js";
|
|
3
|
+
import { approvalPlugin } from "../plugins/approval/index.js";
|
|
4
|
+
import { osAgent } from "../agents/os-agent.js";
|
|
5
|
+
import { agentCreatorAgent } from "../agents/agent-creator.js";
|
|
6
|
+
import { plannerAgent } from "../agents/planner-agent.js";
|
|
7
|
+
import { PluginRegistry, discoverPlugins } from "../registry/index.js";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
/**
|
|
10
|
+
* Build the unified plugin registry.
|
|
11
|
+
*
|
|
12
|
+
* Registers built-in tools and agents, then discovers community
|
|
13
|
+
* plugins (tools + agents) from ~/.openbot/plugins/.
|
|
14
|
+
*/
|
|
15
|
+
export async function setupPluginRegistry(resolvedBaseDir, model, options) {
|
|
16
|
+
const registry = new PluginRegistry();
|
|
17
|
+
// ── Built-in tools ───────────────────────────────────────────────
|
|
18
|
+
registry.register({
|
|
19
|
+
name: "shell",
|
|
20
|
+
description: "Execute shell commands",
|
|
21
|
+
type: "tool",
|
|
22
|
+
toolDefinitions: shellToolDefinitions,
|
|
23
|
+
plugin: () => shellPlugin({ cwd: process.cwd() }),
|
|
24
|
+
isBuiltIn: true,
|
|
25
|
+
});
|
|
26
|
+
registry.register({
|
|
27
|
+
name: "file-system",
|
|
28
|
+
description: "Read, write, list, and delete files",
|
|
29
|
+
type: "tool",
|
|
30
|
+
toolDefinitions: fileSystemToolDefinitions,
|
|
31
|
+
plugin: () => fileSystemPlugin({ baseDir: "/" }),
|
|
32
|
+
isBuiltIn: true,
|
|
33
|
+
});
|
|
34
|
+
registry.register({
|
|
35
|
+
name: "approval",
|
|
36
|
+
description: "Require user approval for specific actions",
|
|
37
|
+
type: "tool",
|
|
38
|
+
toolDefinitions: {},
|
|
39
|
+
plugin: (opts) => approvalPlugin(opts),
|
|
40
|
+
isBuiltIn: true,
|
|
41
|
+
});
|
|
42
|
+
// ── Built-in agents ──────────────────────────────────────────────
|
|
43
|
+
registry.register({
|
|
44
|
+
name: "os",
|
|
45
|
+
description: "Handles shell commands and file system operations",
|
|
46
|
+
type: "agent",
|
|
47
|
+
capabilities: {
|
|
48
|
+
...Object.fromEntries(Object.entries(shellToolDefinitions).map(([k, v]) => [k, v.description])),
|
|
49
|
+
...Object.fromEntries(Object.entries(fileSystemToolDefinitions).map(([k, v]) => [k, v.description])),
|
|
50
|
+
},
|
|
51
|
+
plugin: osAgent({ model }),
|
|
52
|
+
isBuiltIn: true,
|
|
53
|
+
});
|
|
54
|
+
registry.register({
|
|
55
|
+
name: "agent-creator",
|
|
56
|
+
description: "Helps the user create and update custom OpenBot agents via natural language.",
|
|
57
|
+
type: "agent",
|
|
58
|
+
capabilities: {
|
|
59
|
+
...Object.fromEntries(Object.entries(fileSystemToolDefinitions).map(([k, v]) => [k, v.description])),
|
|
60
|
+
},
|
|
61
|
+
plugin: agentCreatorAgent({ model }),
|
|
62
|
+
isBuiltIn: true,
|
|
63
|
+
});
|
|
64
|
+
registry.register({
|
|
65
|
+
name: "planner-agent",
|
|
66
|
+
description: "Creates concise execution plans from user intent for OpenBot to run.",
|
|
67
|
+
type: "agent",
|
|
68
|
+
plugin: plannerAgent({ model }),
|
|
69
|
+
isBuiltIn: true,
|
|
70
|
+
});
|
|
71
|
+
// ── Custom agents and plugins ────────────────────────────────────
|
|
72
|
+
const agentsDir = path.join(resolvedBaseDir, "agents");
|
|
73
|
+
const pluginsDir = path.join(resolvedBaseDir, "plugins");
|
|
74
|
+
await discoverPlugins(agentsDir, registry, model, options);
|
|
75
|
+
await discoverPlugins(pluginsDir, registry, model, options);
|
|
76
|
+
return registry;
|
|
77
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export async function* runOpenBot(event, context, managerRuntime, agentRuntimes) {
|
|
2
|
+
const { state } = context;
|
|
3
|
+
// Initialize state
|
|
4
|
+
if (!state.messages)
|
|
5
|
+
state.messages = [];
|
|
6
|
+
if (!state.agentStates)
|
|
7
|
+
state.agentStates = {};
|
|
8
|
+
// 1. Direct agent routing (e.g. "@os list files")
|
|
9
|
+
if (event.type === "agent:input") {
|
|
10
|
+
const content = event.data.content;
|
|
11
|
+
if (content?.startsWith("@")) {
|
|
12
|
+
const match = content.match(/^@([a-zA-Z0-9_-]+)\s*(.*)$/);
|
|
13
|
+
if (match) {
|
|
14
|
+
const [, agentName, remaining] = match;
|
|
15
|
+
const runtime = agentRuntimes.get(agentName);
|
|
16
|
+
if (runtime) {
|
|
17
|
+
if (!state.agentStates[agentName])
|
|
18
|
+
state.agentStates[agentName] = {};
|
|
19
|
+
const agentEvent = {
|
|
20
|
+
...event,
|
|
21
|
+
data: {
|
|
22
|
+
content: remaining || "Hello",
|
|
23
|
+
attachments: event.data.attachments,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
yield* runtime.run(agentEvent, {
|
|
27
|
+
runId: context.runId,
|
|
28
|
+
state: state.agentStates[agentName],
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// 2. Default routing: translate user event to manager input
|
|
36
|
+
yield* managerRuntime.run({ ...event, type: "agent:input" }, {
|
|
37
|
+
runId: context.runId,
|
|
38
|
+
state: state,
|
|
39
|
+
});
|
|
40
|
+
}
|