openbot 0.3.6 → 0.4.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/README.md +15 -16
- package/dist/app/agent-ids.js +4 -0
- package/dist/app/cli.js +1 -1
- package/dist/app/config.js +10 -19
- package/dist/app/server.js +208 -17
- package/dist/bus/services.js +34 -124
- package/dist/harness/agent-invoke-run.js +44 -0
- package/dist/harness/agent-turn.js +99 -0
- package/dist/harness/channel-participants.js +40 -0
- package/dist/harness/constants.js +2 -0
- package/dist/harness/context-meter.js +97 -0
- package/dist/harness/context.js +95 -47
- package/dist/harness/dispatch.js +144 -0
- package/dist/harness/dispatcher.js +45 -156
- package/dist/harness/history.js +177 -0
- package/dist/harness/index.js +109 -0
- package/dist/harness/orchestration.js +88 -0
- package/dist/harness/participants.js +22 -0
- package/dist/harness/run-harness.js +154 -0
- package/dist/harness/run.js +98 -0
- package/dist/harness/runtime-factory.js +0 -34
- package/dist/harness/runtime.js +57 -0
- package/dist/harness/todo-dispatch.js +51 -0
- package/dist/harness/todos.js +5 -0
- package/dist/harness/turn.js +79 -0
- package/dist/plugins/approval/index.js +120 -149
- package/dist/plugins/bash/index.js +195 -0
- package/dist/plugins/delegation/index.js +121 -32
- package/dist/plugins/memory/index.js +103 -14
- package/dist/plugins/memory/service.js +152 -0
- package/dist/plugins/openbot/context.js +125 -0
- package/dist/plugins/openbot/history.js +144 -0
- package/dist/plugins/openbot/index.js +71 -0
- package/dist/plugins/openbot/runtime.js +381 -0
- package/dist/plugins/openbot/system-prompt.js +25 -0
- package/dist/plugins/plugin-manager/index.js +189 -0
- package/dist/plugins/shell/index.js +2 -1
- package/dist/plugins/storage/files.js +67 -0
- package/dist/plugins/storage/index.js +750 -0
- package/dist/plugins/storage/service.js +1316 -0
- package/dist/plugins/storage-tools/index.js +2 -2
- package/dist/plugins/thread-namer/index.js +72 -0
- package/dist/plugins/thread-naming/generate-title.js +44 -0
- package/dist/plugins/thread-naming/index.js +103 -0
- package/dist/plugins/threads/index.js +114 -0
- package/dist/plugins/todo/index.js +24 -25
- package/dist/plugins/ui/index.js +109 -180
- package/dist/registry/plugins.js +3 -9
- package/dist/services/abort.js +43 -0
- package/dist/services/plugins/domain.js +1 -0
- package/dist/services/plugins/plugin-cache.js +9 -0
- package/dist/services/plugins/registry.js +112 -0
- package/dist/services/plugins/service.js +232 -0
- package/dist/services/plugins/types.js +1 -0
- package/dist/services/process.js +29 -0
- package/dist/services/storage.js +11 -10
- package/dist/services/thread-naming.js +81 -0
- package/docs/agents.md +15 -12
- package/docs/architecture.md +2 -2
- package/docs/plugins.md +29 -17
- package/docs/templates/AGENT.example.md +8 -14
- package/package.json +1 -2
- package/src/app/agent-ids.ts +5 -0
- package/src/app/cli.ts +1 -1
- package/src/app/config.ts +14 -31
- package/src/app/server.ts +243 -19
- package/src/app/types.ts +331 -187
- package/src/harness/index.ts +166 -0
- package/src/plugins/approval/index.ts +107 -188
- package/src/plugins/bash/index.ts +232 -0
- package/src/plugins/delegation/index.ts +139 -39
- package/src/plugins/memory/index.ts +112 -15
- package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
- package/src/plugins/openbot/context.ts +140 -0
- package/src/plugins/openbot/history.ts +158 -0
- package/src/plugins/openbot/index.ts +79 -0
- package/src/plugins/openbot/runtime.ts +478 -0
- package/src/plugins/openbot/system-prompt.ts +27 -0
- package/src/plugins/plugin-manager/index.ts +224 -0
- package/src/plugins/storage/files.ts +81 -0
- package/src/plugins/storage/index.ts +823 -0
- package/src/{services/storage.ts → plugins/storage/service.ts} +485 -105
- package/src/plugins/ui/index.ts +117 -221
- package/src/services/abort.ts +46 -0
- package/src/{bus/types.ts → services/plugins/domain.ts} +50 -8
- package/src/services/plugins/plugin-cache.ts +13 -0
- package/src/{registry/plugins.ts → services/plugins/registry.ts} +28 -28
- package/src/services/plugins/service.ts +318 -0
- package/src/{bus/plugin.ts → services/plugins/types.ts} +7 -3
- package/src/bus/services.ts +0 -954
- package/src/harness/context.ts +0 -365
- package/src/harness/dispatcher.ts +0 -379
- package/src/harness/mcp.ts +0 -78
- package/src/harness/runtime-factory.ts +0 -129
- package/src/harness/todo-advance.ts +0 -128
- package/src/plugins/ai-sdk/index.ts +0 -41
- package/src/plugins/ai-sdk/runtime.ts +0 -468
- package/src/plugins/ai-sdk/system-prompt.ts +0 -18
- package/src/plugins/mcp/index.ts +0 -128
- package/src/plugins/shell/index.ts +0 -123
- package/src/plugins/storage-tools/index.ts +0 -90
- package/src/plugins/todo/index.ts +0 -64
- package/src/services/plugins.ts +0 -133
- /package/src/{harness → services}/process.ts +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { Plugin } from '../../services/plugins/types.js';
|
|
2
|
+
import { STATE_AGENT_ID } from '../../app/agent-ids.js';
|
|
3
|
+
import { OpenBotEvent } from '../../app/types.js';
|
|
4
|
+
import {
|
|
5
|
+
pluginService,
|
|
6
|
+
resolveMarketplaceRegistry,
|
|
7
|
+
} from '../../services/plugins/service.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `plugin-manager` — marketplace listing, npm plugin install/uninstall, and
|
|
11
|
+
* installing agents from the registry. Wired on the **`state`** built-in agent
|
|
12
|
+
* via its default `pluginRefs`.
|
|
13
|
+
*
|
|
14
|
+
* Handlers register only when `agentId === state` so attaching this plugin to
|
|
15
|
+
* other agents via AGENT.md does not widen infra privileges.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const pluginManagerPlugin: Plugin = {
|
|
19
|
+
id: 'plugin-manager',
|
|
20
|
+
name: 'Plugin manager',
|
|
21
|
+
description:
|
|
22
|
+
'Marketplace listings, npm-based plugin lifecycle, and agent installs from marketplace metadata.',
|
|
23
|
+
factory: ({ agentId, storage }) => {
|
|
24
|
+
if (agentId !== STATE_AGENT_ID) {
|
|
25
|
+
return () => {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (builder) => {
|
|
29
|
+
builder.on('action:plugin:install', async function* (event) {
|
|
30
|
+
try {
|
|
31
|
+
const { name, version } = event.data;
|
|
32
|
+
const result = await pluginService.install({ packageName: name, version });
|
|
33
|
+
yield {
|
|
34
|
+
type: 'action:plugin:install:result',
|
|
35
|
+
data: { success: true, plugin: result },
|
|
36
|
+
} as OpenBotEvent;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
yield {
|
|
39
|
+
type: 'action:plugin:install:result',
|
|
40
|
+
data: { success: false, error: (error as Error).message },
|
|
41
|
+
} as OpenBotEvent;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
builder.on('action:plugin:uninstall', async function* (event) {
|
|
46
|
+
try {
|
|
47
|
+
await pluginService.uninstall(event.data.id);
|
|
48
|
+
yield { type: 'action:plugin:uninstall:result', data: { success: true } };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
yield {
|
|
51
|
+
type: 'action:plugin:uninstall:result',
|
|
52
|
+
data: { success: false, error: (error as Error).message },
|
|
53
|
+
} as OpenBotEvent;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
builder.on('action:marketplace:list', async function* () {
|
|
58
|
+
const { agents, channels } = await resolveMarketplaceRegistry();
|
|
59
|
+
yield {
|
|
60
|
+
type: 'action:marketplace:list:result',
|
|
61
|
+
data: { success: true, agents, channels },
|
|
62
|
+
} as OpenBotEvent;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
builder.on('action:channel:install', async function* (event) {
|
|
66
|
+
try {
|
|
67
|
+
const {
|
|
68
|
+
channelId: instanceId,
|
|
69
|
+
name: templateName,
|
|
70
|
+
participants: customParticipants,
|
|
71
|
+
initialState: customInitialState,
|
|
72
|
+
} = event.data;
|
|
73
|
+
const { agents: marketplaceAgents, channels } = await resolveMarketplaceRegistry();
|
|
74
|
+
|
|
75
|
+
// Try to find the template by ID or Name
|
|
76
|
+
const channelListing =
|
|
77
|
+
channels.find((c) => c.id === instanceId) ||
|
|
78
|
+
channels.find((c) => c.name === templateName);
|
|
79
|
+
|
|
80
|
+
const channelId = instanceId;
|
|
81
|
+
const participants = customParticipants || channelListing?.participants || [];
|
|
82
|
+
const initialState = {
|
|
83
|
+
...(channelListing?.initialState || {}),
|
|
84
|
+
...(customInitialState || {}),
|
|
85
|
+
};
|
|
86
|
+
const spec = channelListing?.spec || '';
|
|
87
|
+
|
|
88
|
+
// 1. Auto-install participant agents if missing
|
|
89
|
+
for (const agentId of participants) {
|
|
90
|
+
const existingAgents = await storage.getAgents();
|
|
91
|
+
if (existingAgents.some((a) => a.id === agentId)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Not found locally, look in marketplace
|
|
96
|
+
const agentListing = marketplaceAgents.find((a) => a.id === agentId);
|
|
97
|
+
if (agentListing) {
|
|
98
|
+
console.log(`[plugin-manager] Auto-installing agent ${agentId} for channel ${channelId}`);
|
|
99
|
+
|
|
100
|
+
// Install plugins for this agent
|
|
101
|
+
for (const ref of agentListing.plugins) {
|
|
102
|
+
const installed = await pluginService.isInstalled(ref.id);
|
|
103
|
+
if (
|
|
104
|
+
!installed &&
|
|
105
|
+
ref.id.includes('/') === false &&
|
|
106
|
+
ref.id.includes('-plugin-') === false
|
|
107
|
+
) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!installed) {
|
|
111
|
+
try {
|
|
112
|
+
await pluginService.install({ packageName: ref.id });
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.warn(`[plugins] Failed to pre-install plugin ${ref.id}`, err);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create the agent
|
|
120
|
+
await storage.createAgent({
|
|
121
|
+
agentId: agentListing.id,
|
|
122
|
+
name: agentListing.name,
|
|
123
|
+
description: agentListing.description,
|
|
124
|
+
image: agentListing.image,
|
|
125
|
+
instructions: agentListing.instructions,
|
|
126
|
+
plugins: agentListing.plugins,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. Create the channel
|
|
132
|
+
await storage.createChannel({
|
|
133
|
+
channelId,
|
|
134
|
+
spec,
|
|
135
|
+
initialState: {
|
|
136
|
+
...initialState,
|
|
137
|
+
participants,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const channelUrl = `/channels/${channelId}`;
|
|
142
|
+
yield {
|
|
143
|
+
type: 'action:channel:install:result',
|
|
144
|
+
data: { success: true, channelId, channelUrl },
|
|
145
|
+
} as OpenBotEvent;
|
|
146
|
+
|
|
147
|
+
yield {
|
|
148
|
+
type: 'agent:output',
|
|
149
|
+
data: {
|
|
150
|
+
content: `Successfully installed channel **${
|
|
151
|
+
channelListing?.name || templateName || channelId
|
|
152
|
+
}** and created channel \`${channelId}\`.`,
|
|
153
|
+
},
|
|
154
|
+
meta: { agentId: 'system' },
|
|
155
|
+
} as OpenBotEvent;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
yield {
|
|
158
|
+
type: 'action:channel:install:result',
|
|
159
|
+
data: {
|
|
160
|
+
success: false,
|
|
161
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
162
|
+
},
|
|
163
|
+
} as OpenBotEvent;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
builder.on('action:agent:install', async function* (event) {
|
|
168
|
+
try {
|
|
169
|
+
const {
|
|
170
|
+
agentId: newAgentId,
|
|
171
|
+
name,
|
|
172
|
+
description,
|
|
173
|
+
image,
|
|
174
|
+
instructions,
|
|
175
|
+
plugins,
|
|
176
|
+
} = event.data;
|
|
177
|
+
|
|
178
|
+
for (const ref of plugins) {
|
|
179
|
+
const installed = await pluginService.isInstalled(ref.id);
|
|
180
|
+
if (!installed && ref.id.includes('/') === false && ref.id.includes('-plugin-') === false) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!installed) {
|
|
184
|
+
try {
|
|
185
|
+
await pluginService.install({ packageName: ref.id });
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.warn(`[plugins] Failed to pre-install plugin ${ref.id}`, err);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await storage.createAgent({
|
|
193
|
+
agentId: newAgentId,
|
|
194
|
+
name,
|
|
195
|
+
description,
|
|
196
|
+
image,
|
|
197
|
+
instructions,
|
|
198
|
+
plugins,
|
|
199
|
+
});
|
|
200
|
+
yield {
|
|
201
|
+
type: 'action:agent:install:result',
|
|
202
|
+
data: { success: true, agentId: newAgentId },
|
|
203
|
+
} as OpenBotEvent;
|
|
204
|
+
yield {
|
|
205
|
+
type: 'agent:output',
|
|
206
|
+
data: {
|
|
207
|
+
content: `Successfully installed agent **${name}** (${newAgentId}) from marketplace.`,
|
|
208
|
+
},
|
|
209
|
+
meta: { agentId: 'system' },
|
|
210
|
+
} as OpenBotEvent;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
yield {
|
|
213
|
+
type: 'action:agent:install:result',
|
|
214
|
+
data: {
|
|
215
|
+
success: false,
|
|
216
|
+
agentId: event.data.agentId,
|
|
217
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
218
|
+
},
|
|
219
|
+
} as OpenBotEvent;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolvePath } from '../../app/config.js';
|
|
5
|
+
|
|
6
|
+
const MIME_BY_EXT: Record<string, string> = {
|
|
7
|
+
'.png': 'image/png',
|
|
8
|
+
'.jpg': 'image/jpeg',
|
|
9
|
+
'.jpeg': 'image/jpeg',
|
|
10
|
+
'.gif': 'image/gif',
|
|
11
|
+
'.webp': 'image/webp',
|
|
12
|
+
'.svg': 'image/svg+xml',
|
|
13
|
+
'.ico': 'image/x-icon',
|
|
14
|
+
'.mp4': 'video/mp4',
|
|
15
|
+
'.webm': 'video/webm',
|
|
16
|
+
'.mov': 'video/quicktime',
|
|
17
|
+
'.mp3': 'audio/mpeg',
|
|
18
|
+
'.wav': 'audio/wav',
|
|
19
|
+
'.ogg': 'audio/ogg',
|
|
20
|
+
'.pdf': 'application/pdf',
|
|
21
|
+
'.json': 'application/json',
|
|
22
|
+
'.txt': 'text/plain',
|
|
23
|
+
'.html': 'text/html',
|
|
24
|
+
'.css': 'text/css',
|
|
25
|
+
'.js': 'text/javascript',
|
|
26
|
+
'.zip': 'application/zip',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function guessMimeType(filePath: string): string {
|
|
30
|
+
return MIME_BY_EXT[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Resolve a relative path under a channel cwd; rejects directory escape. */
|
|
34
|
+
export function resolveChannelFile(baseCwd: string, relativePath: string): string {
|
|
35
|
+
const resolvedBase = resolvePath(baseCwd);
|
|
36
|
+
const normalized = relativePath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
37
|
+
const target = path.resolve(resolvedBase, normalized);
|
|
38
|
+
if (target !== resolvedBase && !target.startsWith(resolvedBase + path.sep)) {
|
|
39
|
+
throw new Error('Access denied: directory escape');
|
|
40
|
+
}
|
|
41
|
+
return target;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function statChannelFile(
|
|
45
|
+
baseCwd: string,
|
|
46
|
+
relativePath: string,
|
|
47
|
+
): Promise<{ abs: string; size: number }> {
|
|
48
|
+
const abs = resolveChannelFile(baseCwd, relativePath);
|
|
49
|
+
const stat = await fs.stat(abs);
|
|
50
|
+
if (!stat.isFile()) {
|
|
51
|
+
throw new Error('Not a file');
|
|
52
|
+
}
|
|
53
|
+
return { abs, size: stat.size };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function openChannelFileStream(abs: string) {
|
|
57
|
+
return createReadStream(abs);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildWorkspaceFileUrl(args: {
|
|
61
|
+
baseUrl: string;
|
|
62
|
+
channelId: string;
|
|
63
|
+
filePath: string;
|
|
64
|
+
}): string {
|
|
65
|
+
const base = args.baseUrl.replace(/\/$/, '');
|
|
66
|
+
const data = encodeURIComponent(JSON.stringify({ path: args.filePath }));
|
|
67
|
+
const channelId = encodeURIComponent(args.channelId);
|
|
68
|
+
return `${base}/api/state?channelId=${channelId}&type=${encodeURIComponent('action:storage:serve-file')}&data=${data}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getPublicBaseUrl(port: number, configPublicUrl?: string): string {
|
|
72
|
+
const fromConfig = configPublicUrl?.trim();
|
|
73
|
+
if (fromConfig) {
|
|
74
|
+
return fromConfig.replace(/\/$/, '');
|
|
75
|
+
}
|
|
76
|
+
const fromEnv = process.env.OPENBOT_PUBLIC_URL?.trim();
|
|
77
|
+
if (fromEnv) {
|
|
78
|
+
return fromEnv.replace(/\/$/, '');
|
|
79
|
+
}
|
|
80
|
+
return `http://localhost:${port}`;
|
|
81
|
+
}
|