openbot 0.1.18 → 0.1.22
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/browser-agent.js +31 -0
- package/dist/agents/os-agent.js +31 -0
- package/dist/cli.js +13 -3
- package/dist/config.js +1 -1
- package/dist/handlers/init.js +22 -3
- package/dist/handlers/session-change.js +21 -0
- package/dist/handlers/tab-change.js +14 -0
- package/dist/models.js +53 -0
- package/dist/open-bot.js +166 -0
- package/dist/plugins/agent/index.js +81 -0
- package/dist/plugins/brain/identity.js +76 -0
- package/dist/plugins/brain/index.js +269 -0
- package/dist/plugins/brain/memory.js +120 -0
- package/dist/plugins/brain/prompt.js +64 -0
- package/dist/plugins/brain/types.js +60 -0
- package/dist/plugins/brain/ui.js +7 -0
- package/dist/plugins/browser/index.js +629 -0
- package/dist/plugins/browser/ui.js +13 -0
- package/dist/plugins/file-system/index.js +166 -0
- package/dist/plugins/file-system/ui.js +6 -0
- package/dist/plugins/llm/index.js +81 -0
- package/dist/plugins/meta-agent/index.js +570 -0
- package/dist/plugins/meta-agent/ui.js +11 -0
- package/dist/plugins/shell/index.js +95 -0
- package/dist/plugins/shell/ui.js +6 -0
- package/dist/plugins/skills/index.js +275 -0
- package/dist/plugins/skills/types.js +50 -0
- package/dist/plugins/skills/ui.js +12 -0
- package/dist/registry/agent-registry.js +35 -0
- package/dist/registry/index.js +3 -0
- package/dist/registry/plugin-registry.js +27 -0
- package/dist/registry/yaml-agent-loader.js +100 -0
- package/dist/server.js +35 -31
- package/dist/session.js +79 -1
- package/dist/ui/header.js +52 -0
- package/dist/ui/layout.js +22 -1
- package/dist/ui/navigation.js +5 -6
- package/dist/ui/settings.js +44 -0
- package/dist/ui/sidebar.js +83 -4
- package/dist/ui/skills.js +7 -0
- package/dist/ui/thread.js +15 -10
- package/package.json +12 -13
- package/dist/agent.js +0 -103
package/README.md
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { llmPlugin } from "../plugins/llm/index.js";
|
|
2
|
+
import { browserPlugin, browserToolDefinitions, } from "../plugins/browser/index.js";
|
|
3
|
+
const DEFAULT_SYSTEM_PROMPT = `You are a Browser Agent.
|
|
4
|
+
Your job is to help the user browse the web, extract information, and perform actions.
|
|
5
|
+
|
|
6
|
+
Tools:
|
|
7
|
+
- browser_act: Give a natural language instruction to interact with the page (e.g. "click the login button").
|
|
8
|
+
- browser_observe: See what actions are available on the current page.
|
|
9
|
+
- browser_extract: Pull structured data from the page.
|
|
10
|
+
- browser_state_update: Get a fresh screenshot and URL.
|
|
11
|
+
|
|
12
|
+
Always describe what you see and what you are doing.`;
|
|
13
|
+
/**
|
|
14
|
+
* High-level Browser Agent plugin for Melony.
|
|
15
|
+
* Composes the low-level browserPlugin with an llmPlugin.
|
|
16
|
+
*/
|
|
17
|
+
export const browserAgent = (options) => (builder) => {
|
|
18
|
+
const { model, systemPrompt = DEFAULT_SYSTEM_PROMPT } = options;
|
|
19
|
+
builder
|
|
20
|
+
.use(browserPlugin({ ...options, model }))
|
|
21
|
+
.use(llmPlugin({
|
|
22
|
+
model,
|
|
23
|
+
system: systemPrompt,
|
|
24
|
+
toolDefinitions: browserToolDefinitions,
|
|
25
|
+
promptInputType: "agent:browser:input",
|
|
26
|
+
actionResultInputType: "agent:browser:result",
|
|
27
|
+
completionEventType: "agent:browser:output",
|
|
28
|
+
}));
|
|
29
|
+
// NOTE: Bridge-back to the manager is handled generically by open-bot.ts.
|
|
30
|
+
// No per-agent boilerplate needed.
|
|
31
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { llmPlugin } from "../plugins/llm/index.js";
|
|
2
|
+
import { shellPlugin, shellToolDefinitions } from "../plugins/shell/index.js";
|
|
3
|
+
import { shellUIPlugin } from "../plugins/shell/ui.js";
|
|
4
|
+
import { fileSystemPlugin, fileSystemToolDefinitions } from "../plugins/file-system/index.js";
|
|
5
|
+
import { fileSystemUIPlugin } from "../plugins/file-system/ui.js";
|
|
6
|
+
const DEFAULT_SYSTEM_PROMPT = `You are an OS Agent with access to the shell and file system.
|
|
7
|
+
Your job is to help the user with file operations and command execution.
|
|
8
|
+
You can read, write, list, and delete files, as well as execute shell commands.
|
|
9
|
+
Always be careful with destructive operations.
|
|
10
|
+
When you are done with the task, summarize what you did.`;
|
|
11
|
+
export const osAgent = (options) => (builder) => {
|
|
12
|
+
const { model, cwd = process.cwd(), systemPrompt = DEFAULT_SYSTEM_PROMPT } = options;
|
|
13
|
+
builder
|
|
14
|
+
.use(shellPlugin({ cwd }))
|
|
15
|
+
.use(shellUIPlugin())
|
|
16
|
+
.use(fileSystemPlugin({ baseDir: "/" }))
|
|
17
|
+
.use(fileSystemUIPlugin())
|
|
18
|
+
.use(llmPlugin({
|
|
19
|
+
model,
|
|
20
|
+
system: systemPrompt,
|
|
21
|
+
toolDefinitions: {
|
|
22
|
+
...shellToolDefinitions,
|
|
23
|
+
...fileSystemToolDefinitions,
|
|
24
|
+
},
|
|
25
|
+
promptInputType: "agent:os:input",
|
|
26
|
+
actionResultInputType: "agent:os:result",
|
|
27
|
+
completionEventType: "agent:os:output",
|
|
28
|
+
}));
|
|
29
|
+
// NOTE: Bridge-back to the manager is handled generically by open-bot.ts.
|
|
30
|
+
// No per-agent boilerplate needed.
|
|
31
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import * as readline from "node:readline/promises";
|
|
4
4
|
import { saveConfig } from "./config.js";
|
|
5
|
+
import { startServer } from "./server.js";
|
|
5
6
|
const program = new Command();
|
|
6
7
|
program
|
|
7
8
|
.name("openbot")
|
|
8
9
|
.description("OpenBot CLI - Secure and easy configuration")
|
|
9
|
-
.version("0.1.
|
|
10
|
+
.version("0.1.20");
|
|
10
11
|
program
|
|
11
12
|
.command("configure")
|
|
12
13
|
.description("Configure OpenBot model and settings")
|
|
@@ -46,15 +47,24 @@ program
|
|
|
46
47
|
console.log("------------------------------------------");
|
|
47
48
|
if (provider === "openai") {
|
|
48
49
|
console.log("To start the server with your OpenAI key, use:");
|
|
49
|
-
console.log(`\n openbot
|
|
50
|
+
console.log(`\n openbot server --openai-api-key YOUR_OPENAI_KEY\n`);
|
|
50
51
|
}
|
|
51
52
|
else {
|
|
52
53
|
console.log("To start the server with your Anthropic key, use:");
|
|
53
|
-
console.log(`\n openbot
|
|
54
|
+
console.log(`\n openbot server --anthropic-api-key YOUR_ANTHROPIC_KEY\n`);
|
|
54
55
|
}
|
|
55
56
|
console.log("Alternatively, you can set the environment variable:");
|
|
56
57
|
console.log(provider === "openai" ? " export OPENAI_API_KEY=your-key" : " export ANTHROPIC_API_KEY=your-key");
|
|
57
58
|
console.log("------------------------------------------");
|
|
58
59
|
rl.close();
|
|
59
60
|
});
|
|
61
|
+
program
|
|
62
|
+
.command("server")
|
|
63
|
+
.description("Start the OpenBot server")
|
|
64
|
+
.option("-p, --port <number>", "Port to listen on")
|
|
65
|
+
.option("--openai-api-key <key>", "OpenAI API Key")
|
|
66
|
+
.option("--anthropic-api-key <key>", "Anthropic API Key")
|
|
67
|
+
.action(async (options) => {
|
|
68
|
+
await startServer(options);
|
|
69
|
+
});
|
|
60
70
|
program.parse();
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
|
-
export const DEFAULT_BASE_DIR = "
|
|
4
|
+
export const DEFAULT_BASE_DIR = "~/.openbot";
|
|
5
5
|
export function loadConfig() {
|
|
6
6
|
const configPath = path.join(os.homedir(), ".openbot", "config.json");
|
|
7
7
|
if (fs.existsSync(configPath)) {
|
package/dist/handlers/init.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sidebarOnlyUI, tabOnlyUI } from "../ui/layout.js";
|
|
2
2
|
/**
|
|
3
3
|
* Initial application layout handler
|
|
4
4
|
*/
|
|
5
|
-
export async function* initHandler(event) {
|
|
5
|
+
export async function* initHandler(event, { state }) {
|
|
6
|
+
// Initialize path state if not already present
|
|
7
|
+
if (!state.cwd) {
|
|
8
|
+
state.cwd = process.cwd();
|
|
9
|
+
}
|
|
10
|
+
if (!state.workspaceRoot) {
|
|
11
|
+
state.workspaceRoot = process.cwd();
|
|
12
|
+
}
|
|
13
|
+
const tab = event.data?.tab || "chat";
|
|
14
|
+
// For init, return both sidebar and content
|
|
6
15
|
yield {
|
|
7
16
|
type: "ui",
|
|
8
|
-
|
|
17
|
+
meta: {
|
|
18
|
+
type: "sidebar",
|
|
19
|
+
},
|
|
20
|
+
data: await sidebarOnlyUI({ sessionId: state.sessionId })
|
|
21
|
+
};
|
|
22
|
+
yield {
|
|
23
|
+
type: "ui",
|
|
24
|
+
meta: {
|
|
25
|
+
type: "content",
|
|
26
|
+
},
|
|
27
|
+
data: await tabOnlyUI({ tab })
|
|
9
28
|
};
|
|
10
29
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { sidebarOnlyUI, tabOnlyUI } from "../ui/layout.js";
|
|
2
|
+
/**
|
|
3
|
+
* Session change handler
|
|
4
|
+
*/
|
|
5
|
+
export async function* sessionChangeHandler(event, { state }) {
|
|
6
|
+
const tab = event.data?.tab || "chat";
|
|
7
|
+
yield {
|
|
8
|
+
type: "ui",
|
|
9
|
+
meta: {
|
|
10
|
+
type: "sidebar",
|
|
11
|
+
},
|
|
12
|
+
data: await sidebarOnlyUI({ sessionId: state.sessionId })
|
|
13
|
+
};
|
|
14
|
+
yield {
|
|
15
|
+
type: "ui",
|
|
16
|
+
meta: {
|
|
17
|
+
type: "content",
|
|
18
|
+
},
|
|
19
|
+
data: await tabOnlyUI({ tab })
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { tabOnlyUI } from "../ui/layout.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tab change handler
|
|
4
|
+
*/
|
|
5
|
+
export async function* tabChangeHandler(event, { state }) {
|
|
6
|
+
const tab = event.data?.tab || "chat";
|
|
7
|
+
yield {
|
|
8
|
+
type: "ui",
|
|
9
|
+
meta: {
|
|
10
|
+
type: "content",
|
|
11
|
+
},
|
|
12
|
+
data: await tabOnlyUI({ tab })
|
|
13
|
+
};
|
|
14
|
+
}
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { openai } from "@ai-sdk/openai";
|
|
2
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
3
|
+
import { loadConfig } from "./config.js";
|
|
4
|
+
/**
|
|
5
|
+
* Parse model string to extract provider and model ID
|
|
6
|
+
* Supports formats: "provider:model", "provider/model", or just "model" (defaults to openai)
|
|
7
|
+
*/
|
|
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
|
+
// Check for provider/model format
|
|
17
|
+
if (modelString.includes("/")) {
|
|
18
|
+
const [provider, modelId] = modelString.split("/");
|
|
19
|
+
if (provider === "openai" || provider === "anthropic") {
|
|
20
|
+
return { provider: provider, modelId };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Auto-detect provider based on model name
|
|
24
|
+
if (modelString.startsWith("claude") || modelString.startsWith("claude-")) {
|
|
25
|
+
return { provider: "anthropic", modelId: modelString };
|
|
26
|
+
}
|
|
27
|
+
// Default to OpenAI
|
|
28
|
+
return { provider: "openai", modelId: modelString };
|
|
29
|
+
}
|
|
30
|
+
export function createModel(options) {
|
|
31
|
+
const config = loadConfig();
|
|
32
|
+
const openaiKey = options?.openaiApiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
|
|
33
|
+
const anthropicKey = options?.anthropicApiKey || config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
34
|
+
if (openaiKey) {
|
|
35
|
+
process.env.OPENAI_API_KEY = openaiKey;
|
|
36
|
+
}
|
|
37
|
+
if (anthropicKey) {
|
|
38
|
+
process.env.ANTHROPIC_API_KEY = anthropicKey;
|
|
39
|
+
}
|
|
40
|
+
const { provider, modelId } = parseModelString(options?.model || config.model || "gpt-4o-mini");
|
|
41
|
+
if (provider === "anthropic") {
|
|
42
|
+
if (!anthropicKey) {
|
|
43
|
+
console.warn("Warning: Anthropic model selected but ANTHROPIC_API_KEY is not set");
|
|
44
|
+
}
|
|
45
|
+
return anthropic(modelId);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (!openaiKey) {
|
|
49
|
+
console.warn("Warning: OpenAI model selected but OPENAI_API_KEY is not set");
|
|
50
|
+
}
|
|
51
|
+
return openai(modelId);
|
|
52
|
+
}
|
|
53
|
+
}
|
package/dist/open-bot.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { melony } from "melony";
|
|
2
|
+
import { osAgent } from "./agents/os-agent.js";
|
|
3
|
+
import { browserAgent } from "./agents/browser-agent.js";
|
|
4
|
+
import { browserUIPlugin } from "./plugins/browser/ui.js";
|
|
5
|
+
import { brainPlugin, brainToolDefinitions, createBrainPromptBuilder } from "./plugins/brain/index.js";
|
|
6
|
+
import { brainUIPlugin } from "./plugins/brain/ui.js";
|
|
7
|
+
import { llmPlugin } from "./plugins/llm/index.js";
|
|
8
|
+
import { initHandler } from "./handlers/init.js";
|
|
9
|
+
import { sessionChangeHandler } from "./handlers/session-change.js";
|
|
10
|
+
import { tabChangeHandler } from "./handlers/tab-change.js";
|
|
11
|
+
import { loadConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
|
|
12
|
+
import { createModel } from "./models.js";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
// Plugin imports for the registry
|
|
16
|
+
import { shellPlugin, shellToolDefinitions } from "./plugins/shell/index.js";
|
|
17
|
+
import { shellUIPlugin } from "./plugins/shell/ui.js";
|
|
18
|
+
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
|
+
// Registry
|
|
22
|
+
import { PluginRegistry, AgentRegistry, discoverYamlAgents } from "./registry/index.js";
|
|
23
|
+
/**
|
|
24
|
+
* Create the OpenBot manager agent.
|
|
25
|
+
*
|
|
26
|
+
* Architecture:
|
|
27
|
+
* 1. Built-in plugins are registered in the Plugin Registry so YAML agents can reference them by name.
|
|
28
|
+
* 2. Built-in agents + YAML agents discovered from ~/.openbot/agents/ are registered in the Agent Registry.
|
|
29
|
+
* 3. The manager LLM gets a dynamically-built `delegateTask` tool listing all registered agents.
|
|
30
|
+
* 4. A generic delegation handler + bridge-back wiring replaces per-agent boilerplate.
|
|
31
|
+
*/
|
|
32
|
+
export async function createOpenBot(options) {
|
|
33
|
+
const config = loadConfig();
|
|
34
|
+
const baseDir = config.baseDir || DEFAULT_BASE_DIR;
|
|
35
|
+
const resolvedBaseDir = resolvePath(baseDir);
|
|
36
|
+
const model = createModel(options);
|
|
37
|
+
const userDataDir = path.join(resolvedBaseDir, "browser-data");
|
|
38
|
+
// ─── Plugin Registry ─────────────────────────────────────────────
|
|
39
|
+
// Register built-in plugins so YAML agents can reference them by name.
|
|
40
|
+
const pluginRegistry = new PluginRegistry();
|
|
41
|
+
pluginRegistry.register({
|
|
42
|
+
name: "shell",
|
|
43
|
+
description: "Execute shell commands",
|
|
44
|
+
toolDefinitions: shellToolDefinitions,
|
|
45
|
+
factory: () => shellPlugin({ cwd: process.cwd() }),
|
|
46
|
+
uiFactory: () => shellUIPlugin(),
|
|
47
|
+
});
|
|
48
|
+
pluginRegistry.register({
|
|
49
|
+
name: "file-system",
|
|
50
|
+
description: "Read, write, list, and delete files",
|
|
51
|
+
toolDefinitions: fileSystemToolDefinitions,
|
|
52
|
+
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
|
+
});
|
|
67
|
+
// ─── Agent Registry ──────────────────────────────────────────────
|
|
68
|
+
// Register built-in agents, then discover YAML agents from ~/.openbot/agents/.
|
|
69
|
+
const agentRegistry = new AgentRegistry();
|
|
70
|
+
agentRegistry.register({
|
|
71
|
+
name: "os",
|
|
72
|
+
description: "Handles shell commands and file system operations",
|
|
73
|
+
plugin: osAgent({ model: model }),
|
|
74
|
+
});
|
|
75
|
+
agentRegistry.register({
|
|
76
|
+
name: "browser",
|
|
77
|
+
description: "Browses the web, extracts data, and interacts with pages",
|
|
78
|
+
plugin: browserAgent({
|
|
79
|
+
model: model,
|
|
80
|
+
headless: true,
|
|
81
|
+
userDataDir,
|
|
82
|
+
channel: "chrome",
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
// Discover community / user agents from ~/.openbot/agents/
|
|
86
|
+
const yamlAgents = await discoverYamlAgents(path.join(resolvedBaseDir, "agents"), pluginRegistry, model, options);
|
|
87
|
+
for (const agent of yamlAgents) {
|
|
88
|
+
agentRegistry.register(agent);
|
|
89
|
+
}
|
|
90
|
+
// ─── Compose the Melony App ──────────────────────────────────────
|
|
91
|
+
const allAgents = agentRegistry.getAll();
|
|
92
|
+
const agentNames = agentRegistry.getNames();
|
|
93
|
+
const app = melony();
|
|
94
|
+
// 1. Register all agent plugins
|
|
95
|
+
for (const agent of allAgents) {
|
|
96
|
+
app.use(agent.plugin);
|
|
97
|
+
}
|
|
98
|
+
// 2. Register global plugins (brain, UI, etc.)
|
|
99
|
+
const buildBrainPrompt = createBrainPromptBuilder(baseDir);
|
|
100
|
+
app
|
|
101
|
+
.use(browserUIPlugin())
|
|
102
|
+
.use(brainPlugin({
|
|
103
|
+
baseDir: resolvedBaseDir,
|
|
104
|
+
allowSoulModification: false,
|
|
105
|
+
}))
|
|
106
|
+
.use(brainUIPlugin());
|
|
107
|
+
// 3. Build dynamic delegation tool from the agent registry
|
|
108
|
+
const agentDescriptions = allAgents
|
|
109
|
+
.map((a) => `- ${a.name}: ${a.description}`)
|
|
110
|
+
.join("\n");
|
|
111
|
+
app.use(llmPlugin({
|
|
112
|
+
model: model,
|
|
113
|
+
system: async (context) => {
|
|
114
|
+
const [brainPrompt] = await Promise.all([
|
|
115
|
+
buildBrainPrompt(context),
|
|
116
|
+
]);
|
|
117
|
+
return `${brainPrompt}`;
|
|
118
|
+
},
|
|
119
|
+
toolDefinitions: {
|
|
120
|
+
...brainToolDefinitions,
|
|
121
|
+
delegateTask: {
|
|
122
|
+
description: `Delegate a task to a specialized agent.\n\nAvailable agents:\n${agentDescriptions}`,
|
|
123
|
+
inputSchema: z.object({
|
|
124
|
+
agent: z.enum(agentNames).describe("The specialized agent to use"),
|
|
125
|
+
task: z.string().describe("The task description"),
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
// 4. Generic delegation handler — works for any registered agent
|
|
131
|
+
app.on("action:delegateTask", async function* (event, { state }) {
|
|
132
|
+
const { agent, task, toolCallId } = event.data;
|
|
133
|
+
const s = state;
|
|
134
|
+
if (!s.pendingAgentTasks)
|
|
135
|
+
s.pendingAgentTasks = {};
|
|
136
|
+
s.pendingAgentTasks[agent] = { toolCallId };
|
|
137
|
+
yield {
|
|
138
|
+
type: `agent:${agent}:input`,
|
|
139
|
+
data: { content: task },
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
// 5. Generic bridge-back handlers — auto-wired for every registered agent
|
|
143
|
+
for (const agent of allAgents) {
|
|
144
|
+
app.on(`agent:${agent.name}:output`, async function* (event, { state }) {
|
|
145
|
+
const s = state;
|
|
146
|
+
const pending = s.pendingAgentTasks?.[agent.name];
|
|
147
|
+
if (pending) {
|
|
148
|
+
delete s.pendingAgentTasks[agent.name];
|
|
149
|
+
yield {
|
|
150
|
+
type: "action:taskResult",
|
|
151
|
+
data: {
|
|
152
|
+
action: "delegateTask",
|
|
153
|
+
toolCallId: pending.toolCallId,
|
|
154
|
+
result: event.data.content,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// 6. Init handlers
|
|
161
|
+
app
|
|
162
|
+
.on("init", initHandler)
|
|
163
|
+
.on("sessionChange", sessionChangeHandler)
|
|
164
|
+
.on("tabChange", tabChangeHandler);
|
|
165
|
+
return app;
|
|
166
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { streamText } from "ai";
|
|
2
|
+
import { ui } from "@melony/ui-kit";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a simple history summary from recent messages.
|
|
5
|
+
* Keeps the last N messages as simple role/content pairs.
|
|
6
|
+
*/
|
|
7
|
+
function getRecentHistory(messages, maxMessages) {
|
|
8
|
+
return messages.slice(-maxMessages);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* AI SDK Plugin for Melony.
|
|
12
|
+
* Automatically handles text events and routes them through an LLM using Vercel AI SDK.
|
|
13
|
+
* It can also automatically trigger events based on tool calls.
|
|
14
|
+
*/
|
|
15
|
+
export const agentPlugin = (options) => (builder) => {
|
|
16
|
+
const { model, system, toolDefinitions = {}, actionEventPrefix = "action:", promptInputType = "user:text", actionResultInputType = "action:result", completionEventType } = options;
|
|
17
|
+
async function* routeToLLM(newMessage, context) {
|
|
18
|
+
const state = context.state;
|
|
19
|
+
if (!state.messages) {
|
|
20
|
+
state.messages = [];
|
|
21
|
+
}
|
|
22
|
+
// Add new message to history
|
|
23
|
+
state.messages.push(newMessage);
|
|
24
|
+
// Evaluate dynamic system prompt if it's a function
|
|
25
|
+
const systemPrompt = typeof system === "function" ? await system(context) : system;
|
|
26
|
+
const result = streamText({
|
|
27
|
+
model,
|
|
28
|
+
system: systemPrompt,
|
|
29
|
+
messages: getRecentHistory(state.messages, 20).map(m => m.role === "system" ? { role: "user", content: `System: ${m.content}` } : m),
|
|
30
|
+
tools: toolDefinitions,
|
|
31
|
+
});
|
|
32
|
+
let assistantText = "";
|
|
33
|
+
for await (const delta of result.textStream) {
|
|
34
|
+
assistantText += delta;
|
|
35
|
+
yield {
|
|
36
|
+
type: "assistant:text-delta",
|
|
37
|
+
data: { delta },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Wait for tool calls to complete
|
|
41
|
+
const toolCalls = await result.toolCalls;
|
|
42
|
+
// Store assistant response as simple text
|
|
43
|
+
if (assistantText) {
|
|
44
|
+
state.messages.push({
|
|
45
|
+
role: "assistant",
|
|
46
|
+
content: assistantText,
|
|
47
|
+
});
|
|
48
|
+
if (completionEventType) {
|
|
49
|
+
yield {
|
|
50
|
+
type: completionEventType,
|
|
51
|
+
data: { content: assistantText },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const usage = await result.usage;
|
|
56
|
+
yield ui.event(ui.status(`Usage: ${usage.totalTokens} tokens`, "info"));
|
|
57
|
+
// Emit tool call events
|
|
58
|
+
for (const call of toolCalls) {
|
|
59
|
+
yield {
|
|
60
|
+
type: `${actionEventPrefix}${call.toolName}`,
|
|
61
|
+
data: {
|
|
62
|
+
...call.input,
|
|
63
|
+
toolCallId: call.toolCallId,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Handle user text input
|
|
69
|
+
builder.on(promptInputType, async function* (event, context) {
|
|
70
|
+
const content = event.data.content;
|
|
71
|
+
yield* routeToLLM({ role: "user", content }, context);
|
|
72
|
+
});
|
|
73
|
+
// Feed action results back to the LLM as user messages (with a System prefix)
|
|
74
|
+
// We use "user" role instead of "system" to avoid errors with providers like Anthropic
|
|
75
|
+
// that don't support multiple system messages or system messages after the first turn.
|
|
76
|
+
builder.on(actionResultInputType, async function* (event, context) {
|
|
77
|
+
const { action, result } = event.data;
|
|
78
|
+
const summary = typeof result === "string" ? result : JSON.stringify(result);
|
|
79
|
+
yield* routeToLLM({ role: "user", content: `System: Action "${action}" completed: ${summary}` }, context);
|
|
80
|
+
});
|
|
81
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
// --- Defaults ---
|
|
4
|
+
const DEFAULT_SOUL = `# Soul
|
|
5
|
+
|
|
6
|
+
## Core Values
|
|
7
|
+
- Be helpful, honest, and harmless
|
|
8
|
+
- Respect user privacy and data
|
|
9
|
+
- Learn and improve continuously
|
|
10
|
+
- Be transparent about capabilities and limitations
|
|
11
|
+
|
|
12
|
+
## Ethical Guidelines
|
|
13
|
+
- Never assist with harmful or illegal activities
|
|
14
|
+
- Protect sensitive information
|
|
15
|
+
- Acknowledge uncertainty when unsure
|
|
16
|
+
- Prioritize user well-being
|
|
17
|
+
`;
|
|
18
|
+
const DEFAULT_IDENTITY = `# Identity
|
|
19
|
+
|
|
20
|
+
I am OpenBot, a self-evolving AI assistant built on the OpenBot framework.
|
|
21
|
+
|
|
22
|
+
## Personality
|
|
23
|
+
- Friendly and approachable
|
|
24
|
+
- Technically competent
|
|
25
|
+
- Eager to learn and adapt
|
|
26
|
+
|
|
27
|
+
## Capabilities
|
|
28
|
+
- Shell command execution
|
|
29
|
+
- File system operations
|
|
30
|
+
- Skill-based task execution
|
|
31
|
+
- Self-modification and learning
|
|
32
|
+
`;
|
|
33
|
+
// --- Factory ---
|
|
34
|
+
export function createIdentityModule(baseDir) {
|
|
35
|
+
const soulPath = path.join(baseDir, "SOUL.md");
|
|
36
|
+
const identityPath = path.join(baseDir, "IDENTITY.md");
|
|
37
|
+
return {
|
|
38
|
+
async initialize() {
|
|
39
|
+
try {
|
|
40
|
+
await fs.access(soulPath);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
await fs.writeFile(soulPath, DEFAULT_SOUL, "utf-8");
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(identityPath);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
await fs.writeFile(identityPath, DEFAULT_IDENTITY, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
async getSoul() {
|
|
53
|
+
try {
|
|
54
|
+
return (await fs.readFile(soulPath, "utf-8")).trim();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
async getIdentity() {
|
|
61
|
+
try {
|
|
62
|
+
return (await fs.readFile(identityPath, "utf-8")).trim();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async updateIdentity(content) {
|
|
69
|
+
await fs.writeFile(identityPath, content, "utf-8");
|
|
70
|
+
},
|
|
71
|
+
async readFile(file) {
|
|
72
|
+
const filePath = file === "SOUL.md" ? soulPath : identityPath;
|
|
73
|
+
return await fs.readFile(filePath, "utf-8");
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|