openbot 0.4.7 → 0.5.4
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/agents/runtime-security-reviewer.md +32 -0
- package/.claude/hooks/format.sh +18 -0
- package/.claude/hooks/protect-secrets.sh +23 -0
- package/.claude/settings.json +26 -0
- package/.claude/skills/deploy/SKILL.md +26 -0
- package/.claude/skills/new-plugin/SKILL.md +46 -0
- package/.dockerignore +9 -0
- package/.mcp.json +8 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +50 -0
- package/Dockerfile +41 -0
- package/deploy/README.md +61 -0
- package/deploy/entrypoint.sh +6 -0
- package/deploy/fly.toml +36 -0
- package/dist/app/bootstrap.js +29 -0
- package/dist/app/cli.js +3 -1
- package/dist/app/cloud-mode.js +26 -0
- package/dist/app/config.js +61 -16
- package/dist/app/server.js +38 -6
- package/dist/plugins/approval/index.js +1 -1
- package/dist/plugins/bash/index.js +386 -164
- package/dist/plugins/memory/service.js +2 -5
- package/dist/plugins/openbot/context.js +17 -1
- package/dist/plugins/openbot/index.js +23 -8
- package/dist/plugins/openbot/model.js +59 -0
- package/dist/plugins/openbot/runtime.js +39 -56
- package/dist/plugins/openbot/system-prompt.js +43 -8
- package/dist/plugins/preview/index.js +267 -0
- package/dist/plugins/storage/index.js +3 -3
- package/dist/plugins/storage/service.js +58 -12
- package/dist/plugins/todo/index.js +131 -38
- package/dist/plugins/todo/service.js +93 -0
- package/dist/plugins/ui/index.js +8 -1
- package/dist/services/plugins/model-registry.js +101 -0
- package/dist/services/plugins/registry.js +6 -4
- package/dist/services/plugins/service.js +2 -6
- package/docs/agents.md +20 -2
- package/docs/architecture.md +1 -1
- package/docs/plugins.md +13 -3
- package/docs/templates/AGENT.example.md +1 -1
- package/package.json +5 -2
- package/pnpm-workspace.yaml +2 -0
- package/src/app/bootstrap.ts +43 -0
- package/src/app/cli.ts +3 -1
- package/src/app/cloud-mode.ts +41 -0
- package/src/app/config.ts +71 -19
- package/src/app/server.ts +45 -6
- package/src/app/types.ts +162 -31
- package/src/plugins/approval/index.ts +1 -1
- package/src/plugins/bash/index.ts +498 -185
- package/src/plugins/memory/service.ts +2 -5
- package/src/plugins/openbot/context.ts +17 -1
- package/src/plugins/openbot/index.ts +25 -10
- package/src/plugins/openbot/model.ts +76 -0
- package/src/plugins/openbot/runtime.ts +47 -69
- package/src/plugins/openbot/system-prompt.ts +43 -8
- package/src/plugins/preview/index.ts +323 -0
- package/src/plugins/storage/index.ts +5 -8
- package/src/plugins/storage/service.ts +79 -15
- package/src/plugins/todo/index.ts +166 -0
- package/src/plugins/todo/service.ts +123 -0
- package/src/plugins/ui/index.ts +8 -1
- package/src/services/plugins/domain.ts +2 -0
- package/src/services/plugins/model-registry.ts +146 -0
- package/src/services/plugins/registry.ts +6 -4
- package/src/services/plugins/service.ts +2 -6
- package/dist/agents/openbot/index.js +0 -76
- package/dist/agents/openbot/middleware/approval.js +0 -132
- package/dist/agents/openbot/runtime.js +0 -289
- package/dist/agents/openbot/system-prompt.js +0 -32
- package/dist/agents/openbot/tools/delegation.js +0 -78
- package/dist/agents/openbot/tools/mcp.js +0 -99
- package/dist/agents/openbot/tools/shell.js +0 -91
- package/dist/agents/openbot/tools/storage.js +0 -75
- package/dist/agents/openbot/tools/ui.js +0 -176
- package/dist/agents/system.js +0 -33
- package/dist/bus/agent-package.js +0 -1
- package/dist/bus/plugin.js +0 -1
- package/dist/bus/services.js +0 -754
- package/dist/bus/types.js +0 -1
- package/dist/harness/agent-harness.js +0 -45
- package/dist/harness/agent-invoke-run.js +0 -44
- package/dist/harness/agent-turn.js +0 -99
- package/dist/harness/channel-participants.js +0 -40
- package/dist/harness/constants.js +0 -2
- package/dist/harness/context-meter.js +0 -97
- package/dist/harness/context.js +0 -363
- package/dist/harness/dispatch.js +0 -144
- package/dist/harness/dispatcher.js +0 -156
- package/dist/harness/event-normalizer.js +0 -59
- package/dist/harness/history.js +0 -177
- package/dist/harness/mcp.js +0 -61
- package/dist/harness/orchestration.js +0 -88
- package/dist/harness/orchestrator.js +0 -149
- package/dist/harness/participants.js +0 -22
- package/dist/harness/process.js +0 -29
- package/dist/harness/queue-processor.js +0 -187
- package/dist/harness/run-harness.js +0 -154
- package/dist/harness/run.js +0 -98
- package/dist/harness/runtime-factory.js +0 -73
- package/dist/harness/runtime.js +0 -57
- package/dist/harness/todo-advance.js +0 -93
- package/dist/harness/todo-dispatch.js +0 -51
- package/dist/harness/todos.js +0 -5
- package/dist/harness/turn.js +0 -79
- package/dist/harness/types.js +0 -1
- package/dist/plugins/ai-sdk/index.js +0 -34
- package/dist/plugins/ai-sdk/runtime.js +0 -395
- package/dist/plugins/ai-sdk/system-prompt.js +0 -18
- package/dist/plugins/ai-sdk.js +0 -331
- package/dist/plugins/approval.js +0 -163
- package/dist/plugins/delegation.js +0 -108
- package/dist/plugins/mcp/index.js +0 -108
- package/dist/plugins/mcp.js +0 -140
- package/dist/plugins/shell/index.js +0 -100
- package/dist/plugins/shell.js +0 -123
- package/dist/plugins/storage-tools/index.js +0 -76
- package/dist/plugins/storage.js +0 -737
- package/dist/plugins/thread-namer/index.js +0 -72
- package/dist/plugins/threads/index.js +0 -114
- package/dist/plugins/ui.js +0 -211
- package/dist/plugins/workflow/index.js +0 -65
- package/dist/registry/agents.js +0 -138
- package/dist/registry/plugins.js +0 -110
- package/dist/services/agent-packages.js +0 -103
- package/dist/services/memory.js +0 -152
- package/dist/services/plugins.js +0 -98
- package/dist/services/storage.js +0 -1028
- package/dist/workflow/service.js +0 -106
- package/dist/workflow/types.js +0 -3
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { generateText } from 'ai';
|
|
2
|
-
import { openai } from '@ai-sdk/openai';
|
|
3
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
4
|
-
import { createDefaultContextEngine } from '../../harness/context.js';
|
|
5
|
-
function resolveModel(modelString) {
|
|
6
|
-
const [provider, ...rest] = modelString.split('/');
|
|
7
|
-
const modelId = rest.join('/');
|
|
8
|
-
if (!modelId) {
|
|
9
|
-
throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
|
|
10
|
-
}
|
|
11
|
-
switch (provider) {
|
|
12
|
-
case 'openai':
|
|
13
|
-
return openai(modelId);
|
|
14
|
-
case 'anthropic':
|
|
15
|
-
return anthropic(modelId);
|
|
16
|
-
default:
|
|
17
|
-
throw new Error(`Unsupported AI provider: "${provider}"`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const asRecord = (value) => value && typeof value === 'object' && !Array.isArray(value)
|
|
21
|
-
? value
|
|
22
|
-
: {};
|
|
23
|
-
const readPersistedShortTermMessages = (state) => {
|
|
24
|
-
const source = state.threadDetails?.state ?? state.channelDetails?.state;
|
|
25
|
-
const record = asRecord(source);
|
|
26
|
-
const raw = record.shortTermMessages;
|
|
27
|
-
return Array.isArray(raw) ? raw : [];
|
|
28
|
-
};
|
|
29
|
-
const persistShortTermMessages = async (state, storage) => {
|
|
30
|
-
if (!storage)
|
|
31
|
-
return;
|
|
32
|
-
const shortTermMessages = state.shortTermMessages ?? [];
|
|
33
|
-
if (state.threadId) {
|
|
34
|
-
await storage.patchThreadState({
|
|
35
|
-
channelId: state.channelId,
|
|
36
|
-
threadId: state.threadId,
|
|
37
|
-
state: { shortTermMessages },
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
await storage.patchChannelState({
|
|
42
|
-
channelId: state.channelId,
|
|
43
|
-
state: { shortTermMessages },
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
async function buildSystemPrompt(state, system, context, storage, contextEngine) {
|
|
47
|
-
const sections = [];
|
|
48
|
-
if (system && typeof system === 'string')
|
|
49
|
-
sections.push(system);
|
|
50
|
-
if (system && typeof system === 'function' && context)
|
|
51
|
-
sections.push(await system(context));
|
|
52
|
-
if (contextEngine)
|
|
53
|
-
sections.push(await contextEngine.buildContext(state, storage));
|
|
54
|
-
return sections.join('\n\n');
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* The ai-sdk based runtime that drives OpenBot's first-party agent.
|
|
58
|
-
*
|
|
59
|
-
* It owns `agent:invoke`, runs the LLM, emits tool-call events and stitches
|
|
60
|
-
* tool results back into the conversation. The runtime is intentionally
|
|
61
|
-
* agent-package-internal: it is not a generic OpenBot abstraction.
|
|
62
|
-
*/
|
|
63
|
-
export const openBotRuntime = (options) => (builder) => {
|
|
64
|
-
const { model: modelString = 'openai/gpt-4o-mini', system, storage, contextEngine = createDefaultContextEngine(), toolDefinitions = {}, } = options;
|
|
65
|
-
const model = resolveModel(modelString);
|
|
66
|
-
const ensureShortTermMessages = (state) => {
|
|
67
|
-
if (!state.shortTermMessages || state.shortTermMessages.length === 0) {
|
|
68
|
-
state.shortTermMessages = readPersistedShortTermMessages(state);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const mapToCoreMessages = (messages) => {
|
|
72
|
-
return messages.map((m) => {
|
|
73
|
-
if (m.role === 'assistant' && m.toolCalls) {
|
|
74
|
-
return {
|
|
75
|
-
role: 'assistant',
|
|
76
|
-
content: [
|
|
77
|
-
{ type: 'text', text: m.content || '' },
|
|
78
|
-
...m.toolCalls.map((tc) => ({
|
|
79
|
-
type: 'tool-call',
|
|
80
|
-
toolCallId: tc.id,
|
|
81
|
-
toolName: tc.function.name,
|
|
82
|
-
input: JSON.parse(tc.function.arguments),
|
|
83
|
-
})),
|
|
84
|
-
],
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
if (m.role === 'assistant') {
|
|
88
|
-
return { role: 'assistant', content: m.content || '' };
|
|
89
|
-
}
|
|
90
|
-
if (m.role === 'tool') {
|
|
91
|
-
return {
|
|
92
|
-
role: 'tool',
|
|
93
|
-
content: [
|
|
94
|
-
{
|
|
95
|
-
type: 'tool-result',
|
|
96
|
-
toolCallId: m.toolCallId,
|
|
97
|
-
toolName: m.toolName,
|
|
98
|
-
output: { type: 'text', value: JSON.stringify(m.content) },
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return m;
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
const runLLM = async function* (context, threadId) {
|
|
107
|
-
ensureShortTermMessages(context.state);
|
|
108
|
-
const systemPrompt = await buildSystemPrompt(context.state, system, context, storage, contextEngine);
|
|
109
|
-
const coreMessages = mapToCoreMessages(context.state.shortTermMessages || []);
|
|
110
|
-
try {
|
|
111
|
-
const result = await generateText({
|
|
112
|
-
model,
|
|
113
|
-
system: systemPrompt,
|
|
114
|
-
messages: coreMessages,
|
|
115
|
-
tools: toolDefinitions,
|
|
116
|
-
});
|
|
117
|
-
const toolCalls = result.toolCalls ?? [];
|
|
118
|
-
if (toolCalls.length > 0) {
|
|
119
|
-
context.state.shortTermMessages = [
|
|
120
|
-
...(context.state.shortTermMessages ?? []),
|
|
121
|
-
{
|
|
122
|
-
role: 'assistant',
|
|
123
|
-
content: result.text || '',
|
|
124
|
-
toolCalls: toolCalls.map((tc) => ({
|
|
125
|
-
id: tc.toolCallId,
|
|
126
|
-
type: 'function',
|
|
127
|
-
function: {
|
|
128
|
-
name: tc.toolName,
|
|
129
|
-
arguments: JSON.stringify(tc.input),
|
|
130
|
-
},
|
|
131
|
-
})),
|
|
132
|
-
},
|
|
133
|
-
];
|
|
134
|
-
await persistShortTermMessages(context.state, storage);
|
|
135
|
-
for (const toolCall of toolCalls) {
|
|
136
|
-
yield {
|
|
137
|
-
type: `action:${toolCall.toolName}`,
|
|
138
|
-
data: toolCall.input,
|
|
139
|
-
meta: {
|
|
140
|
-
toolCallId: toolCall.toolCallId,
|
|
141
|
-
agentId: context.state.agentId,
|
|
142
|
-
threadId,
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (result.text) {
|
|
148
|
-
if (toolCalls.length === 0) {
|
|
149
|
-
context.state.shortTermMessages = [
|
|
150
|
-
...(context.state.shortTermMessages ?? []),
|
|
151
|
-
{ role: 'assistant', content: result.text },
|
|
152
|
-
];
|
|
153
|
-
await persistShortTermMessages(context.state, storage);
|
|
154
|
-
}
|
|
155
|
-
yield {
|
|
156
|
-
type: 'agent:output',
|
|
157
|
-
data: { content: result.text },
|
|
158
|
-
meta: { agentId: context.state.agentId, threadId },
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
const errorMessage = error?.message || String(error);
|
|
164
|
-
const isApiKeyError = errorMessage.includes('API key') ||
|
|
165
|
-
errorMessage.includes('401') ||
|
|
166
|
-
errorMessage.includes('Unauthorized') ||
|
|
167
|
-
errorMessage.includes('authentication');
|
|
168
|
-
if (isApiKeyError) {
|
|
169
|
-
const provider = modelString.split('/')[0];
|
|
170
|
-
const envVar = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
|
|
171
|
-
yield {
|
|
172
|
-
type: 'client:ui:widget',
|
|
173
|
-
data: {
|
|
174
|
-
kind: 'form',
|
|
175
|
-
widgetId: `api_key_request_${Date.now()}`,
|
|
176
|
-
title: `${provider.toUpperCase()} API Key Required`,
|
|
177
|
-
description: `The ${provider} API returned an authentication error. Please provide a valid API key to continue. The key never leaves your local runtime.`,
|
|
178
|
-
fields: [
|
|
179
|
-
{
|
|
180
|
-
id: 'apiKey',
|
|
181
|
-
label: 'API Key',
|
|
182
|
-
type: 'text',
|
|
183
|
-
placeholder: `sk-...`,
|
|
184
|
-
required: true,
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
submitLabel: 'Save API Key',
|
|
188
|
-
metadata: {
|
|
189
|
-
type: 'api_key_request',
|
|
190
|
-
provider,
|
|
191
|
-
envVar,
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
meta: { agentId: context.state.agentId, threadId },
|
|
195
|
-
};
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
throw error;
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
builder.on('agent:invoke', async function* (event, context) {
|
|
202
|
-
const routedTo = event.data?.agentId;
|
|
203
|
-
if (typeof routedTo === 'string' && routedTo && routedTo !== context.state.agentId) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const threadId = event.meta?.threadId || context.state.threadId;
|
|
207
|
-
ensureShortTermMessages(context.state);
|
|
208
|
-
context.state.shortTermMessages = [
|
|
209
|
-
...(context.state.shortTermMessages ?? []),
|
|
210
|
-
{
|
|
211
|
-
role: event.data?.role || 'user',
|
|
212
|
-
content: event?.data?.content || '',
|
|
213
|
-
},
|
|
214
|
-
];
|
|
215
|
-
await persistShortTermMessages(context.state, storage);
|
|
216
|
-
yield* runLLM(context, threadId);
|
|
217
|
-
});
|
|
218
|
-
builder.on('*', async function* (event, context) {
|
|
219
|
-
if (!event.type.endsWith(':result'))
|
|
220
|
-
return;
|
|
221
|
-
if (event.meta?.agentId !== context.state.agentId)
|
|
222
|
-
return;
|
|
223
|
-
const toolCallId = event.meta?.toolCallId;
|
|
224
|
-
if (!toolCallId)
|
|
225
|
-
return;
|
|
226
|
-
ensureShortTermMessages(context.state);
|
|
227
|
-
const toolName = event.type.replace(/^action:/, '').replace(/:result$/, '');
|
|
228
|
-
const resultData = event.data;
|
|
229
|
-
const content = typeof resultData === 'string' ? resultData : JSON.stringify(resultData);
|
|
230
|
-
context.state.shortTermMessages = [
|
|
231
|
-
...(context.state.shortTermMessages ?? []),
|
|
232
|
-
{ role: 'tool', content, toolCallId, toolName },
|
|
233
|
-
];
|
|
234
|
-
await persistShortTermMessages(context.state, storage);
|
|
235
|
-
const lastAssistant = [...(context.state.shortTermMessages ?? [])]
|
|
236
|
-
.reverse()
|
|
237
|
-
.find((m) => m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0);
|
|
238
|
-
if (lastAssistant && lastAssistant.toolCalls) {
|
|
239
|
-
const allFulfilled = lastAssistant.toolCalls.every((tc) => context.state.shortTermMessages?.some((m) => m.role === 'tool' && m.toolCallId === tc.id));
|
|
240
|
-
if (allFulfilled) {
|
|
241
|
-
// handoff terminates the current agent path; the orchestrator continues with the target agent.
|
|
242
|
-
if (toolName === 'handoff')
|
|
243
|
-
return;
|
|
244
|
-
const threadId = event.meta?.threadId || context.state.threadId;
|
|
245
|
-
yield* runLLM(context, threadId);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
builder.on('client:ui:widget:response', async function* (event, context) {
|
|
250
|
-
const { metadata, values } = event.data;
|
|
251
|
-
if (metadata?.type === 'api_key_request' && values?.apiKey) {
|
|
252
|
-
const key = metadata.envVar;
|
|
253
|
-
const value = values.apiKey;
|
|
254
|
-
if (storage) {
|
|
255
|
-
try {
|
|
256
|
-
await storage.createVariable({ key, value, secret: true });
|
|
257
|
-
yield {
|
|
258
|
-
type: 'agent:output',
|
|
259
|
-
data: {
|
|
260
|
-
content: `Successfully saved ${metadata.provider} API key to workspace variables.`,
|
|
261
|
-
},
|
|
262
|
-
meta: { agentId: context.state.agentId },
|
|
263
|
-
};
|
|
264
|
-
yield {
|
|
265
|
-
type: 'client:ui:widget',
|
|
266
|
-
data: {
|
|
267
|
-
widgetId: event.data.widgetId,
|
|
268
|
-
kind: 'message',
|
|
269
|
-
title: 'API Key Saved',
|
|
270
|
-
body: `Successfully saved ${metadata.provider} API key. You can now continue your conversation.`,
|
|
271
|
-
state: 'submitted',
|
|
272
|
-
actions: [{ id: 'ok', label: 'Got it', variant: 'primary' }],
|
|
273
|
-
},
|
|
274
|
-
meta: { agentId: context.state.agentId },
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
yield {
|
|
279
|
-
type: 'agent:output',
|
|
280
|
-
data: {
|
|
281
|
-
content: `Failed to save API key: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
282
|
-
},
|
|
283
|
-
meta: { agentId: context.state.agentId },
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export const OPENBOT_SYSTEM_PROMPT = 'You are OpenBot, the primary AI assistant and orchestrator of this workspace. ' +
|
|
2
|
-
'Your goal is to help users onboard, answer questions about the system, and ' +
|
|
3
|
-
'suggest specialized agents for specific tasks.\n\n' +
|
|
4
|
-
'### How to use OpenBot:\n' +
|
|
5
|
-
'1. **General Chat**: Just type your message here, and I will help you.\n' +
|
|
6
|
-
'2. **Specialized Agents**: Use `handoff` when you want another agent to take over. ' +
|
|
7
|
-
'Use `delegate` when you want another agent to return results so you can continue.\n' +
|
|
8
|
-
'3. **Channels**: Channels are shared spaces where multiple agents can participate. ' +
|
|
9
|
-
'You can create new channels for different topics.\n' +
|
|
10
|
-
'4. **Local-First**: OpenBot runs entirely on your machine. Your data stays private and local.\n\n' +
|
|
11
|
-
'### Workflow Guidelines:\n' +
|
|
12
|
-
'- **Todo Schema**: Keep todo items simple. Each item should have a short `id`, ' +
|
|
13
|
-
'a clear `task` description, and a `status` (e.g. "pending", "in_progress", "done").\n' +
|
|
14
|
-
'- **Handoff/Delegation**: Use handoff for ownership transfer and delegation for ' +
|
|
15
|
-
'subtask-return patterns. Reference the relevant Task ID from thread state and update ' +
|
|
16
|
-
'task status with `patch_thread_details` as progress is made.\n\n' +
|
|
17
|
-
'If you need to know what agents or packages are installed, I can help you find that information.';
|
|
18
|
-
export const DEFAULT_OPENBOT_APPROVAL_RULES = [
|
|
19
|
-
{
|
|
20
|
-
action: 'action:shell_exec',
|
|
21
|
-
denyEvent: 'action:shell_exec:result',
|
|
22
|
-
message: 'The agent wants to run a terminal command.',
|
|
23
|
-
detailKeys: ['command', 'cwd', 'shell', 'timeoutMs'],
|
|
24
|
-
hiddenKeys: ['env'],
|
|
25
|
-
denyData: {
|
|
26
|
-
exitCode: null,
|
|
27
|
-
stdout: '',
|
|
28
|
-
stderr: 'Command execution was denied by the user.',
|
|
29
|
-
timedOut: false,
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
];
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import z from 'zod';
|
|
2
|
-
/**
|
|
3
|
-
* Tool definitions for the OpenBot orchestrator's delegation/handoff capability.
|
|
4
|
-
*
|
|
5
|
-
* The actual cross-agent routing lives in the bus's queue processor: when this
|
|
6
|
-
* plugin yields `handoff:request` / `delegation:request` events, the orchestrator
|
|
7
|
-
* intercepts them and dispatches an `agent:invoke` to the target agent.
|
|
8
|
-
*/
|
|
9
|
-
export const delegationToolDefinitions = {
|
|
10
|
-
handoff: {
|
|
11
|
-
description: 'Transfer control to another agent. The target agent continues the task and you do not wait for a tool result.',
|
|
12
|
-
inputSchema: z.object({
|
|
13
|
-
agentId: z.string().describe('The ID of the target agent.'),
|
|
14
|
-
content: z.string().describe('The message or task to hand off.'),
|
|
15
|
-
}),
|
|
16
|
-
},
|
|
17
|
-
delegate: {
|
|
18
|
-
description: 'Delegate a subtask to another agent and wait for its result so you can continue.',
|
|
19
|
-
inputSchema: z.object({
|
|
20
|
-
agentId: z.string().describe('The ID of the target agent.'),
|
|
21
|
-
content: z.string().describe('The subtask you want the target agent to execute.'),
|
|
22
|
-
}),
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
export const delegationPlugin = () => (builder) => {
|
|
26
|
-
builder.on('action:handoff', async function* (event, context) {
|
|
27
|
-
const { agentId, content } = event.data;
|
|
28
|
-
yield {
|
|
29
|
-
type: 'agent:output',
|
|
30
|
-
data: { content: `Handing off to **${agentId}**: ${content}` },
|
|
31
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
32
|
-
};
|
|
33
|
-
yield {
|
|
34
|
-
type: 'handoff:request',
|
|
35
|
-
data: { agentId, content },
|
|
36
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
37
|
-
};
|
|
38
|
-
if (event.meta?.toolCallId) {
|
|
39
|
-
yield {
|
|
40
|
-
type: 'action:handoff:result',
|
|
41
|
-
data: { success: true, agentId, accepted: true },
|
|
42
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
builder.on('action:delegate', async function* (event, context) {
|
|
47
|
-
const { agentId, content } = event.data;
|
|
48
|
-
const widgetId = event.meta?.toolCallId
|
|
49
|
-
? `delegate_${event.meta.toolCallId}`
|
|
50
|
-
: `delegate_${Date.now()}`;
|
|
51
|
-
yield {
|
|
52
|
-
type: 'client:ui:widget',
|
|
53
|
-
data: {
|
|
54
|
-
kind: 'message',
|
|
55
|
-
widgetId,
|
|
56
|
-
title: `Delegation started: ${agentId}`,
|
|
57
|
-
body: `Running delegated task in background.\n\n${content}`,
|
|
58
|
-
state: 'open',
|
|
59
|
-
metadata: {
|
|
60
|
-
type: 'delegation:status',
|
|
61
|
-
phase: 'started',
|
|
62
|
-
delegatedAgentId: agentId,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
66
|
-
};
|
|
67
|
-
yield {
|
|
68
|
-
type: 'delegation:request',
|
|
69
|
-
data: { agentId, content },
|
|
70
|
-
meta: {
|
|
71
|
-
...(event.meta || {}),
|
|
72
|
-
parentAgentId: context.state.agentId,
|
|
73
|
-
delegationWidgetId: widgetId,
|
|
74
|
-
agentId: context.state.agentId,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
});
|
|
78
|
-
};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import z from 'zod';
|
|
2
|
-
import { mcpService } from '../../../harness/mcp.js';
|
|
3
|
-
function stringifyResult(value) {
|
|
4
|
-
if (typeof value === 'string')
|
|
5
|
-
return value;
|
|
6
|
-
try {
|
|
7
|
-
return JSON.stringify(value, null, 2);
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return String(value);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
export const mcpToolDefinitions = {
|
|
14
|
-
mcp_list_tools: {
|
|
15
|
-
description: 'List available tools from a configured MCP server. Use this first before calling tools on an unknown server.',
|
|
16
|
-
inputSchema: z.object({
|
|
17
|
-
serverId: z.string().describe('Configured MCP server id (e.g. github, notion, linear).'),
|
|
18
|
-
}),
|
|
19
|
-
},
|
|
20
|
-
mcp_call: {
|
|
21
|
-
description: 'Call a tool on a configured MCP server. Provide tool arguments as a JSON object. Use mcp_list_tools first when uncertain.',
|
|
22
|
-
inputSchema: z.object({
|
|
23
|
-
serverId: z.string().describe('Configured MCP server id.'),
|
|
24
|
-
toolName: z.string().describe('Exact MCP tool name from mcp_list_tools.'),
|
|
25
|
-
args: z
|
|
26
|
-
.record(z.string(), z.unknown())
|
|
27
|
-
.default({})
|
|
28
|
-
.describe('Tool arguments as a JSON object.'),
|
|
29
|
-
}),
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
export const mcpPlugin = () => (builder) => {
|
|
33
|
-
builder.on('action:mcp_list_tools', async function* (event, context) {
|
|
34
|
-
const serverId = event.data?.serverId;
|
|
35
|
-
try {
|
|
36
|
-
const tools = await mcpService.listTools(serverId);
|
|
37
|
-
const toolNames = tools.map((tool) => `- ${tool.name}${tool.description ? `: ${tool.description}` : ''}`);
|
|
38
|
-
yield {
|
|
39
|
-
type: 'action:mcp_list_tools:result',
|
|
40
|
-
data: { success: true, serverId, tools },
|
|
41
|
-
meta: event.meta,
|
|
42
|
-
};
|
|
43
|
-
yield {
|
|
44
|
-
type: 'agent:output',
|
|
45
|
-
data: {
|
|
46
|
-
content: toolNames.length > 0
|
|
47
|
-
? `MCP tools available on \`${serverId}\`:\n${toolNames.join('\n')}`
|
|
48
|
-
: `MCP server \`${serverId}\` has no tools.`,
|
|
49
|
-
},
|
|
50
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
const message = error instanceof Error ? error.message : 'Unknown MCP error';
|
|
55
|
-
yield {
|
|
56
|
-
type: 'action:mcp_list_tools:result',
|
|
57
|
-
data: { success: false, serverId, tools: [], error: message },
|
|
58
|
-
meta: event.meta,
|
|
59
|
-
};
|
|
60
|
-
yield {
|
|
61
|
-
type: 'agent:output',
|
|
62
|
-
data: { content: `Failed to list MCP tools for \`${serverId}\`: ${message}` },
|
|
63
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
builder.on('action:mcp_call', async function* (event, context) {
|
|
68
|
-
const serverId = event.data?.serverId;
|
|
69
|
-
const toolName = event.data?.toolName;
|
|
70
|
-
const args = (event.data?.args || {});
|
|
71
|
-
try {
|
|
72
|
-
const result = await mcpService.callTool(serverId, toolName, args);
|
|
73
|
-
const rendered = stringifyResult(result);
|
|
74
|
-
yield {
|
|
75
|
-
type: 'action:mcp_call:result',
|
|
76
|
-
data: { success: true, serverId, toolName, result },
|
|
77
|
-
meta: event.meta,
|
|
78
|
-
};
|
|
79
|
-
yield {
|
|
80
|
-
type: 'agent:output',
|
|
81
|
-
data: { content: `MCP \`${serverId}.${toolName}\` result:\n\n${rendered}` },
|
|
82
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
const message = error instanceof Error ? error.message : 'Unknown MCP error';
|
|
87
|
-
yield {
|
|
88
|
-
type: 'action:mcp_call:result',
|
|
89
|
-
data: { success: false, serverId, toolName, error: message },
|
|
90
|
-
meta: event.meta,
|
|
91
|
-
};
|
|
92
|
-
yield {
|
|
93
|
-
type: 'agent:output',
|
|
94
|
-
data: { content: `MCP call failed for \`${serverId}.${toolName}\`: ${message}` },
|
|
95
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
};
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
export const shellToolDefinitions = {
|
|
4
|
-
shell_exec: {
|
|
5
|
-
description: 'Execute a shell command in the terminal. Use this for file operations, running scripts, or system tasks.',
|
|
6
|
-
inputSchema: z.object({
|
|
7
|
-
command: z.string().describe('The shell command to execute.'),
|
|
8
|
-
cwd: z
|
|
9
|
-
.string()
|
|
10
|
-
.optional()
|
|
11
|
-
.describe('Working directory. Defaults to the channel cwd or workspace root. Leave empty unless the user requests a specific directory.'),
|
|
12
|
-
shell: z.enum(['bash', 'sh', 'zsh']).optional().describe('Shell to use. Defaults to bash.'),
|
|
13
|
-
timeoutMs: z
|
|
14
|
-
.number()
|
|
15
|
-
.optional()
|
|
16
|
-
.default(30000)
|
|
17
|
-
.describe('Maximum execution time in milliseconds. Defaults to 30000 (30s).'),
|
|
18
|
-
}),
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
export const shellPlugin = () => (builder) => {
|
|
22
|
-
builder.on('action:shell_exec', async function* (event, context) {
|
|
23
|
-
const { command, cwd, shell = 'bash', timeoutMs = 30000 } = event.data;
|
|
24
|
-
const actualTimeout = Math.max(1000, Math.min(timeoutMs, 60000));
|
|
25
|
-
const actualCwd = cwd || context.state.channelDetails?.cwd || process.cwd();
|
|
26
|
-
try {
|
|
27
|
-
const result = await new Promise((resolve) => {
|
|
28
|
-
const child = spawn(command, {
|
|
29
|
-
shell,
|
|
30
|
-
cwd: actualCwd,
|
|
31
|
-
env: { ...process.env },
|
|
32
|
-
});
|
|
33
|
-
let stdout = '';
|
|
34
|
-
let stderr = '';
|
|
35
|
-
let timedOut = false;
|
|
36
|
-
const timer = setTimeout(() => {
|
|
37
|
-
timedOut = true;
|
|
38
|
-
child.kill();
|
|
39
|
-
}, actualTimeout);
|
|
40
|
-
child.stdout.on('data', (data) => {
|
|
41
|
-
stdout += data.toString();
|
|
42
|
-
if (stdout.length > 100000) {
|
|
43
|
-
stdout = stdout.substring(0, 100000) + '\n... [output truncated]';
|
|
44
|
-
child.kill();
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
child.stderr.on('data', (data) => {
|
|
48
|
-
stderr += data.toString();
|
|
49
|
-
if (stderr.length > 100000) {
|
|
50
|
-
stderr = stderr.substring(0, 100000) + '\n... [output truncated]';
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
child.on('close', (code) => {
|
|
54
|
-
clearTimeout(timer);
|
|
55
|
-
resolve({ exitCode: code, stdout, stderr, timedOut });
|
|
56
|
-
});
|
|
57
|
-
child.on('error', (err) => {
|
|
58
|
-
clearTimeout(timer);
|
|
59
|
-
resolve({ exitCode: -1, stdout, stderr: stderr + err.message, timedOut: false });
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
const success = result.exitCode === 0 && !result.timedOut;
|
|
63
|
-
yield {
|
|
64
|
-
type: 'action:shell_exec:result',
|
|
65
|
-
data: {
|
|
66
|
-
success,
|
|
67
|
-
exitCode: result.exitCode,
|
|
68
|
-
stdout: result.stdout,
|
|
69
|
-
stderr: result.stderr,
|
|
70
|
-
timedOut: result.timedOut,
|
|
71
|
-
},
|
|
72
|
-
meta: event.meta,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
const message = error instanceof Error ? error.message : 'Unknown shell error';
|
|
77
|
-
yield {
|
|
78
|
-
type: 'action:shell_exec:result',
|
|
79
|
-
data: {
|
|
80
|
-
success: false,
|
|
81
|
-
exitCode: -1,
|
|
82
|
-
stdout: '',
|
|
83
|
-
stderr: message,
|
|
84
|
-
timedOut: false,
|
|
85
|
-
error: message,
|
|
86
|
-
},
|
|
87
|
-
meta: event.meta,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import z from 'zod';
|
|
2
|
-
/**
|
|
3
|
-
* Tool schemas the OpenBot orchestrator agent exposes to its LLM for talking to
|
|
4
|
-
* the bus's storage service. The actual handlers live in `src/bus/services.ts`
|
|
5
|
-
* since storage is platform infrastructure, not agent behaviour.
|
|
6
|
-
*/
|
|
7
|
-
export const storageToolDefinitions = {
|
|
8
|
-
create_channel: {
|
|
9
|
-
description: '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.',
|
|
10
|
-
inputSchema: z.object({
|
|
11
|
-
channelId: z
|
|
12
|
-
.string()
|
|
13
|
-
.describe('Unique channel ID (e.g. product-launch, backend-platform, channel_roadmap).'),
|
|
14
|
-
spec: z
|
|
15
|
-
.string()
|
|
16
|
-
.optional()
|
|
17
|
-
.describe('Optional initial markdown content for the channel spec.'),
|
|
18
|
-
initialState: z
|
|
19
|
-
.record(z.string(), z.unknown())
|
|
20
|
-
.optional()
|
|
21
|
-
.describe('Optional initial state object for the channel.'),
|
|
22
|
-
cwd: z
|
|
23
|
-
.string()
|
|
24
|
-
.optional()
|
|
25
|
-
.describe('Optional initial current working directory for the channel.'),
|
|
26
|
-
}),
|
|
27
|
-
},
|
|
28
|
-
patch_channel_details: {
|
|
29
|
-
description: 'Patch current channel details (state, spec, cwd).',
|
|
30
|
-
inputSchema: z
|
|
31
|
-
.object({
|
|
32
|
-
state: z
|
|
33
|
-
.record(z.string(), z.unknown())
|
|
34
|
-
.optional()
|
|
35
|
-
.describe('JSON state object for the channel. Use for structured data like `todos` or metadata.'),
|
|
36
|
-
spec: z
|
|
37
|
-
.string()
|
|
38
|
-
.optional()
|
|
39
|
-
.describe('Markdown content for the channel specification (SPEC.md). Use for goals and rules.'),
|
|
40
|
-
cwd: z.string().optional().describe('Current working directory for the channel.'),
|
|
41
|
-
})
|
|
42
|
-
.refine((value) => value.state !== undefined || value.spec !== undefined || value.cwd !== undefined, { message: 'Provide at least one of state, spec, or cwd.' }),
|
|
43
|
-
},
|
|
44
|
-
patch_thread_details: {
|
|
45
|
-
description: 'Patch current thread details (state and/or spec).',
|
|
46
|
-
inputSchema: z
|
|
47
|
-
.object({
|
|
48
|
-
state: z
|
|
49
|
-
.record(z.string(), z.unknown())
|
|
50
|
-
.optional()
|
|
51
|
-
.describe('JSON state object for the thread. Use for structured data like `todos` or progress.'),
|
|
52
|
-
spec: z
|
|
53
|
-
.string()
|
|
54
|
-
.optional()
|
|
55
|
-
.describe('Markdown content for the thread specification (SPEC.md). Use for plans and goals.'),
|
|
56
|
-
})
|
|
57
|
-
.refine((value) => value.state !== undefined || value.spec !== undefined, {
|
|
58
|
-
message: 'Provide at least one of state or spec.',
|
|
59
|
-
}),
|
|
60
|
-
},
|
|
61
|
-
create_variable: {
|
|
62
|
-
description: 'Create or update a variable in the workspace storage.',
|
|
63
|
-
inputSchema: z.object({
|
|
64
|
-
key: z.string().describe('The key of the variable.'),
|
|
65
|
-
value: z.string().describe('The value of the variable.'),
|
|
66
|
-
secret: z.boolean().optional().describe('Whether the variable is a secret.'),
|
|
67
|
-
}),
|
|
68
|
-
},
|
|
69
|
-
delete_variable: {
|
|
70
|
-
description: 'Delete a variable from the workspace storage.',
|
|
71
|
-
inputSchema: z.object({
|
|
72
|
-
key: z.string().describe('The key of the variable to delete.'),
|
|
73
|
-
}),
|
|
74
|
-
},
|
|
75
|
-
};
|