openbot 0.5.4 → 0.5.6
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/CLAUDE.md +5 -4
- package/deploy/README.md +2 -4
- package/dist/app/cli.js +3 -1
- package/dist/app/cloud-mode.js +5 -16
- package/dist/app/ensure-default-stack.js +54 -0
- package/dist/app/openbot-plugin.js +4 -0
- package/dist/app/server.js +2 -3
- package/dist/harness/index.js +3 -0
- package/dist/plugins/openbot/index.js +6 -5
- package/dist/plugins/openbot/model.js +2 -41
- package/dist/plugins/openbot/runtime.js +9 -4
- package/dist/plugins/storage/index.js +3 -325
- package/dist/plugins/storage/service.js +18 -21
- package/dist/services/plugins/host.js +21 -0
- package/dist/services/plugins/model-registry.js +34 -9
- package/dist/services/plugins/registry.js +26 -42
- package/dist/services/plugins/service.js +5 -1
- package/dist/services/todo/types.js +1 -0
- package/docs/plugins.md +14 -12
- package/docs/templates/AGENT.example.md +8 -14
- package/package.json +2 -2
- package/src/app/cli.ts +3 -1
- package/src/app/cloud-mode.ts +7 -22
- package/src/app/ensure-default-stack.ts +63 -0
- package/src/app/openbot-plugin.ts +5 -0
- package/src/app/server.ts +2 -5
- package/src/app/types.ts +4 -8
- package/src/harness/index.ts +4 -0
- package/src/plugins/storage/index.ts +3 -368
- package/src/plugins/storage/service.ts +17 -22
- package/src/services/plugins/host.ts +36 -0
- package/src/services/plugins/model-registry.ts +41 -9
- package/src/services/plugins/registry.ts +28 -43
- package/src/services/plugins/service.ts +13 -12
- package/src/services/plugins/types.ts +36 -2
- package/src/services/todo/types.ts +12 -0
- package/src/plugins/approval/index.ts +0 -147
- package/src/plugins/bash/index.ts +0 -545
- package/src/plugins/delegation/index.ts +0 -153
- package/src/plugins/memory/index.ts +0 -182
- package/src/plugins/openbot/context.ts +0 -137
- package/src/plugins/openbot/history.ts +0 -158
- package/src/plugins/openbot/index.ts +0 -95
- package/src/plugins/openbot/model.ts +0 -76
- package/src/plugins/openbot/runtime.ts +0 -504
- package/src/plugins/openbot/system-prompt.ts +0 -57
- package/src/plugins/preview/index.ts +0 -323
- package/src/plugins/todo/index.ts +0 -166
- package/src/plugins/todo/service.ts +0 -123
- package/src/plugins/ui/index.ts +0 -130
|
@@ -1,106 +1,15 @@
|
|
|
1
|
-
import z from 'zod';
|
|
2
1
|
import type { Plugin } from '../../services/plugins/types.js';
|
|
3
2
|
import { OpenBotEvent } from '../../app/types.js';
|
|
4
3
|
import { buildWorkspaceFileUrl } from './files.js';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* `storage` —
|
|
8
|
-
*
|
|
6
|
+
* `storage` — platform infra handlers for `/api/state` and deterministic reads.
|
|
7
|
+
* Agent-facing storage tools live in `@meetopenbot/openbot`.
|
|
9
8
|
*/
|
|
10
|
-
const storageToolDefinitions = {
|
|
11
|
-
create_channel: {
|
|
12
|
-
description:
|
|
13
|
-
'Create a new channel. Use when the user intent is clearly different from the current channel and should be split. Always confirm before creating. Skip for simple Q&A.',
|
|
14
|
-
inputSchema: z.object({
|
|
15
|
-
channelId: z
|
|
16
|
-
.string()
|
|
17
|
-
.describe('Unique channel ID (e.g. product-launch, backend-platform, channel_roadmap).'),
|
|
18
|
-
spec: z
|
|
19
|
-
.string()
|
|
20
|
-
.optional()
|
|
21
|
-
.describe('Optional initial markdown content for the channel spec.'),
|
|
22
|
-
initialState: z
|
|
23
|
-
.record(z.string(), z.unknown())
|
|
24
|
-
.optional()
|
|
25
|
-
.describe('Optional initial state object for the channel.'),
|
|
26
|
-
cwd: z
|
|
27
|
-
.string()
|
|
28
|
-
.optional()
|
|
29
|
-
.describe(
|
|
30
|
-
'Optional initial current working directory for the channel. Defaults to an absolute path under ~/openbot/{channelId}.',
|
|
31
|
-
),
|
|
32
|
-
}),
|
|
33
|
-
},
|
|
34
|
-
patch_channel_details: {
|
|
35
|
-
description: 'Patch current channel details (state, spec, cwd).',
|
|
36
|
-
inputSchema: z
|
|
37
|
-
.object({
|
|
38
|
-
state: z
|
|
39
|
-
.record(z.string(), z.unknown())
|
|
40
|
-
.optional()
|
|
41
|
-
.describe('JSON state object for the channel.'),
|
|
42
|
-
spec: z
|
|
43
|
-
.string()
|
|
44
|
-
.optional()
|
|
45
|
-
.describe(
|
|
46
|
-
'Markdown content for the channel specification (SPEC.md). Use for goals and rules.',
|
|
47
|
-
),
|
|
48
|
-
cwd: z.string().optional().describe('Current working directory for the channel.'),
|
|
49
|
-
})
|
|
50
|
-
.refine(
|
|
51
|
-
(value) =>
|
|
52
|
-
value.state !== undefined ||
|
|
53
|
-
value.spec !== undefined ||
|
|
54
|
-
value.cwd !== undefined,
|
|
55
|
-
{ message: 'Provide at least one of state, spec, or cwd.' },
|
|
56
|
-
),
|
|
57
|
-
},
|
|
58
|
-
patch_thread_details: {
|
|
59
|
-
description:
|
|
60
|
-
'Patch current thread details (state). Use for thread metadata such as `name` or `isSmartNamed`. For multi-step task tracking, use `todo_write` instead.',
|
|
61
|
-
inputSchema: z.object({
|
|
62
|
-
state: z
|
|
63
|
-
.record(z.string(), z.unknown())
|
|
64
|
-
.describe('JSON state object for the thread. Merges with existing state.'),
|
|
65
|
-
}),
|
|
66
|
-
},
|
|
67
|
-
create_variable: {
|
|
68
|
-
description: 'Create or update a variable in the workspace storage.',
|
|
69
|
-
inputSchema: z.object({
|
|
70
|
-
key: z.string().describe('The key of the variable.'),
|
|
71
|
-
value: z.string().describe('The value of the variable.'),
|
|
72
|
-
secret: z.boolean().optional().describe('Whether the variable is a secret.'),
|
|
73
|
-
}),
|
|
74
|
-
},
|
|
75
|
-
delete_variable: {
|
|
76
|
-
description: 'Delete a variable from the workspace storage.',
|
|
77
|
-
inputSchema: z.object({
|
|
78
|
-
key: z.string().describe('The key of the variable to delete.'),
|
|
79
|
-
}),
|
|
80
|
-
},
|
|
81
|
-
delete_channel: {
|
|
82
|
-
description:
|
|
83
|
-
'Permanently delete a channel and all its threads and events. Always confirm with the user before deleting.',
|
|
84
|
-
inputSchema: z.object({
|
|
85
|
-
channelId: z.string().describe('The channel ID to delete.'),
|
|
86
|
-
}),
|
|
87
|
-
},
|
|
88
|
-
get_workspace_file_url: {
|
|
89
|
-
description:
|
|
90
|
-
'Get a fetchable HTTP URL for a file in the current channel workspace (images, video, audio, documents).',
|
|
91
|
-
inputSchema: z.object({
|
|
92
|
-
path: z
|
|
93
|
-
.string()
|
|
94
|
-
.describe('Path relative to the channel working directory, e.g. "uploads/clip.mp4".'),
|
|
95
|
-
}),
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
|
|
99
9
|
export const storagePlugin: Plugin = {
|
|
100
10
|
id: 'storage',
|
|
101
11
|
name: 'Storage',
|
|
102
|
-
description: '
|
|
103
|
-
toolDefinitions: storageToolDefinitions,
|
|
12
|
+
description: 'Infrastructure storage handlers for channels, threads, agents, and files.',
|
|
104
13
|
factory: ({ storage, publicBaseUrl }) => (builder) => {
|
|
105
14
|
const resolvePublicBaseUrl = () => publicBaseUrl;
|
|
106
15
|
|
|
@@ -144,243 +53,6 @@ export const storagePlugin: Plugin = {
|
|
|
144
53
|
} as OpenBotEvent;
|
|
145
54
|
});
|
|
146
55
|
|
|
147
|
-
builder.on('action:create_channel', async function* (event, context) {
|
|
148
|
-
const { channelId, spec, initialState, cwd } = (event as any).data;
|
|
149
|
-
const rawChannelId = (channelId || '').trim();
|
|
150
|
-
const channelSpec = typeof spec === 'string' ? spec : '';
|
|
151
|
-
|
|
152
|
-
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
153
|
-
|
|
154
|
-
if (!rawChannelId) {
|
|
155
|
-
yield {
|
|
156
|
-
type: 'action:create_channel:result',
|
|
157
|
-
data: { success: false, channelId: '', channelUrl: '' },
|
|
158
|
-
meta: resultMeta,
|
|
159
|
-
} as OpenBotEvent;
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const channelUrl = `/channels/${rawChannelId}`;
|
|
164
|
-
|
|
165
|
-
const mergedInitial: Record<string, unknown> = { ...(initialState || {}) };
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
await storage.createChannel({
|
|
169
|
-
channelId: rawChannelId,
|
|
170
|
-
spec: channelSpec,
|
|
171
|
-
initialState: mergedInitial,
|
|
172
|
-
cwd,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
yield {
|
|
176
|
-
type: 'action:create_channel:result',
|
|
177
|
-
data: { success: true, channelId: rawChannelId, channelUrl },
|
|
178
|
-
meta: resultMeta,
|
|
179
|
-
} as OpenBotEvent;
|
|
180
|
-
|
|
181
|
-
yield {
|
|
182
|
-
type: 'agent:output',
|
|
183
|
-
data: { content: `Created channel \`${rawChannelId}\`.` },
|
|
184
|
-
meta: resultMeta,
|
|
185
|
-
} as OpenBotEvent;
|
|
186
|
-
} catch {
|
|
187
|
-
yield {
|
|
188
|
-
type: 'action:create_channel:result',
|
|
189
|
-
data: { success: false, channelId: rawChannelId, channelUrl },
|
|
190
|
-
meta: resultMeta,
|
|
191
|
-
} as OpenBotEvent;
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
builder.on('action:delete_channel', async function* (event, context) {
|
|
196
|
-
const rawChannelId = ((event.data as { channelId?: string })?.channelId || '').trim();
|
|
197
|
-
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
198
|
-
|
|
199
|
-
if (!rawChannelId) {
|
|
200
|
-
yield {
|
|
201
|
-
type: 'action:delete_channel:result',
|
|
202
|
-
data: { success: false, channelId: '', error: 'channelId is required' },
|
|
203
|
-
meta: resultMeta,
|
|
204
|
-
} as OpenBotEvent;
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
await storage.deleteChannel({ channelId: rawChannelId });
|
|
210
|
-
yield {
|
|
211
|
-
type: 'action:delete_channel:result',
|
|
212
|
-
data: { success: true, channelId: rawChannelId },
|
|
213
|
-
meta: resultMeta,
|
|
214
|
-
} as OpenBotEvent;
|
|
215
|
-
yield {
|
|
216
|
-
type: 'agent:output',
|
|
217
|
-
data: { content: `Deleted channel \`${rawChannelId}\`.` },
|
|
218
|
-
meta: resultMeta,
|
|
219
|
-
} as OpenBotEvent;
|
|
220
|
-
} catch (error) {
|
|
221
|
-
yield {
|
|
222
|
-
type: 'action:delete_channel:result',
|
|
223
|
-
data: {
|
|
224
|
-
success: false,
|
|
225
|
-
channelId: rawChannelId,
|
|
226
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
227
|
-
},
|
|
228
|
-
meta: resultMeta,
|
|
229
|
-
} as OpenBotEvent;
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
builder.on('action:update_channel', async function* (event, context) {
|
|
234
|
-
const data = (event.data || {}) as {
|
|
235
|
-
channelId?: string;
|
|
236
|
-
name?: string;
|
|
237
|
-
cwd?: string;
|
|
238
|
-
};
|
|
239
|
-
const targetChannelId = (data.channelId || context.state.channelId || '').trim();
|
|
240
|
-
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
241
|
-
|
|
242
|
-
if (!targetChannelId) {
|
|
243
|
-
yield {
|
|
244
|
-
type: 'action:update_channel:result',
|
|
245
|
-
data: { success: false, channelId: '', updatedFields: [] as string[] },
|
|
246
|
-
meta: resultMeta,
|
|
247
|
-
} as OpenBotEvent;
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const patch: Record<string, unknown> = {};
|
|
252
|
-
const updatedFields: string[] = [];
|
|
253
|
-
|
|
254
|
-
if (typeof data.name === 'string' && data.name.trim()) {
|
|
255
|
-
patch.name = data.name.trim();
|
|
256
|
-
updatedFields.push('name');
|
|
257
|
-
}
|
|
258
|
-
if (typeof data.cwd === 'string' && data.cwd.trim()) {
|
|
259
|
-
patch.cwd = data.cwd.trim();
|
|
260
|
-
updatedFields.push('cwd');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
if (updatedFields.length > 0) {
|
|
265
|
-
await storage.patchChannelState({ channelId: targetChannelId, state: patch });
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (targetChannelId === context.state.channelId) {
|
|
269
|
-
context.state.channelDetails = await storage.getChannelDetails({
|
|
270
|
-
channelId: context.state.channelId,
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
yield {
|
|
275
|
-
type: 'action:update_channel:result',
|
|
276
|
-
data: { success: true, channelId: targetChannelId, updatedFields },
|
|
277
|
-
meta: resultMeta,
|
|
278
|
-
} as OpenBotEvent;
|
|
279
|
-
} catch {
|
|
280
|
-
yield {
|
|
281
|
-
type: 'action:update_channel:result',
|
|
282
|
-
data: { success: false, channelId: targetChannelId, updatedFields },
|
|
283
|
-
meta: resultMeta,
|
|
284
|
-
} as OpenBotEvent;
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
builder.on('action:patch_channel_details', async function* (event, context) {
|
|
289
|
-
const updatedFields: ('state' | 'spec' | 'cwd')[] = [];
|
|
290
|
-
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
291
|
-
const data = (event.data || {}) as {
|
|
292
|
-
state?: Record<string, unknown>;
|
|
293
|
-
spec?: string;
|
|
294
|
-
cwd?: string;
|
|
295
|
-
};
|
|
296
|
-
try {
|
|
297
|
-
if (data.state !== undefined) {
|
|
298
|
-
await storage.patchChannelState({
|
|
299
|
-
channelId: context.state.channelId,
|
|
300
|
-
state: data.state,
|
|
301
|
-
});
|
|
302
|
-
updatedFields.push('state');
|
|
303
|
-
}
|
|
304
|
-
if (typeof data.spec === 'string') {
|
|
305
|
-
await storage.patchChannelSpec({
|
|
306
|
-
channelId: context.state.channelId,
|
|
307
|
-
spec: data.spec,
|
|
308
|
-
});
|
|
309
|
-
updatedFields.push('spec');
|
|
310
|
-
}
|
|
311
|
-
if (typeof data.cwd === 'string') {
|
|
312
|
-
await storage.patchChannelState({
|
|
313
|
-
channelId: context.state.channelId,
|
|
314
|
-
state: { cwd: data.cwd },
|
|
315
|
-
});
|
|
316
|
-
updatedFields.push('cwd');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
context.state.channelDetails = await storage.getChannelDetails({
|
|
320
|
-
channelId: context.state.channelId,
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
yield {
|
|
324
|
-
type: "client:ui:widget",
|
|
325
|
-
data: {
|
|
326
|
-
widgetId: "patch-channel-details-result" + Date.now(),
|
|
327
|
-
kind: "message",
|
|
328
|
-
title: "Channel details updated.",
|
|
329
|
-
body: `The channel details have been updated. ${updatedFields.join(', ')}`,
|
|
330
|
-
display: "collapsed",
|
|
331
|
-
},
|
|
332
|
-
meta: resultMeta,
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
yield {
|
|
336
|
-
type: 'action:patch_channel_details:result',
|
|
337
|
-
data: { success: true, updatedFields },
|
|
338
|
-
meta: resultMeta,
|
|
339
|
-
};
|
|
340
|
-
} catch {
|
|
341
|
-
yield {
|
|
342
|
-
type: 'action:patch_channel_details:result',
|
|
343
|
-
data: { success: false, updatedFields },
|
|
344
|
-
meta: resultMeta,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
builder.on('action:patch_thread_details', async function* (event, context) {
|
|
350
|
-
const updatedFields: ('state')[] = [];
|
|
351
|
-
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
352
|
-
try {
|
|
353
|
-
if (!context.state.threadId) {
|
|
354
|
-
throw new Error('Missing threadId in state for patch_thread_details');
|
|
355
|
-
}
|
|
356
|
-
if ((event.data as any).state !== undefined) {
|
|
357
|
-
await storage.patchThreadState({
|
|
358
|
-
channelId: context.state.channelId,
|
|
359
|
-
threadId: context.state.threadId,
|
|
360
|
-
state: (event.data as any).state,
|
|
361
|
-
});
|
|
362
|
-
updatedFields.push('state');
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
context.state.threadDetails = await storage.getThreadDetails({
|
|
366
|
-
channelId: context.state.channelId,
|
|
367
|
-
threadId: context.state.threadId,
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
yield {
|
|
371
|
-
type: 'action:patch_thread_details:result',
|
|
372
|
-
data: { success: true, updatedFields },
|
|
373
|
-
meta: resultMeta,
|
|
374
|
-
};
|
|
375
|
-
} catch {
|
|
376
|
-
yield {
|
|
377
|
-
type: 'action:patch_thread_details:result',
|
|
378
|
-
data: { success: false, updatedFields },
|
|
379
|
-
meta: resultMeta,
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
56
|
builder.on('agent:usage', async function* (event, context) {
|
|
385
57
|
const { usage } = event.data;
|
|
386
58
|
if (!context.state.threadId) return;
|
|
@@ -735,43 +407,6 @@ export const storagePlugin: Plugin = {
|
|
|
735
407
|
};
|
|
736
408
|
}
|
|
737
409
|
});
|
|
738
|
-
|
|
739
|
-
builder.on('action:get_workspace_file_url', async function* (event, context) {
|
|
740
|
-
const channelId = context.state.channelId;
|
|
741
|
-
const filePath = (event.data as { path?: string })?.path;
|
|
742
|
-
const toolCallId = event.meta?.toolCallId;
|
|
743
|
-
|
|
744
|
-
if (!filePath) {
|
|
745
|
-
yield {
|
|
746
|
-
type: 'action:get_workspace_file_url:result',
|
|
747
|
-
data: { success: false, path: '', error: 'Path is required', output: 'Path is required' },
|
|
748
|
-
meta: { ...(event.meta || {}), toolCallId },
|
|
749
|
-
};
|
|
750
|
-
return;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
try {
|
|
754
|
-
const { size, mimeType } = await storage.getChannelFileStat({ channelId, path: filePath });
|
|
755
|
-
const url = buildWorkspaceFileUrl({
|
|
756
|
-
baseUrl: resolvePublicBaseUrl(),
|
|
757
|
-
channelId,
|
|
758
|
-
filePath,
|
|
759
|
-
});
|
|
760
|
-
const output = JSON.stringify({ path: filePath, url, mimeType, size });
|
|
761
|
-
yield {
|
|
762
|
-
type: 'action:get_workspace_file_url:result',
|
|
763
|
-
data: { success: true, path: filePath, url, mimeType, size, output },
|
|
764
|
-
meta: { ...(event.meta || {}), toolCallId },
|
|
765
|
-
};
|
|
766
|
-
} catch (error) {
|
|
767
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
768
|
-
yield {
|
|
769
|
-
type: 'action:get_workspace_file_url:result',
|
|
770
|
-
data: { success: false, path: filePath, error: message, output: message },
|
|
771
|
-
meta: { ...(event.meta || {}), toolCallId },
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
410
|
},
|
|
776
411
|
};
|
|
777
412
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ORCHESTRATOR_AGENT_ID, STATE_AGENT_ID } from '../../app/agent-ids.js';
|
|
2
2
|
import {
|
|
3
3
|
assertCloudSystemAgentPluginsMutable,
|
|
4
|
-
|
|
4
|
+
DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
5
5
|
isCloudMode,
|
|
6
6
|
} from '../../app/cloud-mode.js';
|
|
7
7
|
import {
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
ThreadState,
|
|
32
32
|
} from '../../services/plugins/domain.js';
|
|
33
33
|
import type { PluginRef } from '../../services/plugins/types.js';
|
|
34
|
-
import {
|
|
34
|
+
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
35
35
|
import { listBuiltInPlugins, parsePluginModule } from '../../services/plugins/registry.js';
|
|
36
36
|
import {
|
|
37
37
|
enrichOpenbotPluginDescriptor,
|
|
@@ -122,34 +122,32 @@ const SYSTEM_AGENT_ID = ORCHESTRATOR_AGENT_ID;
|
|
|
122
122
|
|
|
123
123
|
const SYSTEM_DEFAULT_PLUGINS: PluginRef[] = [
|
|
124
124
|
{
|
|
125
|
-
id:
|
|
125
|
+
id: OPENBOT_PLUGIN_ID,
|
|
126
126
|
config: {
|
|
127
127
|
model: 'openai/gpt-5.4-mini',
|
|
128
|
-
approval: {
|
|
129
|
-
actions: ['action:shell_exec', 'action:create_channel', 'action:delete_channel'],
|
|
130
|
-
},
|
|
131
128
|
},
|
|
132
129
|
},
|
|
133
130
|
];
|
|
134
131
|
|
|
135
|
-
|
|
136
|
-
{
|
|
137
|
-
id
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
function getCloudSystemDefaultPlugins(): PluginRef[] {
|
|
133
|
+
return SYSTEM_DEFAULT_PLUGINS.map((ref) => {
|
|
134
|
+
if (ref.id !== OPENBOT_PLUGIN_ID) return ref;
|
|
135
|
+
return {
|
|
136
|
+
...ref,
|
|
137
|
+
config: {
|
|
138
|
+
...ref.config,
|
|
139
|
+
authMode: DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
142
140
|
},
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
146
144
|
|
|
147
145
|
function mergeCloudSystemPluginRefs(
|
|
148
146
|
defaults: PluginRef[],
|
|
149
147
|
diskRefs: PluginRef[],
|
|
150
148
|
): PluginRef[] {
|
|
151
|
-
const defaultOpenbot = defaults.find((ref) => ref.id ===
|
|
152
|
-
const diskOpenbot = diskRefs.find((ref) => ref.id ===
|
|
149
|
+
const defaultOpenbot = defaults.find((ref) => ref.id === OPENBOT_PLUGIN_ID);
|
|
150
|
+
const diskOpenbot = diskRefs.find((ref) => ref.id === OPENBOT_PLUGIN_ID);
|
|
153
151
|
|
|
154
152
|
if (!defaultOpenbot) return defaults;
|
|
155
153
|
|
|
@@ -174,7 +172,7 @@ const STATE_AGENT_INSTRUCTIONS =
|
|
|
174
172
|
'Built-in infra agent for deterministic state reads. No conversational model is attached; handle storage, approvals, memory, and plugin marketplace events.';
|
|
175
173
|
|
|
176
174
|
function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails {
|
|
177
|
-
const defaultPlugins = isCloudMode() ?
|
|
175
|
+
const defaultPlugins = isCloudMode() ? getCloudSystemDefaultPlugins() : SYSTEM_DEFAULT_PLUGINS;
|
|
178
176
|
const defaults: AgentDetails = {
|
|
179
177
|
id: SYSTEM_AGENT_ID,
|
|
180
178
|
name: 'OpenBot',
|
|
@@ -277,9 +275,6 @@ const agentSummaryFromDetails = (details: AgentDetails): Agent => ({
|
|
|
277
275
|
updatedAt: details.updatedAt,
|
|
278
276
|
});
|
|
279
277
|
|
|
280
|
-
// Suppress unused warning until system agent customization re-uses openbotPlugin metadata.
|
|
281
|
-
void openbotPlugin;
|
|
282
|
-
|
|
283
278
|
/** Built-in agents may persist optional `agents/<id>/AGENT.md` overlays; read path merges them with defaults. */
|
|
284
279
|
const isBuiltinOverlayAgentId = (agentId: string): boolean =>
|
|
285
280
|
agentId === SYSTEM_AGENT_ID || agentId === STATE_AGENT_ID;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
3
|
+
isCloudMode,
|
|
4
|
+
isCloudSystemAgent,
|
|
5
|
+
parseOpenbotAuthMode,
|
|
6
|
+
type OpenbotAuthMode,
|
|
7
|
+
} from '../../app/cloud-mode.js';
|
|
8
|
+
import { ORCHESTRATOR_AGENT_ID } from '../../app/agent-ids.js';
|
|
9
|
+
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
10
|
+
import { getBaseDir, resolvePath, saveConfig } from '../../app/config.js';
|
|
11
|
+
import {
|
|
12
|
+
listApiKeyProvidersFromRegistry,
|
|
13
|
+
resolveModelRegistry,
|
|
14
|
+
} from './model-registry.js';
|
|
15
|
+
import type { PluginHost, RunAgentHostOptions } from './types.js';
|
|
16
|
+
|
|
17
|
+
export function createPluginHost(
|
|
18
|
+
runAgent: (options: RunAgentHostOptions) => Promise<void>,
|
|
19
|
+
): PluginHost {
|
|
20
|
+
return {
|
|
21
|
+
runAgent,
|
|
22
|
+
isCloudSystemAgent,
|
|
23
|
+
isCloudMode,
|
|
24
|
+
parseOpenbotAuthMode,
|
|
25
|
+
resolveModelRegistry,
|
|
26
|
+
listApiKeyProvidersFromRegistry,
|
|
27
|
+
saveConfig,
|
|
28
|
+
getBaseDir,
|
|
29
|
+
resolvePath,
|
|
30
|
+
orchestratorAgentId: ORCHESTRATOR_AGENT_ID,
|
|
31
|
+
openbotPluginId: OPENBOT_PLUGIN_ID,
|
|
32
|
+
defaultCloudAuthMode: DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type { OpenbotAuthMode };
|
|
@@ -2,7 +2,11 @@ import {
|
|
|
2
2
|
DEFAULT_MARKETPLACE_REGISTRY_URL,
|
|
3
3
|
loadConfig,
|
|
4
4
|
} from '../../app/config.js';
|
|
5
|
-
import {
|
|
5
|
+
import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
8
|
+
isCloudMode,
|
|
9
|
+
} from '../../app/cloud-mode.js';
|
|
6
10
|
import type { ConfigSchema, PluginDescriptor } from './domain.js';
|
|
7
11
|
|
|
8
12
|
export type RegistryProviderCatalog = Record<
|
|
@@ -92,9 +96,7 @@ export function listApiKeyProvidersFromRegistry(
|
|
|
92
96
|
function pickDefaultModelValue(options: RegistryModelOption[]): string | undefined {
|
|
93
97
|
if (options.length === 0) return undefined;
|
|
94
98
|
const values = options.map((option) => option.value);
|
|
95
|
-
const preferred =
|
|
96
|
-
? values.find((value) => value.startsWith('openbot/'))
|
|
97
|
-
: values.find((value) => value.startsWith('openai/'));
|
|
99
|
+
const preferred = values.find((value) => value.startsWith('openai/'));
|
|
98
100
|
return preferred ?? values[0];
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -102,7 +104,7 @@ export function enrichOpenbotPluginDescriptor(
|
|
|
102
104
|
descriptor: PluginDescriptor,
|
|
103
105
|
modelOptions: RegistryModelOption[],
|
|
104
106
|
): PluginDescriptor {
|
|
105
|
-
if (descriptor.id !==
|
|
107
|
+
if (descriptor.id !== OPENBOT_PLUGIN_ID || !descriptor.configSchema) return descriptor;
|
|
106
108
|
|
|
107
109
|
const modelProperty = descriptor.configSchema.properties.model;
|
|
108
110
|
if (!modelProperty) return descriptor;
|
|
@@ -133,14 +135,44 @@ export function enrichOpenbotPluginDescriptor(
|
|
|
133
135
|
nextModelProperty.default = defaultModel;
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
const { model: _model, authMode: _authMode, ...otherProperties } =
|
|
139
|
+
descriptor.configSchema.properties;
|
|
140
|
+
|
|
141
|
+
const cloudAuthModeProperty: ConfigSchema['properties'][string] = {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: 'Credits uses OpenBot platform billing. BYOK uses your own API keys.',
|
|
144
|
+
enum: ['credits', 'byok'],
|
|
145
|
+
default: DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
|
|
146
|
+
options: [
|
|
147
|
+
{
|
|
148
|
+
label: 'Credits',
|
|
149
|
+
value: 'credits',
|
|
150
|
+
description: 'Use OpenBot platform credits.',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
label: 'Bring your own key',
|
|
154
|
+
value: 'byok',
|
|
155
|
+
description: 'Use your own provider API keys.',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const nextProperties: ConfigSchema['properties'] = isCloudMode()
|
|
161
|
+
? {
|
|
162
|
+
authMode: cloudAuthModeProperty,
|
|
163
|
+
model: nextModelProperty,
|
|
164
|
+
...otherProperties,
|
|
165
|
+
}
|
|
166
|
+
: {
|
|
167
|
+
model: nextModelProperty,
|
|
168
|
+
...otherProperties,
|
|
169
|
+
};
|
|
170
|
+
|
|
136
171
|
return {
|
|
137
172
|
...descriptor,
|
|
138
173
|
configSchema: {
|
|
139
174
|
...descriptor.configSchema,
|
|
140
|
-
properties:
|
|
141
|
-
...descriptor.configSchema.properties,
|
|
142
|
-
model: nextModelProperty,
|
|
143
|
-
},
|
|
175
|
+
properties: nextProperties,
|
|
144
176
|
},
|
|
145
177
|
};
|
|
146
178
|
}
|