openbot 0.3.6 → 0.4.0
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 +0 -19
- package/dist/app/server.js +8 -14
- 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 +91 -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 +105 -149
- package/dist/plugins/delegation/index.js +119 -32
- package/dist/plugins/memory/index.js +103 -14
- package/dist/plugins/memory/service.js +152 -0
- package/dist/plugins/openbot/context.js +80 -0
- package/dist/plugins/openbot/history.js +98 -0
- package/dist/plugins/openbot/index.js +31 -0
- package/dist/plugins/openbot/runtime.js +317 -0
- package/dist/plugins/openbot/system-prompt.js +5 -0
- package/dist/plugins/plugin-manager/index.js +105 -0
- package/dist/plugins/storage/index.js +573 -0
- package/dist/plugins/storage/service.js +1159 -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 +2 -32
- package/dist/registry/plugins.js +3 -9
- package/dist/services/plugins/domain.js +1 -0
- package/dist/services/plugins/plugin-cache.js +9 -0
- package/dist/services/plugins/registry.js +110 -0
- package/dist/services/plugins/service.js +177 -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 +16 -10
- package/docs/architecture.md +2 -2
- package/docs/plugins.md +6 -15
- package/docs/templates/AGENT.example.md +7 -13
- 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 +1 -31
- package/src/app/server.ts +8 -16
- package/src/app/types.ts +63 -189
- package/src/harness/index.ts +145 -0
- package/src/plugins/approval/index.ts +91 -189
- package/src/plugins/delegation/index.ts +136 -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 +91 -0
- package/src/plugins/openbot/history.ts +107 -0
- package/src/plugins/openbot/index.ts +37 -0
- package/src/plugins/openbot/runtime.ts +384 -0
- package/src/plugins/openbot/system-prompt.ts +7 -0
- package/src/plugins/plugin-manager/index.ts +122 -0
- package/src/plugins/shell/index.ts +1 -1
- package/src/plugins/storage/index.ts +633 -0
- package/src/{services/storage.ts → plugins/storage/service.ts} +224 -67
- package/src/{bus/types.ts → services/plugins/domain.ts} +16 -7
- package/src/services/plugins/plugin-cache.ts +13 -0
- package/src/{registry/plugins.ts → services/plugins/registry.ts} +25 -27
- package/src/services/{plugins.ts → plugins/service.ts} +96 -2
- package/src/{bus/plugin.ts → services/plugins/types.ts} +3 -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/storage-tools/index.ts +0 -90
- package/src/plugins/todo/index.ts +0 -64
- package/src/plugins/ui/index.ts +0 -227
- /package/src/{harness → services}/process.ts +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
import { openai } from '@ai-sdk/openai';
|
|
3
|
+
import { anthropic } from '@ai-sdk/anthropic';
|
|
4
|
+
import { eventsToModelMessages } from './history.js';
|
|
5
|
+
import { ORCHESTRATOR_AGENT_ID, buildContext, } from './context.js';
|
|
6
|
+
import { saveConfig } from '../../app/config.js';
|
|
7
|
+
import { API_KEY_SETUP_MESSAGE, OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
|
|
8
|
+
function resolveModel(modelString) {
|
|
9
|
+
const [provider, ...rest] = modelString.split('/');
|
|
10
|
+
const modelId = rest.join('/');
|
|
11
|
+
if (!modelId) {
|
|
12
|
+
throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
|
|
13
|
+
}
|
|
14
|
+
switch (provider) {
|
|
15
|
+
case 'openai':
|
|
16
|
+
return openai(modelId);
|
|
17
|
+
case 'anthropic':
|
|
18
|
+
return anthropic(modelId);
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`Unsupported AI provider: "${provider}"`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function buildSystemPrompt(state, storage) {
|
|
24
|
+
const context = await buildContext(state, storage);
|
|
25
|
+
const instructions = state.agentId === ORCHESTRATOR_AGENT_ID
|
|
26
|
+
? (state.agentDetails?.instructions?.trim() || OPENBOT_SYSTEM_PROMPT)
|
|
27
|
+
: OPENBOT_SYSTEM_PROMPT;
|
|
28
|
+
const sections = [instructions, '', context];
|
|
29
|
+
// Hardcoded naming hint logic
|
|
30
|
+
const threadState = state.threadDetails?.state;
|
|
31
|
+
if (!threadState?.isSmartNamed) {
|
|
32
|
+
sections.push('', '## SYSTEM HINT', 'This thread is unnamed. Please use the `patch_thread_details` tool to set a concise, descriptive, and regular `name` (e.g., "Project Brainstorming" instead of "project-brainstorm") in the thread state and set `isSmartNamed: true` in the same patch. Only do this once.');
|
|
33
|
+
}
|
|
34
|
+
return sections.join('\n');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Tracks tool-call IDs from one LLM turn until matching `:result` events arrive.
|
|
38
|
+
*
|
|
39
|
+
* Melony runs yielded `action:*` events depth-first, so parallel tool calls from
|
|
40
|
+
* a single `generateText` response execute one-by-one. We must wait for every ID
|
|
41
|
+
* in the batch before calling the LLM again — not after the first result.
|
|
42
|
+
*/
|
|
43
|
+
function createToolBatchTracker() {
|
|
44
|
+
let pending = null;
|
|
45
|
+
return {
|
|
46
|
+
startBatch(toolCallIds) {
|
|
47
|
+
pending = new Set(toolCallIds);
|
|
48
|
+
},
|
|
49
|
+
clear() {
|
|
50
|
+
pending = null;
|
|
51
|
+
},
|
|
52
|
+
/** Returns true when this result completes the batch (time to call the LLM again). */
|
|
53
|
+
recordResult(toolCallId) {
|
|
54
|
+
if (!pending?.has(toolCallId))
|
|
55
|
+
return false;
|
|
56
|
+
pending.delete(toolCallId);
|
|
57
|
+
if (pending.size > 0)
|
|
58
|
+
return false;
|
|
59
|
+
pending = null;
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* OpenBot agent runtime.
|
|
66
|
+
*
|
|
67
|
+
* - One `generateText` call per `runLLM` (tools have no `execute`; SDK stops at 1 step).
|
|
68
|
+
* - Tool calls become `action:*` events; plugins emit `:result` when done.
|
|
69
|
+
* - When a full batch of results is in, `runLLM` runs again with updated history.
|
|
70
|
+
*/
|
|
71
|
+
export const openbotRuntime = (options) => (builder) => {
|
|
72
|
+
const { model: modelString = 'openai/gpt-4o-mini', storage, toolDefinitions = {}, } = options;
|
|
73
|
+
let currentModelString = modelString;
|
|
74
|
+
let model = resolveModel(currentModelString);
|
|
75
|
+
const toolBatch = createToolBatchTracker();
|
|
76
|
+
const runLLM = async function* (context, threadId, trigger) {
|
|
77
|
+
if (!storage)
|
|
78
|
+
return;
|
|
79
|
+
// Capture parent metadata for event enrichment
|
|
80
|
+
const triggerEvent = trigger || context.state.triggerEvent;
|
|
81
|
+
const parentAgentId = triggerEvent?.meta?.parentAgentId;
|
|
82
|
+
const parentToolCallId = triggerEvent?.meta?.parentToolCallId;
|
|
83
|
+
context.state.model = currentModelString;
|
|
84
|
+
const systemPrompt = await buildSystemPrompt(context.state, storage);
|
|
85
|
+
const events = await storage.getEvents({
|
|
86
|
+
channelId: context.state.channelId,
|
|
87
|
+
threadId: context.state.threadId,
|
|
88
|
+
});
|
|
89
|
+
const messages = eventsToModelMessages(events);
|
|
90
|
+
// console.log('systemPrompt:::::::\n', systemPrompt);
|
|
91
|
+
// console.log('messages:::::::\n', JSON.stringify(messages, null, 2));
|
|
92
|
+
// console.log('toolDefinitions:::::::\n', JSON.stringify(toolDefinitions, null, 2));
|
|
93
|
+
try {
|
|
94
|
+
// Single LLM request — tool execution happens externally via action:* handlers.
|
|
95
|
+
const result = await generateText({
|
|
96
|
+
model,
|
|
97
|
+
system: systemPrompt,
|
|
98
|
+
messages,
|
|
99
|
+
tools: toolDefinitions,
|
|
100
|
+
stopWhen: ({ steps }) => steps.length === 1,
|
|
101
|
+
allowSystemInMessages: true,
|
|
102
|
+
});
|
|
103
|
+
const toolCalls = result.toolCalls ?? [];
|
|
104
|
+
// if (result.usage) {
|
|
105
|
+
// const usage = result.usage;
|
|
106
|
+
// yield {
|
|
107
|
+
// type: 'agent:usage',
|
|
108
|
+
// data: {
|
|
109
|
+
// usage: {
|
|
110
|
+
// promptTokens: usage.inputTokens,
|
|
111
|
+
// completionTokens: usage.outputTokens,
|
|
112
|
+
// totalTokens: usage.totalTokens,
|
|
113
|
+
// currentContextTokens: usage.inputTokens,
|
|
114
|
+
// contextBudget: getContextBudgetForModel(currentModelString),
|
|
115
|
+
// },
|
|
116
|
+
// model: currentModelString,
|
|
117
|
+
// },
|
|
118
|
+
// meta: {
|
|
119
|
+
// agentId: context.state.agentId,
|
|
120
|
+
// threadId,
|
|
121
|
+
// runId: context.state.runId,
|
|
122
|
+
// },
|
|
123
|
+
// } as OpenBotEvent;
|
|
124
|
+
// }
|
|
125
|
+
const outputMeta = {
|
|
126
|
+
agentId: context.state.agentId,
|
|
127
|
+
threadId,
|
|
128
|
+
parentAgentId,
|
|
129
|
+
parentToolCallId,
|
|
130
|
+
};
|
|
131
|
+
// Text before actions so history/UI show the model's intent first.
|
|
132
|
+
if (result.text) {
|
|
133
|
+
yield {
|
|
134
|
+
type: 'agent:output',
|
|
135
|
+
data: { content: result.text },
|
|
136
|
+
meta: outputMeta,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (toolCalls.length > 0) {
|
|
140
|
+
// when multiple tool calls are made, Melony runtime handles them one by one, thats why we need to start a new batch
|
|
141
|
+
toolBatch.startBatch(toolCalls.map((tc) => tc.toolCallId));
|
|
142
|
+
for (const toolCall of toolCalls) {
|
|
143
|
+
yield {
|
|
144
|
+
type: `action:${toolCall.toolName}`,
|
|
145
|
+
data: toolCall.input,
|
|
146
|
+
meta: {
|
|
147
|
+
toolCallId: toolCall.toolCallId,
|
|
148
|
+
...outputMeta,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// clear the tool batch if there are no tool calls
|
|
155
|
+
toolBatch.clear();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
160
|
+
const isApiKeyError = errorMessage.includes('API key') ||
|
|
161
|
+
errorMessage.includes('401') ||
|
|
162
|
+
errorMessage.includes('Unauthorized') ||
|
|
163
|
+
errorMessage.includes('authentication');
|
|
164
|
+
if (isApiKeyError) {
|
|
165
|
+
const [currentProvider, ...rest] = currentModelString.split('/');
|
|
166
|
+
const currentModelId = rest.join('/');
|
|
167
|
+
yield {
|
|
168
|
+
type: 'client:ui:widget',
|
|
169
|
+
data: {
|
|
170
|
+
kind: 'form',
|
|
171
|
+
widgetId: `api_key_request_${Date.now()}`,
|
|
172
|
+
title: `AI Provider API Key Required`,
|
|
173
|
+
description: API_KEY_SETUP_MESSAGE,
|
|
174
|
+
fields: [
|
|
175
|
+
{
|
|
176
|
+
id: 'provider',
|
|
177
|
+
label: 'Provider',
|
|
178
|
+
type: 'select',
|
|
179
|
+
required: true,
|
|
180
|
+
options: [
|
|
181
|
+
{ label: 'OpenAI', value: 'openai' },
|
|
182
|
+
{ label: 'Anthropic', value: 'anthropic' },
|
|
183
|
+
],
|
|
184
|
+
defaultValue: currentProvider === 'anthropic' ? 'anthropic' : 'openai',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'model',
|
|
188
|
+
label: 'Model',
|
|
189
|
+
type: 'text',
|
|
190
|
+
description: 'Model name without the provider prefix (e.g. `gpt-4o-mini` or `claude-3-5-sonnet-20240620`).',
|
|
191
|
+
placeholder: 'gpt-4o-mini',
|
|
192
|
+
required: true,
|
|
193
|
+
defaultValue: currentModelId,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'apiKey',
|
|
197
|
+
label: 'API Key',
|
|
198
|
+
type: 'text',
|
|
199
|
+
placeholder: `sk-...`,
|
|
200
|
+
required: true,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
submitLabel: 'Save & Continue',
|
|
204
|
+
metadata: {
|
|
205
|
+
type: 'api_key_request',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
meta: { agentId: context.state.agentId, threadId },
|
|
209
|
+
};
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
builder.on('agent:invoke', async function* (event, context) {
|
|
216
|
+
const routedTo = event.data?.agentId;
|
|
217
|
+
if (typeof routedTo === 'string' && routedTo && routedTo !== context.state.agentId) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// clear the tool batch if the agent is invoked
|
|
221
|
+
// this is to prevent the tool batch from being used for a new agent invocation
|
|
222
|
+
toolBatch.clear();
|
|
223
|
+
const threadId = event.meta?.threadId || context.state.threadId;
|
|
224
|
+
yield* runLLM(context, threadId, event);
|
|
225
|
+
});
|
|
226
|
+
// this is to handle the tool results from the tool calls
|
|
227
|
+
// because Melony runtime handles them one by one, thats why we need to record the result
|
|
228
|
+
builder.on('*', async function* (event, context) {
|
|
229
|
+
if (!event.type.endsWith(':result'))
|
|
230
|
+
return;
|
|
231
|
+
if (event.meta?.agentId !== context.state.agentId)
|
|
232
|
+
return;
|
|
233
|
+
const toolCallId = event.meta?.toolCallId;
|
|
234
|
+
// record the result of the tool call
|
|
235
|
+
if (!toolCallId || !toolBatch.recordResult(toolCallId))
|
|
236
|
+
return;
|
|
237
|
+
const threadId = event.meta?.threadId || context.state.threadId;
|
|
238
|
+
yield* runLLM(context, threadId);
|
|
239
|
+
});
|
|
240
|
+
builder.on('client:ui:widget:response', async function* (event, context) {
|
|
241
|
+
const { metadata, values } = event.data;
|
|
242
|
+
if (metadata?.type !== 'api_key_request')
|
|
243
|
+
return;
|
|
244
|
+
if (!values?.apiKey || !values?.provider || !values?.model)
|
|
245
|
+
return;
|
|
246
|
+
const provider = String(values.provider);
|
|
247
|
+
const modelId = String(values.model).trim();
|
|
248
|
+
const apiKey = String(values.apiKey);
|
|
249
|
+
if (provider !== 'openai' && provider !== 'anthropic') {
|
|
250
|
+
yield {
|
|
251
|
+
type: 'agent:output',
|
|
252
|
+
data: { content: `Unsupported provider: ${provider}` },
|
|
253
|
+
meta: { agentId: context.state.agentId },
|
|
254
|
+
};
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const envVar = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
|
|
258
|
+
const newModelString = `${provider}/${modelId}`;
|
|
259
|
+
if (!storage)
|
|
260
|
+
return;
|
|
261
|
+
try {
|
|
262
|
+
await storage.createVariable({ key: envVar, value: apiKey, secret: true });
|
|
263
|
+
process.env[envVar] = apiKey;
|
|
264
|
+
currentModelString = newModelString;
|
|
265
|
+
model = resolveModel(currentModelString);
|
|
266
|
+
try {
|
|
267
|
+
saveConfig({ model: currentModelString });
|
|
268
|
+
// Also update the agent's AGENT.md if it has an openbot plugin config
|
|
269
|
+
const details = await storage.getAgentDetails({ agentId: context.state.agentId });
|
|
270
|
+
const updatedPlugins = details.pluginRefs.map((ref) => {
|
|
271
|
+
if (ref.id === 'openbot') {
|
|
272
|
+
return {
|
|
273
|
+
...ref,
|
|
274
|
+
config: { ...ref.config, model: currentModelString },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return ref;
|
|
278
|
+
});
|
|
279
|
+
await storage.updateAgent({
|
|
280
|
+
agentId: context.state.agentId,
|
|
281
|
+
plugins: updatedPlugins,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// best-effort: config persistence failure shouldn't block the conversation
|
|
286
|
+
}
|
|
287
|
+
yield {
|
|
288
|
+
type: 'agent:output',
|
|
289
|
+
data: {
|
|
290
|
+
content: `Saved ${provider} API key and set model to \`${newModelString}\`.`,
|
|
291
|
+
},
|
|
292
|
+
meta: { agentId: context.state.agentId },
|
|
293
|
+
};
|
|
294
|
+
yield {
|
|
295
|
+
type: 'client:ui:widget',
|
|
296
|
+
data: {
|
|
297
|
+
widgetId: event.data.widgetId,
|
|
298
|
+
kind: 'message',
|
|
299
|
+
title: 'API Key Saved',
|
|
300
|
+
body: `Successfully saved ${provider} API key and selected model \`${newModelString}\`. You can now continue your conversation.`,
|
|
301
|
+
state: 'submitted',
|
|
302
|
+
actions: [{ id: 'ok', label: 'Got it', variant: 'primary' }],
|
|
303
|
+
},
|
|
304
|
+
meta: { agentId: context.state.agentId },
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
yield {
|
|
309
|
+
type: 'agent:output',
|
|
310
|
+
data: {
|
|
311
|
+
content: `Failed to save API key: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
312
|
+
},
|
|
313
|
+
meta: { agentId: context.state.agentId },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const OPENBOT_SYSTEM_PROMPT = [
|
|
2
|
+
'You are a helpful AI assistant for your human. Your job is to help the user with their questions and tasks.',
|
|
3
|
+
].join('\n');
|
|
4
|
+
/** Shown in the API key setup form when no provider credentials are configured. */
|
|
5
|
+
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.';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { STATE_AGENT_ID } from '../../app/agent-ids.js';
|
|
2
|
+
import { pluginService, resolveMarketplaceAgentList, } from '../../services/plugins/service.js';
|
|
3
|
+
/**
|
|
4
|
+
* `plugin-manager` — marketplace listing, npm plugin install/uninstall, and
|
|
5
|
+
* installing agents from the registry. Wired on the **`state`** built-in agent
|
|
6
|
+
* via its default `pluginRefs`.
|
|
7
|
+
*
|
|
8
|
+
* Handlers register only when `agentId === state` so attaching this plugin to
|
|
9
|
+
* other agents via AGENT.md does not widen infra privileges.
|
|
10
|
+
*/
|
|
11
|
+
export const pluginManagerPlugin = {
|
|
12
|
+
id: 'plugin-manager',
|
|
13
|
+
name: 'Plugin manager',
|
|
14
|
+
description: 'Marketplace listings, npm-based plugin lifecycle, and agent installs from marketplace metadata.',
|
|
15
|
+
factory: ({ agentId, storage }) => {
|
|
16
|
+
if (agentId !== STATE_AGENT_ID) {
|
|
17
|
+
return () => { };
|
|
18
|
+
}
|
|
19
|
+
return (builder) => {
|
|
20
|
+
builder.on('action:plugin:install', async function* (event) {
|
|
21
|
+
try {
|
|
22
|
+
const { name, version } = event.data;
|
|
23
|
+
const result = await pluginService.install({ packageName: name, version });
|
|
24
|
+
yield {
|
|
25
|
+
type: 'action:plugin:install:result',
|
|
26
|
+
data: { success: true, plugin: result },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
yield {
|
|
31
|
+
type: 'action:plugin:install:result',
|
|
32
|
+
data: { success: false, error: error.message },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
builder.on('action:plugin:uninstall', async function* (event) {
|
|
37
|
+
try {
|
|
38
|
+
await pluginService.uninstall(event.data.id);
|
|
39
|
+
yield { type: 'action:plugin:uninstall:result', data: { success: true } };
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
yield {
|
|
43
|
+
type: 'action:plugin:uninstall:result',
|
|
44
|
+
data: { success: false, error: error.message },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
builder.on('action:marketplace:list', async function* () {
|
|
49
|
+
const agents = await resolveMarketplaceAgentList();
|
|
50
|
+
yield {
|
|
51
|
+
type: 'action:marketplace:list:result',
|
|
52
|
+
data: { success: true, agents },
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
builder.on('action:agent:install', async function* (event) {
|
|
56
|
+
try {
|
|
57
|
+
const { agentId: newAgentId, name, description, image, instructions, plugins, } = event.data;
|
|
58
|
+
for (const ref of plugins) {
|
|
59
|
+
const installed = await pluginService.isInstalled(ref.id);
|
|
60
|
+
if (!installed && ref.id.includes('/') === false && ref.id.includes('-plugin-') === false) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!installed) {
|
|
64
|
+
try {
|
|
65
|
+
await pluginService.install({ packageName: ref.id });
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.warn(`[plugins] Failed to pre-install plugin ${ref.id}`, err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await storage.createAgent({
|
|
73
|
+
agentId: newAgentId,
|
|
74
|
+
name,
|
|
75
|
+
description,
|
|
76
|
+
image,
|
|
77
|
+
instructions,
|
|
78
|
+
plugins,
|
|
79
|
+
});
|
|
80
|
+
yield {
|
|
81
|
+
type: 'action:agent:install:result',
|
|
82
|
+
data: { success: true, agentId: newAgentId },
|
|
83
|
+
};
|
|
84
|
+
yield {
|
|
85
|
+
type: 'agent:output',
|
|
86
|
+
data: {
|
|
87
|
+
content: `Successfully installed agent **${name}** (${newAgentId}) from marketplace.`,
|
|
88
|
+
},
|
|
89
|
+
meta: { agentId: 'system' },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
yield {
|
|
94
|
+
type: 'action:agent:install:result',
|
|
95
|
+
data: {
|
|
96
|
+
success: false,
|
|
97
|
+
agentId: event.data.agentId,
|
|
98
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
};
|