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.
Files changed (96) hide show
  1. package/README.md +15 -16
  2. package/dist/app/agent-ids.js +4 -0
  3. package/dist/app/cli.js +1 -1
  4. package/dist/app/config.js +0 -19
  5. package/dist/app/server.js +8 -14
  6. package/dist/bus/services.js +34 -124
  7. package/dist/harness/agent-invoke-run.js +44 -0
  8. package/dist/harness/agent-turn.js +99 -0
  9. package/dist/harness/channel-participants.js +40 -0
  10. package/dist/harness/constants.js +2 -0
  11. package/dist/harness/context-meter.js +97 -0
  12. package/dist/harness/context.js +95 -47
  13. package/dist/harness/dispatch.js +144 -0
  14. package/dist/harness/dispatcher.js +45 -156
  15. package/dist/harness/history.js +177 -0
  16. package/dist/harness/index.js +91 -0
  17. package/dist/harness/orchestration.js +88 -0
  18. package/dist/harness/participants.js +22 -0
  19. package/dist/harness/run-harness.js +154 -0
  20. package/dist/harness/run.js +98 -0
  21. package/dist/harness/runtime-factory.js +0 -34
  22. package/dist/harness/runtime.js +57 -0
  23. package/dist/harness/todo-dispatch.js +51 -0
  24. package/dist/harness/todos.js +5 -0
  25. package/dist/harness/turn.js +79 -0
  26. package/dist/plugins/approval/index.js +105 -149
  27. package/dist/plugins/delegation/index.js +119 -32
  28. package/dist/plugins/memory/index.js +103 -14
  29. package/dist/plugins/memory/service.js +152 -0
  30. package/dist/plugins/openbot/context.js +80 -0
  31. package/dist/plugins/openbot/history.js +98 -0
  32. package/dist/plugins/openbot/index.js +31 -0
  33. package/dist/plugins/openbot/runtime.js +317 -0
  34. package/dist/plugins/openbot/system-prompt.js +5 -0
  35. package/dist/plugins/plugin-manager/index.js +105 -0
  36. package/dist/plugins/storage/index.js +573 -0
  37. package/dist/plugins/storage/service.js +1159 -0
  38. package/dist/plugins/storage-tools/index.js +2 -2
  39. package/dist/plugins/thread-namer/index.js +72 -0
  40. package/dist/plugins/thread-naming/generate-title.js +44 -0
  41. package/dist/plugins/thread-naming/index.js +103 -0
  42. package/dist/plugins/threads/index.js +114 -0
  43. package/dist/plugins/todo/index.js +24 -25
  44. package/dist/plugins/ui/index.js +2 -32
  45. package/dist/registry/plugins.js +3 -9
  46. package/dist/services/plugins/domain.js +1 -0
  47. package/dist/services/plugins/plugin-cache.js +9 -0
  48. package/dist/services/plugins/registry.js +110 -0
  49. package/dist/services/plugins/service.js +177 -0
  50. package/dist/services/plugins/types.js +1 -0
  51. package/dist/services/process.js +29 -0
  52. package/dist/services/storage.js +11 -10
  53. package/dist/services/thread-naming.js +81 -0
  54. package/docs/agents.md +16 -10
  55. package/docs/architecture.md +2 -2
  56. package/docs/plugins.md +6 -15
  57. package/docs/templates/AGENT.example.md +7 -13
  58. package/package.json +1 -2
  59. package/src/app/agent-ids.ts +5 -0
  60. package/src/app/cli.ts +1 -1
  61. package/src/app/config.ts +1 -31
  62. package/src/app/server.ts +8 -16
  63. package/src/app/types.ts +63 -189
  64. package/src/harness/index.ts +145 -0
  65. package/src/plugins/approval/index.ts +91 -189
  66. package/src/plugins/delegation/index.ts +136 -39
  67. package/src/plugins/memory/index.ts +112 -15
  68. package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
  69. package/src/plugins/openbot/context.ts +91 -0
  70. package/src/plugins/openbot/history.ts +107 -0
  71. package/src/plugins/openbot/index.ts +37 -0
  72. package/src/plugins/openbot/runtime.ts +384 -0
  73. package/src/plugins/openbot/system-prompt.ts +7 -0
  74. package/src/plugins/plugin-manager/index.ts +122 -0
  75. package/src/plugins/shell/index.ts +1 -1
  76. package/src/plugins/storage/index.ts +633 -0
  77. package/src/{services/storage.ts → plugins/storage/service.ts} +224 -67
  78. package/src/{bus/types.ts → services/plugins/domain.ts} +16 -7
  79. package/src/services/plugins/plugin-cache.ts +13 -0
  80. package/src/{registry/plugins.ts → services/plugins/registry.ts} +25 -27
  81. package/src/services/{plugins.ts → plugins/service.ts} +96 -2
  82. package/src/{bus/plugin.ts → services/plugins/types.ts} +3 -3
  83. package/src/bus/services.ts +0 -954
  84. package/src/harness/context.ts +0 -365
  85. package/src/harness/dispatcher.ts +0 -379
  86. package/src/harness/mcp.ts +0 -78
  87. package/src/harness/runtime-factory.ts +0 -129
  88. package/src/harness/todo-advance.ts +0 -128
  89. package/src/plugins/ai-sdk/index.ts +0 -41
  90. package/src/plugins/ai-sdk/runtime.ts +0 -468
  91. package/src/plugins/ai-sdk/system-prompt.ts +0 -18
  92. package/src/plugins/mcp/index.ts +0 -128
  93. package/src/plugins/storage-tools/index.ts +0 -90
  94. package/src/plugins/todo/index.ts +0 -64
  95. package/src/plugins/ui/index.ts +0 -227
  96. /package/src/{harness → services}/process.ts +0 -0
