openbot 0.2.13 → 0.3.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 (80) hide show
  1. package/dist/agents/openbot/index.js +76 -0
  2. package/dist/agents/openbot/middleware/approval.js +132 -0
  3. package/dist/agents/openbot/runtime.js +289 -0
  4. package/dist/agents/openbot/system-prompt.js +32 -0
  5. package/dist/agents/openbot/tools/delegation.js +78 -0
  6. package/dist/agents/openbot/tools/mcp.js +99 -0
  7. package/dist/agents/openbot/tools/shell.js +91 -0
  8. package/dist/agents/openbot/tools/storage.js +75 -0
  9. package/dist/agents/openbot/tools/ui.js +176 -0
  10. package/dist/agents/system.js +20 -93
  11. package/dist/app/cli.js +1 -1
  12. package/dist/app/config.js +4 -1
  13. package/dist/app/server.js +15 -8
  14. package/dist/bus/agent-package.js +1 -0
  15. package/dist/bus/plugin.js +1 -0
  16. package/dist/bus/services.js +600 -0
  17. package/dist/bus/types.js +1 -0
  18. package/dist/harness/context.js +131 -0
  19. package/dist/harness/event-normalizer.js +59 -0
  20. package/dist/harness/orchestrator.js +27 -227
  21. package/dist/harness/process.js +25 -3
  22. package/dist/harness/queue-processor.js +227 -0
  23. package/dist/harness/runtime-factory.js +103 -0
  24. package/dist/plugins/ai-sdk/index.js +37 -0
  25. package/dist/plugins/ai-sdk/runtime.js +330 -0
  26. package/dist/plugins/ai-sdk/system-prompt.js +3 -0
  27. package/dist/plugins/ai-sdk.js +277 -87
  28. package/dist/plugins/approval/index.js +159 -0
  29. package/dist/plugins/approval.js +163 -0
  30. package/dist/plugins/delegation/index.js +79 -0
  31. package/dist/plugins/delegation.js +67 -11
  32. package/dist/plugins/mcp/index.js +108 -0
  33. package/dist/plugins/shell/index.js +99 -0
  34. package/dist/plugins/shell.js +123 -0
  35. package/dist/plugins/storage-tools/index.js +85 -0
  36. package/dist/plugins/storage.js +240 -5
  37. package/dist/plugins/ui/index.js +184 -0
  38. package/dist/plugins/ui.js +185 -21
  39. package/dist/registry/agents.js +138 -0
  40. package/dist/registry/plugins.js +91 -50
  41. package/dist/services/agent-packages.js +103 -0
  42. package/dist/services/plugins.js +98 -0
  43. package/dist/services/storage.js +360 -94
  44. package/docs/agents.md +39 -66
  45. package/docs/architecture.md +1 -1
  46. package/docs/plugins.md +70 -58
  47. package/docs/templates/AGENT.example.md +57 -0
  48. package/package.json +3 -2
  49. package/src/app/cli.ts +1 -1
  50. package/src/app/config.ts +14 -4
  51. package/src/app/server.ts +23 -10
  52. package/src/app/types.ts +385 -16
  53. package/src/assets/icon.svg +4 -1
  54. package/src/bus/plugin.ts +67 -0
  55. package/src/bus/services.ts +666 -0
  56. package/src/bus/types.ts +147 -0
  57. package/src/harness/context.ts +160 -0
  58. package/src/harness/event-normalizer.ts +82 -0
  59. package/src/harness/orchestrator.ts +35 -273
  60. package/src/harness/process.ts +28 -4
  61. package/src/harness/queue-processor.ts +309 -0
  62. package/src/harness/runtime-factory.ts +125 -0
  63. package/src/plugins/ai-sdk/index.ts +44 -0
  64. package/src/plugins/ai-sdk/runtime.ts +410 -0
  65. package/src/plugins/ai-sdk/system-prompt.ts +4 -0
  66. package/src/plugins/approval/index.ts +228 -0
  67. package/src/plugins/delegation/index.ts +94 -0
  68. package/src/plugins/mcp/index.ts +128 -0
  69. package/src/plugins/shell/index.ts +123 -0
  70. package/src/plugins/storage-tools/index.ts +101 -0
  71. package/src/plugins/ui/index.ts +227 -0
  72. package/src/registry/plugins.ts +106 -55
  73. package/src/services/plugins.ts +133 -0
  74. package/src/services/storage.ts +465 -137
  75. package/src/agents/system.ts +0 -112
  76. package/src/plugins/ai-sdk.ts +0 -197
  77. package/src/plugins/delegation.ts +0 -60
  78. package/src/plugins/mcp.ts +0 -154
  79. package/src/plugins/storage.ts +0 -725
  80. package/src/plugins/ui.ts +0 -57
