openbot 0.4.7 → 0.5.5

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 (139) 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 +51 -0
  11. package/Dockerfile +41 -0
  12. package/deploy/README.md +59 -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 +5 -1
  17. package/dist/app/cloud-mode.js +15 -0
  18. package/dist/app/config.js +61 -16
  19. package/dist/app/ensure-default-stack.js +54 -0
  20. package/dist/app/openbot-plugin.js +4 -0
  21. package/dist/app/server.js +37 -6
  22. package/dist/harness/index.js +3 -0
  23. package/dist/plugins/approval/index.js +1 -1
  24. package/dist/plugins/bash/index.js +386 -164
  25. package/dist/plugins/memory/service.js +2 -5
  26. package/dist/plugins/openbot/context.js +17 -1
  27. package/dist/plugins/openbot/index.js +23 -7
  28. package/dist/plugins/openbot/model.js +20 -0
  29. package/dist/plugins/openbot/runtime.js +45 -57
  30. package/dist/plugins/openbot/system-prompt.js +43 -8
  31. package/dist/plugins/preview/index.js +267 -0
  32. package/dist/plugins/storage/index.js +3 -325
  33. package/dist/plugins/storage/service.js +61 -18
  34. package/dist/plugins/todo/index.js +131 -38
  35. package/dist/plugins/todo/service.js +93 -0
  36. package/dist/plugins/ui/index.js +8 -1
  37. package/dist/services/plugins/host.js +21 -0
  38. package/dist/services/plugins/model-registry.js +126 -0
  39. package/dist/services/plugins/registry.js +28 -42
  40. package/dist/services/plugins/service.js +2 -6
  41. package/docs/agents.md +20 -2
  42. package/docs/architecture.md +1 -1
  43. package/docs/plugins.md +13 -3
  44. package/docs/templates/AGENT.example.md +8 -14
  45. package/package.json +5 -2
  46. package/pnpm-workspace.yaml +2 -0
  47. package/src/app/bootstrap.ts +43 -0
  48. package/src/app/cli.ts +5 -1
  49. package/src/app/cloud-mode.ts +26 -0
  50. package/src/app/config.ts +71 -19
  51. package/src/app/ensure-default-stack.ts +63 -0
  52. package/src/app/openbot-plugin.ts +5 -0
  53. package/src/app/server.ts +42 -6
  54. package/src/app/types.ts +158 -32
  55. package/src/harness/index.ts +4 -0
  56. package/src/plugins/memory/service.ts +2 -5
  57. package/src/plugins/storage/index.ts +3 -371
  58. package/src/plugins/storage/service.ts +81 -22
  59. package/src/services/plugins/domain.ts +2 -0
  60. package/src/services/plugins/host.ts +36 -0
  61. package/src/services/plugins/model-registry.ts +178 -0
  62. package/src/services/plugins/registry.ts +30 -43
  63. package/src/services/plugins/service.ts +8 -17
  64. package/src/services/plugins/types.ts +36 -2
  65. package/src/services/todo/types.ts +12 -0
  66. package/dist/agents/openbot/index.js +0 -76
  67. package/dist/agents/openbot/middleware/approval.js +0 -132
  68. package/dist/agents/openbot/runtime.js +0 -289
  69. package/dist/agents/openbot/system-prompt.js +0 -32
  70. package/dist/agents/openbot/tools/delegation.js +0 -78
  71. package/dist/agents/openbot/tools/mcp.js +0 -99
  72. package/dist/agents/openbot/tools/shell.js +0 -91
  73. package/dist/agents/openbot/tools/storage.js +0 -75
  74. package/dist/agents/openbot/tools/ui.js +0 -176
  75. package/dist/agents/system.js +0 -33
  76. package/dist/bus/agent-package.js +0 -1
  77. package/dist/bus/plugin.js +0 -1
  78. package/dist/bus/services.js +0 -754
  79. package/dist/harness/agent-harness.js +0 -45
  80. package/dist/harness/agent-invoke-run.js +0 -44
  81. package/dist/harness/agent-turn.js +0 -99
  82. package/dist/harness/channel-participants.js +0 -40
  83. package/dist/harness/constants.js +0 -2
  84. package/dist/harness/context-meter.js +0 -97
  85. package/dist/harness/context.js +0 -363
  86. package/dist/harness/dispatch.js +0 -144
  87. package/dist/harness/dispatcher.js +0 -156
  88. package/dist/harness/event-normalizer.js +0 -59
  89. package/dist/harness/history.js +0 -177
  90. package/dist/harness/mcp.js +0 -61
  91. package/dist/harness/orchestration.js +0 -88
  92. package/dist/harness/orchestrator.js +0 -149
  93. package/dist/harness/participants.js +0 -22
  94. package/dist/harness/process.js +0 -29
  95. package/dist/harness/queue-processor.js +0 -187
  96. package/dist/harness/run-harness.js +0 -154
  97. package/dist/harness/run.js +0 -98
  98. package/dist/harness/runtime-factory.js +0 -73
  99. package/dist/harness/runtime.js +0 -57
  100. package/dist/harness/todo-advance.js +0 -93
  101. package/dist/harness/todo-dispatch.js +0 -51
  102. package/dist/harness/todos.js +0 -5
  103. package/dist/harness/turn.js +0 -79
  104. package/dist/harness/types.js +0 -1
  105. package/dist/plugins/ai-sdk/index.js +0 -34
  106. package/dist/plugins/ai-sdk/runtime.js +0 -395
  107. package/dist/plugins/ai-sdk/system-prompt.js +0 -18
  108. package/dist/plugins/ai-sdk.js +0 -331
  109. package/dist/plugins/approval.js +0 -163
  110. package/dist/plugins/delegation.js +0 -108
  111. package/dist/plugins/mcp/index.js +0 -108
  112. package/dist/plugins/mcp.js +0 -140
  113. package/dist/plugins/shell/index.js +0 -100
  114. package/dist/plugins/shell.js +0 -123
  115. package/dist/plugins/storage-tools/index.js +0 -76
  116. package/dist/plugins/storage.js +0 -737
  117. package/dist/plugins/thread-namer/index.js +0 -72
  118. package/dist/plugins/threads/index.js +0 -114
  119. package/dist/plugins/ui.js +0 -211
  120. package/dist/plugins/workflow/index.js +0 -65
  121. package/dist/registry/agents.js +0 -138
  122. package/dist/registry/plugins.js +0 -110
  123. package/dist/services/agent-packages.js +0 -103
  124. package/dist/services/memory.js +0 -152
  125. package/dist/services/plugins.js +0 -98
  126. package/dist/services/storage.js +0 -1028
  127. package/dist/workflow/service.js +0 -106
  128. package/dist/workflow/types.js +0 -3
  129. package/src/plugins/approval/index.ts +0 -147
  130. package/src/plugins/bash/index.ts +0 -232
  131. package/src/plugins/delegation/index.ts +0 -153
  132. package/src/plugins/memory/index.ts +0 -182
  133. package/src/plugins/openbot/context.ts +0 -121
  134. package/src/plugins/openbot/history.ts +0 -158
  135. package/src/plugins/openbot/index.ts +0 -80
  136. package/src/plugins/openbot/runtime.ts +0 -526
  137. package/src/plugins/openbot/system-prompt.ts +0 -22
  138. package/src/plugins/ui/index.ts +0 -123
  139. /package/dist/{bus → services/todo}/types.js +0 -0
