openbot 0.4.5 → 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.
@@ -0,0 +1,3 @@
1
+ /** Default channel when requests omit channelId (general-purpose conversation). */
2
+ export const UNCATEGORIZED_CHANNEL_ID = 'uncategorized';
3
+ export const DEFAULT_UNCATEGORIZED_SPEC = '# Uncategorized\n\nGeneral-purpose channel for conversations without a dedicated channel.';
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.4.5');
19
+ program.name('openbot').description('OpenBot CLI').version('0.4.6');
20
20
  program
21
21
  .command('start')
22
22
  .description('Start the OpenBot harness')
@@ -17,6 +17,7 @@ import { buildWorkspaceFileUrl, getPublicBaseUrl, openChannelFileStream, } from
17
17
  import { ensureEventId, openBotEventFromQuery } from './utils.js';
18
18
  import { abortRegistry, abortKey } from '../services/abort.js';
19
19
  import { resolveRespondingAgentId } from './responding-agent.js';
20
+ import { DEFAULT_UNCATEGORIZED_SPEC, UNCATEGORIZED_CHANNEL_ID, } from './channel-ids.js';
20
21
  export async function startServer(options = {}) {
21
22
  const publishEventSchema = z
22
23
  .object({
@@ -43,8 +44,17 @@ export async function startServer(options = {}) {
43
44
  // Pre-warm caches for agents and plugins to speed up first UI load
44
45
  storageService.getAgents().catch((err) => console.warn('[server] Failed to pre-warm agents cache', err));
45
46
  storageService.getPlugins().catch((err) => console.warn('[server] Failed to pre-warm plugins cache', err));
47
+ const getRawChannelId = (req) => {
48
+ const raw = req.get('x-openbot-channel-id') ||
49
+ req.query.channelId ||
50
+ (req.body && req.body.channelId);
51
+ if (typeof raw !== 'string')
52
+ return undefined;
53
+ const trimmed = raw.trim();
54
+ return trimmed || undefined;
55
+ };
46
56
  const getContext = (req) => {
47
- const channelId = req.get('x-openbot-channel-id') || req.query.channelId || (req.body && req.body.channelId);
57
+ const rawChannelId = getRawChannelId(req);
48
58
  const threadId = req.get('x-openbot-thread-id') || req.query.threadId || (req.body && req.body.threadId);
49
59
  const agentId = req.get('x-openbot-agent-id') || req.query.agentId || (req.body && req.body.agentId);
50
60
  const runId = req.get('x-openbot-run-id') ||
@@ -55,7 +65,8 @@ export async function startServer(options = {}) {
55
65
  req.query.responseType ||
56
66
  (req.body && req.body.responseType);
57
67
  return {
58
- channelId: (channelId || (threadId ? 'uncategorized' : 'uncategorized')), // Default to uncategorized if none
68
+ channelId: (rawChannelId || UNCATEGORIZED_CHANNEL_ID),
69
+ rawChannelId,
59
70
  threadId: threadId,
60
71
  agentId: agentId,
61
72
  runId: runId,
@@ -434,6 +445,17 @@ export async function startServer(options = {}) {
434
445
  };
435
446
  try {
436
447
  ensureEventId(event);
448
+ const isUserConversationStart = event.type === 'agent:invoke' &&
449
+ event.data?.role === 'user' &&
450
+ typeof event.data.content === 'string' &&
451
+ event.data.content.trim().length > 0;
452
+ if (isUserConversationStart && channelId === UNCATEGORIZED_CHANNEL_ID) {
453
+ await storageService.ensureChannel({
454
+ channelId: UNCATEGORIZED_CHANNEL_ID,
455
+ spec: DEFAULT_UNCATEGORIZED_SPEC,
456
+ initialState: { name: 'Uncategorized' },
457
+ });
458
+ }
437
459
  const bindIfUnbound = event.type === 'agent:invoke';
438
460
  const resolved = await resolveRespondingAgentId({
439
461
  channelId,
@@ -20,19 +20,11 @@ export const getContextBudgetForModel = (modelString) => {
20
20
  };
21
21
  /** Built-in orchestrator agent id. */
22
22
  export const ORCHESTRATOR_AGENT_ID = 'system';
23
- /**
24
- * Check if a channel is a solo DM (only the agent is present).
25
- */
26
- export function isDmSoloChannel(participants, agentId) {
27
- return participants.length === 0 || (participants.length === 1 && participants[0] === agentId);
28
- }
29
23
  /**
30
24
  * Simplified context builder for MVP.
31
25
  */
32
26
  export async function buildContext(state, storage) {
33
27
  const { channelId, threadId, channelDetails, agentId, threadDetails, agentDetails } = state;
34
- const participants = channelDetails?.participants || [];
35
- const isDm = isDmSoloChannel(participants, agentId);
36
28
  const sections = [];
37
29
  // Fetch agents once if storage is available
38
30
  const allAgents = storage?.getAgents ? await storage.getAgents().catch(() => []) : [];
@@ -42,24 +34,13 @@ export async function buildContext(state, storage) {
42
34
  }
43
35
  // 2. Environment
44
36
  let env = '## ENVIRONMENT\n';
45
- if (isDm) {
46
- env += '- Mode: Direct Message (Solo)\n';
37
+ const channelName = channelDetails?.name || channelId;
38
+ env += `- Mode: Channel (#${channelName})\n`;
39
+ if (channelDetails?.cwd) {
40
+ env += `- Workspace: ${channelDetails.cwd}\n`;
47
41
  }
48
- else {
49
- const channelName = channelDetails?.name || channelId;
50
- env += `- Mode: Channel (#${channelName})\n`;
51
- if (channelDetails?.cwd) {
52
- env += `- Workspace: ${channelDetails.cwd}\n`;
53
- }
54
- if (threadId) {
55
- env += `- Thread: ${threadDetails?.name || threadId}\n`;
56
- }
57
- const peerIds = participants.filter((id) => id !== agentId);
58
- const participantLabels = peerIds.map((id) => {
59
- const agent = allAgents.find((a) => a.id === id);
60
- return agent ? `${agent.name} (${id})` : id;
61
- });
62
- env += `- Participants: ${participantLabels.length > 0 ? participantLabels.join(', ') : 'None'}\n`;
42
+ if (threadId) {
43
+ env += `- Thread: ${threadDetails?.name || threadId}\n`;
63
44
  }
64
45
  sections.push(env);
65
46
  // 2.5 Installed Agents
@@ -1,10 +1,27 @@
1
1
  import { generateText } from 'ai';
2
2
  import { openai } from '@ai-sdk/openai';
3
3
  import { anthropic } from '@ai-sdk/anthropic';
4
+ import { google } from '@ai-sdk/google';
4
5
  import { eventsToModelMessages } from './history.js';
5
6
  import { buildContext, } from './context.js';
6
- import { saveConfig } from '../../app/config.js';
7
- import { API_KEY_SETUP_MESSAGE, OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
7
+ import { saveConfig, DEFAULT_MARKETPLACE_REGISTRY_URL } from '../../app/config.js';
8
+ import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
9
+ let cachedRegistry = null;
10
+ async function fetchRegistry() {
11
+ if (cachedRegistry)
12
+ return cachedRegistry;
13
+ try {
14
+ const response = await fetch(DEFAULT_MARKETPLACE_REGISTRY_URL);
15
+ if (!response.ok)
16
+ throw new Error(`Failed to fetch registry: ${response.statusText}`);
17
+ cachedRegistry = (await response.json());
18
+ return cachedRegistry;
19
+ }
20
+ catch (error) {
21
+ console.error('[openbot] Failed to fetch model registry:', error);
22
+ return null;
23
+ }
24
+ }
8
25
  function resolveModel(modelString) {
9
26
  const [provider, ...rest] = modelString.split('/');
10
27
  const modelId = rest.join('/');
@@ -16,6 +33,8 @@ function resolveModel(modelString) {
16
33
  return openai(modelId);
17
34
  case 'anthropic':
18
35
  return anthropic(modelId);
36
+ case 'google':
37
+ return google(modelId);
19
38
  default:
20
39
  throw new Error(`Unsupported AI provider: "${provider}"`);
21
40
  }
@@ -182,47 +201,20 @@ export const openbotRuntime = (options) => (builder) => {
182
201
  errorMessage.includes('Unauthorized') ||
183
202
  errorMessage.includes('authentication');
184
203
  if (isApiKeyError) {
185
- const [currentProvider, ...rest] = currentModelString.split('/');
186
- const currentModelId = rest.join('/');
187
204
  yield {
188
205
  type: 'client:ui:widget',
189
206
  data: {
190
- kind: 'form',
191
- widgetId: `api_key_request_${Date.now()}`,
192
- title: `AI Provider API Key Required`,
193
- description: API_KEY_SETUP_MESSAGE,
194
- fields: [
195
- {
196
- id: 'provider',
197
- label: 'Provider',
198
- type: 'select',
199
- required: true,
200
- options: [
201
- { label: 'OpenAI', value: 'openai' },
202
- { label: 'Anthropic', value: 'anthropic' },
203
- ],
204
- defaultValue: currentProvider === 'anthropic' ? 'anthropic' : 'openai',
205
- },
206
- {
207
- id: 'model',
208
- label: 'Model',
209
- type: 'text',
210
- description: 'Model name without the provider prefix (e.g. `gpt-4o-mini` or `claude-3-5-sonnet-20240620`).',
211
- placeholder: 'gpt-4o-mini',
212
- required: true,
213
- defaultValue: currentModelId,
214
- },
215
- {
216
- id: 'apiKey',
217
- label: 'API Key',
218
- type: 'text',
219
- placeholder: `sk-...`,
220
- required: true,
221
- },
207
+ kind: 'choice',
208
+ widgetId: `api_provider_selection_${Date.now()}`,
209
+ title: `Setup AI Provider`,
210
+ description: `Select a provider to continue.`,
211
+ actions: [
212
+ { id: 'openai', label: 'OpenAI', variant: 'primary' },
213
+ { id: 'anthropic', label: 'Anthropic', variant: 'primary' },
214
+ { id: 'google', label: 'Google', variant: 'primary' },
222
215
  ],
223
- submitLabel: 'Save & Continue',
224
216
  metadata: {
225
- type: 'api_key_request',
217
+ type: 'api_provider_selection',
226
218
  },
227
219
  },
228
220
  meta: { agentId: context.state.agentId, threadId },
@@ -244,43 +236,6 @@ export const openbotRuntime = (options) => (builder) => {
244
236
  };
245
237
  }
246
238
  const threadId = event.meta?.threadId || context.state.threadId;
247
- // Auto-add participants if tagged in the prompt
248
- const content = event.data?.content;
249
- if (content && storage) {
250
- try {
251
- const allAgents = await storage.getAgents();
252
- const tags = content.match(/@([\w-]+)/g);
253
- if (tags) {
254
- const taggedAgentIds = tags.map((t) => t.slice(1));
255
- const validAgentIds = taggedAgentIds.filter((id) => allAgents.some((a) => a.id === id));
256
- const currentParticipants = context.state.channelDetails?.participants || [];
257
- const newParticipants = [...new Set([...currentParticipants, ...validAgentIds])];
258
- if (newParticipants.length > currentParticipants.length) {
259
- // Update storage
260
- await storage.patchChannelState({
261
- channelId: context.state.channelId,
262
- state: { participants: newParticipants },
263
- });
264
- // Refresh local state
265
- context.state.channelDetails = await storage.getChannelDetails({
266
- channelId: context.state.channelId,
267
- });
268
- // Notify UI/others about the change
269
- yield {
270
- type: 'action:patch_channel_details:result',
271
- data: { success: true, updatedFields: ['participants'] },
272
- meta: {
273
- agentId: context.state.agentId,
274
- threadId,
275
- },
276
- };
277
- }
278
- }
279
- }
280
- catch (error) {
281
- console.warn('[openbot] Failed to auto-add participants from tags:', error);
282
- }
283
- }
284
239
  // clear the tool batch if the agent is invoked
285
240
  // this is to prevent the tool batch from being used for a new agent invocation
286
241
  await createToolBatchTracker(context.state, storage, context.state.channelId, threadId).clear();
@@ -302,15 +257,88 @@ export const openbotRuntime = (options) => (builder) => {
302
257
  yield* runLLM(context, threadId);
303
258
  });
304
259
  builder.on('client:ui:widget:response', async function* (event, context) {
305
- const { metadata, values } = event.data;
260
+ const { metadata, values, actionId } = event.data;
261
+ const threadId = event.meta?.threadId || context.state.threadId;
262
+ if (metadata?.type === 'api_provider_selection') {
263
+ const provider = actionId;
264
+ const [_, ...rest] = currentModelString.split('/');
265
+ const currentModelId = rest.join('/');
266
+ const registry = await fetchRegistry();
267
+ const providerData = registry?.providers[provider];
268
+ const providerLinks = {
269
+ openai: 'https://platform.openai.com/api-keys',
270
+ anthropic: 'https://console.anthropic.com/settings/keys',
271
+ google: 'https://aistudio.google.com/app/apikey',
272
+ };
273
+ const label = providerData?.label || provider;
274
+ const link = providerLinks[provider] || '';
275
+ const modelOptions = providerData?.models.map((m) => ({
276
+ label: m.label,
277
+ value: m.id,
278
+ }));
279
+ const defaultModel = modelOptions?.[0]?.value || 'gpt-4o-mini';
280
+ const defaultValue = modelOptions?.find((m) => m.value === currentModelId)?.value ||
281
+ currentModelId ||
282
+ defaultModel;
283
+ yield {
284
+ type: 'client:ui:widget',
285
+ data: {
286
+ widgetId: event.data.widgetId,
287
+ kind: 'message',
288
+ title: 'Provider Selected',
289
+ body: `${label} provider was selected.`,
290
+ state: 'submitted',
291
+ display: 'collapsed',
292
+ disabled: true,
293
+ actions: [],
294
+ },
295
+ meta: { agentId: context.state.agentId, threadId },
296
+ };
297
+ yield {
298
+ type: 'client:ui:widget',
299
+ data: {
300
+ kind: 'form',
301
+ widgetId: `api_key_request_${Date.now()}`,
302
+ title: `${label} Setup`,
303
+ description: `Enter your API key and select a model.`,
304
+ fields: [
305
+ {
306
+ id: 'model',
307
+ label: 'Model',
308
+ type: modelOptions ? 'select' : 'text',
309
+ description: modelOptions ? undefined : `Model name (e.g. \`${defaultModel}\`).`,
310
+ options: modelOptions,
311
+ placeholder: defaultModel,
312
+ required: true,
313
+ defaultValue,
314
+ },
315
+ {
316
+ id: 'apiKey',
317
+ label: 'API Key',
318
+ type: 'password',
319
+ description: `Get your key here: [${link}](${link})`,
320
+ placeholder: `sk-...`,
321
+ required: true,
322
+ },
323
+ ],
324
+ submitLabel: 'Save & Continue',
325
+ metadata: {
326
+ type: 'api_key_request',
327
+ provider,
328
+ },
329
+ },
330
+ meta: { agentId: context.state.agentId, threadId },
331
+ };
332
+ return;
333
+ }
306
334
  if (metadata?.type !== 'api_key_request')
307
335
  return;
308
- if (!values?.apiKey || !values?.provider || !values?.model)
336
+ if (!values?.apiKey || !values?.model)
309
337
  return;
310
- const provider = String(values.provider);
338
+ const provider = String(values.provider || metadata.provider);
311
339
  const modelId = String(values.model).trim();
312
340
  const apiKey = String(values.apiKey);
313
- if (provider !== 'openai' && provider !== 'anthropic') {
341
+ if (provider !== 'openai' && provider !== 'anthropic' && provider !== 'google') {
314
342
  yield {
315
343
  type: 'agent:output',
316
344
  data: { content: `Unsupported provider: ${provider}` },
@@ -318,7 +346,11 @@ export const openbotRuntime = (options) => (builder) => {
318
346
  };
319
347
  return;
320
348
  }
321
- const envVar = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
349
+ const envVar = provider === 'openai'
350
+ ? 'OPENAI_API_KEY'
351
+ : provider === 'anthropic'
352
+ ? 'ANTHROPIC_API_KEY'
353
+ : 'GOOGLE_GENERATIVE_AI_API_KEY';
322
354
  const newModelString = `${provider}/${modelId}`;
323
355
  if (!storage)
324
356
  return;
@@ -363,10 +395,13 @@ export const openbotRuntime = (options) => (builder) => {
363
395
  title: 'API Key Saved',
364
396
  body: `Successfully saved ${provider} API key and selected model \`${newModelString}\`. You can now continue your conversation.`,
365
397
  state: 'submitted',
366
- actions: [{ id: 'ok', label: 'Got it', variant: 'primary' }],
398
+ display: 'collapsed',
399
+ disabled: true,
400
+ actions: [],
367
401
  },
368
402
  meta: { agentId: context.state.agentId },
369
403
  };
404
+ yield* runLLM(context, threadId);
370
405
  }
371
406
  catch (error) {
372
407
  yield {
@@ -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,5 +20,3 @@ export const OPENBOT_SYSTEM_PROMPT = [
21
20
  '# COMMUNICATION STYLE',
22
21
  '- Be always concise, professional, and proactive.',
23
22
  ].join('\n');
24
- /** Shown in the API key setup form when no provider credentials are configured. */
25
- export const API_KEY_SETUP_MESSAGE = '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.';
@@ -54,64 +54,22 @@ export const pluginManagerPlugin = {
54
54
  });
55
55
  builder.on('action:channel:install', async function* (event) {
56
56
  try {
57
- const { channelId: instanceId, name: templateName, participants: customParticipants, initialState: customInitialState, } = event.data;
58
- const { agents: marketplaceAgents, channels } = await resolveMarketplaceRegistry();
57
+ const { channelId: instanceId, name: templateName, initialState: customInitialState, } = event.data;
58
+ const { channels } = await resolveMarketplaceRegistry();
59
59
  // Try to find the template by ID or Name
60
60
  const channelListing = channels.find((c) => c.id === instanceId) ||
61
61
  channels.find((c) => c.name === templateName);
62
62
  const channelId = instanceId;
63
- const participants = customParticipants || channelListing?.participants || [];
64
63
  const initialState = {
65
64
  ...(channelListing?.initialState || {}),
66
65
  ...(customInitialState || {}),
67
66
  };
68
67
  const spec = channelListing?.spec || '';
69
- // 1. Auto-install participant agents if missing
70
- for (const agentId of participants) {
71
- const existingAgents = await storage.getAgents();
72
- if (existingAgents.some((a) => a.id === agentId)) {
73
- continue;
74
- }
75
- // Not found locally, look in marketplace
76
- const agentListing = marketplaceAgents.find((a) => a.id === agentId);
77
- if (agentListing) {
78
- console.log(`[plugin-manager] Auto-installing agent ${agentId} for channel ${channelId}`);
79
- // Install plugins for this agent
80
- for (const ref of agentListing.plugins) {
81
- const installed = await pluginService.isInstalled(ref.id);
82
- if (!installed &&
83
- ref.id.includes('/') === false &&
84
- ref.id.includes('-plugin-') === false) {
85
- continue;
86
- }
87
- if (!installed) {
88
- try {
89
- await pluginService.install({ packageName: ref.id });
90
- }
91
- catch (err) {
92
- console.warn(`[plugins] Failed to pre-install plugin ${ref.id}`, err);
93
- }
94
- }
95
- }
96
- // Create the agent
97
- await storage.createAgent({
98
- agentId: agentListing.id,
99
- name: agentListing.name,
100
- description: agentListing.description,
101
- image: agentListing.image,
102
- instructions: agentListing.instructions,
103
- plugins: agentListing.plugins,
104
- });
105
- }
106
- }
107
68
  // 2. Create the channel
108
69
  await storage.createChannel({
109
70
  channelId,
110
71
  spec,
111
- initialState: {
112
- ...initialState,
113
- participants,
114
- },
72
+ initialState,
115
73
  });
116
74
  const channelUrl = `/channels/${channelId}`;
117
75
  yield {
@@ -38,15 +38,10 @@ const storageToolDefinitions = {
38
38
  .optional()
39
39
  .describe('Markdown content for the channel specification (SPEC.md). Use for goals and rules.'),
40
40
  cwd: z.string().optional().describe('Current working directory for the channel.'),
41
- participants: z
42
- .array(z.string())
43
- .optional()
44
- .describe('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.'),
45
41
  })
46
42
  .refine((value) => value.state !== undefined ||
47
43
  value.spec !== undefined ||
48
- value.cwd !== undefined ||
49
- value.participants !== undefined, { message: 'Provide at least one of state, spec, cwd, or participants.' }),
44
+ value.cwd !== undefined, { message: 'Provide at least one of state, spec, or cwd.' }),
50
45
  },
51
46
  patch_thread_details: {
52
47
  description: 'Patch current thread details (state).',
@@ -125,7 +120,7 @@ export const storagePlugin = {
125
120
  };
126
121
  });
127
122
  builder.on('action:create_channel', async function* (event, context) {
128
- const { channelId, spec, initialState, cwd, participants } = event.data;
123
+ const { channelId, spec, initialState, cwd } = event.data;
129
124
  const rawChannelId = (channelId || '').trim();
130
125
  const channelSpec = typeof spec === 'string' ? spec : '';
131
126
  const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
@@ -139,15 +134,6 @@ export const storagePlugin = {
139
134
  }
140
135
  const channelUrl = `/channels/${rawChannelId}`;
141
136
  const mergedInitial = { ...(initialState || {}) };
142
- if (participants !== undefined) {
143
- const normalized = Array.isArray(participants)
144
- ? participants
145
- .filter((x) => typeof x === 'string')
146
- .map((s) => s.trim())
147
- .filter(Boolean)
148
- : [];
149
- mergedInitial.participants = normalized;
150
- }
151
137
  try {
152
138
  await storage.createChannel({
153
139
  channelId: rawChannelId,
@@ -232,18 +218,6 @@ export const storagePlugin = {
232
218
  patch.cwd = data.cwd.trim();
233
219
  updatedFields.push('cwd');
234
220
  }
235
- if (data.participants !== undefined) {
236
- if (Array.isArray(data.participants)) {
237
- patch.participants = data.participants
238
- .filter((x) => typeof x === 'string')
239
- .map((s) => s.trim())
240
- .filter(Boolean);
241
- }
242
- else {
243
- patch.participants = [];
244
- }
245
- updatedFields.push('participants');
246
- }
247
221
  try {
248
222
  if (updatedFields.length > 0) {
249
223
  await storage.patchChannelState({ channelId: targetChannelId, state: patch });
@@ -293,19 +267,6 @@ export const storagePlugin = {
293
267
  });
294
268
  updatedFields.push('cwd');
295
269
  }
296
- if (data.participants !== undefined) {
297
- const normalized = Array.isArray(data.participants)
298
- ? data.participants
299
- .filter((x) => typeof x === 'string')
300
- .map((s) => s.trim())
301
- .filter(Boolean)
302
- : [];
303
- await storage.patchChannelState({
304
- channelId: context.state.channelId,
305
- state: { participants: normalized },
306
- });
307
- updatedFields.push('participants');
308
- }
309
270
  context.state.channelDetails = await storage.getChannelDetails({
310
271
  channelId: context.state.channelId,
311
272
  });