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.
- package/README.md +15 -16
- package/dist/app/agent-ids.js +4 -0
- package/dist/app/cli.js +1 -1
- package/dist/app/config.js +10 -19
- package/dist/app/server.js +208 -17
- package/dist/bus/services.js +34 -124
- package/dist/harness/agent-invoke-run.js +44 -0
- package/dist/harness/agent-turn.js +99 -0
- package/dist/harness/channel-participants.js +40 -0
- package/dist/harness/constants.js +2 -0
- package/dist/harness/context-meter.js +97 -0
- package/dist/harness/context.js +95 -47
- package/dist/harness/dispatch.js +144 -0
- package/dist/harness/dispatcher.js +45 -156
- package/dist/harness/history.js +177 -0
- package/dist/harness/index.js +109 -0
- package/dist/harness/orchestration.js +88 -0
- package/dist/harness/participants.js +22 -0
- package/dist/harness/run-harness.js +154 -0
- package/dist/harness/run.js +98 -0
- package/dist/harness/runtime-factory.js +0 -34
- package/dist/harness/runtime.js +57 -0
- package/dist/harness/todo-dispatch.js +51 -0
- package/dist/harness/todos.js +5 -0
- package/dist/harness/turn.js +79 -0
- package/dist/plugins/approval/index.js +120 -149
- package/dist/plugins/bash/index.js +195 -0
- package/dist/plugins/delegation/index.js +121 -32
- package/dist/plugins/memory/index.js +103 -14
- package/dist/plugins/memory/service.js +152 -0
- package/dist/plugins/openbot/context.js +125 -0
- package/dist/plugins/openbot/history.js +144 -0
- package/dist/plugins/openbot/index.js +71 -0
- package/dist/plugins/openbot/runtime.js +381 -0
- package/dist/plugins/openbot/system-prompt.js +25 -0
- package/dist/plugins/plugin-manager/index.js +189 -0
- package/dist/plugins/shell/index.js +2 -1
- package/dist/plugins/storage/files.js +67 -0
- package/dist/plugins/storage/index.js +750 -0
- package/dist/plugins/storage/service.js +1316 -0
- package/dist/plugins/storage-tools/index.js +2 -2
- package/dist/plugins/thread-namer/index.js +72 -0
- package/dist/plugins/thread-naming/generate-title.js +44 -0
- package/dist/plugins/thread-naming/index.js +103 -0
- package/dist/plugins/threads/index.js +114 -0
- package/dist/plugins/todo/index.js +24 -25
- package/dist/plugins/ui/index.js +109 -180
- package/dist/registry/plugins.js +3 -9
- package/dist/services/abort.js +43 -0
- package/dist/services/plugins/domain.js +1 -0
- package/dist/services/plugins/plugin-cache.js +9 -0
- package/dist/services/plugins/registry.js +112 -0
- package/dist/services/plugins/service.js +232 -0
- package/dist/services/plugins/types.js +1 -0
- package/dist/services/process.js +29 -0
- package/dist/services/storage.js +11 -10
- package/dist/services/thread-naming.js +81 -0
- package/docs/agents.md +15 -12
- package/docs/architecture.md +2 -2
- package/docs/plugins.md +29 -17
- package/docs/templates/AGENT.example.md +8 -14
- package/package.json +1 -2
- package/src/app/agent-ids.ts +5 -0
- package/src/app/cli.ts +1 -1
- package/src/app/config.ts +14 -31
- package/src/app/server.ts +243 -19
- package/src/app/types.ts +331 -187
- package/src/harness/index.ts +166 -0
- package/src/plugins/approval/index.ts +107 -188
- package/src/plugins/bash/index.ts +232 -0
- package/src/plugins/delegation/index.ts +139 -39
- package/src/plugins/memory/index.ts +112 -15
- package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
- package/src/plugins/openbot/context.ts +140 -0
- package/src/plugins/openbot/history.ts +158 -0
- package/src/plugins/openbot/index.ts +79 -0
- package/src/plugins/openbot/runtime.ts +478 -0
- package/src/plugins/openbot/system-prompt.ts +27 -0
- package/src/plugins/plugin-manager/index.ts +224 -0
- package/src/plugins/storage/files.ts +81 -0
- package/src/plugins/storage/index.ts +823 -0
- package/src/{services/storage.ts → plugins/storage/service.ts} +485 -105
- package/src/plugins/ui/index.ts +117 -221
- package/src/services/abort.ts +46 -0
- package/src/{bus/types.ts → services/plugins/domain.ts} +50 -8
- package/src/services/plugins/plugin-cache.ts +13 -0
- package/src/{registry/plugins.ts → services/plugins/registry.ts} +28 -28
- package/src/services/plugins/service.ts +318 -0
- package/src/{bus/plugin.ts → services/plugins/types.ts} +7 -3
- package/src/bus/services.ts +0 -954
- package/src/harness/context.ts +0 -365
- package/src/harness/dispatcher.ts +0 -379
- package/src/harness/mcp.ts +0 -78
- package/src/harness/runtime-factory.ts +0 -129
- package/src/harness/todo-advance.ts +0 -128
- package/src/plugins/ai-sdk/index.ts +0 -41
- package/src/plugins/ai-sdk/runtime.ts +0 -468
- package/src/plugins/ai-sdk/system-prompt.ts +0 -18
- package/src/plugins/mcp/index.ts +0 -128
- package/src/plugins/shell/index.ts +0 -123
- package/src/plugins/storage-tools/index.ts +0 -90
- package/src/plugins/todo/index.ts +0 -64
- package/src/services/plugins.ts +0 -133
- /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 '
|
|
3
|
-
import { AgentDetails, ConfigSchema, Storage } from './
|
|
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. `
|
|
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
|
/**
|