openbot 0.3.1 → 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.
- package/dist/app/server.js +1 -4
- package/dist/bus/services.js +106 -10
- package/dist/harness/context.js +66 -6
- package/dist/harness/queue-processor.js +44 -110
- package/dist/harness/runtime-factory.js +11 -7
- package/dist/harness/todo-advance.js +93 -0
- package/dist/plugins/ai-sdk/index.js +0 -3
- package/dist/plugins/ai-sdk/runtime.js +4 -11
- package/dist/plugins/ai-sdk/system-prompt.js +18 -3
- package/dist/plugins/delegation/index.js +7 -46
- package/dist/plugins/storage-tools/index.js +2 -11
- package/dist/plugins/todo/index.js +54 -0
- package/dist/plugins/workflow/index.js +65 -0
- package/dist/registry/plugins.js +2 -2
- package/dist/services/storage.js +3 -31
- package/dist/workflow/service.js +106 -0
- package/dist/workflow/types.js +3 -0
- package/docs/plugins.md +0 -1
- package/package.json +1 -1
- package/src/app/cli.ts +1 -1
- package/src/app/server.ts +3 -4
- package/src/app/types.ts +80 -45
- package/src/bus/plugin.ts +0 -2
- package/src/bus/services.ts +133 -12
- package/src/bus/types.ts +0 -4
- package/src/harness/context.ts +73 -10
- package/src/harness/queue-processor.ts +54 -143
- package/src/harness/runtime-factory.ts +11 -7
- package/src/harness/todo-advance.ts +128 -0
- package/src/plugins/ai-sdk/index.ts +0 -3
- package/src/plugins/ai-sdk/runtime.ts +284 -300
- package/src/plugins/ai-sdk/system-prompt.ts +18 -4
- package/src/plugins/delegation/index.ts +7 -50
- package/src/plugins/storage-tools/index.ts +8 -19
- package/src/plugins/todo/index.ts +64 -0
- package/src/registry/plugins.ts +2 -3
- package/src/services/storage.ts +2 -49
|
@@ -3,26 +3,18 @@ import z from 'zod';
|
|
|
3
3
|
import type { Plugin } from '../../bus/plugin.js';
|
|
4
4
|
import { OpenBotEvent, OpenBotState } from '../../app/types.js';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const handoffToolDefinitions = {
|
|
7
7
|
handoff: {
|
|
8
8
|
description:
|
|
9
|
-
'Transfer control to another agent. The target agent continues the task
|
|
9
|
+
'Transfer control to another agent. The target agent continues the task in this thread.',
|
|
10
10
|
inputSchema: z.object({
|
|
11
11
|
agentId: z.string().describe('The ID of the target agent.'),
|
|
12
12
|
content: z.string().describe('The message or task to hand off.'),
|
|
13
13
|
}),
|
|
14
14
|
},
|
|
15
|
-
delegate: {
|
|
16
|
-
description:
|
|
17
|
-
'Delegate a subtask to another agent and wait for its result so you can continue.',
|
|
18
|
-
inputSchema: z.object({
|
|
19
|
-
agentId: z.string().describe('The ID of the target agent.'),
|
|
20
|
-
content: z.string().describe('The subtask you want the target agent to execute.'),
|
|
21
|
-
}),
|
|
22
|
-
},
|
|
23
15
|
};
|
|
24
16
|
|
|
25
|
-
const
|
|
17
|
+
const handoffPluginRuntime = (): MelonyPlugin<OpenBotState, OpenBotEvent> => (builder) => {
|
|
26
18
|
builder.on('action:handoff', async function* (event, context) {
|
|
27
19
|
const { agentId, content } = event.data;
|
|
28
20
|
|
|
@@ -46,49 +38,14 @@ const delegationPluginRuntime = (): MelonyPlugin<OpenBotState, OpenBotEvent> =>
|
|
|
46
38
|
};
|
|
47
39
|
}
|
|
48
40
|
});
|
|
49
|
-
|
|
50
|
-
builder.on('action:delegate', async function* (event, context) {
|
|
51
|
-
const { agentId, content } = event.data;
|
|
52
|
-
const widgetId = event.meta?.toolCallId
|
|
53
|
-
? `delegate_${event.meta.toolCallId}`
|
|
54
|
-
: `delegate_${Date.now()}`;
|
|
55
|
-
|
|
56
|
-
yield {
|
|
57
|
-
type: 'client:ui:widget',
|
|
58
|
-
data: {
|
|
59
|
-
kind: 'message',
|
|
60
|
-
widgetId,
|
|
61
|
-
title: `Delegation started: ${agentId}`,
|
|
62
|
-
body: `Running delegated task in background.\n\n${content}`,
|
|
63
|
-
state: 'open',
|
|
64
|
-
metadata: {
|
|
65
|
-
type: 'delegation:status',
|
|
66
|
-
phase: 'started',
|
|
67
|
-
delegatedAgentId: agentId,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
meta: { ...(event.meta || {}), agentId: context.state.agentId },
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
yield {
|
|
74
|
-
type: 'delegation:request',
|
|
75
|
-
data: { agentId, content },
|
|
76
|
-
meta: {
|
|
77
|
-
...(event.meta || {}),
|
|
78
|
-
parentAgentId: context.state.agentId,
|
|
79
|
-
delegationWidgetId: widgetId,
|
|
80
|
-
agentId: context.state.agentId,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
41
|
};
|
|
85
42
|
|
|
86
43
|
export const delegationPlugin: Plugin = {
|
|
87
44
|
id: 'delegation',
|
|
88
|
-
name: '
|
|
89
|
-
description: 'Hand off
|
|
90
|
-
toolDefinitions:
|
|
91
|
-
factory: () =>
|
|
45
|
+
name: 'Handoff',
|
|
46
|
+
description: 'Hand off tasks to other agents on the bus.',
|
|
47
|
+
toolDefinitions: handoffToolDefinitions,
|
|
48
|
+
factory: () => handoffPluginRuntime(),
|
|
92
49
|
};
|
|
93
50
|
|
|
94
51
|
export default delegationPlugin;
|
|
@@ -52,25 +52,14 @@ const storageToolDefinitions = {
|
|
|
52
52
|
),
|
|
53
53
|
},
|
|
54
54
|
patch_thread_details: {
|
|
55
|
-
description: 'Patch current thread details (state
|
|
56
|
-
inputSchema: z
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
),
|
|
64
|
-
spec: z
|
|
65
|
-
.string()
|
|
66
|
-
.optional()
|
|
67
|
-
.describe(
|
|
68
|
-
'Markdown content for the thread specification (SPEC.md). Use for plans and goals.',
|
|
69
|
-
),
|
|
70
|
-
})
|
|
71
|
-
.refine((value) => value.state !== undefined || value.spec !== undefined, {
|
|
72
|
-
message: 'Provide at least one of state or spec.',
|
|
73
|
-
}),
|
|
55
|
+
description: 'Patch current thread details (state).',
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
state: z
|
|
58
|
+
.record(z.string(), z.unknown())
|
|
59
|
+
.describe(
|
|
60
|
+
'JSON state object for the thread. Use for structured data like `todos` or progress.',
|
|
61
|
+
),
|
|
62
|
+
}),
|
|
74
63
|
},
|
|
75
64
|
create_variable: {
|
|
76
65
|
description: 'Create or update a variable in the workspace storage.',
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import type { Plugin } from '../../bus/plugin.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `todo` — shared, per-thread task list for autonomous multi-agent flows.
|
|
6
|
+
*
|
|
7
|
+
* Todos live in `threadDetails.state.todos` and are owned by the system
|
|
8
|
+
* (handlers in `bus/services.ts`). Any agent in the thread can read the
|
|
9
|
+
* list via context, and propose mutations through these tools. Each item
|
|
10
|
+
* may carry an `assignee` agent id; combine with `handoff` to drive an
|
|
11
|
+
* autonomous, multi-step plan across agents.
|
|
12
|
+
*
|
|
13
|
+
* Keep the surface minimal: two tools (replace-all, patch-one) cover plan
|
|
14
|
+
* authoring, status transitions, and reassignment.
|
|
15
|
+
*/
|
|
16
|
+
const todoStatus = z.enum(['pending', 'in_progress', 'done', 'cancelled']);
|
|
17
|
+
|
|
18
|
+
const todoToolDefinitions = {
|
|
19
|
+
todo_write: {
|
|
20
|
+
description:
|
|
21
|
+
'Author or rewrite the shared todo plan for the current thread. Pass the full ordered list — missing items are removed. Use at the start of multi-step work, or whenever the plan changes shape. For status flips, prefer `todo_update`.',
|
|
22
|
+
inputSchema: z.object({
|
|
23
|
+
todos: z
|
|
24
|
+
.array(
|
|
25
|
+
z.object({
|
|
26
|
+
id: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Stable id. Reuse existing ids to preserve history; omit to create.'),
|
|
30
|
+
content: z.string().min(1).describe('What needs to be done. One concrete step.'),
|
|
31
|
+
status: todoStatus.optional().describe('Defaults to `pending`.'),
|
|
32
|
+
assignee: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Agent id responsible for this step. Pair with `handoff` to delegate.'),
|
|
36
|
+
}),
|
|
37
|
+
)
|
|
38
|
+
.describe('The complete, ordered plan.'),
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
todo_update: {
|
|
42
|
+
description:
|
|
43
|
+
'Patch a single todo by id. Use to mark progress (`in_progress`, `done`, `cancelled`), rephrase, or reassign without rewriting the whole list.',
|
|
44
|
+
inputSchema: z.object({
|
|
45
|
+
id: z.string().describe('Todo id from `todo_write` or the rendered list.'),
|
|
46
|
+
status: todoStatus.optional(),
|
|
47
|
+
content: z.string().min(1).optional(),
|
|
48
|
+
assignee: z.string().optional().describe('Use empty string to clear.'),
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const todoPlugin: Plugin = {
|
|
54
|
+
id: 'todo',
|
|
55
|
+
name: 'Todo',
|
|
56
|
+
description:
|
|
57
|
+
'Shared per-thread task list for coordinating multi-step, multi-agent work.',
|
|
58
|
+
toolDefinitions: todoToolDefinitions,
|
|
59
|
+
factory: () => () => {
|
|
60
|
+
// Handlers live in bus/services.ts; this plugin only contributes tool definitions.
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default todoPlugin;
|
package/src/registry/plugins.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { storageToolsPlugin } from '../plugins/storage-tools/index.js';
|
|
|
10
10
|
import { uiPlugin } from '../plugins/ui/index.js';
|
|
11
11
|
import { approvalPlugin } from '../plugins/approval/index.js';
|
|
12
12
|
import { memoryPlugin } from '../plugins/memory/index.js';
|
|
13
|
+
import { todoPlugin } from '../plugins/todo/index.js';
|
|
13
14
|
import { DEFAULT_PLUGINS_DIR, DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../app/config.js';
|
|
14
15
|
|
|
15
16
|
let pluginsDir: string | null = null;
|
|
@@ -25,6 +26,7 @@ const BUILT_IN: Record<string, Plugin> = {
|
|
|
25
26
|
[uiPlugin.id]: uiPlugin,
|
|
26
27
|
[approvalPlugin.id]: approvalPlugin,
|
|
27
28
|
[memoryPlugin.id]: memoryPlugin,
|
|
29
|
+
[todoPlugin.id]: todoPlugin,
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -50,8 +52,6 @@ export function parsePluginModule(
|
|
|
50
52
|
const name = typeof raw.name === 'string' ? raw.name : '';
|
|
51
53
|
const description = typeof raw.description === 'string' ? raw.description : '';
|
|
52
54
|
const image = typeof raw.image === 'string' ? raw.image : undefined;
|
|
53
|
-
const defaultInstructions =
|
|
54
|
-
typeof raw.defaultInstructions === 'string' ? raw.defaultInstructions : undefined;
|
|
55
55
|
const configSchema = raw.configSchema as Plugin['configSchema'];
|
|
56
56
|
const toolDefinitions = raw.toolDefinitions as Plugin['toolDefinitions'];
|
|
57
57
|
|
|
@@ -59,7 +59,6 @@ export function parsePluginModule(
|
|
|
59
59
|
name,
|
|
60
60
|
description,
|
|
61
61
|
image,
|
|
62
|
-
defaultInstructions,
|
|
63
62
|
configSchema,
|
|
64
63
|
toolDefinitions,
|
|
65
64
|
factory: factory as Plugin['factory'],
|
package/src/services/storage.ts
CHANGED
|
@@ -91,7 +91,7 @@ const SYSTEM_DEFAULT_PLUGINS: PluginRef[] = [
|
|
|
91
91
|
{ id: 'storage-tools' },
|
|
92
92
|
// { id: 'mcp' },
|
|
93
93
|
{ id: 'shell' },
|
|
94
|
-
{ id: '
|
|
94
|
+
{ id: 'todo' },
|
|
95
95
|
// { id: 'ui' },
|
|
96
96
|
{ id: 'approval' },
|
|
97
97
|
{ id: 'memory' },
|
|
@@ -103,7 +103,7 @@ function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails
|
|
|
103
103
|
name: 'OpenBot',
|
|
104
104
|
image: undefined,
|
|
105
105
|
description:
|
|
106
|
-
'First-party orchestration agent for OpenBot. Coordinates other agents via handoff
|
|
106
|
+
'First-party orchestration agent for OpenBot. Coordinates other agents via handoff.',
|
|
107
107
|
instructions: AI_SDK_SYSTEM_PROMPT,
|
|
108
108
|
plugins: SYSTEM_DEFAULT_PLUGINS.map((ref) => ref.id),
|
|
109
109
|
pluginRefs: SYSTEM_DEFAULT_PLUGINS,
|
|
@@ -211,7 +211,6 @@ const listBuiltInPluginDescriptors = async (): Promise<PluginDescriptor[]> => {
|
|
|
211
211
|
description: plugin.description,
|
|
212
212
|
builtIn: true,
|
|
213
213
|
image: plugin.image,
|
|
214
|
-
defaultInstructions: plugin.defaultInstructions,
|
|
215
214
|
configSchema: plugin.configSchema,
|
|
216
215
|
createdAt: new Date(),
|
|
217
216
|
updatedAt: new Date(),
|
|
@@ -281,7 +280,6 @@ const listPluginsFromDisk = async (): Promise<PluginDescriptor[]> => {
|
|
|
281
280
|
description: parsed.description || '',
|
|
282
281
|
builtIn: false,
|
|
283
282
|
image: parsed.image || image,
|
|
284
|
-
defaultInstructions: parsed.defaultInstructions,
|
|
285
283
|
configSchema: parsed.configSchema,
|
|
286
284
|
createdAt: new Date(),
|
|
287
285
|
updatedAt: new Date(),
|
|
@@ -450,13 +448,11 @@ export const storageService = {
|
|
|
450
448
|
channelId,
|
|
451
449
|
threadId,
|
|
452
450
|
threadTitle,
|
|
453
|
-
spec,
|
|
454
451
|
initialState,
|
|
455
452
|
}: {
|
|
456
453
|
channelId: string;
|
|
457
454
|
threadId: string;
|
|
458
455
|
threadTitle?: string;
|
|
459
|
-
spec?: string;
|
|
460
456
|
initialState?: Record<string, unknown>;
|
|
461
457
|
}): Promise<void> => {
|
|
462
458
|
const normalizedChannelId = channelId.trim();
|
|
@@ -466,7 +462,6 @@ export const storageService = {
|
|
|
466
462
|
if (!normalizedThreadId) throw new Error('threadId is required');
|
|
467
463
|
|
|
468
464
|
const threadDir = getConversationDir(normalizedChannelId, normalizedThreadId);
|
|
469
|
-
const specPath = `${threadDir}/SPEC.md`;
|
|
470
465
|
const statePath = `${threadDir}/state.json`;
|
|
471
466
|
|
|
472
467
|
try {
|
|
@@ -487,11 +482,6 @@ export const storageService = {
|
|
|
487
482
|
}
|
|
488
483
|
|
|
489
484
|
await fs.mkdir(threadDir, { recursive: true });
|
|
490
|
-
await fs.writeFile(
|
|
491
|
-
specPath,
|
|
492
|
-
spec?.trim() ||
|
|
493
|
-
`# ${normalizedThreadId}\n\nDefine the goals and plan for this thread here.\n`,
|
|
494
|
-
);
|
|
495
485
|
await fs.writeFile(statePath, JSON.stringify(baseState, null, 2));
|
|
496
486
|
},
|
|
497
487
|
getThreads: async ({ channelId }: { channelId: string }): Promise<Thread[]> => {
|
|
@@ -550,21 +540,8 @@ export const storageService = {
|
|
|
550
540
|
threadId: string;
|
|
551
541
|
}): Promise<ThreadDetails> => {
|
|
552
542
|
const threadDir = getConversationDir(channelId, threadId);
|
|
553
|
-
const specPath = `${threadDir}/SPEC.md`;
|
|
554
543
|
const statePath = `${threadDir}/state.json`;
|
|
555
544
|
|
|
556
|
-
let spec = '';
|
|
557
|
-
try {
|
|
558
|
-
spec = await fs.readFile(specPath, 'utf-8');
|
|
559
|
-
} catch (error: unknown) {
|
|
560
|
-
if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {
|
|
561
|
-
console.error(
|
|
562
|
-
`Failed to read thread spec for channel ${channelId} thread ${threadId}`,
|
|
563
|
-
error,
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
545
|
let state: unknown = {};
|
|
569
546
|
try {
|
|
570
547
|
const stateContent = await fs.readFile(statePath, 'utf-8');
|
|
@@ -587,7 +564,6 @@ export const storageService = {
|
|
|
587
564
|
id: threadId,
|
|
588
565
|
name: generatedName || threadId,
|
|
589
566
|
channelId,
|
|
590
|
-
spec,
|
|
591
567
|
state,
|
|
592
568
|
};
|
|
593
569
|
},
|
|
@@ -704,29 +680,6 @@ export const storageService = {
|
|
|
704
680
|
throw error;
|
|
705
681
|
}
|
|
706
682
|
},
|
|
707
|
-
patchThreadSpec: async ({
|
|
708
|
-
channelId,
|
|
709
|
-
threadId,
|
|
710
|
-
spec,
|
|
711
|
-
}: {
|
|
712
|
-
channelId: string;
|
|
713
|
-
threadId: string;
|
|
714
|
-
spec: string;
|
|
715
|
-
}): Promise<void> => {
|
|
716
|
-
const threadDir = getConversationDir(channelId, threadId);
|
|
717
|
-
const specPath = `${threadDir}/SPEC.md`;
|
|
718
|
-
|
|
719
|
-
try {
|
|
720
|
-
await fs.mkdir(threadDir, { recursive: true });
|
|
721
|
-
await fs.writeFile(specPath, spec);
|
|
722
|
-
} catch (error) {
|
|
723
|
-
console.error(
|
|
724
|
-
`Failed to patch thread spec for channel ${channelId} thread ${threadId}`,
|
|
725
|
-
error,
|
|
726
|
-
);
|
|
727
|
-
throw error;
|
|
728
|
-
}
|
|
729
|
-
},
|
|
730
683
|
getAgents: async (): Promise<Agent[]> => {
|
|
731
684
|
const agentsDir = resolvePath(resolveBaseDir() + '/' + DEFAULT_AGENTS_DIR);
|
|
732
685
|
try {
|