openbot 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/app/cli.js +1 -1
  2. package/dist/app/config.js +10 -0
  3. package/dist/app/server.js +200 -3
  4. package/dist/harness/index.js +18 -0
  5. package/dist/plugins/approval/index.js +35 -20
  6. package/dist/plugins/bash/index.js +195 -0
  7. package/dist/plugins/delegation/index.js +4 -2
  8. package/dist/plugins/openbot/context.js +54 -9
  9. package/dist/plugins/openbot/history.js +47 -1
  10. package/dist/plugins/openbot/index.js +43 -3
  11. package/dist/plugins/openbot/runtime.js +91 -27
  12. package/dist/plugins/openbot/system-prompt.js +21 -1
  13. package/dist/plugins/plugin-manager/index.js +87 -3
  14. package/dist/plugins/shell/index.js +2 -1
  15. package/dist/plugins/storage/files.js +67 -0
  16. package/dist/plugins/storage/index.js +184 -7
  17. package/dist/plugins/storage/service.js +201 -44
  18. package/dist/plugins/ui/index.js +109 -150
  19. package/dist/services/abort.js +43 -0
  20. package/dist/services/plugins/registry.js +5 -3
  21. package/dist/services/plugins/service.js +66 -11
  22. package/docs/agents.md +5 -8
  23. package/docs/architecture.md +1 -1
  24. package/docs/plugins.md +28 -7
  25. package/docs/templates/AGENT.example.md +4 -4
  26. package/package.json +1 -1
  27. package/src/app/cli.ts +1 -1
  28. package/src/app/config.ts +13 -0
  29. package/src/app/server.ts +235 -3
  30. package/src/app/types.ts +284 -14
  31. package/src/harness/index.ts +21 -0
  32. package/src/plugins/approval/index.ts +37 -20
  33. package/src/plugins/bash/index.ts +232 -0
  34. package/src/plugins/delegation/index.ts +5 -2
  35. package/src/plugins/openbot/context.ts +58 -9
  36. package/src/plugins/openbot/history.ts +52 -1
  37. package/src/plugins/openbot/index.ts +45 -3
  38. package/src/plugins/openbot/runtime.ts +121 -27
  39. package/src/plugins/openbot/system-prompt.ts +21 -1
  40. package/src/plugins/plugin-manager/index.ts +105 -3
  41. package/src/plugins/storage/files.ts +81 -0
  42. package/src/plugins/storage/index.ts +198 -8
  43. package/src/plugins/storage/service.ts +267 -44
  44. package/src/plugins/ui/index.ts +123 -0
  45. package/src/services/abort.ts +46 -0
  46. package/src/services/plugins/domain.ts +34 -1
  47. package/src/services/plugins/registry.ts +5 -3
  48. package/src/services/plugins/service.ts +136 -45
  49. package/src/services/plugins/types.ts +5 -1
  50. package/src/plugins/shell/index.ts +0 -123
@@ -1,6 +1,7 @@
1
1
  import z from 'zod';
2
2
  import type { Plugin } from '../../services/plugins/types.js';
3
3
  import { OpenBotEvent } from '../../app/types.js';
4
+ import { buildWorkspaceFileUrl } from './files.js';
4
5
 
