openbot 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ const program = new Command();
13
13
  program
14
14
  .name("openbot")
15
15
  .description("OpenBot CLI - Secure and easy configuration")
16
- .version("0.1.28");
16
+ .version("0.1.29");
17
17
  /**
18
18
  * Check if a GitHub repository exists.
19
19
  */
package/dist/open-bot.js CHANGED
@@ -16,7 +16,7 @@ import { shellPlugin, shellToolDefinitions } from "./plugins/shell/index.js";
16
16
  import { fileSystemPlugin, fileSystemToolDefinitions } from "./plugins/file-system/index.js";
17
17
  import { approvalPlugin } from "./plugins/approval/index.js";
18
18
  // Registry
19
- import { PluginRegistry, AgentRegistry, discoverYamlAgents, loadPluginsFromDir } from "./registry/index.js";
19
+ import { PluginRegistry, AgentRegistry, discoverYamlAgents, discoverTsAgents, loadPluginsFromDir } from "./registry/index.js";
20
20
  /**
21
21
  * Create the OpenBot manager agent.
22
22
  *
@@ -81,8 +81,12 @@ export async function createOpenBot(options) {
81
81
  subscribe: ["manager:completion"],
82
82
  });
83
83
  // Discover community / user agents from ~/.openbot/agents/
84
- const yamlAgents = await discoverYamlAgents(path.join(resolvedBaseDir, "agents"), pluginRegistry, model, options);
85
- for (const agent of yamlAgents) {
84
+ const agentsDir = path.join(resolvedBaseDir, "agents");
85
+ const [yamlAgents, tsAgents] = await Promise.all([
86
+ discoverYamlAgents(agentsDir, pluginRegistry, model, options),
87
+ discoverTsAgents(agentsDir, model, options),
88
+ ]);
89
+ for (const agent of [...yamlAgents, ...tsAgents]) {
86
90
  agentRegistry.register(agent);
87
91
  }
88
92
  // ─── Compose the Melony App ──────────────────────────────────────
@@ -131,6 +135,61 @@ export async function createOpenBot(options) {
131
135
  return `- **${a.name}**: ${a.description}${tools ? `\n Capabilities:\n${tools}` : ""}`;
132
136
  })
133
137
  .join("\n\n");
138
+ // 2.5 Command Prefix Router
139
+ // Allows bypassing the manager using "/agent task" (e.g. "/os list files")
140
+ app.on("user:text", async function* (event, { state }) {
141
+ const content = event.data.content.trim();
142
+ if (content.startsWith("/")) {
143
+ const firstSpace = content.indexOf(" ");
144
+ const prefix = firstSpace === -1 ? content.slice(1) : content.slice(1, firstSpace);
145
+ const task = firstSpace === -1 ? "" : content.slice(firstSpace + 1).trim();
146
+ if (agentNames.includes(prefix)) {
147
+ // Direct route to specialized agent
148
+ state.lastDirectAgent = prefix;
149
+ yield {
150
+ type: `agent:${prefix}:input`,
151
+ data: { content: task },
152
+ };
153
+ return;
154
+ }
155
+ }
156
+ // Default: send to manager for reasoning/delegation
157
+ state.lastDirectAgent = undefined;
158
+ yield { type: "manager:input", data: event.data };
159
+ });
160
+ // 2.6 Global Tool Result Dispatcher
161
+ // Routes tool results to the appropriate agent's internal loop.
162
+ // taskResult means that the loop is complete.
163
+ app.on("action:taskResult", async function* (event, { state, suspend }) {
164
+ const s = state;
165
+ // 1. Direct route (prefix command)
166
+ if (s.lastDirectAgent) {
167
+ // suspend not to go to infinite loop
168
+ // suspend({
169
+ // type: `agent:${s.lastDirectAgent}:result`,
170
+ // data: event.data,
171
+ // } as ChatEvent);
172
+ yield {
173
+ type: `agent:${s.lastDirectAgent}:result`,
174
+ data: event.data,
175
+ };
176
+ }
177
+ // 2. Delegation route (manager-led)
178
+ // Find which agent has a pending task
179
+ const pendingAgentName = Object.keys(s.pendingAgentTasks || {}).find((name) => s.pendingAgentTasks[name]);
180
+ if (pendingAgentName) {
181
+ yield {
182
+ type: `agent:${pendingAgentName}:result`,
183
+ data: event.data,
184
+ };
185
+ return;
186
+ }
187
+ // 3. Manager route (brain tools, etc.)
188
+ yield {
189
+ type: "manager:result",
190
+ data: event.data,
191
+ };
192
+ });
134
193
  app.use(llmPlugin({
135
194
  model: model,
136
195
  system: async (context) => {
@@ -139,21 +198,23 @@ export async function createOpenBot(options) {
139
198
  ]);
140
199
  return `${brainPrompt}
141
200
 
142
- ## Delegation & Specialized Agents
143
- You are the **Manager Agent**. Your primary role is to orchestrate tasks by delegating them to specialized agents when appropriate.
144
- If a task falls outside your core capabilities (memory and orchestration), you **MUST** use the \`delegateTask\` tool.
201
+ ## Core Role: Manager Agent
202
+ You are the **Manager Agent**. You have exactly two jobs:
203
+ 1. **Task Delegation**: Orchestrate tasks by delegating them to specialized agents.
204
+ 2. **Memory & Identity**: Manage your long-term memory and identity using your core brain tools (\`remember\`, \`recall\`, \`updateIdentity\`, etc.).
205
+
206
+ ### Delegation & Reporting Guidelines:
207
+ - **Delegate by Default**: If a task requires capabilities outside of memory/identity management (like shell access, file operations, web browsing), you **MUST** delegate to the appropriate agent.
208
+ - **Be Concise**: When a sub-agent completes a task, provide a **nice, concise summary**. Sub-agents provide detailed outputs in the background; your response should be a brief, high-level answer to the user. Do not repeat details unless necessary.
209
+ - **Detailed Delegation**: When calling \`delegateTask\`, provide a thorough description of the task so the sub-agent has full context.
145
210
 
146
211
  ### Available Agents:
147
212
  ${agentDescriptions}
148
213
 
149
- ### Delegation Guidelines:
150
- 1. **Choose the Best Expert**: Analyze the user's request and select the agent whose description most closely matches the required expertise.
151
- 2. **Task Description**: When delegating, provide a clear and detailed task description. Include any context the agent might need to succeed.
152
- 3. **No "I Can't"**: If an agent is available that can handle a request, do not tell the user you cannot do it. Simply delegate.
153
- 4. **Summary**: Once an agent returns a result, summarize the findings or actions for the user.
154
-
155
- Example: If the user asks to "check the weather", and you see a 'browser' agent, delegate the task to it.`;
214
+ Remember: You are the orchestrator. Let the specialized agents do the work, and you provide the concise final answer.`;
156
215
  },
216
+ promptInputType: "manager:input",
217
+ actionResultInputType: "manager:result",
157
218
  completionEventType: "manager:completion",
158
219
  toolDefinitions: {
159
220
  ...brainToolDefinitions,
@@ -184,9 +245,10 @@ Example: If the user asks to "check the weather", and you see a 'browser' agent,
184
245
  const s = state;
185
246
  const pending = s.pendingAgentTasks?.[agent.name];
186
247
  if (pending) {
248
+ // This was a delegated task — bridge back to the manager's tool call
187
249
  delete s.pendingAgentTasks[agent.name];
188
250
  yield {
189
- type: "action:taskResult",
251
+ type: "manager:result",
190
252
  data: {
191
253
  action: "delegateTask",
192
254
  toolCallId: pending.toolCallId,
@@ -194,6 +256,14 @@ Example: If the user asks to "check the weather", and you see a 'browser' agent,
194
256
  },
195
257
  };
196
258
  }
259
+ else {
260
+ // This was a direct command (prefix) — show output directly as assistant text
261
+ yield {
262
+ type: "assistant:text",
263
+ data: { content: event.data.content },
264
+ meta: { agent: agent.name }
265
+ };
266
+ }
197
267
  });
198
268
  }
199
269
  // 6. Init handlers
@@ -22,10 +22,11 @@ export const llmPlugin = (options) => (builder) => {
22
22
  state.messages.push(newMessage);
23
23
  // Evaluate dynamic system prompt if it's a function
24
24
  const systemPrompt = typeof system === "function" ? await system(context) : system;
25
+ const recentMessages = getRecentHistory(state.messages, 20);
25
26
  const result = streamText({
26
27
  model,
27
28
  system: systemPrompt,
28
- messages: getRecentHistory(state.messages, 20).map(m => m.role === "system" ? { role: "user", content: `System: ${m.content}` } : m),
29
+ messages: recentMessages,
29
30
  tools: toolDefinitions,
30
31
  });
31
32
  let assistantText = "";
@@ -1,4 +1,5 @@
1
1
  export { PluginRegistry } from "./plugin-registry.js";
2
2
  export { AgentRegistry } from "./agent-registry.js";
3
3
  export { discoverYamlAgents, listYamlAgents } from "./yaml-agent-loader.js";
4
+ export { discoverTsAgents } from "./ts-agent-loader.js";
4
5
  export { loadPluginsFromDir } from "./plugin-loader.js";
@@ -0,0 +1,82 @@
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.yaml
26
+ // (This avoids double-loading if someone has both for some reason)
27
+ const yamlPath = path.join(agentDir, "agent.yaml");
28
+ const hasYaml = await fs.access(yamlPath).then(() => true).catch(() => false);
29
+ if (hasYaml)
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbot",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {