openbot 0.2.11 → 0.2.13
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/.prettierrc +8 -0
- package/AGENTS.md +68 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE +21 -0
- package/README.md +117 -14
- package/dist/agents/system.js +106 -0
- package/dist/app/cli.js +27 -0
- package/dist/app/config.js +64 -0
- package/dist/app/server.js +237 -0
- package/dist/app/utils.js +35 -0
- package/dist/harness/agent-harness.js +45 -0
- package/dist/harness/mcp.js +61 -0
- package/dist/harness/orchestrator.js +273 -0
- package/dist/harness/process.js +7 -0
- package/dist/plugins/ai-sdk.js +141 -0
- package/dist/plugins/delegation.js +52 -0
- package/dist/plugins/mcp.js +140 -0
- package/dist/plugins/storage.js +502 -0
- package/dist/plugins/ui.js +47 -0
- package/dist/registry/plugins.js +73 -0
- package/dist/services/storage.js +724 -0
- package/docs/README.md +7 -0
- package/docs/agents.md +83 -0
- package/docs/architecture.md +34 -0
- package/docs/plugins.md +77 -0
- package/logo-black.png +0 -0
- package/{dist/assets/logo.js → logo-black.svg} +24 -24
- package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
- package/package.json +10 -9
- package/src/agents/system.ts +112 -0
- package/src/app/cli.ts +38 -0
- package/src/app/config.ts +104 -0
- package/src/app/server.ts +284 -0
- package/src/app/types.ts +476 -0
- package/src/app/utils.ts +43 -0
- package/src/assets/icon.svg +1 -0
- package/src/harness/agent-harness.ts +58 -0
- package/src/harness/mcp.ts +78 -0
- package/src/harness/orchestrator.ts +342 -0
- package/src/harness/process.ts +9 -0
- package/src/harness/types.ts +34 -0
- package/src/plugins/ai-sdk.ts +197 -0
- package/src/plugins/delegation.ts +60 -0
- package/src/plugins/mcp.ts +154 -0
- package/src/plugins/storage.ts +725 -0
- package/src/plugins/ui.ts +57 -0
- package/src/registry/plugins.ts +85 -0
- package/src/services/storage.ts +957 -0
- package/tsconfig.json +18 -0
- package/dist/agents/agent-creator.js +0 -74
- package/dist/agents/browser-agent.js +0 -31
- package/dist/agents/os-agent.js +0 -32
- package/dist/agents/planner-agent.js +0 -32
- package/dist/agents/topic-agent.js +0 -46
- package/dist/architecture/execution-engine.js +0 -151
- package/dist/architecture/intent-classifier.js +0 -26
- package/dist/architecture/planner.js +0 -106
- package/dist/automation-worker.js +0 -121
- package/dist/automations.js +0 -52
- package/dist/cli.js +0 -275
- package/dist/config.js +0 -53
- package/dist/core/agents.js +0 -41
- package/dist/core/delegation.js +0 -230
- package/dist/core/manager.js +0 -96
- package/dist/core/plugins.js +0 -74
- package/dist/core/router.js +0 -191
- package/dist/handlers/init.js +0 -29
- package/dist/handlers/session-change.js +0 -21
- package/dist/handlers/settings.js +0 -47
- package/dist/handlers/tab-change.js +0 -14
- package/dist/installers.js +0 -156
- package/dist/marketplace.js +0 -80
- package/dist/model-catalog.js +0 -132
- package/dist/model-defaults.js +0 -25
- package/dist/models.js +0 -47
- package/dist/open-bot.js +0 -51
- package/dist/orchestrator/direct-invocation.js +0 -13
- package/dist/orchestrator/events.js +0 -36
- package/dist/orchestrator/state.js +0 -54
- package/dist/orchestrator.js +0 -422
- package/dist/plugins/agent/index.js +0 -81
- package/dist/plugins/approval/index.js +0 -100
- package/dist/plugins/brain/identity.js +0 -77
- package/dist/plugins/brain/index.js +0 -204
- package/dist/plugins/brain/memory.js +0 -120
- package/dist/plugins/brain/prompt.js +0 -46
- package/dist/plugins/brain/types.js +0 -45
- package/dist/plugins/brain/ui.js +0 -7
- package/dist/plugins/browser/index.js +0 -629
- package/dist/plugins/browser/ui.js +0 -13
- package/dist/plugins/file-system/index.js +0 -171
- package/dist/plugins/file-system/ui.js +0 -6
- package/dist/plugins/llm/context-budget.js +0 -139
- package/dist/plugins/llm/context-shaping.js +0 -177
- package/dist/plugins/llm/index.js +0 -380
- package/dist/plugins/memory/index.js +0 -220
- package/dist/plugins/memory/memory.js +0 -122
- package/dist/plugins/memory/prompt.js +0 -55
- package/dist/plugins/memory/types.js +0 -45
- package/dist/plugins/meta-agent/index.js +0 -570
- package/dist/plugins/meta-agent/ui.js +0 -11
- package/dist/plugins/shell/index.js +0 -100
- package/dist/plugins/shell/ui.js +0 -6
- package/dist/plugins/skills/index.js +0 -286
- package/dist/plugins/skills/types.js +0 -50
- package/dist/plugins/skills/ui.js +0 -12
- package/dist/registry/agent-registry.js +0 -35
- package/dist/registry/index.js +0 -2
- package/dist/registry/plugin-loader.js +0 -499
- package/dist/registry/plugin-registry.js +0 -44
- package/dist/registry/ts-agent-loader.js +0 -82
- package/dist/registry/yaml-agent-loader.js +0 -246
- package/dist/runtime/execution-trace.js +0 -41
- package/dist/runtime/intent-routing.js +0 -26
- package/dist/runtime/openbot-runtime.js +0 -354
- package/dist/server.js +0 -890
- package/dist/session.js +0 -179
- package/dist/ui/block.js +0 -12
- package/dist/ui/header.js +0 -52
- package/dist/ui/layout.js +0 -26
- package/dist/ui/navigation.js +0 -15
- package/dist/ui/settings.js +0 -106
- package/dist/ui/skills.js +0 -7
- package/dist/ui/thread.js +0 -16
- package/dist/ui/widgets/action-list.js +0 -2
- package/dist/ui/widgets/approval-card.js +0 -9
- package/dist/ui/widgets/code-snippet.js +0 -2
- package/dist/ui/widgets/data-block.js +0 -2
- package/dist/ui/widgets/data-table.js +0 -2
- package/dist/ui/widgets/delegation.js +0 -29
- package/dist/ui/widgets/empty-state.js +0 -2
- package/dist/ui/widgets/index.js +0 -23
- package/dist/ui/widgets/inquiry.js +0 -7
- package/dist/ui/widgets/key-value.js +0 -2
- package/dist/ui/widgets/progress-step.js +0 -2
- package/dist/ui/widgets/resource-card.js +0 -2
- package/dist/ui/widgets/status.js +0 -2
- package/dist/ui/widgets/todo-list.js +0 -2
- package/dist/version.js +0 -62
- /package/dist/{types.js → app/types.js} +0 -0
- /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import cors from 'cors';
|
|
4
|
+
import z from 'zod';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import { generateId } from 'melony';
|
|
8
|
+
import { DEFAULT_BASE_DIR, loadConfig, loadVariables, resolvePath } from '../app/config.js';
|
|
9
|
+
import { ActiveRunsSnapshotEvent, OpenBotEvent, OpenBotState } from './types.js';
|
|
10
|
+
import { processService } from '../harness/process.js';
|
|
11
|
+
import { storageService } from '../services/storage.js';
|
|
12
|
+
import { AgentHarness } from '../harness/agent-harness.js';
|
|
13
|
+
import { initPlugins } from '../registry/plugins.js';
|
|
14
|
+
import { ensureEventId, openBotEventFromQuery } from './utils.js';
|
|
15
|
+
|
|
16
|
+
export interface ServerOptions {
|
|
17
|
+
port?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function startServer(options: ServerOptions = {}) {
|
|
21
|
+
const publishEventSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
id: z.string().optional(),
|
|
24
|
+
type: z.string().min(1, 'Event type is required'),
|
|
25
|
+
data: z.unknown().optional(),
|
|
26
|
+
meta: z.unknown().optional(),
|
|
27
|
+
})
|
|
28
|
+
.passthrough();
|
|
29
|
+
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
const variables = loadVariables();
|
|
32
|
+
|
|
33
|
+
processService.applyVariablesToProcessEnv(variables.variables);
|
|
34
|
+
|
|
35
|
+
const baseDir = config.baseDir || DEFAULT_BASE_DIR;
|
|
36
|
+
const openBotDir = resolvePath(baseDir);
|
|
37
|
+
const PORT = Number(options.port ?? config.port ?? process.env.PORT ?? 4132);
|
|
38
|
+
const app = express();
|
|
39
|
+
const clients: Map<string, express.Response[]> = new Map();
|
|
40
|
+
const GLOBAL_CHANNEL_ID = '__global__';
|
|
41
|
+
const activeRuns = new Map<
|
|
42
|
+
string,
|
|
43
|
+
{ runId: string; channelId: string; threadId?: string; agentId: string }
|
|
44
|
+
>();
|
|
45
|
+
|
|
46
|
+
const agentsDir = path.join(openBotDir, 'agents');
|
|
47
|
+
const pluginsDir = path.join(openBotDir, 'plugins');
|
|
48
|
+
|
|
49
|
+
await fs.mkdir(agentsDir, { recursive: true });
|
|
50
|
+
await fs.mkdir(pluginsDir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
initPlugins(pluginsDir);
|
|
53
|
+
|
|
54
|
+
const getContext = (req: express.Request) => {
|
|
55
|
+
const channelId =
|
|
56
|
+
req.get('x-openbot-channel-id') || req.query.channelId || (req.body && req.body.channelId);
|
|
57
|
+
const threadId =
|
|
58
|
+
req.get('x-openbot-thread-id') || req.query.threadId || (req.body && req.body.threadId);
|
|
59
|
+
const agentId =
|
|
60
|
+
req.get('x-openbot-agent-id') || req.query.agentId || (req.body && req.body.agentId);
|
|
61
|
+
const runId =
|
|
62
|
+
req.get('x-openbot-run-id') ||
|
|
63
|
+
req.query.runId ||
|
|
64
|
+
(req.body && req.body.runId) ||
|
|
65
|
+
`run_${generateId()}`;
|
|
66
|
+
const responseType =
|
|
67
|
+
req.get('x-openbot-response-type') ||
|
|
68
|
+
req.query.responseType ||
|
|
69
|
+
(req.body && req.body.responseType);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
channelId: (channelId || (threadId ? 'general' : 'general')) as string, // Default to general if none
|
|
73
|
+
threadId: threadId as string | undefined,
|
|
74
|
+
agentId: agentId as string | undefined,
|
|
75
|
+
runId: runId as string,
|
|
76
|
+
responseType: responseType as string | undefined,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const getClientKey = (channelId: string, threadId?: string) =>
|
|
81
|
+
threadId ? `${channelId}:${threadId}` : channelId;
|
|
82
|
+
|
|
83
|
+
const sendToClientKey = (clientKey: string, chunk: OpenBotEvent) => {
|
|
84
|
+
const threadClients = clients.get(clientKey);
|
|
85
|
+
if (!threadClients) return;
|
|
86
|
+
threadClients.forEach((client) => {
|
|
87
|
+
if (!client.writableEnded) {
|
|
88
|
+
client.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const buildActiveRunsSnapshot = (): ActiveRunsSnapshotEvent => {
|
|
94
|
+
const byChannel = new Map<string, { activeCount: number; agentIds: Set<string> }>();
|
|
95
|
+
for (const run of activeRuns.values()) {
|
|
96
|
+
const existing = byChannel.get(run.channelId) ?? {
|
|
97
|
+
activeCount: 0,
|
|
98
|
+
agentIds: new Set<string>(),
|
|
99
|
+
};
|
|
100
|
+
existing.activeCount += 1;
|
|
101
|
+
existing.agentIds.add(run.agentId);
|
|
102
|
+
byChannel.set(run.channelId, existing);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
type: 'agent:active-runs:snapshot',
|
|
106
|
+
data: {
|
|
107
|
+
channels: Array.from(byChannel.entries()).map(([channelId, value]) => ({
|
|
108
|
+
channelId,
|
|
109
|
+
activeCount: value.activeCount,
|
|
110
|
+
agentIds: Array.from(value.agentIds),
|
|
111
|
+
})),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
app.use(cors());
|
|
117
|
+
app.use(express.json({ limit: '20mb' }));
|
|
118
|
+
|
|
119
|
+
app.get('/api/events', (req, res) => {
|
|
120
|
+
const { channelId, threadId } = getContext(req);
|
|
121
|
+
const clientKey = getClientKey(channelId, threadId);
|
|
122
|
+
|
|
123
|
+
// SSE response headers: keep the HTTP connection open and unbuffered.
|
|
124
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
125
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
126
|
+
res.setHeader('Connection', 'keep-alive');
|
|
127
|
+
// Helpful behind proxies (for example nginx) to avoid response buffering.
|
|
128
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
129
|
+
// Flush headers immediately so the browser moves from "connecting" to "open".
|
|
130
|
+
res.flushHeaders();
|
|
131
|
+
|
|
132
|
+
// Tell EventSource clients how long to wait before reconnecting.
|
|
133
|
+
res.write('retry: 3000\n');
|
|
134
|
+
// Initial comment frame so the stream has activity right after subscribe.
|
|
135
|
+
res.write(': connected\n\n');
|
|
136
|
+
|
|
137
|
+
// Track all active SSE subscribers for fan-out in /api/publish.
|
|
138
|
+
if (!clients.has(clientKey)) {
|
|
139
|
+
clients.set(clientKey, []);
|
|
140
|
+
}
|
|
141
|
+
clients.get(clientKey)!.push(res);
|
|
142
|
+
|
|
143
|
+
if (channelId === GLOBAL_CHANNEL_ID) {
|
|
144
|
+
const snapshot = buildActiveRunsSnapshot();
|
|
145
|
+
ensureEventId(snapshot);
|
|
146
|
+
res.write(`data: ${JSON.stringify(snapshot)}\n\n`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Keep connection alive through intermediaries that close idle streams.
|
|
150
|
+
const heartbeat = setInterval(() => {
|
|
151
|
+
if (!res.writableEnded) {
|
|
152
|
+
res.write(': keepalive\n\n');
|
|
153
|
+
}
|
|
154
|
+
}, 25000);
|
|
155
|
+
|
|
156
|
+
req.on('close', () => {
|
|
157
|
+
// Cleanup heartbeat + subscriber when the client disconnects.
|
|
158
|
+
clearInterval(heartbeat);
|
|
159
|
+
const threadClients = clients.get(clientKey);
|
|
160
|
+
if (threadClients) {
|
|
161
|
+
const index = threadClients.indexOf(res);
|
|
162
|
+
if (index !== -1) {
|
|
163
|
+
threadClients.splice(index, 1);
|
|
164
|
+
}
|
|
165
|
+
if (threadClients.length === 0) {
|
|
166
|
+
clients.delete(clientKey);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
app.post('/api/publish', async (req, res) => {
|
|
173
|
+
const parseResult = publishEventSchema.safeParse(req.body);
|
|
174
|
+
if (!parseResult.success) {
|
|
175
|
+
res.status(400).json({
|
|
176
|
+
error: 'Invalid publish event payload',
|
|
177
|
+
details: parseResult.error.issues.map((issue) => issue.message),
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const event = parseResult.data as OpenBotEvent;
|
|
183
|
+
|
|
184
|
+
const { channelId, threadId, agentId, runId } = getContext(req);
|
|
185
|
+
|
|
186
|
+
if (!channelId || !channelId.trim()) {
|
|
187
|
+
res.status(400).json({ error: 'channelId is required' });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const onEvent = async (chunk: OpenBotEvent, state?: OpenBotState) => {
|
|
192
|
+
ensureEventId(chunk);
|
|
193
|
+
|
|
194
|
+
const targetChannelId = state?.channelId || channelId;
|
|
195
|
+
const targetThreadId = state?.threadId || threadId;
|
|
196
|
+
const targetClientKey = getClientKey(targetChannelId, targetThreadId);
|
|
197
|
+
|
|
198
|
+
if (chunk.type === 'agent:run:start') {
|
|
199
|
+
activeRuns.set(chunk.data.runId, {
|
|
200
|
+
runId: chunk.data.runId,
|
|
201
|
+
channelId: chunk.data.channelId,
|
|
202
|
+
threadId: chunk.data.threadId,
|
|
203
|
+
agentId: chunk.data.agentId,
|
|
204
|
+
});
|
|
205
|
+
} else if (chunk.type === 'agent:run:end') {
|
|
206
|
+
activeRuns.delete(chunk.data.runId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await storageService.storeEvent({
|
|
210
|
+
channelId: targetChannelId,
|
|
211
|
+
threadId: targetThreadId,
|
|
212
|
+
event: chunk,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
sendToClientKey(targetClientKey, chunk);
|
|
216
|
+
|
|
217
|
+
if (chunk.type === 'agent:run:start' || chunk.type === 'agent:run:end') {
|
|
218
|
+
sendToClientKey(GLOBAL_CHANNEL_ID, chunk);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const harness = new AgentHarness({
|
|
224
|
+
runId,
|
|
225
|
+
agentId: agentId || 'system',
|
|
226
|
+
channelId,
|
|
227
|
+
threadId,
|
|
228
|
+
onEvent,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await harness.dispatch(event);
|
|
232
|
+
res.sendStatus(200);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('[publish] Failed to dispatch event', {
|
|
235
|
+
runId,
|
|
236
|
+
channelId,
|
|
237
|
+
threadId,
|
|
238
|
+
eventType: event.type,
|
|
239
|
+
error,
|
|
240
|
+
});
|
|
241
|
+
res.status(500).json({ error: 'Failed to process publish event' });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
app.get('/api/state', async (req, res) => {
|
|
246
|
+
let event: OpenBotEvent;
|
|
247
|
+
try {
|
|
248
|
+
event = openBotEventFromQuery(req.query);
|
|
249
|
+
} catch (e) {
|
|
250
|
+
const message = e instanceof Error ? e.message : 'Invalid query';
|
|
251
|
+
res.status(400).json({ error: message });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const { channelId, threadId, agentId, runId } = getContext(req);
|
|
256
|
+
const events: OpenBotEvent[] = [];
|
|
257
|
+
|
|
258
|
+
const onEvent = async (chunk: OpenBotEvent) => {
|
|
259
|
+
events.push(chunk);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const harness = new AgentHarness({
|
|
264
|
+
runId,
|
|
265
|
+
agentId: agentId || 'system',
|
|
266
|
+
channelId,
|
|
267
|
+
threadId,
|
|
268
|
+
onEvent,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await harness.dispatch(event);
|
|
272
|
+
res.json({ events });
|
|
273
|
+
} catch (error) {
|
|
274
|
+
res.status(500).json({ error: 'Failed to process state request' });
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
app.listen(PORT, () => {
|
|
279
|
+
console.log(`\x1b[32mOpenBot server listening at http://localhost:${PORT}\x1b[0m`);
|
|
280
|
+
console.log(` - Events endpoint: GET /events (SSE)`);
|
|
281
|
+
console.log(` - Publish endpoint: POST /publish`);
|
|
282
|
+
console.log(` - State endpoint: GET /state`);
|
|
283
|
+
});
|
|
284
|
+
}
|