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.
Files changed (104) 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 +10 -19
  5. package/dist/app/server.js +208 -17
  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 +109 -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 +120 -149
  27. package/dist/plugins/bash/index.js +195 -0
  28. package/dist/plugins/delegation/index.js +121 -32
  29. package/dist/plugins/memory/index.js +103 -14
  30. package/dist/plugins/memory/service.js +152 -0
  31. package/dist/plugins/openbot/context.js +125 -0
  32. package/dist/plugins/openbot/history.js +144 -0
  33. package/dist/plugins/openbot/index.js +71 -0
  34. package/dist/plugins/openbot/runtime.js +381 -0
  35. package/dist/plugins/openbot/system-prompt.js +25 -0
  36. package/dist/plugins/plugin-manager/index.js +189 -0
  37. package/dist/plugins/shell/index.js +2 -1
  38. package/dist/plugins/storage/files.js +67 -0
  39. package/dist/plugins/storage/index.js +750 -0
  40. package/dist/plugins/storage/service.js +1316 -0
  41. package/dist/plugins/storage-tools/index.js +2 -2
  42. package/dist/plugins/thread-namer/index.js +72 -0
  43. package/dist/plugins/thread-naming/generate-title.js +44 -0
  44. package/dist/plugins/thread-naming/index.js +103 -0
  45. package/dist/plugins/threads/index.js +114 -0
  46. package/dist/plugins/todo/index.js +24 -25
  47. package/dist/plugins/ui/index.js +109 -180
  48. package/dist/registry/plugins.js +3 -9
  49. package/dist/services/abort.js +43 -0
  50. package/dist/services/plugins/domain.js +1 -0
  51. package/dist/services/plugins/plugin-cache.js +9 -0
  52. package/dist/services/plugins/registry.js +112 -0
  53. package/dist/services/plugins/service.js +232 -0
  54. package/dist/services/plugins/types.js +1 -0
  55. package/dist/services/process.js +29 -0
  56. package/dist/services/storage.js +11 -10
  57. package/dist/services/thread-naming.js +81 -0
  58. package/docs/agents.md +15 -12
  59. package/docs/architecture.md +2 -2
  60. package/docs/plugins.md +29 -17
  61. package/docs/templates/AGENT.example.md +8 -14
  62. package/package.json +1 -2
  63. package/src/app/agent-ids.ts +5 -0
  64. package/src/app/cli.ts +1 -1
  65. package/src/app/config.ts +14 -31
  66. package/src/app/server.ts +243 -19
  67. package/src/app/types.ts +331 -187
  68. package/src/harness/index.ts +166 -0
  69. package/src/plugins/approval/index.ts +107 -188
  70. package/src/plugins/bash/index.ts +232 -0
  71. package/src/plugins/delegation/index.ts +139 -39
  72. package/src/plugins/memory/index.ts +112 -15
  73. package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
  74. package/src/plugins/openbot/context.ts +140 -0
  75. package/src/plugins/openbot/history.ts +158 -0
  76. package/src/plugins/openbot/index.ts +79 -0
  77. package/src/plugins/openbot/runtime.ts +478 -0
  78. package/src/plugins/openbot/system-prompt.ts +27 -0
  79. package/src/plugins/plugin-manager/index.ts +224 -0
  80. package/src/plugins/storage/files.ts +81 -0
  81. package/src/plugins/storage/index.ts +823 -0
  82. package/src/{services/storage.ts → plugins/storage/service.ts} +485 -105
  83. package/src/plugins/ui/index.ts +117 -221
  84. package/src/services/abort.ts +46 -0
  85. package/src/{bus/types.ts → services/plugins/domain.ts} +50 -8
  86. package/src/services/plugins/plugin-cache.ts +13 -0
  87. package/src/{registry/plugins.ts → services/plugins/registry.ts} +28 -28
  88. package/src/services/plugins/service.ts +318 -0
  89. package/src/{bus/plugin.ts → services/plugins/types.ts} +7 -3
  90. package/src/bus/services.ts +0 -954
  91. package/src/harness/context.ts +0 -365
  92. package/src/harness/dispatcher.ts +0 -379
  93. package/src/harness/mcp.ts +0 -78
  94. package/src/harness/runtime-factory.ts +0 -129
  95. package/src/harness/todo-advance.ts +0 -128
  96. package/src/plugins/ai-sdk/index.ts +0 -41
  97. package/src/plugins/ai-sdk/runtime.ts +0 -468
  98. package/src/plugins/ai-sdk/system-prompt.ts +0 -18
  99. package/src/plugins/mcp/index.ts +0 -128
  100. package/src/plugins/shell/index.ts +0 -123
  101. package/src/plugins/storage-tools/index.ts +0 -90
  102. package/src/plugins/todo/index.ts +0 -64
  103. package/src/services/plugins.ts +0 -133
  104. /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
+ }