@meetopenbot/openbot 1.0.4 → 1.1.1
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/agent-models.js +125 -0
- package/dist/build-tools.js +5 -1
- package/dist/context.js +19 -13
- package/dist/index.js +12 -2
- package/dist/runtime.js +6 -6
- package/dist/system-prompt.js +31 -3
- package/dist/tools/ask-agent.js +16 -1
- package/dist/tools/install-agent.js +50 -0
- package/package.json +2 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const MAX_LISTED_MODELS = 8;
|
|
2
|
+
function trimmedString(value) {
|
|
3
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
4
|
+
}
|
|
5
|
+
function unique(values) {
|
|
6
|
+
return [...new Set(values)];
|
|
7
|
+
}
|
|
8
|
+
function optionFromSchema(value, schema) {
|
|
9
|
+
const match = schema?.options?.find((option) => option.value === value);
|
|
10
|
+
return {
|
|
11
|
+
id: value,
|
|
12
|
+
label: trimmedString(match?.label),
|
|
13
|
+
description: trimmedString(match?.description),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function schemaModelIds(schema) {
|
|
17
|
+
const fromOptions = schema?.options
|
|
18
|
+
?.map((option) => option.value)
|
|
19
|
+
.filter((value) => typeof value === "string" && value.length > 0) ?? [];
|
|
20
|
+
if (fromOptions.length > 0)
|
|
21
|
+
return unique(fromOptions);
|
|
22
|
+
const fromEnum = Array.isArray(schema?.enum)
|
|
23
|
+
? schema.enum.filter((value) => typeof value === "string" && value.length > 0)
|
|
24
|
+
: [];
|
|
25
|
+
return unique(fromEnum);
|
|
26
|
+
}
|
|
27
|
+
export function catalogForAgent(pluginConfig, schema) {
|
|
28
|
+
const allowedModels = schemaModelIds(schema);
|
|
29
|
+
const defaultModel = trimmedString(pluginConfig?.model) ?? trimmedString(schema?.default) ?? allowedModels[0];
|
|
30
|
+
const models = allowedModels.map((id) => optionFromSchema(id, schema));
|
|
31
|
+
return {
|
|
32
|
+
defaultModel,
|
|
33
|
+
allowedModels,
|
|
34
|
+
models,
|
|
35
|
+
constrained: allowedModels.length > 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function listModelsForPrompt(catalog, limit = MAX_LISTED_MODELS) {
|
|
39
|
+
const byId = new Map(catalog.models.map((model) => [model.id, model]));
|
|
40
|
+
const ordered = unique([catalog.defaultModel, ...catalog.allowedModels].filter((value) => Boolean(value)));
|
|
41
|
+
return ordered.slice(0, limit).map((id) => byId.get(id) ?? { id });
|
|
42
|
+
}
|
|
43
|
+
function formatModelOption(model) {
|
|
44
|
+
const hint = model.description ?? (model.label && model.label !== model.id ? model.label : undefined);
|
|
45
|
+
return hint ? `${model.id} — ${hint}` : model.id;
|
|
46
|
+
}
|
|
47
|
+
export function formatInstalledAgentLine(agent, catalog) {
|
|
48
|
+
let line = `- ${agent.id}: ${agent.name}${agent.description ? ` - ${agent.description}` : ""}`;
|
|
49
|
+
if (!catalog?.defaultModel && !catalog?.constrained)
|
|
50
|
+
return line;
|
|
51
|
+
if (catalog.defaultModel) {
|
|
52
|
+
line += `\n default: ${catalog.defaultModel}`;
|
|
53
|
+
}
|
|
54
|
+
if (catalog.constrained && catalog.allowedModels.length > 0) {
|
|
55
|
+
const listed = listModelsForPrompt(catalog);
|
|
56
|
+
const truncated = catalog.allowedModels.length > listed.length;
|
|
57
|
+
const rows = listed.map((model) => ` - ${formatModelOption(model)}`);
|
|
58
|
+
if (truncated)
|
|
59
|
+
rows.push(" - …");
|
|
60
|
+
line += `\n models:\n${rows.join("\n")}`;
|
|
61
|
+
}
|
|
62
|
+
return line;
|
|
63
|
+
}
|
|
64
|
+
export function formatInstalledAgentsSection(agents, catalogs) {
|
|
65
|
+
const formatted = agents
|
|
66
|
+
.map((agent) => formatInstalledAgentLine(agent, catalogs.get(agent.id)))
|
|
67
|
+
.join("\n");
|
|
68
|
+
return `## INSTALLED AGENTS\n${formatted}\n\nWhen you ask_agent, pick \`model\` from that agent's list using the descriptions (cheaper/faster for simple work, stronger for complex coding or reasoning). Omit \`model\` to use their default. Do not reuse another agent's ids.\nAnswer questions about people from this list. Do not create a Space to inspect agents.`;
|
|
69
|
+
}
|
|
70
|
+
export function formatAvailableAgentsSection(agents) {
|
|
71
|
+
const formatted = agents
|
|
72
|
+
.map((agent) => `- ${agent.id}: ${agent.name}${agent.description ? ` - ${agent.description}` : ""}`)
|
|
73
|
+
.join("\n");
|
|
74
|
+
return `## AVAILABLE AGENTS\n${formatted}\n\nNot installed yet. Use \`install_agent\` with an id from this list when the human needs that person (they must approve). After install, ask them with \`ask_agent\`.`;
|
|
75
|
+
}
|
|
76
|
+
export function availableMarketplaceAgents(installed, marketplace) {
|
|
77
|
+
const installedIds = new Set(installed.map((agent) => agent.id));
|
|
78
|
+
return marketplace.filter((agent) => !installedIds.has(agent.id));
|
|
79
|
+
}
|
|
80
|
+
export function validateAskModel(model, catalog) {
|
|
81
|
+
const requested = trimmedString(model);
|
|
82
|
+
if (!requested)
|
|
83
|
+
return { ok: true };
|
|
84
|
+
if (!catalog?.constrained)
|
|
85
|
+
return { ok: true, model: requested };
|
|
86
|
+
if (catalog.allowedModels.includes(requested))
|
|
87
|
+
return { ok: true, model: requested };
|
|
88
|
+
const listed = listModelsForPrompt(catalog).map((option) => option.id);
|
|
89
|
+
const defaultHint = catalog.defaultModel ? ` Omit model to use default (${catalog.defaultModel}).` : "";
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
error: `model "${requested}" is not in this agent's list. Use one of: ${listed.join(", ")}.${defaultHint}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function primaryPluginRef(pluginRefs) {
|
|
96
|
+
return pluginRefs?.find((ref) => typeof ref.id === "string" && ref.id.length > 0);
|
|
97
|
+
}
|
|
98
|
+
export async function loadAgentModelCatalogs(storage, agentIds) {
|
|
99
|
+
const catalogs = new Map();
|
|
100
|
+
if (!storage?.getAgentDetails || agentIds.length === 0)
|
|
101
|
+
return catalogs;
|
|
102
|
+
const [detailsList, plugins] = await Promise.all([
|
|
103
|
+
Promise.all(agentIds.map(async (agentId) => {
|
|
104
|
+
try {
|
|
105
|
+
const details = await storage.getAgentDetails({ agentId });
|
|
106
|
+
return { agentId, details };
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return { agentId, details: undefined };
|
|
110
|
+
}
|
|
111
|
+
})),
|
|
112
|
+
storage.getPlugins
|
|
113
|
+
? storage.getPlugins().catch(() => [])
|
|
114
|
+
: Promise.resolve([]),
|
|
115
|
+
]);
|
|
116
|
+
const pluginsById = new Map(plugins.map((plugin) => [plugin.id, plugin]));
|
|
117
|
+
for (const { agentId, details } of detailsList) {
|
|
118
|
+
const ref = primaryPluginRef(details?.pluginRefs);
|
|
119
|
+
if (!ref)
|
|
120
|
+
continue;
|
|
121
|
+
const schema = pluginsById.get(ref.id)?.configSchema?.properties?.model;
|
|
122
|
+
catalogs.set(agentId, catalogForAgent(ref.config, schema));
|
|
123
|
+
}
|
|
124
|
+
return catalogs;
|
|
125
|
+
}
|
package/dist/build-tools.js
CHANGED
|
@@ -2,10 +2,13 @@ import { memoryTools } from "./tools/memory.js";
|
|
|
2
2
|
import { todoTools } from "./tools/todo.js";
|
|
3
3
|
import { storageTools } from "./tools/storage.js";
|
|
4
4
|
import { askAgentTools } from "./tools/ask-agent.js";
|
|
5
|
+
import { installAgentTools } from "./tools/install-agent.js";
|
|
5
6
|
import { startWorkTools } from "./tools/start-work.js";
|
|
6
7
|
import { threadStatusTools } from "./tools/thread-status.js";
|
|
7
8
|
import { threadTitleTools } from "./tools/thread-title.js";
|
|
8
9
|
import { wrapToolsWithApproval } from "./tools/approval.js";
|
|
10
|
+
/** Always gated: installing an agent changes the workspace. */
|
|
11
|
+
const ALWAYS_APPROVE = ["install_agent"];
|
|
9
12
|
/**
|
|
10
13
|
* Merge first-party OpenBot coordinator tools and wrap configured actions
|
|
11
14
|
* with approval.
|
|
@@ -16,11 +19,12 @@ export function buildOpenbotTools(args) {
|
|
|
16
19
|
...todoTools,
|
|
17
20
|
...storageTools,
|
|
18
21
|
...askAgentTools,
|
|
22
|
+
...installAgentTools,
|
|
19
23
|
...startWorkTools,
|
|
20
24
|
...threadStatusTools,
|
|
21
25
|
...threadTitleTools,
|
|
22
26
|
...args.context.tools,
|
|
23
27
|
};
|
|
24
28
|
const approvalConfig = args.context.config?.approval ?? { actions: [] };
|
|
25
|
-
return wrapToolsWithApproval(tools, approvalConfig.actions ?? [], args.context);
|
|
29
|
+
return wrapToolsWithApproval(tools, [...new Set([...ALWAYS_APPROVE, ...(approvalConfig.actions ?? [])])], args.context);
|
|
26
30
|
}
|
package/dist/context.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { formatAvailableAgentsSection, formatInstalledAgentsSection, loadAgentModelCatalogs, availableMarketplaceAgents, } from "./agent-models.js";
|
|
1
2
|
import { GENERAL_SPACE_ID } from "./space-id.js";
|
|
2
|
-
import { OPENBOT_SYSTEM_PROMPT } from "./system-prompt.js";
|
|
3
3
|
import { todoService } from "./tools/todo-service.js";
|
|
4
4
|
export const DEFAULT_CONTEXT_BUDGET = 8000;
|
|
5
5
|
export const MAX_CONTEXT_FILES = 50;
|
|
@@ -26,8 +26,8 @@ export const getContextBudgetForModel = (modelString) => {
|
|
|
26
26
|
/**
|
|
27
27
|
* Simplified context builder for MVP.
|
|
28
28
|
*/
|
|
29
|
-
export async function buildContext(state, storage) {
|
|
30
|
-
const { channelId, threadId, channelDetails, agentId, threadDetails,
|
|
29
|
+
export async function buildContext(state, storage, host) {
|
|
30
|
+
const { channelId, threadId, channelDetails, agentId, threadDetails, } = state;
|
|
31
31
|
const sections = [];
|
|
32
32
|
// Fetch agents once if storage is available
|
|
33
33
|
const allAgents = storage?.getAgents
|
|
@@ -108,10 +108,21 @@ export async function buildContext(state, storage) {
|
|
|
108
108
|
}
|
|
109
109
|
// 2.6 Installed Agents
|
|
110
110
|
if (allAgents.length > 0) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
const catalogs = await loadAgentModelCatalogs(storage, allAgents.map((agent) => agent.id));
|
|
112
|
+
sections.push(formatInstalledAgentsSection(allAgents, catalogs));
|
|
113
|
+
}
|
|
114
|
+
// 2.7 Marketplace agents not yet installed
|
|
115
|
+
if (host?.listMarketplaceAgents) {
|
|
116
|
+
try {
|
|
117
|
+
const marketplace = await host.listMarketplaceAgents();
|
|
118
|
+
const available = availableMarketplaceAgents(allAgents, marketplace);
|
|
119
|
+
if (available.length > 0) {
|
|
120
|
+
sections.push(formatAvailableAgentsSection(available));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
console.warn("[context] Failed to fetch marketplace agents:", error);
|
|
125
|
+
}
|
|
115
126
|
}
|
|
116
127
|
if (storage?.getChannels) {
|
|
117
128
|
try {
|
|
@@ -178,12 +189,7 @@ export async function buildContext(state, storage) {
|
|
|
178
189
|
console.warn("[context] Failed to fetch files:", error);
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
|
-
// 5.
|
|
182
|
-
const rawInstructions = agentDetails?.instructions?.trim();
|
|
183
|
-
if (rawInstructions && rawInstructions !== OPENBOT_SYSTEM_PROMPT.trim()) {
|
|
184
|
-
sections.push(`## Instructions\n${rawInstructions}`);
|
|
185
|
-
}
|
|
186
|
-
// 6. Memories
|
|
192
|
+
// 5. Memories
|
|
187
193
|
if (storage?.listMemories) {
|
|
188
194
|
try {
|
|
189
195
|
const scopes = ["global", `agent:${agentId}`];
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineOpenbotPlugin } from './types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { modelConfigField, } from '@meetopenbot/plugin-sdk';
|
|
3
3
|
import { AUTO_MODEL_ID, AUTO_MODEL_OPTION } from './auto-model.js';
|
|
4
4
|
import { handleOpenbotWidgetResponse, runOpenbotTurn } from './runtime.js';
|
|
5
|
+
import { MAX_USER_INSTRUCTIONS_LENGTH } from './system-prompt.js';
|
|
5
6
|
import { memoryTools } from './tools/memory.js';
|
|
6
7
|
import { todoTools } from './tools/todo.js';
|
|
7
8
|
import { storageTools } from './tools/storage.js';
|
|
8
9
|
import { askAgentTools } from './tools/ask-agent.js';
|
|
10
|
+
import { installAgentTools } from './tools/install-agent.js';
|
|
9
11
|
import { startWorkTools } from './tools/start-work.js';
|
|
10
12
|
import { threadStatusTools } from './tools/thread-status.js';
|
|
11
13
|
import { threadTitleTools } from './tools/thread-title.js';
|
|
@@ -31,8 +33,13 @@ export const openbotPlugin = defineOpenbotPlugin({
|
|
|
31
33
|
configSchema: {
|
|
32
34
|
type: 'object',
|
|
33
35
|
properties: {
|
|
34
|
-
...cloudAuthModeProperties(),
|
|
35
36
|
model: modelField,
|
|
37
|
+
instructions: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
format: 'textarea',
|
|
40
|
+
description: 'Optional extra instructions injected into the coordinator system prompt.',
|
|
41
|
+
maxLength: MAX_USER_INSTRUCTIONS_LENGTH,
|
|
42
|
+
},
|
|
36
43
|
},
|
|
37
44
|
},
|
|
38
45
|
tools: {
|
|
@@ -40,6 +47,7 @@ export const openbotPlugin = defineOpenbotPlugin({
|
|
|
40
47
|
...todoTools,
|
|
41
48
|
...storageTools,
|
|
42
49
|
...askAgentTools,
|
|
50
|
+
...installAgentTools,
|
|
43
51
|
...startWorkTools,
|
|
44
52
|
...threadStatusTools,
|
|
45
53
|
...threadTitleTools,
|
|
@@ -53,6 +61,7 @@ export const openbotPlugin = defineOpenbotPlugin({
|
|
|
53
61
|
yield* runOpenbotTurn(args, {
|
|
54
62
|
model: context.config?.model || AUTO_MODEL_ID,
|
|
55
63
|
authMode,
|
|
64
|
+
instructions: context.config?.instructions,
|
|
56
65
|
agentId: context.agentId,
|
|
57
66
|
storage: context.storage,
|
|
58
67
|
toolDefinitions,
|
|
@@ -78,6 +87,7 @@ export const openbotPlugin = defineOpenbotPlugin({
|
|
|
78
87
|
yield* handleOpenbotWidgetResponse(turnArgs, {
|
|
79
88
|
model: context.config?.model || AUTO_MODEL_ID,
|
|
80
89
|
authMode,
|
|
90
|
+
instructions: context.config?.instructions,
|
|
81
91
|
agentId: context.agentId,
|
|
82
92
|
storage: context.storage,
|
|
83
93
|
toolDefinitions,
|
package/dist/runtime.js
CHANGED
|
@@ -3,14 +3,14 @@ import { BYOK_ENV_KEYS, isAuthErrorMessage, isCreditsErrorMessage, fetchModelReg
|
|
|
3
3
|
import { eventsToModelMessages } from './history.js';
|
|
4
4
|
import { addImageParts, eventsHaveImages, pickVisionModel } from './images.js';
|
|
5
5
|
import { buildContext } from './context.js';
|
|
6
|
-
import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
|
|
6
|
+
import { OPENBOT_SYSTEM_PROMPT, withUserInstructions } from './system-prompt.js';
|
|
7
7
|
import { AUTO_MODEL_ID, isAutoModel, isRetryableModelError, listAutoModelCandidates, missingByokApiKeyMessage, } from './auto-model.js';
|
|
8
8
|
import { reasoningForModel, resolveModel } from './model.js';
|
|
9
9
|
import { errorFromStreamPart } from './stream-error.js';
|
|
10
10
|
import { createAgentOutputBuffer, isOutputBoundaryPart } from './output-buffer.js';
|
|
11
|
-
async function buildSystemPrompt(state, storage) {
|
|
12
|
-
const context = await buildContext(state, storage);
|
|
13
|
-
const sections = [OPENBOT_SYSTEM_PROMPT, '', context];
|
|
11
|
+
async function buildSystemPrompt(state, storage, instructions, host) {
|
|
12
|
+
const context = await buildContext(state, storage, host);
|
|
13
|
+
const sections = [withUserInstructions(OPENBOT_SYSTEM_PROMPT, instructions), '', context];
|
|
14
14
|
const threadState = state.threadDetails?.state;
|
|
15
15
|
if (!threadState?.isSmartNamed) {
|
|
16
16
|
sections.push('', '## SYSTEM HINT', 'This thread is unnamed. Please use the `set_thread_title` tool to set a concise, descriptive title (e.g., "Project Brainstorming" instead of "project-brainstorm"). Only do this once.');
|
|
@@ -84,7 +84,7 @@ function toAiTools(tools, args, flushPendingOutput) {
|
|
|
84
84
|
return mapped;
|
|
85
85
|
}
|
|
86
86
|
export async function* runOpenbotTurn(args, options) {
|
|
87
|
-
const { model: modelString = AUTO_MODEL_ID, authMode = 'byok', storage, toolDefinitions = {}, abortSignal, host, } = options;
|
|
87
|
+
const { model: modelString = AUTO_MODEL_ID, authMode = 'byok', instructions, storage, toolDefinitions = {}, abortSignal, host, } = options;
|
|
88
88
|
let configuredModelString = modelString;
|
|
89
89
|
const isCreditsCloudAgent = (id) => host.isCloudSystemAgent(id ?? '') && authMode === 'credits';
|
|
90
90
|
if (!storage || abortSignal?.aborted)
|
|
@@ -92,7 +92,7 @@ export async function* runOpenbotTurn(args, options) {
|
|
|
92
92
|
const state = args.state;
|
|
93
93
|
state.model = configuredModelString;
|
|
94
94
|
const threadId = args.threadId || state.threadId;
|
|
95
|
-
const systemPrompt = await buildSystemPrompt(state, storage);
|
|
95
|
+
const systemPrompt = await buildSystemPrompt(state, storage, instructions, host);
|
|
96
96
|
const events = await storage.getEvents({
|
|
97
97
|
channelId: state.channelId ?? '',
|
|
98
98
|
threadId,
|
package/dist/system-prompt.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import { settingsVariablesMarkdown, SETTINGS_VARIABLES_MARKDOWN } from '@meetopenbot/plugin-sdk';
|
|
1
2
|
export const OPENBOT_SYSTEM_PROMPT = [
|
|
2
3
|
"# ROLE",
|
|
3
4
|
"You are OpenBot, the coordinator. Humans talk only to you. Specialized agents have their own harnesses; you ask them to do work. You do not impersonate them, and you do not do their jobs yourself.",
|
|
4
5
|
"",
|
|
5
6
|
"# SECURITY POLICY",
|
|
6
7
|
"- **CRITICAL**: Never request API keys, passwords, or sensitive credentials via text or UI widgets; these are managed deterministically via secure forms and must never enter your context.",
|
|
7
|
-
|
|
8
|
+
`- **Credential Guidance**: If an agent or tool requires credentials, tell the user to add them under ${SETTINGS_VARIABLES_MARKDOWN}. When you know the variable name, link to that page with \`?new=ENV_VAR_NAME\` so the client opens Add Variable with the name filled in. Example: ${settingsVariablesMarkdown('LINEAR_API_KEY')}.`,
|
|
8
9
|
"",
|
|
9
10
|
"# CORE MISSION",
|
|
10
11
|
"You are a high-level manager. Not every message is project work.",
|
|
11
|
-
"- Questions, listings, setup, routing, and status stay in `#general`. Answer them from `## SPACES
|
|
12
|
+
"- Questions, listings, setup, routing, and status stay in `#general`. Answer them from `## SPACES`, `## INSTALLED AGENTS`, and `## AVAILABLE AGENTS`. Do not create a Space to look anything up.",
|
|
12
13
|
"- For real project work (build, code, design, ops) you decide **where** (which Space) and **who** (which installed agent), then ask that agent, close without restating their work, and keep todos current.",
|
|
13
14
|
"",
|
|
14
15
|
"# THE AGENT LOOP",
|
|
@@ -42,8 +43,9 @@ export const OPENBOT_SYSTEM_PROMPT = [
|
|
|
42
43
|
"# OPERATIONAL GUIDELINES",
|
|
43
44
|
'- **Spaces**: Work lives in Spaces (product name for channels). `#general` is dispatch — setup, questions, listings, routing, and "what should we do next." Project, coding, design, and ops work belongs in a dedicated Space. Prefer existing Spaces. Create a new Space only when the human asked for one, or they agreed after you suggested it.',
|
|
44
45
|
"- **Asking**: You ask specialists with `ask_agent`. If `## MENTIONED AGENTS` is present, you MUST ask those people. You may also ask others if needed. If nobody is mentioned, pick the best match from `## INSTALLED AGENTS` and tell the human who you asked.",
|
|
46
|
+
"- **Models**: When you `ask_agent`, pick `model` from that agent's list in `## INSTALLED AGENTS` using the descriptions — cheaper/faster for simple work, stronger for complex coding or reasoning. Omit `model` to use their default. Do not reuse another agent's ids.",
|
|
45
47
|
"- **Closer**: After `ask_agent`, you are the closer, not a second author of their work. Don't repeat. Add or close. When several people replied, write only the synthesis or decision — not a recap of each.",
|
|
46
|
-
"- **Missing people**: If no installed agent can do the job, tell the human who to install. Do not attempt the specialist work yourself.",
|
|
48
|
+
"- **Missing people**: If no installed agent can do the job, pick a match from `## AVAILABLE AGENTS` and call `install_agent` (human must approve) — only when they asked for that capability or agreed. One agent per call. Then `ask_agent`. If nobody is listed, tell the human who to install from Settings. Do not attempt the specialist work yourself.",
|
|
47
49
|
"- **Hub-and-spoke**: Specialists cannot talk to each other. You pass context from one to the next.",
|
|
48
50
|
"- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.",
|
|
49
51
|
"",
|
|
@@ -59,3 +61,29 @@ export const OPENBOT_SYSTEM_PROMPT = [
|
|
|
59
61
|
"- If an asked agent fails, try a different agent or tell the human what blocked them.",
|
|
60
62
|
"- Only ask the human for help after you cannot route or ask anyone who can do the work.",
|
|
61
63
|
].join("\n");
|
|
64
|
+
/** Max characters accepted from the OpenBot plugin `instructions` config field. */
|
|
65
|
+
export const MAX_USER_INSTRUCTIONS_LENGTH = 4000;
|
|
66
|
+
/** Trim and cap user-provided coordinator instructions. */
|
|
67
|
+
export function normalizeUserInstructions(value) {
|
|
68
|
+
if (typeof value !== "string")
|
|
69
|
+
return undefined;
|
|
70
|
+
const trimmed = value.trim();
|
|
71
|
+
if (!trimmed)
|
|
72
|
+
return undefined;
|
|
73
|
+
return trimmed.length > MAX_USER_INSTRUCTIONS_LENGTH
|
|
74
|
+
? trimmed.slice(0, MAX_USER_INSTRUCTIONS_LENGTH)
|
|
75
|
+
: trimmed;
|
|
76
|
+
}
|
|
77
|
+
/** Append capped user instructions to the static system prompt. */
|
|
78
|
+
export function withUserInstructions(basePrompt, value) {
|
|
79
|
+
const instructions = normalizeUserInstructions(value);
|
|
80
|
+
if (!instructions)
|
|
81
|
+
return basePrompt;
|
|
82
|
+
return [
|
|
83
|
+
basePrompt,
|
|
84
|
+
"",
|
|
85
|
+
"# USER INSTRUCTIONS",
|
|
86
|
+
"The following are additional instructions from the user. Follow them when they do not conflict with the security policy or core mission above.",
|
|
87
|
+
instructions,
|
|
88
|
+
].join("\n");
|
|
89
|
+
}
|
package/dist/tools/ask-agent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { loadAgentModelCatalogs, validateAskModel } from "../agent-models.js";
|
|
3
4
|
/**
|
|
4
5
|
* `ask_agent` — OpenBot sends a message to another agent in its own harness.
|
|
5
6
|
* Only the orchestrator may ask. Child events stream into this thread so the
|
|
@@ -13,6 +14,10 @@ export const toolDefinitions = {
|
|
|
13
14
|
.string()
|
|
14
15
|
.describe("The ID of the agent to ask (from INSTALLED AGENTS)."),
|
|
15
16
|
prompt: z.string().describe("The message or question for that agent."),
|
|
17
|
+
model: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Optional model id from that agent's list in INSTALLED AGENTS. Omit to use their default."),
|
|
16
21
|
}),
|
|
17
22
|
},
|
|
18
23
|
};
|
|
@@ -34,6 +39,15 @@ async function runAskedAgent(ctx, args) {
|
|
|
34
39
|
output: "agentId, prompt, and toolCallId are required",
|
|
35
40
|
};
|
|
36
41
|
}
|
|
42
|
+
const catalogs = await loadAgentModelCatalogs(ctx.storage, [agentId]);
|
|
43
|
+
const modelCheck = validateAskModel(args.model, catalogs.get(agentId));
|
|
44
|
+
if (!modelCheck.ok) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: modelCheck.error,
|
|
48
|
+
output: modelCheck.error,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
37
51
|
const runId = `ask_${randomUUID()}`;
|
|
38
52
|
let lastAgentOutput = "";
|
|
39
53
|
const eventQueue = [];
|
|
@@ -49,9 +63,10 @@ async function runAskedAgent(ctx, args) {
|
|
|
49
63
|
role: "agent",
|
|
50
64
|
content: prompt,
|
|
51
65
|
agentId,
|
|
66
|
+
...(modelCheck.model ? { config: { model: modelCheck.model } } : {}),
|
|
52
67
|
},
|
|
53
68
|
meta: {
|
|
54
|
-
channelId: ctx.channelId ?? ctx.state.channelId ??
|
|
69
|
+
channelId: ctx.channelId ?? ctx.state.channelId ?? "",
|
|
55
70
|
threadId: ctx.threadId ?? ctx.state.threadId,
|
|
56
71
|
parentAgentId: ctx.agentId,
|
|
57
72
|
parentToolCallId: toolCallId,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* `install_agent` — OpenBot installs a marketplace specialist so it can ask them.
|
|
4
|
+
* The host looks up the listing, installs plugins, and materializes AGENT.md.
|
|
5
|
+
* Approval wrapping happens in `buildOpenbotTools`.
|
|
6
|
+
*/
|
|
7
|
+
export const toolDefinitions = {
|
|
8
|
+
install_agent: {
|
|
9
|
+
description: "Install a marketplace agent so you can ask them. Use an id from AVAILABLE AGENTS. The human must approve. After install, ask them with ask_agent.",
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
agentId: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe("The marketplace agent id to install (from AVAILABLE AGENTS)."),
|
|
14
|
+
}),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
async function installMarketplaceAgent(ctx, args) {
|
|
18
|
+
if (ctx.agentId !== ctx.host.orchestratorAgentId) {
|
|
19
|
+
const message = "Only OpenBot can install agents.";
|
|
20
|
+
return { success: false, error: message, output: message };
|
|
21
|
+
}
|
|
22
|
+
const agentId = typeof args.agentId === "string" ? args.agentId.trim() : "";
|
|
23
|
+
if (!agentId) {
|
|
24
|
+
const message = "agentId is required";
|
|
25
|
+
return { success: false, error: message, output: message };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const installed = await ctx.host.installMarketplaceAgent(agentId);
|
|
29
|
+
const output = `Installed ${installed.name} (${installed.agentId}). You can ask them with ask_agent.`;
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
agentId: installed.agentId,
|
|
33
|
+
name: installed.name,
|
|
34
|
+
output,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : "Failed to install agent";
|
|
39
|
+
return { success: false, error: message, output: message };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export const tools = {
|
|
43
|
+
install_agent: {
|
|
44
|
+
...toolDefinitions.install_agent,
|
|
45
|
+
execute: async (rawArgs, ctx) => {
|
|
46
|
+
return installMarketplaceAgent(ctx, (rawArgs ?? {}));
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export const installAgentTools = tools;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetopenbot/openbot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "OpenBot coordinator runtime: ask specialists, route work into Spaces, and track todos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@ai-sdk/openai": "^4.0.46",
|
|
26
26
|
"ai": "^7.0.77",
|
|
27
27
|
"zod": "^4.3.5",
|
|
28
|
-
"@meetopenbot/plugin-sdk": "^1.0.
|
|
28
|
+
"@meetopenbot/plugin-sdk": "^1.0.5"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^20.10.1",
|