openbot 0.4.4 → 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/responding-agent.js +48 -0
- package/dist/app/server.js +112 -6
- 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 +55 -15
- 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/responding-agent.ts +74 -0
- package/src/app/server.ts +137 -7
- package/src/app/types.ts +7 -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 +80 -15
- package/src/services/plugins/domain.ts +22 -5
- 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
package/src/app/types.ts
CHANGED
|
@@ -141,6 +141,10 @@ export type GetEventsResultEvent = BaseEvent & {
|
|
|
141
141
|
};
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
export type GetActiveRunsEvent = BaseEvent & {
|
|
145
|
+
type: 'action:storage:get-active-runs';
|
|
146
|
+
};
|
|
147
|
+
|
|
144
148
|
export type GetAgentDetailsEvent = BaseEvent & {
|
|
145
149
|
type: 'action:storage:get-agent-details';
|
|
146
150
|
data: {
|
|
@@ -297,8 +301,6 @@ export type PatchChannelDetailsEvent = BaseEvent & {
|
|
|
297
301
|
state?: Record<string, unknown>;
|
|
298
302
|
spec?: string;
|
|
299
303
|
cwd?: string;
|
|
300
|
-
/** When set, replaces `state.json` `participants` (merged after `state` if both are sent). */
|
|
301
|
-
participants?: string[];
|
|
302
304
|
};
|
|
303
305
|
};
|
|
304
306
|
|
|
@@ -306,7 +308,7 @@ export type PatchChannelDetailsResultEvent = BaseEvent & {
|
|
|
306
308
|
type: 'action:patch_channel_details:result';
|
|
307
309
|
data: {
|
|
308
310
|
success: boolean;
|
|
309
|
-
updatedFields: ('state' | 'spec' | 'cwd'
|
|
311
|
+
updatedFields: ('state' | 'spec' | 'cwd')[];
|
|
310
312
|
};
|
|
311
313
|
};
|
|
312
314
|
|
|
@@ -565,8 +567,6 @@ export type CreateChannelEvent = BaseEvent & {
|
|
|
565
567
|
spec?: string;
|
|
566
568
|
initialState?: Record<string, unknown>;
|
|
567
569
|
cwd?: string;
|
|
568
|
-
/** Initial channel agent ids; written into `state.json` (overrides `initialState.participants` if both are set). */
|
|
569
|
-
participants?: string[];
|
|
570
570
|
};
|
|
571
571
|
meta?: {
|
|
572
572
|
toolCallId?: string;
|
|
@@ -590,8 +590,6 @@ export type UpdateChannelEvent = BaseEvent & {
|
|
|
590
590
|
channelId?: string;
|
|
591
591
|
name?: string;
|
|
592
592
|
cwd?: string;
|
|
593
|
-
/** Replaces the channel participant list when provided. */
|
|
594
|
-
participants?: string[];
|
|
595
593
|
};
|
|
596
594
|
};
|
|
597
595
|
|
|
@@ -651,7 +649,7 @@ export type UIWidgetAction = {
|
|
|
651
649
|
export type UIWidgetField = {
|
|
652
650
|
id: string;
|
|
653
651
|
label: string;
|
|
654
|
-
type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'multiselect' | 'date';
|
|
652
|
+
type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'multiselect' | 'date' | 'password';
|
|
655
653
|
description?: string;
|
|
656
654
|
placeholder?: string;
|
|
657
655
|
required?: boolean;
|
|
@@ -847,7 +845,6 @@ export type ListMarketplaceRegistryResultEvent = BaseEvent & {
|
|
|
847
845
|
image?: string;
|
|
848
846
|
spec?: string;
|
|
849
847
|
initialState?: Record<string, unknown>;
|
|
850
|
-
participants: string[];
|
|
851
848
|
starterPrompts?: Array<{ label: string; prompt: string }>;
|
|
852
849
|
}>;
|
|
853
850
|
error?: string;
|
|
@@ -859,7 +856,6 @@ export type InstallChannelEvent = BaseEvent & {
|
|
|
859
856
|
data: {
|
|
860
857
|
channelId: string;
|
|
861
858
|
name?: string;
|
|
862
|
-
participants?: string[];
|
|
863
859
|
initialState?: Record<string, unknown>;
|
|
864
860
|
};
|
|
865
861
|
};
|
|
@@ -1059,6 +1055,7 @@ export type OpenBotEvent =
|
|
|
1059
1055
|
| DeleteAgentResultEvent
|
|
1060
1056
|
| GetEventsEvent
|
|
1061
1057
|
| GetEventsResultEvent
|
|
1058
|
+
| GetActiveRunsEvent
|
|
1062
1059
|
| StreamThreadEvent
|
|
1063
1060
|
| GetVariablesEvent
|
|
1064
1061
|
| GetVariablesResultEvent
|
|
@@ -27,20 +27,11 @@ export const getContextBudgetForModel = (modelString: string): number => {
|
|
|
27
27
|
/** Built-in orchestrator agent id. */
|
|
28
28
|
export const ORCHESTRATOR_AGENT_ID = 'system';
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Check if a channel is a solo DM (only the agent is present).
|
|
32
|
-
*/
|
|
33
|
-
export function isDmSoloChannel(participants: string[], agentId: string): boolean {
|
|
34
|
-
return participants.length === 0 || (participants.length === 1 && participants[0] === agentId);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
30
|
/**
|
|
38
31
|
* Simplified context builder for MVP.
|
|
39
32
|
*/
|
|
40
33
|
export async function buildContext(state: OpenBotState, storage?: Storage): Promise<string> {
|
|
41
34
|
const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
|
|
42
|
-
const participants = channelDetails?.participants || [];
|
|
43
|
-
const isDm = isDmSoloChannel(participants, agentId);
|
|
44
35
|
|
|
45
36
|
const sections: string[] = [];
|
|
46
37
|
|
|
@@ -54,23 +45,13 @@ export async function buildContext(state: OpenBotState, storage?: Storage): Prom
|
|
|
54
45
|
|
|
55
46
|
// 2. Environment
|
|
56
47
|
let env = '## ENVIRONMENT\n';
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
if (threadId) {
|
|
66
|
-
env += `- Thread: ${threadDetails?.name || threadId}\n`;
|
|
67
|
-
}
|
|
68
|
-
const peerIds = participants.filter((id: string) => id !== agentId);
|
|
69
|
-
const participantLabels = peerIds.map((id) => {
|
|
70
|
-
const agent = allAgents.find((a) => a.id === id);
|
|
71
|
-
return agent ? `${agent.name} (${id})` : id;
|
|
72
|
-
});
|
|
73
|
-
env += `- Participants: ${participantLabels.length > 0 ? participantLabels.join(', ') : 'None'}\n`;
|
|
48
|
+
const channelName = channelDetails?.name || channelId;
|
|
49
|
+
env += `- Mode: Channel (#${channelName})\n`;
|
|
50
|
+
if (channelDetails?.cwd) {
|
|
51
|
+
env += `- Workspace: ${channelDetails.cwd}\n`;
|
|
52
|
+
}
|
|
53
|
+
if (threadId) {
|
|
54
|
+
env += `- Thread: ${threadDetails?.name || threadId}\n`;
|
|
74
55
|
}
|
|
75
56
|
sections.push(env);
|
|
76
57
|
|
|
@@ -2,6 +2,7 @@ import { MelonyPlugin, RuntimeContext } from 'melony';
|
|
|
2
2
|
import { generateText, type LanguageModel } from 'ai';
|
|
3
3
|
import { openai } from '@ai-sdk/openai';
|
|
4
4
|
import { anthropic } from '@ai-sdk/anthropic';
|
|
5
|
+
import { google } from '@ai-sdk/google';
|
|
5
6
|
import { OpenBotEvent, OpenBotState, AgentInvokeEvent } from '../../app/types.js';
|
|
6
7
|
import { eventsToModelMessages } from './history.js';
|
|
7
8
|
import { Storage } from '../../services/plugins/domain.js';
|
|
@@ -10,8 +11,33 @@ import {
|
|
|
10
11
|
ORCHESTRATOR_AGENT_ID,
|
|
11
12
|
buildContext,
|
|
12
13
|
} from './context.js';
|
|
13
|
-
import { saveConfig } from '../../app/config.js';
|
|
14
|
-
import {
|
|
14
|
+
import { saveConfig, DEFAULT_MARKETPLACE_REGISTRY_URL } from '../../app/config.js';
|
|
15
|
+
import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
|
|
16
|
+
|
|
17
|
+
interface ModelRegistry {
|
|
18
|
+
providers: Record<
|
|
19
|
+
string,
|
|
20
|
+
{
|
|
21
|
+
label: string;
|
|
22
|
+
models: Array<{ id: string; label: string; description: string }>;
|
|
23
|
+
}
|
|
24
|
+
>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let cachedRegistry: ModelRegistry | null = null;
|
|
28
|
+
|
|
29
|
+
async function fetchRegistry(): Promise<ModelRegistry | null> {
|
|
30
|
+
if (cachedRegistry) return cachedRegistry;
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(DEFAULT_MARKETPLACE_REGISTRY_URL);
|
|
33
|
+
if (!response.ok) throw new Error(`Failed to fetch registry: ${response.statusText}`);
|
|
34
|
+
cachedRegistry = (await response.json()) as ModelRegistry;
|
|
35
|
+
return cachedRegistry;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('[openbot] Failed to fetch model registry:', error);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
15
41
|
|
|
16
42
|
export interface OpenBotRuntimeOptions {
|
|
17
43
|
/** Provider model string (e.g. `openai/gpt-4o-mini`, `anthropic/claude-3-5-sonnet-20240620`). */
|
|
@@ -34,6 +60,8 @@ function resolveModel(modelString: string): LanguageModel {
|
|
|
34
60
|
return openai(modelId);
|
|
35
61
|
case 'anthropic':
|
|
36
62
|
return anthropic(modelId);
|
|
63
|
+
case 'google':
|
|
64
|
+
return google(modelId);
|
|
37
65
|
default:
|
|
38
66
|
throw new Error(`Unsupported AI provider: "${provider}"`);
|
|
39
67
|
}
|
|
@@ -246,49 +274,20 @@ export const openbotRuntime =
|
|
|
246
274
|
errorMessage.includes('authentication');
|
|
247
275
|
|
|
248
276
|
if (isApiKeyError) {
|
|
249
|
-
const [currentProvider, ...rest] = currentModelString.split('/');
|
|
250
|
-
const currentModelId = rest.join('/');
|
|
251
|
-
|
|
252
277
|
yield {
|
|
253
278
|
type: 'client:ui:widget',
|
|
254
279
|
data: {
|
|
255
|
-
kind: '
|
|
256
|
-
widgetId: `
|
|
257
|
-
title: `AI Provider
|
|
258
|
-
description:
|
|
259
|
-
|
|
260
|
-
{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
type: 'select',
|
|
264
|
-
required: true,
|
|
265
|
-
options: [
|
|
266
|
-
{ label: 'OpenAI', value: 'openai' },
|
|
267
|
-
{ label: 'Anthropic', value: 'anthropic' },
|
|
268
|
-
],
|
|
269
|
-
defaultValue: currentProvider === 'anthropic' ? 'anthropic' : 'openai',
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
id: 'model',
|
|
273
|
-
label: 'Model',
|
|
274
|
-
type: 'text',
|
|
275
|
-
description:
|
|
276
|
-
'Model name without the provider prefix (e.g. `gpt-4o-mini` or `claude-3-5-sonnet-20240620`).',
|
|
277
|
-
placeholder: 'gpt-4o-mini',
|
|
278
|
-
required: true,
|
|
279
|
-
defaultValue: currentModelId,
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
id: 'apiKey',
|
|
283
|
-
label: 'API Key',
|
|
284
|
-
type: 'text',
|
|
285
|
-
placeholder: `sk-...`,
|
|
286
|
-
required: true,
|
|
287
|
-
},
|
|
280
|
+
kind: 'choice',
|
|
281
|
+
widgetId: `api_provider_selection_${Date.now()}`,
|
|
282
|
+
title: `Setup AI Provider`,
|
|
283
|
+
description: `Select a provider to continue.`,
|
|
284
|
+
actions: [
|
|
285
|
+
{ id: 'openai', label: 'OpenAI', variant: 'primary' },
|
|
286
|
+
{ id: 'anthropic', label: 'Anthropic', variant: 'primary' },
|
|
287
|
+
{ id: 'google', label: 'Google', variant: 'primary' },
|
|
288
288
|
],
|
|
289
|
-
submitLabel: 'Save & Continue',
|
|
290
289
|
metadata: {
|
|
291
|
-
type: '
|
|
290
|
+
type: 'api_provider_selection',
|
|
292
291
|
},
|
|
293
292
|
},
|
|
294
293
|
meta: { agentId: context.state.agentId, threadId },
|
|
@@ -315,49 +314,6 @@ export const openbotRuntime =
|
|
|
315
314
|
|
|
316
315
|
const threadId = event.meta?.threadId || context.state.threadId;
|
|
317
316
|
|
|
318
|
-
// Auto-add participants if tagged in the prompt
|
|
319
|
-
const content = (event as AgentInvokeEvent).data?.content;
|
|
320
|
-
if (content && storage) {
|
|
321
|
-
try {
|
|
322
|
-
const allAgents = await storage.getAgents();
|
|
323
|
-
const tags = content.match(/@([\w-]+)/g);
|
|
324
|
-
if (tags) {
|
|
325
|
-
const taggedAgentIds = tags.map((t) => t.slice(1));
|
|
326
|
-
const validAgentIds = taggedAgentIds.filter((id) =>
|
|
327
|
-
allAgents.some((a) => a.id === id),
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
const currentParticipants = context.state.channelDetails?.participants || [];
|
|
331
|
-
const newParticipants = [...new Set([...currentParticipants, ...validAgentIds])];
|
|
332
|
-
|
|
333
|
-
if (newParticipants.length > currentParticipants.length) {
|
|
334
|
-
// Update storage
|
|
335
|
-
await storage.patchChannelState({
|
|
336
|
-
channelId: context.state.channelId,
|
|
337
|
-
state: { participants: newParticipants },
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// Refresh local state
|
|
341
|
-
context.state.channelDetails = await storage.getChannelDetails({
|
|
342
|
-
channelId: context.state.channelId,
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// Notify UI/others about the change
|
|
346
|
-
yield {
|
|
347
|
-
type: 'action:patch_channel_details:result',
|
|
348
|
-
data: { success: true, updatedFields: ['participants'] },
|
|
349
|
-
meta: {
|
|
350
|
-
agentId: context.state.agentId,
|
|
351
|
-
threadId,
|
|
352
|
-
},
|
|
353
|
-
} as OpenBotEvent;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
} catch (error) {
|
|
357
|
-
console.warn('[openbot] Failed to auto-add participants from tags:', error);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
317
|
// clear the tool batch if the agent is invoked
|
|
362
318
|
// this is to prevent the tool batch from being used for a new agent invocation
|
|
363
319
|
await createToolBatchTracker(
|
|
@@ -394,15 +350,98 @@ export const openbotRuntime =
|
|
|
394
350
|
});
|
|
395
351
|
|
|
396
352
|
builder.on('client:ui:widget:response', async function* (event, context) {
|
|
397
|
-
const { metadata, values } = event.data;
|
|
353
|
+
const { metadata, values, actionId } = event.data;
|
|
354
|
+
const threadId = event.meta?.threadId || context.state.threadId;
|
|
355
|
+
|
|
356
|
+
if (metadata?.type === 'api_provider_selection') {
|
|
357
|
+
const provider = actionId;
|
|
358
|
+
const [_, ...rest] = currentModelString.split('/');
|
|
359
|
+
const currentModelId = rest.join('/');
|
|
360
|
+
|
|
361
|
+
const registry = await fetchRegistry();
|
|
362
|
+
const providerData = registry?.providers[provider as string];
|
|
363
|
+
|
|
364
|
+
const providerLinks: Record<string, string> = {
|
|
365
|
+
openai: 'https://platform.openai.com/api-keys',
|
|
366
|
+
anthropic: 'https://console.anthropic.com/settings/keys',
|
|
367
|
+
google: 'https://aistudio.google.com/app/apikey',
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const label = providerData?.label || (provider as string);
|
|
371
|
+
const link = providerLinks[provider as string] || '';
|
|
372
|
+
|
|
373
|
+
const modelOptions = providerData?.models.map((m) => ({
|
|
374
|
+
label: m.label,
|
|
375
|
+
value: m.id,
|
|
376
|
+
}));
|
|
377
|
+
|
|
378
|
+
const defaultModel = modelOptions?.[0]?.value || 'gpt-4o-mini';
|
|
379
|
+
const defaultValue =
|
|
380
|
+
modelOptions?.find((m) => m.value === currentModelId)?.value ||
|
|
381
|
+
currentModelId ||
|
|
382
|
+
defaultModel;
|
|
383
|
+
|
|
384
|
+
yield {
|
|
385
|
+
type: 'client:ui:widget',
|
|
386
|
+
data: {
|
|
387
|
+
widgetId: event.data.widgetId,
|
|
388
|
+
kind: 'message',
|
|
389
|
+
title: 'Provider Selected',
|
|
390
|
+
body: `${label} provider was selected.`,
|
|
391
|
+
state: 'submitted',
|
|
392
|
+
display: 'collapsed',
|
|
393
|
+
disabled: true,
|
|
394
|
+
actions: [],
|
|
395
|
+
},
|
|
396
|
+
meta: { agentId: context.state.agentId, threadId },
|
|
397
|
+
} as OpenBotEvent;
|
|
398
|
+
|
|
399
|
+
yield {
|
|
400
|
+
type: 'client:ui:widget',
|
|
401
|
+
data: {
|
|
402
|
+
kind: 'form',
|
|
403
|
+
widgetId: `api_key_request_${Date.now()}`,
|
|
404
|
+
title: `${label} Setup`,
|
|
405
|
+
description: `Enter your API key and select a model.`,
|
|
406
|
+
fields: [
|
|
407
|
+
{
|
|
408
|
+
id: 'model',
|
|
409
|
+
label: 'Model',
|
|
410
|
+
type: modelOptions ? 'select' : 'text',
|
|
411
|
+
description: modelOptions ? undefined : `Model name (e.g. \`${defaultModel}\`).`,
|
|
412
|
+
options: modelOptions,
|
|
413
|
+
placeholder: defaultModel,
|
|
414
|
+
required: true,
|
|
415
|
+
defaultValue,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: 'apiKey',
|
|
419
|
+
label: 'API Key',
|
|
420
|
+
type: 'password',
|
|
421
|
+
description: `Get your key here: [${link}](${link})`,
|
|
422
|
+
placeholder: `sk-...`,
|
|
423
|
+
required: true,
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
submitLabel: 'Save & Continue',
|
|
427
|
+
metadata: {
|
|
428
|
+
type: 'api_key_request',
|
|
429
|
+
provider,
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
meta: { agentId: context.state.agentId, threadId },
|
|
433
|
+
} as OpenBotEvent;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
398
437
|
if (metadata?.type !== 'api_key_request') return;
|
|
399
|
-
if (!values?.apiKey || !values?.
|
|
438
|
+
if (!values?.apiKey || !values?.model) return;
|
|
400
439
|
|
|
401
|
-
const provider = String(values.provider);
|
|
440
|
+
const provider = String(values.provider || metadata.provider);
|
|
402
441
|
const modelId = String(values.model).trim();
|
|
403
442
|
const apiKey = String(values.apiKey);
|
|
404
443
|
|
|
405
|
-
if (provider !== 'openai' && provider !== 'anthropic') {
|
|
444
|
+
if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'google') {
|
|
406
445
|
yield {
|
|
407
446
|
type: 'agent:output',
|
|
408
447
|
data: { content: `Unsupported provider: ${provider}` },
|
|
@@ -411,7 +450,12 @@ export const openbotRuntime =
|
|
|
411
450
|
return;
|
|
412
451
|
}
|
|
413
452
|
|
|
414
|
-
const envVar =
|
|
453
|
+
const envVar =
|
|
454
|
+
provider === 'openai'
|
|
455
|
+
? 'OPENAI_API_KEY'
|
|
456
|
+
: provider === 'anthropic'
|
|
457
|
+
? 'ANTHROPIC_API_KEY'
|
|
458
|
+
: 'GOOGLE_GENERATIVE_AI_API_KEY';
|
|
415
459
|
const newModelString = `${provider}/${modelId}`;
|
|
416
460
|
|
|
417
461
|
if (!storage) return;
|
|
@@ -460,10 +504,14 @@ export const openbotRuntime =
|
|
|
460
504
|
title: 'API Key Saved',
|
|
461
505
|
body: `Successfully saved ${provider} API key and selected model \`${newModelString}\`. You can now continue your conversation.`,
|
|
462
506
|
state: 'submitted',
|
|
463
|
-
|
|
507
|
+
display: 'collapsed',
|
|
508
|
+
disabled: true,
|
|
509
|
+
actions: [],
|
|
464
510
|
},
|
|
465
511
|
meta: { agentId: context.state.agentId },
|
|
466
512
|
};
|
|
513
|
+
|
|
514
|
+
yield* runLLM(context, threadId);
|
|
467
515
|
} catch (error) {
|
|
468
516
|
yield {
|
|
469
517
|
type: 'agent:output',
|
|
@@ -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,7 +20,3 @@ export const OPENBOT_SYSTEM_PROMPT = [
|
|
|
21
20
|
'# COMMUNICATION STYLE',
|
|
22
21
|
'- Be always concise, professional, and proactive.',
|
|
23
22
|
].join('\n');
|
|
24
|
-
|
|
25
|
-
/** Shown in the API key setup form when no provider credentials are configured. */
|
|
26
|
-
export const API_KEY_SETUP_MESSAGE =
|
|
27
|
-
'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.';
|
|
@@ -67,10 +67,9 @@ export const pluginManagerPlugin: Plugin = {
|
|
|
67
67
|
const {
|
|
68
68
|
channelId: instanceId,
|
|
69
69
|
name: templateName,
|
|
70
|
-
participants: customParticipants,
|
|
71
70
|
initialState: customInitialState,
|
|
72
71
|
} = event.data;
|
|
73
|
-
const {
|
|
72
|
+
const { channels } = await resolveMarketplaceRegistry();
|
|
74
73
|
|
|
75
74
|
// Try to find the template by ID or Name
|
|
76
75
|
const channelListing =
|
|
@@ -78,64 +77,17 @@ export const pluginManagerPlugin: Plugin = {
|
|
|
78
77
|
channels.find((c) => c.name === templateName);
|
|
79
78
|
|
|
80
79
|
const channelId = instanceId;
|
|
81
|
-
const participants = customParticipants || channelListing?.participants || [];
|
|
82
80
|
const initialState = {
|
|
83
81
|
...(channelListing?.initialState || {}),
|
|
84
82
|
...(customInitialState || {}),
|
|
85
83
|
};
|
|
86
84
|
const spec = channelListing?.spec || '';
|
|
87
85
|
|
|
88
|
-
// 1. Auto-install participant agents if missing
|
|
89
|
-
for (const agentId of participants) {
|
|
90
|
-
const existingAgents = await storage.getAgents();
|
|
91
|
-
if (existingAgents.some((a) => a.id === agentId)) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Not found locally, look in marketplace
|
|
96
|
-
const agentListing = marketplaceAgents.find((a) => a.id === agentId);
|
|
97
|
-
if (agentListing) {
|
|
98
|
-
console.log(`[plugin-manager] Auto-installing agent ${agentId} for channel ${channelId}`);
|
|
99
|
-
|
|
100
|
-
// Install plugins for this agent
|
|
101
|
-
for (const ref of agentListing.plugins) {
|
|
102
|
-
const installed = await pluginService.isInstalled(ref.id);
|
|
103
|
-
if (
|
|
104
|
-
!installed &&
|
|
105
|
-
ref.id.includes('/') === false &&
|
|
106
|
-
ref.id.includes('-plugin-') === false
|
|
107
|
-
) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
if (!installed) {
|
|
111
|
-
try {
|
|
112
|
-
await pluginService.install({ packageName: ref.id });
|
|
113
|
-
} catch (err) {
|
|
114
|
-
console.warn(`[plugins] Failed to pre-install plugin ${ref.id}`, err);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Create the agent
|
|
120
|
-
await storage.createAgent({
|
|
121
|
-
agentId: agentListing.id,
|
|
122
|
-
name: agentListing.name,
|
|
123
|
-
description: agentListing.description,
|
|
124
|
-
image: agentListing.image,
|
|
125
|
-
instructions: agentListing.instructions,
|
|
126
|
-
plugins: agentListing.plugins,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
86
|
// 2. Create the channel
|
|
132
87
|
await storage.createChannel({
|
|
133
88
|
channelId,
|
|
134
89
|
spec,
|
|
135
|
-
initialState
|
|
136
|
-
...initialState,
|
|
137
|
-
participants,
|
|
138
|
-
},
|
|
90
|
+
initialState,
|
|
139
91
|
});
|
|
140
92
|
|
|
141
93
|
const channelUrl = `/channels/${channelId}`;
|
|
@@ -48,20 +48,13 @@ const storageToolDefinitions = {
|
|
|
48
48
|
'Markdown content for the channel specification (SPEC.md). Use for goals and rules.',
|
|
49
49
|
),
|
|
50
50
|
cwd: z.string().optional().describe('Current working directory for the channel.'),
|
|
51
|
-
participants: z
|
|
52
|
-
.array(z.string())
|
|
53
|
-
.optional()
|
|
54
|
-
.describe(
|
|
55
|
-
'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.',
|
|
56
|
-
),
|
|
57
51
|
})
|
|
58
52
|
.refine(
|
|
59
53
|
(value) =>
|
|
60
54
|
value.state !== undefined ||
|
|
61
55
|
value.spec !== undefined ||
|
|
62
|
-
value.cwd !== undefined
|
|
63
|
-
|
|
64
|
-
{ message: 'Provide at least one of state, spec, cwd, or participants.' },
|
|
56
|
+
value.cwd !== undefined,
|
|
57
|
+
{ message: 'Provide at least one of state, spec, or cwd.' },
|
|
65
58
|
),
|
|
66
59
|
},
|
|
67
60
|
patch_thread_details: {
|
|
@@ -155,7 +148,7 @@ export const storagePlugin: Plugin = {
|
|
|
155
148
|
});
|
|
156
149
|
|
|
157
150
|
builder.on('action:create_channel', async function* (event, context) {
|
|
158
|
-
const { channelId, spec, initialState, cwd
|
|
151
|
+
const { channelId, spec, initialState, cwd } = (event as any).data;
|
|
159
152
|
const rawChannelId = (channelId || '').trim();
|
|
160
153
|
const channelSpec = typeof spec === 'string' ? spec : '';
|
|
161
154
|
|
|
@@ -173,15 +166,6 @@ export const storagePlugin: Plugin = {
|
|
|
173
166
|
const channelUrl = `/channels/${rawChannelId}`;
|
|
174
167
|
|
|
175
168
|
const mergedInitial: Record<string, unknown> = { ...(initialState || {}) };
|
|
176
|
-
if (participants !== undefined) {
|
|
177
|
-
const normalized = Array.isArray(participants)
|
|
178
|
-
? participants
|
|
179
|
-
.filter((x: unknown): x is string => typeof x === 'string')
|
|
180
|
-
.map((s: string) => s.trim())
|
|
181
|
-
.filter(Boolean)
|
|
182
|
-
: [];
|
|
183
|
-
mergedInitial.participants = normalized;
|
|
184
|
-
}
|
|
185
169
|
|
|
186
170
|
try {
|
|
187
171
|
await storage.createChannel({
|
|
@@ -254,7 +238,6 @@ export const storagePlugin: Plugin = {
|
|
|
254
238
|
channelId?: string;
|
|
255
239
|
name?: string;
|
|
256
240
|
cwd?: string;
|
|
257
|
-
participants?: string[];
|
|
258
241
|
};
|
|
259
242
|
const targetChannelId = (data.channelId || context.state.channelId || '').trim();
|
|
260
243
|
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
@@ -279,17 +262,6 @@ export const storagePlugin: Plugin = {
|
|
|
279
262
|
patch.cwd = data.cwd.trim();
|
|
280
263
|
updatedFields.push('cwd');
|
|
281
264
|
}
|
|
282
|
-
if (data.participants !== undefined) {
|
|
283
|
-
if (Array.isArray(data.participants)) {
|
|
284
|
-
patch.participants = data.participants
|
|
285
|
-
.filter((x): x is string => typeof x === 'string')
|
|
286
|
-
.map((s) => s.trim())
|
|
287
|
-
.filter(Boolean);
|
|
288
|
-
} else {
|
|
289
|
-
patch.participants = [];
|
|
290
|
-
}
|
|
291
|
-
updatedFields.push('participants');
|
|
292
|
-
}
|
|
293
265
|
|
|
294
266
|
try {
|
|
295
267
|
if (updatedFields.length > 0) {
|
|
@@ -317,13 +289,12 @@ export const storagePlugin: Plugin = {
|
|
|
317
289
|
});
|
|
318
290
|
|
|
319
291
|
builder.on('action:patch_channel_details', async function* (event, context) {
|
|
320
|
-
const updatedFields: ('state' | 'spec' | 'cwd'
|
|
292
|
+
const updatedFields: ('state' | 'spec' | 'cwd')[] = [];
|
|
321
293
|
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
322
294
|
const data = (event.data || {}) as {
|
|
323
295
|
state?: Record<string, unknown>;
|
|
324
296
|
spec?: string;
|
|
325
297
|
cwd?: string;
|
|
326
|
-
participants?: string[];
|
|
327
298
|
};
|
|
328
299
|
try {
|
|
329
300
|
if (data.state !== undefined) {
|
|
@@ -347,19 +318,6 @@ export const storagePlugin: Plugin = {
|
|
|
347
318
|
});
|
|
348
319
|
updatedFields.push('cwd');
|
|
349
320
|
}
|
|
350
|
-
if (data.participants !== undefined) {
|
|
351
|
-
const normalized = Array.isArray(data.participants)
|
|
352
|
-
? data.participants
|
|
353
|
-
.filter((x): x is string => typeof x === 'string')
|
|
354
|
-
.map((s) => s.trim())
|
|
355
|
-
.filter(Boolean)
|
|
356
|
-
: [];
|
|
357
|
-
await storage.patchChannelState({
|
|
358
|
-
channelId: context.state.channelId,
|
|
359
|
-
state: { participants: normalized },
|
|
360
|
-
});
|
|
361
|
-
updatedFields.push('participants');
|
|
362
|
-
}
|
|
363
321
|
|
|
364
322
|
context.state.channelDetails = await storage.getChannelDetails({
|
|
365
323
|
channelId: context.state.channelId,
|