openbot 0.1.22 → 0.1.24
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/dist/agents/os-agent.js +0 -4
- package/dist/agents/topic-agent.js +32 -0
- package/dist/cli.js +115 -9
- package/dist/handlers/settings.js +29 -0
- package/dist/models.js +1 -8
- package/dist/open-bot.js +72 -38
- package/dist/plugins/brain/identity.js +5 -4
- package/dist/plugins/brain/index.js +4 -1
- package/dist/plugins/brain/prompt.js +7 -7
- package/dist/plugins/brain/types.js +1 -1
- package/dist/plugins/file-system/index.js +4 -0
- package/dist/plugins/llm/index.js +10 -2
- package/dist/plugins/shell/index.js +4 -0
- package/dist/plugins/skills/index.js +9 -0
- package/dist/registry/index.js +1 -0
- package/dist/registry/plugin-loader.js +100 -0
- package/dist/registry/yaml-agent-loader.js +61 -18
- package/dist/server.js +0 -2
- package/dist/session.js +18 -8
- package/dist/ui/layout.js +3 -2
- package/dist/ui/settings.js +70 -39
- package/dist/ui/sidebar.js +26 -25
- package/package.json +2 -3
package/dist/agents/os-agent.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { llmPlugin } from "../plugins/llm/index.js";
|
|
2
2
|
import { shellPlugin, shellToolDefinitions } from "../plugins/shell/index.js";
|
|
3
|
-
import { shellUIPlugin } from "../plugins/shell/ui.js";
|
|
4
3
|
import { fileSystemPlugin, fileSystemToolDefinitions } from "../plugins/file-system/index.js";
|
|
5
|
-
import { fileSystemUIPlugin } from "../plugins/file-system/ui.js";
|
|
6
4
|
const DEFAULT_SYSTEM_PROMPT = `You are an OS Agent with access to the shell and file system.
|
|
7
5
|
Your job is to help the user with file operations and command execution.
|
|
8
6
|
You can read, write, list, and delete files, as well as execute shell commands.
|
|
@@ -12,9 +10,7 @@ export const osAgent = (options) => (builder) => {
|
|
|
12
10
|
const { model, cwd = process.cwd(), systemPrompt = DEFAULT_SYSTEM_PROMPT } = options;
|
|
13
11
|
builder
|
|
14
12
|
.use(shellPlugin({ cwd }))
|
|
15
|
-
.use(shellUIPlugin())
|
|
16
13
|
.use(fileSystemPlugin({ baseDir: "/" }))
|
|
17
|
-
.use(fileSystemUIPlugin())
|
|
18
14
|
.use(llmPlugin({
|
|
19
15
|
model,
|
|
20
16
|
system: systemPrompt,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { generateText } from "ai";
|
|
2
|
+
export const topicAgent = (options) => (builder) => {
|
|
3
|
+
builder.on("manager:completion", async function* (event, { state }) {
|
|
4
|
+
// Only title if it doesn't have one and there's history
|
|
5
|
+
if (state.title || !state.messages || state.messages.length === 0) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
// Don't title if there are too few messages
|
|
9
|
+
if (state.messages.length < 2) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const { text } = await generateText({
|
|
14
|
+
model: options.model,
|
|
15
|
+
system: "You are a Topic Agent. Create a very concise (3-5 words) title for the conversation based on the user's intent. Do not use quotes or special characters.",
|
|
16
|
+
prompt: `Analyze these messages and provide a title: ${JSON.stringify(state.messages.slice(0, 6))}`,
|
|
17
|
+
});
|
|
18
|
+
const newTitle = text.replace(/["']/g, "").trim();
|
|
19
|
+
if (newTitle) {
|
|
20
|
+
state.title = newTitle;
|
|
21
|
+
// Notify the client to refresh the sessions list in the sidebar
|
|
22
|
+
yield {
|
|
23
|
+
type: "client:invalidate",
|
|
24
|
+
data: { tags: ["sessions"] }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error("[topic-agent] Failed to generate title:", error);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import * as readline from "node:readline/promises";
|
|
4
|
-
import
|
|
4
|
+
import * as fs from "node:fs/promises";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { saveConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
5
9
|
import { startServer } from "./server.js";
|
|
10
|
+
import { ensurePluginReady } from "./registry/plugin-loader.js";
|
|
6
11
|
const program = new Command();
|
|
7
12
|
program
|
|
8
13
|
.name("openbot")
|
|
9
14
|
.description("OpenBot CLI - Secure and easy configuration")
|
|
10
|
-
.version("0.1.
|
|
15
|
+
.version("0.1.23");
|
|
11
16
|
program
|
|
12
17
|
.command("configure")
|
|
13
18
|
.description("Configure OpenBot model and settings")
|
|
@@ -20,13 +25,13 @@ program
|
|
|
20
25
|
console.log("🍎 OpenBot Configuration");
|
|
21
26
|
console.log("------------------------------------------");
|
|
22
27
|
const models = [
|
|
23
|
-
{ name: "GPT-5 Nano (OpenAI)", value: "openai
|
|
24
|
-
{ name: "GPT-4o (OpenAI)", value: "openai
|
|
25
|
-
{ name: "GPT-4o-mini (OpenAI)", value: "openai
|
|
26
|
-
{ name: "Claude Opus 4.5 (Anthropic)", value: "anthropic
|
|
27
|
-
{ name: "Claude Sonnet 4.5 (Anthropic)", value: "anthropic
|
|
28
|
-
{ name: "Claude 3.7 Sonnet (Anthropic)", value: "anthropic
|
|
29
|
-
{ name: "Claude 3.5 Sonnet (Anthropic)", value: "anthropic
|
|
28
|
+
{ name: "GPT-5 Nano (OpenAI)", value: "openai/gpt-5-nano" },
|
|
29
|
+
{ name: "GPT-4o (OpenAI)", value: "openai/gpt-4o" },
|
|
30
|
+
{ name: "GPT-4o-mini (OpenAI)", value: "openai/gpt-4o-mini" },
|
|
31
|
+
{ name: "Claude Opus 4.5 (Anthropic)", value: "anthropic/claude-opus-4-5-20251101" },
|
|
32
|
+
{ name: "Claude Sonnet 4.5 (Anthropic)", value: "anthropic/claude-sonnet-4-5-20250929" },
|
|
33
|
+
{ name: "Claude 3.7 Sonnet (Anthropic)", value: "anthropic/claude-3-7-sonnet-20250219" },
|
|
34
|
+
{ name: "Claude 3.5 Sonnet (Anthropic)", value: "anthropic/claude-3-5-sonnet-20240620" },
|
|
30
35
|
];
|
|
31
36
|
console.log("Please choose a model:");
|
|
32
37
|
models.forEach((m, i) => console.log(`${i + 1}) ${m.name}`));
|
|
@@ -67,4 +72,105 @@ program
|
|
|
67
72
|
.action(async (options) => {
|
|
68
73
|
await startServer(options);
|
|
69
74
|
});
|
|
75
|
+
const plugin = program.command("plugin").description("Manage OpenBot plugins");
|
|
76
|
+
plugin
|
|
77
|
+
.command("install <source>")
|
|
78
|
+
.description("Install a shared plugin from GitHub (user/repo) or a local path")
|
|
79
|
+
.action(async (source) => {
|
|
80
|
+
const isGitHub = source.includes("/") && !source.startsWith("/") && !source.startsWith(".");
|
|
81
|
+
const repoUrl = isGitHub ? `https://github.com/${source}.git` : source;
|
|
82
|
+
const tempDir = path.join(tmpdir(), `openbot-plugin-install-${Date.now()}`);
|
|
83
|
+
try {
|
|
84
|
+
console.log(`📦 Installing plugin from: ${repoUrl}`);
|
|
85
|
+
// 1. Clone or copy to temp directory
|
|
86
|
+
if (isGitHub) {
|
|
87
|
+
execSync(`git clone --depth 1 ${repoUrl} ${tempDir}`, { stdio: "inherit" });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const absoluteSource = path.resolve(source);
|
|
91
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
92
|
+
execSync(`cp -R ${absoluteSource}/. ${tempDir}`, { stdio: "inherit" });
|
|
93
|
+
}
|
|
94
|
+
// 2. Identify name from package.json
|
|
95
|
+
let name = path.basename(source.replace(".git", ""));
|
|
96
|
+
const pkgPath = path.join(tempDir, "package.json");
|
|
97
|
+
if (await fs.access(pkgPath).then(() => true).catch(() => false)) {
|
|
98
|
+
try {
|
|
99
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
100
|
+
if (pkg.name)
|
|
101
|
+
name = pkg.name.split("/").pop(); // Use last part of scoped names
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Fallback to source basename
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
108
|
+
const targetDir = path.join(baseDir, "plugins", name);
|
|
109
|
+
// 3. Move to target directory
|
|
110
|
+
await fs.mkdir(path.dirname(targetDir), { recursive: true });
|
|
111
|
+
if (await fs.access(targetDir).then(() => true).catch(() => false)) {
|
|
112
|
+
console.log(`⚠️ Plugin "${name}" already exists. Overwriting...`);
|
|
113
|
+
await fs.rm(targetDir, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
await fs.rename(tempDir, targetDir);
|
|
116
|
+
console.log(`✅ Moved to: ${targetDir}`);
|
|
117
|
+
// 4. Prepare
|
|
118
|
+
console.log(`⚙️ Preparing plugin "${name}"...`);
|
|
119
|
+
await ensurePluginReady(targetDir);
|
|
120
|
+
console.log(`\n🎉 Successfully installed plugin: ${name}`);
|
|
121
|
+
console.log(`This plugin is now available to all agents.`);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error("\n❌ Plugin installation failed:", err instanceof Error ? err.message : String(err));
|
|
125
|
+
try {
|
|
126
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
catch { /* ignore */ }
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
plugin
|
|
133
|
+
.command("list")
|
|
134
|
+
.description("List all installed shared plugins")
|
|
135
|
+
.action(async () => {
|
|
136
|
+
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
137
|
+
const pluginsDir = path.join(baseDir, "plugins");
|
|
138
|
+
try {
|
|
139
|
+
await fs.access(pluginsDir);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
console.log("No shared plugins found.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
|
|
146
|
+
const plugins = [];
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
if (!entry.isDirectory())
|
|
149
|
+
continue;
|
|
150
|
+
if (entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
151
|
+
continue;
|
|
152
|
+
const pkgPath = path.join(pluginsDir, entry.name, "package.json");
|
|
153
|
+
let description = "No description";
|
|
154
|
+
let version = "0.0.0";
|
|
155
|
+
try {
|
|
156
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
157
|
+
description = pkg.description || description;
|
|
158
|
+
version = pkg.version || version;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Use defaults
|
|
162
|
+
}
|
|
163
|
+
plugins.push({ name: entry.name, version, description });
|
|
164
|
+
}
|
|
165
|
+
if (plugins.length === 0) {
|
|
166
|
+
console.log("No shared plugins found.");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
console.log("\n🔌 Installed Shared Plugins:");
|
|
170
|
+
console.log("------------------------------------------");
|
|
171
|
+
for (const p of plugins) {
|
|
172
|
+
console.log(`${p.name.padEnd(20)} (${p.version}) - ${p.description}`);
|
|
173
|
+
}
|
|
174
|
+
console.log("------------------------------------------\n");
|
|
175
|
+
});
|
|
70
176
|
program.parse();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { saveConfig } from "../config.js";
|
|
2
|
+
import { tabOnlyUI } from "../ui/layout.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handle settings updates (like API keys)
|
|
5
|
+
*/
|
|
6
|
+
export async function* updateSettingsHandler(event, { state }) {
|
|
7
|
+
const { openai_api_key, anthropic_api_key, model } = event.data;
|
|
8
|
+
const updates = {};
|
|
9
|
+
if (model) {
|
|
10
|
+
updates.model = model.trim();
|
|
11
|
+
}
|
|
12
|
+
if (openai_api_key && openai_api_key !== "••••••••••••••••") {
|
|
13
|
+
updates.openaiApiKey = openai_api_key.trim();
|
|
14
|
+
}
|
|
15
|
+
if (anthropic_api_key && anthropic_api_key !== "••••••••••••••••") {
|
|
16
|
+
updates.anthropicApiKey = anthropic_api_key.trim();
|
|
17
|
+
}
|
|
18
|
+
if (Object.keys(updates).length > 0) {
|
|
19
|
+
saveConfig(updates);
|
|
20
|
+
// Refresh the settings UI to show the "saved" state (masking the keys)
|
|
21
|
+
yield {
|
|
22
|
+
type: "ui",
|
|
23
|
+
meta: {
|
|
24
|
+
type: "content",
|
|
25
|
+
},
|
|
26
|
+
data: await tabOnlyUI({ tab: "settings" })
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/models.js
CHANGED
|
@@ -3,16 +3,9 @@ import { anthropic } from "@ai-sdk/anthropic";
|
|
|
3
3
|
import { loadConfig } from "./config.js";
|
|
4
4
|
/**
|
|
5
5
|
* Parse model string to extract provider and model ID
|
|
6
|
-
* Supports formats: "provider
|
|
6
|
+
* Supports formats: "provider/model" or just "model" (defaults to openai)
|
|
7
7
|
*/
|
|
8
8
|
export function parseModelString(modelString) {
|
|
9
|
-
// Check for provider:model format
|
|
10
|
-
if (modelString.includes(":")) {
|
|
11
|
-
const [provider, modelId] = modelString.split(":");
|
|
12
|
-
if (provider === "openai" || provider === "anthropic") {
|
|
13
|
-
return { provider: provider, modelId };
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
9
|
// Check for provider/model format
|
|
17
10
|
if (modelString.includes("/")) {
|
|
18
11
|
const [provider, modelId] = modelString.split("/");
|
package/dist/open-bot.js
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import { melony } from "melony";
|
|
2
2
|
import { osAgent } from "./agents/os-agent.js";
|
|
3
|
-
import {
|
|
4
|
-
import { browserUIPlugin } from "./plugins/browser/ui.js";
|
|
3
|
+
import { topicAgent } from "./agents/topic-agent.js";
|
|
5
4
|
import { brainPlugin, brainToolDefinitions, createBrainPromptBuilder } from "./plugins/brain/index.js";
|
|
6
|
-
import { brainUIPlugin } from "./plugins/brain/ui.js";
|
|
7
5
|
import { llmPlugin } from "./plugins/llm/index.js";
|
|
8
6
|
import { initHandler } from "./handlers/init.js";
|
|
9
7
|
import { sessionChangeHandler } from "./handlers/session-change.js";
|
|
10
8
|
import { tabChangeHandler } from "./handlers/tab-change.js";
|
|
9
|
+
import { updateSettingsHandler } from "./handlers/settings.js";
|
|
11
10
|
import { loadConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
12
11
|
import { createModel } from "./models.js";
|
|
13
12
|
import path from "node:path";
|
|
14
13
|
import { z } from "zod";
|
|
15
14
|
// Plugin imports for the registry
|
|
16
15
|
import { shellPlugin, shellToolDefinitions } from "./plugins/shell/index.js";
|
|
17
|
-
import { shellUIPlugin } from "./plugins/shell/ui.js";
|
|
18
16
|
import { fileSystemPlugin, fileSystemToolDefinitions } from "./plugins/file-system/index.js";
|
|
19
|
-
import { fileSystemUIPlugin } from "./plugins/file-system/ui.js";
|
|
20
|
-
import { browserPlugin, browserToolDefinitions } from "./plugins/browser/index.js";
|
|
21
17
|
// Registry
|
|
22
|
-
import { PluginRegistry, AgentRegistry, discoverYamlAgents } from "./registry/index.js";
|
|
18
|
+
import { PluginRegistry, AgentRegistry, discoverYamlAgents, loadPluginsFromDir } from "./registry/index.js";
|
|
23
19
|
/**
|
|
24
20
|
* Create the OpenBot manager agent.
|
|
25
21
|
*
|
|
@@ -43,44 +39,40 @@ export async function createOpenBot(options) {
|
|
|
43
39
|
description: "Execute shell commands",
|
|
44
40
|
toolDefinitions: shellToolDefinitions,
|
|
45
41
|
factory: () => shellPlugin({ cwd: process.cwd() }),
|
|
46
|
-
uiFactory: () => shellUIPlugin(),
|
|
47
42
|
});
|
|
48
43
|
pluginRegistry.register({
|
|
49
44
|
name: "file-system",
|
|
50
45
|
description: "Read, write, list, and delete files",
|
|
51
46
|
toolDefinitions: fileSystemToolDefinitions,
|
|
52
47
|
factory: () => fileSystemPlugin({ baseDir: "/" }),
|
|
53
|
-
uiFactory: () => fileSystemUIPlugin(),
|
|
54
|
-
});
|
|
55
|
-
pluginRegistry.register({
|
|
56
|
-
name: "browser",
|
|
57
|
-
description: "Browse the web and interact with pages",
|
|
58
|
-
toolDefinitions: browserToolDefinitions,
|
|
59
|
-
factory: () => browserPlugin({
|
|
60
|
-
headless: true,
|
|
61
|
-
userDataDir,
|
|
62
|
-
channel: "chrome",
|
|
63
|
-
model: model,
|
|
64
|
-
}),
|
|
65
|
-
uiFactory: () => browserUIPlugin(),
|
|
66
48
|
});
|
|
49
|
+
// ─── Shared Plugins ──────────────────────────────────────────────
|
|
50
|
+
// Load community/user plugins from ~/.openbot/plugins/
|
|
51
|
+
const sharedPlugins = await loadPluginsFromDir(path.join(resolvedBaseDir, "plugins"));
|
|
52
|
+
for (const p of sharedPlugins) {
|
|
53
|
+
pluginRegistry.register(p);
|
|
54
|
+
console.log(`[plugins] Loaded shared plugin: ${p.name}`);
|
|
55
|
+
}
|
|
67
56
|
// ─── Agent Registry ──────────────────────────────────────────────
|
|
68
57
|
// Register built-in agents, then discover YAML agents from ~/.openbot/agents/.
|
|
69
58
|
const agentRegistry = new AgentRegistry();
|
|
70
59
|
agentRegistry.register({
|
|
71
60
|
name: "os",
|
|
72
61
|
description: "Handles shell commands and file system operations",
|
|
62
|
+
capabilities: {
|
|
63
|
+
...Object.fromEntries(Object.entries(shellToolDefinitions).map(([k, v]) => [k, v.description])),
|
|
64
|
+
...Object.fromEntries(Object.entries(fileSystemToolDefinitions).map(([k, v]) => [
|
|
65
|
+
k,
|
|
66
|
+
v.description,
|
|
67
|
+
])),
|
|
68
|
+
},
|
|
73
69
|
plugin: osAgent({ model: model }),
|
|
74
70
|
});
|
|
75
71
|
agentRegistry.register({
|
|
76
|
-
name: "
|
|
77
|
-
description: "
|
|
78
|
-
plugin:
|
|
79
|
-
|
|
80
|
-
headless: true,
|
|
81
|
-
userDataDir,
|
|
82
|
-
channel: "chrome",
|
|
83
|
-
}),
|
|
72
|
+
name: "topic",
|
|
73
|
+
description: "Automatically titles threads",
|
|
74
|
+
plugin: topicAgent({ model: model }),
|
|
75
|
+
subscribe: ["manager:completion"],
|
|
84
76
|
});
|
|
85
77
|
// Discover community / user agents from ~/.openbot/agents/
|
|
86
78
|
const yamlAgents = await discoverYamlAgents(path.join(resolvedBaseDir, "agents"), pluginRegistry, model, options);
|
|
@@ -94,35 +86,76 @@ export async function createOpenBot(options) {
|
|
|
94
86
|
// 1. Register all agent plugins
|
|
95
87
|
for (const agent of allAgents) {
|
|
96
88
|
app.use(agent.plugin);
|
|
89
|
+
// Choreography bridge: Auto-wire subscriptions
|
|
90
|
+
if (agent.subscribe) {
|
|
91
|
+
for (const eventType of agent.subscribe) {
|
|
92
|
+
app.on(eventType, async function* (event, { state }) {
|
|
93
|
+
// Avoid self-triggering if the event has agent meta
|
|
94
|
+
if (event.meta?.agent === agent.name)
|
|
95
|
+
return;
|
|
96
|
+
yield {
|
|
97
|
+
type: `agent:${agent.name}:input`,
|
|
98
|
+
data: {
|
|
99
|
+
content: `Event observed: ${event.type}\nData: ${JSON.stringify(event.data)}`,
|
|
100
|
+
},
|
|
101
|
+
meta: {
|
|
102
|
+
background: true,
|
|
103
|
+
agent: agent.name
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
97
109
|
}
|
|
98
110
|
// 2. Register global plugins (brain, UI, etc.)
|
|
99
111
|
const buildBrainPrompt = createBrainPromptBuilder(baseDir);
|
|
100
112
|
app
|
|
101
|
-
.use(browserUIPlugin())
|
|
102
113
|
.use(brainPlugin({
|
|
103
114
|
baseDir: resolvedBaseDir,
|
|
104
115
|
allowSoulModification: false,
|
|
105
|
-
}))
|
|
106
|
-
.use(brainUIPlugin());
|
|
116
|
+
}));
|
|
107
117
|
// 3. Build dynamic delegation tool from the agent registry
|
|
108
118
|
const agentDescriptions = allAgents
|
|
109
|
-
.map((a) =>
|
|
110
|
-
.
|
|
119
|
+
.map((a) => {
|
|
120
|
+
const tools = a.capabilities
|
|
121
|
+
? Object.entries(a.capabilities)
|
|
122
|
+
.map(([name, desc]) => ` - ${name}: ${desc}`)
|
|
123
|
+
.join("\n")
|
|
124
|
+
: "";
|
|
125
|
+
return `- **${a.name}**: ${a.description}${tools ? `\n Capabilities:\n${tools}` : ""}`;
|
|
126
|
+
})
|
|
127
|
+
.join("\n\n");
|
|
111
128
|
app.use(llmPlugin({
|
|
112
129
|
model: model,
|
|
113
130
|
system: async (context) => {
|
|
114
131
|
const [brainPrompt] = await Promise.all([
|
|
115
132
|
buildBrainPrompt(context),
|
|
116
133
|
]);
|
|
117
|
-
return `${brainPrompt}
|
|
134
|
+
return `${brainPrompt}
|
|
135
|
+
|
|
136
|
+
## Delegation & Specialized Agents
|
|
137
|
+
You are the **Manager Agent**. Your primary role is to orchestrate tasks by delegating them to specialized agents when appropriate.
|
|
138
|
+
If a task falls outside your core capabilities (memory and orchestration), you **MUST** use the \`delegateTask\` tool.
|
|
139
|
+
|
|
140
|
+
### Available Agents:
|
|
141
|
+
${agentDescriptions}
|
|
142
|
+
|
|
143
|
+
### Delegation Guidelines:
|
|
144
|
+
1. **Choose the Best Expert**: Analyze the user's request and select the agent whose description most closely matches the required expertise.
|
|
145
|
+
2. **Task Description**: When delegating, provide a clear and detailed task description. Include any context the agent might need to succeed.
|
|
146
|
+
3. **No "I Can't"**: If an agent is available that can handle a request, do not tell the user you cannot do it. Simply delegate.
|
|
147
|
+
4. **Summary**: Once an agent returns a result, summarize the findings or actions for the user.
|
|
148
|
+
|
|
149
|
+
Example: If the user asks to "check the weather", and you see a 'browser' agent, delegate the task to it.`;
|
|
118
150
|
},
|
|
151
|
+
completionEventType: "manager:completion",
|
|
119
152
|
toolDefinitions: {
|
|
120
153
|
...brainToolDefinitions,
|
|
121
154
|
delegateTask: {
|
|
122
|
-
description: `Delegate a task to a
|
|
155
|
+
description: `Delegate a specialized task to another agent. Use this whenever a task matches the capabilities of one of the available agents.`,
|
|
123
156
|
inputSchema: z.object({
|
|
124
157
|
agent: z.enum(agentNames).describe("The specialized agent to use"),
|
|
125
|
-
task: z.string().describe("The task description"),
|
|
158
|
+
task: z.string().describe("The detailed task description for the agent"),
|
|
126
159
|
}),
|
|
127
160
|
},
|
|
128
161
|
},
|
|
@@ -161,6 +194,7 @@ export async function createOpenBot(options) {
|
|
|
161
194
|
app
|
|
162
195
|
.on("init", initHandler)
|
|
163
196
|
.on("sessionChange", sessionChangeHandler)
|
|
164
|
-
.on("tabChange", tabChangeHandler)
|
|
197
|
+
.on("tabChange", tabChangeHandler)
|
|
198
|
+
.on("action:updateSettings", updateSettingsHandler);
|
|
165
199
|
return app;
|
|
166
200
|
}
|
|
@@ -17,17 +17,18 @@ const DEFAULT_SOUL = `# Soul
|
|
|
17
17
|
`;
|
|
18
18
|
const DEFAULT_IDENTITY = `# Identity
|
|
19
19
|
|
|
20
|
-
I am
|
|
20
|
+
I am the Manager Agent and central orchestrator of this AI system. My name and specific personality are defined by the user in this IDENTITY.md file.
|
|
21
21
|
|
|
22
22
|
## Personality
|
|
23
23
|
- Friendly and approachable
|
|
24
24
|
- Technically competent
|
|
25
25
|
- Eager to learn and adapt
|
|
26
|
+
- Professional manager and delegator
|
|
26
27
|
|
|
27
28
|
## Capabilities
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
29
|
+
- Task Orchestration & Delegation
|
|
30
|
+
- Long-term Memory & Knowledge Management
|
|
31
|
+
- Executing specialized tasks via expert agents (Web, OS, etc.)
|
|
31
32
|
- Self-modification and learning
|
|
32
33
|
`;
|
|
33
34
|
// --- Factory ---
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ui } from "@melony/ui-kit/server";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { createIdentityModule } from "./identity.js";
|
|
@@ -6,7 +7,6 @@ import { buildBrainPrompt } from "./prompt.js";
|
|
|
6
7
|
// Re-exports
|
|
7
8
|
export { brainToolDefinitions } from "./types.js";
|
|
8
9
|
export { buildBrainPrompt } from "./prompt.js";
|
|
9
|
-
// --- Helpers ---
|
|
10
10
|
function expandPath(p) {
|
|
11
11
|
if (p.startsWith("~/")) {
|
|
12
12
|
return path.join(process.env.HOME || "", p.slice(2));
|
|
@@ -265,5 +265,8 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
});
|
|
268
|
+
builder.on("brain:status", async function* (event) {
|
|
269
|
+
yield ui.event(ui.status(event.data.message, event.data.severity));
|
|
270
|
+
});
|
|
268
271
|
};
|
|
269
272
|
export default brainPlugin;
|
|
@@ -18,26 +18,26 @@ export async function buildBrainPrompt(baseDir, modules, context) {
|
|
|
18
18
|
// 1. Environment context
|
|
19
19
|
const now = new Date();
|
|
20
20
|
parts.push(`## Environment
|
|
21
|
-
You are
|
|
21
|
+
You are the **Manager Agent**, the central orchestrator of this system.
|
|
22
22
|
- **Current Time**: ${now.toLocaleString()} (${Intl.DateTimeFormat().resolvedOptions().timeZone})
|
|
23
23
|
- **Current Working Directory (CWD)**: ${currentCwd}
|
|
24
24
|
- **System Access**: You have access to the entire file system (root: /).
|
|
25
25
|
- **Bot Home (Internal State)**: ${baseDir}
|
|
26
26
|
|
|
27
|
+
### Delegation Policy:
|
|
28
|
+
You are designed to delegate specialized tasks to expert agents. Analyze the "Available Agents" list provided in your instructions and delegate whenever a user request matches an agent's expertise. You should not claim you cannot perform a task if a suitable agent is available.
|
|
29
|
+
|
|
27
30
|
### Path Rules:
|
|
28
31
|
1. **Shell Commands**: All commands (executeCommand) run in the CWD: ${currentCwd}.
|
|
29
32
|
2. **File Operations**: Relative paths in readFile, writeFile, listFiles, etc. resolve against the CWD.
|
|
30
|
-
3. **Changing Directory**: Use \`cd <path>\` in executeCommand to move. Your CWD is persisted across turns
|
|
31
|
-
4. **Skills/Memory**: To access your own skills and memory, use absolute paths starting with "${baseDir}/".
|
|
32
|
-
|
|
33
|
-
When you want to execute skill scripts, always use the full path to the skill directory.`);
|
|
33
|
+
3. **Changing Directory**: Use \`cd <path>\` in executeCommand to move. Your CWD is persisted across turns.`);
|
|
34
34
|
// 2. Identity (small, always included)
|
|
35
35
|
const soul = await modules.identity.getSoul();
|
|
36
36
|
if (soul)
|
|
37
|
-
parts.push(soul);
|
|
37
|
+
parts.push(`<soul>\n${soul}\n</soul>`);
|
|
38
38
|
const identity = await modules.identity.getIdentity();
|
|
39
39
|
if (identity)
|
|
40
|
-
parts.push(identity);
|
|
40
|
+
parts.push(`<identity>\n${identity}\n</identity>`);
|
|
41
41
|
// 3. Recent memories (lean — just a few to keep context fresh)
|
|
42
42
|
const recentFacts = await modules.memory.getRecentFacts(3);
|
|
43
43
|
if (recentFacts.length > 0) {
|
|
@@ -44,7 +44,7 @@ export const brainToolDefinitions = {
|
|
|
44
44
|
},
|
|
45
45
|
// Identity tools
|
|
46
46
|
updateIdentity: {
|
|
47
|
-
description: "Update your identity file to refine your personality and traits.",
|
|
47
|
+
description: "Update your identity file to refine your personality and traits. Start it with # Identity.",
|
|
48
48
|
inputSchema: z.object({
|
|
49
49
|
content: z.string().describe("New content for IDENTITY.md"),
|
|
50
50
|
}),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ui } from "@melony/ui-kit/server";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -163,4 +164,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
163
164
|
};
|
|
164
165
|
}
|
|
165
166
|
});
|
|
167
|
+
builder.on("file-system:status", async function* (event) {
|
|
168
|
+
yield ui.event(ui.status(event.data.message, event.data.severity));
|
|
169
|
+
});
|
|
166
170
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { streamText } from "ai";
|
|
2
|
-
import { ui } from "@melony/ui-kit";
|
|
3
2
|
/**
|
|
4
3
|
* Builds a simple history summary from recent messages.
|
|
5
4
|
* Keeps the last N messages as simple role/content pairs.
|
|
@@ -53,7 +52,16 @@ export const llmPlugin = (options) => (builder) => {
|
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
const usage = await result.usage;
|
|
56
|
-
|
|
55
|
+
if (!state.usage) {
|
|
56
|
+
state.usage = {
|
|
57
|
+
inputTokens: 0,
|
|
58
|
+
outputTokens: 0,
|
|
59
|
+
totalTokens: 0,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
state.usage.inputTokens += usage.inputTokens ?? 0;
|
|
63
|
+
state.usage.outputTokens += usage.outputTokens ?? 0;
|
|
64
|
+
state.usage.totalTokens += usage.totalTokens ?? 0;
|
|
57
65
|
// Emit tool call events
|
|
58
66
|
for (const call of toolCalls) {
|
|
59
67
|
yield {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ui } from "@melony/ui-kit/server";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { exec } from "node:child_process";
|
|
3
4
|
import { promisify } from "node:util";
|
|
@@ -92,4 +93,7 @@ export const shellPlugin = (options = {}) => (builder) => {
|
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
});
|
|
96
|
+
builder.on("shell:status", async function* (event) {
|
|
97
|
+
yield ui.event(ui.status(event.data.message, event.data.severity));
|
|
98
|
+
});
|
|
95
99
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ui } from "@melony/ui-kit/server";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import matter from "gray-matter";
|
|
@@ -271,5 +272,13 @@ export const skillsPlugin = (options) => (builder) => {
|
|
|
271
272
|
};
|
|
272
273
|
}
|
|
273
274
|
});
|
|
275
|
+
builder.on("skills:status", async function* (event) {
|
|
276
|
+
yield ui.event(ui.status(event.data.message, event.data.severity));
|
|
277
|
+
});
|
|
278
|
+
builder.on("skills:loaded", async function* (event) {
|
|
279
|
+
yield ui.event(ui.resourceCard(event.data.title, "", [
|
|
280
|
+
ui.text(event.data.instructions),
|
|
281
|
+
]));
|
|
282
|
+
});
|
|
274
283
|
};
|
|
275
284
|
export default skillsPlugin;
|
package/dist/registry/index.js
CHANGED