@@ -1,90 +1,12 @@
1
- import z from 'zod';
2
1
  import { buildWorkspaceFileUrl } from './files.js';
3
2
  /**
4
- * `storage` — exposes channel/thread/variable mutation tools and provides
5
- * platform-level storage handlers.
3
+ * `storage` — platform infra handlers for `/api/state` and deterministic reads.
4
+ * Agent-facing storage tools live in `@meetopenbot/openbot`.
6
5
  */
7
- const storageToolDefinitions = {
8
- create_channel: {
9
- description: 'Create a new channel. Use when the user intent is clearly different from the current channel and should be split. Always confirm before creating. Skip for simple Q&A.',
10
- inputSchema: z.object({
11
- channelId: z
12
- .string()
13
- .describe('Unique channel ID (e.g. product-launch, backend-platform, channel_roadmap).'),
14
- spec: z
15
- .string()
16
- .optional()
17
- .describe('Optional initial markdown content for the channel spec.'),
18
- initialState: z
19
- .record(z.string(), z.unknown())
20
- .optional()
21
- .describe('Optional initial state object for the channel.'),
22
- cwd: z
23
- .string()
24
- .optional()
25
- .describe('Optional initial current working directory for the channel. Defaults to an absolute path under ~/openbot/{channelId}.'),
26
- }),
27
- },
28
- patch_channel_details: {
29
- description: 'Patch current channel details (state, spec, cwd).',
30
- inputSchema: z
31
- .object({
32
- state: z
33
- .record(z.string(), z.unknown())
34
- .optional()
35
- .describe('JSON state object for the channel. Use for structured metadata.'),
36
- spec: z
37
- .string()
38
- .optional()
39
- .describe('Markdown content for the channel specification (SPEC.md). Use for goals and rules.'),
40
- cwd: z.string().optional().describe('Current working directory for the channel.'),
41
- })
42
- .refine((value) => value.state !== undefined ||
43
- value.spec !== undefined ||
44
- value.cwd !== undefined, { message: 'Provide at least one of state, spec, or cwd.' }),
45
- },
46
- patch_thread_details: {
47
- description: 'Patch current thread details (state).',
48
- inputSchema: z.object({
49
- state: z
50
- .record(z.string(), z.unknown())
51
- .describe('JSON state object for the thread. Use for structured progress or metadata.'),
52
- }),
53
- },
54
- create_variable: {
55
- description: 'Create or update a variable in the workspace storage.',
56
- inputSchema: z.object({
57
- key: z.string().describe('The key of the variable.'),
58
- value: z.string().describe('The value of the variable.'),
59
- secret: z.boolean().optional().describe('Whether the variable is a secret.'),
60
- }),
61
- },
62
- delete_variable: {
63
- description: 'Delete a variable from the workspace storage.',
64
- inputSchema: z.object({
65
- key: z.string().describe('The key of the variable to delete.'),
66
- }),
67
- },
68
- delete_channel: {
69
- description: 'Permanently delete a channel and all its threads and events. Always confirm with the user before deleting.',
70
- inputSchema: z.object({
71
- channelId: z.string().describe('The channel ID to delete.'),
72
- }),
73
- },
74
- get_workspace_file_url: {
75
- description: 'Get a fetchable HTTP URL for a file in the current channel workspace (images, video, audio, documents).',
76
- inputSchema: z.object({
77
- path: z
78
- .string()
79
- .describe('Path relative to the channel working directory, e.g. "uploads/clip.mp4".'),
80
- }),
81
- },
82
- };
83
6
  export const storagePlugin = {
84
7
  id: 'storage',
85
8
  name: 'Storage',
86
- description: 'Tools for creating channels, patching state, and managing workspace variables.',
87
- toolDefinitions: storageToolDefinitions,
9
+ description: 'Infrastructure storage handlers for channels, threads, agents, and files.',
88
10
  factory: ({ storage, publicBaseUrl }) => (builder) => {
89
11
  const resolvePublicBaseUrl = () => publicBaseUrl;
90
12
  builder.on('action:create_thread', async function* (event, context) {
@@ -119,215 +41,6 @@ export const storagePlugin = {
119
41
  meta: { ...(event.meta || {}), threadId, agentId: context.state.agentId },
120
42
  };
121
43
  });
122
- builder.on('action:create_channel', async function* (event, context) {
123
- const { channelId, spec, initialState, cwd } = event.data;
124
- const rawChannelId = (channelId || '').trim();
125
- const channelSpec = typeof spec === 'string' ? spec : '';
126
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
127
- if (!rawChannelId) {
128
- yield {
129
- type: 'action:create_channel:result',
130
- data: { success: false, channelId: '', channelUrl: '' },
131
- meta: resultMeta,
132
- };
133
- return;
134
- }
135
- const channelUrl = `/channels/${rawChannelId}`;
136
- const mergedInitial = { ...(initialState || {}) };
137
- try {
138
- await storage.createChannel({
139
- channelId: rawChannelId,
140
- spec: channelSpec,
141
- initialState: mergedInitial,
142
- cwd,
143
- });
144
- yield {
145
- type: 'action:create_channel:result',
146
- data: { success: true, channelId: rawChannelId, channelUrl },
147
- meta: resultMeta,
148
- };
149
- yield {
150
- type: 'agent:output',
151
- data: { content: `Created channel \`${rawChannelId}\`.` },
152
- meta: resultMeta,
153
- };
154
- }
155
- catch {
156
- yield {
157
- type: 'action:create_channel:result',
158
- data: { success: false, channelId: rawChannelId, channelUrl },
159
- meta: resultMeta,
160
- };
161
- }
162
- });
163
- builder.on('action:delete_channel', async function* (event, context) {
164
- const rawChannelId = (event.data?.channelId || '').trim();
165
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
166
- if (!rawChannelId) {
167
- yield {
168
- type: 'action:delete_channel:result',
169
- data: { success: false, channelId: '', error: 'channelId is required' },
170
- meta: resultMeta,
171
- };
172
- return;
173
- }
174
- try {
175
- await storage.deleteChannel({ channelId: rawChannelId });
176
- yield {
177
- type: 'action:delete_channel:result',
178
- data: { success: true, channelId: rawChannelId },
179
- meta: resultMeta,
180
- };
181
- yield {
182
- type: 'agent:output',
183
- data: { content: `Deleted channel \`${rawChannelId}\`.` },
184
- meta: resultMeta,
185
- };
186
- }
187
- catch (error) {
188
- yield {
189
- type: 'action:delete_channel:result',
190
- data: {
191
- success: false,
192
- channelId: rawChannelId,
193
- error: error instanceof Error ? error.message : 'Unknown error',
194
- },
195
- meta: resultMeta,
196
- };
197
- }
198
- });
199
- builder.on('action:update_channel', async function* (event, context) {
200
- const data = (event.data || {});
201
- const targetChannelId = (data.channelId || context.state.channelId || '').trim();
202
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
203
- if (!targetChannelId) {
204
- yield {
205
- type: 'action:update_channel:result',
206
- data: { success: false, channelId: '', updatedFields: [] },
207
- meta: resultMeta,
208
- };
209
- return;
210
- }
211
- const patch = {};
212
- const updatedFields = [];
213
- if (typeof data.name === 'string' && data.name.trim()) {
214
- patch.name = data.name.trim();
215
- updatedFields.push('name');
216
- }
217
- if (typeof data.cwd === 'string' && data.cwd.trim()) {
218
- patch.cwd = data.cwd.trim();
219
- updatedFields.push('cwd');
220
- }
221
- try {
222
- if (updatedFields.length > 0) {
223
- await storage.patchChannelState({ channelId: targetChannelId, state: patch });
224
- }
225
- if (targetChannelId === context.state.channelId) {
226
- context.state.channelDetails = await storage.getChannelDetails({
227
- channelId: context.state.channelId,
228
- });
229
- }
230
- yield {
231
- type: 'action:update_channel:result',
232
- data: { success: true, channelId: targetChannelId, updatedFields },
233
- meta: resultMeta,
234
- };
235
- }
236
- catch {
237
- yield {
238
- type: 'action:update_channel:result',
239
- data: { success: false, channelId: targetChannelId, updatedFields },
240
- meta: resultMeta,
241
- };
242
- }
243
- });
244
- builder.on('action:patch_channel_details', async function* (event, context) {
245
- const updatedFields = [];
246
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
247
- const data = (event.data || {});
248
- try {
249
- if (data.state !== undefined) {
250
- await storage.patchChannelState({
251
- channelId: context.state.channelId,
252
- state: data.state,
253
- });
254
- updatedFields.push('state');
255
- }
256
- if (typeof data.spec === 'string') {
257
- await storage.patchChannelSpec({
258
- channelId: context.state.channelId,
259
- spec: data.spec,
260
- });
261
- updatedFields.push('spec');
262
- }
263
- if (typeof data.cwd === 'string') {
264
- await storage.patchChannelState({
265
- channelId: context.state.channelId,
266
- state: { cwd: data.cwd },
267
- });
268
- updatedFields.push('cwd');
269
- }
270
- context.state.channelDetails = await storage.getChannelDetails({
271
- channelId: context.state.channelId,
272
- });
273
- yield {
274
- type: "client:ui:widget",
275
- data: {
276
- widgetId: "patch-channel-details-result" + Date.now(),
277
- kind: "message",
278
- title: "Channel details updated.",
279
- body: `The channel details have been updated. ${updatedFields.join(', ')}`,
280
- display: "collapsed",
281
- },
282
- meta: resultMeta,
283
- };
284
- yield {
285
- type: 'action:patch_channel_details:result',
286
- data: { success: true, updatedFields },
287
- meta: resultMeta,
288
- };
289
- }
290
- catch {
291
- yield {
292
- type: 'action:patch_channel_details:result',
293
- data: { success: false, updatedFields },
294
- meta: resultMeta,
295
- };
296
- }
297
- });
298
- builder.on('action:patch_thread_details', async function* (event, context) {
299
- const updatedFields = [];
300
- const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
301
- try {
302
- if (!context.state.threadId) {
303
- throw new Error('Missing threadId in state for patch_thread_details');
304
- }
305
- if (event.data.state !== undefined) {
306
- await storage.patchThreadState({
307
- channelId: context.state.channelId,
308
- threadId: context.state.threadId,
309
- state: event.data.state,
310
- });
311
- updatedFields.push('state');
312
- }
313
- context.state.threadDetails = await storage.getThreadDetails({
314
- channelId: context.state.channelId,
315
- threadId: context.state.threadId,
316
- });
317
- yield {
318
- type: 'action:patch_thread_details:result',
319
- data: { success: true, updatedFields },
320
- meta: resultMeta,
321
- };
322
- }
323
- catch {
324
- yield {
325
- type: 'action:patch_thread_details:result',
326
- data: { success: false, updatedFields },
327
- meta: resultMeta,
328
- };
329
- }
330
- });
331
44
  builder.on('agent:usage', async function* (event, context) {
332
45
  const { usage } = event.data;
333
46
  if (!context.state.threadId)
@@ -671,41 +384,6 @@ export const storagePlugin = {
671
384
  };
672
385
  }
673
386
  });
674
- builder.on('action:get_workspace_file_url', async function* (event, context) {
675
- const channelId = context.state.channelId;
676
- const filePath = event.data?.path;
677
- const toolCallId = event.meta?.toolCallId;
678
- if (!filePath) {
679
- yield {
680
- type: 'action:get_workspace_file_url:result',
681
- data: { success: false, path: '', error: 'Path is required', output: 'Path is required' },
682
- meta: { ...(event.meta || {}), toolCallId },
683
- };
684
- return;
685
- }
686
- try {
687
- const { size, mimeType } = await storage.getChannelFileStat({ channelId, path: filePath });
688
- const url = buildWorkspaceFileUrl({
689
- baseUrl: resolvePublicBaseUrl(),
690
- channelId,
691
- filePath,
692
- });
693
- const output = JSON.stringify({ path: filePath, url, mimeType, size });
694
- yield {
695
- type: 'action:get_workspace_file_url:result',
696
- data: { success: true, path: filePath, url, mimeType, size, output },
697
- meta: { ...(event.meta || {}), toolCallId },
698
- };
699
- }
700
- catch (error) {
701
- const message = error instanceof Error ? error.message : 'Unknown error';
702
- yield {
703
- type: 'action:get_workspace_file_url:result',
704
- data: { success: false, path: filePath, error: message, output: message },
705
- meta: { ...(event.meta || {}), toolCallId },
706
- };
707
- }
708
- });
709
387
  },
