openbot 0.3.6 → 0.4.2
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 +10 -19
- package/dist/app/server.js +208 -17
- 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 +109 -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 +120 -149
- package/dist/plugins/bash/index.js +195 -0
- package/dist/plugins/delegation/index.js +121 -32
- package/dist/plugins/memory/index.js +103 -14
- package/dist/plugins/memory/service.js +152 -0
- package/dist/plugins/openbot/context.js +125 -0
- package/dist/plugins/openbot/history.js +144 -0
- package/dist/plugins/openbot/index.js +71 -0
- package/dist/plugins/openbot/runtime.js +381 -0
- package/dist/plugins/openbot/system-prompt.js +25 -0
- package/dist/plugins/plugin-manager/index.js +189 -0
- package/dist/plugins/shell/index.js +2 -1
- package/dist/plugins/storage/files.js +67 -0
- package/dist/plugins/storage/index.js +750 -0
- package/dist/plugins/storage/service.js +1316 -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 +109 -180
- package/dist/registry/plugins.js +3 -9
- package/dist/services/abort.js +43 -0
- package/dist/services/plugins/domain.js +1 -0
- package/dist/services/plugins/plugin-cache.js +9 -0
- package/dist/services/plugins/registry.js +112 -0
- package/dist/services/plugins/service.js +232 -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 +15 -12
- package/docs/architecture.md +2 -2
- package/docs/plugins.md +29 -17
- package/docs/templates/AGENT.example.md +8 -14
- 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 +14 -31
- package/src/app/server.ts +243 -19
- package/src/app/types.ts +331 -187
- package/src/harness/index.ts +166 -0
- package/src/plugins/approval/index.ts +107 -188
- package/src/plugins/bash/index.ts +232 -0
- package/src/plugins/delegation/index.ts +139 -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 +140 -0
- package/src/plugins/openbot/history.ts +158 -0
- package/src/plugins/openbot/index.ts +79 -0
- package/src/plugins/openbot/runtime.ts +478 -0
- package/src/plugins/openbot/system-prompt.ts +27 -0
- package/src/plugins/plugin-manager/index.ts +224 -0
- package/src/plugins/storage/files.ts +81 -0
- package/src/plugins/storage/index.ts +823 -0
- package/src/{services/storage.ts → plugins/storage/service.ts} +485 -105
- package/src/plugins/ui/index.ts +117 -221
- package/src/services/abort.ts +46 -0
- package/src/{bus/types.ts → services/plugins/domain.ts} +50 -8
- package/src/services/plugins/plugin-cache.ts +13 -0
- package/src/{registry/plugins.ts → services/plugins/registry.ts} +28 -28
- package/src/services/plugins/service.ts +318 -0
- package/src/{bus/plugin.ts → services/plugins/types.ts} +7 -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/shell/index.ts +0 -123
- package/src/plugins/storage-tools/index.ts +0 -90
- package/src/plugins/todo/index.ts +0 -64
- package/src/services/plugins.ts +0 -133
- /package/src/{harness → services}/process.ts +0 -0
package/src/app/config.ts
CHANGED
|
@@ -9,20 +9,13 @@ export interface OpenBotconfig {
|
|
|
9
9
|
image?: string;
|
|
10
10
|
baseDir?: string;
|
|
11
11
|
port?: number;
|
|
12
|
-
mcpServers?: MCPServerConfig[];
|
|
13
12
|
/**
|
|
14
13
|
* Overrides the default public marketplace registry URL. If omitted or blank,
|
|
15
14
|
* {@link DEFAULT_MARKETPLACE_REGISTRY_URL} is used.
|
|
16
15
|
*/
|
|
17
16
|
marketplaceRegistryUrl?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface MCPServerConfig {
|
|
21
|
-
id: string;
|
|
22
|
-
command: string;
|
|
23
|
-
args?: string[];
|
|
24
|
-
env?: Record<string, string>;
|
|
25
|
-
cwd?: string;
|
|
17
|
+
/** Public base URL for workspace file links (e.g. https://my-host.example). Falls back to OPENBOT_PUBLIC_URL env or http://localhost:{port}. */
|
|
18
|
+
publicUrl?: string;
|
|
26
19
|
}
|
|
27
20
|
|
|
28
21
|
export interface StoredVariable {
|
|
@@ -32,6 +25,8 @@ export interface StoredVariable {
|
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
export const DEFAULT_BASE_DIR = '~/.openbot';
|
|
28
|
+
/** Default parent directory for per-channel working directories (user-facing workspace). */
|
|
29
|
+
export const DEFAULT_CHANNELS_WORKSPACE_DIR = '~/openbot';
|
|
35
30
|
export const DEFAULT_PLUGINS_DIR = 'plugins';
|
|
36
31
|
export const DEFAULT_AGENTS_DIR = 'agents';
|
|
37
32
|
export const DEFAULT_CHANNELS_DIR = 'channels';
|
|
@@ -46,6 +41,15 @@ export function resolvePath(p: string) {
|
|
|
46
41
|
return p.startsWith('~/') ? path.join(os.homedir(), p.slice(2)) : path.resolve(p);
|
|
47
42
|
}
|
|
48
43
|
|
|
44
|
+
/** Default absolute cwd for a channel when none is provided at creation time. */
|
|
45
|
+
export function getDefaultChannelCwd(channelId: string): string {
|
|
46
|
+
const id = channelId.trim();
|
|
47
|
+
if (!id) {
|
|
48
|
+
throw new Error('channelId is required');
|
|
49
|
+
}
|
|
50
|
+
return resolvePath(`${DEFAULT_CHANNELS_WORKSPACE_DIR}/${id}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
export function loadConfig(): OpenBotconfig {
|
|
50
54
|
const configPath = path.join(os.homedir(), '.openbot', CONFIG_FILE);
|
|
51
55
|
if (fs.existsSync(configPath)) {
|
|
@@ -90,25 +94,4 @@ export function loadVariables(): { version: number; variables: StoredVariable[]
|
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
return { version: 1, variables: [] };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export const DEFAULT_AGENT_MD = `---
|
|
96
|
-
description: A specialized AI agent
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
# Agent Profile
|
|
100
|
-
|
|
101
|
-
You are a specialized AI agent within the OpenBot system.
|
|
102
|
-
Your role is defined by your configuration and the tools you have access to.
|
|
103
|
-
|
|
104
|
-
## Persona
|
|
105
|
-
- Helpful and precise
|
|
106
|
-
- Focused on my specific domain
|
|
107
|
-
- Professional in all interactions
|
|
108
|
-
`;
|
|
109
|
-
|
|
110
|
-
export const DEFAULT_USER_MD = `# About Me
|
|
111
|
-
|
|
112
|
-
<!-- OpenBot reads this file to understand who you are and how you like to work. -->
|
|
113
|
-
<!-- Edit it here or just chat — agents can update it with the "remember" tool. -->
|
|
114
|
-
`;
|
|
97
|
+
}
|
package/src/app/server.ts
CHANGED
|
@@ -10,11 +10,17 @@ const pkg = require('../../package.json');
|
|
|
10
10
|
import { generateId } from 'melony';
|
|
11
11
|
import { DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../app/config.js';
|
|
12
12
|
import { ActiveRunsSnapshotEvent, OpenBotEvent, OpenBotState } from './types.js';
|
|
13
|
-
import { processService } from '../
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { processService } from '../services/process.js';
|
|
14
|
+
import { runAgent, STATE_AGENT_ID, ORCHESTRATOR_AGENT_ID } from '../harness/index.js';
|
|
15
|
+
import { initPlugins } from '../services/plugins/registry.js';
|
|
16
|
+
import { storageService } from '../plugins/storage/service.js';
|
|
17
|
+
import {
|
|
18
|
+
buildWorkspaceFileUrl,
|
|
19
|
+
getPublicBaseUrl,
|
|
20
|
+
openChannelFileStream,
|
|
21
|
+
} from '../plugins/storage/files.js';
|
|
17
22
|
import { ensureEventId, openBotEventFromQuery } from './utils.js';
|
|
23
|
+
import { abortRegistry, abortKey } from '../services/abort.js';
|
|
18
24
|
|
|
19
25
|
type Bucket = { channelId: string; threadId?: string; activeCount: number; agentIds: Set<string> };
|
|
20
26
|
|
|
@@ -54,6 +60,10 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
54
60
|
|
|
55
61
|
initPlugins(pluginsDir);
|
|
56
62
|
|
|
63
|
+
// Pre-warm caches for agents and plugins to speed up first UI load
|
|
64
|
+
storageService.getAgents().catch((err) => console.warn('[server] Failed to pre-warm agents cache', err));
|
|
65
|
+
storageService.getPlugins().catch((err) => console.warn('[server] Failed to pre-warm plugins cache', err));
|
|
66
|
+
|
|
57
67
|
const getContext = (req: express.Request) => {
|
|
58
68
|
const channelId =
|
|
59
69
|
req.get('x-openbot-channel-id') || req.query.channelId || (req.body && req.body.channelId);
|
|
@@ -72,7 +82,7 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
72
82
|
(req.body && req.body.responseType);
|
|
73
83
|
|
|
74
84
|
return {
|
|
75
|
-
channelId: (channelId || (threadId ? '
|
|
85
|
+
channelId: (channelId || (threadId ? 'uncategorized' : 'uncategorized')) as string, // Default to uncategorized if none
|
|
76
86
|
threadId: threadId as string | undefined,
|
|
77
87
|
agentId: agentId as string | undefined,
|
|
78
88
|
runId: runId as string,
|
|
@@ -87,7 +97,16 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
87
97
|
|
|
88
98
|
const sendToClientKey = (clientKey: string, chunk: OpenBotEvent) => {
|
|
89
99
|
const threadClients = clients.get(clientKey);
|
|
90
|
-
if (!threadClients) return;
|
|
100
|
+
if (!threadClients || threadClients.length === 0) return;
|
|
101
|
+
|
|
102
|
+
// Auto-detect "read" state: if someone is listening, they just "read" this event.
|
|
103
|
+
if (chunk.id && clientKey !== GLOBAL_CHANNEL_ID) {
|
|
104
|
+
const parts = clientKey.split(':');
|
|
105
|
+
const channelId = parts[0];
|
|
106
|
+
const threadId = parts[1]; // undefined if no ":"
|
|
107
|
+
storageService.setLastRead({ channelId, threadId, lastReadEventId: chunk.id }).catch(() => {});
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
threadClients.forEach((client) => {
|
|
92
111
|
if (!client.writableEnded) {
|
|
93
112
|
client.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
@@ -131,8 +150,37 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
131
150
|
};
|
|
132
151
|
};
|
|
133
152
|
|
|
153
|
+
// Drop every tracked run for a channel/thread. A stop aborts the whole
|
|
154
|
+
// chain (parent + delegated sub-agents), but the sub-agents' `agent:run:end`
|
|
155
|
+
// events can be swallowed when the parent run loop breaks on abort, leaving
|
|
156
|
+
// orphaned entries that keep a channel falsely "active". Purging by
|
|
157
|
+
// channel/thread guarantees the snapshot self-heals after a stop.
|
|
158
|
+
const purgeActiveRunsForThread = (channelId: string, threadId?: string): void => {
|
|
159
|
+
const target = threadId || undefined;
|
|
160
|
+
for (const [key, run] of activeRuns) {
|
|
161
|
+
if (run.channelId === channelId && (run.threadId || undefined) === target) {
|
|
162
|
+
activeRuns.delete(key);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
134
167
|
app.use(cors());
|
|
135
|
-
|
|
168
|
+
|
|
169
|
+
const resolvePublicBaseUrl = () => getPublicBaseUrl(PORT, config.publicUrl);
|
|
170
|
+
|
|
171
|
+
app.use((req, res, next) => {
|
|
172
|
+
const isWorkspaceUpload =
|
|
173
|
+
req.method === 'POST' &&
|
|
174
|
+
req.path === '/api/publish' &&
|
|
175
|
+
req.get('x-openbot-event-type') === 'action:storage:upload-file';
|
|
176
|
+
|
|
177
|
+
if (isWorkspaceUpload) {
|
|
178
|
+
express.raw({ type: () => true, limit: '100mb' })(req, res, next);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
express.json({ limit: '20mb' })(req, res, next);
|
|
183
|
+
});
|
|
136
184
|
|
|
137
185
|
app.get('/api/health', (req, res) => {
|
|
138
186
|
res.json({ status: 'ok', version: pkg.version });
|
|
@@ -162,6 +210,19 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
162
210
|
}
|
|
163
211
|
clients.get(clientKey)!.push(res);
|
|
164
212
|
|
|
213
|
+
// Auto-detect "read" state on connection: mark the latest event as seen.
|
|
214
|
+
if (channelId !== GLOBAL_CHANNEL_ID) {
|
|
215
|
+
storageService
|
|
216
|
+
.getEvents({ channelId, threadId })
|
|
217
|
+
.then((events) => {
|
|
218
|
+
const latestId = events[events.length - 1]?.id;
|
|
219
|
+
if (latestId) {
|
|
220
|
+
return storageService.setLastRead({ channelId, threadId, lastReadEventId: latestId });
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
.catch(() => {});
|
|
224
|
+
}
|
|
225
|
+
|
|
165
226
|
if (channelId === GLOBAL_CHANNEL_ID) {
|
|
166
227
|
const snapshot = buildActiveRunsSnapshot();
|
|
167
228
|
|
|
@@ -193,6 +254,57 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
193
254
|
});
|
|
194
255
|
|
|
195
256
|
app.post('/api/publish', async (req, res) => {
|
|
257
|
+
if (req.get('x-openbot-event-type') === 'action:storage:upload-file') {
|
|
258
|
+
const channelId =
|
|
259
|
+
req.get('x-openbot-channel-id') ||
|
|
260
|
+
(typeof req.query.channelId === 'string' ? req.query.channelId : undefined);
|
|
261
|
+
const filePath = req.get('x-openbot-file-path');
|
|
262
|
+
const overwrite = req.get('x-openbot-file-overwrite') === 'true';
|
|
263
|
+
|
|
264
|
+
if (!channelId?.trim()) {
|
|
265
|
+
res.status(400).json({ error: 'channelId is required' });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (!filePath?.trim()) {
|
|
269
|
+
res.status(400).json({ error: 'x-openbot-file-path header is required' });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const body = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
|
|
274
|
+
if (body.length === 0) {
|
|
275
|
+
res.status(400).json({ error: 'Request body is empty' });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const result = await storageService.uploadChannelFile({
|
|
281
|
+
channelId: channelId.trim(),
|
|
282
|
+
path: filePath.trim(),
|
|
283
|
+
body,
|
|
284
|
+
overwrite,
|
|
285
|
+
});
|
|
286
|
+
const url = buildWorkspaceFileUrl({
|
|
287
|
+
baseUrl: resolvePublicBaseUrl(),
|
|
288
|
+
channelId: channelId.trim(),
|
|
289
|
+
filePath: result.path,
|
|
290
|
+
});
|
|
291
|
+
res.json({
|
|
292
|
+
type: 'action:storage:upload-file:result',
|
|
293
|
+
data: { success: true, ...result, url },
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
res.status(400).json({
|
|
297
|
+
type: 'action:storage:upload-file:result',
|
|
298
|
+
data: {
|
|
299
|
+
success: false,
|
|
300
|
+
path: filePath,
|
|
301
|
+
error: error instanceof Error ? error.message : 'Upload failed',
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
196
308
|
const parseResult = publishEventSchema.safeParse(req.body);
|
|
197
309
|
if (!parseResult.success) {
|
|
198
310
|
res.status(400).json({
|
|
@@ -211,9 +323,93 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
211
323
|
return;
|
|
212
324
|
}
|
|
213
325
|
|
|
214
|
-
|
|
215
|
-
|
|
326
|
+
if (event.type === 'action:storage:write-file') {
|
|
327
|
+
const data = (event.data ?? {}) as {
|
|
328
|
+
path?: string;
|
|
329
|
+
content?: string;
|
|
330
|
+
encoding?: 'utf8' | 'base64';
|
|
331
|
+
overwrite?: boolean;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
if (!data.path?.trim()) {
|
|
335
|
+
res.status(400).json({
|
|
336
|
+
type: 'action:storage:write-file:result',
|
|
337
|
+
data: { success: false, path: '', error: 'path is required' },
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (typeof data.content !== 'string') {
|
|
342
|
+
res.status(400).json({
|
|
343
|
+
type: 'action:storage:write-file:result',
|
|
344
|
+
data: { success: false, path: data.path, error: 'content is required' },
|
|
345
|
+
});
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const result = await storageService.writeChannelFile({
|
|
351
|
+
channelId,
|
|
352
|
+
path: data.path.trim(),
|
|
353
|
+
content: data.content,
|
|
354
|
+
encoding: data.encoding ?? 'utf8',
|
|
355
|
+
overwrite: data.overwrite ?? false,
|
|
356
|
+
});
|
|
357
|
+
const url = buildWorkspaceFileUrl({
|
|
358
|
+
baseUrl: resolvePublicBaseUrl(),
|
|
359
|
+
channelId,
|
|
360
|
+
filePath: result.path,
|
|
361
|
+
});
|
|
362
|
+
res.json({
|
|
363
|
+
type: 'action:storage:write-file:result',
|
|
364
|
+
data: { success: true, ...result, url },
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
res.status(400).json({
|
|
368
|
+
type: 'action:storage:write-file:result',
|
|
369
|
+
data: {
|
|
370
|
+
success: false,
|
|
371
|
+
path: data.path,
|
|
372
|
+
error: error instanceof Error ? error.message : 'Write failed',
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Stop request: cancel the in-flight run (and any delegated sub-agents in the
|
|
380
|
+
// same thread) instead of spinning up a new agent turn.
|
|
381
|
+
if (event.type === 'action:agent_run_stop') {
|
|
382
|
+
const data = (event.data ?? {}) as {
|
|
383
|
+
runId?: string;
|
|
384
|
+
agentId?: string;
|
|
385
|
+
channelId?: string;
|
|
386
|
+
threadId?: string;
|
|
387
|
+
reason?: string;
|
|
388
|
+
};
|
|
389
|
+
const targetChannelId = data.channelId || channelId;
|
|
390
|
+
const targetThreadId = data.threadId || threadId;
|
|
391
|
+
const stopped = abortRegistry.abort(abortKey(targetChannelId, targetThreadId));
|
|
392
|
+
purgeActiveRunsForThread(targetChannelId, targetThreadId);
|
|
393
|
+
|
|
394
|
+
const stoppedEvent: OpenBotEvent = {
|
|
395
|
+
type: 'agent:run:stopped',
|
|
396
|
+
data: {
|
|
397
|
+
runId: data.runId || runId,
|
|
398
|
+
agentId: data.agentId || agentId || ORCHESTRATOR_AGENT_ID,
|
|
399
|
+
channelId: targetChannelId,
|
|
400
|
+
threadId: targetThreadId,
|
|
401
|
+
reason: data.reason,
|
|
402
|
+
},
|
|
403
|
+
} as OpenBotEvent;
|
|
404
|
+
ensureEventId(stoppedEvent);
|
|
405
|
+
sendToClientKey(getClientKey(targetChannelId, targetThreadId), stoppedEvent);
|
|
406
|
+
sendToClientKey(GLOBAL_CHANNEL_ID, stoppedEvent);
|
|
407
|
+
|
|
408
|
+
res.json({ success: stopped });
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
216
411
|
|
|
412
|
+
const onEvent = async (chunk: OpenBotEvent, state?: OpenBotState) => {
|
|
217
413
|
const targetChannelId = state?.channelId || channelId;
|
|
218
414
|
const targetThreadId = state?.threadId || threadId;
|
|
219
415
|
const targetClientKey = getClientKey(targetChannelId, targetThreadId);
|
|
@@ -232,14 +428,10 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
232
428
|
activeRuns.delete(
|
|
233
429
|
getRunKey(chunk.data.runId, chunk.data.agentId, chunk.data.channelId, chunk.data.threadId),
|
|
234
430
|
);
|
|
431
|
+
} else if (chunk.type === 'agent:run:stopped') {
|
|
432
|
+
purgeActiveRunsForThread(chunk.data.channelId, chunk.data.threadId);
|
|
235
433
|
}
|
|
236
434
|
|
|
237
|
-
await storageService.storeEvent({
|
|
238
|
-
channelId: targetChannelId,
|
|
239
|
-
threadId: targetThreadId,
|
|
240
|
-
event: chunk,
|
|
241
|
-
});
|
|
242
|
-
|
|
243
435
|
sendToClientKey(targetClientKey, chunk);
|
|
244
436
|
|
|
245
437
|
if (
|
|
@@ -254,12 +446,13 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
254
446
|
try {
|
|
255
447
|
ensureEventId(event);
|
|
256
448
|
|
|
257
|
-
await
|
|
449
|
+
await runAgent({
|
|
258
450
|
runId,
|
|
259
|
-
agentId: agentId ||
|
|
451
|
+
agentId: agentId || ORCHESTRATOR_AGENT_ID,
|
|
260
452
|
event,
|
|
261
453
|
channelId,
|
|
262
454
|
threadId,
|
|
455
|
+
publicBaseUrl: resolvePublicBaseUrl(),
|
|
263
456
|
onEvent,
|
|
264
457
|
});
|
|
265
458
|
res.sendStatus(200);
|
|
@@ -286,6 +479,35 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
286
479
|
}
|
|
287
480
|
|
|
288
481
|
const { channelId, threadId, agentId, runId } = getContext(req);
|
|
482
|
+
|
|
483
|
+
if (event.type === 'action:storage:serve-file') {
|
|
484
|
+
const filePath = (event.data as { path?: string })?.path;
|
|
485
|
+
if (!channelId?.trim()) {
|
|
486
|
+
res.status(400).json({ error: 'channelId is required' });
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (!filePath?.trim()) {
|
|
490
|
+
res.status(400).json({ error: 'path is required' });
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
const { abs, size, mimeType } = await storageService.getChannelFileStat({
|
|
496
|
+
channelId,
|
|
497
|
+
path: filePath.trim(),
|
|
498
|
+
});
|
|
499
|
+
res.setHeader('Content-Type', mimeType);
|
|
500
|
+
res.setHeader('Content-Length', String(size));
|
|
501
|
+
res.setHeader('Cache-Control', 'private, max-age=3600');
|
|
502
|
+
openChannelFileStream(abs).pipe(res);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
res.status(404).json({
|
|
505
|
+
error: error instanceof Error ? error.message : 'File not found',
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
289
511
|
const events: OpenBotEvent[] = [];
|
|
290
512
|
|
|
291
513
|
const onEvent = async (chunk: OpenBotEvent) => {
|
|
@@ -295,12 +517,14 @@ export async function startServer(options: ServerOptions = {}) {
|
|
|
295
517
|
try {
|
|
296
518
|
ensureEventId(event);
|
|
297
519
|
|
|
298
|
-
await
|
|
520
|
+
await runAgent({
|
|
299
521
|
runId,
|
|
300
|
-
agentId: agentId ||
|
|
522
|
+
agentId: agentId || STATE_AGENT_ID,
|
|
301
523
|
event,
|
|
302
524
|
channelId,
|
|
303
525
|
threadId,
|
|
526
|
+
persistEvents: false,
|
|
527
|
+
publicBaseUrl: resolvePublicBaseUrl(),
|
|
304
528
|
onEvent,
|
|
305
529
|
});
|
|
306
530
|
res.json({ events });
|