openbot 0.2.3 → 0.2.6

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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/agent-creator.js +58 -19
  3. package/dist/agents/os-agent.js +1 -4
  4. package/dist/agents/planner-agent.js +32 -0
  5. package/dist/agents/topic-agent.js +1 -1
  6. package/dist/architecture/contracts.js +1 -0
  7. package/dist/architecture/execution-engine.js +151 -0
  8. package/dist/architecture/intent-classifier.js +26 -0
  9. package/dist/architecture/planner.js +106 -0
  10. package/dist/automation-worker.js +121 -0
  11. package/dist/automations.js +52 -0
  12. package/dist/cli.js +116 -146
  13. package/dist/config.js +20 -0
  14. package/dist/core/agents.js +41 -0
  15. package/dist/core/delegation.js +124 -0
  16. package/dist/core/manager.js +73 -0
  17. package/dist/core/plugins.js +77 -0
  18. package/dist/core/router.js +40 -0
  19. package/dist/installers.js +156 -0
  20. package/dist/marketplace.js +80 -0
  21. package/dist/open-bot.js +34 -157
  22. package/dist/orchestrator.js +247 -51
  23. package/dist/plugins/approval/index.js +107 -3
  24. package/dist/plugins/brain/index.js +17 -86
  25. package/dist/plugins/brain/memory.js +1 -1
  26. package/dist/plugins/brain/prompt.js +8 -13
  27. package/dist/plugins/brain/types.js +0 -15
  28. package/dist/plugins/file-system/index.js +8 -8
  29. package/dist/plugins/llm/context-shaping.js +177 -0
  30. package/dist/plugins/llm/index.js +223 -49
  31. package/dist/plugins/memory/index.js +220 -0
  32. package/dist/plugins/memory/memory.js +122 -0
  33. package/dist/plugins/memory/prompt.js +55 -0
  34. package/dist/plugins/memory/types.js +45 -0
  35. package/dist/plugins/shell/index.js +3 -3
  36. package/dist/plugins/skills/index.js +9 -9
  37. package/dist/registry/index.js +1 -4
  38. package/dist/registry/plugin-loader.js +361 -56
  39. package/dist/registry/plugin-registry.js +21 -4
  40. package/dist/registry/ts-agent-loader.js +4 -4
  41. package/dist/registry/yaml-agent-loader.js +78 -20
  42. package/dist/runtime/execution-trace.js +41 -0
  43. package/dist/runtime/intent-routing.js +26 -0
  44. package/dist/runtime/openbot-runtime.js +354 -0
  45. package/dist/server.js +513 -41
  46. package/dist/ui/widgets/approval-card.js +22 -2
  47. package/dist/ui/widgets/delegation.js +29 -0
  48. package/dist/version.js +62 -0
  49. package/package.json +4 -1
package/dist/cli.js CHANGED
@@ -3,171 +3,58 @@ 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";
6
+ import { spawn } from "node:child_process";
8
7
  import { saveConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
9
8
  import { startServer } from "./server.js";
10
- import { ensurePluginReady, getPluginMetadata } from "./registry/plugin-loader.js";
11
- import { readAgentConfig } from "./registry/yaml-agent-loader.js";
9
+ import { getPluginMetadata } from "./registry/plugin-loader.js";
10
+ import { checkGitHubRepo, checkNpmPackage, parsePluginInstallSource, parseAgentInstallSource, installPluginFromSource, installAgentFromSource, } from "./installers.js";
12
11
  const program = new Command();
12
+ const REQUIRED_NODE_VERSION = "20.12.0";
13
+ function checkNodeVersion() {
14
+ const [major, minor, patch] = process.versions.node.split(".").map(Number);
15
+ const [reqMajor, reqMinor, reqPatch] = REQUIRED_NODE_VERSION.split(".").map(Number);
16
+ const isOld = major < reqMajor ||
17
+ (major === reqMajor && minor < reqMinor) ||
18
+ (major === reqMajor && minor === reqMinor && patch < reqPatch);
19
+ if (isOld) {
20
+ console.warn(`\n⚠️ WARNING: You are using Node.js ${process.version}.`);
21
+ console.warn(` OpenBot works best with Node.js >=${REQUIRED_NODE_VERSION}.`);
22
+ console.warn(` You may encounter "ERR_REQUIRE_ESM" or other compatibility issues on older versions.\n`);
23
+ }
24
+ }
25
+ checkNodeVersion();
13
26
  program
14
27
  .name("openbot")
15
28
  .description("OpenBot CLI - Secure and easy configuration")
16
- .version("0.2.3");
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
- */
44
- 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()}`);
29
+ .version("0.2.6");
30
+ async function installPlugin(source, id, quiet = false) {
51
31
  try {
52
- if (!quiet)
53
- console.log(`📦 Installing plugin from: ${isNpm ? source : repoUrl}`);
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}`);
32
+ const parsed = parsePluginInstallSource(source);
33
+ const name = await installPluginFromSource(parsed, { quiet, id });
94
34
  return name;
