openbot 0.3.6 → 0.4.2

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 (104) hide show
  1. package/README.md +15 -16
  2. package/dist/app/agent-ids.js +4 -0
  3. package/dist/app/cli.js +1 -1
  4. package/dist/app/config.js +10 -19
  5. package/dist/app/server.js +208 -17
  6. package/dist/bus/services.js +34 -124
  7. package/dist/harness/agent-invoke-run.js +44 -0
  8. package/dist/harness/agent-turn.js +99 -0
  9. package/dist/harness/channel-participants.js +40 -0
  10. package/dist/harness/constants.js +2 -0
  11. package/dist/harness/context-meter.js +97 -0
  12. package/dist/harness/context.js +95 -47
  13. package/dist/harness/dispatch.js +144 -0
  14. package/dist/harness/dispatcher.js +45 -156
  15. package/dist/harness/history.js +177 -0
  16. package/dist/harness/index.js +109 -0
  17. package/dist/harness/orchestration.js +88 -0
  18. package/dist/harness/participants.js +22 -0
  19. package/dist/harness/run-harness.js +154 -0
  20. package/dist/harness/run.js +98 -0
  21. package/dist/harness/runtime-factory.js +0 -34
  22. package/dist/harness/runtime.js +57 -0
  23. package/dist/harness/todo-dispatch.js +51 -0
  24. package/dist/harness/todos.js +5 -0
  25. package/dist/harness/turn.js +79 -0
  26. package/dist/plugins/approval/index.js +120 -149
  27. package/dist/plugins/bash/index.js +195 -0
  28. package/dist/plugins/delegation/index.js +121 -32
  29. package/dist/plugins/memory/index.js +103 -14
  30. package/dist/plugins/memory/service.js +152 -0
  31. package/dist/plugins/openbot/context.js +125 -0
  32. package/dist/plugins/openbot/history.js +144 -0
  33. package/dist/plugins/openbot/index.js +71 -0
  34. package/dist/plugins/openbot/runtime.js +381 -0
  35. package/dist/plugins/openbot/system-prompt.js +25 -0
  36. package/dist/plugins/plugin-manager/index.js +189 -0
  37. package/dist/plugins/shell/index.js +2 -1
  38. package/dist/plugins/storage/files.js +67 -0
  39. package/dist/plugins/storage/index.js +750 -0
  40. package/dist/plugins/storage/service.js +1316 -0
  41. package/dist/plugins/storage-tools/index.js +2 -2
  42. package/dist/plugins/thread-namer/index.js +72 -0
  43. package/dist/plugins/thread-naming/generate-title.js +44 -0
  44. package/dist/plugins/thread-naming/index.js +103 -0
  45. package/dist/plugins/threads/index.js +114 -0
  46. package/dist/plugins/todo/index.js +24 -25
  47. package/dist/plugins/ui/index.js +109 -180
  48. package/dist/registry/plugins.js +3 -9
  49. package/dist/services/abort.js +43 -0
  50. package/dist/services/plugins/domain.js +1 -0
  51. package/dist/services/plugins/plugin-cache.js +9 -0
  52. package/dist/services/plugins/registry.js +112 -0
  53. package/dist/services/plugins/service.js +232 -0
  54. package/dist/services/plugins/types.js +1 -0
  55. package/dist/services/process.js +29 -0
  56. package/dist/services/storage.js +11 -10
  57. package/dist/services/thread-naming.js +81 -0
  58. package/docs/agents.md +15 -12
  59. package/docs/architecture.md +2 -2
  60. package/docs/plugins.md +29 -17
  61. package/docs/templates/AGENT.example.md +8 -14
  62. package/package.json +1 -2
  63. package/src/app/agent-ids.ts +5 -0
  64. package/src/app/cli.ts +1 -1
  65. package/src/app/config.ts +14 -31
  66. package/src/app/server.ts +243 -19
  67. package/src/app/types.ts +331 -187
  68. package/src/harness/index.ts +166 -0
  69. package/src/plugins/approval/index.ts +107 -188
  70. package/src/plugins/bash/index.ts +232 -0
  71. package/src/plugins/delegation/index.ts +139 -39
  72. package/src/plugins/memory/index.ts +112 -15
  73. package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
  74. package/src/plugins/openbot/context.ts +140 -0
  75. package/src/plugins/openbot/history.ts +158 -0
  76. package/src/plugins/openbot/index.ts +79 -0
  77. package/src/plugins/openbot/runtime.ts +478 -0
  78. package/src/plugins/openbot/system-prompt.ts +27 -0
  79. package/src/plugins/plugin-manager/index.ts +224 -0
  80. package/src/plugins/storage/files.ts +81 -0
  81. package/src/plugins/storage/index.ts +823 -0
  82. package/src/{services/storage.ts → plugins/storage/service.ts} +485 -105
  83. package/src/plugins/ui/index.ts +117 -221
  84. package/src/services/abort.ts +46 -0
  85. package/src/{bus/types.ts → services/plugins/domain.ts} +50 -8
  86. package/src/services/plugins/plugin-cache.ts +13 -0
  87. package/src/{registry/plugins.ts → services/plugins/registry.ts} +28 -28
  88. package/src/services/plugins/service.ts +318 -0
  89. package/src/{bus/plugin.ts → services/plugins/types.ts} +7 -3
  90. package/src/bus/services.ts +0 -954
  91. package/src/harness/context.ts +0 -365
  92. package/src/harness/dispatcher.ts +0 -379
  93. package/src/harness/mcp.ts +0 -78
  94. package/src/harness/runtime-factory.ts +0 -129
  95. package/src/harness/todo-advance.ts +0 -128
  96. package/src/plugins/ai-sdk/index.ts +0 -41
  97. package/src/plugins/ai-sdk/runtime.ts +0 -468
  98. package/src/plugins/ai-sdk/system-prompt.ts +0 -18
  99. package/src/plugins/mcp/index.ts +0 -128
  100. package/src/plugins/shell/index.ts +0 -123
  101. package/src/plugins/storage-tools/index.ts +0 -90
  102. package/src/plugins/todo/index.ts +0 -64
  103. package/src/services/plugins.ts +0 -133
  104. /package/src/{harness → services}/process.ts +0 -0
