openbot 0.3.4 → 0.3.6

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 CHANGED
@@ -18,7 +18,7 @@ OpenBot is a local-first harness for running AI agents. It is built around a sma
18
18
 
19
19
  - Runs a local agent server.
20
20
  - Stores channels, threads, agents, plugins, config, and variables under `~/.openbot`.
21
- - Ships with a built-in `system` agent named Lolly.
21
+ - Ships with a built-in `system` agent named OpenBot.
22
22
  - Loads custom agents from `~/.openbot/agents/<agent-id>/AGENT.md`.
23
23
  - Loads shared plugins from `~/.openbot/plugins`.
24
24
  - Streams events to clients with Server-Sent Events.
package/dist/app/cli.js CHANGED
@@ -16,7 +16,7 @@ function checkNodeVersion() {
16
16
  }
17
17
  }
18
18
  checkNodeVersion();
19
- program.name('openbot').description('OpenBot CLI').version('0.3.4');
19
+ program.name('openbot').description('OpenBot CLI').version('0.3.6');
20
20
  program
21
21
  .command('start')
22
22
  .description('Start the OpenBot harness')
@@ -0,0 +1,10 @@
1
+ <svg width="60.2" height="58.1" viewBox="272.584 370.297 64.635 62.51" xmlns="http://www.w3.org/2000/svg">
2
+ <style>
3
+ .mark { fill: #000000; }
4
+ @media (prefers-color-scheme: dark) {
5
+ .mark { fill: #ffffff; }
6
+ }
7
+ </style>
8
+ <path class="mark" fill="#000000" d="M301.841 380.896C302.069 380.893 302.297 380.889 302.531 380.885C304.636 380.853 306.741 380.836 308.845 380.822C310.26 380.813 311.675 380.8 313.09 380.775C314.456 380.752 315.822 380.739 317.189 380.735C317.71 380.731 318.231 380.724 318.752 380.712C322.824 380.626 322.824 380.626 323.909 381.43C324.315 382.025 324.561 382.569 324.8 383.246C324.965 383.518 325.13 383.789 325.301 384.068C325.519 384.445 325.519 384.445 325.741 384.83C325.909 385.118 326.077 385.407 326.249 385.704C326.336 385.854 326.423 386.004 326.513 386.159C328.304 389.25 330.137 392.314 332.004 395.361C332.482 396.144 332.957 396.929 333.431 397.715C333.631 398.035 333.631 398.035 333.835 398.361C334.546 399.544 334.969 400.367 334.7 401.777C334.24 402.838 333.683 403.834 333.113 404.839C332.867 405.285 332.621 405.73 332.375 406.176C332.125 406.626 331.874 407.075 331.622 407.525C330.952 408.724 330.296 409.931 329.639 411.137C329.518 411.359 329.397 411.58 329.272 411.807C328.303 413.581 327.349 415.362 326.398 417.144C324.53 420.642 322.621 424.107 320.611 427.525C320.282 428.088 319.962 428.655 319.644 429.224C319.215 429.954 319.215 429.954 318.708 430.462C317.978 430.557 317.978 430.557 317.185 430.462C316.049 429.578 315.42 428.258 314.71 427.035C313.685 425.291 312.637 423.6 311.454 421.96C311.024 421.203 310.906 420.668 310.838 419.8C311.092 419.081 311.092 419.081 311.491 418.343C311.707 417.937 311.707 417.937 311.927 417.522C312.081 417.242 312.235 416.963 312.393 416.675C312.615 416.26 312.615 416.26 312.841 415.836C314.003 413.671 314.003 413.671 314.549 412.797C315.269 411.676 315.269 411.676 315.308 410.387C314.801 409.466 314.268 408.575 313.694 407.695C313.466 407.335 313.238 406.976 313.011 406.616C312.845 406.353 312.845 406.353 312.676 406.085C312.247 405.402 311.829 404.713 311.425 404.014C311.304 403.806 311.183 403.598 311.058 403.384C310.786 402.65 310.848 402.26 311.092 401.523C311.6 401.015 311.6 401.015 312.57 400.967C312.988 400.97 313.407 400.975 313.826 400.982C314.281 400.981 314.737 400.981 315.192 400.98C315.911 400.98 316.629 400.984 317.348 400.995C318.041 401.005 318.733 401.002 319.427 400.998C319.641 401.004 319.854 401.011 320.075 401.017C320.695 401.007 321.171 400.974 321.754 400.762C322.329 400.154 322.665 399.481 323.023 398.731C323.171 398.49 323.318 398.248 323.47 398C323.899 397.287 324.326 396.573 324.752 395.859C324.904 395.608 325.056 395.357 325.212 395.099C325.355 394.859 325.498 394.618 325.646 394.371C325.841 394.045 325.841 394.045 326.04 393.713C326.38 393.138 326.38 393.138 326.323 392.385C326.167 392.384 326.011 392.383 325.85 392.383C322.054 392.368 318.257 392.349 314.461 392.325C312.625 392.313 310.789 392.303 308.953 392.297C307.352 392.291 305.752 392.282 304.152 392.271C303.305 392.265 302.458 392.26 301.61 392.258C300.664 392.256 299.719 392.249 298.773 392.241C298.351 392.241 298.351 392.241 297.921 392.242C297.663 392.238 297.406 392.235 297.14 392.232C296.916 392.231 296.693 392.23 296.462 392.228C295.73 392.109 295.379 391.879 294.846 391.369C294.591 389.978 295.187 388.999 295.837 387.818C295.938 387.63 296.038 387.441 296.142 387.246C296.464 386.646 296.789 386.049 297.115 385.451C297.333 385.045 297.551 384.638 297.769 384.231C299.545 380.925 299.545 380.925 301.841 380.896Z"/>
9
+ <path class="mark" fill="#000000" d="M291.476 372.547C292.795 373.009 293.243 374.055 293.942 375.218C294.23 375.684 294.519 376.15 294.807 376.615C294.945 376.84 295.082 377.065 295.223 377.297C295.544 377.813 295.875 378.314 296.225 378.811C296.388 379.044 296.551 379.277 296.718 379.518C296.856 379.711 296.994 379.903 297.137 380.102C297.492 380.97 297.407 381.615 297.131 382.485C296.704 383.459 296.209 384.39 295.703 385.325C295.497 385.714 295.497 385.714 295.287 386.111C294.44 387.702 293.559 389.255 292.565 390.759C292.217 391.585 292.351 392.026 292.562 392.892C292.955 393.665 292.955 393.665 293.466 394.431C293.555 394.572 293.644 394.713 293.736 394.858C294.019 395.304 294.305 395.748 294.592 396.192C294.971 396.778 295.346 397.365 295.719 397.953C295.886 398.212 296.053 398.471 296.225 398.738C296.646 399.535 296.848 400.115 296.877 401.015C296.559 401.435 296.559 401.435 296.115 401.777C295.364 401.849 294.663 401.877 293.911 401.871C293.467 401.876 293.023 401.88 292.579 401.885C291.879 401.887 291.18 401.888 290.48 401.888C289.804 401.888 289.129 401.895 288.453 401.903C288.244 401.901 288.036 401.899 287.82 401.897C286.637 401.91 286.637 401.91 285.616 402.466C284.844 403.543 284.229 404.69 283.598 405.854C283.455 406.11 283.312 406.365 283.165 406.628C283.031 406.873 282.897 407.118 282.76 407.371C282.637 407.594 282.515 407.818 282.389 408.048C282.071 408.656 282.071 408.656 282.408 409.392C282.559 409.393 282.71 409.394 282.865 409.395C286.539 409.416 290.213 409.439 293.886 409.465C295.663 409.478 297.439 409.49 299.216 409.499C300.929 409.509 302.642 409.521 304.356 409.534C305.011 409.539 305.665 409.543 306.32 409.546C307.235 409.55 308.15 409.557 309.064 409.565C309.473 409.566 309.473 409.566 309.89 409.567C310.139 409.57 310.388 409.573 310.644 409.576C310.861 409.577 311.077 409.578 311.3 409.58C311.854 409.646 311.854 409.646 312.615 410.154C312.797 411.581 312.41 412.577 311.745 413.808C311.656 413.979 311.567 414.15 311.475 414.326C311.288 414.685 311.099 415.042 310.908 415.399C310.616 415.946 310.329 416.495 310.042 417.045C309.859 417.394 309.676 417.743 309.492 418.092C309.363 418.337 309.363 418.337 309.231 418.588C308.881 419.242 308.574 419.78 308.046 420.308C307.364 420.361 306.707 420.382 306.024 420.382C305.815 420.383 305.606 420.385 305.391 420.387C304.698 420.392 304.005 420.392 303.312 420.393C302.832 420.395 302.351 420.396 301.871 420.398C300.862 420.401 299.854 420.402 298.845 420.402C297.552 420.402 296.259 420.409 294.966 420.417C293.973 420.423 292.98 420.424 291.987 420.424C291.51 420.424 291.034 420.427 290.557 420.431C289.89 420.436 289.224 420.434 288.557 420.431C288.36 420.434 288.163 420.437 287.959 420.44C287.061 420.429 286.569 420.386 285.852 419.824C285.47 419.314 285.137 418.817 284.831 418.259C284.718 418.056 284.605 417.852 284.488 417.643C284.367 417.423 284.246 417.203 284.121 416.976C282.907 414.808 281.638 412.679 280.329 410.566C280.156 410.286 279.982 410.006 279.803 409.717C279.469 409.176 279.134 408.636 278.799 408.096C277.871 406.592 276.959 405.077 276.062 403.554C275.968 403.409 275.874 403.264 275.777 403.114C275.143 402.097 274.834 401.442 275.046 400.254C275.425 399.337 275.889 398.475 276.363 397.604C276.568 397.22 276.568 397.22 276.777 396.828C278.005 394.541 279.284 392.283 280.569 390.029C282.224 387.119 283.868 384.197 285.327 381.184C289.509 372.654 289.509 372.654 291.476 372.547Z"/>
10
+ </svg>
@@ -163,7 +163,7 @@ export const busServicesPlugin = (options) => (builder) => {
163
163
  };
164
164
  });
