openbot 0.4.5 → 0.4.7

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.
@@ -64,8 +64,6 @@ export type Channel = {
64
64
  name: string;
65
65
  description: string;
66
66
  cwd?: string;
67
- /** Agent ids associated with this channel (from `state.json`). */
68
- participants: string[];
69
67
  createdAt: Date;
70
68
  updatedAt: Date;
71
69
  hasUnseenMessages?: boolean;
@@ -108,8 +106,6 @@ export type ChannelDetails = {
108
106
  spec: string;
109
107
  state: unknown;
110
108
  cwd?: string;
111
- /** Agent ids for this channel (from `state.json`). */
112
- participants: string[];
113
109
  threads?: Thread[];
114
110
  };
115
111
 
@@ -121,6 +117,13 @@ export interface Storage {
121
117
  initialState?: Record<string, unknown>;
122
118
  cwd?: string;
123
119
  }) => Promise<void>;
120
+ /** Idempotent channel setup; repairs partial dirs missing cwd/state. */
121
+ ensureChannel: (args: {
122
+ channelId: string;
123
+ spec?: string;
124
+ initialState?: Record<string, unknown>;
125
+ cwd?: string;
126
+ }) => Promise<void>;
124
127
  /** Removes the channel directory and cleans up `_meta/last-read.json`. */
125
128
  deleteChannel: (args: { channelId: string }) => Promise<void>;
