openbot 0.4.7 → 0.5.4

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 (130) hide show
  1. package/.claude/agents/runtime-security-reviewer.md +32 -0
  2. package/.claude/hooks/format.sh +18 -0
  3. package/.claude/hooks/protect-secrets.sh +23 -0
  4. package/.claude/settings.json +26 -0
  5. package/.claude/skills/deploy/SKILL.md +26 -0
  6. package/.claude/skills/new-plugin/SKILL.md +46 -0
  7. package/.dockerignore +9 -0
  8. package/.mcp.json +8 -0
  9. package/AGENTS.md +2 -2
  10. package/CLAUDE.md +50 -0
  11. package/Dockerfile +41 -0
  12. package/deploy/README.md +61 -0
  13. package/deploy/entrypoint.sh +6 -0
  14. package/deploy/fly.toml +36 -0
  15. package/dist/app/bootstrap.js +29 -0
  16. package/dist/app/cli.js +3 -1
  17. package/dist/app/cloud-mode.js +26 -0
  18. package/dist/app/config.js +61 -16
  19. package/dist/app/server.js +38 -6
  20. package/dist/plugins/approval/index.js +1 -1
  21. package/dist/plugins/bash/index.js +386 -164
  22. package/dist/plugins/memory/service.js +2 -5
  23. package/dist/plugins/openbot/context.js +17 -1
  24. package/dist/plugins/openbot/index.js +23 -8
  25. package/dist/plugins/openbot/model.js +59 -0
  26. package/dist/plugins/openbot/runtime.js +39 -56
  27. package/dist/plugins/openbot/system-prompt.js +43 -8
  28. package/dist/plugins/preview/index.js +267 -0
  29. package/dist/plugins/storage/index.js +3 -3
  30. package/dist/plugins/storage/service.js +58 -12
  31. package/dist/plugins/todo/index.js +131 -38
  32. package/dist/plugins/todo/service.js +93 -0
  33. package/dist/plugins/ui/index.js +8 -1
  34. package/dist/services/plugins/model-registry.js +101 -0
  35. package/dist/services/plugins/registry.js +6 -4
  36. package/dist/services/plugins/service.js +2 -6
  37. package/docs/agents.md +20 -2
  38. package/docs/architecture.md +1 -1
  39. package/docs/plugins.md +13 -3
  40. package/docs/templates/AGENT.example.md +1 -1
  41. package/package.json +5 -2
  42. package/pnpm-workspace.yaml +2 -0
  43. package/src/app/bootstrap.ts +43 -0
  44. package/src/app/cli.ts +3 -1
  45. package/src/app/cloud-mode.ts +41 -0
  46. package/src/app/config.ts +71 -19
  47. package/src/app/server.ts +45 -6
  48. package/src/app/types.ts +162 -31
  49. package/src/plugins/approval/index.ts +1 -1
  50. package/src/plugins/bash/index.ts +498 -185
  51. package/src/plugins/memory/service.ts +2 -5
  52. package/src/plugins/openbot/context.ts +17 -1
  53. package/src/plugins/openbot/index.ts +25 -10
  54. package/src/plugins/openbot/model.ts +76 -0
  55. package/src/plugins/openbot/runtime.ts +47 -69
  56. package/src/plugins/openbot/system-prompt.ts +43 -8
  57. package/src/plugins/preview/index.ts +323 -0
  58. package/src/plugins/storage/index.ts +5 -8
  59. package/src/plugins/storage/service.ts +79 -15
  60. package/src/plugins/todo/index.ts +166 -0
  61. package/src/plugins/todo/service.ts +123 -0
  62. package/src/plugins/ui/index.ts +8 -1
  63. package/src/services/plugins/domain.ts +2 -0
  64. package/src/services/plugins/model-registry.ts +146 -0
  65. package/src/services/plugins/registry.ts +6 -4
  66. package/src/services/plugins/service.ts +2 -6
  67. package/dist/agents/openbot/index.js +0 -76
  68. package/dist/agents/openbot/middleware/approval.js +0 -132
  69. package/dist/agents/openbot/runtime.js +0 -289
  70. package/dist/agents/openbot/system-prompt.js +0 -32
  71. package/dist/agents/openbot/tools/delegation.js +0 -78
  72. package/dist/agents/openbot/tools/mcp.js +0 -99
  73. package/dist/agents/openbot/tools/shell.js +0 -91
  74. package/dist/agents/openbot/tools/storage.js +0 -75
  75. package/dist/agents/openbot/tools/ui.js +0 -176
  76. package/dist/agents/system.js +0 -33
  77. package/dist/bus/agent-package.js +0 -1
  78. package/dist/bus/plugin.js +0 -1
  79. package/dist/bus/services.js +0 -754
  80. package/dist/bus/types.js +0 -1
  81. package/dist/harness/agent-harness.js +0 -45
  82. package/dist/harness/agent-invoke-run.js +0 -44
  83. package/dist/harness/agent-turn.js +0 -99
  84. package/dist/harness/channel-participants.js +0 -40
  85. package/dist/harness/constants.js +0 -2
  86. package/dist/harness/context-meter.js +0 -97
  87. package/dist/harness/context.js +0 -363
  88. package/dist/harness/dispatch.js +0 -144
  89. package/dist/harness/dispatcher.js +0 -156
  90. package/dist/harness/event-normalizer.js +0 -59
  91. package/dist/harness/history.js +0 -177
  92. package/dist/harness/mcp.js +0 -61
  93. package/dist/harness/orchestration.js +0 -88
  94. package/dist/harness/orchestrator.js +0 -149
  95. package/dist/harness/participants.js +0 -22
  96. package/dist/harness/process.js +0 -29
  97. package/dist/harness/queue-processor.js +0 -187
  98. package/dist/harness/run-harness.js +0 -154
  99. package/dist/harness/run.js +0 -98
  100. package/dist/harness/runtime-factory.js +0 -73
  101. package/dist/harness/runtime.js +0 -57
  102. package/dist/harness/todo-advance.js +0 -93
  103. package/dist/harness/todo-dispatch.js +0 -51
  104. package/dist/harness/todos.js +0 -5
  105. package/dist/harness/turn.js +0 -79
  106. package/dist/harness/types.js +0 -1
  107. package/dist/plugins/ai-sdk/index.js +0 -34
  108. package/dist/plugins/ai-sdk/runtime.js +0 -395
  109. package/dist/plugins/ai-sdk/system-prompt.js +0 -18
  110. package/dist/plugins/ai-sdk.js +0 -331
  111. package/dist/plugins/approval.js +0 -163
  112. package/dist/plugins/delegation.js +0 -108
  113. package/dist/plugins/mcp/index.js +0 -108
  114. package/dist/plugins/mcp.js +0 -140
  115. package/dist/plugins/shell/index.js +0 -100
  116. package/dist/plugins/shell.js +0 -123
  117. package/dist/plugins/storage-tools/index.js +0 -76
  118. package/dist/plugins/storage.js +0 -737
  119. package/dist/plugins/thread-namer/index.js +0 -72
  120. package/dist/plugins/threads/index.js +0 -114
  121. package/dist/plugins/ui.js +0 -211
  122. package/dist/plugins/workflow/index.js +0 -65
  123. package/dist/registry/agents.js +0 -138
  124. package/dist/registry/plugins.js +0 -110
  125. package/dist/services/agent-packages.js +0 -103
  126. package/dist/services/memory.js +0 -152
  127. package/dist/services/plugins.js +0 -98
  128. package/dist/services/storage.js +0 -1028
  129. package/dist/workflow/service.js +0 -106
  130. package/dist/workflow/types.js +0 -3