@@ -0,0 +1,227 @@
1
+ import { ensureEventId } from '../app/utils.js';
2
+ import { storageService } from '../services/storage.js';
3
+ export class QueueProcessor {
4
+ constructor(options) {
5
+ this.options = options;
6
+ this.currentQueue = [];
7
+ this.MAX_ITERATIONS = 20;
8
+ this.currentThreadId = options.threadId;
9
+ }
10
+ enqueue(item) {
11
+ this.currentQueue.push(item);
12
+ }
13
+ async run() {
14
+ let iterations = 0;
15
+ while (this.currentQueue.length > 0 && iterations < this.MAX_ITERATIONS) {
16
+ iterations++;
17
+ // Group by agentId to avoid parallel state corruption for the same agent.
18
+ const groups = new Map();
19
+ for (const item of this.currentQueue) {
20
+ const list = groups.get(item.agentId) || [];
21
+ list.push(item);
22
+ groups.set(item.agentId, list);
23
+ }
24
+ const nextQueue = [];
25
+ // Run each agent group in parallel
26
+ await Promise.all(Array.from(groups.entries()).map(async ([agentId, items]) => {
27
+ // Run items for the SAME agent sequentially to preserve event order and state consistency.
28
+ for (const item of items) {
29
+ const { event: currentEvent, delegationContext } = item;
30
+ // Track delegation/handoff requests queued in this step to avoid accidental duplicates.
31
+ const queuedRequestKeys = new Set();
32
+ const queuedItems = [];
33
+ const runOutputs = [];
34
+ const runOnEvent = async (chunk, state) => {
35
+ // 0. Filter out echoed input events to prevent duplication in the UI/storage
36
+ if (chunk.type === currentEvent.type && chunk.id === currentEvent.id) {
37
+ return false;
38
+ }
39
+ // 1. Detect if a new thread was created and update the context for the rest of the loop
40
+ if (chunk.type === 'action:create_thread:result' && chunk.data.success) {
41
+ this.currentThreadId = chunk.data.threadId || this.currentThreadId;
42
+ }
43
+ // 2. Internal routing (handoff/delegation requests are internal — not forwarded)
44
+ if (chunk.type === 'handoff:request' || chunk.type === 'delegation:request') {
45
+ const isHandoff = chunk.type === 'handoff:request';
46
+ const request = isHandoff
47
+ ? chunk
48
+ : chunk;
49
+ const targetAgentId = request.data?.agentId;
50
+ const toolCallId = typeof request.meta?.toolCallId === 'string'
51
+ ? request.meta.toolCallId
52
+ : undefined;
53
+ const requestKey = isHandoff
54
+ ? `handoff:${targetAgentId}:${request.data?.content ?? ''}`
55
+ : `delegate:${toolCallId ?? 'missing'}:${targetAgentId}:${request.data?.content ?? ''}`;
56
+ if (targetAgentId &&
57
+ targetAgentId !== agentId &&
58
+ !queuedRequestKeys.has(requestKey)) {
59
+ queuedRequestKeys.add(requestKey);
60
+ const targetEvent = ensureEventId({
61
+ type: 'agent:invoke',
62
+ data: {
63
+ role: 'user',
64
+ content: request.data.content,
65
+ },
66
+ meta: {
67
+ ...(request.meta || {}),
68
+ threadId: this.currentThreadId,
69
+ },
70
+ });
71
+ if (isHandoff) {
72
+ queuedItems.push({ agentId: targetAgentId, event: targetEvent });
73
+ }
74
+ else {
75
+ if (!toolCallId) {
76
+ // Emit error output (this triggers run start if not already started)
77
+ await runOnEvent(ensureEventId({
78
+ type: 'agent:output',
79
+ data: {
80
+ content: 'Delegation request ignored: missing toolCallId. Please retry delegation.',
81
+ },
82
+ meta: {
83
+ agentId,
84
+ threadId: this.currentThreadId,
85
+ },
86
+ }), state);
87
+ return true;
88
+ }
89
+ const parentAgentId = typeof request.meta?.parentAgentId === 'string'
90
+ ? request.meta.parentAgentId
91
+ : agentId;
92
+ const delegationWidgetId = typeof request.meta?.delegationWidgetId === 'string'
93
+ ? request.meta.delegationWidgetId
94
+ : undefined;
95
+ queuedItems.push({
96
+ agentId: targetAgentId,
97
+ event: targetEvent,
98
+ delegationContext: {
99
+ parentAgentId,
100
+ toolCallId,
101
+ delegationWidgetId,
102
+ },
103
+ });
104
+ }
105
+ }
106
+ return false;
107
+ }
108
+ if (chunk.type === 'agent:output') {
109
+ const content = chunk.data?.content;
110
+ if (typeof content === 'string' && content.trim().length > 0) {
111
+ runOutputs.push(content.trim());
112
+ }
113
+ }
114
+ // For delegate mode, child agent execution is internal:
115
+ // capture outputs for parent tool result, but don't stream child events to clients/storage.
116
+ if (delegationContext) {
117
+ return false;
118
+ }
119
+ // If we get here, the event is accepted and should be emitted.
120
+ await this.options.onEvent(chunk, state);
121
+ return true;
122
+ };
123
+ const startState = await storageService.getOpenBotState({
124
+ runId: this.options.runId,
125
+ agentId,
126
+ channelId: this.options.channelId,
127
+ threadId: this.currentThreadId,
128
+ event: currentEvent,
129
+ });
130
+ await this.options.onEvent({
131
+ type: 'agent:run:start',
132
+ data: {
133
+ runId: this.options.runId,
134
+ agentId,
135
+ channelId: this.options.channelId,
136
+ threadId: this.currentThreadId
137
+ },
138
+ }, startState);
139
+ try {
140
+ await this.options.executeAgent({
141
+ runId: this.options.runId,
142
+ agentId,
143
+ event: currentEvent,
144
+ channelId: this.options.channelId,
145
+ threadId: this.currentThreadId,
146
+ onEvent: runOnEvent,
147
+ });
148
+ }
149
+ finally {
150
+ const endState = await storageService.getOpenBotState({
151
+ runId: this.options.runId,
152
+ agentId,
153
+ channelId: this.options.channelId,
154
+ threadId: this.currentThreadId,
155
+ event: currentEvent,
156
+ });
157
+ await this.options.onEvent({
158
+ type: 'agent:run:end',
159
+ data: {
160
+ runId: this.options.runId,
161
+ agentId,
162
+ channelId: this.options.channelId,
163
+ threadId: this.currentThreadId
164
+ },
165
+ }, endState);
166
+ }
167
+ if (delegationContext) {
168
+ const summary = runOutputs.length > 0
169
+ ? runOutputs.join('\n\n').slice(0, 4000)
170
+ : `Delegated agent "${agentId}" completed with no textual output.`;
171
+ const delegateResultEvent = ensureEventId({
172
+ type: 'action:delegate:result',
173
+ data: {
174
+ success: true,
175
+ agentId,
176
+ summary,
177
+ },
178
+ meta: {
179
+ toolCallId: delegationContext.toolCallId,
180
+ agentId: delegationContext.parentAgentId,
181
+ threadId: this.currentThreadId,
182
+ },
183
+ });
184
+ if (delegationContext.delegationWidgetId) {
185
+ await this.options.onEvent(ensureEventId({
186
+ type: 'client:ui:widget',
187
+ data: {
188
+ kind: 'message',
189
+ widgetId: delegationContext.delegationWidgetId,
190
+ title: `Delegation complete: ${agentId}`,
191
+ body: runOutputs.length > 0
192
+ ? 'Delegated task finished. Parent agent is preparing final response.'
193
+ : 'Delegated task finished with no textual output. Parent agent will continue.',
194
+ state: 'submitted',
195
+ metadata: {
196
+ type: 'delegation:status',
197
+ phase: 'completed',
198
+ delegatedAgentId: agentId,
199
+ },
200
+ },
201
+ meta: {
202
+ agentId: delegationContext.parentAgentId,
203
+ threadId: this.currentThreadId,
204
+ },
205
+ }), await storageService.getOpenBotState({
206
+ runId: this.options.runId,
207
+ agentId: delegationContext.parentAgentId,
208
+ channelId: this.options.channelId,
209
+ threadId: this.currentThreadId,
210
+ event: delegateResultEvent,
211
+ }));
212
+ }
213
+ nextQueue.push({
214
+ agentId: delegationContext.parentAgentId,
215
+ event: delegateResultEvent,
216
+ });
217
+ }
218
+ nextQueue.push(...queuedItems);
219
+ }
220
+ }));
221
+ this.currentQueue = nextQueue;
222
+ }
223
+ if (iterations >= this.MAX_ITERATIONS) {
224
+ console.warn(`[orchestrator] Reached MAX_ITERATIONS (${this.MAX_ITERATIONS}). Stopping execution.`);
225
+ }
226
+ }
227
+ }
@@ -0,0 +1,103 @@
1
+ import { melony } from 'melony';
2
+ import { resolvePlugin } from '../registry/plugins.js';
3
+ import { storageService } from '../services/storage.js';
4
+ import { busServicesPlugin } from '../bus/services.js';
5
+ /**
6
+ * Enhances the agent's instructions with a list of other available agents the
7
+ * orchestrator can hand off / delegate to. Agents that include the
8
+ * `delegation` plugin will surface peers; agents without it can ignore this.
9
+ */
10
+ export async function enhanceInstructions(state) {
11
+ const { agentId, agentDetails } = state;
12
+ if (!agentDetails)
13
+ return;
14
+ try {
15
+ const agents = await storageService.getAgents();
16
+ const otherAgents = agents.filter((a) => a.id !== agentId);
17
+ if (otherAgents.length === 0)
18
+ return;
19
+ const agentsList = otherAgents
20
+ .map((a) => `- **${a.id}**${a.description ? `: ${a.description}` : ''}`)
21
+ .join('\n');
22
+ const header = '### Available Agents for Handoff/Delegation:';
23
+ if (!agentDetails.instructions.includes(header)) {
24
+ agentDetails.instructions +=
25
+ `\n\n${header}\n${agentsList}\n\n` +
26
+ 'Use `handoff` to transfer control to another agent. ' +
27
+ 'Use `delegate` when you need a sub-result from another agent and want to continue after it returns.';
28
+ }
29
+ }
30
+ catch (error) {
31
+ console.warn('[agent] Failed to enhance instructions', error);
32
+ }
33
+ }
34
+ const composeMelonyPlugin = (...plugins) => {
35
+ return (builder) => {
36
+ for (const plugin of plugins) {
37
+ plugin(builder);
38
+ }
39
+ };
40
+ };
41
+ /**
42
+ * Build the Melony runtime that drives a single agent run on the OpenBot bus.
43
+ *
44
+ * The runtime always wires:
45
+ * 1. `busServicesPlugin` — bus-level services (storage, channels, threads,
46
+ * plugin install/marketplace) shared by every agent.
47
+ * 2. Every Plugin referenced by the agent's `plugins[]` frontmatter, in
48
+ * order. Tool definitions from each plugin are merged into a single map
49
+ * and passed to every plugin via `PluginContext.tools`. Runtime plugins
50
+ * (those that handle `agent:invoke`) consume the merged map; tool plugins
51
+ * ignore it.
52
+ *
53
+ * Tool name collisions across plugins log a warning; the first plugin wins.
54
+ */
55
+ export async function createAgentRuntime(state) {
56
+ await enhanceInstructions(state);
57
+ const runtime = melony({
58
+ initialState: state,
59
+ });
60
+ runtime.use(busServicesPlugin({ storage: storageService }));
61
+ const refs = state.agentDetails?.pluginRefs || [];
62
+ if (refs.length === 0) {
63
+ console.warn(`[agent] Agent "${state.agentId}" has no plugins; only bus services will be active.`);
64
+ return runtime.build();
65
+ }
66
+ // Resolve all plugins first so we can merge tool definitions before factory calls.
67
+ const resolved = [];
68
+ for (const ref of refs) {
69
+ const plugin = await resolvePlugin(ref.id);
70
+ if (!plugin) {
71
+ console.warn(`[agent] Plugin "${ref.id}" for agent "${state.agentId}" could not be resolved.`);
72
+ continue;
73
+ }
74
+ resolved.push({ ref, plugin });
75
+ }
76
+ // Merge tool definitions; first plugin wins on collision.
77
+ const tools = {};
78
+ for (const { plugin } of resolved) {
79
+ if (!plugin.toolDefinitions)
80
+ continue;
81
+ for (const [name, def] of Object.entries(plugin.toolDefinitions)) {
82
+ if (tools[name]) {
83
+ console.warn(`[agent] Tool name collision for "${name}" while loading plugin "${plugin.id}"; keeping first registration.`);
84
+ continue;
85
+ }
86
+ tools[name] = def;
87
+ }
88
+ }
89
+ // Compose all plugin factories with the shared context.
90
+ const pluginPlugins = [];
91
+ for (const { ref, plugin } of resolved) {
92
+ const context = {
93
+ agentId: state.agentId,
94
+ agentDetails: state.agentDetails,
95
+ config: ref.config || {},
96
+ storage: storageService,
97
+ tools,
98
+ };
99
+ pluginPlugins.push(plugin.factory(context));
100
+ }
101
+ runtime.use(composeMelonyPlugin(...pluginPlugins));
102
+ return runtime.build();
103
+ }
@@ -0,0 +1,37 @@
1
+ import { aiSdkRuntime } from './runtime.js';
2
+ import { AI_SDK_SYSTEM_PROMPT } from './system-prompt.js';
3
+ /**
4
+ * `ai-sdk` — generic LLM runtime plugin built on the Vercel AI SDK.
5
+ *
6
+ * Owns `agent:invoke` and consumes the merged `tools` map provided by the
7
+ * agent loader (collected from every tool plugin attached to the same agent).
8
+ * Pair with tool plugins like `shell`, `mcp`, `delegation`, etc.
9
+ */
10
+ export const aiSdkPlugin = {
11
+ id: 'ai-sdk',
12
+ name: 'AI SDK Runtime',
13
+ description: 'Generic LLM runtime built on the Vercel AI SDK. Consumes tools contributed by other plugins.',
14
+ defaultInstructions: AI_SDK_SYSTEM_PROMPT,
15
+ configSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ model: {
19
+ type: 'string',
20
+ description: 'Provider model string, e.g. openai/gpt-4o-mini, anthropic/claude-3-5-sonnet-20240620',
21
+ default: 'openai/gpt-4o-mini',
22
+ },
23
+ },
24
+ },
25
+ factory: ({ agentDetails, config, storage, tools }) => {
26
+ const model = typeof config.model === 'string' && config.model
27
+ ? config.model
28
+ : 'openai/gpt-4o-mini';
29
+ return aiSdkRuntime({
30
+ model,
31
+ system: agentDetails.instructions || AI_SDK_SYSTEM_PROMPT,
32
+ storage,
33
+ toolDefinitions: tools,
34
+ });
35
+ },
36
+ };
37
+ export default aiSdkPlugin;