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 CHANGED
@@ -18,7 +18,7 @@ function checkNodeVersion() {
18
18
  }
19
19
  }
20
20
  checkNodeVersion();
21
- program.name('openbot').description('OpenBot CLI').version('0.5.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 module = await import(pathToFileURL(distPath).href);
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
- if (!version || pkgJson.version === version) {
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
- invalidatePlugin(packageName);
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>; // from AGENT.md plugins[].config
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 | 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 |
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.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.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
@@ -27,7 +27,7 @@ function checkNodeVersion() {
27
27
 
28
28
  checkNodeVersion();
29
29
 
30
- program.name('openbot').description('OpenBot CLI').version('0.5.5');
30
+ program.name('openbot').description('OpenBot CLI').version('0.5.7');
31
31
 
32
32
  program
33
33
  .command('start')
package/src/app/types.ts CHANGED
@@ -875,6 +875,7 @@ export type InstallPluginEvent = BaseEvent & {
875
875
  type: 'action:plugin:install';
876
876
  data: {
877
877
  name: string;
878
+ /** Concrete semver, or `"latest"` to upgrade an existing install. Omit to ensure present. */
878
879
  version?: string;
879
880
  };
880
881
  };
@@ -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 module = await import(pathToFileURL(distPath).href);
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
- if (!version || pkgJson.version === version) {
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
- invalidatePlugin(packageName);
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);