@@ -1,365 +0,0 @@
1
- import { OpenBotEvent, OpenBotState, TodoItem } from '../app/types.js';
2
- import { Storage } from '../bus/types.js';
3
-
4
- /**
5
- * Represents a piece of context that can be used in a prompt.
6
- *
7
- * Items flow through the engine in two phases:
8
- * 1. Each registered `ContextProvider` emits zero or more items.
9
- * 2. Each registered `ContextProcessor` may transform / drop / re-rank
10
- * items (e.g. token-budget enforcement).
11
- *
12
- * Higher `priority` items appear first in the assembled prompt and are the
13
- * last to be dropped under budget pressure.
14
- */
15
- export interface ContextItem {
16
- id: string;
17
- type: string;
18
- priority: number;
19
- content: string;
20
- metadata?: Record<string, any>;
21
- }
22
-
23
- export interface ContextProvider {
24
- name: string;
25
- provide(state: OpenBotState, storage?: Storage): Promise<ContextItem[]>;
26
- }
27
-
28
- export interface ContextProcessor {
29
- name: string;
30
- process(items: ContextItem[], state: OpenBotState): Promise<ContextItem[]>;
31
- }
32
-
33
- /**
34
- * Cheap, dependency-free token estimator. Roughly char/4 — fine for budget
35
- * enforcement; can be swapped for a tokenizer-backed implementation later
36
- * without touching providers.
37
- */
38
- export const estimateTokens = (text: string): number =>
39
- Math.ceil((text?.length ?? 0) / 4);
40
-
41
- /**
42
- * Hard cap (in characters) on a single context item. Keeps any one provider
43
- * — typically the recent-events feed — from monopolising the prompt budget.
44
- */
45
- const ITEM_HARD_CHAR_CAP = 6000;
46
-
47
- const truncate = (text: string, maxChars: number): string =>
48
- text.length <= maxChars ? text : `${text.slice(0, maxChars)}\n…[truncated]`;
49
-
50
- export class ContextEngine {
51
- private providers: ContextProvider[] = [];
52
- private processors: ContextProcessor[] = [];
53
-
54
- registerProvider(provider: ContextProvider) {
55
- this.providers.push(provider);
56
- }
57
-
58
- registerProcessor(processor: ContextProcessor) {
59
- this.processors.push(processor);
60
- }
61
-
62
- async buildContext(state: OpenBotState, storage?: Storage): Promise<string> {
63
- let items: ContextItem[] = [];
64
- for (const provider of this.providers) {
65
- try {
66
- const providedItems = await provider.provide(state, storage);
67
- for (const item of providedItems) {
68
- items.push({ ...item, content: truncate(item.content, ITEM_HARD_CHAR_CAP) });
69
- }
70
- } catch (error) {
71
- console.warn(`[ContextEngine] Provider ${provider.name} failed:`, error);
72
- }
73
- }
74
-
75
- for (const processor of this.processors) {
76
- try {
77
- items = await processor.process(items, state);
78
- } catch (error) {
79
- console.warn(`[ContextEngine] Processor ${processor.name} failed:`, error);
80
- }
81
- }
82
-
83
- return items
84
- .sort((a, b) => b.priority - a.priority)
85
- .map((item) => item.content)
86
- .join('\n\n');
87
- }
88
- }
89
-
90
- /**
91
- * Default context engine. Order of providers is by emit order; final ordering
92
- * in the prompt is determined by `priority`. The token-budget processor runs
93
- * last so dropping happens after every provider has contributed.
94
- */
95
- export function createDefaultContextEngine(): ContextEngine {
96
- const engine = new ContextEngine();
97
-
98
- engine.registerProvider(new AgentDetailsProvider());
99
- engine.registerProvider(new ChannelDetailsProvider());
100
- engine.registerProvider(new ThreadDetailsProvider());
101
- engine.registerProvider(new TodoProvider());
102
- engine.registerProvider(new MemoryProvider());
103
- engine.registerProvider(new RecentEventsProvider());
104
-
105
- engine.registerProcessor(new TokenBudgetProcessor());
106
-
107
- return engine;
108
- }
109
-
110
- class AgentDetailsProvider implements ContextProvider {
111
- name = 'agent-details';
112
- async provide(state: OpenBotState): Promise<ContextItem[]> {
113
- if (!state.agentDetails) return [];
114
- const instructions = state.agentDetails.instructions?.trim();
115
- if (!instructions) return [];
116
-
117
- return [{
118
- id: 'agent-details',
119
- type: 'agent',
120
- priority: 100,
121
- content: `# ${state.agentDetails.name}\n\n${instructions}`,
122
- }];
123
- }
124
- }
125
-
126
- class ChannelDetailsProvider implements ContextProvider {
127
- name = 'channel-details';
128
- async provide(state: OpenBotState): Promise<ContextItem[]> {
129
- if (!state.channelDetails) return [];
130
-
131
- const participants = state.channelDetails.participants;
132
- if (!participants?.length) return [];
133
-
134
- const channelLabel =
135
- state.channelDetails.name?.trim() || state.channelDetails.id;
136
- const lines = participants.map((id) => `- \`${id}\``).join('\n');
137
-
138
- return [
139
- {
140
- id: 'channel-details',
141
- type: 'channel',
142
- priority: 80,
143
- content:
144
- `## Channel participants (${channelLabel})\n` +
145
- `Agent ids collaborating in this channel:\n${lines}`,
146
- },
147
- ];
148
- }
149
- }
150
-
151
- class ThreadDetailsProvider implements ContextProvider {
152
- name = 'thread-details';
153
- async provide(state: OpenBotState): Promise<ContextItem[]> {
154
- if (!state.threadDetails) return [];
155
-
156
- // For now, this provider is a placeholder for future state-based assembly.
157
- // It currently only surfaces the thread name to provide basic context.
158
- return [
159
- {
160
- id: 'thread-details',
161
- type: 'thread',
162
- priority: 90,
163
- content: `# Thread you are in: ${state.threadDetails.name}`,
164
- },
165
- ];
166
- }
167
- }
168
-
169
- /**
170
- * Surfaces the shared per-thread todo list. The list lives in
171
- * `threadDetails.state.todos` and is owned by bus services — every agent in
172
- * the thread reads from the same canonical source, which is how multi-agent
173
- * autonomous flows stay coordinated.
174
- */
175
- class TodoProvider implements ContextProvider {
176
- name = 'todos';
177
- async provide(state: OpenBotState): Promise<ContextItem[]> {
178
- const raw = (state.threadDetails?.state as Record<string, unknown> | undefined)?.todos;
179
- const todos: TodoItem[] = Array.isArray(raw) ? (raw as TodoItem[]) : [];
180
- if (todos.length === 0) return [];
181
-
182
- const DISPLAY_RESULT_CAP = 2500;
183
-
184
- const marker: Record<TodoItem['status'], string> = {
185
- pending: '[ ]',
186
- in_progress: '[~]',
187
- done: '[x]',
188
- cancelled: '[-]',
189
- };
190
- const formatted = todos
191
- .map((t) => {
192
- const assignee = t.assignee ? ` @${t.assignee}` : '';
193
- let line = `- ${marker[t.status]} (${t.id})${assignee} ${t.content}`;
194
- if (t.status === 'done' && t.result?.trim()) {
195
- let snippet = t.result.trim();
196
- if (snippet.length > DISPLAY_RESULT_CAP) {
197
- snippet = `${snippet.slice(0, DISPLAY_RESULT_CAP)}…[truncated]`;
198
- }
199
- line += `\n Result: ${snippet}`;
200
- }
201
- return line;
202
- })
203
- .join('\n');
204
-
205
- return [
206
- {
207
- id: 'todos',
208
- type: 'todos',
209
- priority: 92,
210
- content:
211
- `## Shared todo plan (thread state)\n` +
212
- `Orchestrator authors with \`todo_write\`; assignees run one step at a time. ` +
213
- `When an item is \`done\`, its captured output appears below so every agent can see prior steps without relying on merged chat history.\n\n` +
214
- `${formatted}`,
215
- },
216
- ];
217
- }
218
- }
219
-
220
- /**
221
- * Fetches relevant memories (global + active agent + active channel) and
222
- * surfaces them at high priority so the LLM treats them as ground truth
223
- * rather than chat history.
224
- */
225
- class MemoryProvider implements ContextProvider {
226
- name = 'memory';
227
- async provide(state: OpenBotState, storage?: Storage): Promise<ContextItem[]> {
228
- if (!storage?.listMemories) return [];
229
-
230
- try {
231
- const scopes = ['global', `agent:${state.agentId}`];
232
- if (state.channelId) scopes.push(`channel:${state.channelId}`);
233
-
234
- const records = await storage.listMemories({ scopes, limit: 50 });
235
- if (records.length === 0) return [];
236
-
237
- const formatted = records
238
- .map((r) => {
239
- const tags = r.tags?.length ? ` [${r.tags.join(', ')}]` : '';
240
- const scopeLabel = r.scope === 'global' ? 'global' : r.scope;
241
- return `- (${scopeLabel}${tags}) ${r.content}`;
242
- })
243
- .join('\n');
244
-
245
- return [
246
- {
247
- id: 'memory',
248
- type: 'memory',
249
- priority: 95,
250
- content: `## Remembered facts\nTrust these unless the user contradicts them. Use \`forget\` to remove stale ones.\n\n${formatted}`,
251
- },
252
- ];
253
- } catch (error) {
254
- console.warn('[ContextEngine] MemoryProvider failed:', error);
255
- return [];
256
- }
257
- }
258
- }
259
-
260
- /**
261
- * Event types we omit from the recent-events context block. They duplicate
262
- * information already in the conversation history, are infrastructural
263
- * noise, or are too large to be useful as a tail summary.
264
- */
265
- const NOISY_EVENT_PREFIXES = [
266
- 'agent:invoke',
267
- 'agent:output',
268
- 'agent:run',
269
- 'agent:active-runs',
270
- 'client:ui',
271
- 'stream:',
272
- 'action:storage:get-',
273
- 'action:storage:patch-',
274
- ];
275
-
276
- const MAX_RECENT_EVENTS = 20;
277
- const MAX_EVENT_DATA_CHARS = 300;
278
-
279
- const isNoisyEvent = (event: OpenBotEvent): boolean =>
280
- NOISY_EVENT_PREFIXES.some((prefix) => event.type.startsWith(prefix));
281
-
282
- const summarizeEvent = (event: OpenBotEvent): string => {
283
- const data = (event as { data?: unknown }).data;
284
- if (data === undefined) return `- ${event.type}`;
285
- let payload: string;
286
- try {
287
- payload = typeof data === 'string' ? data : JSON.stringify(data);
288
- } catch {
289
- payload = '[unserialisable]';
290
- }
291
- if (payload.length > MAX_EVENT_DATA_CHARS) {
292
- payload = `${payload.slice(0, MAX_EVENT_DATA_CHARS)}…`;
293
- }
294
- return `- ${event.type}: ${payload}`;
295
- };
296
-
297
- class RecentEventsProvider implements ContextProvider {
298
- name = 'recent-events';
299
- async provide(state: OpenBotState, storage?: Storage): Promise<ContextItem[]> {
300
- if (!storage) return [];
301
-
302
- const channelId = state.channelId;
303
- const threadId = state.threadId;
304
-
305
- try {
306
- const events = await storage.getEvents({ channelId, threadId });
307
- const filtered = events.filter((e) => !isNoisyEvent(e));
308
- if (filtered.length === 0) return [];
309
-
310
- const formatted = filtered.slice(-MAX_RECENT_EVENTS).map(summarizeEvent).join('\n');
311
-
312
- return [
313
- {
314
- id: threadId ? 'thread-events' : 'channel-events',
315
- type: 'events',
316
- priority: 70,
317
- content: `## ${threadId ? 'THREAD' : 'CHANNEL'} RECENT ACTIVITIES (events)\n${formatted}`,
318
- },
319
- ];
320
- } catch (error) {
321
- console.warn('[ContextEngine] Failed to fetch events:', error);
322
- return [];
323
- }
324
- }
325
- }
326
-
327
- /**
328
- * Drops the lowest-priority items until the assembled prompt fits within the
329
- * token budget. The first item with priority >= `keepFloor` is always kept,
330
- * so the agent's own instructions can never be evicted. Stable on ties:
331
- * later-emitted items go first.
332
- */
333
- export class TokenBudgetProcessor implements ContextProcessor {
334
- name = 'token-budget';
335
- /** Soft prompt budget in tokens (matches gpt-4o-mini's reasonable system slice). */
336
- static DEFAULT_BUDGET = 8000;
337
- /** Items at or above this priority are never dropped. */
338
- static KEEP_FLOOR = 100;
339
-
340
- constructor(
341
- private budget: number = TokenBudgetProcessor.DEFAULT_BUDGET,
342
- private keepFloor: number = TokenBudgetProcessor.KEEP_FLOOR,
343
- ) {}
344
-
345
- async process(items: ContextItem[]): Promise<ContextItem[]> {
346
- const sorted = [...items].sort((a, b) => b.priority - a.priority);
347
- const out: ContextItem[] = [];
348
- let used = 0;
349
-
350
- for (const item of sorted) {
351
- const cost = estimateTokens(item.content);
352
- if (item.priority >= this.keepFloor) {
353
- out.push(item);
354
- used += cost;
355
- continue;
356
- }
357
- if (used + cost <= this.budget) {
358
- out.push(item);
359
- used += cost;
360
- }
361
- }
362
-
363
- return out;
364
- }
365
- }