710
388
  };
711
389
  export default storagePlugin;
@@ -1,20 +1,19 @@
1
1
  import { ORCHESTRATOR_AGENT_ID, STATE_AGENT_ID } from '../../app/agent-ids.js';
2
- import { DEFAULT_PLUGINS_DIR, DEFAULT_AGENTS_DIR, DEFAULT_BASE_DIR, DEFAULT_CHANNELS_DIR, getDefaultChannelCwd, loadConfig, resolvePath, VARIABLES_FILE, } from '../../app/config.js';
2
+ import { assertCloudSystemAgentPluginsMutable, DEFAULT_CLOUD_OPENBOT_AUTH_MODE, isCloudMode, } from '../../app/cloud-mode.js';
3
+ import { DEFAULT_PLUGINS_DIR, DEFAULT_AGENTS_DIR, DEFAULT_CHANNELS_DIR, getBaseDir, getDefaultChannelCwd, resolvePath, VARIABLES_FILE, } from '../../app/config.js';
3
4
  import fs from 'node:fs/promises';
4
5
  import { readFileSync } from 'node:fs';
5
6
  import path from 'node:path';
6
7
  import { fileURLToPath, pathToFileURL } from 'node:url';
7
8
  import crypto from 'node:crypto';
8
9
  import matter from 'gray-matter';