95
35
  }
96
36
  catch (err) {
97
37
  if (!quiet)
98
38
  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
39
  if (!quiet)
104
40
  process.exit(1);
105
41
  throw err;
106
42
  }
107
43
  }
108
- /**
109
- * Install an agent from a source (GitHub).
110
- */
111
- 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()}`);
44
+ async function installAgent(source, id) {
114
45
  try {
115
- console.log(`🤖 Installing agent from: ${repoUrl}`);
116
- // 1. Clone to temp directory
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}`);
46
+ const parsed = parseAgentInstallSource(source);
47
+ const name = await installAgentFromSource(parsed, { id });
160
48
  return name;
161
49
  }
162
50
  catch (err) {
163
51
  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
52
  process.exit(1);
169
53
  }
170
54
  }
55
+ function shellEscape(arg) {
56
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
57
+ }
171
58
  program
172
59
  .command("configure")
173
60
  .description("Configure OpenBot model and settings")
@@ -216,6 +103,8 @@ program
216
103
  console.log("Alternatively, you can set the environment variable:");
217
104
  console.log(provider === "openai" ? " export OPENAI_API_KEY=your-key" : " export ANTHROPIC_API_KEY=your-key");
218
105
  console.log("------------------------------------------");
106
+ console.log("\n🚀 TIP: Use 'openbot up' to start the server and web UI together.");
107
+ console.log("------------------------------------------\n");
219
108
  rl.close();
220
109
  });
221
110
  program
@@ -227,6 +116,43 @@ program
227
116
  .action(async (options) => {
228
117
  await startServer(options);
229
118
  });
119
+ program
120
+ .command("up")
121
+ .description("Start OpenBot server and web dashboard together")
122
+ .option("-p, --port <number>", "Port to listen on")
123
+ .option("--openai-api-key <key>", "OpenAI API Key")
124
+ .option("--anthropic-api-key <key>", "Anthropic API Key")
125
+ .action(async (options) => {
126
+ const serverArgs = ["openbot", "server"];
127
+ if (options.port)
128
+ serverArgs.push("--port", String(options.port));
129
+ if (options.openaiApiKey)
130
+ serverArgs.push("--openai-api-key", options.openaiApiKey);
131
+ if (options.anthropicApiKey)
132
+ serverArgs.push("--anthropic-api-key", options.anthropicApiKey);
133
+ const serverCommand = serverArgs.map(shellEscape).join(" ");
134
+ await new Promise((resolve, reject) => {
135
+ const child = spawn("npx", [
136
+ "-y",
137
+ "concurrently",
138
+ "--kill-others",
139
+ "--names",
140
+ "SERVER,WEBUI",
141
+ "--prefix",
142
+ "{name}",
143
+ "--prefix-colors",
144
+ "blue.bold,green.bold",
145
+ serverCommand,
146
+ "openbot-web",
147
+ ], { stdio: "inherit" });
148
+ child.on("error", reject);
149
+ child.on("exit", (code) => {
150
+ if (typeof code === "number")
151
+ process.exitCode = code;
152
+ resolve();
153
+ });
154
+ });
155
+ });
230
156
  program
