openbot 0.4.4 → 0.4.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/src/app/types.ts CHANGED
@@ -141,6 +141,10 @@ export type GetEventsResultEvent = BaseEvent & {
141
141
  };
142
142
  };
143
143
 
144
+ export type GetActiveRunsEvent = BaseEvent & {
145
+ type: 'action:storage:get-active-runs';
146
+ };
147
+
144
148
  export type GetAgentDetailsEvent = BaseEvent & {
145
149
  type: 'action:storage:get-agent-details';
146
150
  data: {
@@ -297,8 +301,6 @@ export type PatchChannelDetailsEvent = BaseEvent & {
297
301
  state?: Record<string, unknown>;
298
302
  spec?: string;
299
303
  cwd?: string;
300
- /** When set, replaces `state.json` `participants` (merged after `state` if both are sent). */
301
- participants?: string[];
302
304
  };
303
305
  };
304
306
 
@@ -306,7 +308,7 @@ export type PatchChannelDetailsResultEvent = BaseEvent & {
306
308
  type: 'action:patch_channel_details:result';
307
309
  data: {
308
310
  success: boolean;
309
- updatedFields: ('state' | 'spec' | 'cwd' | 'participants')[];
311
+ updatedFields: ('state' | 'spec' | 'cwd')[];
310
312
  };
311
313
  };
312
314
 
@@ -565,8 +567,6 @@ export type CreateChannelEvent = BaseEvent & {
565
567
  spec?: string;
566
568
  initialState?: Record<string, unknown>;
567
569
  cwd?: string;
568
- /** Initial channel agent ids; written into `state.json` (overrides `initialState.participants` if both are set). */
569
- participants?: string[];
570
570
  };
571
571
  meta?: {
572
572
  toolCallId?: string;
@@ -590,8 +590,6 @@ export type UpdateChannelEvent = BaseEvent & {
590
590
  channelId?: string;
591
591
  name?: string;
592
592
  cwd?: string;
593
- /** Replaces the channel participant list when provided. */
594
- participants?: string[];
595
593
  };
596
594
  };
597
595
 
@@ -651,7 +649,7 @@ export type UIWidgetAction = {
651
649
  export type UIWidgetField = {
652
650
  id: string;
653
651
  label: string;
654
- type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'multiselect' | 'date';
652
+ type: 'text' | 'textarea' | 'number' | 'boolean' | 'select' | 'multiselect' | 'date' | 'password';
655
653
  description?: string;
656
654
  placeholder?: string;
657
655
  required?: boolean;
@@ -847,7 +845,6 @@ export type ListMarketplaceRegistryResultEvent = BaseEvent & {
847
845
  image?: string;
848
846
  spec?: string;
849
847
  initialState?: Record<string, unknown>;
850
- participants: string[];
851
848
  starterPrompts?: Array<{ label: string; prompt: string }>;
852
849
  }>;
853
850
  error?: string;
@@ -859,7 +856,6 @@ export type InstallChannelEvent = BaseEvent & {
859
856
  data: {
860
857
  channelId: string;
861
858
  name?: string;
862
- participants?: string[];
863
859
  initialState?: Record<string, unknown>;
864
860
  };
865
861
  };
@@ -1059,6 +1055,7 @@ export type OpenBotEvent =
1059
1055
  | DeleteAgentResultEvent
1060
1056
  | GetEventsEvent
1061
1057
  | GetEventsResultEvent
1058
+ | GetActiveRunsEvent
1062
1059
  | StreamThreadEvent
1063
1060
  | GetVariablesEvent
1064
1061
  | GetVariablesResultEvent
@@ -27,20 +27,11 @@ export const getContextBudgetForModel = (modelString: string): number => {
27
27
  /** Built-in orchestrator agent id. */
28
28
  export const ORCHESTRATOR_AGENT_ID = 'system';
29
29
 
30
- /**
31
- * Check if a channel is a solo DM (only the agent is present).
32
- */
33
- export function isDmSoloChannel(participants: string[], agentId: string): boolean {
34
- return participants.length === 0 || (participants.length === 1 && participants[0] === agentId);
35
- }
36
-
37
30
  /**
38
31
  * Simplified context builder for MVP.
39
32
  */
40
33
  export async function buildContext(state: OpenBotState, storage?: Storage): Promise<string> {
41
34
  const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
42
- const participants = channelDetails?.participants || [];
43
- const isDm = isDmSoloChannel(participants, agentId);
44
35
 
45
36
  const sections: string[] = [];
46
37
 
@@ -54,23 +45,13 @@ export async function buildContext(state: OpenBotState, storage?: Storage): Prom
54
45
 
55
46
  // 2. Environment
56
47
  let env = '## ENVIRONMENT\n';
57
- if (isDm) {
58
- env += '- Mode: Direct Message (Solo)\n';
59
- } else {
60
- const channelName = channelDetails?.name || channelId;
61
- env += `- Mode: Channel (#${channelName})\n`;
62
- if (channelDetails?.cwd) {
63
- env += `- Workspace: ${channelDetails.cwd}\n`;
64
- }
65
- if (threadId) {
66
- env += `- Thread: ${threadDetails?.name || threadId}\n`;
67
- }
68
- const peerIds = participants.filter((id: string) => id !== agentId);
69
- const participantLabels = peerIds.map((id) => {
70
- const agent = allAgents.find((a) => a.id === id);
71
- return agent ? `${agent.name} (${id})` : id;
72
- });
73
- env += `- Participants: ${participantLabels.length > 0 ? participantLabels.join(', ') : 'None'}\n`;
48
+ const channelName = channelDetails?.name || channelId;
49
+ env += `- Mode: Channel (#${channelName})\n`;
50
+ if (channelDetails?.cwd) {
51
+ env += `- Workspace: ${channelDetails.cwd}\n`;
52
+ }
53
+ if (threadId) {
54
+ env += `- Thread: ${threadDetails?.name || threadId}\n`;
74
55
  }
75
56
  sections.push(env);
76
57
 
@@ -2,6 +2,7 @@ import { MelonyPlugin, RuntimeContext } from 'melony';
2
2
  import { generateText, type LanguageModel } from 'ai';
3
3
  import { openai } from '@ai-sdk/openai';
4
4
  import { anthropic } from '@ai-sdk/anthropic';
5
+ import { google } from '@ai-sdk/google';
5
6
  import { OpenBotEvent, OpenBotState, AgentInvokeEvent } from '../../app/types.js';
6
7
  import { eventsToModelMessages } from './history.js';
7
8
  import { Storage } from '../../services/plugins/domain.js';
@@ -10,8 +11,33 @@ import {
10
11
  ORCHESTRATOR_AGENT_ID,
11
12
  buildContext,
12
13
  } from './context.js';
13
- import { saveConfig } from '../../app/config.js';
14
- import { API_KEY_SETUP_MESSAGE, OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
14
+ import { saveConfig, DEFAULT_MARKETPLACE_REGISTRY_URL } from '../../app/config.js';
15
+ import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
16
+
17
+ interface ModelRegistry {
18
+ providers: Record<
19
+ string,
20
+ {
21
+ label: string;
22
+ models: Array<{ id: string; label: string; description: string }>;
23
+ }
24
+ >;
25
+ }
26
+
27
+ let cachedRegistry: ModelRegistry | null = null;
28
+
29
+ async function fetchRegistry(): Promise<ModelRegistry | null> {
30
+ if (cachedRegistry) return cachedRegistry;
31
+ try {
32
+ const response = await fetch(DEFAULT_MARKETPLACE_REGISTRY_URL);
33
+ if (!response.ok) throw new Error(`Failed to fetch registry: ${response.statusText}`);
34
+ cachedRegistry = (await response.json()) as ModelRegistry;
35
+ return cachedRegistry;
36
+ } catch (error) {
37
+ console.error('[openbot] Failed to fetch model registry:', error);
38
+ return null;
39
+ }
40
+ }
15
41
 
16
42
  export interface OpenBotRuntimeOptions {
17
43
  /** Provider model string (e.g. `openai/gpt-4o-mini`, `anthropic/claude-3-5-sonnet-20240620`). */
@@ -34,6 +60,8 @@ function resolveModel(modelString: string): LanguageModel {
34
60
  return openai(modelId);
35
61
  case 'anthropic':
36
62
  return anthropic(modelId);
63
+ case 'google':
64
+ return google(modelId);
37
65
  default:
38
66
  throw new Error(`Unsupported AI provider: "${provider}"`);
39
67
  }
@@ -246,49 +274,20 @@ export const openbotRuntime =
246
274
  errorMessage.includes('authentication');
247
275
 
248
276
  if (isApiKeyError) {
249
- const [currentProvider, ...rest] = currentModelString.split('/');
250
- const currentModelId = rest.join('/');
251
-
252
277
  yield {
253
278
  type: 'client:ui:widget',
254
279
  data: {
255
- kind: 'form',
256
- widgetId: `api_key_request_${Date.now()}`,
257
- title: `AI Provider API Key Required`,
258
- description: API_KEY_SETUP_MESSAGE,
259
- fields: [
260
- {
261
- id: 'provider',
262
- label: 'Provider',
263
- type: 'select',
264
- required: true,
265
- options: [
266
- { label: 'OpenAI', value: 'openai' },
267
- { label: 'Anthropic', value: 'anthropic' },
268
- ],
269
- defaultValue: currentProvider === 'anthropic' ? 'anthropic' : 'openai',
270
- },
271
- {
272
- id: 'model',
273
- label: 'Model',
274
- type: 'text',
275
- description:
276
- 'Model name without the provider prefix (e.g. `gpt-4o-mini` or `claude-3-5-sonnet-20240620`).',
277
- placeholder: 'gpt-4o-mini',
278
- required: true,
279
- defaultValue: currentModelId,
280
- },
281
- {
282
- id: 'apiKey',
283
- label: 'API Key',
284
- type: 'text',
285
- placeholder: `sk-...`,
286
- required: true,
287
- },
280
+ kind: 'choice',
281
+ widgetId: `api_provider_selection_${Date.now()}`,
282
+ title: `Setup AI Provider`,
283
+ description: `Select a provider to continue.`,
284
+ actions: [
285
+ { id: 'openai', label: 'OpenAI', variant: 'primary' },
286
+ { id: 'anthropic', label: 'Anthropic', variant: 'primary' },
287
+ { id: 'google', label: 'Google', variant: 'primary' },
288
288
  ],
289
- submitLabel: 'Save & Continue',
290
289
  metadata: {
291
- type: 'api_key_request',
290
+ type: 'api_provider_selection',
292
291
  },
293
292
  },
294
293
  meta: { agentId: context.state.agentId, threadId },
@@ -315,49 +314,6 @@ export const openbotRuntime =
315
314
 
316
315
  const threadId = event.meta?.threadId || context.state.threadId;
317
316
 
318
- // Auto-add participants if tagged in the prompt
319
- const content = (event as AgentInvokeEvent).data?.content;
320
- if (content && storage) {
321
- try {
322
- const allAgents = await storage.getAgents();
323
- const tags = content.match(/@([\w-]+)/g);
324
- if (tags) {
325
- const taggedAgentIds = tags.map((t) => t.slice(1));
326
- const validAgentIds = taggedAgentIds.filter((id) =>
327
- allAgents.some((a) => a.id === id),
328
- );
329
-
330
- const currentParticipants = context.state.channelDetails?.participants || [];
331
- const newParticipants = [...new Set([...currentParticipants, ...validAgentIds])];
332
-
333
- if (newParticipants.length > currentParticipants.length) {
334
- // Update storage
335
- await storage.patchChannelState({
336
- channelId: context.state.channelId,
337
- state: { participants: newParticipants },
338
- });
339
-
340
- // Refresh local state
341
- context.state.channelDetails = await storage.getChannelDetails({
342
- channelId: context.state.channelId,
343
- });
344
-
345
- // Notify UI/others about the change
346
- yield {
347
- type: 'action:patch_channel_details:result',
348
- data: { success: true, updatedFields: ['participants'] },
349
- meta: {
350
- agentId: context.state.agentId,
351
- threadId,
352
- },
353
- } as OpenBotEvent;
354
- }
355
- }
356
- } catch (error) {
357
- console.warn('[openbot] Failed to auto-add participants from tags:', error);
358
- }
359
- }
360
-
361
317
  // clear the tool batch if the agent is invoked
362
318
  // this is to prevent the tool batch from being used for a new agent invocation
363
319
  await createToolBatchTracker(
@@ -394,15 +350,98 @@ export const openbotRuntime =
394
350
  });
395
351
 
396
352
  builder.on('client:ui:widget:response', async function* (event, context) {
397
- const { metadata, values } = event.data;
353
+ const { metadata, values, actionId } = event.data;
354
+ const threadId = event.meta?.threadId || context.state.threadId;
355
+
356
+ if (metadata?.type === 'api_provider_selection') {
357
+ const provider = actionId;
358
+ const [_, ...rest] = currentModelString.split('/');
359
+ const currentModelId = rest.join('/');
360
+
361
+ const registry = await fetchRegistry();
362
+ const providerData = registry?.providers[provider as string];
363
+
364
+ const providerLinks: Record<string, string> = {
365
+ openai: 'https://platform.openai.com/api-keys',
366
+ anthropic: 'https://console.anthropic.com/settings/keys',
367
+ google: 'https://aistudio.google.com/app/apikey',
368
+ };
369
+
370
+ const label = providerData?.label || (provider as string);
371
+ const link = providerLinks[provider as string] || '';
372
+
373
+ const modelOptions = providerData?.models.map((m) => ({
374
+ label: m.label,
375
+ value: m.id,
376
+ }));
377
+
378
+ const defaultModel = modelOptions?.[0]?.value || 'gpt-4o-mini';
379
+ const defaultValue =
380
+ modelOptions?.find((m) => m.value === currentModelId)?.value ||
381
+ currentModelId ||
382
+ defaultModel;
383
+
384
+ yield {
385
+ type: 'client:ui:widget',
386
+ data: {
387
+ widgetId: event.data.widgetId,
388
+ kind: 'message',
389
+ title: 'Provider Selected',
390
+ body: `${label} provider was selected.`,
391
+ state: 'submitted',
392
+ display: 'collapsed',
393
+ disabled: true,
394
+ actions: [],
395
+ },
396
+ meta: { agentId: context.state.agentId, threadId },
397
+ } as OpenBotEvent;
398
+
399
+ yield {
400
+ type: 'client:ui:widget',
401
+ data: {
402
+ kind: 'form',
403
+ widgetId: `api_key_request_${Date.now()}`,
404
+ title: `${label} Setup`,
405
+ description: `Enter your API key and select a model.`,
406
+ fields: [
407
+ {
408
+ id: 'model',
409
+ label: 'Model',
410
+ type: modelOptions ? 'select' : 'text',
411
+ description: modelOptions ? undefined : `Model name (e.g. \`${defaultModel}\`).`,
412
+ options: modelOptions,
413
+ placeholder: defaultModel,
414
+ required: true,
415
+ defaultValue,
416
+ },
417
+ {
418
+ id: 'apiKey',
419
+ label: 'API Key',
420
+ type: 'password',
421
+ description: `Get your key here: [${link}](${link})`,
422
+ placeholder: `sk-...`,
423
+ required: true,
424
+ },
425
+ ],
426
+ submitLabel: 'Save & Continue',
427
+ metadata: {
428
+ type: 'api_key_request',
429
+ provider,
430
+ },
431
+ },
432
+ meta: { agentId: context.state.agentId, threadId },
433
+ } as OpenBotEvent;
434
+ return;
435
+ }
436
+
398
437
  if (metadata?.type !== 'api_key_request') return;
399
- if (!values?.apiKey || !values?.provider || !values?.model) return;
438
+ if (!values?.apiKey || !values?.model) return;
400
439
 
401
- const provider = String(values.provider);
440
+ const provider = String(values.provider || metadata.provider);
402
441
  const modelId = String(values.model).trim();
403
442
  const apiKey = String(values.apiKey);
404
443
 
405
- if (provider !== 'openai' && provider !== 'anthropic') {
444
+ if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'google') {
406
445
  yield {
407
446
  type: 'agent:output',
408
447
  data: { content: `Unsupported provider: ${provider}` },
@@ -411,7 +450,12 @@ export const openbotRuntime =
411
450
  return;
412
451
  }
413
452
 
414
- const envVar = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
453
+ const envVar =
454
+ provider === 'openai'
455
+ ? 'OPENAI_API_KEY'
456
+ : provider === 'anthropic'
457
+ ? 'ANTHROPIC_API_KEY'
458
+ : 'GOOGLE_GENERATIVE_AI_API_KEY';
415
459
  const newModelString = `${provider}/${modelId}`;
416
460
 
417
461
  if (!storage) return;
@@ -460,10 +504,14 @@ export const openbotRuntime =
460
504
  title: 'API Key Saved',
461
505
  body: `Successfully saved ${provider} API key and selected model \`${newModelString}\`. You can now continue your conversation.`,
462
506
  state: 'submitted',
463
- actions: [{ id: 'ok', label: 'Got it', variant: 'primary' }],
507
+ display: 'collapsed',
508
+ disabled: true,
509
+ actions: [],
464
510
  },
465
511
  meta: { agentId: context.state.agentId },
466
512
  };
513
+
514
+ yield* runLLM(context, threadId);
467
515
  } catch (error) {
468
516
  yield {
469
517
  type: 'agent:output',
@@ -7,13 +7,12 @@ export const OPENBOT_SYSTEM_PROMPT = [
7
7
  '- **Credential Guidance**: If an agent or tool requires credentials, inform the user they can be managed under "Settings > Variables".',
8
8
  '',
9
9
  '# CORE MISSION',
10
- 'You almost never execute tasks yourself. Instead, you delegate tasks to specialized agents (channel participants). You act as a high-level manager, ensuring the right agent is working on the right task.',
10
+ 'You almost never execute tasks yourself. Instead, you delegate tasks to specialized agents. You act as a high-level manager, ensuring the right agent is working on the right task.',
11
11
  '',
12
12
  '# OPERATIONAL GUIDELINES',
13
13
  '- **Channel and Threads**: The main and only way to communicate and act is through channels and threads. There might be a channel called "uncategorized" for general purpose communication.',
14
- '- **Agent Participation**: ONLY add an agent via `patch_channel_details` if the user manually tags them (e.g., `@name`) AND they are missing from the `Participants` list in `ENVIRONMENT`.',
15
- '- **Delegation**: NEVER delegate to an agent who is not a participant. Only if existing participants clearly cannot handle a task should you suggest relevant agents from the `INSTALLED AGENTS` list.',
16
- '- **Bash Tool Usage**: You should use the `bash` tool very rarely. Only use it when the user explicitly requests a command to be run or when it is absolutely necessary for a task that no other participant can handle.',
14
+ '- **Delegation**: You can delegate tasks to any specialized agent in the `INSTALLED AGENTS` list.',
15
+ '- **Bash Tool Usage**: You should use the `bash` tool very rarely. Only use it when the user explicitly requests a command to be run or when it is absolutely necessary for a task that no other agent can handle.',
17
16
  '- **Context Awareness**: Use the provided ENVIRONMENT, CHANNEL SPECIFICATION, and MEMORIES to maintain continuity. Do not ask for information already present in these sections.',
18
17
  '- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.',
19
18
  '- **Structured Interaction**: Use the `render_widget` tool to collect information via forms, offer choices, or display lists. This is preferred over asking multiple separate questions in plain text.',
@@ -21,7 +20,3 @@ export const OPENBOT_SYSTEM_PROMPT = [
21
20
  '# COMMUNICATION STYLE',
22
21
  '- Be always concise, professional, and proactive.',
23
22
  ].join('\n');
24
-
25
- /** Shown in the API key setup form when no provider credentials are configured. */
26
- export const API_KEY_SETUP_MESSAGE =
27
- 'OpenBot runs AI agents locally with tools, memory, and delegation. Bring your own OpenAI or Anthropic key — it stays on your machine. Use the form below to get started.';
@@ -67,10 +67,9 @@ export const pluginManagerPlugin: Plugin = {
67
67
  const {
68
68
  channelId: instanceId,
69
69
  name: templateName,
70
- participants: customParticipants,
71
70
  initialState: customInitialState,
72
71
  } = event.data;
73
- const { agents: marketplaceAgents, channels } = await resolveMarketplaceRegistry();
72
+ const { channels } = await resolveMarketplaceRegistry();
74
73
 
75
74
  // Try to find the template by ID or Name
76
75
  const channelListing =
@@ -78,64 +77,17 @@ export const pluginManagerPlugin: Plugin = {
78
77
  channels.find((c) => c.name === templateName);
79
78
 
80
79
  const channelId = instanceId;
81
- const participants = customParticipants || channelListing?.participants || [];
82
80
  const initialState = {
83
81
  ...(channelListing?.initialState || {}),
84
82
  ...(customInitialState || {}),
85
83
  };
86
84
  const spec = channelListing?.spec || '';
87
85
 
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
86
  // 2. Create the channel
132
87
  await storage.createChannel({
133
88
  channelId,
134
89
  spec,
135
- initialState: {
136
- ...initialState,
137
- participants,
138
- },
90
+ initialState,
139
91
  });
140
92
 
141
93
  const channelUrl = `/channels/${channelId}`;
@@ -48,20 +48,13 @@ const storageToolDefinitions = {
48
48
  'Markdown content for the channel specification (SPEC.md). Use for goals and rules.',
49
49
  ),
50
50
  cwd: z.string().optional().describe('Current working directory for the channel.'),
51
- participants: z
52
- .array(z.string())
53
- .optional()
54
- .describe(
55
- 'List of agent IDs that are participants in this channel. When a user tags an agent (e.g. @agent-id), you should ensure they are added to this list if they are not already there.',
56
- ),
57
51
  })
58
52
  .refine(
59
53
  (value) =>
60
54
  value.state !== undefined ||
61
55
  value.spec !== undefined ||
62
- value.cwd !== undefined ||
63
- value.participants !== undefined,
64
- { message: 'Provide at least one of state, spec, cwd, or participants.' },
56
+ value.cwd !== undefined,
57
+ { message: 'Provide at least one of state, spec, or cwd.' },
65
58
  ),
66
59
  },
67
60
  patch_thread_details: {
@@ -155,7 +148,7 @@ export const storagePlugin: Plugin = {
155
148
  });
156
149
 
157
150
  builder.on('action:create_channel', async function* (event, context) {
158
- const { channelId, spec, initialState, cwd, participants } = (event as any).data;
151
+ const { channelId, spec, initialState, cwd } = (event as any).data;
159
152
  const rawChannelId = (channelId || '').trim();
160
153
  const channelSpec = typeof spec === 'string' ? spec : '';
161
154
 
@@ -173,15 +166,6 @@ export const storagePlugin: Plugin = {
173
166
  const channelUrl = `/channels/${rawChannelId}`;
174
167
 
175
168
  const mergedInitial: Record<string, unknown> = { ...(initialState || {}) };
176
- if (participants !== undefined) {
177
- const normalized = Array.isArray(participants)
178
- ? participants
179
- .filter((x: unknown): x is string => typeof x === 'string')
180
- .map((s: string) => s.trim())
181
- .filter(Boolean)
182
- : [];
183
- mergedInitial.participants = normalized;
184
- }
185
169
 
186
170
  try {
187
171
  await storage.createChannel({
@@ -254,7 +238,6 @@ export const storagePlugin: Plugin = {
254
238
  channelId?: string;
255
239
  name?: string;
256
240
  cwd?: string;
257
- participants?: string[];
258
241
  };
259
242
  const targetChannelId = (data.channelId || context.state.channelId || '').trim();
260
243
  const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
@@ -279,17 +262,6 @@ export const storagePlugin: Plugin = {
279
262
  patch.cwd = data.cwd.trim();
280
263
  updatedFields.push('cwd');
281
264
  }
282
- if (data.participants !== undefined) {
283
- if (Array.isArray(data.participants)) {
284
- patch.participants = data.participants
285
- .filter((x): x is string => typeof x === 'string')
286
- .map((s) => s.trim())
287
- .filter(Boolean);
288
- } else {
289
- patch.participants = [];
290
- }
291
- updatedFields.push('participants');
292
- }
293
265
 
294
266
  try {
295
267
  if (updatedFields.length > 0) {
@@ -317,13 +289,12 @@ export const storagePlugin: Plugin = {
317
289
  });
318
290
 
319
291
  builder.on('action:patch_channel_details', async function* (event, context) {
320
- const updatedFields: ('state' | 'spec' | 'cwd' | 'participants')[] = [];
292
+ const updatedFields: ('state' | 'spec' | 'cwd')[] = [];
321
293
  const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
322
294
  const data = (event.data || {}) as {
323
295
  state?: Record<string, unknown>;
324
296
  spec?: string;
325
297
  cwd?: string;
326
- participants?: string[];
327
298
  };
328
299
  try {
329
300
  if (data.state !== undefined) {
@@ -347,19 +318,6 @@ export const storagePlugin: Plugin = {
347
318
  });
348
319
  updatedFields.push('cwd');
349
320
  }
350
- if (data.participants !== undefined) {
351
- const normalized = Array.isArray(data.participants)
352
- ? data.participants
353
- .filter((x): x is string => typeof x === 'string')
354
- .map((s) => s.trim())
355
- .filter(Boolean)
356
- : [];
357
- await storage.patchChannelState({
358
- channelId: context.state.channelId,
359
- state: { participants: normalized },
360
- });
361
- updatedFields.push('participants');
362
- }
363
321
 
364
322
  context.state.channelDetails = await storage.getChannelDetails({
365
323
  channelId: context.state.channelId,