9
- import { openbotPlugin } from '../openbot/index.js';
10
+ import { OPENBOT_PLUGIN_ID } from '../../app/openbot-plugin.js';
10
11
  import { listBuiltInPlugins, parsePluginModule } from '../../services/plugins/registry.js';
12
+ import { enrichOpenbotPluginDescriptor, listRegistryModelOptions, } from '../../services/plugins/model-registry.js';
11
13
  import { processService } from '../../services/process.js';
12
14
  import { memoryService } from '../memory/service.js';
13
15
  import { guessMimeType, resolveChannelFile, statChannelFile, } from './files.js';
14
- const resolveBaseDir = () => {
15
- const config = loadConfig();
16
- return resolvePath(config.baseDir || DEFAULT_BASE_DIR);
17
- };
16
+ const resolveBaseDir = () => getBaseDir();
18
17
  const ENTITY_SVG_CANDIDATE_NAMES = ['avatar.svg', 'icon.svg', 'image.svg', 'logo.svg'];
19
18
  const toSvgDataUrl = (svg) => `data:image/svg+xml;base64,${Buffer.from(svg, 'utf-8').toString('base64')}`;
20
19
  let bundledSystemAgentImage;
@@ -81,15 +80,40 @@ const getConversationDir = (channelId, threadId) => {
81
80
  const SYSTEM_AGENT_ID = ORCHESTRATOR_AGENT_ID;
82
81
  const SYSTEM_DEFAULT_PLUGINS = [
83
82
  {
84
- id: 'openbot',
83
+ id: OPENBOT_PLUGIN_ID,
85
84
  config: {
86
85
  model: 'openai/gpt-5.4-mini',
87
- approval: {
88
- actions: ['action:bash', 'action:create_channel', 'action:delete_channel'],
89
- },
90
86
  },
91
87
  },
92
88
  ];
89
+ function getCloudSystemDefaultPlugins() {
90
+ return SYSTEM_DEFAULT_PLUGINS.map((ref) => {
91
+ if (ref.id !== OPENBOT_PLUGIN_ID)
92
+ return ref;
93
+ return {
94
+ ...ref,
95
+ config: {
96
+ ...ref.config,
97
+ authMode: DEFAULT_CLOUD_OPENBOT_AUTH_MODE,
98
+ },
99
+ };
100
+ });
101
+ }
102
+ function mergeCloudSystemPluginRefs(defaults, diskRefs) {
103
+ const defaultOpenbot = defaults.find((ref) => ref.id === OPENBOT_PLUGIN_ID);
104
+ const diskOpenbot = diskRefs.find((ref) => ref.id === OPENBOT_PLUGIN_ID);
105
+ if (!defaultOpenbot)
106
+ return defaults;
107
+ return [
108
+ {
109
+ ...defaultOpenbot,
110
+ config: {
111
+ ...defaultOpenbot.config,
112
+ ...diskOpenbot?.config,
113
+ },
114
+ },
115
+ ];
116
+ }
93
117
  /** No `openbot` / `bash` — storage-side effects and infra plugins only. */
94
118
  const STATE_DEFAULT_PLUGINS = [
95
119
  { id: 'storage' },
@@ -97,24 +121,41 @@ const STATE_DEFAULT_PLUGINS = [
97
121
  ];
98
122
  const STATE_AGENT_INSTRUCTIONS = 'Built-in infra agent for deterministic state reads. No conversational model is attached; handle storage, approvals, memory, and plugin marketplace events.';
99
123
  function getSystemAgentDetails(overrides) {
124
+ const defaultPlugins = isCloudMode() ? getCloudSystemDefaultPlugins() : SYSTEM_DEFAULT_PLUGINS;
100
125
  const defaults = {
101
126
  id: SYSTEM_AGENT_ID,
102
127
  name: 'OpenBot',
103
128
  image: getBundledSystemAgentImage(),
104
129
  description: 'First-party orchestration agent for OpenBot.',
105
130
  instructions: '',
106
- plugins: SYSTEM_DEFAULT_PLUGINS.map((ref) => ref.id),
107
- pluginRefs: SYSTEM_DEFAULT_PLUGINS,
131
+ plugins: defaultPlugins.map((ref) => ref.id),
132
+ pluginRefs: defaultPlugins,
108
133
  createdAt: new Date(),
109
134
  updatedAt: new Date(),
110
135
  };
111
136
  if (!overrides)
112
137
  return defaults;
138
+ const diskInstructions = overrides.instructions?.trim();
139
+ const instructions = diskInstructions && diskInstructions.length > 0 ? diskInstructions : defaults.instructions;
140
+ if (isCloudMode()) {
141
+ const diskRefs = overrides.pluginRefs && overrides.pluginRefs.length > 0 ? overrides.pluginRefs : undefined;
142
+ const refs = diskRefs
143
+ ? mergeCloudSystemPluginRefs(defaults.pluginRefs, diskRefs)
144
+ : defaults.pluginRefs;
145
+ return {
146
+ ...defaults,
147
+ ...overrides,
148
+ id: SYSTEM_AGENT_ID,
149
+ instructions,
150
+ image: overrides.image || defaults.image,
151
+ plugins: refs.map((ref) => ref.id),
152
+ pluginRefs: refs,
153
+ updatedAt: new Date(),
154
+ };
155
+ }
113
156
  const refs = overrides.pluginRefs && overrides.pluginRefs.length > 0
114
157
  ? overrides.pluginRefs
115
158
  : defaults.pluginRefs;
116
- const diskInstructions = overrides.instructions?.trim();
117
- const instructions = diskInstructions && diskInstructions.length > 0 ? diskInstructions : defaults.instructions;
118
159
  return {
119
160
  ...defaults,
120
161
  ...overrides,
@@ -168,8 +209,6 @@ const agentSummaryFromDetails = (details) => ({
168
209
  createdAt: details.createdAt,
169
210
  updatedAt: details.updatedAt,
170
211
  });
171
- // Suppress unused warning until system agent customization re-uses openbotPlugin metadata.
172
- void openbotPlugin;
173
212
  /** Built-in agents may persist optional `agents/<id>/AGENT.md` overlays; read path merges them with defaults. */
174
213
  const isBuiltinOverlayAgentId = (agentId) => agentId === SYSTEM_AGENT_ID || agentId === STATE_AGENT_ID;
175
214
  const assertAgentIdFormat = (agentId) => {
@@ -775,11 +814,13 @@ export const storageService = {
775
814
  return Array.from(deduped.values()).filter((agent) => !agent.hidden);
776
815
  },
777
816
  getPlugins: async () => {
778
- const [builtIn, fromDisk] = await Promise.all([
817
+ const [builtIn, fromDisk, modelOptions] = await Promise.all([
779
818
  listBuiltInPluginDescriptors(),
780
819
  listPluginsFromDisk(),
820
+ listRegistryModelOptions(),
781
821
  ]);
782
- const merged = [...builtIn, ...fromDisk];
822
+ const enrichedBuiltIn = builtIn.map((plugin) => enrichOpenbotPluginDescriptor(plugin, modelOptions));
823
+ const merged = [...enrichedBuiltIn, ...fromDisk];
783
824
  const deduped = new Map();
784
825
  for (const plugin of merged) {
785
826
  if (!deduped.has(plugin.id)) {
@@ -839,6 +880,7 @@ export const storageService = {
839
880
  },
840
881
  createAgent: async ({ agentId, name, description = '', image, hidden, instructions, plugins, }) => {
841
882
  assertAgentIdFormat(agentId);
883
+ assertCloudSystemAgentPluginsMutable(agentId, plugins);
842
884
  const agentDir = resolvePath(path.join(getAgentsRootDir(), agentId));
843
885
  const agentMdPath = path.join(agentDir, 'AGENT.md');
844
886
  try {
@@ -874,6 +916,7 @@ export const storageService = {
874
916
  },
875
917
  updateAgent: async ({ agentId, name, description, image, hidden, instructions, plugins, }) => {
876
918
  assertAgentIdFormat(agentId);
919
+ assertCloudSystemAgentPluginsMutable(agentId, plugins);
877
920
  const agentDir = resolvePath(path.join(getAgentsRootDir(), agentId));
878
921
  const agentMdPath = path.join(agentDir, 'AGENT.md');
879
922
  let raw;