5
6
  /**
6
7
  * `storage` — exposes channel/thread/variable mutation tools and provides
@@ -25,7 +26,9 @@ const storageToolDefinitions = {
25
26
  cwd: z
26
27
  .string()
27
28
  .optional()
28
- .describe('Optional initial current working directory for the channel.'),
29
+ .describe(
30
+ 'Optional initial current working directory for the channel. Defaults to an absolute path under ~/openbot/{channelId}.',
31
+ ),
29
32
  }),
30
33
  },
31
34
  patch_channel_details: {
@@ -45,10 +48,20 @@ const storageToolDefinitions = {
45
48
  'Markdown content for the channel specification (SPEC.md). Use for goals and rules.',
46
49
  ),
47
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
+ ),
48
57
  })
49
58
  .refine(
50
- (value) => value.state !== undefined || value.spec !== undefined || value.cwd !== undefined,
51
- { message: 'Provide at least one of state, spec, or cwd.' },
59
+ (value) =>
60
+ value.state !== undefined ||
61
+ value.spec !== undefined ||
62
+ value.cwd !== undefined ||
63
+ value.participants !== undefined,
64
+ { message: 'Provide at least one of state, spec, cwd, or participants.' },
52
65
  ),
53
66
  },
54
67
  patch_thread_details: {
@@ -75,6 +88,22 @@ const storageToolDefinitions = {
75
88
  key: z.string().describe('The key of the variable to delete.'),
76
89
  }),
77
90
  },
91
+ delete_channel: {
92
+ description:
93
+ 'Permanently delete a channel and all its threads and events. Always confirm with the user before deleting.',
94
+ inputSchema: z.object({
95
+ channelId: z.string().describe('The channel ID to delete.'),
96
+ }),
97
+ },
98
+ get_workspace_file_url: {
99
+ description:
100
+ 'Get a fetchable HTTP URL for a file in the current channel workspace (images, video, audio, documents).',
101
+ inputSchema: z.object({
102
+ path: z
103
+ .string()
104
+ .describe('Path relative to the channel working directory, e.g. "uploads/clip.mp4".'),
105
+ }),
106
+ },
78
107
  };
79
108
 
80
109
  export const storagePlugin: Plugin = {
@@ -82,7 +111,9 @@ export const storagePlugin: Plugin = {
82
111
  name: 'Storage',
83
112
  description: 'Tools for creating channels, patching state, and managing workspace variables.',
84
113
  toolDefinitions: storageToolDefinitions,
85
- factory: ({ storage }) => (builder) => {
114
+ factory: ({ storage, publicBaseUrl }) => (builder) => {
115
+ const resolvePublicBaseUrl = () => publicBaseUrl;
116
+
86
117
  builder.on('action:create_thread', async function* (event, context) {
87
118
  const threadId = event.meta?.threadId;
88
119
  const channelId = context.state.channelId;
@@ -180,6 +211,44 @@ export const storagePlugin: Plugin = {
180
211
  }
181
212
  });
182
213
 
214
+ builder.on('action:delete_channel', async function* (event, context) {
215
+ const rawChannelId = ((event.data as { channelId?: string })?.channelId || '').trim();
216
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
217
+
218
+ if (!rawChannelId) {
219
+ yield {
220
+ type: 'action:delete_channel:result',
221
+ data: { success: false, channelId: '', error: 'channelId is required' },
222
+ meta: resultMeta,
223
+ } as OpenBotEvent;
224
+ return;
225
+ }
226
+
227
+ try {
228
+ await storage.deleteChannel({ channelId: rawChannelId });
229
+ yield {
230
+ type: 'action:delete_channel:result',
231
+ data: { success: true, channelId: rawChannelId },
232
+ meta: resultMeta,
233
+ } as OpenBotEvent;
234
+ yield {
235
+ type: 'agent:output',
236
+ data: { content: `Deleted channel \`${rawChannelId}\`.` },
237
+ meta: resultMeta,
238
+ } as OpenBotEvent;
239
+ } catch (error) {
240
+ yield {
241
+ type: 'action:delete_channel:result',
242
+ data: {
243
+ success: false,
244
+ channelId: rawChannelId,
245
+ error: error instanceof Error ? error.message : 'Unknown error',
246
+ },
247
+ meta: resultMeta,
248
+ } as OpenBotEvent;
249
+ }
250
+ });
251
+
183
252
  builder.on('action:update_channel', async function* (event, context) {
184
253
  const data = (event.data || {}) as {
185
254
  channelId?: string;
@@ -302,7 +371,8 @@ export const storagePlugin: Plugin = {
302
371
  widgetId: "patch-channel-details-result" + Date.now(),
303
372
  kind: "message",
304
373
  title: "Channel details updated.",
305
- body: "The channel details have been updated.",
374
+ body: `The channel details have been updated. ${updatedFields.join(', ')}`,
375
+ display: "collapsed",
306
376
  },
307
377
  meta: resultMeta,
308
378
  }
@@ -395,6 +465,26 @@ export const storagePlugin: Plugin = {
395
465
  yield { type: 'action:storage:get-threads-result', data: { threads } };
396
466
  });
397
467
 
468
+ builder.on('action:storage:set-last-read', async function* (event, context) {
469
+ const { channelId, threadId, lastReadEventId } = event.data;
470
+ try {
471
+ await storage.setLastRead({
472
+ channelId: channelId || context.state.channelId,
473
+ threadId: threadId || context.state.threadId,
474
+ lastReadEventId,
475
+ });
476
+ yield { type: 'action:storage:set-last-read-result', data: { success: true } };
477
+ } catch (error) {
478
+ yield {
479
+ type: 'action:storage:set-last-read-result',
480
+ data: {
481
+ success: false,
482
+ error: error instanceof Error ? error.message : 'Unknown error',
483
+ },
484
+ };
485
+ }
486
+ });
487
+
398
488
  builder.on('action:storage:get-channel-details', async function* (_, state) {
399
489
  const channelDetails = await storage.getChannelDetails({
400
490
  channelId: state.state.channelId,
@@ -484,6 +574,21 @@ export const storagePlugin: Plugin = {
484
574
  }
485
575
  });
486
576
 
577
+ builder.on('action:storage:delete-channel', async function* (event) {
578
+ try {
579
+ await storage.deleteChannel({ channelId: event.data.channelId });
580
+ yield { type: 'action:storage:delete-channel-result', data: { success: true } };
581
+ } catch (error) {
582
+ yield {
583
+ type: 'action:storage:delete-channel-result',
584
+ data: {
585
+ success: false,
586
+ error: error instanceof Error ? error.message : 'Unknown error',
587
+ },
588
+ };
589
+ }
590
+ });
591
+
487
592
  builder.on('action:storage:delete-agent', async function* (event) {
488
593
  try {
489
594
  await storage.deleteAgent({ agentId: event.data.agentId });
@@ -602,7 +707,9 @@ export const storagePlugin: Plugin = {
602
707
 
603
708
  builder.on('action:storage:read-file', async function* (event, context) {
604
709
  const channelId = context.state.channelId;
605
- const filePath = (event.data as any)?.path;
710
+ const data = event.data as { path?: string; encoding?: 'utf8' | 'base64' };
711
+ const filePath = data?.path;
712
+ const encoding = data?.encoding ?? 'utf8';
606
713
  if (!filePath) {
607
714
  yield {
608
715
  type: 'action:storage:read-file:result',
@@ -611,10 +718,23 @@ export const storagePlugin: Plugin = {
611
718
  return;
612
719
  }
613
720
  try {
614
- const content = await storage.readFile({ channelId, path: filePath });
721
+ if (encoding === 'utf8') {
722
+ const content = await storage.readFile({ channelId, path: filePath });
723
+ yield {
724
+ type: 'action:storage:read-file:result',
725
+ data: { success: true, content, path: filePath, encoding },
726
+ };
727
+ return;
728
+ }
729
+
730
+ const { content, mimeType, size } = await storage.readChannelFile({
731
+ channelId,
732
+ path: filePath,
733
+ encoding,
734
+ });
615
735
  yield {
616
736
  type: 'action:storage:read-file:result',
617
- data: { success: true, content, path: filePath },
737
+ data: { success: true, content, path: filePath, encoding, mimeType, size },
618
738
  };
619
739
  } catch (error) {
620
740
  yield {
@@ -627,6 +747,76 @@ export const storagePlugin: Plugin = {
627
747
  };
628
748
  }
629
749
  });
750
+
751
+ builder.on('action:storage:get-file-url', async function* (event, context) {
752
+ const channelId = context.state.channelId;
753
+ const filePath = (event.data as { path?: string })?.path;
754
+ if (!filePath) {
755
+ yield {
756
+ type: 'action:storage:get-file-url:result',
757
+ data: { success: false, path: '', error: 'Path is required' },
758
+ };
759
+ return;
760
+ }
761
+ try {
762
+ const { size, mimeType } = await storage.getChannelFileStat({ channelId, path: filePath });
763
+ const url = buildWorkspaceFileUrl({
764
+ baseUrl: resolvePublicBaseUrl(),
765
+ channelId,
766
+ filePath,
767
+ });
768
+ yield {
769
+ type: 'action:storage:get-file-url:result',
770
+ data: { success: true, path: filePath, url, mimeType, size },
771
+ };
772
+ } catch (error) {
773
+ yield {
774
+ type: 'action:storage:get-file-url:result',
775
+ data: {
776
+ success: false,
777
+ path: filePath,
778
+ error: error instanceof Error ? error.message : 'Unknown error',
779
+ },
780
+ };
781
+ }
782
+ });
783
+
784
+ builder.on('action:get_workspace_file_url', async function* (event, context) {
785
+ const channelId = context.state.channelId;
786
+ const filePath = (event.data as { path?: string })?.path;
787
+ const toolCallId = event.meta?.toolCallId;
788
+
789
+ if (!filePath) {
790
+ yield {
791
+ type: 'action:get_workspace_file_url:result',
792
+ data: { success: false, path: '', error: 'Path is required', output: 'Path is required' },
793
+ meta: { ...(event.meta || {}), toolCallId },
794
+ };
795
+ return;
796
+ }
797
+
798
+ try {
799
+ const { size, mimeType } = await storage.getChannelFileStat({ channelId, path: filePath });
800
+ const url = buildWorkspaceFileUrl({
801
+ baseUrl: resolvePublicBaseUrl(),
802
+ channelId,
803
+ filePath,
804
+ });
805
+ const output = JSON.stringify({ path: filePath, url, mimeType, size });
806
+ yield {
807
+ type: 'action:get_workspace_file_url:result',
808
+ data: { success: true, path: filePath, url, mimeType, size, output },
809
+ meta: { ...(event.meta || {}), toolCallId },
810
+ };
811
+ } catch (error) {
812
+ const message = error instanceof Error ? error.message : 'Unknown error';
813
+ yield {
814
+ type: 'action:get_workspace_file_url:result',
815
+ data: { success: false, path: filePath, error: message, output: message },
816
+ meta: { ...(event.meta || {}), toolCallId },
817
+ };
818
+ }
819
+ });
630
820
  },
631
821
  };
632
822