@@ -1,72 +0,0 @@
1
- import { generateText } from 'ai';
2
- import { openai } from '@ai-sdk/openai';
3
- import { anthropic } from '@ai-sdk/anthropic';
4
- import { THREAD_NAMER_AGENT_ID } from '../../app/agent-ids.js';
5
- import { loadConfig } from '../../app/config.js';
6
- const DEFAULT_MODEL = 'openai/gpt-4o-mini';
7
- const TITLE_SYSTEM_PROMPT = 'Generate a concise thread title from the user message. Reply with the title only: no quotes, no punctuation at the end, max 8 words.';
8
- function resolveModel(modelString) {
9
- const [provider, ...rest] = modelString.split('/');
10
- const modelId = rest.join('/');
11
- if (!modelId) {
12
- throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
13
- }
14
- switch (provider) {
15
- case 'openai':
16
- return openai(modelId);
17
- case 'anthropic':
18
- return anthropic(modelId);
19
- default:
20
- throw new Error(`Unsupported AI provider: "${provider}"`);
21
- }
22
- }
23
- /**
24
- * `thread-namer` — internal agent plugin that generates short thread titles via LLM.
25
- */
26
- export const threadNamerPlugin = {
27
- id: 'thread-namer',
28
- name: 'Thread namer',
29
- description: 'Generates concise thread titles from seed messages.',
30
- configSchema: {
31
- type: 'object',
32
- properties: {
33
- model: {
34
- type: 'string',
35
- description: 'Provider model string, e.g. openai/gpt-4o-mini',
36
- default: DEFAULT_MODEL,
37
- },
38
- },
39
- },
40
- factory: ({ agentId, config }) => {
41
- if (agentId !== THREAD_NAMER_AGENT_ID) {
42
- return () => { };
43
- }
44
- const configuredModel = typeof config?.model === 'string' && config.model.trim()
45
- ? config.model.trim()
46
- : loadConfig().model?.trim() || DEFAULT_MODEL;
47
- return (builder) => {
48
- builder.on('thread:title:generate', async function* (event) {
49
- const data = (event.data || {});
50
- const seedMessage = typeof data.seedMessage === 'string' ? data.seedMessage.trim() : '';
51
- if (!seedMessage) {
52
- yield {
53
- type: 'thread:title:generated',
54
- data: { title: 'New thread', channelId: data.channelId },
55
- };
56
- return;
57
- }
58
- const result = await generateText({
59
- model: resolveModel(configuredModel),
60
- system: TITLE_SYSTEM_PROMPT,
61
- prompt: seedMessage,
62
- });
63
- const title = result.text.replace(/\s+/g, ' ').trim() || 'New thread';
64
- yield {
65
- type: 'thread:title:generated',
66
- data: { title, channelId: data.channelId },
67
- };
68
- });
69
- };
70
- },
71
- };
72
- export default threadNamerPlugin;
@@ -1,114 +0,0 @@
1
- import { generateId } from 'melony';
2
- import { STATE_AGENT_ID, THREAD_NAMER_AGENT_ID } from '../../app/agent-ids.js';
3
- import { runAgent } from '../../harness/index.js';
4
- const THREAD_TITLE_MAX_LENGTH = 80;
5
- function buildFallbackTitle(seedMessage) {
6
- const normalized = seedMessage.replace(/\s+/g, ' ').trim();
7
- if (!normalized)
8
- return 'New thread';
9
- if (normalized.length <= THREAD_TITLE_MAX_LENGTH)
10
- return normalized;
11
- return `${normalized.slice(0, THREAD_TITLE_MAX_LENGTH).trimEnd()}...`;
12
- }
13
- async function requestTitleFromSystem(args) {
14
- let title;
15
- await runAgent({
16
- runId: `tn_${generateId()}`,
17
- agentId: THREAD_NAMER_AGENT_ID,
18
- channelId: args.channelId,
19
- threadId: args.threadId,
20
- persistEvents: false,
21
- event: {
22
- type: 'thread:title:generate',
23
- data: {
24
- seedMessage: args.seedMessage,
25
- channelId: args.channelId,
26
- },
27
- },
28
- onEvent: async (chunk) => {
29
- if (chunk.type === 'thread:title:generated') {
30
- const generated = chunk.data.title;
31
- if (typeof generated === 'string' && generated.trim()) {
32
- title = generated.trim();
33
- }
34
- }
35
- },
36
- });
37
- if (!title) {
38
- throw new Error('Thread namer did not return a title');
39
- }
40
- return title;
41
- }
42
- /**
43
- * `threads` — orchestrates explicit thread creation on the state agent.
44
- */
45
- export const threadsPlugin = {
46
- id: 'threads',
47
- name: 'Threads',
48
- description: 'Creates threads with LLM-generated titles and notifies clients.',
49
- factory: ({ agentId, storage }) => {
50
- if (agentId !== STATE_AGENT_ID) {
51
- return () => { };
52
- }
53
- return (builder) => {
54
- builder.on('thread:create', async function* (event, context) {
55
- const data = (event.data || {});
56
- const channelId = (data.channelId || context.state.channelId || '').trim();
57
- const threadId = (data.threadId || '').trim();
58
- if (!channelId || !threadId) {
59
- yield {
60
- type: 'thread:create:failed',
61
- data: {
62
- channelId,
63
- threadId,
64
- error: 'channelId and threadId are required',
65
- },
66
- };
67
- return;
68
- }
69
- const seedMessage = typeof data.seedMessage === 'string' ? data.seedMessage.trim() : '';
70
- let title = threadId;
71
- if (seedMessage) {
72
- try {
73
- title = await requestTitleFromSystem({ channelId, threadId, seedMessage });
74
- }
75
- catch (error) {
76
- console.warn('[threads] Title generation failed, using fallback', error);
77
- title = buildFallbackTitle(seedMessage);
78
- }
79
- }
80
- try {
81
- await storage.createThread({
82
- channelId,
83
- threadId,
84
- threadTitle: title,
85
- initialState: data.initialState,
86
- });
87
- }
88
- catch (error) {
89
- const message = error instanceof Error ? error.message : String(error);
90
- if (message.includes('already exists')) {
91
- const existing = await storage.getThreadDetails({ channelId, threadId });
92
- title = existing.name || title;
93
- }
94
- else {
95
- yield {
96
- type: 'thread:create:failed',
97
- data: { channelId, threadId, error: message },
98
- };
99
- return;
100
- }
101
- }
102
- yield {
103
- type: 'thread:created',
104
- data: { channelId, threadId, title },
105
- };
106
- yield {
107
- type: 'client:invalidate',
108
- data: { channelId, scopes: ['threads'], threadId },
109
- };
110
- });
111
- };
112
- },
113
- };
114
- export default threadsPlugin;
@@ -1,211 +0,0 @@
1
- import z from 'zod';
2
- const actionSchema = z.object({
3
- id: z.string().describe('Stable action ID returned by client:ui:widget:response.'),
4
- label: z.string().describe('Human-readable button label.'),
5
- value: z.unknown().optional().describe('Optional machine-readable value for this action.'),
6
- variant: z.enum(['primary', 'secondary', 'danger']).optional(),
7
- disabled: z.boolean().optional(),
8
- });
9
- const optionSchema = z.object({
10
- label: z.string(),
11
- value: z.string(),
12
- });
13
- const fieldSchema = z.object({
14
- id: z.string().describe('Stable field ID used as the submitted value key.'),
15
- label: z.string(),
16
- type: z.enum(['text', 'textarea', 'number', 'boolean', 'select', 'multiselect', 'date']),
17
- description: z.string().optional(),
18
- placeholder: z.string().optional(),
19
- required: z.boolean().optional(),
20
- options: z.array(optionSchema).optional(),
21
- defaultValue: z.unknown().optional(),
22
- });
23
- const listItemSchema = z.object({
24
- id: z.string(),
25
- label: z.string(),
26
- description: z.string().optional(),
27
- status: z.enum(['pending', 'in_progress', 'done', 'error', 'cancelled']).optional(),
28
- metadata: z.record(z.string(), z.unknown()).optional(),
29
- });
30
- const widgetBaseSchema = {
31
- widgetId: z.string().optional().describe('Optional stable widget ID. Defaults from toolCallId.'),
32
- title: z.string().optional(),
33
- description: z.string().optional(),
34
- body: z.string().optional(),
35
- state: z.enum(['open', 'submitted', 'cancelled', 'error']).optional(),
36
- metadata: z.record(z.string(), z.unknown()).optional(),
37
- };
38
- const renderWidgetSchema = z.union([
39
- z.object({
40
- ...widgetBaseSchema,
41
- kind: z.literal('message'),
42
- actions: z.array(actionSchema).optional(),
43
- }),
44
- z.object({
45
- ...widgetBaseSchema,
46
- kind: z.literal('choice'),
47
- actions: z.array(actionSchema).min(1),
48
- }),
49
- z.object({
50
- ...widgetBaseSchema,
51
- kind: z.literal('form'),
52
- fields: z.array(fieldSchema).optional(),
53
- submitLabel: z.string().optional(),
54
- actions: z.array(actionSchema).optional(),
55
- props: z
56
- .record(z.string(), z.unknown())
57
- .optional()
58
- .describe('Legacy form props. Prefer fields and submitLabel.'),
59
- }),
60
- z.object({
61
- ...widgetBaseSchema,
62
- kind: z.literal('list'),
63
- items: z.array(listItemSchema).optional(),
64
- actions: z.array(actionSchema).optional(),
65
- }),
66
- z.object({
67
- kind: z.enum(['approval', 'todo_list']).describe('Legacy preset. Prefer choice or list.'),
68
- widgetId: z.string().optional(),
69
- title: z.string().optional(),
70
- props: z.record(z.string(), z.unknown()).optional(),
71
- metadata: z.record(z.string(), z.unknown()).optional(),
72
- }),
73
- ]);
74
- const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
75
- const readString = (value) => typeof value === 'string' && value.trim() ? value : undefined;
76
- const asFields = (value) => Array.isArray(value) ? value : undefined;
77
- const asListItems = (value) => Array.isArray(value) ? value : undefined;
78
- const todoToListItem = (todo, index) => {
79
- if (!isRecord(todo)) {
80
- return {
81
- id: `todo_${index + 1}`,
82
- label: String(todo),
83
- };
84
- }
85
- return {
86
- id: readString(todo.id) || `todo_${index + 1}`,
87
- label: readString(todo.label) ||
88
- readString(todo.task) ||
89
- readString(todo.title) ||
90
- `Todo ${index + 1}`,
91
- description: readString(todo.description),
92
- status: readString(todo.status),
93
- metadata: todo,
94
- };
95
- };
96
- const createWidgetId = (data, toolCallId) => {
97
- if ('widgetId' in data && data.widgetId)
98
- return data.widgetId;
99
- if (toolCallId)
100
- return `widget_${toolCallId}`;
101
- return `widget_${Date.now()}`;
102
- };
103
- const normalizeWidget = (data, state, toolCallId) => {
104
- const widgetId = createWidgetId(data, toolCallId);
105
- if (data.kind === 'approval') {
106
- const props = data.props || {};
107
- return {
108
- widgetId,
109
- kind: 'choice',
110
- title: data.title || 'Approval Required',
111
- body: readString(props.message) ||
112
- readString(props.summary) ||
113
- 'Please approve or deny this action.',
114
- metadata: {
115
- ...(data.metadata || {}),
116
- legacyKind: 'approval',
117
- actionId: props.actionId,
118
- },
119
- actions: [
120
- { id: 'approve', label: 'Approve', value: props.actionId || 'approve', variant: 'primary' },
121
- { id: 'deny', label: 'Deny', value: props.actionId || 'deny', variant: 'danger' },
122
- ],
123
- };
124
- }
125
- if (data.kind === 'todo_list') {
126
- const props = data.props || {};
127
- const stateTodos = isRecord(state.threadDetails?.state)
128
- ? state.threadDetails.state.todos
129
- : undefined;
130
- const todos = asListItems(props.todos) || asListItems(stateTodos) || [];
131
- return {
132
- widgetId,
133
- kind: 'list',
134
- title: data.title || readString(props.title) || 'Task List',
135
- description: readString(props.description),
136
- metadata: {
137
- ...(data.metadata || {}),
138
- legacyKind: 'todo_list',
139
- },
140
- items: todos.map(todoToListItem),
141
- };
142
- }
143
- if (data.kind === 'form') {
144
- const propsSource = data.props;
145
- const props = isRecord(propsSource) ? propsSource : {};
146
- return {
147
- widgetId,
148
- kind: 'form',
149
- title: data.title || 'Details Required',
150
- description: data.description,
151
- body: data.body,
152
- state: data.state,
153
- metadata: data.metadata,
154
- fields: data.fields || asFields(props.schema) || [],
155
- submitLabel: data.submitLabel || readString(props.submitLabel),
156
- actions: data.actions,
157
- };
158
- }
159
- if (data.kind === 'list') {
160
- return {
161
- ...data,
162
- widgetId,
163
- title: data.title || 'Task List',
164
- items: data.items || [],
165
- };
166
- }
167
- if (data.kind === 'choice') {
168
- return {
169
- ...data,
170
- widgetId,
171
- title: data.title || 'Choose an Option',
172
- };
173
- }
174
- if (data.kind === 'message') {
175
- return {
176
- ...data,
177
- widgetId,
178
- title: data.title || 'Message',
179
- };
180
- }
181
- throw new Error(`Unsupported UI widget kind: ${data.kind || 'unknown'}`);
182
- };
183
- /**
184
- * UI Plugin for Melony.
185
- * Provides tools for agents to trigger interactive UI widgets.
186
- */
187
- export const uiPlugin = () => (builder) => {
188
- builder.on('action:render_ui_widget', async function* (event, context) {
189
- const widget = normalizeWidget(event.data, context.state, event.meta?.toolCallId);
190
- yield {
191
- type: 'client:ui:widget',
192
- data: widget,
193
- meta: event.meta,
194
- };
195
- });
196
- };
197
- export const uiToolDefinitions = {
198
- render_ui_widget: {
199
- description: 'Render a small server-driven UI widget in the conversation. Prefer primitive kinds: message, choice, form, or list. Legacy presets approval and todo_list are still accepted.',
200
- inputSchema: renderWidgetSchema,
201
- },
202
- };
203
- export const plugin = {
204
- name: 'ui',
205
- description: 'UI Widgets plugin',
206
- version: '1.0.0',
207
- author: 'OpenBot',
208
- license: 'MIT',
209
- factory: uiPlugin,
210
- toolDefinitions: uiToolDefinitions,
211
- };
@@ -1,65 +0,0 @@
1
- import z from 'zod';
2
- import { setWorkflowPlan, updateWorkflowTodo } from '../../workflow/service.js';
3
- const workflowToolDefinitions = {
4
- workflow_set_plan: {
5
- description: 'Define or replace the multi-agent plan for this thread.',
6
- inputSchema: z.object({
7
- title: z.string().optional().describe('Optional title for the plan.'),
8
- todos: z.array(z.object({
9
- id: z.string().optional().describe('Optional stable id.'),
10
- text: z.string().describe('What needs to be done.'),
11
- })).min(1).describe('Ordered steps.'),
12
- }),
13
- },
14
- workflow_update_todo: {
15
- description: 'Update a step in the plan with progress or results.',
16
- inputSchema: z.object({
17
- todoId: z.string().describe('The id of the todo to update.'),
18
- status: z.enum(['pending', 'done', 'failed']).optional().describe('New status.'),
19
- summary: z.string().optional().describe('Concise result or note about this step.'),
20
- }),
21
- },
22
- };
23
- const workflowPluginRuntime = () => (builder) => {
24
- builder.on('action:workflow_set_plan', async function* (event, context) {
25
- const { threadId, channelId } = context.state;
26
- if (!threadId) {
27
- yield { type: 'action:workflow_set_plan:result', data: { success: false, error: 'No active thread.' }, meta: event.meta };
28
- return;
29
- }
30
- try {
31
- const workflow = await setWorkflowPlan({ channelId, threadId, plan: event.data });
32
- yield { type: 'action:workflow_set_plan:result', data: { success: true, workflowId: workflow.id }, meta: event.meta };
33
- yield {
34
- type: 'agent:output',
35
- data: { content: `Plan updated: **${workflow.title || workflow.id}** (${workflow.todos.length} steps).` },
36
- meta: { ...(event.meta || {}), agentId: context.state.agentId, threadId },
37
- };
38
- }
39
- catch (error) {
40
- yield { type: 'action:workflow_set_plan:result', data: { success: false, error: String(error) }, meta: event.meta };
41
- }
42
- });
43
- builder.on('action:workflow_update_todo', async function* (event, context) {
44
- const { threadId, channelId } = context.state;
45
- if (!threadId) {
46
- yield { type: 'action:workflow_update_todo:result', data: { success: false, error: 'No active thread.' }, meta: event.meta };
47
- return;
48
- }
49
- try {
50
- const workflow = await updateWorkflowTodo({ channelId, threadId, update: event.data });
51
- yield { type: 'action:workflow_update_todo:result', data: { success: true, todoId: event.data.todoId }, meta: event.meta };
52
- }
53
- catch (error) {
54
- yield { type: 'action:workflow_update_todo:result', data: { success: false, error: String(error) }, meta: event.meta };
55
- }
56
- });
57
- };
58
- export const workflowPlugin = {
59
- id: 'workflow',
60
- name: 'Workflow',
61
- description: 'Simple thread-scoped planning for multi-agent coordination.',
62
- toolDefinitions: workflowToolDefinitions,
63
- factory: () => workflowPluginRuntime(),
64
- };
65
- export default workflowPlugin;
@@ -1,138 +0,0 @@
1
- import fs from 'node:fs';
2
- import fsPromises from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
- import { openBotAgentPackage } from '../agents/openbot/index.js';
6
- import { DEFAULT_AGENT_PACKAGES_DIR, DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../app/config.js';
7
- let agentPackagesDir = null;
8
- const loadedPackages = new Set();
9
- const cache = new Map();
10
- const BUILT_IN = {
11
- [openBotAgentPackage.id]: openBotAgentPackage,
12
- };
13
- /**
14
- * Normalize a dynamically imported agent package module. Supports:
15
- * - `agentPackage`, `default` (current AgentPackage layout)
16
- * - `plugin`: legacy/community bundles that used `{ kind: "runtime", factory: (opts) => ... }`
17
- * where opts should come from agent config (merged at runtime via AgentPackageContext).
18
- */
19
- export function parseAgentPackageModule(module) {
20
- const raw = module.agentPackage ??
21
- module.default ??
22
- module.plugin;
23
- if (!raw || typeof raw !== 'object')
24
- return null;
25
- const id = raw.id;
26
- const name = raw.name;
27
- const factory = raw.factory;
28
- if (typeof id !== 'string' || typeof name !== 'string' || typeof factory !== 'function') {
29
- return null;
30
- }
31
- const description = typeof raw.description === 'string' ? raw.description : '';
32
- if (raw.kind === 'runtime') {
33
- const legacyFactory = factory;
34
- return {
35
- id,
36
- name,
37
- description,
38
- image: typeof raw.image === 'string' ? raw.image : undefined,
39
- defaultInstructions: typeof raw.defaultInstructions === 'string' ? raw.defaultInstructions : undefined,
40
- configSchema: raw.configSchema,
41
- factory: (ctx) => {
42
- const opts = ctx.config && typeof ctx.config === 'object' && !Array.isArray(ctx.config)
43
- ? { ...ctx.config }
44
- : {};
45
- return legacyFactory(opts);
46
- },
47
- };
48
- }
49
- return raw;
50
- }
51
- async function resolveCommunityDistPath(agentPackagesDir, id) {
52
- const direct = path.join(agentPackagesDir, id, 'dist', 'index.js');
53
- if (fs.existsSync(direct))
54
- return direct;
55
- try {
56
- const entries = await fsPromises.readdir(agentPackagesDir, { withFileTypes: true });
57
- for (const entry of entries) {
58
- if (entry.name.startsWith('.'))
59
- continue;
60
- if (!entry.isDirectory() && !entry.isSymbolicLink())
61
- continue;
62
- const distPath = path.join(agentPackagesDir, entry.name, 'dist', 'index.js');
63
- if (!fs.existsSync(distPath))
64
- continue;
65
- try {
66
- const mod = await import(pathToFileURL(distPath).href);
67
- const pkg = parseAgentPackageModule(mod);
68
- if (pkg?.id === id)
69
- return distPath;
70
- }
71
- catch {
72
- continue;
73
- }
74
- }
75
- }
76
- catch {
77
- return null;
78
- }
79
- return null;
80
- }
81
- /** Initialize the on-disk agent packages directory (defaults to ~/.openbot/agent-packages). */
82
- export function initAgentPackages(dir) {
83
- if (dir) {
84
- agentPackagesDir = dir;
85
- }
86
- else {
87
- const config = loadConfig();
88
- const baseDir = config.baseDir || DEFAULT_BASE_DIR;
89
- agentPackagesDir = path.join(resolvePath(baseDir), DEFAULT_AGENT_PACKAGES_DIR);
90
- }
91
- }
92
- /**
93
- * Resolve an AgentPackage by id. Looks up built-in packages first, then any
94
- * community packages installed under the agent-packages directory.
95
- */
96
- export async function resolveAgentPackage(id) {
97
- if (cache.has(id))
98
- return cache.get(id);
99
- if (BUILT_IN[id]) {
100
- cache.set(id, BUILT_IN[id]);
101
- return BUILT_IN[id];
102
- }
103
- if (!agentPackagesDir) {
104
- initAgentPackages();
105
- }
106
- if (!agentPackagesDir)
107
- return null;
108
- const distPath = await resolveCommunityDistPath(agentPackagesDir, id);
109
- if (!distPath) {
110
- console.warn(`[agents] AgentPackage "${id}" not found in registry or under ${agentPackagesDir}.`);
111
- return null;
112
- }
113
- try {
114
- const module = await import(pathToFileURL(distPath).href);
115
- const pkg = parseAgentPackageModule(module);
116
- if (!pkg) {
117
- console.warn(`[agents] AgentPackage "${id}" at ${distPath} has no recognizable export.`);
118
- return null;
119
- }
120
- cache.set(id, pkg);
121
- if (!loadedPackages.has(id)) {
122
- console.log(`[agents] Loaded community agent package "${id}" from ${distPath}`);
123
- loadedPackages.add(id);
124
- }
125
- return pkg;
126
- }
127
- catch (e) {
128
- console.warn(`[agents] Failed to load agent package "${id}" from ${distPath}:`, e);
129
- return null;
130
- }
131
- }
132
- /** List built-in agent package descriptors (for marketplace/registry views). */
133
- export function listBuiltInAgentPackages() {
134
- return Object.values(BUILT_IN);
135
- }
136
- export function getAgentPackagesDir() {
137
- return agentPackagesDir;
138
- }