165
165
  builder.on('action:create_channel', async function* (event, context) {
166
- const { channelId, spec, initialState, cwd } = event.data;
166
+ const { channelId, spec, initialState, cwd, participants } = event.data;
167
167
  const rawChannelId = (channelId || '').trim();
168
168
  const channelSpec = typeof spec === 'string' ? spec : '';
169
169
  const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
@@ -176,11 +176,21 @@ export const busServicesPlugin = (options) => (builder) => {
176
176
  return;
177
177
  }
178
178
  const channelUrl = `/channels/${rawChannelId}`;
179
+ const mergedInitial = { ...(initialState || {}) };
180
+ if (participants !== undefined) {
181
+ const normalized = Array.isArray(participants)
182
+ ? participants
183
+ .filter((x) => typeof x === 'string')
184
+ .map((s) => s.trim())
185
+ .filter(Boolean)
186
+ : [];
187
+ mergedInitial.participants = normalized;
188
+ }
179
189
  try {
180
190
  await storage.createChannel({
181
191
  channelId: rawChannelId,
182
192
  spec: channelSpec,
183
- initialState: initialState,
193
+ initialState: mergedInitial,
184
194
  cwd,
185
195
  });
186
196
  yield {
@@ -224,6 +234,18 @@ export const busServicesPlugin = (options) => (builder) => {
224
234
  patch.cwd = data.cwd.trim();
225
235
  updatedFields.push('cwd');
226
236
  }
237
+ if (data.participants !== undefined) {
238
+ if (Array.isArray(data.participants)) {
239
+ patch.participants = data.participants
240
+ .filter((x) => typeof x === 'string')
241
+ .map((s) => s.trim())
242
+ .filter(Boolean);
243
+ }
244
+ else {
245
+ patch.participants = [];
246
+ }
247
+ updatedFields.push('participants');
248
+ }
227
249
  try {
228
250
  if (updatedFields.length > 0) {
229
251
  await storage.patchChannelState({ channelId: targetChannelId, state: patch });
@@ -250,28 +272,42 @@ export const busServicesPlugin = (options) => (builder) => {
250
272
  builder.on('action:patch_channel_details', async function* (event, context) {
251
273
  const updatedFields = [];
252
274
  const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
275
+ const data = (event.data || {});
253
276
  try {
254
- if (event.data.state !== undefined) {
277
+ if (data.state !== undefined) {
255
278
  await storage.patchChannelState({
256
279
  channelId: context.state.channelId,
257
- state: event.data.state,
280
+ state: data.state,
258
281
  });
259
282
  updatedFields.push('state');
260
283
  }
261
- if (typeof event.data.spec === 'string') {
284
+ if (typeof data.spec === 'string') {
262
285
  await storage.patchChannelSpec({
263
286
  channelId: context.state.channelId,
264
- spec: event.data.spec,
287
+ spec: data.spec,
265
288
  });
266
289
  updatedFields.push('spec');
267
290
  }
268
- if (typeof event.data.cwd === 'string') {
291
+ if (typeof data.cwd === 'string') {
269
292
  await storage.patchChannelState({
270
293
  channelId: context.state.channelId,
271
- state: { cwd: event.data.cwd },
294
+ state: { cwd: data.cwd },
272
295
  });
273
296
  updatedFields.push('cwd');
274
297
  }
298
+ if (data.participants !== undefined) {
299
+ const normalized = Array.isArray(data.participants)
300
+ ? data.participants
301
+ .filter((x) => typeof x === 'string')
302
+ .map((s) => s.trim())
303
+ .filter(Boolean)
304
+ : [];
305
+ await storage.patchChannelState({
306
+ channelId: context.state.channelId,
307
+ state: { participants: normalized },
308
+ });
309
+ updatedFields.push('participants');
310
+ }
275
311
  context.state.channelDetails = await storage.getChannelDetails({
276
312
  channelId: context.state.channelId,
277
313
  });
@@ -455,8 +491,8 @@ export const busServicesPlugin = (options) => (builder) => {
455
491
  });
456
492
  builder.on('action:storage:create-agent', async function* (event) {
457
493
  try {
458
- const { agentId, name, description, instructions, plugins } = event.data;
459
- await storage.createAgent({ agentId, name, description, instructions, plugins });
494
+ const { agentId, name, description, image, instructions, plugins } = event.data;
495
+ await storage.createAgent({ agentId, name, description, image, instructions, plugins });
460
496
  yield { type: 'action:storage:create-agent-result', data: { success: true } };
461
497
  }
462
498
  catch (error) {
@@ -471,8 +507,8 @@ export const busServicesPlugin = (options) => (builder) => {
471
507
  });
472
508
  builder.on('action:storage:update-agent', async function* (event) {
473
509
  try {
474
- const { agentId, name, description, instructions, plugins } = event.data;
475
- await storage.updateAgent({ agentId, name, description, instructions, plugins });
510
+ const { agentId, name, description, image, instructions, plugins } = event.data;
511
+ await storage.updateAgent({ agentId, name, description, image, instructions, plugins });
476
512
  yield { type: 'action:storage:update-agent-result', data: { success: true } };
477
513
  }
478
514
  catch (error) {
@@ -758,7 +794,7 @@ export const busServicesPlugin = (options) => (builder) => {
758
794
  });
759
795
  builder.on('action:agent:install', async function* (event) {
760
796
  try {
761
- const { agentId, name, description, instructions, plugins } = event.data;
797
+ const { agentId, name, description, image, instructions, plugins } = event.data;
762
798
  // Ensure each plugin is available locally. Built-in ids resolve
763
799
  // immediately; npm-name ids are fetched on demand.
764
800
  for (const ref of plugins) {
@@ -780,6 +816,7 @@ export const busServicesPlugin = (options) => (builder) => {
780
816
  agentId,
781
817
  name,
782
818
  description,
819
+ image,
783
820
  instructions,
784
821
  plugins,
785
822
  });
@@ -89,15 +89,20 @@ class ChannelDetailsProvider {
89
89
  async provide(state) {
90
90
  if (!state.channelDetails)
91
91
  return [];
92
- const spec = state.channelDetails.spec?.trim();
93
- if (!spec)
92
+ const participants = state.channelDetails.participants;
93
+ if (!participants?.length)
94
94
  return [];
95
- return [{
95
+ const channelLabel = state.channelDetails.name?.trim() || state.channelDetails.id;
96
+ const lines = participants.map((id) => `- \`${id}\``).join('\n');
97
+ return [
98
+ {
96
99
  id: 'channel-details',
97
100
  type: 'channel',
98
101
  priority: 80,
99
- content: `# Channel you are in: ${state.channelDetails.name}\n\n Channel Specification: ${spec}`,
100
- }];
102
+ content: `## Channel participants (${channelLabel})\n` +
103
+ `Agent ids collaborating in this channel:\n${lines}`,
104
+ },
105
+ ];
101
106
  }
102
107
  }
103
108
  class ThreadDetailsProvider {
@@ -1,6 +1,8 @@
1
1
  import { DEFAULT_PLUGINS_DIR, DEFAULT_AGENTS_DIR, DEFAULT_BASE_DIR, DEFAULT_CHANNELS_DIR, loadConfig, resolvePath, VARIABLES_FILE, } from '../app/config.js';
2
2
  import fs from 'node:fs/promises';
3
+ import { readFileSync } from 'node:fs';
3
4
  import path from 'node:path';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
6
  import crypto from 'node:crypto';
5
7
  import matter from 'gray-matter';
6
8
  import { aiSdkPlugin } from '../plugins/ai-sdk/index.js';
@@ -8,13 +10,31 @@ import { AI_SDK_SYSTEM_PROMPT } from '../plugins/ai-sdk/system-prompt.js';
8
10
  import { listBuiltInPlugins, parsePluginModule } from '../registry/plugins.js';
9
11
  import { processService } from '../harness/process.js';
10
12
  import { memoryService } from './memory.js';
11
- import { pathToFileURL } from 'node:url';
12
13
  const resolveBaseDir = () => {
13
14
  const config = loadConfig();
14
15
  return resolvePath(config.baseDir || DEFAULT_BASE_DIR);
15
16
  };
16
17
  const ENTITY_SVG_CANDIDATE_NAMES = ['avatar.svg', 'icon.svg', 'image.svg', 'logo.svg'];
17
18
  const toSvgDataUrl = (svg) => `data:image/svg+xml;base64,${Buffer.from(svg, 'utf-8').toString('base64')}`;
19
+ let bundledSystemAgentImage;
20
+ let bundledSystemAgentImageLoaded = false;
21
+ /** OpenBot mark from `src/assets/icon.svg` (also copied to `dist/assets` at build). */
22
+ function getBundledSystemAgentImage() {
23
+ if (bundledSystemAgentImageLoaded)
24
+ return bundledSystemAgentImage;
25
+ bundledSystemAgentImageLoaded = true;
26
+ try {
27
+ const iconPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../assets/icon.svg');
28
+ const trimmed = readFileSync(iconPath, 'utf-8').trim();
29
+ if (!trimmed.startsWith('<svg'))
30
+ return undefined;
31
+ bundledSystemAgentImage = toSvgDataUrl(trimmed);
32
+ }
33
+ catch {
34
+ bundledSystemAgentImage = undefined;
35
+ }
36
+ return bundledSystemAgentImage;
37
+ }
18
38
  const tryReadSvgDataUrl = async (filePath) => {
19
39
  try {
20
40
  const svg = await fs.readFile(filePath, 'utf-8');
@@ -72,7 +92,7 @@ function getSystemAgentDetails(overrides) {
72
92
  const defaults = {
73
93
  id: SYSTEM_AGENT_ID,
74
94
  name: 'OpenBot',
75
- image: undefined,
95
+ image: getBundledSystemAgentImage(),
76
96
  description: 'First-party orchestration agent for OpenBot. Coordinates other agents via handoff.',
77
97
  instructions: AI_SDK_SYSTEM_PROMPT,
78
98
  plugins: SYSTEM_DEFAULT_PLUGINS.map((ref) => ref.id),
@@ -242,6 +262,22 @@ const listPluginsFromDisk = async () => {
242
262
  return descriptors.filter((d) => d !== null);
243
263
  };
244
264
  const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
265
+ /** Display-oriented fields persisted in a channel's `state.json`. */
266
+ const readChannelStateFileFields = (parsed) => {
267
+ if (!isRecord(parsed)) {
268
+ return { participants: [] };
269
+ }
270
+ const name = typeof parsed.name === 'string' && parsed.name.trim() ? parsed.name.trim() : undefined;
271
+ const cwd = typeof parsed.cwd === 'string' ? parsed.cwd : undefined;
272
+ const participants = [];
273
+ if (Array.isArray(parsed.participants)) {
274
+ for (const x of parsed.participants) {
275
+ if (typeof x === 'string' && x.trim())
276
+ participants.push(x.trim());
277
+ }
278
+ }
279
+ return { name, cwd, participants };
280
+ };
245
281
  /**
246
282
  * Parse the `plugins:` array from AGENT.md frontmatter. Each entry must have an
247
283
  * `id`; `config` is optional. Strings are accepted as a shorthand for `{ id }`.
@@ -288,19 +324,25 @@ export const storageService = {
288
324
  const channelDir = getConversationDir(name);
289
325
  const statePath = path.join(channelDir, 'state.json');
290
326
  let cwd;
327
+ let displayName = name;
328
+ let participants = [];
291
329
  try {
292
330
  const stateContent = await fs.readFile(statePath, 'utf-8');
293
- const state = JSON.parse(stateContent);
294
- cwd = typeof state.cwd === 'string' ? state.cwd : undefined;
331
+ const parsed = JSON.parse(stateContent);
332
+ const fields = readChannelStateFileFields(parsed);
333
+ cwd = fields.cwd;
334
+ displayName = fields.name ?? name;
335
+ participants = fields.participants;
295
336
  }
296
337
  catch {
297
338
  // ignore
298
339
  }
299
340
  const channel = {
300
341
  id: name,
301
- name: name,
342
+ name: displayName,
302
343
  description: '',
303
344
  cwd,
345
+ participants,
304
346
  createdAt: new Date(),
305
347
  updatedAt: new Date(),
306
348
  };
@@ -464,13 +506,16 @@ export const storageService = {
464
506
  console.error(`Failed to read state file for channel ${channelId}`, error);
465
507
  }
466
508
  }
467
- const cwd = isRecord(state) && typeof state.cwd === 'string' ? state.cwd : undefined;
509
+ const diskFields = readChannelStateFileFields(state);
510
+ const cwd = diskFields.cwd;
511
+ const displayName = diskFields.name ?? channelId;
468
512
  const details = {
469
513
  id: channelId,
470
- name: channelId,
514
+ name: displayName,
471
515
  spec,
472
516
  state,
473
517
  cwd,
518
+ participants: diskFields.participants,
474
519
  };
475
520
  details.threads = await storageService.getThreads({ channelId });
476
521
  return details;
@@ -599,6 +644,9 @@ export const storageService = {
599
644
  const discoveredImage = await resolveEntityImageDataUrl(agentDir);
600
645
  const stats = await fs.stat(agentMdPath);
601
646
  const pluginRefs = parsePluginRefs(data.plugins);
647
+ const frontmatterImage = typeof data.image === 'string' && data.image.trim() !== ''
648
+ ? data.image.trim()
649
+ : undefined;
602
650
  diskDetails = {
603
651
  id: agentId,
604
652
  name: typeof data.name === 'string' ? data.name : agentId,
@@ -606,7 +654,7 @@ export const storageService = {
606
654
  plugins: pluginRefs.map((ref) => ref.id),
607
655
  pluginRefs,
608
656
  description: typeof data.description === 'string' ? data.description : '',
609
- image: discoveredImage || undefined,
657
+ image: frontmatterImage || discoveredImage || undefined,
610
658
  createdAt: stats.birthtime,
611
659
  updatedAt: stats.mtime,
612
660
  };
@@ -630,7 +678,7 @@ export const storageService = {
630
678
  }
631
679
  return diskDetails;
632
680
  },
633
- createAgent: async ({ agentId, name, description = '', instructions, plugins, }) => {
681
+ createAgent: async ({ agentId, name, description = '', image, instructions, plugins, }) => {
634
682
  assertValidDiskAgentId(agentId);
635
683
  const agentDir = resolvePath(path.join(getAgentsRootDir(), agentId));
636
684
  const agentMdPath = path.join(agentDir, 'AGENT.md');
@@ -656,10 +704,13 @@ export const storageService = {
656
704
  description,
657
705
  plugins: serializePluginRefs(plugins),
658
706
  };
707
+ if (typeof image === 'string' && image.trim() !== '') {
708
+ data.image = image.trim();
709
+ }
659
710
  const body = matter.stringify(`${instructions.trim()}\n`, data);
660
711
  await fs.writeFile(agentMdPath, body, 'utf-8');
661
712
  },
662
- updateAgent: async ({ agentId, name, description, instructions, plugins, }) => {
713
+ updateAgent: async ({ agentId, name, description, image, instructions, plugins, }) => {
663
714
  assertValidDiskAgentId(agentId);
664
715
  const agentDir = resolvePath(path.join(getAgentsRootDir(), agentId));
665
716
  const agentMdPath = path.join(agentDir, 'AGENT.md');
@@ -683,6 +734,14 @@ export const storageService = {
683
734
  nextData.description = description;
684
735
  if (plugins !== undefined)
685
736
  nextData.plugins = serializePluginRefs(plugins);
737
+ if (image !== undefined) {
738
+ if (typeof image === 'string' && image.trim() !== '') {
739
+ nextData.image = image.trim();
740
+ }
741
+ else {
742
+ delete nextData.image;
743
+ }
744
+ }
686
745
  const nextContent = instructions !== undefined ? instructions : parsed.content;
687
746
  const body = matter.stringify(`${String(nextContent).trim()}\n`, nextData);
688
747
  await fs.writeFile(agentMdPath, body, 'utf-8');
package/package.json CHANGED
@@ -1,16 +1,11 @@
1
1
  {
2
2
  "name": "openbot",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=20.12.0"
8
8
  },
9
- "scripts": {
10
- "dev": "tsx watch src/app/cli.ts start",
11
- "build": "tsc",
12
- "start": "node dist/app/cli.js start"
13
- },
14
9
  "bin": {
15
10
  "openbot": "./dist/app/cli.js"
16
11
  },
@@ -36,5 +31,10 @@
36
31
  "@types/node": "^20.10.1",
37
32
  "tsx": "^4.21.0",
38
33
  "typescript": "^5.9.3"
34
+ },
35
+ "scripts": {
36
+ "dev": "tsx watch src/app/cli.ts start",
37
+ "build": "tsc && mkdir -p dist/assets && cp src/assets/icon.svg dist/assets/icon.svg",
38
+ "start": "node dist/app/cli.js start"
39
39
  }
40
- }
40
+ }
package/src/app/cli.ts CHANGED
@@ -25,7 +25,7 @@ function checkNodeVersion() {
25
25
 
26
26
  checkNodeVersion();
27
27
 
28
- program.name('openbot').description('OpenBot CLI').version('0.3.4');
28
+ program.name('openbot').description('OpenBot CLI').version('0.3.6');
29
29
 
30
30
  program
31
31
  .command('start')
package/src/app/types.ts CHANGED
@@ -143,6 +143,7 @@ export type CreateAgentEvent = BaseEvent & {
143
143
  agentId: string;
144
144
  name: string;
145
145
  description?: string;
146
+ image?: string;
146
147
  instructions: string;
147
148
  plugins: PluginRef[];
148
149
  };
@@ -162,6 +163,7 @@ export type UpdateAgentEvent = BaseEvent & {
162
163
  agentId: string;
163
164
  name?: string;
164
165
  description?: string;
166
+ image?: string;
165
167
  instructions?: string;
166
168
  plugins?: PluginRef[];
167
169
  };
@@ -275,6 +277,8 @@ export type PatchChannelDetailsEvent = BaseEvent & {
275
277
  state?: Record<string, unknown>;
276
278
  spec?: string;
277
279
  cwd?: string;
280
+ /** When set, replaces `state.json` `participants` (merged after `state` if both are sent). */
281
+ participants?: string[];
278
282
  };
279
283
  };
280
284
 
@@ -282,7 +286,7 @@ export type PatchChannelDetailsResultEvent = BaseEvent & {
282
286
  type: 'action:patch_channel_details:result';
283
287
  data: {
284
288
  success: boolean;
285
- updatedFields: ('state' | 'spec' | 'cwd')[];
289
+ updatedFields: ('state' | 'spec' | 'cwd' | 'participants')[];
286
290
  };
287
291
  };
288
292
 
@@ -439,6 +443,8 @@ export type CreateChannelEvent = BaseEvent & {
439
443
  spec?: string;
440
444
  initialState?: Record<string, unknown>;
441
445
  cwd?: string;
446
+ /** Initial channel agent ids; written into `state.json` (overrides `initialState.participants` if both are set). */
447
+ participants?: string[];
442
448
  };
443
449
  meta?: {
444
450
  toolCallId?: string;
@@ -462,6 +468,8 @@ export type UpdateChannelEvent = BaseEvent & {
462
468
  channelId?: string;
463
469
  name?: string;
464
470
  cwd?: string;
471
+ /** Replaces the channel participant list when provided. */
472
+ participants?: string[];
465
473
  };
466
474
  };
467
475
 
@@ -745,6 +753,7 @@ export type InstallAgentEvent = BaseEvent & {
745
753
  agentId: string;
746
754
  name: string;
747
755
  description?: string;
756
+ image?: string;
748
757
  instructions: string;
749
758
  plugins: PluginRef[];
750
759
  };
@@ -1,4 +1,10 @@
1
- <svg width="60.2" height="58.1" viewBox="262.8 360.5 84.2 82.1" fill="#9333EA" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M301.841 380.896C302.069 380.893 302.297 380.889 302.531 380.885C304.636 380.853 306.741 380.836 308.845 380.822C310.26 380.813 311.675 380.8 313.09 380.775C314.456 380.752 315.822 380.739 317.189 380.735C317.71 380.731 318.231 380.724 318.752 380.712C322.824 380.626 322.824 380.626 323.909 381.43C324.315 382.025 324.561 382.569 324.8 383.246C324.965 383.518 325.13 383.789 325.301 384.068C325.519 384.445 325.519 384.445 325.741 384.83C325.909 385.118 326.077 385.407 326.249 385.704C326.336 385.854 326.423 386.004 326.513 386.159C328.304 389.25 330.137 392.314 332.004 395.361C332.482 396.144 332.957 396.929 333.431 397.715C333.631 398.035 333.631 398.035 333.835 398.361C334.546 399.544 334.969 400.367 334.7 401.777C334.24 402.838 333.683 403.834 333.113 404.839C332.867 405.285 332.621 405.73 332.375 406.176C332.125 406.626 331.874 407.075 331.622 407.525C330.952 408.724 330.296 409.931 329.639 411.137C329.518 411.359 329.397 411.58 329.272 411.807C328.303 413.581 327.349 415.362 326.398 417.144C324.53 420.642 322.621 424.107 320.611 427.525C320.282 428.088 319.962 428.655 319.644 429.224C319.215 429.954 319.215 429.954 318.708 430.462C317.978 430.557 317.978 430.557 317.185 430.462C316.049 429.578 315.42 428.258 314.71 427.035C313.685 425.291 312.637 423.6 311.454 421.96C311.024 421.203 310.906 420.668 310.838 419.8C311.092 419.081 311.092 419.081 311.491 418.343C311.707 417.937 311.707 417.937 311.927 417.522C312.081 417.242 312.235 416.963 312.393 416.675C312.615 416.26 312.615 416.26 312.841 415.836C314.003 413.671 314.003 413.671 314.549 412.797C315.269 411.676 315.269 411.676 315.308 410.387C314.801 409.466 314.268 408.575 313.694 407.695C313.466 407.335 313.238 406.976 313.011 406.616C312.845 406.353 312.845 406.353 312.676 406.085C312.247 405.402 311.829 404.713 311.425 404.014C311.304 403.806 311.183 403.598 311.058 403.384C310.786 402.65 310.848 402.26 311.092 401.523C311.6 401.015 311.6 401.015 312.57 400.967C312.988 400.97 313.407 400.975 313.826 400.982C314.281 400.981 314.737 400.981 315.192 400.98C315.911 400.98 316.629 400.984 317.348 400.995C318.041 401.005 318.733 401.002 319.427 400.998C319.641 401.004 319.854 401.011 320.075 401.017C320.695 401.007 321.171 400.974 321.754 400.762C322.329 400.154 322.665 399.481 323.023 398.731C323.171 398.49 323.318 398.248 323.47 398C323.899 397.287 324.326 396.573 324.752 395.859C324.904 395.608 325.056 395.357 325.212 395.099C325.355 394.859 325.498 394.618 325.646 394.371C325.841 394.045 325.841 394.045 326.04 393.713C326.38 393.138 326.38 393.138 326.323 392.385C326.167 392.384 326.011 392.383 325.85 392.383C322.054 392.368 318.257 392.349 314.461 392.325C312.625 392.313 310.789 392.303 308.953 392.297C307.352 392.291 305.752 392.282 304.152 392.271C303.305 392.265 302.458 392.26 301.61 392.258C300.664 392.256 299.719 392.249 298.773 392.241C298.351 392.241 298.351 392.241 297.921 392.242C297.663 392.238 297.406 392.235 297.14 392.232C296.916 392.231 296.693 392.23 296.462 392.228C295.73 392.109 295.379 391.879 294.846 391.369C294.591 389.978 295.187 388.999 295.837 387.818C295.938 387.63 296.038 387.441 296.142 387.246C296.464 386.646 296.789 386.049 297.115 385.451C297.333 385.045 297.551 384.638 297.769 384.231C299.545 380.925 299.545 380.925 301.841 380.896Z" fill="#9333EA"/>
3
- <path d="M291.476 372.547C292.795 373.009 293.243 374.055 293.942 375.218C294.23 375.684 294.519 376.15 294.807 376.615C294.945 376.84 295.082 377.065 295.223 377.297C295.544 377.813 295.875 378.314 296.225 378.811C296.388 379.044 296.551 379.277 296.718 379.518C296.856 379.711 296.994 379.903 297.137 380.102C297.492 380.97 297.407 381.615 297.131 382.485C296.704 383.459 296.209 384.39 295.703 385.325C295.497 385.714 295.497 385.714 295.287 386.111C294.44 387.702 293.559 389.255 292.565 390.759C292.217 391.585 292.351 392.026 292.562 392.892C292.955 393.665 292.955 393.665 293.466 394.431C293.555 394.572 293.644 394.713 293.736 394.858C294.019 395.304 294.305 395.748 294.592 396.192C294.971 396.778 295.346 397.365 295.719 397.953C295.886 398.212 296.053 398.471 296.225 398.738C296.646 399.535 296.848 400.115 296.877 401.015C296.559 401.435 296.559 401.435 296.115 401.777C295.364 401.849 294.663 401.877 293.911 401.871C293.467 401.876 293.023 401.88 292.579 401.885C291.879 401.887 291.18 401.888 290.48 401.888C289.804 401.888 289.129 401.895 288.453 401.903C288.244 401.901 288.036 401.899 287.82 401.897C286.637 401.91 286.637 401.91 285.616 402.466C284.844 403.543 284.229 404.69 283.598 405.854C283.455 406.11 283.312 406.365 283.165 406.628C283.031 406.873 282.897 407.118 282.76 407.371C282.637 407.594 282.515 407.818 282.389 408.048C282.071 408.656 282.071 408.656 282.408 409.392C282.559 409.393 282.71 409.394 282.865 409.395C286.539 409.416 290.213 409.439 293.886 409.465C295.663 409.478 297.439 409.49 299.216 409.499C300.929 409.509 302.642 409.521 304.356 409.534C305.011 409.539 305.665 409.543 306.32 409.546C307.235 409.55 308.15 409.557 309.064 409.565C309.473 409.566 309.473 409.566 309.89 409.567C310.139 409.57 310.388 409.573 310.644 409.576C310.861 409.577 311.077 409.578 311.3 409.58C311.854 409.646 311.854 409.646 312.615 410.154C312.797 411.581 312.41 412.577 311.745 413.808C311.656 413.979 311.567 414.15 311.475 414.326C311.288 414.685 311.099 415.042 310.908 415.399C310.616 415.946 310.329 416.495 310.042 417.045C309.859 417.394 309.676 417.743 309.492 418.092C309.363 418.337 309.363 418.337 309.231 418.588C308.881 419.242 308.574 419.78 308.046 420.308C307.364 420.361 306.707 420.382 306.024 420.382C305.815 420.383 305.606 420.385 305.391 420.387C304.698 420.392 304.005 420.392 303.312 420.393C302.832 420.395 302.351 420.396 301.871 420.398C300.862 420.401 299.854 420.402 298.845 420.402C297.552 420.402 296.259 420.409 294.966 420.417C293.973 420.423 292.98 420.424 291.987 420.424C291.51 420.424 291.034 420.427 290.557 420.431C289.89 420.436 289.224 420.434 288.557 420.431C288.36 420.434 288.163 420.437 287.959 420.44C287.061 420.429 286.569 420.386 285.852 419.824C285.47 419.314 285.137 418.817 284.831 418.259C284.718 418.056 284.605 417.852 284.488 417.643C284.367 417.423 284.246 417.203 284.121 416.976C282.907 414.808 281.638 412.679 280.329 410.566C280.156 410.286 279.982 410.006 279.803 409.717C279.469 409.176 279.134 408.636 278.799 408.096C277.871 406.592 276.959 405.077 276.062 403.554C275.968 403.409 275.874 403.264 275.777 403.114C275.143 402.097 274.834 401.442 275.046 400.254C275.425 399.337 275.889 398.475 276.363 397.604C276.568 397.22 276.568 397.22 276.777 396.828C278.005 394.541 279.284 392.283 280.569 390.029C282.224 387.119 283.868 384.197 285.327 381.184C289.509 372.654 289.509 372.654 291.476 372.547Z" fill="#9333EA"/>
1
+ <svg width="60.2" height="58.1" viewBox="272.584 370.297 64.635 62.51" xmlns="http://www.w3.org/2000/svg">
2
+ <style>
3
+ .mark { fill: #000000; }
4
+ @media (prefers-color-scheme: dark) {
5
+ .mark { fill: #ffffff; }
6
+ }
7
+ </style>
8
+ <path class="mark" fill="#000000" d="M301.841 380.896C302.069 380.893 302.297 380.889 302.531 380.885C304.636 380.853 306.741 380.836 308.845 380.822C310.26 380.813 311.675 380.8 313.09 380.775C314.456 380.752 315.822 380.739 317.189 380.735C317.71 380.731 318.231 380.724 318.752 380.712C322.824 380.626 322.824 380.626 323.909 381.43C324.315 382.025 324.561 382.569 324.8 383.246C324.965 383.518 325.13 383.789 325.301 384.068C325.519 384.445 325.519 384.445 325.741 384.83C325.909 385.118 326.077 385.407 326.249 385.704C326.336 385.854 326.423 386.004 326.513 386.159C328.304 389.25 330.137 392.314 332.004 395.361C332.482 396.144 332.957 396.929 333.431 397.715C333.631 398.035 333.631 398.035 333.835 398.361C334.546 399.544 334.969 400.367 334.7 401.777C334.24 402.838 333.683 403.834 333.113 404.839C332.867 405.285 332.621 405.73 332.375 406.176C332.125 406.626 331.874 407.075 331.622 407.525C330.952 408.724 330.296 409.931 329.639 411.137C329.518 411.359 329.397 411.58 329.272 411.807C328.303 413.581 327.349 415.362 326.398 417.144C324.53 420.642 322.621 424.107 320.611 427.525C320.282 428.088 319.962 428.655 319.644 429.224C319.215 429.954 319.215 429.954 318.708 430.462C317.978 430.557 317.978 430.557 317.185 430.462C316.049 429.578 315.42 428.258 314.71 427.035C313.685 425.291 312.637 423.6 311.454 421.96C311.024 421.203 310.906 420.668 310.838 419.8C311.092 419.081 311.092 419.081 311.491 418.343C311.707 417.937 311.707 417.937 311.927 417.522C312.081 417.242 312.235 416.963 312.393 416.675C312.615 416.26 312.615 416.26 312.841 415.836C314.003 413.671 314.003 413.671 314.549 412.797C315.269 411.676 315.269 411.676 315.308 410.387C314.801 409.466 314.268 408.575 313.694 407.695C313.466 407.335 313.238 406.976 313.011 406.616C312.845 406.353 312.845 406.353 312.676 406.085C312.247 405.402 311.829 404.713 311.425 404.014C311.304 403.806 311.183 403.598 311.058 403.384C310.786 402.65 310.848 402.26 311.092 401.523C311.6 401.015 311.6 401.015 312.57 400.967C312.988 400.97 313.407 400.975 313.826 400.982C314.281 400.981 314.737 400.981 315.192 400.98C315.911 400.98 316.629 400.984 317.348 400.995C318.041 401.005 318.733 401.002 319.427 400.998C319.641 401.004 319.854 401.011 320.075 401.017C320.695 401.007 321.171 400.974 321.754 400.762C322.329 400.154 322.665 399.481 323.023 398.731C323.171 398.49 323.318 398.248 323.47 398C323.899 397.287 324.326 396.573 324.752 395.859C324.904 395.608 325.056 395.357 325.212 395.099C325.355 394.859 325.498 394.618 325.646 394.371C325.841 394.045 325.841 394.045 326.04 393.713C326.38 393.138 326.38 393.138 326.323 392.385C326.167 392.384 326.011 392.383 325.85 392.383C322.054 392.368 318.257 392.349 314.461 392.325C312.625 392.313 310.789 392.303 308.953 392.297C307.352 392.291 305.752 392.282 304.152 392.271C303.305 392.265 302.458 392.26 301.61 392.258C300.664 392.256 299.719 392.249 298.773 392.241C298.351 392.241 298.351 392.241 297.921 392.242C297.663 392.238 297.406 392.235 297.14 392.232C296.916 392.231 296.693 392.23 296.462 392.228C295.73 392.109 295.379 391.879 294.846 391.369C294.591 389.978 295.187 388.999 295.837 387.818C295.938 387.63 296.038 387.441 296.142 387.246C296.464 386.646 296.789 386.049 297.115 385.451C297.333 385.045 297.551 384.638 297.769 384.231C299.545 380.925 299.545 380.925 301.841 380.896Z"/>
9
+ <path class="mark" fill="#000000" d="M291.476 372.547C292.795 373.009 293.243 374.055 293.942 375.218C294.23 375.684 294.519 376.15 294.807 376.615C294.945 376.84 295.082 377.065 295.223 377.297C295.544 377.813 295.875 378.314 296.225 378.811C296.388 379.044 296.551 379.277 296.718 379.518C296.856 379.711 296.994 379.903 297.137 380.102C297.492 380.97 297.407 381.615 297.131 382.485C296.704 383.459 296.209 384.39 295.703 385.325C295.497 385.714 295.497 385.714 295.287 386.111C294.44 387.702 293.559 389.255 292.565 390.759C292.217 391.585 292.351 392.026 292.562 392.892C292.955 393.665 292.955 393.665 293.466 394.431C293.555 394.572 293.644 394.713 293.736 394.858C294.019 395.304 294.305 395.748 294.592 396.192C294.971 396.778 295.346 397.365 295.719 397.953C295.886 398.212 296.053 398.471 296.225 398.738C296.646 399.535 296.848 400.115 296.877 401.015C296.559 401.435 296.559 401.435 296.115 401.777C295.364 401.849 294.663 401.877 293.911 401.871C293.467 401.876 293.023 401.88 292.579 401.885C291.879 401.887 291.18 401.888 290.48 401.888C289.804 401.888 289.129 401.895 288.453 401.903C288.244 401.901 288.036 401.899 287.82 401.897C286.637 401.91 286.637 401.91 285.616 402.466C284.844 403.543 284.229 404.69 283.598 405.854C283.455 406.11 283.312 406.365 283.165 406.628C283.031 406.873 282.897 407.118 282.76 407.371C282.637 407.594 282.515 407.818 282.389 408.048C282.071 408.656 282.071 408.656 282.408 409.392C282.559 409.393 282.71 409.394 282.865 409.395C286.539 409.416 290.213 409.439 293.886 409.465C295.663 409.478 297.439 409.49 299.216 409.499C300.929 409.509 302.642 409.521 304.356 409.534C305.011 409.539 305.665 409.543 306.32 409.546C307.235 409.55 308.15 409.557 309.064 409.565C309.473 409.566 309.473 409.566 309.89 409.567C310.139 409.57 310.388 409.573 310.644 409.576C310.861 409.577 311.077 409.578 311.3 409.58C311.854 409.646 311.854 409.646 312.615 410.154C312.797 411.581 312.41 412.577 311.745 413.808C311.656 413.979 311.567 414.15 311.475 414.326C311.288 414.685 311.099 415.042 310.908 415.399C310.616 415.946 310.329 416.495 310.042 417.045C309.859 417.394 309.676 417.743 309.492 418.092C309.363 418.337 309.363 418.337 309.231 418.588C308.881 419.242 308.574 419.78 308.046 420.308C307.364 420.361 306.707 420.382 306.024 420.382C305.815 420.383 305.606 420.385 305.391 420.387C304.698 420.392 304.005 420.392 303.312 420.393C302.832 420.395 302.351 420.396 301.871 420.398C300.862 420.401 299.854 420.402 298.845 420.402C297.552 420.402 296.259 420.409 294.966 420.417C293.973 420.423 292.98 420.424 291.987 420.424C291.51 420.424 291.034 420.427 290.557 420.431C289.89 420.436 289.224 420.434 288.557 420.431C288.36 420.434 288.163 420.437 287.959 420.44C287.061 420.429 286.569 420.386 285.852 419.824C285.47 419.314 285.137 418.817 284.831 418.259C284.718 418.056 284.605 417.852 284.488 417.643C284.367 417.423 284.246 417.203 284.121 416.976C282.907 414.808 281.638 412.679 280.329 410.566C280.156 410.286 279.982 410.006 279.803 409.717C279.469 409.176 279.134 408.636 278.799 408.096C277.871 406.592 276.959 405.077 276.062 403.554C275.968 403.409 275.874 403.264 275.777 403.114C275.143 402.097 274.834 401.442 275.046 400.254C275.425 399.337 275.889 398.475 276.363 397.604C276.568 397.22 276.568 397.22 276.777 396.828C278.005 394.541 279.284 392.283 280.569 390.029C282.224 387.119 283.868 384.197 285.327 381.184C289.509 372.654 289.509 372.654 291.476 372.547Z"/>
4
10
  </svg>
@@ -226,7 +226,7 @@ export const busServicesPlugin =
226
226
  });
227
227
 
228
228
  builder.on('action:create_channel', async function* (event, context) {
229
- const { channelId, spec, initialState, cwd } = (event as any).data;
229
+ const { channelId, spec, initialState, cwd, participants } = (event as any).data;
230
230
  const rawChannelId = (channelId || '').trim();
231
231
  const channelSpec = typeof spec === 'string' ? spec : '';
232
232
 
@@ -243,11 +243,22 @@ export const busServicesPlugin =
243
243
 
244
244
  const channelUrl = `/channels/${rawChannelId}`;
245
245
 
246
+ const mergedInitial: Record<string, unknown> = { ...(initialState || {}) };
247
+ if (participants !== undefined) {
248
+ const normalized = Array.isArray(participants)
249
+ ? participants
250
+ .filter((x: unknown): x is string => typeof x === 'string')
251
+ .map((s: string) => s.trim())
252
+ .filter(Boolean)
253
+ : [];
254
+ mergedInitial.participants = normalized;
255
+ }
256
+
246
257
  try {
247
258
  await storage.createChannel({
248
259
  channelId: rawChannelId,
249
260
  spec: channelSpec,
250
- initialState: initialState as Record<string, unknown>,
261
+ initialState: mergedInitial,
251
262
  cwd,
252
263
  });
253
264
 
@@ -272,7 +283,12 @@ export const busServicesPlugin =
272
283
  });
273
284
 
274
285
  builder.on('action:update_channel', async function* (event, context) {
275
- const data = (event.data || {}) as { channelId?: string; name?: string; cwd?: string };
286
+ const data = (event.data || {}) as {
287
+ channelId?: string;
288
+ name?: string;
289
+ cwd?: string;
290
+ participants?: string[];
291
+ };
276
292
  const targetChannelId = (data.channelId || context.state.channelId || '').trim();
277
293
  const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
278
294
 
@@ -296,6 +312,17 @@ export const busServicesPlugin =
296
312
  patch.cwd = data.cwd.trim();
297
313
  updatedFields.push('cwd');
298
314
  }
315
+ if (data.participants !== undefined) {
316
+ if (Array.isArray(data.participants)) {
317
+ patch.participants = data.participants
318
+ .filter((x): x is string => typeof x === 'string')
319
+ .map((s) => s.trim())
320
+ .filter(Boolean);
321
+ } else {
322
+ patch.participants = [];
323
+ }
324
+ updatedFields.push('participants');
325
+ }
299
326
 
300
327
  try {
301
328
  if (updatedFields.length > 0) {
@@ -323,30 +350,49 @@ export const busServicesPlugin =
323
350
  });
324
351
 
325
352
  builder.on('action:patch_channel_details', async function* (event, context) {
326
- const updatedFields: ('state' | 'spec' | 'cwd')[] = [];
353
+ const updatedFields: ('state' | 'spec' | 'cwd' | 'participants')[] = [];
327
354
  const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
355
+ const data = (event.data || {}) as {
356
+ state?: Record<string, unknown>;
357
+ spec?: string;
358
+ cwd?: string;
359
+ participants?: string[];
360
+ };
328
361
  try {
329
- if ((event.data as any).state !== undefined) {
362
+ if (data.state !== undefined) {
330
363
  await storage.patchChannelState({
331
364
  channelId: context.state.channelId,
332
- state: (event.data as any).state,
365
+ state: data.state,
333
366
  });
334
367
  updatedFields.push('state');
335
368
  }
336
- if (typeof (event.data as any).spec === 'string') {
369
+ if (typeof data.spec === 'string') {
337
370
  await storage.patchChannelSpec({
338
371
  channelId: context.state.channelId,
339
- spec: (event.data as any).spec,
372
+ spec: data.spec,
340
373
  });
341
374
  updatedFields.push('spec');
342
375
  }
343
- if (typeof (event.data as any).cwd === 'string') {
376
+ if (typeof data.cwd === 'string') {
344
377
  await storage.patchChannelState({
345
378
  channelId: context.state.channelId,
346
- state: { cwd: (event.data as any).cwd },
379
+ state: { cwd: data.cwd },
347
380
  });
348
381
  updatedFields.push('cwd');
349
382
  }
383
+ if (data.participants !== undefined) {
384
+ const normalized = Array.isArray(data.participants)
385
+ ? data.participants
386
+ .filter((x): x is string => typeof x === 'string')
387
+ .map((s) => s.trim())
388
+ .filter(Boolean)
389
+ : [];
390
+ await storage.patchChannelState({
391
+ channelId: context.state.channelId,
392
+ state: { participants: normalized },
393
+ });
394
+ updatedFields.push('participants');
395
+ }
350
396
 
351
397
  context.state.channelDetails = await storage.getChannelDetails({
352
398
  channelId: context.state.channelId,
@@ -550,8 +596,8 @@ export const busServicesPlugin =
550
596
 
551
597
  builder.on('action:storage:create-agent', async function* (event) {
552
598
  try {
553
- const { agentId, name, description, instructions, plugins } = event.data;
554
- await storage.createAgent({ agentId, name, description, instructions, plugins });
599
+ const { agentId, name, description, image, instructions, plugins } = event.data;
600
+ await storage.createAgent({ agentId, name, description, image, instructions, plugins });
555
601
  yield { type: 'action:storage:create-agent-result', data: { success: true } };
556
602
  } catch (error) {
557
603
  yield {
@@ -566,8 +612,8 @@ export const busServicesPlugin =
566
612
 
567
613
  builder.on('action:storage:update-agent', async function* (event) {
568
614
  try {
569
- const { agentId, name, description, instructions, plugins } = event.data;
570
- await storage.updateAgent({ agentId, name, description, instructions, plugins });
615
+ const { agentId, name, description, image, instructions, plugins } = event.data;
616
+ await storage.updateAgent({ agentId, name, description, image, instructions, plugins });
571
617
  yield { type: 'action:storage:update-agent-result', data: { success: true } };
572
618
  } catch (error) {
573
619
  yield {
@@ -858,7 +904,7 @@ export const busServicesPlugin =
858
904
 
859
905
  builder.on('action:agent:install', async function* (event) {
860
906
  try {
861
- const { agentId, name, description, instructions, plugins } = event.data;
907
+ const { agentId, name, description, image, instructions, plugins } = event.data;
862
908
 
863
909
  // Ensure each plugin is available locally. Built-in ids resolve
864
910
  // immediately; npm-name ids are fetched on demand.
@@ -881,6 +927,7 @@ export const busServicesPlugin =
881
927
  agentId,
882
928
  name,
883
929
  description,
930
+ image,
884
931
  instructions,
885
932
  plugins,
886
933
  });
package/src/bus/types.ts CHANGED
@@ -60,6 +60,8 @@ export type Channel = {
60
60
  name: string;
61
61
  description: string;
62
62
  cwd?: string;
63
+ /** Agent ids associated with this channel (from `state.json`). */
64
+ participants: string[];
63
65
  createdAt: Date;
64
66
  updatedAt: Date;
65
67
  hasUnseenMessages?: boolean;
@@ -87,6 +89,8 @@ export type ChannelDetails = {
87
89
  spec: string;
88
90
  state: unknown;
89
91
  cwd?: string;
92
+ /** Agent ids for this channel (from `state.json`). */
93
+ participants: string[];
90
94
  threads?: Thread[];
91
95
  };
92
96
 
@@ -113,6 +117,8 @@ export interface Storage {
113
117
  agentId: string;
114
118
  name: string;
115
119
  description?: string;
120
+ /** Avatar/logo URL or data URI; persisted in AGENT.md frontmatter. */
121
+ image?: string;
116
122
  instructions: string;
117
123
  plugins: PluginRef[];
118
124
  }) => Promise<void>;
@@ -120,6 +126,8 @@ export interface Storage {
120
126
  agentId: string;
121
127
  name?: string;
122
128
  description?: string;
129
+ /** Omit to leave unchanged; empty string removes stored image. */
130
+ image?: string;
123
131
  instructions?: string;
124
132
  plugins?: PluginRef[];
125
133
  }) => Promise<void>;
@@ -127,15 +127,24 @@ class ChannelDetailsProvider implements ContextProvider {
127
127
  name = 'channel-details';
128
128
  async provide(state: OpenBotState): Promise<ContextItem[]> {
129
129
  if (!state.channelDetails) return [];
130
- const spec = state.channelDetails.spec?.trim();
131
- if (!spec) return [];
132
130
 
133
- return [{
134
- id: 'channel-details',
135
- type: 'channel',
136
- priority: 80,
137
- content: `# Channel you are in: ${state.channelDetails.name}\n\n Channel Specification: ${spec}`,
138
- }];
131
+ const participants = state.channelDetails.participants;
132
+ if (!participants?.length) return [];
133
+
134
+ const channelLabel =
135
+ state.channelDetails.name?.trim() || state.channelDetails.id;
136
+ const lines = participants.map((id) => `- \`${id}\``).join('\n');
137
+
138
+ return [
139
+ {
140
+ id: 'channel-details',
141
+ type: 'channel',
142
+ priority: 80,
143
+ content:
144
+ `## Channel participants (${channelLabel})\n` +
145
+ `Agent ids collaborating in this channel:\n${lines}`,
146
+ },
147
+ ];
139
148
  }
140
149
  }
141
150
 
@@ -9,7 +9,9 @@ import {
9
9
  VARIABLES_FILE,
10
10
  } from '../app/config.js';
11
11
  import fs from 'node:fs/promises';
12
+ import { readFileSync } from 'node:fs';
12
13
  import path from 'node:path';
14
+ import { fileURLToPath, pathToFileURL } from 'node:url';
13
15
  import crypto from 'node:crypto';
14
16
  import matter from 'gray-matter';
15
17
  import {
@@ -28,7 +30,6 @@ import { listBuiltInPlugins, parsePluginModule } from '../registry/plugins.js';
28
30
  import { OpenBotEvent, OpenBotState } from '../app/types.js';
29
31
  import { processService } from '../harness/process.js';
30
32
  import { memoryService } from './memory.js';
31
- import { pathToFileURL } from 'node:url';
32
33
 
33
34
  const resolveBaseDir = () => {
34
35
  const config = loadConfig();
@@ -40,6 +41,24 @@ const ENTITY_SVG_CANDIDATE_NAMES = ['avatar.svg', 'icon.svg', 'image.svg', 'logo
40
41
  const toSvgDataUrl = (svg: string) =>
41
42
  `data:image/svg+xml;base64,${Buffer.from(svg, 'utf-8').toString('base64')}`;
42
43
 
44
+ let bundledSystemAgentImage: string | undefined;
45
+ let bundledSystemAgentImageLoaded = false;
46
+
47
+ /** OpenBot mark from `src/assets/icon.svg` (also copied to `dist/assets` at build). */
48
+ function getBundledSystemAgentImage(): string | undefined {
49
+ if (bundledSystemAgentImageLoaded) return bundledSystemAgentImage;
50
+ bundledSystemAgentImageLoaded = true;
51
+ try {
52
+ const iconPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../assets/icon.svg');
53
+ const trimmed = readFileSync(iconPath, 'utf-8').trim();
54
+ if (!trimmed.startsWith('<svg')) return undefined;
55
+ bundledSystemAgentImage = toSvgDataUrl(trimmed);
56
+ } catch {
57
+ bundledSystemAgentImage = undefined;
58
+ }
59
+ return bundledSystemAgentImage;
60
+ }
61
+
43
62
  const tryReadSvgDataUrl = async (filePath: string): Promise<string | null> => {
44
63
  try {
45
64
  const svg = await fs.readFile(filePath, 'utf-8');
@@ -101,7 +120,7 @@ function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails
101
120
  const defaults: AgentDetails = {
102
121
  id: SYSTEM_AGENT_ID,
103
122
  name: 'OpenBot',
104
- image: undefined,
123
+ image: getBundledSystemAgentImage(),
105
124
  description:
106
125
  'First-party orchestration agent for OpenBot. Coordinates other agents via handoff.',
107
126
  instructions: AI_SDK_SYSTEM_PROMPT,
@@ -297,6 +316,25 @@ const listPluginsFromDisk = async (): Promise<PluginDescriptor[]> => {
297
316
  const isRecord = (value: unknown): value is Record<string, unknown> =>
298
317
  !!value && typeof value === 'object' && !Array.isArray(value);
299
318
 
319
+ /** Display-oriented fields persisted in a channel's `state.json`. */
320
+ const readChannelStateFileFields = (
321
+ parsed: unknown,
322
+ ): { name?: string; cwd?: string; participants: string[] } => {
323
+ if (!isRecord(parsed)) {
324
+ return { participants: [] };
325
+ }
326
+ const name =
327
+ typeof parsed.name === 'string' && parsed.name.trim() ? parsed.name.trim() : undefined;
328
+ const cwd = typeof parsed.cwd === 'string' ? parsed.cwd : undefined;
329
+ const participants: string[] = [];
330
+ if (Array.isArray(parsed.participants)) {
331
+ for (const x of parsed.participants) {
332
+ if (typeof x === 'string' && x.trim()) participants.push(x.trim());
333
+ }
334
+ }
335
+ return { name, cwd, participants };
336
+ };
337
+
300
338
  /**
301
339
  * Parse the `plugins:` array from AGENT.md frontmatter. Each entry must have an
302
340
  * `id`; `config` is optional. Strings are accepted as a shorthand for `{ id }`.
@@ -357,20 +395,26 @@ export const storageService = {
357
395
  const channelDir = getConversationDir(name);
358
396
  const statePath = path.join(channelDir, 'state.json');
359
397
  let cwd: string | undefined;
398
+ let displayName = name;
399
+ let participants: string[] = [];
360
400
 
361
401
  try {
362
402
  const stateContent = await fs.readFile(statePath, 'utf-8');
363
- const state = JSON.parse(stateContent);
364
- cwd = typeof state.cwd === 'string' ? state.cwd : undefined;
403
+ const parsed = JSON.parse(stateContent);
404
+ const fields = readChannelStateFileFields(parsed);
405
+ cwd = fields.cwd;
406
+ displayName = fields.name ?? name;
407
+ participants = fields.participants;
365
408
  } catch {
366
409
  // ignore
367
410
  }
368
411
 
369
412
  const channel: Channel = {
370
413
  id: name,
371
- name: name,
414
+ name: displayName,
372
415
  description: '',
373
416
  cwd,
417
+ participants,
374
418
  createdAt: new Date(),
375
419
  updatedAt: new Date(),
376
420
  };
@@ -591,14 +635,17 @@ export const storageService = {
591
635
  }
592
636
  }
593
637
 
594
- const cwd = isRecord(state) && typeof state.cwd === 'string' ? state.cwd : undefined;
638
+ const diskFields = readChannelStateFileFields(state);
639
+ const cwd = diskFields.cwd;
640
+ const displayName = diskFields.name ?? channelId;
595
641
 
596
642
  const details: ChannelDetails = {
597
643
  id: channelId,
598
- name: channelId,
644
+ name: displayName,
599
645
  spec,
600
646
  state,
601
647
  cwd,
648
+ participants: diskFields.participants,
602
649
  };
603
650
 
604
651
  details.threads = await storageService.getThreads({ channelId });
@@ -764,6 +811,10 @@ export const storageService = {
764
811
  const stats = await fs.stat(agentMdPath);
765
812
 
766
813
  const pluginRefs = parsePluginRefs(data.plugins);
814
+ const frontmatterImage =
815
+ typeof data.image === 'string' && data.image.trim() !== ''
816
+ ? data.image.trim()
817
+ : undefined;
767
818
 
768
819
  diskDetails = {
769
820
  id: agentId,
@@ -772,7 +823,7 @@ export const storageService = {
772
823
  plugins: pluginRefs.map((ref) => ref.id),
773
824
  pluginRefs,
774
825
  description: typeof data.description === 'string' ? data.description : '',
775
- image: discoveredImage || undefined,
826
+ image: frontmatterImage || discoveredImage || undefined,
776
827
  createdAt: stats.birthtime,
777
828
  updatedAt: stats.mtime,
778
829
  };
@@ -802,12 +853,14 @@ export const storageService = {
802
853
  agentId,
803
854
  name,
804
855
  description = '',
856
+ image,
805
857
  instructions,
806
858
  plugins,
807
859
  }: {
808
860
  agentId: string;
809
861
  name: string;
810
862
  description?: string;
863
+ image?: string;
811
864
  instructions: string;
812
865
  plugins: PluginRef[];
813
866
  }): Promise<void> => {
@@ -836,6 +889,9 @@ export const storageService = {
836
889
  description,
837
890
  plugins: serializePluginRefs(plugins),
838
891
  };
892
+ if (typeof image === 'string' && image.trim() !== '') {
893
+ data.image = image.trim();
894
+ }
839
895
 
840
896
  const body = matter.stringify(`${instructions.trim()}\n`, data);
841
897
  await fs.writeFile(agentMdPath, body, 'utf-8');
@@ -844,12 +900,14 @@ export const storageService = {
844
900
  agentId,
845
901
  name,
846
902
  description,
903
+ image,
847
904
  instructions,
848
905
  plugins,
849
906
  }: {
850
907
  agentId: string;
851
908
  name?: string;
852
909
  description?: string;
910
+ image?: string;
853
911
  instructions?: string;
854
912
  plugins?: PluginRef[];
855
913
  }): Promise<void> => {
@@ -874,6 +932,13 @@ export const storageService = {
874
932
  if (name !== undefined) nextData.name = name;
875
933
  if (description !== undefined) nextData.description = description;
876
934
  if (plugins !== undefined) nextData.plugins = serializePluginRefs(plugins);
935
+ if (image !== undefined) {
936
+ if (typeof image === 'string' && image.trim() !== '') {
937
+ nextData.image = image.trim();
938
+ } else {
939
+ delete nextData.image;
940
+ }
941
+ }
877
942
 
878
943
  const nextContent = instructions !== undefined ? instructions : parsed.content;
879
944
  const body = matter.stringify(`${String(nextContent).trim()}\n`, nextData);