openbot 0.3.0 → 0.3.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.
Files changed (43) hide show
  1. package/dist/app/cli.js +1 -1
  2. package/dist/app/server.js +1 -4
  3. package/dist/bus/services.js +222 -15
  4. package/dist/harness/context.js +205 -26
  5. package/dist/harness/queue-processor.js +44 -110
  6. package/dist/harness/runtime-factory.js +11 -7
  7. package/dist/harness/todo-advance.js +93 -0
  8. package/dist/plugins/ai-sdk/index.js +0 -3
  9. package/dist/plugins/ai-sdk/runtime.js +78 -13
  10. package/dist/plugins/ai-sdk/system-prompt.js +18 -3
  11. package/dist/plugins/delegation/index.js +7 -46
  12. package/dist/plugins/memory/index.js +71 -0
  13. package/dist/plugins/storage-tools/index.js +2 -11
  14. package/dist/plugins/todo/index.js +54 -0
  15. package/dist/plugins/workflow/index.js +65 -0
  16. package/dist/registry/plugins.js +4 -2
  17. package/dist/services/memory.js +152 -0
  18. package/dist/services/storage.js +9 -31
  19. package/dist/workflow/service.js +106 -0
  20. package/dist/workflow/types.js +3 -0
  21. package/docs/agents.md +15 -1
  22. package/docs/plugins.md +0 -1
  23. package/package.json +1 -1
  24. package/src/app/cli.ts +1 -1
  25. package/src/app/server.ts +3 -4
  26. package/src/app/types.ts +140 -45
  27. package/src/bus/plugin.ts +0 -2
  28. package/src/bus/services.ts +258 -17
  29. package/src/bus/types.ts +13 -4
  30. package/src/harness/context.ts +233 -37
  31. package/src/harness/queue-processor.ts +54 -143
  32. package/src/harness/runtime-factory.ts +11 -7
  33. package/src/harness/todo-advance.ts +128 -0
  34. package/src/plugins/ai-sdk/index.ts +0 -3
  35. package/src/plugins/ai-sdk/runtime.ts +356 -298
  36. package/src/plugins/ai-sdk/system-prompt.ts +18 -4
  37. package/src/plugins/delegation/index.ts +7 -50
  38. package/src/plugins/memory/index.ts +85 -0
  39. package/src/plugins/storage-tools/index.ts +8 -19
  40. package/src/plugins/todo/index.ts +64 -0
  41. package/src/registry/plugins.ts +4 -3
  42. package/src/services/memory.ts +213 -0
  43. package/src/services/storage.ts +9 -49
package/dist/app/cli.js CHANGED
@@ -16,7 +16,7 @@ function checkNodeVersion() {
16
16
  }
17
17
  }
18
18
  checkNodeVersion();
19
- program.name('openbot').description('OpenBot CLI').version('0.2.14');
19
+ program.name('openbot').description('OpenBot CLI').version('0.3.1');
20
20
  program
21
21
  .command('start')
22
22
  .description('Start the OpenBot harness')
@@ -236,9 +236,6 @@ export async function startServer(options = {}) {
236
236
  });
237
237
  app.listen(PORT, () => {
238
238
  console.log(`\x1b[32mOpenBot server listening at http://localhost:${PORT}\x1b[0m`);
239
- console.log(` - Health endpoint: GET /health`);
240
- console.log(` - Events endpoint: GET /api/events (SSE)`);
241
- console.log(` - Publish endpoint: POST /api/publish`);
242
- console.log(` - State endpoint: GET /api/state`);
239
+ console.log(`🌐 Visit \x1b[96m\x1b[1mhttps://openbot.one\x1b[0m to connect to this runtime and manage everything from there. ✨`);
243
240
  });
244
241
  }
@@ -1,6 +1,49 @@
1
1
  import { DEFAULT_MARKETPLACE_REGISTRY_URL, loadConfig } from '../app/config.js';
2
2
  import { storageService } from '../services/storage.js';
3
3
  import { pluginService } from '../services/plugins.js';
