openbot 0.2.12 → 0.2.13
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/.prettierrc +8 -0
- package/AGENTS.md +68 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE +21 -0
- package/README.md +117 -14
- package/dist/agents/system.js +106 -0
- package/dist/app/cli.js +27 -0
- package/dist/app/config.js +64 -0
- package/dist/app/server.js +237 -0
- package/dist/app/utils.js +35 -0
- package/dist/harness/agent-harness.js +45 -0
- package/dist/harness/mcp.js +61 -0
- package/dist/harness/orchestrator.js +273 -0
- package/dist/harness/process.js +7 -0
- package/dist/plugins/ai-sdk.js +141 -0
- package/dist/plugins/delegation.js +52 -0
- package/dist/plugins/mcp.js +140 -0
- package/dist/plugins/storage.js +502 -0
- package/dist/plugins/ui.js +47 -0
- package/dist/registry/plugins.js +73 -0
- package/dist/services/storage.js +724 -0
- package/docs/README.md +7 -0
- package/docs/agents.md +83 -0
- package/docs/architecture.md +34 -0
- package/docs/plugins.md +77 -0
- package/logo-black.png +0 -0
- package/{dist/assets/logo.js → logo-black.svg} +24 -24
- package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
- package/package.json +10 -9
- package/src/agents/system.ts +112 -0
- package/src/app/cli.ts +38 -0
- package/src/app/config.ts +104 -0
- package/src/app/server.ts +284 -0
- package/src/app/types.ts +476 -0
- package/src/app/utils.ts +43 -0
- package/src/assets/icon.svg +1 -0
- package/src/harness/agent-harness.ts +58 -0
- package/src/harness/mcp.ts +78 -0
- package/src/harness/orchestrator.ts +342 -0
- package/src/harness/process.ts +9 -0
- package/src/harness/types.ts +34 -0
- package/src/plugins/ai-sdk.ts +197 -0
- package/src/plugins/delegation.ts +60 -0
- package/src/plugins/mcp.ts +154 -0
- package/src/plugins/storage.ts +725 -0
- package/src/plugins/ui.ts +57 -0
- package/src/registry/plugins.ts +85 -0
- package/src/services/storage.ts +957 -0
- package/tsconfig.json +18 -0
- package/dist/agents/agent-creator.js +0 -74
- package/dist/agents/browser-agent.js +0 -31
- package/dist/agents/os-agent.js +0 -32
- package/dist/agents/planner-agent.js +0 -32
- package/dist/agents/topic-agent.js +0 -46
- package/dist/architecture/execution-engine.js +0 -151
- package/dist/architecture/intent-classifier.js +0 -26
- package/dist/architecture/planner.js +0 -106
- package/dist/automation-worker.js +0 -121
- package/dist/automations.js +0 -52
- package/dist/cli.js +0 -279
- package/dist/config.js +0 -53
- package/dist/core/agents.js +0 -41
- package/dist/core/delegation.js +0 -230
- package/dist/core/manager.js +0 -96
- package/dist/core/plugins.js +0 -74
- package/dist/core/router.js +0 -191
- package/dist/handlers/init.js +0 -29
- package/dist/handlers/session-change.js +0 -21
- package/dist/handlers/settings.js +0 -47
- package/dist/handlers/tab-change.js +0 -14
- package/dist/installers.js +0 -156
- package/dist/marketplace.js +0 -80
- package/dist/model-catalog.js +0 -132
- package/dist/model-defaults.js +0 -25
- package/dist/models.js +0 -47
- package/dist/open-bot.js +0 -51
- package/dist/orchestrator/direct-invocation.js +0 -13
- package/dist/orchestrator/events.js +0 -36
- package/dist/orchestrator/state.js +0 -54
- package/dist/orchestrator.js +0 -422
- package/dist/plugins/agent/index.js +0 -81
- package/dist/plugins/approval/index.js +0 -100
- package/dist/plugins/brain/identity.js +0 -77
- package/dist/plugins/brain/index.js +0 -204
- package/dist/plugins/brain/memory.js +0 -120
- package/dist/plugins/brain/prompt.js +0 -46
- package/dist/plugins/brain/types.js +0 -45
- package/dist/plugins/brain/ui.js +0 -7
- package/dist/plugins/browser/index.js +0 -629
- package/dist/plugins/browser/ui.js +0 -13
- package/dist/plugins/file-system/index.js +0 -171
- package/dist/plugins/file-system/ui.js +0 -6
- package/dist/plugins/llm/context-budget.js +0 -139
- package/dist/plugins/llm/context-shaping.js +0 -177
- package/dist/plugins/llm/index.js +0 -380
- package/dist/plugins/memory/index.js +0 -220
- package/dist/plugins/memory/memory.js +0 -122
- package/dist/plugins/memory/prompt.js +0 -55
- package/dist/plugins/memory/types.js +0 -45
- package/dist/plugins/meta-agent/index.js +0 -570
- package/dist/plugins/meta-agent/ui.js +0 -11
- package/dist/plugins/shell/index.js +0 -100
- package/dist/plugins/shell/ui.js +0 -6
- package/dist/plugins/skills/index.js +0 -286
- package/dist/plugins/skills/types.js +0 -50
- package/dist/plugins/skills/ui.js +0 -12
- package/dist/registry/agent-registry.js +0 -35
- package/dist/registry/index.js +0 -2
- package/dist/registry/plugin-loader.js +0 -499
- package/dist/registry/plugin-registry.js +0 -44
- package/dist/registry/ts-agent-loader.js +0 -82
- package/dist/registry/yaml-agent-loader.js +0 -246
- package/dist/runtime/execution-trace.js +0 -41
- package/dist/runtime/intent-routing.js +0 -26
- package/dist/runtime/openbot-runtime.js +0 -354
- package/dist/server.js +0 -890
- package/dist/session.js +0 -179
- package/dist/ui/block.js +0 -12
- package/dist/ui/header.js +0 -52
- package/dist/ui/layout.js +0 -26
- package/dist/ui/navigation.js +0 -15
- package/dist/ui/settings.js +0 -106
- package/dist/ui/skills.js +0 -7
- package/dist/ui/thread.js +0 -16
- package/dist/ui/widgets/action-list.js +0 -2
- package/dist/ui/widgets/approval-card.js +0 -9
- package/dist/ui/widgets/code-snippet.js +0 -2
- package/dist/ui/widgets/data-block.js +0 -2
- package/dist/ui/widgets/data-table.js +0 -2
- package/dist/ui/widgets/delegation.js +0 -29
- package/dist/ui/widgets/empty-state.js +0 -2
- package/dist/ui/widgets/index.js +0 -23
- package/dist/ui/widgets/inquiry.js +0 -7
- package/dist/ui/widgets/key-value.js +0 -2
- package/dist/ui/widgets/progress-step.js +0 -2
- package/dist/ui/widgets/resource-card.js +0 -2
- package/dist/ui/widgets/status.js +0 -2
- package/dist/ui/widgets/todo-list.js +0 -2
- package/dist/version.js +0 -62
- /package/dist/{types.js → app/types.js} +0 -0
- /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
5
|
-
import matter from "gray-matter";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
import { PluginRegistry } from "./plugin-registry.js";
|
|
8
|
-
import { llmPlugin } from "../plugins/llm/index.js";
|
|
9
|
-
import { createModel } from "../models.js";
|
|
10
|
-
import { resolvePath, DEFAULT_AGENT_MD } from "../config.js";
|
|
11
|
-
// ── Helpers ──────────────────────────────────────────────────────────
|
|
12
|
-
function toTitleCaseFromSlug(value) {
|
|
13
|
-
return value
|
|
14
|
-
.split(/[-_]+/)
|
|
15
|
-
.filter(Boolean)
|
|
16
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
17
|
-
.join(" ") || "Agent";
|
|
18
|
-
}
|
|
19
|
-
async function fileExists(filePath) {
|
|
20
|
-
return fs.access(filePath).then(() => true).catch(() => false);
|
|
21
|
-
}
|
|
22
|
-
async function findIndexFile(dir) {
|
|
23
|
-
for (const file of ["dist/index.js", "index.js", "index.ts"]) {
|
|
24
|
-
if (await fileExists(path.join(dir, file))) {
|
|
25
|
-
return path.join(dir, file);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
function resolveConfigPaths(config) {
|
|
31
|
-
if (typeof config === "string")
|
|
32
|
-
return resolvePath(config);
|
|
33
|
-
if (Array.isArray(config))
|
|
34
|
-
return config.map(resolveConfigPaths);
|
|
35
|
-
if (config !== null && typeof config === "object") {
|
|
36
|
-
const resolved = {};
|
|
37
|
-
for (const [key, value] of Object.entries(config)) {
|
|
38
|
-
resolved[key] = resolveConfigPaths(value);
|
|
39
|
-
}
|
|
40
|
-
return resolved;
|
|
41
|
-
}
|
|
42
|
-
return config;
|
|
43
|
-
}
|
|
44
|
-
// ── Metadata ─────────────────────────────────────────────────────────
|
|
45
|
-
export async function getPluginMetadata(pluginDir) {
|
|
46
|
-
const pkgPath = path.join(pluginDir, "package.json");
|
|
47
|
-
const hasPackageJson = await fileExists(pkgPath);
|
|
48
|
-
let name = "Unnamed Plugin";
|
|
49
|
-
let description = "No description";
|
|
50
|
-
let version = "0.0.0";
|
|
51
|
-
if (hasPackageJson) {
|
|
52
|
-
try {
|
|
53
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
54
|
-
name = (pkg.name?.split("/").pop()) || name;
|
|
55
|
-
description = pkg.description || description;
|
|
56
|
-
version = pkg.version || version;
|
|
57
|
-
}
|
|
58
|
-
catch { /* fallback to defaults */ }
|
|
59
|
-
}
|
|
60
|
-
return { name, description, version };
|
|
61
|
-
}
|
|
62
|
-
export async function ensurePluginReady(pluginDir) {
|
|
63
|
-
try {
|
|
64
|
-
const pkgPath = path.join(pluginDir, "package.json");
|
|
65
|
-
if (!(await fileExists(pkgPath)))
|
|
66
|
-
return;
|
|
67
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
68
|
-
const nodeModulesPath = path.join(pluginDir, "node_modules");
|
|
69
|
-
if (!(await fileExists(nodeModulesPath))) {
|
|
70
|
-
console.log(`[plugins] Installing dependencies for ${path.basename(pluginDir)}...`);
|
|
71
|
-
execSync("npm install", { cwd: pluginDir, stdio: "inherit" });
|
|
72
|
-
}
|
|
73
|
-
const distPath = path.join(pluginDir, "dist");
|
|
74
|
-
if (!(await fileExists(distPath)) && pkg.scripts?.build) {
|
|
75
|
-
console.log(`[plugins] Building ${path.basename(pluginDir)}...`);
|
|
76
|
-
execSync("npm run build", { cwd: pluginDir, stdio: "inherit" });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
console.error(`[plugins] Failed to prepare plugin in ${pluginDir}:`, err);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// ── AGENT.md Config ──────────────────────────────────────────────────
|
|
84
|
-
function jsonToZod(schema) {
|
|
85
|
-
if (typeof schema === "string") {
|
|
86
|
-
if (schema === "string")
|
|
87
|
-
return z.string();
|
|
88
|
-
if (schema === "number")
|
|
89
|
-
return z.number();
|
|
90
|
-
if (schema === "boolean")
|
|
91
|
-
return z.boolean();
|
|
92
|
-
}
|
|
93
|
-
if (Array.isArray(schema)) {
|
|
94
|
-
// If it's a simple array like ["string"], take the first item
|
|
95
|
-
if (schema.length === 1)
|
|
96
|
-
return z.array(jsonToZod(schema[0]));
|
|
97
|
-
// For anything else, treat as array of any (or you could improve this)
|
|
98
|
-
return z.array(z.any());
|
|
99
|
-
}
|
|
100
|
-
if (typeof schema === "object" && schema !== null) {
|
|
101
|
-
const shape = {};
|
|
102
|
-
for (const [key, value] of Object.entries(schema)) {
|
|
103
|
-
shape[key] = jsonToZod(value);
|
|
104
|
-
}
|
|
105
|
-
return z.object(shape);
|
|
106
|
-
}
|
|
107
|
-
return z.any();
|
|
108
|
-
}
|
|
109
|
-
export async function readAgentConfig(agentDir) {
|
|
110
|
-
const mdPath = path.join(agentDir, "AGENT.md");
|
|
111
|
-
let mdContent = "";
|
|
112
|
-
try {
|
|
113
|
-
mdContent = await fs.readFile(mdPath, "utf-8");
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
mdContent = DEFAULT_AGENT_MD;
|
|
117
|
-
}
|
|
118
|
-
const parsed = matter(mdContent);
|
|
119
|
-
const config = (parsed.data || {});
|
|
120
|
-
return {
|
|
121
|
-
name: typeof config.name === "string" ? config.name : "",
|
|
122
|
-
description: typeof config.description === "string" ? config.description : "",
|
|
123
|
-
model: config.model,
|
|
124
|
-
image: config.image,
|
|
125
|
-
base: config.base,
|
|
126
|
-
plugins: config.plugins || [],
|
|
127
|
-
instructions: parsed.content.trim() || "",
|
|
128
|
-
subscribe: config.subscribe,
|
|
129
|
-
outputSchema: config.outputSchema,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
// ── Agent composition (declarative AGENT.md agents) ──────────────────
|
|
133
|
-
function composeAgentFromConfig(config, toolRegistry, model) {
|
|
134
|
-
const allToolDefinitions = {};
|
|
135
|
-
const pluginFactories = [];
|
|
136
|
-
for (const pluginItem of config.plugins) {
|
|
137
|
-
const isString = typeof pluginItem === "string";
|
|
138
|
-
const pluginName = isString ? pluginItem : pluginItem.name;
|
|
139
|
-
const pluginConfig = isString ? {} : (pluginItem.config || {});
|
|
140
|
-
const resolvedConfig = resolveConfigPaths(pluginConfig);
|
|
141
|
-
const entry = toolRegistry.get(pluginName);
|
|
142
|
-
if (!entry || entry.type !== "tool") {
|
|
143
|
-
console.warn(`[plugins] "${config.name}": tool "${pluginName}" not found — skipping`);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
pluginFactories.push({ plugin: entry.plugin, config: resolvedConfig });
|
|
147
|
-
Object.assign(allToolDefinitions, entry.toolDefinitions);
|
|
148
|
-
}
|
|
149
|
-
const plugin = (builder) => {
|
|
150
|
-
// 1. Initialize all regular tool plugins
|
|
151
|
-
for (const { plugin: toolPlugin, config: resolvedConfig } of pluginFactories) {
|
|
152
|
-
builder.use(toolPlugin({ ...resolvedConfig, model }));
|
|
153
|
-
}
|
|
154
|
-
// 2. Resolve the "Brain" (Base Plugin)
|
|
155
|
-
const baseInput = config.base || "llm";
|
|
156
|
-
const isBaseString = typeof baseInput === "string";
|
|
157
|
-
const baseName = isBaseString ? baseInput : baseInput.name;
|
|
158
|
-
const baseConfig = isBaseString ? {} : (baseInput.config || {});
|
|
159
|
-
const resolvedBaseConfig = resolveConfigPaths(baseConfig);
|
|
160
|
-
if (baseName === "llm") {
|
|
161
|
-
// Default built-in brain
|
|
162
|
-
builder.use(llmPlugin({
|
|
163
|
-
model,
|
|
164
|
-
system: config.instructions,
|
|
165
|
-
toolDefinitions: allToolDefinitions,
|
|
166
|
-
outputSchema: config.outputSchema ? jsonToZod(config.outputSchema) : undefined,
|
|
167
|
-
}));
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
// Custom autonomous brain (e.g. codex)
|
|
171
|
-
const baseEntry = toolRegistry.get(baseName);
|
|
172
|
-
if (!baseEntry || baseEntry.type !== "tool") {
|
|
173
|
-
console.error(`[plugins] "${config.name}": base plugin "${baseName}" not found or invalid. Falling back to default LLM brain.`);
|
|
174
|
-
builder.use(llmPlugin({
|
|
175
|
-
model,
|
|
176
|
-
system: config.instructions,
|
|
177
|
-
toolDefinitions: allToolDefinitions,
|
|
178
|
-
}));
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
// Use the custom plugin as the autonomous engine.
|
|
182
|
-
// It must follow the Base Plugin contract (receive model, system, tools).
|
|
183
|
-
builder.use(baseEntry.plugin({
|
|
184
|
-
...resolvedBaseConfig,
|
|
185
|
-
model,
|
|
186
|
-
system: config.instructions,
|
|
187
|
-
toolDefinitions: allToolDefinitions,
|
|
188
|
-
outputSchema: config.outputSchema ? jsonToZod(config.outputSchema) : undefined,
|
|
189
|
-
}));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
return { plugin, toolDefinitions: allToolDefinitions };
|
|
194
|
-
}
|
|
195
|
-
// ── Load tool plugins from a subdirectory (used for agent-local tools) ─
|
|
196
|
-
async function loadToolPluginsFromDir(dir) {
|
|
197
|
-
const plugins = [];
|
|
198
|
-
if (!(await fileExists(dir)))
|
|
199
|
-
return plugins;
|
|
200
|
-
try {
|
|
201
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
202
|
-
for (const entry of entries) {
|
|
203
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
204
|
-
continue;
|
|
205
|
-
const pluginDir = path.join(dir, entry.name);
|
|
206
|
-
await ensurePluginReady(pluginDir);
|
|
207
|
-
const indexPath = await findIndexFile(pluginDir);
|
|
208
|
-
if (!indexPath)
|
|
209
|
-
continue;
|
|
210
|
-
try {
|
|
211
|
-
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
212
|
-
const entryData = module.plugin || module.default || module.entry;
|
|
213
|
-
if (entryData && typeof entryData.factory === "function") {
|
|
214
|
-
plugins.push({
|
|
215
|
-
id: entry.name,
|
|
216
|
-
name: entryData.name || entry.name,
|
|
217
|
-
description: entryData.description || `Tool plugin ${entry.name}`,
|
|
218
|
-
type: "tool",
|
|
219
|
-
plugin: entryData.factory,
|
|
220
|
-
toolDefinitions: entryData.toolDefinitions || {},
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
console.warn(`[plugins] "${entry.name}" does not export a valid plugin entry (missing factory)`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
catch (err) {
|
|
228
|
-
console.error(`[plugins] Failed to load tool plugin "${entry.name}":`, err);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
catch (err) {
|
|
233
|
-
console.warn(`[plugins] Error reading directory ${dir}:`, err);
|
|
234
|
-
}
|
|
235
|
-
return plugins;
|
|
236
|
-
}
|
|
237
|
-
// ── Main unified discovery ───────────────────────────────────────────
|
|
238
|
-
/**
|
|
239
|
-
* Discover all plugins (tools + agents) from a directory.
|
|
240
|
-
*
|
|
241
|
-
* Pass 1: Load code plugins in folders without AGENT.md.
|
|
242
|
-
* - module.agent export → code-only agent
|
|
243
|
-
* - plugin/default/entry export → tool plugin
|
|
244
|
-
* Pass 2: Load agent-type plugins (folders WITH AGENT.md).
|
|
245
|
-
* - AGENT.md only → declarative agent (auto-wrapped with llmPlugin)
|
|
246
|
-
* - AGENT.md + index.ts → TS agent (user controls logic, AGENT.md for UI editing)
|
|
247
|
-
*
|
|
248
|
-
* Discovered entries are registered directly into the provided registry.
|
|
249
|
-
*/
|
|
250
|
-
export async function discoverPlugins(dir, registry, defaultModel, options) {
|
|
251
|
-
try {
|
|
252
|
-
await fs.mkdir(dir, { recursive: true });
|
|
253
|
-
}
|
|
254
|
-
catch { /* best effort */ }
|
|
255
|
-
let entries;
|
|
256
|
-
try {
|
|
257
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
258
|
-
}
|
|
259
|
-
catch {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
// Classify each subdirectory
|
|
263
|
-
const codeDirs = [];
|
|
264
|
-
const agentDirs = [];
|
|
265
|
-
for (const entry of entries) {
|
|
266
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
267
|
-
continue;
|
|
268
|
-
const pluginDir = path.join(dir, entry.name);
|
|
269
|
-
const hasAgentMd = await fileExists(path.join(pluginDir, "AGENT.md"));
|
|
270
|
-
const hasIndex = !!(await findIndexFile(pluginDir));
|
|
271
|
-
const hasPkg = await fileExists(path.join(pluginDir, "package.json"));
|
|
272
|
-
if (hasAgentMd) {
|
|
273
|
-
agentDirs.push({ dir: pluginDir, hasIndex: hasIndex || hasPkg });
|
|
274
|
-
}
|
|
275
|
-
else if (hasIndex || hasPkg) {
|
|
276
|
-
codeDirs.push(pluginDir);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
// Pass 1: code-only agents and tool plugins
|
|
280
|
-
for (const pluginDir of codeDirs) {
|
|
281
|
-
await ensurePluginReady(pluginDir);
|
|
282
|
-
const indexPath = await findIndexFile(pluginDir);
|
|
283
|
-
if (!indexPath)
|
|
284
|
-
continue;
|
|
285
|
-
try {
|
|
286
|
-
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
287
|
-
const codeAgentDef = module.agent;
|
|
288
|
-
const entryData = module.plugin || module.default || module.entry;
|
|
289
|
-
if (codeAgentDef && typeof codeAgentDef.factory === "function") {
|
|
290
|
-
const meta = await getPluginMetadata(pluginDir);
|
|
291
|
-
const folderName = path.basename(pluginDir);
|
|
292
|
-
const id = folderName; // Folder name as slug
|
|
293
|
-
let name = codeAgentDef.name || meta.name;
|
|
294
|
-
if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
|
|
295
|
-
name = toTitleCaseFromSlug(folderName);
|
|
296
|
-
}
|
|
297
|
-
const description = codeAgentDef.description || meta.description || "Code Agent";
|
|
298
|
-
registry.register({
|
|
299
|
-
id,
|
|
300
|
-
name,
|
|
301
|
-
description,
|
|
302
|
-
type: "agent",
|
|
303
|
-
plugin: codeAgentDef.factory({ ...options, model: defaultModel }),
|
|
304
|
-
capabilities: codeAgentDef.capabilities,
|
|
305
|
-
subscribe: codeAgentDef.subscribe,
|
|
306
|
-
folder: pluginDir,
|
|
307
|
-
});
|
|
308
|
-
console.log(`[plugins] Loaded code-only agent: ${id} (${name}) — ${description}`);
|
|
309
|
-
}
|
|
310
|
-
else if (entryData && typeof entryData.factory === "function") {
|
|
311
|
-
const meta = await getPluginMetadata(pluginDir);
|
|
312
|
-
const folderName = path.basename(pluginDir);
|
|
313
|
-
const id = folderName;
|
|
314
|
-
let name = entryData.name || meta.name;
|
|
315
|
-
if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
|
|
316
|
-
name = toTitleCaseFromSlug(folderName);
|
|
317
|
-
}
|
|
318
|
-
const pluginEntry = {
|
|
319
|
-
id,
|
|
320
|
-
name,
|
|
321
|
-
description: entryData.description || meta.description || "Tool plugin",
|
|
322
|
-
type: "tool",
|
|
323
|
-
plugin: entryData.factory,
|
|
324
|
-
toolDefinitions: entryData.toolDefinitions || {},
|
|
325
|
-
folder: pluginDir,
|
|
326
|
-
};
|
|
327
|
-
registry.register(pluginEntry);
|
|
328
|
-
console.log(`[plugins] Loaded tool: ${id} (${pluginEntry.name})`);
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
console.warn(`[plugins] "${path.basename(pluginDir)}" does not export a valid plugin (missing factory)`);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
catch (err) {
|
|
335
|
-
console.error(`[plugins] Failed to load "${path.basename(pluginDir)}":`, err);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
// Pass 2: agent plugins
|
|
339
|
-
for (const { dir: agentDir, hasIndex } of agentDirs) {
|
|
340
|
-
const folderName = path.basename(agentDir);
|
|
341
|
-
try {
|
|
342
|
-
if (hasIndex) {
|
|
343
|
-
// TS Agent — has AGENT.md + code. User controls logic; AGENT.md is for UI editing.
|
|
344
|
-
await ensurePluginReady(agentDir);
|
|
345
|
-
const indexPath = await findIndexFile(agentDir);
|
|
346
|
-
if (!indexPath)
|
|
347
|
-
continue;
|
|
348
|
-
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
349
|
-
const definition = module.agent || module.plugin || module.default || module.entry;
|
|
350
|
-
if (definition && typeof definition.factory === "function") {
|
|
351
|
-
const config = await readAgentConfig(agentDir);
|
|
352
|
-
const meta = await getPluginMetadata(agentDir);
|
|
353
|
-
const id = folderName;
|
|
354
|
-
let name = config.name || definition.name || meta.name;
|
|
355
|
-
if (!name || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(name)) {
|
|
356
|
-
name = toTitleCaseFromSlug(folderName);
|
|
357
|
-
}
|
|
358
|
-
const description = definition.description || config.description || "TS Agent";
|
|
359
|
-
registry.register({
|
|
360
|
-
id,
|
|
361
|
-
name,
|
|
362
|
-
description,
|
|
363
|
-
type: "agent",
|
|
364
|
-
plugin: definition.factory({ ...options, model: defaultModel }),
|
|
365
|
-
capabilities: definition.capabilities,
|
|
366
|
-
subscribe: definition.subscribe || config.subscribe,
|
|
367
|
-
folder: agentDir,
|
|
368
|
-
});
|
|
369
|
-
console.log(`[plugins] Loaded TS agent: ${id} (${name}) — ${description}`);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
// Declarative Agent — AGENT.md only, auto-wrapped with llmPlugin.
|
|
374
|
-
const config = await readAgentConfig(agentDir);
|
|
375
|
-
const meta = await getPluginMetadata(agentDir);
|
|
376
|
-
const id = folderName;
|
|
377
|
-
let resolvedName = config.name || meta.name;
|
|
378
|
-
if (!resolvedName || /^Unnamed\s+(Plugin|Tool|Agent)$/i.test(resolvedName)) {
|
|
379
|
-
resolvedName = toTitleCaseFromSlug(folderName);
|
|
380
|
-
}
|
|
381
|
-
const resolvedDescription = config.description || meta.description || "No description";
|
|
382
|
-
const agentModel = config.model
|
|
383
|
-
? createModel({ ...options, model: config.model })
|
|
384
|
-
: defaultModel;
|
|
385
|
-
// Load agent-local tool plugins
|
|
386
|
-
const localPlugins = await loadToolPluginsFromDir(path.join(agentDir, "plugins"));
|
|
387
|
-
// Scoped registry: global tools + local tools
|
|
388
|
-
const scopedRegistry = new PluginRegistry();
|
|
389
|
-
for (const p of registry.getTools()) {
|
|
390
|
-
scopedRegistry.register(p);
|
|
391
|
-
}
|
|
392
|
-
for (const p of localPlugins) {
|
|
393
|
-
scopedRegistry.register(p);
|
|
394
|
-
}
|
|
395
|
-
// Initialize AGENT.md if missing
|
|
396
|
-
const agentMdPath = path.join(agentDir, "AGENT.md");
|
|
397
|
-
if (!(await fileExists(agentMdPath))) {
|
|
398
|
-
const content = DEFAULT_AGENT_MD.replace("name: Agent", `name: ${resolvedName}`);
|
|
399
|
-
await fs.writeFile(agentMdPath, content, "utf-8");
|
|
400
|
-
console.log(`[plugins] Initialized ${resolvedName}/AGENT.md`);
|
|
401
|
-
}
|
|
402
|
-
const { plugin, toolDefinitions } = composeAgentFromConfig(config, scopedRegistry, agentModel);
|
|
403
|
-
registry.register({
|
|
404
|
-
id,
|
|
405
|
-
name: resolvedName,
|
|
406
|
-
description: resolvedDescription,
|
|
407
|
-
type: "agent",
|
|
408
|
-
plugin,
|
|
409
|
-
capabilities: Object.fromEntries(Object.entries(toolDefinitions).map(([name, def]) => [name, def.description])),
|
|
410
|
-
subscribe: config.subscribe,
|
|
411
|
-
folder: agentDir,
|
|
412
|
-
});
|
|
413
|
-
console.log(`[plugins] Loaded agent: ${id} (${resolvedName}) — ${resolvedDescription}${config.model ? ` (model: ${config.model})` : ""}`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
catch (err) {
|
|
417
|
-
if (err.code !== "ENOENT") {
|
|
418
|
-
console.warn(`[plugins] Error loading "${folderName}":`, err);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
// ── Lightweight listing (for API) ────────────────────────────────────
|
|
424
|
-
export async function listPlugins(dir) {
|
|
425
|
-
const plugins = [];
|
|
426
|
-
try {
|
|
427
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
428
|
-
for (const entry of entries) {
|
|
429
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
430
|
-
continue;
|
|
431
|
-
const pluginDir = path.join(dir, entry.name);
|
|
432
|
-
const hasAgentMd = await fileExists(path.join(pluginDir, "AGENT.md"));
|
|
433
|
-
const hasCode = await fileExists(path.join(pluginDir, "package.json"))
|
|
434
|
-
|| !!(await findIndexFile(pluginDir));
|
|
435
|
-
if (hasAgentMd) {
|
|
436
|
-
const config = await readAgentConfig(pluginDir);
|
|
437
|
-
const { name: fallbackName, description: fallbackDescription } = await getPluginMetadata(pluginDir);
|
|
438
|
-
plugins.push({
|
|
439
|
-
name: config.name || fallbackName || "Unnamed Agent",
|
|
440
|
-
description: config.description || fallbackDescription || "No description",
|
|
441
|
-
folder: pluginDir,
|
|
442
|
-
type: "agent",
|
|
443
|
-
hasAgentMd: true,
|
|
444
|
-
image: config.image,
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
else if (hasCode) {
|
|
448
|
-
await ensurePluginReady(pluginDir);
|
|
449
|
-
const indexPath = await findIndexFile(pluginDir);
|
|
450
|
-
const { name: fallbackName, description: fallbackDescription } = await getPluginMetadata(pluginDir);
|
|
451
|
-
if (!indexPath) {
|
|
452
|
-
plugins.push({
|
|
453
|
-
name: fallbackName,
|
|
454
|
-
description: fallbackDescription,
|
|
455
|
-
folder: pluginDir,
|
|
456
|
-
type: "tool",
|
|
457
|
-
hasAgentMd: false,
|
|
458
|
-
});
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
try {
|
|
462
|
-
const module = await import(pathToFileURL(indexPath).href + `?update=${Date.now()}`);
|
|
463
|
-
const codeAgentDef = module.agent;
|
|
464
|
-
const toolEntry = module.plugin || module.default || module.entry;
|
|
465
|
-
if (codeAgentDef && typeof codeAgentDef.factory === "function") {
|
|
466
|
-
plugins.push({
|
|
467
|
-
name: codeAgentDef.name || fallbackName || "Unnamed Agent",
|
|
468
|
-
description: codeAgentDef.description || fallbackDescription || "Code Agent",
|
|
469
|
-
folder: pluginDir,
|
|
470
|
-
type: "agent",
|
|
471
|
-
hasAgentMd: false,
|
|
472
|
-
image: codeAgentDef.image,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
else if (toolEntry && typeof toolEntry.factory === "function") {
|
|
476
|
-
plugins.push({
|
|
477
|
-
name: toolEntry.name || fallbackName,
|
|
478
|
-
description: toolEntry.description || fallbackDescription,
|
|
479
|
-
folder: pluginDir,
|
|
480
|
-
type: "tool",
|
|
481
|
-
hasAgentMd: false,
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
catch {
|
|
486
|
-
plugins.push({
|
|
487
|
-
name: fallbackName,
|
|
488
|
-
description: fallbackDescription,
|
|
489
|
-
folder: pluginDir,
|
|
490
|
-
type: "tool",
|
|
491
|
-
hasAgentMd: false,
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
catch { /* directory doesn't exist */ }
|
|
498
|
-
return plugins;
|
|
499
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified Plugin Registry
|
|
3
|
-
*
|
|
4
|
-
* Holds both tool plugins and agent plugins in a single registry.
|
|
5
|
-
* Built-in entries are registered at startup; community plugins
|
|
6
|
-
* are discovered from ~/.openbot/plugins/.
|
|
7
|
-
*/
|
|
8
|
-
export class PluginRegistry {
|
|
9
|
-
constructor() {
|
|
10
|
-
this.plugins = new Map();
|
|
11
|
-
}
|
|
12
|
-
register(entry) {
|
|
13
|
-
if (this.plugins.has(entry.name)) {
|
|
14
|
-
console.warn(`Plugin "${entry.name}" is already registered — overwriting`);
|
|
15
|
-
}
|
|
16
|
-
this.plugins.set(entry.name, entry);
|
|
17
|
-
}
|
|
18
|
-
get(name) {
|
|
19
|
-
return this.plugins.get(name);
|
|
20
|
-
}
|
|
21
|
-
has(name) {
|
|
22
|
-
return this.plugins.has(name);
|
|
23
|
-
}
|
|
24
|
-
getAll() {
|
|
25
|
-
return Array.from(this.plugins.values());
|
|
26
|
-
}
|
|
27
|
-
getNames() {
|
|
28
|
-
return Array.from(this.plugins.keys());
|
|
29
|
-
}
|
|
30
|
-
getAgents() {
|
|
31
|
-
return this.getAll().filter(p => p.type === "agent");
|
|
32
|
-
}
|
|
33
|
-
getTools() {
|
|
34
|
-
return this.getAll().filter(p => p.type === "tool");
|
|
35
|
-
}
|
|
36
|
-
/** Returns agent IDs as a tuple suitable for z.enum(). */
|
|
37
|
-
getAgentIds() {
|
|
38
|
-
const ids = this.getAgents().map(a => a.id);
|
|
39
|
-
if (ids.length === 0) {
|
|
40
|
-
throw new Error("No agents registered — at least one agent is required");
|
|
41
|
-
}
|
|
42
|
-
return ids;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { ensurePluginReady } from "./plugin-loader.js";
|
|
5
|
-
/**
|
|
6
|
-
* Discover and load TS-defined agents from a directory.
|
|
7
|
-
*
|
|
8
|
-
* Scans each subdirectory for a package.json and an index file.
|
|
9
|
-
*
|
|
10
|
-
* @param agentsDir Absolute path to the agents directory (e.g. ~/.openbot/agents)
|
|
11
|
-
* @param defaultModel Language model to use for agent LLMs
|
|
12
|
-
* @param options Optional API keys for creating specific models
|
|
13
|
-
* @returns Array of discovered agent entries ready for registration
|
|
14
|
-
*/
|
|
15
|
-
export async function discoverTsAgents(agentsDir, defaultModel, options) {
|
|
16
|
-
const agents = [];
|
|
17
|
-
try {
|
|
18
|
-
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
19
|
-
for (const entry of entries) {
|
|
20
|
-
if (!entry.isDirectory())
|
|
21
|
-
continue;
|
|
22
|
-
if (entry.name.startsWith(".") || entry.name.startsWith("_"))
|
|
23
|
-
continue;
|
|
24
|
-
const agentDir = path.join(agentsDir, entry.name);
|
|
25
|
-
// We only consider it a TS agent if it doesn't have an AGENT.md
|
|
26
|
-
// (This avoids double-loading if someone has both for some reason)
|
|
27
|
-
const mdPath = path.join(agentDir, "AGENT.md");
|
|
28
|
-
const hasMd = await fs.access(mdPath).then(() => true).catch(() => false);
|
|
29
|
-
if (hasMd)
|
|
30
|
-
continue;
|
|
31
|
-
// Check for package.json to see if it's a package
|
|
32
|
-
const pkgPath = path.join(agentDir, "package.json");
|
|
33
|
-
const hasPackageJson = await fs.access(pkgPath).then(() => true).catch(() => false);
|
|
34
|
-
if (!hasPackageJson)
|
|
35
|
-
continue;
|
|
36
|
-
try {
|
|
37
|
-
// 1. Ensure dependencies and build are ready
|
|
38
|
-
await ensurePluginReady(agentDir);
|
|
39
|
-
// 2. Find index file
|
|
40
|
-
let indexPath;
|
|
41
|
-
const possibleIndices = ["dist/index.js", "index.js", "index.ts"];
|
|
42
|
-
for (const file of possibleIndices) {
|
|
43
|
-
try {
|
|
44
|
-
const fullPath = path.join(agentDir, file);
|
|
45
|
-
await fs.access(fullPath);
|
|
46
|
-
indexPath = fullPath;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (!indexPath)
|
|
54
|
-
continue;
|
|
55
|
-
// 3. Import and instantiate
|
|
56
|
-
const moduleUrl = pathToFileURL(indexPath).href;
|
|
57
|
-
const module = await import(moduleUrl);
|
|
58
|
-
// Support 'agent', 'plugin', 'default', or 'entry'
|
|
59
|
-
const definition = module.agent || module.plugin || module.default || module.entry;
|
|
60
|
-
if (definition && typeof definition.factory === "function") {
|
|
61
|
-
const name = definition.name || entry.name;
|
|
62
|
-
const description = definition.description || "TS Agent";
|
|
63
|
-
agents.push({
|
|
64
|
-
name,
|
|
65
|
-
description,
|
|
66
|
-
plugin: definition.factory({ ...options, model: defaultModel }),
|
|
67
|
-
capabilities: definition.capabilities,
|
|
68
|
-
subscribe: definition.subscribe,
|
|
69
|
-
});
|
|
70
|
-
console.log(`[agents] Loaded TS agent: ${name} — ${description}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
console.warn(`[agents] Failed to load TS agent package "${entry.name}":`, err);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
// Agents directory doesn't exist
|
|
80
|
-
}
|
|
81
|
-
return agents;
|
|
82
|
-
}
|