openbot 0.1.20 → 0.1.23
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/handlers/init.js +14 -2
- 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 +30 -31
- package/dist/ui/header.js +4 -7
- package/dist/ui/layout.js +7 -7
- package/dist/ui/navigation.js +4 -2
- package/dist/ui/sidebar.js +42 -11
- package/dist/ui/thread.js +10 -8
- package/package.json +12 -13
- package/dist/agent.js +0 -110
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
export const fileSystemToolDefinitions = {
|
|
5
|
+
readFile: {
|
|
6
|
+
description: "Read the contents of a file. Path can be absolute or relative to the current working directory.",
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
path: z.string().describe("The path to the file (e.g., 'src/main.ts' or '/etc/hosts')"),
|
|
9
|
+
}),
|
|
10
|
+
},
|
|
11
|
+
writeFile: {
|
|
12
|
+
description: "Write content to a file. Path can be absolute or relative to the current working directory.",
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
path: z.string().describe("The path to the file (e.g., 'new-file.ts')"),
|
|
15
|
+
content: z.string().describe("The content to write to the file"),
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
listFiles: {
|
|
19
|
+
description: "List files in a directory. Path can be absolute or relative to the current working directory.",
|
|
20
|
+
inputSchema: z.object({
|
|
21
|
+
path: z.string().describe("The path to the directory (use '.' for current directory)"),
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
deleteFile: {
|
|
25
|
+
description: "Delete a file. Path can be absolute or relative to the current working directory.",
|
|
26
|
+
inputSchema: z.object({
|
|
27
|
+
path: z.string().describe("The path to the file"),
|
|
28
|
+
}),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Truncates a string by keeping the first and last N characters.
|
|
33
|
+
*/
|
|
34
|
+
function truncate(str, maxChars) {
|
|
35
|
+
if (!str || str.length <= maxChars)
|
|
36
|
+
return str;
|
|
37
|
+
const half = Math.floor(maxChars / 2);
|
|
38
|
+
const truncatedCount = str.length - maxChars;
|
|
39
|
+
return `${str.slice(0, half)}\n\n[... ${truncatedCount} characters truncated ...]\n\n${str.slice(-half)}`;
|
|
40
|
+
}
|
|
41
|
+
export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
42
|
+
const { baseDir = "/", maxFileReadLength = 10000 } = options;
|
|
43
|
+
const resolvePath = (p, stateCwd) => {
|
|
44
|
+
// If p is absolute, resolve from root. If relative, resolve from stateCwd or baseDir.
|
|
45
|
+
const resolved = path.isAbsolute(p) ? p : path.resolve(stateCwd || baseDir, p);
|
|
46
|
+
return resolved;
|
|
47
|
+
};
|
|
48
|
+
builder.on("action:readFile", async function* (event, { state }) {
|
|
49
|
+
const { path: filePath, toolCallId } = event.data;
|
|
50
|
+
yield {
|
|
51
|
+
type: "file-system:status",
|
|
52
|
+
data: { message: `Reading file: ${filePath} (relative to ${state.cwd || baseDir})` }
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
const content = await fs.readFile(resolvePath(filePath, state.cwd), "utf-8");
|
|
56
|
+
yield {
|
|
57
|
+
type: "action:taskResult",
|
|
58
|
+
data: {
|
|
59
|
+
action: "readFile",
|
|
60
|
+
result: { content: truncate(content, maxFileReadLength) },
|
|
61
|
+
toolCallId
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
yield {
|
|
65
|
+
type: "file-system:status",
|
|
66
|
+
data: { message: `File read successfully`, severity: "success" }
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
yield {
|
|
71
|
+
type: "action:taskResult",
|
|
72
|
+
data: { action: "readFile", result: { error: error.message }, toolCallId },
|
|
73
|
+
};
|
|
74
|
+
yield {
|
|
75
|
+
type: "file-system:status",
|
|
76
|
+
data: { message: `File read failed: ${error.message}`, severity: "error" }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
builder.on("action:writeFile", async function* (event, { state }) {
|
|
81
|
+
const { path: filePath, content, toolCallId } = event.data;
|
|
82
|
+
yield {
|
|
83
|
+
type: "file-system:status",
|
|
84
|
+
data: { message: `Writing file: ${filePath}` }
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
const fullPath = resolvePath(filePath, state.cwd);
|
|
88
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
89
|
+
await fs.writeFile(fullPath, content, "utf-8");
|
|
90
|
+
yield {
|
|
91
|
+
type: "action:taskResult",
|
|
92
|
+
data: { action: "writeFile", result: { success: true }, toolCallId },
|
|
93
|
+
};
|
|
94
|
+
yield {
|
|
95
|
+
type: "file-system:status",
|
|
96
|
+
data: { message: `File written successfully`, severity: "success" }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
yield {
|
|
101
|
+
type: "action:taskResult",
|
|
102
|
+
data: { action: "writeFile", result: { error: error.message }, toolCallId },
|
|
103
|
+
};
|
|
104
|
+
yield {
|
|
105
|
+
type: "file-system:status",
|
|
106
|
+
data: { message: `File write failed: ${error.message}`, severity: "error" }
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
builder.on("action:listFiles", async function* (event, { state }) {
|
|
111
|
+
const { path: dirPath, toolCallId } = event.data;
|
|
112
|
+
yield {
|
|
113
|
+
type: "file-system:status",
|
|
114
|
+
data: { message: `Listing files in: ${dirPath}` }
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
const files = await fs.readdir(resolvePath(dirPath, state.cwd));
|
|
118
|
+
yield {
|
|
119
|
+
type: "file-system:status",
|
|
120
|
+
data: { message: `Files listed successfully`, severity: "success" }
|
|
121
|
+
};
|
|
122
|
+
yield {
|
|
123
|
+
type: "action:taskResult",
|
|
124
|
+
data: { action: "listFiles", result: { files }, toolCallId },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
yield {
|
|
129
|
+
type: "file-system:status",
|
|
130
|
+
data: { message: `Files listing failed: ${error.message}`, severity: "error" }
|
|
131
|
+
};
|
|
132
|
+
yield {
|
|
133
|
+
type: "action:taskResult",
|
|
134
|
+
data: { action: "listFiles", result: { error: error.message }, toolCallId },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
builder.on("action:deleteFile", async function* (event, { state }) {
|
|
139
|
+
const { path: filePath, toolCallId } = event.data;
|
|
140
|
+
yield {
|
|
141
|
+
type: "file-system:status",
|
|
142
|
+
data: { message: `Deleting file: ${filePath}` }
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
await fs.unlink(resolvePath(filePath, state.cwd));
|
|
146
|
+
yield {
|
|
147
|
+
type: "action:taskResult",
|
|
148
|
+
data: { action: "deleteFile", result: { success: true }, toolCallId },
|
|
149
|
+
};
|
|
150
|
+
yield {
|
|
151
|
+
type: "file-system:status",
|
|
152
|
+
data: { message: `File deleted successfully`, severity: "success" }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
yield {
|
|
157
|
+
type: "action:taskResult",
|
|
158
|
+
data: { action: "deleteFile", result: { error: error.message }, toolCallId },
|
|
159
|
+
};
|
|
160
|
+
yield {
|
|
161
|
+
type: "file-system:status",
|
|
162
|
+
data: { message: `File deletion failed: ${error.message}`, severity: "error" }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
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
|
+
* LLM 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 llmPlugin = (options) => (builder) => {
|
|
16
|
+
const { model, system, toolDefinitions = {}, actionEventPrefix = "action:", promptInputType = "user:text", actionResultInputType = "action:taskResult", 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
|
+
};
|