openbot 0.4.5 → 0.4.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/dist/app/channel-ids.js +3 -0
- package/dist/app/cli.js +1 -1
- package/dist/app/server.js +24 -2
- package/dist/plugins/openbot/context.js +6 -25
- package/dist/plugins/openbot/runtime.js +116 -81
- package/dist/plugins/openbot/system-prompt.js +3 -6
- package/dist/plugins/plugin-manager/index.js +3 -45
- package/dist/plugins/storage/index.js +2 -41
- package/dist/plugins/storage/service.js +54 -14
- package/dist/services/plugins/service.js +0 -4
- package/package.json +2 -1
- package/src/app/channel-ids.ts +5 -0
- package/src/app/cli.ts +1 -1
- package/src/app/server.ts +31 -3
- package/src/app/types.ts +2 -10
- package/src/plugins/openbot/context.ts +7 -26
- package/src/plugins/openbot/runtime.ts +137 -89
- package/src/plugins/openbot/system-prompt.ts +3 -8
- package/src/plugins/plugin-manager/index.ts +2 -50
- package/src/plugins/storage/index.ts +4 -46
- package/src/plugins/storage/service.ts +78 -14
- package/src/services/plugins/domain.ts +7 -4
- package/src/services/plugins/service.ts +0 -6
- package/dist/plugins/thread-naming/generate-title.js +0 -44
- package/dist/plugins/thread-naming/index.js +0 -103
- package/dist/services/thread-naming.js +0 -81
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Default channel when requests omit channelId (general-purpose conversation). */
|
|
2
|
+
export const UNCATEGORIZED_CHANNEL_ID = 'uncategorized';
|
|
3
|
+
export const DEFAULT_UNCATEGORIZED_SPEC = '# Uncategorized\n\nGeneral-purpose channel for conversations without a dedicated channel.';
|
package/dist/app/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ function checkNodeVersion() {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
checkNodeVersion();
|
|
19
|
-
program.name('openbot').description('OpenBot CLI').version('0.4.
|
|
19
|
+
program.name('openbot').description('OpenBot CLI').version('0.4.6');
|
|
20
20
|
program
|
|
21
21
|
.command('start')
|
|
22
22
|
.description('Start the OpenBot harness')
|
package/dist/app/server.js
CHANGED
|
@@ -17,6 +17,7 @@ import { buildWorkspaceFileUrl, getPublicBaseUrl, openChannelFileStream, } from
|
|
|
17
17
|
import { ensureEventId, openBotEventFromQuery } from './utils.js';
|
|
18
18
|
import { abortRegistry, abortKey } from '../services/abort.js';
|
|
19
19
|
import { resolveRespondingAgentId } from './responding-agent.js';
|
|
20
|
+
import { DEFAULT_UNCATEGORIZED_SPEC, UNCATEGORIZED_CHANNEL_ID, } from './channel-ids.js';
|
|
20
21
|
export async function startServer(options = {}) {
|
|
21
22
|
const publishEventSchema = z
|
|
22
23
|
.object({
|
|
@@ -43,8 +44,17 @@ export async function startServer(options = {}) {
|
|
|
43
44
|
// Pre-warm caches for agents and plugins to speed up first UI load
|
|
44
45
|
storageService.getAgents().catch((err) => console.warn('[server] Failed to pre-warm agents cache', err));
|
|
45
46
|
storageService.getPlugins().catch((err) => console.warn('[server] Failed to pre-warm plugins cache', err));
|
|
47
|
+
const getRawChannelId = (req) => {
|
|
48
|
+
const raw = req.get('x-openbot-channel-id') ||
|
|
49
|
+
req.query.channelId ||
|
|
50
|
+
(req.body && req.body.channelId);
|
|
51
|
+
if (typeof raw !== 'string')
|
|
52
|
+
return undefined;
|
|
53
|
+
const trimmed = raw.trim();
|
|
54
|
+
return trimmed || undefined;
|
|
55
|
+
};
|
|
46
56
|
const getContext = (req) => {
|
|
47
|
-
const
|
|
57
|
+
const rawChannelId = getRawChannelId(req);
|
|
48
58
|
const threadId = req.get('x-openbot-thread-id') || req.query.threadId || (req.body && req.body.threadId);
|
|
49
59
|
const agentId = req.get('x-openbot-agent-id') || req.query.agentId || (req.body && req.body.agentId);
|
|
50
60
|
const runId = req.get('x-openbot-run-id') ||
|
|
@@ -55,7 +65,8 @@ export async function startServer(options = {}) {
|
|
|
55
65
|
req.query.responseType ||
|
|
56
66
|
(req.body && req.body.responseType);
|
|
57
67
|
return {
|
|
58
|
-
channelId: (
|
|
68
|
+
channelId: (rawChannelId || UNCATEGORIZED_CHANNEL_ID),
|
|
69
|
+
rawChannelId,
|
|
59
70
|
threadId: threadId,
|
|
60
71
|
agentId: agentId,
|
|
61
72
|
runId: runId,
|
|
@@ -434,6 +445,17 @@ export async function startServer(options = {}) {
|
|
|
434
445
|
};
|
|
435
446
|
try {
|
|
436
447
|
ensureEventId(event);
|
|
448
|
+
const isUserConversationStart = event.type === 'agent:invoke' &&
|
|
449
|
+
event.data?.role === 'user' &&
|
|
450
|
+
typeof event.data.content === 'string' &&
|
|
451
|
+
event.data.content.trim().length > 0;
|
|
452
|
+
if (isUserConversationStart && channelId === UNCATEGORIZED_CHANNEL_ID) {
|
|
453
|
+
await storageService.ensureChannel({
|
|
454
|
+
channelId: UNCATEGORIZED_CHANNEL_ID,
|
|
455
|
+
spec: DEFAULT_UNCATEGORIZED_SPEC,
|
|
456
|
+
initialState: { name: 'Uncategorized' },
|
|
457
|
+
});
|
|
458
|
+
}
|
|
437
459
|
const bindIfUnbound = event.type === 'agent:invoke';
|
|
438
460
|
const resolved = await resolveRespondingAgentId({
|
|
439
461
|
channelId,
|
|
@@ -20,19 +20,11 @@ export const getContextBudgetForModel = (modelString) => {
|
|
|
20
20
|
};
|
|
21
21
|
/** Built-in orchestrator agent id. */
|
|
22
22
|
export const ORCHESTRATOR_AGENT_ID = 'system';
|
|
23
|
-
/**
|
|
24
|
-
* Check if a channel is a solo DM (only the agent is present).
|
|
25
|
-
*/
|
|
26
|
-
export function isDmSoloChannel(participants, agentId) {
|
|
27
|
-
return participants.length === 0 || (participants.length === 1 && participants[0] === agentId);
|
|
28
|
-
}
|
|
29
23
|
/**
|
|
30
24
|
* Simplified context builder for MVP.
|
|
31
25
|
*/
|
|
32
26
|
export async function buildContext(state, storage) {
|
|
33
27
|
const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
|
|
34
|
-
const participants = channelDetails?.participants || [];
|
|
35
|
-
const isDm = isDmSoloChannel(participants, agentId);
|
|
36
28
|
const sections = [];
|
|
37
29
|
// Fetch agents once if storage is available
|
|
38
30
|
const allAgents = storage?.getAgents ? await storage.getAgents().catch(() => []) : [];
|
|
@@ -42,24 +34,13 @@ export async function buildContext(state, storage) {
|
|
|
42
34
|
}
|
|
43
35
|
// 2. Environment
|
|
44
36
|
let env = '## ENVIRONMENT\n';
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
const channelName = channelDetails?.name || channelId;
|
|
38
|
+
env += `- Mode: Channel (#${channelName})\n`;
|
|
39
|
+
if (channelDetails?.cwd) {
|
|
40
|
+
env += `- Workspace: ${channelDetails.cwd}\n`;
|
|
47
41
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
env += `- Mode: Channel (#${channelName})\n`;
|
|
51
|
-
if (channelDetails?.cwd) {
|
|
52
|
-
env += `- Workspace: ${channelDetails.cwd}\n`;
|
|
53
|
-
}
|
|
54
|
-
if (threadId) {
|
|
55
|
-
env += `- Thread: ${threadDetails?.name || threadId}\n`;
|
|
56
|
-
}
|
|
57
|
-
const peerIds = participants.filter((id) => id !== agentId);
|
|
58
|
-
const participantLabels = peerIds.map((id) => {
|
|
59
|
-
const agent = allAgents.find((a) => a.id === id);
|
|
60
|
-
return agent ? `${agent.name} (${id})` : id;
|
|
61
|
-
});
|
|
62
|
-
env += `- Participants: ${participantLabels.length > 0 ? participantLabels.join(', ') : 'None'}\n`;
|
|
42
|
+
if (threadId) {
|
|
43
|
+
env += `- Thread: ${threadDetails?.name || threadId}\n`;
|
|
63
44
|
}
|
|
64
45
|
sections.push(env);
|
|
65
46
|
// 2.5 Installed Agents
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import { generateText } from 'ai';
|
|
2
2
|
import { openai } from '@ai-sdk/openai';
|
|
3
3
|
import { anthropic } from '@ai-sdk/anthropic';
|
|
4
|
+
import { google } from '@ai-sdk/google';
|
|
4
5
|
import { eventsToModelMessages } from './history.js';
|
|
5
6
|
import { buildContext, } from './context.js';
|
|
6
|
-
import { saveConfig } from '../../app/config.js';
|
|
7
|
-
import {
|
|
7
|
+
import { saveConfig, DEFAULT_MARKETPLACE_REGISTRY_URL } from '../../app/config.js';
|
|
8
|
+
import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
|
|
9
|
+
let cachedRegistry = null;
|
|
10
|
+
async function fetchRegistry() {
|
|
11
|
+
if (cachedRegistry)
|
|
12
|
+
return cachedRegistry;
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch(DEFAULT_MARKETPLACE_REGISTRY_URL);
|
|
15
|
+
if (!response.ok)
|
|
16
|
+
throw new Error(`Failed to fetch registry: ${response.statusText}`);
|
|
17
|
+
cachedRegistry = (await response.json());
|
|
18
|
+
return cachedRegistry;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error('[openbot] Failed to fetch model registry:', error);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
8
25
|
function resolveModel(modelString) {
|
|
9
26
|
const [provider, ...rest] = modelString.split('/');
|
|
10
27
|
const modelId = rest.join('/');
|
|
@@ -16,6 +33,8 @@ function resolveModel(modelString) {
|
|
|
16
33
|
return openai(modelId);
|
|
17
34
|
case 'anthropic':
|
|
18
35
|
return anthropic(modelId);
|
|
36
|
+
case 'google':
|
|
37
|
+
return google(modelId);
|
|
19
38
|
default:
|
|
20
39
|
throw new Error(`Unsupported AI provider: "${provider}"`);
|
|
21
40
|
}
|
|
@@ -182,47 +201,20 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
182
201
|
errorMessage.includes('Unauthorized') ||
|
|
183
202
|
errorMessage.includes('authentication');
|
|
184
203
|
if (isApiKeyError) {
|
|
185
|
-
const [currentProvider, ...rest] = currentModelString.split('/');
|
|
186
|
-
const currentModelId = rest.join('/');
|
|
187
204
|
yield {
|
|
188
205
|
type: 'client:ui:widget',
|
|
189
206
|
data: {
|
|
190
|
-
kind: '
|
|
191
|
-
widgetId: `
|
|
192
|
-
title: `AI Provider
|
|
193
|
-
description:
|
|
194
|
-
|
|
195
|
-
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
type: 'select',
|
|
199
|
-
required: true,
|
|
200
|
-
options: [
|
|
201
|
-
{ label: 'OpenAI', value: 'openai' },
|
|
202
|
-
{ label: 'Anthropic', value: 'anthropic' },
|
|
203
|
-
],
|
|
204
|
-
defaultValue: currentProvider === 'anthropic' ? 'anthropic' : 'openai',
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
id: 'model',
|
|
208
|
-
label: 'Model',
|
|
209
|
-
type: 'text',
|
|
210
|
-
description: 'Model name without the provider prefix (e.g. `gpt-4o-mini` or `claude-3-5-sonnet-20240620`).',
|
|
211
|
-
placeholder: 'gpt-4o-mini',
|
|
212
|
-
required: true,
|
|
213
|
-
defaultValue: currentModelId,
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
id: 'apiKey',
|
|
217
|
-
label: 'API Key',
|
|
218
|
-
type: 'text',
|
|
219
|
-
placeholder: `sk-...`,
|
|
220
|
-
required: true,
|
|
221
|
-
},
|
|
207
|
+
kind: 'choice',
|
|
208
|
+
widgetId: `api_provider_selection_${Date.now()}`,
|
|
209
|
+
title: `Setup AI Provider`,
|
|
210
|
+
description: `Select a provider to continue.`,
|
|
211
|
+
actions: [
|
|
212
|
+
{ id: 'openai', label: 'OpenAI', variant: 'primary' },
|
|
213
|
+
{ id: 'anthropic', label: 'Anthropic', variant: 'primary' },
|
|
214
|
+
{ id: 'google', label: 'Google', variant: 'primary' },
|
|
222
215
|
],
|
|
223
|
-
submitLabel: 'Save & Continue',
|
|
224
216
|
metadata: {
|
|
225
|
-
type: '
|
|
217
|
+
type: 'api_provider_selection',
|
|
226
218
|
},
|
|
227
219
|
},
|
|
228
220
|
meta: { agentId: context.state.agentId, threadId },
|
|
@@ -244,43 +236,6 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
244
236
|
};
|
|
245
237
|
}
|
|
246
238
|
const threadId = event.meta?.threadId || context.state.threadId;
|
|
247
|
-
// Auto-add participants if tagged in the prompt
|
|
248
|
-
const content = event.data?.content;
|
|
249
|
-
if (content && storage) {
|
|
250
|
-
try {
|
|
251
|
-
const allAgents = await storage.getAgents();
|
|
252
|
-
const tags = content.match(/@([\w-]+)/g);
|
|
253
|
-
if (tags) {
|
|
254
|
-
const taggedAgentIds = tags.map((t) => t.slice(1));
|
|
255
|
-
const validAgentIds = taggedAgentIds.filter((id) => allAgents.some((a) => a.id === id));
|
|
256
|
-
const currentParticipants = context.state.channelDetails?.participants || [];
|
|
257
|
-
const newParticipants = [...new Set([...currentParticipants, ...validAgentIds])];
|
|
258
|
-
if (newParticipants.length > currentParticipants.length) {
|
|
259
|
-
// Update storage
|
|
260
|
-
await storage.patchChannelState({
|
|
261
|
-
channelId: context.state.channelId,
|
|
262
|
-
state: { participants: newParticipants },
|
|
263
|
-
});
|
|
264
|
-
// Refresh local state
|
|
265
|
-
context.state.channelDetails = await storage.getChannelDetails({
|
|
266
|
-
channelId: context.state.channelId,
|
|
267
|
-
});
|
|
268
|
-
// Notify UI/others about the change
|
|
269
|
-
yield {
|
|
270
|
-
type: 'action:patch_channel_details:result',
|
|
271
|
-
data: { success: true, updatedFields: ['participants'] },
|
|
272
|
-
meta: {
|
|
273
|
-
agentId: context.state.agentId,
|
|
274
|
-
threadId,
|
|
275
|
-
},
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
console.warn('[openbot] Failed to auto-add participants from tags:', error);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
239
|
// clear the tool batch if the agent is invoked
|
|
285
240
|
// this is to prevent the tool batch from being used for a new agent invocation
|
|
286
241
|
await createToolBatchTracker(context.state, storage, context.state.channelId, threadId).clear();
|
|
@@ -302,15 +257,88 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
302
257
|
yield* runLLM(context, threadId);
|
|
303
258
|
});
|
|
304
259
|
builder.on('client:ui:widget:response', async function* (event, context) {
|
|
305
|
-
const { metadata, values } = event.data;
|
|
260
|
+
const { metadata, values, actionId } = event.data;
|
|
261
|
+
const threadId = event.meta?.threadId || context.state.threadId;
|
|
262
|
+
if (metadata?.type === 'api_provider_selection') {
|
|
263
|
+
const provider = actionId;
|
|
264
|
+
const [_, ...rest] = currentModelString.split('/');
|
|
265
|
+
const currentModelId = rest.join('/');
|
|
266
|
+
const registry = await fetchRegistry();
|
|
267
|
+
const providerData = registry?.providers[provider];
|
|
268
|
+
const providerLinks = {
|
|
269
|
+
openai: 'https://platform.openai.com/api-keys',
|
|
270
|
+
anthropic: 'https://console.anthropic.com/settings/keys',
|
|
271
|
+
google: 'https://aistudio.google.com/app/apikey',
|
|
272
|
+
};
|
|
273
|
+
const label = providerData?.label || provider;
|
|
274
|
+
const link = providerLinks[provider] || '';
|
|
275
|
+
const modelOptions = providerData?.models.map((m) => ({
|
|
276
|
+
label: m.label,
|
|
277
|
+
value: m.id,
|
|
278
|
+
}));
|
|
279
|
+
const defaultModel = modelOptions?.[0]?.value || 'gpt-4o-mini';
|
|
280
|
+
const defaultValue = modelOptions?.find((m) => m.value === currentModelId)?.value ||
|
|
281
|
+
currentModelId ||
|
|
282
|
+
defaultModel;
|
|
283
|
+
yield {
|
|
284
|
+
type: 'client:ui:widget',
|
|
285
|
+
data: {
|
|
286
|
+
widgetId: event.data.widgetId,
|
|
287
|
+
kind: 'message',
|
|
288
|
+
title: 'Provider Selected',
|
|
289
|
+
body: `${label} provider was selected.`,
|
|
290
|
+
state: 'submitted',
|
|
291
|
+
display: 'collapsed',
|
|
292
|
+
disabled: true,
|
|
293
|
+
actions: [],
|
|
294
|
+
},
|
|
295
|
+
meta: { agentId: context.state.agentId, threadId },
|
|
296
|
+
};
|
|
297
|
+
yield {
|
|
298
|
+
type: 'client:ui:widget',
|
|
299
|
+
data: {
|
|
300
|
+
kind: 'form',
|
|
301
|
+
widgetId: `api_key_request_${Date.now()}`,
|
|
302
|
+
title: `${label} Setup`,
|
|
303
|
+
description: `Enter your API key and select a model.`,
|
|
304
|
+
fields: [
|
|
305
|
+
{
|
|
306
|
+
id: 'model',
|
|
307
|
+
label: 'Model',
|
|
308
|
+
type: modelOptions ? 'select' : 'text',
|
|
309
|
+
description: modelOptions ? undefined : `Model name (e.g. \`${defaultModel}\`).`,
|
|
310
|
+
options: modelOptions,
|
|
311
|
+
placeholder: defaultModel,
|
|
312
|
+
required: true,
|
|
313
|
+
defaultValue,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: 'apiKey',
|
|
317
|
+
label: 'API Key',
|
|
318
|
+
type: 'password',
|
|
319
|
+
description: `Get your key here: [${link}](${link})`,
|
|
320
|
+
placeholder: `sk-...`,
|
|
321
|
+
required: true,
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
submitLabel: 'Save & Continue',
|
|
325
|
+
metadata: {
|
|
326
|
+
type: 'api_key_request',
|
|
327
|
+
provider,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
meta: { agentId: context.state.agentId, threadId },
|
|
331
|
+
};
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
306
334
|
if (metadata?.type !== 'api_key_request')
|
|
307
335
|
return;
|
|
308
|
-
if (!values?.apiKey || !values?.
|
|
336
|
+
if (!values?.apiKey || !values?.model)
|
|
309
337
|
return;
|
|
310
|
-
const provider = String(values.provider);
|
|
338
|
+
const provider = String(values.provider || metadata.provider);
|
|
311
339
|
const modelId = String(values.model).trim();
|
|
312
340
|
const apiKey = String(values.apiKey);
|
|
313
|
-
if (provider !== 'openai' && provider !== 'anthropic') {
|
|
341
|
+
if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'google') {
|
|
314
342
|
yield {
|
|
315
343
|
type: 'agent:output',
|
|
316
344
|
data: { content: `Unsupported provider: ${provider}` },
|
|
@@ -318,7 +346,11 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
318
346
|
};
|
|
319
347
|
return;
|
|
320
348
|
}
|
|
321
|
-
const envVar = provider === 'openai'
|
|
349
|
+
const envVar = provider === 'openai'
|
|
350
|
+
? 'OPENAI_API_KEY'
|
|
351
|
+
: provider === 'anthropic'
|
|
352
|
+
? 'ANTHROPIC_API_KEY'
|
|
353
|
+
: 'GOOGLE_GENERATIVE_AI_API_KEY';
|
|
322
354
|
const newModelString = `${provider}/${modelId}`;
|
|
323
355
|
if (!storage)
|
|
324
356
|
return;
|
|
@@ -363,10 +395,13 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
363
395
|
title: 'API Key Saved',
|
|
364
396
|
body: `Successfully saved ${provider} API key and selected model \`${newModelString}\`. You can now continue your conversation.`,
|
|
365
397
|
state: 'submitted',
|
|
366
|
-
|
|
398
|
+
display: 'collapsed',
|
|
399
|
+
disabled: true,
|
|
400
|
+
actions: [],
|
|
367
401
|
},
|
|
368
402
|
meta: { agentId: context.state.agentId },
|
|
369
403
|
};
|
|
404
|
+
yield* runLLM(context, threadId);
|
|
370
405
|
}
|
|
371
406
|
catch (error) {
|
|
372
407
|
yield {
|
|
@@ -7,13 +7,12 @@ export const OPENBOT_SYSTEM_PROMPT = [
|
|
|
7
7
|
'- **Credential Guidance**: If an agent or tool requires credentials, inform the user they can be managed under "Settings > Variables".',
|
|
8
8
|
'',
|
|
9
9
|
'# CORE MISSION',
|
|
10
|
-
'You almost never execute tasks yourself. Instead, you delegate tasks to specialized agents
|
|
10
|
+
'You almost never execute tasks yourself. Instead, you delegate tasks to specialized agents. You act as a high-level manager, ensuring the right agent is working on the right task.',
|
|
11
11
|
'',
|
|
12
12
|
'# OPERATIONAL GUIDELINES',
|
|
13
13
|
'- **Channel and Threads**: The main and only way to communicate and act is through channels and threads. There might be a channel called "uncategorized" for general purpose communication.',
|
|
14
|
-
'- **
|
|
15
|
-
'- **
|
|
16
|
-
'- **Bash Tool Usage**: You should use the `bash` tool very rarely. Only use it when the user explicitly requests a command to be run or when it is absolutely necessary for a task that no other participant can handle.',
|
|
14
|
+
'- **Delegation**: You can delegate tasks to any specialized agent in the `INSTALLED AGENTS` list.',
|
|
15
|
+
'- **Bash Tool Usage**: You should use the `bash` tool very rarely. Only use it when the user explicitly requests a command to be run or when it is absolutely necessary for a task that no other agent can handle.',
|
|
17
16
|
'- **Context Awareness**: Use the provided ENVIRONMENT, CHANNEL SPECIFICATION, and MEMORIES to maintain continuity. Do not ask for information already present in these sections.',
|
|
18
17
|
'- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.',
|
|
19
18
|
'- **Structured Interaction**: Use the `render_widget` tool to collect information via forms, offer choices, or display lists. This is preferred over asking multiple separate questions in plain text.',
|
|
@@ -21,5 +20,3 @@ export const OPENBOT_SYSTEM_PROMPT = [
|
|
|
21
20
|
'# COMMUNICATION STYLE',
|
|
22
21
|
'- Be always concise, professional, and proactive.',
|
|
23
22
|
].join('\n');
|
|
24
|
-
/** Shown in the API key setup form when no provider credentials are configured. */
|
|
25
|
-
export const API_KEY_SETUP_MESSAGE = 'OpenBot runs AI agents locally with tools, memory, and delegation. Bring your own OpenAI or Anthropic key — it stays on your machine. Use the form below to get started.';
|
|
@@ -54,64 +54,22 @@ export const pluginManagerPlugin = {
|
|
|
54
54
|
});
|
|
55
55
|
builder.on('action:channel:install', async function* (event) {
|
|
56
56
|
try {
|
|
57
|
-
const { channelId: instanceId, name: templateName,
|
|
58
|
-
const {
|
|
57
|
+
const { channelId: instanceId, name: templateName, initialState: customInitialState, } = event.data;
|
|
58
|
+
const { channels } = await resolveMarketplaceRegistry();
|
|
59
59
|
// Try to find the template by ID or Name
|
|
60
60
|
const channelListing = channels.find((c) => c.id === instanceId) ||
|
|
61
61
|
channels.find((c) => c.name === templateName);
|
|
62
62
|
const channelId = instanceId;
|
|
63
|
-
const participants = customParticipants || channelListing?.participants || [];
|
|
64
63
|
const initialState = {
|
|
65
64
|
...(channelListing?.initialState || {}),
|
|
66
65
|
...(customInitialState || {}),
|
|
67
66
|
};
|
|
68
67
|
const spec = channelListing?.spec || '';
|
|
69
|
-
// 1. Auto-install participant agents if missing
|
|
70
|
-
for (const agentId of participants) {
|
|
71
|
-
const existingAgents = await storage.getAgents();
|
|
72
|
-
if (existingAgents.some((a) => a.id === agentId)) {
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
// Not found locally, look in marketplace
|
|
76
|
-
const agentListing = marketplaceAgents.find((a) => a.id === agentId);
|
|
77
|
-
if (agentListing) {
|
|
78
|
-
console.log(`[plugin-manager] Auto-installing agent ${agentId} for channel ${channelId}`);
|
|
79
|
-
// Install plugins for this agent
|
|
80
|
-
for (const ref of agentListing.plugins) {
|
|
81
|
-
const installed = await pluginService.isInstalled(ref.id);
|
|
82
|
-
if (!installed &&
|
|
83
|
-
ref.id.includes('/') === false &&
|
|
84
|
-
ref.id.includes('-plugin-') === false) {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (!installed) {
|
|
88
|
-
try {
|
|
89
|
-
await pluginService.install({ packageName: ref.id });
|
|
90
|
-
}
|
|
91
|
-
catch (err) {
|
|
92
|
-
console.warn(`[plugins] Failed to pre-install plugin ${ref.id}`, err);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// Create the agent
|
|
97
|
-
await storage.createAgent({
|
|
98
|
-
agentId: agentListing.id,
|
|
99
|
-
name: agentListing.name,
|
|
100
|
-
description: agentListing.description,
|
|
101
|
-
image: agentListing.image,
|
|
102
|
-
instructions: agentListing.instructions,
|
|
103
|
-
plugins: agentListing.plugins,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
68
|
// 2. Create the channel
|
|
108
69
|
await storage.createChannel({
|
|
109
70
|
channelId,
|
|
110
71
|
spec,
|
|
111
|
-
initialState
|
|
112
|
-
...initialState,
|
|
113
|
-
participants,
|
|
114
|
-
},
|
|
72
|
+
initialState,
|
|
115
73
|
});
|
|
116
74
|
const channelUrl = `/channels/${channelId}`;
|
|
117
75
|
yield {
|
|
@@ -38,15 +38,10 @@ const storageToolDefinitions = {
|
|
|
38
38
|
.optional()
|
|
39
39
|
.describe('Markdown content for the channel specification (SPEC.md). Use for goals and rules.'),
|
|
40
40
|
cwd: z.string().optional().describe('Current working directory for the channel.'),
|
|
41
|
-
participants: z
|
|
42
|
-
.array(z.string())
|
|
43
|
-
.optional()
|
|
44
|
-
.describe('List of agent IDs that are participants in this channel. When a user tags an agent (e.g. @agent-id), you should ensure they are added to this list if they are not already there.'),
|
|
45
41
|
})
|
|
46
42
|
.refine((value) => value.state !== undefined ||
|
|
47
43
|
value.spec !== undefined ||
|
|
48
|
-
value.cwd !== undefined
|
|
49
|
-
value.participants !== undefined, { message: 'Provide at least one of state, spec, cwd, or participants.' }),
|
|
44
|
+
value.cwd !== undefined, { message: 'Provide at least one of state, spec, or cwd.' }),
|
|
50
45
|
},
|
|
51
46
|
patch_thread_details: {
|
|
52
47
|
description: 'Patch current thread details (state).',
|
|
@@ -125,7 +120,7 @@ export const storagePlugin = {
|
|
|
125
120
|
};
|
|
126
121
|
});
|
|
127
122
|
builder.on('action:create_channel', async function* (event, context) {
|
|
128
|
-
const { channelId, spec, initialState, cwd
|
|
123
|
+
const { channelId, spec, initialState, cwd } = event.data;
|
|
129
124
|
const rawChannelId = (channelId || '').trim();
|
|
130
125
|
const channelSpec = typeof spec === 'string' ? spec : '';
|
|
131
126
|
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
@@ -139,15 +134,6 @@ export const storagePlugin = {
|
|
|
139
134
|
}
|
|
140
135
|
const channelUrl = `/channels/${rawChannelId}`;
|
|
141
136
|
const mergedInitial = { ...(initialState || {}) };
|
|
142
|
-
if (participants !== undefined) {
|
|
143
|
-
const normalized = Array.isArray(participants)
|
|
144
|
-
? participants
|
|
145
|
-
.filter((x) => typeof x === 'string')
|
|
146
|
-
.map((s) => s.trim())
|
|
147
|
-
.filter(Boolean)
|
|
148
|
-
: [];
|
|
149
|
-
mergedInitial.participants = normalized;
|
|
150
|
-
}
|
|
151
137
|
try {
|
|
152
138
|
await storage.createChannel({
|
|
153
139
|
channelId: rawChannelId,
|
|
@@ -232,18 +218,6 @@ export const storagePlugin = {
|
|
|
232
218
|
patch.cwd = data.cwd.trim();
|
|
233
219
|
updatedFields.push('cwd');
|
|
234
220
|
}
|
|
235
|
-
if (data.participants !== undefined) {
|
|
236
|
-
if (Array.isArray(data.participants)) {
|
|
237
|
-
patch.participants = data.participants
|
|
238
|
-
.filter((x) => typeof x === 'string')
|
|
239
|
-
.map((s) => s.trim())
|
|
240
|
-
.filter(Boolean);
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
patch.participants = [];
|
|
244
|
-
}
|
|
245
|
-
updatedFields.push('participants');
|
|
246
|
-
}
|
|
247
221
|
try {
|
|
248
222
|
if (updatedFields.length > 0) {
|
|
249
223
|
await storage.patchChannelState({ channelId: targetChannelId, state: patch });
|
|
@@ -293,19 +267,6 @@ export const storagePlugin = {
|
|
|
293
267
|
});
|
|
294
268
|
updatedFields.push('cwd');
|
|
295
269
|
}
|
|
296
|
-
if (data.participants !== undefined) {
|
|
297
|
-
const normalized = Array.isArray(data.participants)
|
|
298
|
-
? data.participants
|
|
299
|
-
.filter((x) => typeof x === 'string')
|
|
300
|
-
.map((s) => s.trim())
|
|
301
|
-
.filter(Boolean)
|
|
302
|
-
: [];
|
|
303
|
-
await storage.patchChannelState({
|
|
304
|
-
channelId: context.state.channelId,
|
|
305
|
-
state: { participants: normalized },
|
|
306
|
-
});
|
|
307
|
-
updatedFields.push('participants');
|
|
308
|
-
}
|
|
309
270
|
context.state.channelDetails = await storage.getChannelDetails({
|
|
310
271
|
channelId: context.state.channelId,
|
|
311
272
|
});
|