4
+ const readTodos = (state) => {
5
+ const raw = state.threadDetails?.state?.todos;
6
+ return Array.isArray(raw) ? raw : [];
7
+ };
8
+ let todoCounter = 0;
9
+ const newTodoId = (now, idx) => `todo_${now.toString(36)}_${(todoCounter++).toString(36)}_${idx}`;
10
+ async function persistTodos(storage, state, todos) {
11
+ if (!state.threadId)
12
+ throw new Error('No active thread');
13
+ await storage.patchThreadState({
14
+ channelId: state.channelId,
15
+ threadId: state.threadId,
16
+ state: { todos },
17
+ });
18
+ state.threadDetails = await storage.getThreadDetails({
19
+ channelId: state.channelId,
20
+ threadId: state.threadId,
21
+ });
22
+ }
23
+ /**
24
+ * Resolve a scope alias to a concrete scope string. Aliases let tools accept
25
+ * `agent`/`channel`/`global` without knowing the active ids; the bus rewrites
26
+ * them using `context.state`.
27
+ */
28
+ function resolveMemoryScope(alias, state) {
29
+ switch (alias) {
30
+ case 'agent':
31
+ return `agent:${state.agentId}`;
32
+ case 'channel':
33
+ return `channel:${state.channelId}`;
34
+ case 'global':
35
+ case undefined:
36
+ return 'global';
37
+ default:
38
+ return 'global';
39
+ }
40
+ }
41
+ function resolveMemoryScopeFilter(alias, state) {
42
+ if (alias === 'all' || alias === undefined) {
43
+ return ['global', `agent:${state.agentId}`, `channel:${state.channelId}`];
44
+ }
45
+ return [resolveMemoryScope(alias, state)];
46
+ }
4
47
  const DEFAULT_MARKETPLACE_AGENTS = [
5
48
  {
6
49
  id: 'researcher',
@@ -90,7 +133,7 @@ export const busServicesPlugin = (options) => (builder) => {
90
133
  builder.on('action:create_thread', async function* (event, context) {
91
134
  const threadId = event.meta?.threadId;
92
135
  const channelId = context.state.channelId;
93
- const { threadTitle, spec, initialState } = event.data;
136
+ const { threadTitle, initialState } = event.data;
94
137
  if (!threadId) {
95
138
  console.warn('[bus] Cannot create thread: meta.threadId is missing');
96
139
  return;
@@ -102,7 +145,6 @@ export const busServicesPlugin = (options) => (builder) => {
102
145
  channelId,
103
146
  threadId,
104
147
  threadTitle,
105
- spec,
106
148
  initialState: initialState || {},
107
149
  });
108
150
  context.state.threadDetails = await storage.getThreadDetails({
@@ -117,17 +159,19 @@ export const busServicesPlugin = (options) => (builder) => {
117
159
  yield {
118
160
  type: 'action:create_thread:result',
119
161
  data: { success: true, threadId, threadTitle },
120
- meta: { threadId },
162
+ meta: { ...(event.meta || {}), threadId, agentId: context.state.agentId },
121
163
  };
122
164
  });
123
165
  builder.on('action:create_channel', async function* (event, context) {
124
166
  const { channelId, spec, initialState, cwd } = event.data;
125
167
  const rawChannelId = (channelId || '').trim();
126
168
  const channelSpec = typeof spec === 'string' ? spec : '';
169
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
127
170
  if (!rawChannelId) {
128
171
  yield {
129
172
  type: 'action:create_channel:result',
130
173
  data: { success: false, channelId: '', channelUrl: '' },
174
+ meta: resultMeta,
131
175
  };
132
176
  return;
133
177
  }
@@ -142,30 +186,31 @@ export const busServicesPlugin = (options) => (builder) => {
142
186
  yield {
143
187
  type: 'action:create_channel:result',
144
188
  data: { success: true, channelId: rawChannelId, channelUrl },
189
+ meta: resultMeta,
145
190
  };
146
191
  yield {
147
192
  type: 'agent:output',
148
193
  data: { content: `Created channel \`${rawChannelId}\`.` },
149
- meta: {
150
- ...(event.meta || {}),
151
- agentId: context.state.agentId,
152
- },
194
+ meta: resultMeta,
153
195
  };
154
196
  }
155
197
  catch {
156
198
  yield {
157
199
  type: 'action:create_channel:result',
158
200
  data: { success: false, channelId: rawChannelId, channelUrl },
201
+ meta: resultMeta,
159
202
  };
160
203
  }
161
204
  });
162
205
  builder.on('action:update_channel', async function* (event, context) {
163
206
  const data = (event.data || {});
164
207
  const targetChannelId = (data.channelId || context.state.channelId || '').trim();
208
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
165
209
  if (!targetChannelId) {
166
210
  yield {
167
211
  type: 'action:update_channel:result',
168
212
  data: { success: false, channelId: '', updatedFields: [] },
213
+ meta: resultMeta,
169
214
  };
170
215
  return;
171
216
  }
@@ -191,17 +236,20 @@ export const busServicesPlugin = (options) => (builder) => {
191
236
  yield {
192
237
  type: 'action:update_channel:result',
193
238
  data: { success: true, channelId: targetChannelId, updatedFields },
239
+ meta: resultMeta,
194
240
  };
195
241
  }
196
242
  catch {
197
243
  yield {
198
244
  type: 'action:update_channel:result',
199
245
  data: { success: false, channelId: targetChannelId, updatedFields },
246
+ meta: resultMeta,
200
247
  };
201
248
  }
202
249
  });
203
250
  builder.on('action:patch_channel_details', async function* (event, context) {
204
251
  const updatedFields = [];
252
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
205
253
  try {
206
254
  if (event.data.state !== undefined) {
207
255
  await storage.patchChannelState({
@@ -230,17 +278,20 @@ export const busServicesPlugin = (options) => (builder) => {
230
278
  yield {
231
279
  type: 'action:patch_channel_details:result',
232
280
  data: { success: true, updatedFields },
281
+ meta: resultMeta,
233
282
  };
234
283
  }
235
284
  catch {
236
285
  yield {
237
286
  type: 'action:patch_channel_details:result',
238
287
  data: { success: false, updatedFields },
288
+ meta: resultMeta,
239
289
  };
240
290
  }
241
291
  });
242
292
  builder.on('action:patch_thread_details', async function* (event, context) {
243
293
  const updatedFields = [];
294
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
244
295
  try {
245
296
  if (!context.state.threadId) {
246
297
  throw new Error('Missing threadId in state for patch_thread_details');
@@ -253,14 +304,6 @@ export const busServicesPlugin = (options) => (builder) => {
253
304
  });
254
305
  updatedFields.push('state');
255
306
  }
256
- if (typeof event.data.spec === 'string') {
257
- await storage.patchThreadSpec({
258
- channelId: context.state.channelId,
259
- threadId: context.state.threadId,
260
- spec: event.data.spec,
261
- });
262
- updatedFields.push('spec');
263
- }
264
307
  context.state.threadDetails = await storage.getThreadDetails({
265
308
  channelId: context.state.channelId,
266
309
  threadId: context.state.threadId,
@@ -268,12 +311,100 @@ export const busServicesPlugin = (options) => (builder) => {
268
311
  yield {
269
312
  type: 'action:patch_thread_details:result',
270
313
  data: { success: true, updatedFields },
314
+ meta: resultMeta,
271
315
  };
272
316
  }
273
317
  catch {
274
318
  yield {
275
319
  type: 'action:patch_thread_details:result',
276
320
  data: { success: false, updatedFields },
321
+ meta: resultMeta,
322
+ };
323
+ }
324
+ });
325
+ builder.on('action:todo_write', async function* (event, context) {
326
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
327
+ try {
328
+ if (!context.state.threadId) {
329
+ throw new Error('todo_write requires an active thread');
330
+ }
331
+ const existing = readTodos(context.state);
332
+ const byId = new Map(existing.map((t) => [t.id, t]));
333
+ const now = Date.now();
334
+ const author = context.state.agentId || 'system';
335
+ const inputs = event.data.todos || [];
336
+ const next = inputs.map((raw, idx) => {
337
+ const prior = raw.id ? byId.get(raw.id) : undefined;
338
+ return {
339
+ id: prior?.id || raw.id || newTodoId(now, idx),
340
+ content: raw.content,
341
+ status: raw.status || prior?.status || 'pending',
342
+ assignee: raw.assignee ?? prior?.assignee,
343
+ createdBy: prior?.createdBy || author,
344
+ createdAt: prior?.createdAt || now,
345
+ updatedAt: now,
346
+ ...(prior?.result !== undefined ? { result: prior.result } : {}),
347
+ };
348
+ });
349
+ await persistTodos(storage, context.state, next);
350
+ yield {
351
+ type: 'action:todo_write:result',
352
+ data: { success: true, todos: next },
353
+ meta: resultMeta,
354
+ };
355
+ }
356
+ catch (error) {
357
+ yield {
358
+ type: 'action:todo_write:result',
359
+ data: {
360
+ success: false,
361
+ todos: readTodos(context.state),
362
+ error: error instanceof Error ? error.message : 'Unknown error',
363
+ },
364
+ meta: resultMeta,
365
+ };
366
+ }
367
+ });
368
+ builder.on('action:todo_update', async function* (event, context) {
369
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
370
+ const patch = event.data;
371
+ try {
372
+ if (!context.state.threadId) {
373
+ throw new Error('todo_update requires an active thread');
374
+ }
375
+ const existing = readTodos(context.state);
376
+ const idx = existing.findIndex((t) => t.id === patch.id);
377
+ if (idx === -1) {
378
+ throw new Error(`Todo "${patch.id}" not found`);
379
+ }
380
+ const now = Date.now();
381
+ const updated = {
382
+ ...existing[idx],
383
+ ...(patch.content !== undefined ? { content: patch.content } : {}),
384
+ ...(patch.status !== undefined ? { status: patch.status } : {}),
385
+ ...(patch.assignee !== undefined
386
+ ? { assignee: patch.assignee === '' ? undefined : patch.assignee }
387
+ : {}),
388
+ updatedAt: now,
389
+ };
390
+ const next = [...existing];
391
+ next[idx] = updated;
392
+ await persistTodos(storage, context.state, next);
393
+ yield {
394
+ type: 'action:todo_update:result',
395
+ data: { success: true, todo: updated, todos: next },
396
+ meta: resultMeta,
397
+ };
398
+ }
399
+ catch (error) {
400
+ yield {
401
+ type: 'action:todo_update:result',
402
+ data: {
403
+ success: false,
404
+ todos: readTodos(context.state),
405
+ error: error instanceof Error ? error.message : 'Unknown error',
406
+ },
407
+ meta: resultMeta,
277
408
  };
278
409
  }
279
410
  });
@@ -549,6 +680,82 @@ export const busServicesPlugin = (options) => (builder) => {
549
680
  data: { success: true, agents },
550
681
  };
551
682
  });
683
+ builder.on('action:remember', async function* (event, context) {
684
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
685
+ try {
686
+ const { content, scope, tags } = event.data;
687
+ const record = await storage.appendMemory({
688
+ scope: resolveMemoryScope(scope, context.state),
689
+ content,
690
+ tags,
691
+ });
692
+ yield {
693
+ type: 'action:remember:result',
694
+ data: { success: true, record },
695
+ meta: resultMeta,
696
+ };
697
+ }
698
+ catch (error) {
699
+ yield {
700
+ type: 'action:remember:result',
701
+ data: {
702
+ success: false,
703
+ error: error instanceof Error ? error.message : 'Unknown error',
704
+ },
705
+ meta: resultMeta,
706
+ };
707
+ }
708
+ });
709
+ builder.on('action:recall', async function* (event, context) {
710
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
711
+ try {
712
+ const { query, tag, scope, limit } = event.data;
713
+ const records = await storage.listMemories({
714
+ scopes: resolveMemoryScopeFilter(scope, context.state),
715
+ query,
716
+ tag,
717
+ limit,
718
+ });
719
+ yield {
720
+ type: 'action:recall:result',
721
+ data: { success: true, records },
722
+ meta: resultMeta,
723
+ };
724
+ }
725
+ catch (error) {
726
+ yield {
727
+ type: 'action:recall:result',
728
+ data: {
729
+ success: false,
730
+ records: [],
731
+ error: error instanceof Error ? error.message : 'Unknown error',
732
+ },
733
+ meta: resultMeta,
734
+ };
735
+ }
736
+ });
737
+ builder.on('action:forget', async function* (event, context) {
738
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
739
+ try {
740
+ const deleted = await storage.deleteMemory({ id: event.data.id });
741
+ yield {
742
+ type: 'action:forget:result',
743
+ data: { success: true, deleted },
744
+ meta: resultMeta,
745
+ };
746
+ }
747
+ catch (error) {
748
+ yield {
749
+ type: 'action:forget:result',
750
+ data: {
751
+ success: false,
752
+ deleted: false,
753
+ error: error instanceof Error ? error.message : 'Unknown error',
754
+ },
755
+ meta: resultMeta,
756
+ };
757
+ }
758
+ });
552
759
  builder.on('action:agent:install', async function* (event) {
553
760
  try {
554
761
  const { agentId, name, description, instructions, plugins } = event.data;