231
157
  .command("add <name>")
232
158
  .description("Add an agent or plugin by name (auto-resolves to GitHub/NPM)")
@@ -234,27 +160,29 @@ program
234
160
  // 1. Try as Agent
235
161
  const agentRepo = `meetopenbot/agent-${name}`;
236
162
  if (checkGitHubRepo(agentRepo)) {
237
- await installAgent(agentRepo);
163
+ await installAgent(agentRepo, `agent-${name}`);
238
164
  return;
239
165
  }
240
166
  // 2. Try as Plugin
241
167
  const baseDir = resolvePath(DEFAULT_BASE_DIR);
168
+ const agentPath = path.join(baseDir, "agents", name);
242
169
  const pluginPath = path.join(baseDir, "plugins", name);
243
- const existsLocally = await fs.access(pluginPath).then(() => true).catch(() => false);
244
- if (existsLocally) {
245
- console.log(`✅ Plugin "${name}" is already installed locally.`);
170
+ const agentExists = await fs.access(agentPath).then(() => true).catch(() => false);
171
+ const pluginExists = await fs.access(pluginPath).then(() => true).catch(() => false);
172
+ if (agentExists || pluginExists) {
173
+ console.log(`✅ Agent or Plugin "${name}" is already installed locally.`);
246
174
  return;
247
175
  }
248
176
  // Check GitHub Plugin
249
177
  const pluginGhRepo = `meetopenbot/plugin-${name}`;
250
178
  if (checkGitHubRepo(pluginGhRepo)) {
251
- await installPlugin(pluginGhRepo);
179
+ await installPlugin(pluginGhRepo, `plugin-${name}`);
252
180
  return;
253
181
  }
254
182
  // Check NPM Plugin
255
183
  const pluginNpmPkg = `@melony/plugin-${name}`;
256
184
  if (checkNpmPackage(pluginNpmPkg)) {
257
- await installPlugin(pluginNpmPkg);
185
+ await installPlugin(pluginNpmPkg, `plugin-${name}`);
258
186
  return;
259
187
  }
260
188
  console.error(`❌ Could not find agent or plugin named "${name}" in official repositories.`);
@@ -302,4 +230,46 @@ plugin
302
230
  }
303
231
  console.log("------------------------------------------\n");
304
232
  });
233
+ const agent = program.command("agent").description("Manage OpenBot agents");
234
+ agent
235
+ .command("install <source>")
236
+ .description("Install a custom agent from GitHub (user/repo) or a local path")
237
+ .action(async (source) => {
238
+ await installAgent(source);
239
+ });
240
+ agent
241
+ .command("list")
242
+ .description("List all installed custom agents")
243
+ .action(async () => {
244
+ const baseDir = resolvePath(DEFAULT_BASE_DIR);
245
+ const agentsDir = path.join(baseDir, "agents");
246
+ try {
247
+ await fs.access(agentsDir);
248
+ }
249
+ catch {
250
+ console.log("No custom agents found.");
251
+ return;
252
+ }
253
+ const entries = await fs.readdir(agentsDir, { withFileTypes: true });
254
+ const agents = [];
255
+ for (const entry of entries) {
256
+ if (!entry.isDirectory())
257
+ continue;
258
+ if (entry.name.startsWith(".") || entry.name.startsWith("_"))
259
+ continue;
260
+ const agentDir = path.join(agentsDir, entry.name);
261
+ const { name, version, description } = await getPluginMetadata(agentDir);
262
+ agents.push({ name, version, description });
263
+ }
264
+ if (agents.length === 0) {
265
+ console.log("No custom agents found.");
266
+ return;
267
+ }
268
+ console.log("\n🤖 Installed Custom Agents:");
269
+ console.log("------------------------------------------");
270
+ for (const a of agents) {
271
+ console.log(`${a.name.padEnd(20)} (${a.version}) - ${a.description}`);
272
+ }
273
+ console.log("------------------------------------------\n");
274
+ });
305
275
  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(pluginsDir, registry, model, options);
75
+ await discoverPlugins(agentsDir, registry, model, options);
76
+ return registry;
77
+ }