openbot 0.5.5 → 0.5.7
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/app/cli.js +1 -1
- package/dist/services/plugins/plugin-cache.js +6 -0
- package/dist/services/plugins/registry.js +8 -2
- package/dist/services/plugins/service.js +7 -2
- package/docs/plugins.md +14 -12
- package/package.json +2 -2
- package/src/app/cli.ts +1 -1
- package/src/app/types.ts +1 -0
- package/src/services/plugins/plugin-cache.ts +8 -0
- package/src/services/plugins/registry.ts +10 -4
- package/src/services/plugins/service.ts +9 -2
package/dist/app/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ function checkNodeVersion() {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
checkNodeVersion();
|
|
21
|
-
program.name('openbot').description('OpenBot CLI').version('0.5.
|
|
21
|
+
program.name('openbot').description('OpenBot CLI').version('0.5.7');
|
|
22
22
|
program
|
|
23
23
|
.command('start')
|
|
24
24
|
.description('Start the OpenBot harness')
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
export const resolvedPluginCache = new Map();
|
|
3
3
|
/** Community plugin ids that have already been logged as loaded once. */
|
|
4
4
|
export const loadedCommunityPlugins = new Set();
|
|
5
|
+
/** Bumped on invalidate so dynamic imports use a fresh Node ESM cache key. */
|
|
6
|
+
const pluginImportRevisions = new Map();
|
|
7
|
+
export function getPluginImportRevision(id) {
|
|
8
|
+
return pluginImportRevisions.get(id) ?? 0;
|
|
9
|
+
}
|
|
5
10
|
/** Drop a single id from the in-memory resolver cache (e.g. after install/uninstall). */
|
|
6
11
|
export function invalidatePlugin(id) {
|
|
7
12
|
resolvedPluginCache.delete(id);
|
|
8
13
|
loadedCommunityPlugins.delete(id);
|
|
14
|
+
pluginImportRevisions.set(id, (pluginImportRevisions.get(id) ?? 0) + 1);
|
|
9
15
|
}
|
|
@@ -4,7 +4,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
4
4
|
import { storagePlugin } from '../../plugins/storage/index.js';
|
|
5
5
|
import { pluginManagerPlugin } from '../../plugins/plugin-manager/index.js';
|
|
6
6
|
import { DEFAULT_PLUGINS_DIR, getBaseDir } from '../../app/config.js';
|
|
7
|
-
import { invalidatePlugin as clearResolvedPluginEntry, loadedCommunityPlugins, resolvedPluginCache, } from './plugin-cache.js';
|
|
7
|
+
import { getPluginImportRevision, invalidatePlugin as clearResolvedPluginEntry, loadedCommunityPlugins, resolvedPluginCache, } from './plugin-cache.js';
|
|
8
8
|
let pluginsDir = null;
|
|
9
9
|
const BUILT_IN = {
|
|
10
10
|
[storagePlugin.id]: storagePlugin,
|
|
@@ -59,7 +59,8 @@ export async function resolvePlugin(id) {
|
|
|
59
59
|
const distPath = path.join(pluginsDir, id, 'dist', 'index.js');
|
|
60
60
|
if (existsSync(distPath)) {
|
|
61
61
|
try {
|
|
62
|
-
const
|
|
62
|
+
const importUrl = `${pathToFileURL(distPath).href}?v=${getPluginImportRevision(id)}`;
|
|
63
|
+
const module = await import(importUrl);
|
|
63
64
|
const parsed = parsePluginModule(module);
|
|
64
65
|
if (!parsed) {
|
|
65
66
|
console.warn(`[plugins] Plugin "${id}" at ${distPath} has no recognizable export.`);
|
|
@@ -89,6 +90,11 @@ export async function resolvePlugin(id) {
|
|
|
89
90
|
export function invalidatePlugin(id) {
|
|
90
91
|
clearResolvedPluginEntry(id);
|
|
91
92
|
}
|
|
93
|
+
/** Invalidate and load a community plugin from disk (e.g. after install/upgrade). */
|
|
94
|
+
export async function reloadPlugin(id) {
|
|
95
|
+
clearResolvedPluginEntry(id);
|
|
96
|
+
return resolvePlugin(id);
|
|
97
|
+
}
|
|
92
98
|
/** List built-in plugins (for marketplace/registry views). */
|
|
93
99
|
export function listBuiltInPlugins() {
|
|
94
100
|
return Object.values(BUILT_IN);
|
|
@@ -4,8 +4,12 @@ import path from 'node:path';
|
|
|
4
4
|
import { exec } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { DEFAULT_PLUGINS_DIR, DEFAULT_MARKETPLACE_REGISTRY_URL, getBaseDir, loadConfig, } from '../../app/config.js';
|
|
7
|
+
import { reloadPlugin } from './registry.js';
|
|
7
8
|
import { invalidatePlugin } from './plugin-cache.js';
|
|
8
9
|
const execAsync = promisify(exec);
|
|
10
|
+
function isLatestVersionAlias(version) {
|
|
11
|
+
return version.trim().toLowerCase() === 'latest';
|
|
12
|
+
}
|
|
9
13
|
const DEFAULT_MARKETPLACE_AGENTS = [];
|
|
10
14
|
const DEFAULT_MARKETPLACE_CHANNELS = [];
|
|
11
15
|
function isRecord(value) {
|
|
@@ -155,7 +159,8 @@ export const pluginService = {
|
|
|
155
159
|
if (existsSync(path.join(finalPath, 'package.json'))) {
|
|
156
160
|
try {
|
|
157
161
|
const pkgJson = JSON.parse(await fs.readFile(path.join(finalPath, 'package.json'), 'utf-8'));
|
|
158
|
-
|
|
162
|
+
const wantsLatest = version !== undefined && isLatestVersionAlias(version);
|
|
163
|
+
if (!version || (!wantsLatest && pkgJson.version === version)) {
|
|
159
164
|
console.log(`[plugins] ${packageName}${version ? `@${version}` : ''} is already installed.`);
|
|
160
165
|
return { name: pkgJson.name, version: pkgJson.version };
|
|
161
166
|
}
|
|
@@ -186,7 +191,7 @@ export const pluginService = {
|
|
|
186
191
|
console.warn(`[plugins] Failed to run npm install in ${finalPath}:`, e);
|
|
187
192
|
}
|
|
188
193
|
const pkgJson = JSON.parse(await fs.readFile(path.join(finalPath, 'package.json'), 'utf-8'));
|
|
189
|
-
|
|
194
|
+
await reloadPlugin(packageName);
|
|
190
195
|
return { name: pkgJson.name, version: pkgJson.version };
|
|
191
196
|
}
|
|
192
197
|
catch (error) {
|
package/docs/plugins.md
CHANGED
|
@@ -28,7 +28,7 @@ export interface Plugin {
|
|
|
28
28
|
export interface PluginContext {
|
|
29
29
|
agentId: string;
|
|
30
30
|
agentDetails: AgentDetails;
|
|
31
|
-
config: Record<string, unknown>;
|
|
31
|
+
config: Record<string, unknown>; // from AGENT.md plugins[].config
|
|
32
32
|
storage: Storage;
|
|
33
33
|
tools: Record<string, ToolDefinition>; // merged from all tool plugins
|
|
34
34
|
}
|
|
@@ -49,16 +49,16 @@ model as the tool result (`history.ts`). Structured fields (e.g. `list`,
|
|
|
49
49
|
|
|
50
50
|
## Built-in plugins
|
|
51
51
|
|
|
52
|
-
| Id
|
|
53
|
-
|
|
|
54
|
-
| `openbot`
|
|
55
|
-
| `claude-code`
|
|
56
|
-
| `gemini-cli`
|
|
57
|
-
| `bash`
|
|
58
|
-
| `storage`
|
|
59
|
-
| `memory`
|
|
60
|
-
| `todo`
|
|
61
|
-
| `plugin-manager
|
|
52
|
+
| Id | Role | Notes |
|
|
53
|
+
| ---------------- | ------- | ------------------------------------------------------------- |
|
|
54
|
+
| `openbot` | Runtime | Standard batteries-included OpenBot agent runtime. |
|
|
55
|
+
| `claude-code` | Runtime | Claude Agent SDK; owns its own tool loop |
|
|
56
|
+
| `gemini-cli` | Runtime | Google `gemini` CLI in headless mode |
|
|
57
|
+
| `bash` | Tool | `bash` (inbuilt in `openbot`) |
|
|
58
|
+
| `storage` | Tool | `create_channel`, `patch_*`, ... (inbuilt in `openbot`) |
|
|
59
|
+
| `memory` | Tool | `remember`, `recall`, `forget` (inbuilt in `openbot`) |
|
|
60
|
+
| `todo` | Tool | `todo_write`, `todo_read` (inbuilt in `openbot`) |
|
|
61
|
+
| `plugin-manager` | Infra | Marketplace list, npm plugin install/uninstall, agent install |
|
|
62
62
|
|
|
63
63
|
## Batteries-included: `openbot` runtime
|
|
64
64
|
|
|
@@ -96,7 +96,9 @@ plugins:
|
|
|
96
96
|
|
|
97
97
|
On first use OpenBot installs the package into
|
|
98
98
|
`~/.openbot/plugins/<npm-name>/` (scoped packages live under
|
|
99
|
-
`~/.openbot/plugins/@scope/<name>/`).
|
|
99
|
+
`~/.openbot/plugins/@scope/<name>/`). Publish `action:plugin:install` with no
|
|
100
|
+
`version` to ensure a plugin is present; pass `version: "latest"` to upgrade an
|
|
101
|
+
existing install to npm's latest tag, or a concrete semver to pin.
|
|
100
102
|
|
|
101
103
|
## Approval plugin
|
|
102
104
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"build": "tsc && mkdir -p dist/assets && cp src/assets/icon.svg dist/assets/icon.svg",
|
|
12
12
|
"start": "node dist/app/cli.js start",
|
|
13
13
|
"format": "prettier --write .",
|
|
14
|
-
"push": "fly deploy --build-only --push --config deploy/fly.toml -a openbot-runtime --image-label v0.5.
|
|
14
|
+
"push": "fly deploy --build-only --push --config deploy/fly.toml -a openbot-runtime --image-label v0.5.7"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"openbot": "./dist/app/cli.js"
|
package/src/app/cli.ts
CHANGED
package/src/app/types.ts
CHANGED
|
@@ -6,8 +6,16 @@ export const resolvedPluginCache = new Map<string, Plugin>();
|
|
|
6
6
|
/** Community plugin ids that have already been logged as loaded once. */
|
|
7
7
|
export const loadedCommunityPlugins = new Set<string>();
|
|
8
8
|
|
|
9
|
+
/** Bumped on invalidate so dynamic imports use a fresh Node ESM cache key. */
|
|
10
|
+
const pluginImportRevisions = new Map<string, number>();
|
|
11
|
+
|
|
12
|
+
export function getPluginImportRevision(id: string): number {
|
|
13
|
+
return pluginImportRevisions.get(id) ?? 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
/** Drop a single id from the in-memory resolver cache (e.g. after install/uninstall). */
|
|
10
17
|
export function invalidatePlugin(id: string): void {
|
|
11
18
|
resolvedPluginCache.delete(id);
|
|
12
19
|
loadedCommunityPlugins.delete(id);
|
|
20
|
+
pluginImportRevisions.set(id, (pluginImportRevisions.get(id) ?? 0) + 1);
|
|
13
21
|
}
|
|
@@ -7,6 +7,7 @@ import { storagePlugin } from '../../plugins/storage/index.js';
|
|
|
7
7
|
import { pluginManagerPlugin } from '../../plugins/plugin-manager/index.js';
|
|
8
8
|
import { DEFAULT_PLUGINS_DIR, getBaseDir } from '../../app/config.js';
|
|
9
9
|
import {
|
|
10
|
+
getPluginImportRevision,
|
|
10
11
|
invalidatePlugin as clearResolvedPluginEntry,
|
|
11
12
|
loadedCommunityPlugins,
|
|
12
13
|
resolvedPluginCache,
|
|
@@ -27,9 +28,7 @@ const BUILT_IN: Record<string, Plugin> = {
|
|
|
27
28
|
export type ParsedPluginModule = Omit<Plugin, 'id'>;
|
|
28
29
|
|
|
29
30
|
/** Normalize a dynamically imported plugin module. Supports `plugin`, `default`. */
|
|
30
|
-
export function parsePluginModule(
|
|
31
|
-
module: Record<string, unknown>,
|
|
32
|
-
): ParsedPluginModule | null {
|
|
31
|
+
export function parsePluginModule(module: Record<string, unknown>): ParsedPluginModule | null {
|
|
33
32
|
const raw =
|
|
34
33
|
(module.plugin as Record<string, unknown> | undefined) ??
|
|
35
34
|
(module.default as Record<string, unknown> | undefined);
|
|
@@ -82,7 +81,8 @@ export async function resolvePlugin(id: string): Promise<Plugin | null> {
|
|
|
82
81
|
|
|
83
82
|
if (existsSync(distPath)) {
|
|
84
83
|
try {
|
|
85
|
-
const
|
|
84
|
+
const importUrl = `${pathToFileURL(distPath).href}?v=${getPluginImportRevision(id)}`;
|
|
85
|
+
const module = await import(importUrl);
|
|
86
86
|
const parsed = parsePluginModule(module as Record<string, unknown>);
|
|
87
87
|
if (!parsed) {
|
|
88
88
|
console.warn(`[plugins] Plugin "${id}" at ${distPath} has no recognizable export.`);
|
|
@@ -114,6 +114,12 @@ export function invalidatePlugin(id: string): void {
|
|
|
114
114
|
clearResolvedPluginEntry(id);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/** Invalidate and load a community plugin from disk (e.g. after install/upgrade). */
|
|
118
|
+
export async function reloadPlugin(id: string): Promise<Plugin | null> {
|
|
119
|
+
clearResolvedPluginEntry(id);
|
|
120
|
+
return resolvePlugin(id);
|
|
121
|
+
}
|
|
122
|
+
|
|
117
123
|
/** List built-in plugins (for marketplace/registry views). */
|
|
118
124
|
export function listBuiltInPlugins(): Plugin[] {
|
|
119
125
|
return Object.values(BUILT_IN);
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
resolvePath,
|
|
12
12
|
} from '../../app/config.js';
|
|
13
|
+
import { reloadPlugin } from './registry.js';
|
|
13
14
|
import { invalidatePlugin } from './plugin-cache.js';
|
|
14
15
|
import { PluginRef } from './types.js';
|
|
15
16
|
|
|
@@ -17,9 +18,14 @@ const execAsync = promisify(exec);
|
|
|
17
18
|
|
|
18
19
|
export interface InstallOptions {
|
|
19
20
|
packageName: string;
|
|
21
|
+
/** Concrete semver, or `"latest"` to refresh from npm. Omit to ensure present without upgrading. */
|
|
20
22
|
version?: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
function isLatestVersionAlias(version: string): boolean {
|
|
26
|
+
return version.trim().toLowerCase() === 'latest';
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
export interface InstalledPlugin {
|
|
24
30
|
/** npm package name; doubles as the plugin id used everywhere else. */
|
|
25
31
|
name: string;
|
|
@@ -229,7 +235,8 @@ export const pluginService = {
|
|
|
229
235
|
const pkgJson = JSON.parse(
|
|
230
236
|
await fs.readFile(path.join(finalPath, 'package.json'), 'utf-8'),
|
|
231
237
|
);
|
|
232
|
-
|
|
238
|
+
const wantsLatest = version !== undefined && isLatestVersionAlias(version);
|
|
239
|
+
if (!version || (!wantsLatest && pkgJson.version === version)) {
|
|
233
240
|
console.log(
|
|
234
241
|
`[plugins] ${packageName}${version ? `@${version}` : ''} is already installed.`,
|
|
235
242
|
);
|
|
@@ -267,7 +274,7 @@ export const pluginService = {
|
|
|
267
274
|
|
|
268
275
|
const pkgJson = JSON.parse(await fs.readFile(path.join(finalPath, 'package.json'), 'utf-8'));
|
|
269
276
|
|
|
270
|
-
|
|
277
|
+
await reloadPlugin(packageName);
|
|
271
278
|
return { name: pkgJson.name, version: pkgJson.version };
|
|
272
279
|
} catch (error) {
|
|
273
280
|
console.error(`[plugins] Failed to install ${packageName}:`, error);
|