@@ -0,0 +1,318 @@
1
+ import fs from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { exec } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import {
7
+ DEFAULT_PLUGINS_DIR,
8
+ DEFAULT_BASE_DIR,
9
+ DEFAULT_MARKETPLACE_REGISTRY_URL,
10
+ loadConfig,
11
+ resolvePath,
12
+ } from '../../app/config.js';
13
+ import { invalidatePlugin } from './plugin-cache.js';
14
+ import { PluginRef } from './types.js';
15
+
16
+ const execAsync = promisify(exec);
17
+
18
+ export interface InstallOptions {
19
+ packageName: string;
20
+ version?: string;
21
+ }
22
+
23
+ export interface InstalledPlugin {
24
+ /** npm package name; doubles as the plugin id used everywhere else. */
25
+ name: string;
26
+ version: string;
27
+ }
28
+
29
+ /** One marketplace entry; matches `action:marketplace:list:result` shape. */
30
+ export type MarketplaceAgentListing = {
31
+ id: string;
32
+ name: string;
33
+ description: string;
34
+ image?: string;
35
+ instructions: string;
36
+ plugins: PluginRef[];
37
+ };
38
+
39
+ export type StarterPrompt = {
40
+ label: string;
41
+ prompt: string;
42
+ };
43
+
44
+ /** One channel entry from the marketplace. */
45
+ export type MarketplaceChannelListing = {
46
+ id: string;
47
+ name: string;
48
+ description: string;
49
+ image?: string;
50
+ spec?: string;
51
+ initialState?: Record<string, unknown>;
52
+ /** List of agent IDs that should be participants in the channel. */
53
+ participants: string[];
54
+ /** Starter prompts for the channel. */
55
+ starterPrompts?: StarterPrompt[];
56
+ };
57
+
58
+ export interface MarketplaceRegistry {
59
+ agents: MarketplaceAgentListing[];
60
+ channels: MarketplaceChannelListing[];
61
+ }
62
+
63
+ const DEFAULT_MARKETPLACE_AGENTS: MarketplaceAgentListing[] = [];
64
+ const DEFAULT_MARKETPLACE_CHANNELS: MarketplaceChannelListing[] = [];
65
+
66
+ function isRecord(value: unknown): value is Record<string, unknown> {
67
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
68
+ }
69
+
70
+ /**
71
+ * Parses JSON from a remote registry file. Supports either
72
+ * `{ "agents": [ ... ], "channels": [ ... ] }` or a top-level array (legacy agents-only).
73
+ */
74
+ export function parseMarketplaceRegistryJson(data: unknown): MarketplaceRegistry {
75
+ const isLegacyArray = Array.isArray(data);
76
+ const rawAgents: unknown = isLegacyArray
77
+ ? data
78
+ : isRecord(data) && Array.isArray(data.agents)
79
+ ? data.agents
80
+ : [];
81
+ const rawChannels: unknown =
82
+ !isLegacyArray && isRecord(data) && Array.isArray(data.channels)
83
+ ? data.channels
84
+ : isRecord(data) && Array.isArray((data as any).templates)
85
+ ? (data as any).templates
86
+ : [];
87
+
88
+ const agents: MarketplaceAgentListing[] = (Array.isArray(rawAgents) ? rawAgents : []).map(
89
+ (item, i) => {
90
+ if (!isRecord(item)) {
91
+ throw new Error(`agents[${i}]: expected object`);
92
+ }
93
+ const id = item.id;
94
+ const name = item.name;
95
+ const description = item.description;
96
+ const instructions = item.instructions;
97
+ const pluginsRaw = item.plugins;
98
+ if (typeof id !== 'string' || !id)
99
+ throw new Error(`agents[${i}].id must be a non-empty string`);
100
+ if (typeof name !== 'string') throw new Error(`agents[${i}].name must be a string`);
101
+ if (typeof description !== 'string')
102
+ throw new Error(`agents[${i}].description must be a string`);
103
+ if (typeof instructions !== 'string') {
104
+ throw new Error(`agents[${i}].instructions must be a string`);
105
+ }
106
+ if (!Array.isArray(pluginsRaw)) throw new Error(`agents[${i}].plugins must be an array`);
107
+ const plugins: PluginRef[] = pluginsRaw.map((p, j) => {
108
+ if (!isRecord(p) || typeof p.id !== 'string' || !p.id) {
109
+ throw new Error(`agents[${i}].plugins[${j}]: expected { "id": string, "config"?: object }`);
110
+ }
111
+ const ref: PluginRef = { id: p.id };
112
+ if (p.config !== undefined) {
113
+ if (!isRecord(p.config))
114
+ throw new Error(`agents[${i}].plugins[${j}].config must be an object`);
115
+ ref.config = p.config;
116
+ }
117
+ return ref;
118
+ });
119
+ const listing: MarketplaceAgentListing = { id, name, description, instructions, plugins };
120
+ if (item.image !== undefined) {
121
+ if (typeof item.image !== 'string') throw new Error(`agents[${i}].image must be a string`);
122
+ listing.image = item.image;
123
+ }
124
+ return listing;
125
+ },
126
+ );
127
+
128
+ const channels: MarketplaceChannelListing[] = (Array.isArray(rawChannels) ? rawChannels : []).map(
129
+ (item, i) => {
130
+ if (!isRecord(item)) {
131
+ throw new Error(`channels[${i}]: expected object`);
132
+ }
133
+ const id = item.id;
134
+ const name = item.name;
135
+ const description = item.description;
136
+ const participants = item.participants;
137
+
138
+ if (typeof id !== 'string' || !id)
139
+ throw new Error(`channels[${i}].id must be a non-empty string`);
140
+ if (typeof name !== 'string') throw new Error(`channels[${i}].name must be a string`);
141
+ if (typeof description !== 'string')
142
+ throw new Error(`channels[${i}].description must be a string`);
143
+ if (!Array.isArray(participants))
144
+ throw new Error(`channels[${i}].participants must be an array`);
145
+
146
+ const listing: MarketplaceChannelListing = {
147
+ id,
148
+ name,
149
+ description,
150
+ participants: participants.filter((p): p is string => typeof p === 'string'),
151
+ };
152
+
153
+ if (typeof item.image === 'string') listing.image = item.image;
154
+ if (typeof item.spec === 'string') listing.spec = item.spec;
155
+ if (isRecord(item.initialState)) listing.initialState = item.initialState;
156
+
157
+ if (Array.isArray(item.starterPrompts)) {
158
+ listing.starterPrompts = item.starterPrompts.map((p: any, j: number) => {
159
+ if (!isRecord(p) || typeof p.label !== 'string' || typeof p.prompt !== 'string') {
160
+ throw new Error(`channels[${i}].starterPrompts[${j}] must have label and prompt`);
161
+ }
162
+ return { label: p.label, prompt: p.prompt };
163
+ });
164
+ }
165
+
166
+ return listing;
167
+ },
168
+ );
169
+
170
+ return { agents, channels };
171
+ }
172
+
173
+ async function fetchMarketplaceRegistryFromUrl(url: string): Promise<MarketplaceRegistry> {
174
+ const res = await fetch(url, {
175
+ headers: { Accept: 'application/json' },
176
+ signal: AbortSignal.timeout(15_000),
177
+ });
178
+ if (!res.ok) {
179
+ throw new Error(`Registry HTTP ${res.status} ${res.statusText}`);
180
+ }
181
+ const json: unknown = await res.json();
182
+ return parseMarketplaceRegistryJson(json);
183
+ }
184
+
185
+ /**
186
+ * Resolves marketplace registry (agents and channels) from configured registry URL.
187
+ */
188
+ export async function resolveMarketplaceRegistry(): Promise<MarketplaceRegistry> {
189
+ const { marketplaceRegistryUrl } = loadConfig();
190
+ const registryUrl = marketplaceRegistryUrl?.trim() || DEFAULT_MARKETPLACE_REGISTRY_URL;
191
+ try {
192
+ return await fetchMarketplaceRegistryFromUrl(registryUrl);
193
+ } catch (err) {
194
+ console.warn(
195
+ `[plugins] marketplace registry fetch failed (${registryUrl}), using built-in list:`,
196
+ err instanceof Error ? err.message : err,
197
+ );
198
+ return { agents: DEFAULT_MARKETPLACE_AGENTS, channels: DEFAULT_MARKETPLACE_CHANNELS };
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Resolves marketplace agent listings from configured registry URL.
204
+ * @deprecated Use resolveMarketplaceRegistry instead.
205
+ */
206
+ export async function resolveMarketplaceAgentList(): Promise<MarketplaceAgentListing[]> {
207
+ const registry = await resolveMarketplaceRegistry();
208
+ return registry.agents;
209
+ }
210
+
211
+ const getPluginsDir = (): string => {
212
+ const config = loadConfig();
213
+ const baseDir = resolvePath(config.baseDir || DEFAULT_BASE_DIR);
214
+ return path.join(baseDir, DEFAULT_PLUGINS_DIR);
215
+ };
216
+
217
+ /**
218
+ * Lifecycle for community-built plugins distributed via npm.
219
+ * Each plugin is installed to `<plugins>/<npm-name>/` and is identified
220
+ * everywhere (AGENT.md `plugins[].id`, registry, runtime resolution) by its
221
+ * npm name. Scoped packages (`@scope/foo`) live under `<plugins>/@scope/foo/`.
222
+ */
223
+ export const pluginService = {
224
+ isInstalled: async (packageName: string): Promise<boolean> => {
225
+ const finalPath = path.join(getPluginsDir(), packageName);
226
+ return existsSync(path.join(finalPath, 'dist', 'index.js'));
227
+ },
228
+
229
+ install: async ({ packageName, version }: InstallOptions): Promise<InstalledPlugin> => {
230
+ const pluginsDir = getPluginsDir();
231
+ await fs.mkdir(pluginsDir, { recursive: true });
232
+
233
+ const finalPath = path.join(pluginsDir, packageName);
234
+
235
+ if (existsSync(path.join(finalPath, 'package.json'))) {
236
+ try {
237
+ const pkgJson = JSON.parse(
238
+ await fs.readFile(path.join(finalPath, 'package.json'), 'utf-8'),
239
+ );
240
+ if (!version || pkgJson.version === version) {
241
+ console.log(
242
+ `[plugins] ${packageName}${version ? `@${version}` : ''} is already installed.`,
243
+ );
244
+ return { name: pkgJson.name, version: pkgJson.version };
245
+ }
246
+ } catch {
247
+ // corrupted; reinstall below
248
+ }
249
+ }
250
+
251
+ const target = version ? `${packageName}@${version}` : packageName;
252
+ console.log(`[plugins] Installing ${target} to ${pluginsDir}...`);
253
+
254
+ const tempDir = path.join(pluginsDir, '.tmp_' + Date.now());
255
+ try {
256
+ await fs.mkdir(tempDir, { recursive: true });
257
+ await execAsync(`npm install ${target} --no-save --prefix "${tempDir}"`);
258
+
259
+ const installedPath = path.join(tempDir, 'node_modules', packageName);
260
+ if (!existsSync(installedPath)) {
261
+ throw new Error(`npm did not produce ${installedPath}`);
262
+ }
263
+
264
+ await fs.mkdir(path.dirname(finalPath), { recursive: true });
265
+ await fs.rm(finalPath, { recursive: true, force: true });
266
+ await fs.rename(installedPath, finalPath);
267
+
268
+ console.log(`[plugins] Running npm install in ${finalPath}...`);
269
+ try {
270
+ await execAsync(`npm install`, { cwd: finalPath });
271
+ console.log(`[plugins] npm install completed in ${finalPath}`);
272
+ } catch (e) {
273
+ console.warn(`[plugins] Failed to run npm install in ${finalPath}:`, e);
274
+ }
275
+
276
+ const pkgJson = JSON.parse(
277
+ await fs.readFile(path.join(finalPath, 'package.json'), 'utf-8'),
278
+ );
279
+
280
+ invalidatePlugin(packageName);
281
+ return { name: pkgJson.name, version: pkgJson.version };
282
+ } catch (error) {
283
+ console.error(`[plugins] Failed to install ${packageName}:`, error);
284
+ throw new Error(
285
+ `Failed to install plugin ${packageName}: ${(error as Error).message}`,
286
+ );
287
+ } finally {
288
+ await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
289
+ }
290
+ },
291
+
292
+ uninstall: async (packageName: string): Promise<void> => {
293
+ const pluginsDir = getPluginsDir();
294
+ const pluginPath = path.join(pluginsDir, packageName);
295
+
296
+ try {
297
+ await fs.rm(pluginPath, { recursive: true, force: true });
298
+ invalidatePlugin(packageName);
299
+ console.log(`[plugins] Uninstalled plugin ${packageName}`);
300
+
301
+ if (packageName.startsWith('@')) {
302
+ const scopeDir = path.dirname(pluginPath);
303
+ try {
304
+ const remaining = await fs.readdir(scopeDir);
305
+ if (remaining.length === 0) await fs.rmdir(scopeDir);
306
+ } catch {
307
+ // ignore
308
+ }
309
+ }
310
+ } catch (error) {
311
+ console.error(`[plugins] Failed to uninstall ${packageName}:`, error);
312
+ throw new Error(
313
+ `Failed to uninstall plugin ${packageName}: ${(error as Error).message}`,
314
+ );
315
+ }
316
+ },
317
+ };
318
+
@@ -1,11 +1,11 @@
1
1
  import { MelonyPlugin } from 'melony';
2
- import { OpenBotEvent, OpenBotState } from '../app/types.js';
3
- import { AgentDetails, ConfigSchema, Storage } from './types.js';
2
+ import { OpenBotEvent, OpenBotState } from '../../app/types.js';
3
+ import { AgentDetails, ConfigSchema, Storage } from './domain.js';
4
4
 
5
5
  /**
6
6
  * Reference to a plugin from an agent's AGENT.md frontmatter.
7
7
  *
8
- * The `id` is either a built-in plugin id (e.g. `ai-sdk`, `shell`) or an npm
8
+ * The `id` is either a built-in plugin id (e.g. `openbot`, `bash`) or an npm
9
9
  * package name (e.g. `openbot-plugin-codex`, `@scope/openbot-plugin-foo`).
10
10
  * Each entry may carry plugin-specific `config`.
11
11
  */
@@ -33,6 +33,10 @@ export interface PluginContext {
33
33
  config: Record<string, unknown>;
34
34
  storage: Storage;
35
35
  tools: Record<string, ToolDefinition>;
36
+ /** Resolved public base URL for the server (e.g. https://my-host.example or http://localhost:4132). */
37
+ publicBaseUrl: string;
38
+ /** Signal that fires when this run is stopped; runtimes should pass it to long-running calls. */
39
+ abortSignal?: AbortSignal;
36
40
  }
37
41
 
38
42
  /**