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.
- package/dist/app/channel-ids.js +3 -0
- package/dist/app/cli.js +1 -1
- package/dist/app/responding-agent.js +32 -0
- package/dist/app/server.js +33 -5
- package/dist/plugins/openbot/context.js +6 -25
- package/dist/plugins/openbot/index.js +2 -1
- package/dist/plugins/openbot/runtime.js +116 -81
- package/dist/plugins/openbot/system-prompt.js +4 -7
- package/dist/plugins/plugin-manager/index.js +3 -45
- package/dist/plugins/storage/index.js +2 -41
- package/dist/plugins/storage/service.js +54 -14
- package/dist/services/plugins/service.js +0 -4
- package/package.json +2 -1
- package/src/app/channel-ids.ts +5 -0
- package/src/app/cli.ts +1 -1
- package/src/app/responding-agent.ts +46 -0
- package/src/app/server.ts +41 -6
- package/src/app/types.ts +7 -10
- package/src/plugins/openbot/context.ts +7 -26
- package/src/plugins/openbot/index.ts +2 -1
- package/src/plugins/openbot/runtime.ts +137 -89
- package/src/plugins/openbot/system-prompt.ts +4 -9
- package/src/plugins/plugin-manager/index.ts +2 -50
- package/src/plugins/storage/index.ts +4 -46
- package/src/plugins/storage/service.ts +78 -14
- package/src/services/plugins/domain.ts +7 -4
- package/src/services/plugins/service.ts +0 -6
- package/dist/plugins/thread-naming/generate-title.js +0 -44
- package/dist/plugins/thread-naming/index.js +0 -103
- package/dist/services/thread-naming.js +0 -81
|
@@ -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
|
-
}
|