126
129
  createThread: (args: {
@@ -49,8 +49,6 @@ export type MarketplaceChannelListing = {
49
49
  image?: string;
50
50
  spec?: string;
51
51
  initialState?: Record<string, unknown>;
52
- /** List of agent IDs that should be participants in the channel. */
53
- participants: string[];
54
52
  /** Starter prompts for the channel. */
55
53
  starterPrompts?: StarterPrompt[];
56
54
  };
@@ -133,21 +131,17 @@ export function parseMarketplaceRegistryJson(data: unknown): MarketplaceRegistry
133
131
  const id = item.id;
134
132
  const name = item.name;
135
133
  const description = item.description;
136
- const participants = item.participants;
137
134
 
138
135
  if (typeof id !== 'string' || !id)
139
136
  throw new Error(`channels[${i}].id must be a non-empty string`);
140
137
  if (typeof name !== 'string') throw new Error(`channels[${i}].name must be a string`);
141
138
  if (typeof description !== 'string')
142
139
  throw new Error(`channels[${i}].description must be a string`);
143
- if (!Array.isArray(participants))
144
- throw new Error(`channels[${i}].participants must be an array`);
145
140
 
146
141
  const listing: MarketplaceChannelListing = {
147
142
  id,
148
143
  name,
149
144
  description,
150
- participants: participants.filter((p): p is string => typeof p === 'string'),
151
145
  };
152
146
 
153
147
  if (typeof item.image === 'string') listing.image = item.image;
@@ -1,44 +0,0 @@
1
- import { generateText } from 'ai';
2
- import { openai } from '@ai-sdk/openai';
3
- import { anthropic } from '@ai-sdk/anthropic';
4
- const THREAD_TITLE_MAX_LENGTH = 80;
5
- function resolveModel(modelString) {
6
- const [provider, ...rest] = modelString.split('/');
7
- const modelId = rest.join('/');
8
- if (!modelId) {
9
- throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
10
- }
11
- switch (provider) {
12
- case 'openai':
13
- return openai(modelId);
14
- case 'anthropic':
15
- return anthropic(modelId);
16
- default:
17
- throw new Error(`Unsupported AI provider: "${provider}"`);
18
- }
19
- }
20
- function normalizeTitle(raw) {
21
- let title = raw
22
- .replace(/^["'`]+|["'`]+$/g, '')
23
- .replace(/[.!?]+$/g, '')
24
- .replace(/\s+/g, ' ')
25
- .trim();
26
- if (!title)
27
- return '';
28
- if (title.length > THREAD_TITLE_MAX_LENGTH) {
29
- title = `${title.slice(0, THREAD_TITLE_MAX_LENGTH).trimEnd()}...`;
30
- }
31
- return title;
32
- }
33
- export async function generateThreadTitle(content, modelString) {
34
- const normalized = content.replace(/\s+/g, ' ').trim();
35
- if (!normalized)
36
- return undefined;
37
- const result = await generateText({
38
- model: resolveModel(modelString),
39
- system: 'You name chat threads. Reply with ONLY a short title (3-6 words). No quotes, no trailing punctuation.',
40
- prompt: normalized.slice(0, 500),
41
- maxOutputTokens: 20,
42
- });
43
- return normalizeTitle(result.text) || undefined;
44
- }
@@ -1,103 +0,0 @@
1
- import { ORCHESTRATOR_AGENT_ID } from '../../app/agent-ids.js';
2
- import { loadConfig } from '../../app/config.js';
3
- import { generateThreadTitle } from './generate-title.js';
4
- const namingInFlight = new Set();
5
- function resolveNamingModel(pluginConfig, agentPluginRefs) {
6
- const fromPlugin = typeof pluginConfig.model === 'string' ? pluginConfig.model.trim() : '';
7
- if (fromPlugin)
8
- return fromPlugin;
9
- const openbotRef = agentPluginRefs?.find((ref) => ref.id === 'openbot');
10
- const fromOpenbot = typeof openbotRef?.config?.model === 'string' ? openbotRef.config.model.trim() : '';
11
- if (fromOpenbot)
12
- return fromOpenbot;
13
- return loadConfig().model || 'openai/gpt-4o-mini';
14
- }
15
- async function maybeGenerateThreadName(args) {
16
- const details = await args.storage.getThreadDetails({
17
- channelId: args.channelId,
18
- threadId: args.threadId,
19
- });
20
- const state = details.state || {};
21
- if (state.nameStatus === 'llm' || state.nameStatus === 'manual')
22
- return;
23
- const title = await generateThreadTitle(args.content, args.model);
24
- if (!title)
25
- return;
26
- await args.storage.patchThreadState({
27
- channelId: args.channelId,
28
- threadId: args.threadId,
29
- state: { generatedName: title, nameStatus: 'llm' },
30
- });
31
- if (!args.emitEvent)
32
- return;
33
- await args.emitEvent({
34
- type: 'client:ui:thread:updated',
35
- data: {
36
- channelId: args.channelId,
37
- threadId: args.threadId,
38
- name: title,
39
- },
40
- meta: {
41
- agentId: ORCHESTRATOR_AGENT_ID,
42
- channelId: args.channelId,
43
- threadId: args.threadId,
44
- },
45
- });
46
- }
47
- /**
48
- * `thread-naming` — generates short LLM titles for new threads on the system agent.
49
- * Runs in the background on the first user message so the main turn is not blocked.
50
- */
51
- export const threadNamingPlugin = {
52
- id: 'thread-naming',
53
- name: 'Thread naming',
54
- description: 'Automatically generates short LLM titles for new conversation threads.',
55
- configSchema: {
56
- type: 'object',
57
- properties: {
58
- model: {
59
- type: 'string',
60
- description: 'Provider model string for title generation. Defaults to the openbot plugin model, then workspace config.',
61
- },
62
- },
63
- },
64
- factory: ({ agentId, agentDetails, config, storage, emitEvent }) => {
65
- if (agentId !== ORCHESTRATOR_AGENT_ID) {
66
- return () => { };
67
- }
68
- const model = resolveNamingModel(config, agentDetails.pluginRefs);
69
- return (builder) => {
70
- builder.on('agent:invoke', async function* (event, context) {
71
- const invoke = event;
72
- if (invoke.data?.role && invoke.data.role !== 'user')
73
- return;
74
- const threadId = context.state.threadId;
75
- const channelId = context.state.channelId;
76
- if (!threadId || !channelId)
77
- return;
78
- const content = typeof invoke.data?.content === 'string' ? invoke.data.content : '';
79
- if (!content.trim())
80
- return;
81
- const key = `${channelId}:${threadId}`;
82
- if (namingInFlight.has(key))
83
- return;
84
- namingInFlight.add(key);
85
- void maybeGenerateThreadName({
86
- storage,
87
- channelId,
88
- threadId,
89
- content,
90
- model,
91
- emitEvent,
92
- })
93
- .catch((error) => {
94
- console.warn('[thread-naming] Failed to generate thread name:', error);
95
- })
96
- .finally(() => {
97
- namingInFlight.delete(key);
98
- });
99
- });
100
- };
101
- },
102
- };
103
- export default threadNamingPlugin;
@@ -1,81 +0,0 @@
1
- import { generateText } from 'ai';
2
- import { openai } from '@ai-sdk/openai';
3
- import { anthropic } from '@ai-sdk/anthropic';
4
- import { loadConfig } from '../app/config.js';
5
- import { storageService } from '../plugins/storage/service.js';
6
- const THREAD_TITLE_MAX_LENGTH = 80;
7
- const namingInFlight = new Set();
8
- function resolveModel(modelString) {
9
- const [provider, ...rest] = modelString.split('/');
10
- const modelId = rest.join('/');
11
- if (!modelId) {
12
- throw new Error(`Invalid model string: "${modelString}". Expected "provider/model-id".`);
13
- }
14
- switch (provider) {
15
- case 'openai':
16
- return openai(modelId);
17
- case 'anthropic':
18
- return anthropic(modelId);
19
- default:
20
- throw new Error(`Unsupported AI provider: "${provider}"`);
21
- }
22
- }
23
- function normalizeTitle(raw) {
24
- let title = raw
25
- .replace(/^["'`]+|["'`]+$/g, '')
26
- .replace(/[.!?]+$/g, '')
27
- .replace(/\s+/g, ' ')
28
- .trim();
29
- if (!title)
30
- return '';
31
- if (title.length > THREAD_TITLE_MAX_LENGTH) {
32
- title = `${title.slice(0, THREAD_TITLE_MAX_LENGTH).trimEnd()}...`;
33
- }
34
- return title;
35
- }
36
- export async function generateThreadTitle(content, modelString) {
37
- const normalized = content.replace(/\s+/g, ' ').trim();
38
- if (!normalized)
39
- return undefined;
40
- const config = loadConfig();
41
- const model = resolveModel(modelString || config.model || 'openai/gpt-4o-mini');
42
- const result = await generateText({
43
- model,
44
- system: 'You name chat threads. Reply with ONLY a short title (3-6 words). No quotes, no trailing punctuation.',
45
- prompt: normalized.slice(0, 500),
46
- maxOutputTokens: 20,
47
- });
48
- return normalizeTitle(result.text) || undefined;
49
- }
50
- export async function maybeGenerateThreadName(args) {
51
- const key = `${args.channelId}:${args.threadId}`;
52
- if (namingInFlight.has(key))
53
- return;
54
- namingInFlight.add(key);
55
- try {
56
- const details = await storageService.getThreadDetails({
57
- channelId: args.channelId,
58
- threadId: args.threadId,
59
- });
60
- const state = details.state || {};
61
- if (state.nameStatus === 'llm' || state.nameStatus === 'manual')
62
- return;
63
- if (state.nameStatus !== 'provisional')
64
- return;
65
- const title = await generateThreadTitle(args.content);
66
- if (!title)
67
- return;
68
- await storageService.patchThreadState({
69
- channelId: args.channelId,
70
- threadId: args.threadId,
71
- state: { generatedName: title, nameStatus: 'llm' },
72
- });
73
- await args.onUpdated?.(title);
74
- }
75
- catch (error) {
76
- console.warn('[thread-naming] Failed to generate thread name:', error);
77
- }
78
- finally {
79
- namingInFlight.delete(key);
80
- }
81
- }