openbot 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/AGENTS.md +68 -0
  3. package/CONTRIBUTING.md +74 -0
  4. package/LICENSE +21 -0
  5. package/README.md +117 -14
  6. package/dist/agents/system.js +106 -0
  7. package/dist/app/cli.js +27 -0
  8. package/dist/app/config.js +64 -0
  9. package/dist/app/server.js +237 -0
  10. package/dist/app/utils.js +35 -0
  11. package/dist/harness/agent-harness.js +45 -0
  12. package/dist/harness/mcp.js +61 -0
  13. package/dist/harness/orchestrator.js +273 -0
  14. package/dist/harness/process.js +7 -0
  15. package/dist/plugins/ai-sdk.js +141 -0
  16. package/dist/plugins/delegation.js +52 -0
  17. package/dist/plugins/mcp.js +140 -0
  18. package/dist/plugins/storage.js +502 -0
  19. package/dist/plugins/ui.js +47 -0
  20. package/dist/registry/plugins.js +73 -0
  21. package/dist/services/storage.js +724 -0
  22. package/docs/README.md +7 -0
  23. package/docs/agents.md +83 -0
  24. package/docs/architecture.md +34 -0
  25. package/docs/plugins.md +77 -0
  26. package/logo-black.png +0 -0
  27. package/{dist/assets/logo.js → logo-black.svg} +24 -24
  28. package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
  29. package/package.json +6 -5
  30. package/src/agents/system.ts +112 -0
  31. package/src/app/cli.ts +38 -0
  32. package/src/app/config.ts +104 -0
  33. package/src/app/server.ts +284 -0
  34. package/src/app/types.ts +476 -0
  35. package/src/app/utils.ts +43 -0
  36. package/src/assets/icon.svg +1 -0
  37. package/src/harness/agent-harness.ts +58 -0
  38. package/src/harness/mcp.ts +78 -0
  39. package/src/harness/orchestrator.ts +342 -0
  40. package/src/harness/process.ts +9 -0
  41. package/src/harness/types.ts +34 -0
  42. package/src/plugins/ai-sdk.ts +197 -0
  43. package/src/plugins/delegation.ts +60 -0
  44. package/src/plugins/mcp.ts +154 -0
  45. package/src/plugins/storage.ts +725 -0
  46. package/src/plugins/ui.ts +57 -0
  47. package/src/registry/plugins.ts +85 -0
  48. package/src/services/storage.ts +957 -0
  49. package/tsconfig.json +18 -0
  50. package/dist/agents/agent-creator.js +0 -74
  51. package/dist/agents/browser-agent.js +0 -31
  52. package/dist/agents/os-agent.js +0 -32
  53. package/dist/agents/planner-agent.js +0 -32
  54. package/dist/agents/topic-agent.js +0 -46
  55. package/dist/architecture/execution-engine.js +0 -151
  56. package/dist/architecture/intent-classifier.js +0 -26
  57. package/dist/architecture/planner.js +0 -106
  58. package/dist/automation-worker.js +0 -121
  59. package/dist/automations.js +0 -52
  60. package/dist/cli.js +0 -279
  61. package/dist/config.js +0 -53
  62. package/dist/core/agents.js +0 -41
  63. package/dist/core/delegation.js +0 -230
  64. package/dist/core/manager.js +0 -96
  65. package/dist/core/plugins.js +0 -74
  66. package/dist/core/router.js +0 -191
  67. package/dist/handlers/init.js +0 -29
  68. package/dist/handlers/session-change.js +0 -21
  69. package/dist/handlers/settings.js +0 -47
  70. package/dist/handlers/tab-change.js +0 -14
  71. package/dist/installers.js +0 -156
  72. package/dist/marketplace.js +0 -80
  73. package/dist/model-catalog.js +0 -132
  74. package/dist/model-defaults.js +0 -25
  75. package/dist/models.js +0 -47
  76. package/dist/open-bot.js +0 -51
  77. package/dist/orchestrator/direct-invocation.js +0 -13
  78. package/dist/orchestrator/events.js +0 -36
  79. package/dist/orchestrator/state.js +0 -54
  80. package/dist/orchestrator.js +0 -422
  81. package/dist/plugins/agent/index.js +0 -81
  82. package/dist/plugins/approval/index.js +0 -100
  83. package/dist/plugins/brain/identity.js +0 -77
  84. package/dist/plugins/brain/index.js +0 -204
  85. package/dist/plugins/brain/memory.js +0 -120
  86. package/dist/plugins/brain/prompt.js +0 -46
  87. package/dist/plugins/brain/types.js +0 -45
  88. package/dist/plugins/brain/ui.js +0 -7
  89. package/dist/plugins/browser/index.js +0 -629
  90. package/dist/plugins/browser/ui.js +0 -13
  91. package/dist/plugins/file-system/index.js +0 -171
  92. package/dist/plugins/file-system/ui.js +0 -6
  93. package/dist/plugins/llm/context-budget.js +0 -139
  94. package/dist/plugins/llm/context-shaping.js +0 -177
  95. package/dist/plugins/llm/index.js +0 -380
  96. package/dist/plugins/memory/index.js +0 -220
  97. package/dist/plugins/memory/memory.js +0 -122
  98. package/dist/plugins/memory/prompt.js +0 -55
  99. package/dist/plugins/memory/types.js +0 -45
  100. package/dist/plugins/meta-agent/index.js +0 -570
  101. package/dist/plugins/meta-agent/ui.js +0 -11
  102. package/dist/plugins/shell/index.js +0 -100
  103. package/dist/plugins/shell/ui.js +0 -6
  104. package/dist/plugins/skills/index.js +0 -286
  105. package/dist/plugins/skills/types.js +0 -50
  106. package/dist/plugins/skills/ui.js +0 -12
  107. package/dist/registry/agent-registry.js +0 -35
  108. package/dist/registry/index.js +0 -2
  109. package/dist/registry/plugin-loader.js +0 -499
  110. package/dist/registry/plugin-registry.js +0 -44
  111. package/dist/registry/ts-agent-loader.js +0 -82
  112. package/dist/registry/yaml-agent-loader.js +0 -246
  113. package/dist/runtime/execution-trace.js +0 -41
  114. package/dist/runtime/intent-routing.js +0 -26
  115. package/dist/runtime/openbot-runtime.js +0 -354
  116. package/dist/server.js +0 -890
  117. package/dist/session.js +0 -179
  118. package/dist/ui/block.js +0 -12
  119. package/dist/ui/header.js +0 -52
  120. package/dist/ui/layout.js +0 -26
  121. package/dist/ui/navigation.js +0 -15
  122. package/dist/ui/settings.js +0 -106
  123. package/dist/ui/skills.js +0 -7
  124. package/dist/ui/thread.js +0 -16
  125. package/dist/ui/widgets/action-list.js +0 -2
  126. package/dist/ui/widgets/approval-card.js +0 -9
  127. package/dist/ui/widgets/code-snippet.js +0 -2
  128. package/dist/ui/widgets/data-block.js +0 -2
  129. package/dist/ui/widgets/data-table.js +0 -2
  130. package/dist/ui/widgets/delegation.js +0 -29
  131. package/dist/ui/widgets/empty-state.js +0 -2
  132. package/dist/ui/widgets/index.js +0 -23
  133. package/dist/ui/widgets/inquiry.js +0 -7
  134. package/dist/ui/widgets/key-value.js +0 -2
  135. package/dist/ui/widgets/progress-step.js +0 -2
  136. package/dist/ui/widgets/resource-card.js +0 -2
  137. package/dist/ui/widgets/status.js +0 -2
  138. package/dist/ui/widgets/todo-list.js +0 -2
  139. package/dist/version.js +0 -62
  140. /package/dist/{types.js → app/types.js} +0 -0
  141. /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
- }