@meetopenbot/openbot 0.1.13 → 0.1.15
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/auto-model.js +67 -0
- package/dist/context.js +119 -37
- package/dist/index.js +47 -29
- package/dist/model-registry.js +12 -11
- package/dist/model.js +14 -4
- package/dist/runtime.js +80 -45
- package/dist/space-id.js +22 -0
- package/dist/system-prompt.js +52 -55
- package/dist/tools/ask-agent.js +123 -0
- package/dist/tools/start-work.js +269 -0
- package/dist/tools/storage.js +135 -98
- package/dist/tools/thread-status.js +91 -0
- package/package.json +3 -3
package/dist/runtime.js
CHANGED
|
@@ -3,6 +3,7 @@ import { eventsToModelMessages } from './history.js';
|
|
|
3
3
|
import { buildContext } from './context.js';
|
|
4
4
|
import { OPENBOT_SYSTEM_PROMPT } from './system-prompt.js';
|
|
5
5
|
import { isAuthErrorMessage, isCreditsErrorMessage } from './credits-auth.js';
|
|
6
|
+
import { AUTO_MODEL_ID, isAutoModel, isRetryableModelError, listAutoModelCandidates, } from './auto-model.js';
|
|
6
7
|
import { resolveModel } from './model.js';
|
|
7
8
|
import { fetchModelRegistry, getProviderModelOptions, listApiKeyProviders, PROVIDER_API_KEY_LINKS, } from './model-registry.js';
|
|
8
9
|
async function buildSystemPrompt(state, storage) {
|
|
@@ -68,10 +69,8 @@ function createToolBatchTracker(state, storage, channelId, threadId) {
|
|
|
68
69
|
* - When a full batch of results is in, `runLLM` runs again with updated history.
|
|
69
70
|
*/
|
|
70
71
|
export const openbotRuntime = (options) => (builder) => {
|
|
71
|
-
const { model: modelString =
|
|
72
|
-
let
|
|
73
|
-
const resolveCurrentModel = () => resolveModel(currentModelString, { authMode });
|
|
74
|
-
let model = resolveCurrentModel();
|
|
72
|
+
const { model: modelString = AUTO_MODEL_ID, authMode = 'byok', agentId, storage, toolDefinitions = {}, abortSignal, host, } = options;
|
|
73
|
+
let configuredModelString = modelString;
|
|
75
74
|
const isCreditsCloudAgent = (id) => host.isCloudSystemAgent(id ?? '') && authMode === 'credits';
|
|
76
75
|
const runLLM = async function* (context, threadId, trigger) {
|
|
77
76
|
if (!storage)
|
|
@@ -84,27 +83,48 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
84
83
|
const parentAgentId = triggerEvent?.meta?.parentAgentId;
|
|
85
84
|
const parentToolCallId = triggerEvent?.meta?.parentToolCallId;
|
|
86
85
|
const state = context.state;
|
|
87
|
-
state.model =
|
|
86
|
+
state.model = configuredModelString;
|
|
88
87
|
const systemPrompt = await buildSystemPrompt(context.state, storage);
|
|
89
88
|
const events = await storage.getEvents({
|
|
90
89
|
channelId: context.state.channelId,
|
|
91
90
|
threadId: context.state.threadId,
|
|
92
91
|
});
|
|
93
92
|
const messages = eventsToModelMessages(events);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const candidates = isAutoModel(configuredModelString)
|
|
94
|
+
? listAutoModelCandidates({ authMode })
|
|
95
|
+
: [configuredModelString];
|
|
97
96
|
try {
|
|
98
97
|
// Single LLM request — tool execution happens externally via action:* handlers.
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
const generate = async () => {
|
|
99
|
+
let lastError;
|
|
100
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
101
|
+
try {
|
|
102
|
+
return await generateText({
|
|
103
|
+
model: resolveModel(candidates[i], { authMode }),
|
|
104
|
+
system: systemPrompt,
|
|
105
|
+
messages,
|
|
106
|
+
tools: toolDefinitions,
|
|
107
|
+
stopWhen: ({ steps }) => steps.length === 1,
|
|
108
|
+
allowSystemInMessages: true,
|
|
109
|
+
abortSignal,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
lastError = error;
|
|
114
|
+
if (abortSignal?.aborted)
|
|
115
|
+
throw error;
|
|
116
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
117
|
+
if (isCreditsErrorMessage(errorMessage) || isAuthErrorMessage(errorMessage)) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
if (!isRetryableModelError(error) || i === candidates.length - 1) {
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
throw lastError ?? new Error('Model request failed.');
|
|
126
|
+
};
|
|
127
|
+
const result = await generate();
|
|
108
128
|
const toolCalls = result.toolCalls ?? [];
|
|
109
129
|
// if (result.usage) {
|
|
110
130
|
// const usage = result.usage;
|
|
@@ -252,8 +272,9 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
252
272
|
return;
|
|
253
273
|
if (metadata?.type === 'api_provider_selection') {
|
|
254
274
|
const provider = actionId;
|
|
255
|
-
const
|
|
256
|
-
|
|
275
|
+
const configuredModelId = isAutoModel(configuredModelString)
|
|
276
|
+
? ''
|
|
277
|
+
: configuredModelString.split('/').slice(1).join('/');
|
|
257
278
|
const registry = await fetchModelRegistry();
|
|
258
279
|
const providerData = registry?.providers?.[provider];
|
|
259
280
|
const label = providerData?.label || provider;
|
|
@@ -273,8 +294,8 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
273
294
|
},
|
|
274
295
|
meta: { agentId: context.state.agentId, threadId },
|
|
275
296
|
};
|
|
276
|
-
if (modelOptions && modelOptions.length > 0) {
|
|
277
|
-
const defaultModelId = modelOptions.find((m) => m.value ===
|
|
297
|
+
if (modelOptions && modelOptions.length > 0 && !isAutoModel(configuredModelString)) {
|
|
298
|
+
const defaultModelId = modelOptions.find((m) => m.value === configuredModelId)?.value || modelOptions[0].value;
|
|
278
299
|
yield {
|
|
279
300
|
type: 'client:ui:widget',
|
|
280
301
|
data: {
|
|
@@ -302,23 +323,29 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
302
323
|
kind: 'form',
|
|
303
324
|
widgetId: `api_key_request_${Date.now()}`,
|
|
304
325
|
title: `${label} Setup`,
|
|
305
|
-
description:
|
|
326
|
+
description: isAutoModel(configuredModelString)
|
|
327
|
+
? `Enter your API key to continue.`
|
|
328
|
+
: `Enter your API key and model ID.`,
|
|
306
329
|
fields: [
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
330
|
+
...(isAutoModel(configuredModelString)
|
|
331
|
+
? []
|
|
332
|
+
: [
|
|
333
|
+
{
|
|
334
|
+
id: 'model',
|
|
335
|
+
label: 'Model',
|
|
336
|
+
type: 'text',
|
|
337
|
+
description: 'Enter the model ID for this provider.',
|
|
338
|
+
placeholder: provider === 'openai'
|
|
339
|
+
? 'gpt-4o-mini'
|
|
340
|
+
: provider === 'anthropic'
|
|
341
|
+
? 'claude-3-5-sonnet-20241022'
|
|
342
|
+
: provider === 'deepseek'
|
|
343
|
+
? 'deepseek-chat'
|
|
344
|
+
: 'gemini-2.0-flash',
|
|
345
|
+
required: true,
|
|
346
|
+
defaultValue: configuredModelId || '',
|
|
347
|
+
},
|
|
348
|
+
]),
|
|
322
349
|
{
|
|
323
350
|
id: 'apiKey',
|
|
324
351
|
label: 'API Key',
|
|
@@ -393,8 +420,9 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
393
420
|
if (!values?.apiKey)
|
|
394
421
|
return;
|
|
395
422
|
const provider = String(values.provider || metadata.provider);
|
|
423
|
+
const keepAuto = isAutoModel(configuredModelString);
|
|
396
424
|
const modelId = String(values.model || metadata.model || '').trim();
|
|
397
|
-
if (!modelId)
|
|
425
|
+
if (!keepAuto && !modelId)
|
|
398
426
|
return;
|
|
399
427
|
const apiKey = String(values.apiKey);
|
|
400
428
|
if (provider !== 'openai' &&
|
|
@@ -415,16 +443,19 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
415
443
|
: provider === 'deepseek'
|
|
416
444
|
? 'DEEPSEEK_API_KEY'
|
|
417
445
|
: 'GOOGLE_GENERATIVE_AI_API_KEY';
|
|
418
|
-
const newModelString = `${provider}/${modelId}`;
|
|
446
|
+
const newModelString = keepAuto ? AUTO_MODEL_ID : `${provider}/${modelId}`;
|
|
419
447
|
if (!storage)
|
|
420
448
|
return;
|
|
421
449
|
try {
|
|
422
450
|
await storage.createVariable({ key: envVar, value: apiKey, secret: true });
|
|
423
451
|
process.env[envVar] = apiKey;
|
|
424
|
-
|
|
425
|
-
|
|
452
|
+
if (!keepAuto) {
|
|
453
|
+
configuredModelString = newModelString;
|
|
454
|
+
}
|
|
426
455
|
try {
|
|
427
|
-
|
|
456
|
+
if (!keepAuto) {
|
|
457
|
+
host.saveConfig({ model: configuredModelString });
|
|
458
|
+
}
|
|
428
459
|
const details = await storage.getAgentDetails({ agentId: context.state.agentId });
|
|
429
460
|
const updatedPlugins = details.pluginRefs.map((ref) => {
|
|
430
461
|
if (ref.id === host.openbotPluginId) {
|
|
@@ -432,7 +463,7 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
432
463
|
...ref,
|
|
433
464
|
config: {
|
|
434
465
|
...ref.config,
|
|
435
|
-
model:
|
|
466
|
+
...(keepAuto ? {} : { model: configuredModelString }),
|
|
436
467
|
...(host.isCloudSystemAgent(context.state.agentId) ? { authMode: 'byok' } : {}),
|
|
437
468
|
},
|
|
438
469
|
};
|
|
@@ -450,7 +481,9 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
450
481
|
yield {
|
|
451
482
|
type: 'agent:output',
|
|
452
483
|
data: {
|
|
453
|
-
content:
|
|
484
|
+
content: keepAuto
|
|
485
|
+
? `Saved ${provider} API key. Auto will use it when that provider is the cheapest available.`
|
|
486
|
+
: `Saved ${provider} API key and set model to \`${newModelString}\`.`,
|
|
454
487
|
},
|
|
455
488
|
meta: { agentId: context.state.agentId },
|
|
456
489
|
};
|
|
@@ -460,7 +493,9 @@ export const openbotRuntime = (options) => (builder) => {
|
|
|
460
493
|
widgetId: event.data.widgetId,
|
|
461
494
|
kind: 'message',
|
|
462
495
|
title: 'API Key Saved',
|
|
463
|
-
body:
|
|
496
|
+
body: keepAuto
|
|
497
|
+
? `Successfully saved ${provider} API key. You can now continue your conversation.`
|
|
498
|
+
: `Successfully saved ${provider} API key and selected model \`${newModelString}\`. You can now continue your conversation.`,
|
|
464
499
|
state: 'submitted',
|
|
465
500
|
display: 'collapsed',
|
|
466
501
|
disabled: true,
|
package/dist/space-id.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Default inbox Space. Wire id stays `general` (channelId). */
|
|
2
|
+
export const GENERAL_SPACE_ID = "general";
|
|
3
|
+
/** Dummy ids models invent when they feel forced to "place" work that has no Space. */
|
|
4
|
+
const PLACEHOLDER_SPACE_IDS = new Set([
|
|
5
|
+
"noop",
|
|
6
|
+
"none",
|
|
7
|
+
"null",
|
|
8
|
+
"n-a",
|
|
9
|
+
"na",
|
|
10
|
+
"dummy",
|
|
11
|
+
"placeholder",
|
|
12
|
+
"untitled",
|
|
13
|
+
"misc",
|
|
14
|
+
"temp",
|
|
15
|
+
"tmp",
|
|
16
|
+
]);
|
|
17
|
+
export function isPlaceholderSpaceId(spaceId) {
|
|
18
|
+
return PLACEHOLDER_SPACE_IDS.has(spaceId);
|
|
19
|
+
}
|
|
20
|
+
export function placeholderSpaceError(spaceId) {
|
|
21
|
+
return `Refused to create #${spaceId}: that looks like a placeholder, not a real Space. Stay in #general for questions and listings. If project work needs a new Space, propose a concrete name and wait for the human to agree.`;
|
|
22
|
+
}
|
package/dist/system-prompt.js
CHANGED
|
@@ -1,57 +1,54 @@
|
|
|
1
1
|
export const OPENBOT_SYSTEM_PROMPT = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"# ROLE",
|
|
3
|
+
"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
|
+
"# SECURITY POLICY",
|
|
6
|
+
"- **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
7
|
'- **Credential Guidance**: If an agent or tool requires credentials, inform the user they can be managed under "Settings > Variables".',
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'- **
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'- Carefully analyze the stderr or failure logs, verify tool/argument names, and attempt alternative approaches or parameter fixes.',
|
|
56
|
-
'- Only report failure reasons and ask the human for help after multiple alternative approaches have failed.',
|
|
57
|
-
].join('\n');
|
|
8
|
+
"",
|
|
9
|
+
"# CORE MISSION",
|
|
10
|
+
"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` and `## INSTALLED AGENTS`. Do not create a Space to look anything up.",
|
|
12
|
+
"- For real project work (build, code, design, ops) you decide **where** (which Space) and **who** (which installed agent), then ask that agent, report back in plain language, and keep todos current.",
|
|
13
|
+
"",
|
|
14
|
+
"# THE AGENT LOOP",
|
|
15
|
+
"You operate in an iterative loop:",
|
|
16
|
+
"1. **Analyze Events**: Understand the human, mentioned people, the current Space, and `## SPACES`.",
|
|
17
|
+
"2. **Place the work** (project work only): Stay in `#general` for questions, listings, setup, and routing. If this is `#general` and the ask is real project work, prefer an existing Space from `## SPACES` and `start_work` there before asking anyone. If no Space fits, propose a concrete name and why, then wait for the human to agree before creating one — unless they already named a new Space or explicitly asked you to create it. Never invent placeholder ids (`noop`, `temp`, `misc`, `untitled`). If you are already in the right Space, stay. Never `start_work` on a message that was already forwarded here.",
|
|
18
|
+
"3. **Ask people**: Use `ask_agent` to send a message to a specialist. Write it as a request to a colleague, not as a tool invocation.",
|
|
19
|
+
"4. **Wait**: Let the asked agent run in its own harness. Read their reply, then continue or ask someone else.",
|
|
20
|
+
"5. **Submit Results**: Message the human with clear outcomes. Name who you asked and where the work lives.",
|
|
21
|
+
"6. **Enter Standby**: Idle until the next human message.",
|
|
22
|
+
"",
|
|
23
|
+
"# TASK TRACKING (TODOS)",
|
|
24
|
+
"- For any complex multi-step task (3+ steps), you **MUST** maintain a todo list with `todo_write`.",
|
|
25
|
+
"- Write the full intended list at task start. Each call replaces the previous list (not a partial patch).",
|
|
26
|
+
"- Keep exactly one item `in_progress` while working. Mark items `completed` immediately after finishing them.",
|
|
27
|
+
"- Use `cancelled` for steps that are no longer needed. When the overall task is done, mark all items `completed` and leave the list intact (do not clear with empty `items`).",
|
|
28
|
+
"- The current list is injected into context each turn as `## TODOS`; call `todo_read` only if you need an explicit refresh.",
|
|
29
|
+
"- Do not stop with open `pending` or `in_progress` items unless you are blocked and have told the user why.",
|
|
30
|
+
"",
|
|
31
|
+
"# JOB STATUS",
|
|
32
|
+
"- Every thread is a job. Status is `working`, `needs_input`, `ready_for_review`, `completed`, or `archived`.",
|
|
33
|
+
"- When you need a blocking answer from the human, call `set_thread_status` with `needs_input` and a short `reason`.",
|
|
34
|
+
"- When you believe the job is done, set `ready_for_review`. Never set `completed` or `archived` — only the human can.",
|
|
35
|
+
"- The current status is injected each turn as `## THREAD STATUS`.",
|
|
36
|
+
"",
|
|
37
|
+
"# OPERATIONAL GUIDELINES",
|
|
38
|
+
'- **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.',
|
|
39
|
+
"- **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.",
|
|
40
|
+
"- **Missing people**: If no installed agent can do the job, tell the human who to install. Do not attempt the specialist work yourself.",
|
|
41
|
+
"- **Hub-and-spoke**: Specialists cannot talk to each other. You pass context from one to the next.",
|
|
42
|
+
"- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.",
|
|
43
|
+
"",
|
|
44
|
+
"# COMMUNICATION & WRITING STYLE",
|
|
45
|
+
"- Be concise, professional, proactive, and polite.",
|
|
46
|
+
"- Confirm receipt quickly. For longer work, say where you are taking it and who you will ask.",
|
|
47
|
+
"- Write in clean, continuous prose. Avoid excessive bullet points unless requested.",
|
|
48
|
+
'- Talk about people, not tools: "I asked Claude Code to …" never "I used Claude Code to …".',
|
|
49
|
+
"",
|
|
50
|
+
"# ERROR HANDLING",
|
|
51
|
+
"- Tool failures are provided as events. When an error occurs, do not panic or immediately ask the user for help.",
|
|
52
|
+
"- If an asked agent fails, try a different agent or tell the human what blocked them.",
|
|
53
|
+
"- Only ask the human for help after you cannot route or ask anyone who can do the work.",
|
|
54
|
+
].join("\n");
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
const askAgentToolDefinitions = {
|
|
4
|
+
ask_agent: {
|
|
5
|
+
description: "Ask another installed agent to do work in their own harness. Write the prompt as a message to a colleague, not as a command to a tool.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
agentId: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("The ID of the agent to ask (from INSTALLED AGENTS)."),
|
|
10
|
+
prompt: z.string().describe("The message or question for that agent."),
|
|
11
|
+
}),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
async function* runAskedAgent(pluginContext, event, context, resultType) {
|
|
15
|
+
if (context.state.agentId !== pluginContext.host.orchestratorAgentId) {
|
|
16
|
+
yield {
|
|
17
|
+
type: resultType,
|
|
18
|
+
data: {
|
|
19
|
+
success: false,
|
|
20
|
+
error: "Only OpenBot can ask other agents.",
|
|
21
|
+
},
|
|
22
|
+
meta: event.meta,
|
|
23
|
+
};
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const agentId = event.data.agentId;
|
|
27
|
+
const prompt = event.data.prompt;
|
|
28
|
+
const toolCallId = event.meta?.toolCallId;
|
|
29
|
+
if (!agentId || !prompt || !toolCallId)
|
|
30
|
+
return;
|
|
31
|
+
const runId = `ask_${randomUUID()}`;
|
|
32
|
+
let lastAgentOutput = "";
|
|
33
|
+
const eventQueue = [];
|
|
34
|
+
let resolveNext = null;
|
|
35
|
+
let isFinished = false;
|
|
36
|
+
const runPromise = pluginContext.host
|
|
37
|
+
.runAgent({
|
|
38
|
+
runId,
|
|
39
|
+
agentId,
|
|
40
|
+
event: {
|
|
41
|
+
type: "agent:invoke",
|
|
42
|
+
data: {
|
|
43
|
+
role: "user",
|
|
44
|
+
content: prompt,
|
|
45
|
+
agentId,
|
|
46
|
+
},
|
|
47
|
+
meta: {
|
|
48
|
+
channelId: context.state.channelId,
|
|
49
|
+
threadId: context.state.threadId,
|
|
50
|
+
parentAgentId: context.state.agentId,
|
|
51
|
+
parentToolCallId: toolCallId,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
publicBaseUrl: pluginContext.publicBaseUrl,
|
|
55
|
+
persistEvents: false,
|
|
56
|
+
onEvent: async (outEvent) => {
|
|
57
|
+
const enrichedEvent = {
|
|
58
|
+
...outEvent,
|
|
59
|
+
meta: {
|
|
60
|
+
...outEvent.meta,
|
|
61
|
+
parentAgentId: context.state.agentId,
|
|
62
|
+
parentToolCallId: toolCallId,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
eventQueue.push(enrichedEvent);
|
|
66
|
+
if (outEvent.type === "agent:output") {
|
|
67
|
+
lastAgentOutput = outEvent.data.content;
|
|
68
|
+
}
|
|
69
|
+
if (resolveNext) {
|
|
70
|
+
resolveNext();
|
|
71
|
+
resolveNext = null;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
.catch((error) => {
|
|
76
|
+
console.error(`[ask_agent] Error in asked run ${runId}:`, error);
|
|
77
|
+
})
|
|
78
|
+
.finally(() => {
|
|
79
|
+
isFinished = true;
|
|
80
|
+
if (resolveNext) {
|
|
81
|
+
resolveNext();
|
|
82
|
+
resolveNext = null;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
while (!isFinished || eventQueue.length > 0) {
|
|
86
|
+
if (eventQueue.length === 0) {
|
|
87
|
+
await new Promise((r) => {
|
|
88
|
+
resolveNext = r;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
while (eventQueue.length > 0) {
|
|
92
|
+
yield eventQueue.shift();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
await runPromise;
|
|
96
|
+
yield {
|
|
97
|
+
type: resultType,
|
|
98
|
+
data: {
|
|
99
|
+
success: true,
|
|
100
|
+
output: lastAgentOutput,
|
|
101
|
+
},
|
|
102
|
+
meta: {
|
|
103
|
+
...event.meta,
|
|
104
|
+
agentId: context.state.agentId,
|
|
105
|
+
toolCallId,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export const askAgentPlugin = {
|
|
110
|
+
id: "ask-agent",
|
|
111
|
+
name: "Ask agent",
|
|
112
|
+
description: "Lets OpenBot ask specialized agents to do work in their own harness.",
|
|
113
|
+
toolDefinitions: askAgentToolDefinitions,
|
|
114
|
+
factory: (pluginContext) => (builder) => {
|
|
115
|
+
builder.on("action:ask_agent", async function* (event, context) {
|
|
116
|
+
yield* runAskedAgent(pluginContext, event, context, "action:ask_agent:result");
|
|
117
|
+
});
|
|
118
|
+
builder.on("action:delegate_task", async function* (event, context) {
|
|
119
|
+
yield* runAskedAgent(pluginContext, event, context, "action:delegate_task:result");
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
export default askAgentPlugin;
|