@meetopenbot/openbot 1.2.20 → 1.3.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/context.js +7 -2
- package/dist/runtime.js +20 -11
- package/dist/surface.js +46 -0
- package/dist/tools/storage.js +4 -2
- package/package.json +2 -2
package/dist/context.js
CHANGED
|
@@ -2,6 +2,7 @@ import { formatGitIdentity, readGitIdentity, } from "@meetopenbot/plugin-sdk";
|
|
|
2
2
|
import { formatAvailableAgentsSection, formatInstalledAgentsSection, loadAgentModelCatalogs, availableMarketplaceAgents, } from "./agent-models.js";
|
|
3
3
|
import { GENERAL_SPACE_ID } from "./space-id.js";
|
|
4
4
|
import { todoService } from "./tools/todo-service.js";
|
|
5
|
+
import { readInvokeSurface, surfaceSection } from "./surface.js";
|
|
5
6
|
export const DEFAULT_CONTEXT_BUDGET = 8000;
|
|
6
7
|
export const MAX_CONTEXT_FILES = 50;
|
|
7
8
|
/**
|
|
@@ -30,6 +31,8 @@ export const getContextBudgetForModel = (modelString) => {
|
|
|
30
31
|
export async function buildContext(state, storage, host) {
|
|
31
32
|
const { channelId, threadId, channelDetails, agentId, threadDetails, } = state;
|
|
32
33
|
const sections = [];
|
|
34
|
+
// Inline asks skip routing context (marketplace, other Spaces): they never move work.
|
|
35
|
+
const surface = readInvokeSurface(state.triggerEvent);
|
|
33
36
|
// Fetch agents once if storage is available
|
|
34
37
|
const allAgents = storage?.getAgents
|
|
35
38
|
? await storage.getAgents().catch(() => [])
|
|
@@ -118,7 +121,7 @@ export async function buildContext(state, storage, host) {
|
|
|
118
121
|
sections.push(formatInstalledAgentsSection(otherAgents, catalogs));
|
|
119
122
|
}
|
|
120
123
|
// 2.7 Marketplace agents not yet installed
|
|
121
|
-
if (host?.listMarketplaceAgents) {
|
|
124
|
+
if (!surface && host?.listMarketplaceAgents) {
|
|
122
125
|
try {
|
|
123
126
|
const marketplace = await host.listMarketplaceAgents();
|
|
124
127
|
const available = availableMarketplaceAgents(allAgents, marketplace);
|
|
@@ -130,7 +133,7 @@ export async function buildContext(state, storage, host) {
|
|
|
130
133
|
console.warn("[context] Failed to fetch marketplace agents:", error);
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
|
-
if (storage?.getChannels) {
|
|
136
|
+
if (!surface && storage?.getChannels) {
|
|
134
137
|
try {
|
|
135
138
|
const spaces = (await storage.getChannels());
|
|
136
139
|
if (spaces.length > 0) {
|
|
@@ -213,5 +216,7 @@ export async function buildContext(state, storage, host) {
|
|
|
213
216
|
console.warn("[context] Failed to fetch memories:", error);
|
|
214
217
|
}
|
|
215
218
|
}
|
|
219
|
+
if (surface)
|
|
220
|
+
sections.push(surfaceSection(surface));
|
|
216
221
|
return sections.join("\n\n");
|
|
217
222
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { generateText, streamText, stepCountIs } from 'ai';
|
|
2
|
+
import { generateText, hasToolCall, streamText, stepCountIs } from 'ai';
|
|
3
3
|
import { BYOK_ENV_KEYS, isAuthErrorMessage, isCreditsErrorMessage, fetchModelRegistry, getProviderModelOptions, listApiKeyProviders, PROVIDER_API_KEY_LINKS, } from '@meetopenbot/plugin-sdk';
|
|
4
4
|
import { eventsToModelMessages } from './history.js';
|
|
5
5
|
import { addAttachmentParts, chooseTurnCandidates, eventsHaveAttachments, modelSupportsImages, } from './images.js';
|
|
@@ -11,6 +11,7 @@ import { reasoningForModel, resolveModel } from './model.js';
|
|
|
11
11
|
import { errorFromStreamPart } from './stream-error.js';
|
|
12
12
|
import { createAgentOutputBuffer, isOutputBoundaryPart } from './output-buffer.js';
|
|
13
13
|
import { threadNamingHint } from './thread-naming.js';
|
|
14
|
+
import { readInvokeSurface, RESPOND_TOOL, surfaceTools } from './surface.js';
|
|
14
15
|
async function buildSystemPrompt(state, storage, instructions, host) {
|
|
15
16
|
const context = await buildContext(state, storage, host);
|
|
16
17
|
const sections = [
|
|
@@ -18,8 +19,10 @@ async function buildSystemPrompt(state, storage, instructions, host) {
|
|
|
18
19
|
'',
|
|
19
20
|
context,
|
|
20
21
|
];
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
if (!readInvokeSurface(state.triggerEvent)) {
|
|
23
|
+
const threadState = state.threadDetails?.state;
|
|
24
|
+
sections.push(...threadNamingHint(threadState));
|
|
25
|
+
}
|
|
23
26
|
return sections.join('\n');
|
|
24
27
|
}
|
|
25
28
|
function toAiTools(tools, args, flushPendingOutput) {
|
|
@@ -222,7 +225,11 @@ async function* attemptFastForward(args, options) {
|
|
|
222
225
|
return true;
|
|
223
226
|
}
|
|
224
227
|
export async function* runOpenbotTurn(args, options) {
|
|
225
|
-
const { model: modelString = AUTO_MODEL_ID, authMode = 'byok', instructions, storage, toolDefinitions = {}, abortSignal, host, } = options;
|
|
228
|
+
const { model: modelString = AUTO_MODEL_ID, authMode = 'byok', instructions, storage, toolDefinitions: allToolDefinitions = {}, abortSignal, host, } = options;
|
|
229
|
+
// Asked inline from a UI surface: no thread history, a narrowed tool set, and
|
|
230
|
+
// the run ends when OpenBot calls `respond`.
|
|
231
|
+
const surface = readInvokeSurface(args.event);
|
|
232
|
+
const toolDefinitions = surface ? surfaceTools(allToolDefinitions) : allToolDefinitions;
|
|
226
233
|
let configuredModelString = modelString;
|
|
227
234
|
const isCreditsCloudAgent = (id) => host.isCloudSystemAgent(id ?? '') && authMode === 'credits';
|
|
228
235
|
if (!storage || abortSignal?.aborted)
|
|
@@ -231,10 +238,12 @@ export async function* runOpenbotTurn(args, options) {
|
|
|
231
238
|
state.model = configuredModelString;
|
|
232
239
|
const threadId = args.threadId || state.threadId;
|
|
233
240
|
let systemPrompt = await buildSystemPrompt(state, storage, instructions, host);
|
|
234
|
-
let events =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
241
|
+
let events = surface
|
|
242
|
+
? [args.event]
|
|
243
|
+
: await storage.getEvents({
|
|
244
|
+
channelId: state.channelId ?? '',
|
|
245
|
+
threadId,
|
|
246
|
+
});
|
|
238
247
|
const hasAttachments = eventsHaveAttachments(events);
|
|
239
248
|
const candidates = chooseTurnCandidates({ model: configuredModelString, events, authMode });
|
|
240
249
|
const readImage = async (name) => {
|
|
@@ -271,7 +280,7 @@ export async function* runOpenbotTurn(args, options) {
|
|
|
271
280
|
const missingKey = missingByokApiKeyMessage(candidates[0], { authMode });
|
|
272
281
|
if (missingKey)
|
|
273
282
|
throw new Error(missingKey);
|
|
274
|
-
const forwarded = yield* attemptFastForward(args, {
|
|
283
|
+
const forwarded = !surface && (yield* attemptFastForward(args, {
|
|
275
284
|
model: configuredModelString,
|
|
276
285
|
authMode,
|
|
277
286
|
storage,
|
|
@@ -279,7 +288,7 @@ export async function* runOpenbotTurn(args, options) {
|
|
|
279
288
|
events: events,
|
|
280
289
|
abortSignal,
|
|
281
290
|
host,
|
|
282
|
-
});
|
|
291
|
+
}));
|
|
283
292
|
if (abortSignal?.aborted)
|
|
284
293
|
return;
|
|
285
294
|
if (forwarded) {
|
|
@@ -318,7 +327,7 @@ export async function* runOpenbotTurn(args, options) {
|
|
|
318
327
|
tools,
|
|
319
328
|
abortSignal,
|
|
320
329
|
reasoning: reasoningForModel(candidate),
|
|
321
|
-
stopWhen: stepCountIs(20),
|
|
330
|
+
stopWhen: surface ? [stepCountIs(8), hasToolCall(RESPOND_TOOL)] : stepCountIs(20),
|
|
322
331
|
allowSystemInMessages: true,
|
|
323
332
|
});
|
|
324
333
|
for await (const part of result.fullStream) {
|
package/dist/surface.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const RESPOND_TOOL = 'respond';
|
|
3
|
+
/**
|
|
4
|
+
* Tools OpenBot keeps when asked inline from a UI surface. Anything that writes
|
|
5
|
+
* to a thread, a Space, or todos stays out: there is no thread, and the human
|
|
6
|
+
* did not ask for a job.
|
|
7
|
+
*/
|
|
8
|
+
const SURFACE_TOOL_ALLOWLIST = new Set(['ask_agent', 'recall', RESPOND_TOOL]);
|
|
9
|
+
/** The surface an invoke came from, when it did not come from a thread. */
|
|
10
|
+
export function readInvokeSurface(event) {
|
|
11
|
+
const raw = event?.meta?.surface;
|
|
12
|
+
if (!raw || typeof raw.id !== 'string' || !raw.id)
|
|
13
|
+
return undefined;
|
|
14
|
+
return {
|
|
15
|
+
id: raw.id,
|
|
16
|
+
label: typeof raw.label === 'string' && raw.label ? raw.label : raw.id,
|
|
17
|
+
intent: typeof raw.intent === 'string' ? raw.intent : '',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const respondTool = {
|
|
21
|
+
description: 'Give the surface the value it asked for. Call exactly once, as your final action; the text is shown to the human as-is.',
|
|
22
|
+
inputSchema: z.object({
|
|
23
|
+
text: z.string().describe('The value itself, ready to use. No preamble or quotes.'),
|
|
24
|
+
}),
|
|
25
|
+
execute: () => 'Delivered.',
|
|
26
|
+
};
|
|
27
|
+
/** Narrows the coordinator's tools to the surface allowlist and adds `respond`. */
|
|
28
|
+
export function surfaceTools(tools) {
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [name, def] of Object.entries(tools)) {
|
|
31
|
+
if (SURFACE_TOOL_ALLOWLIST.has(name))
|
|
32
|
+
out[name] = def;
|
|
33
|
+
}
|
|
34
|
+
out[RESPOND_TOOL] = respondTool;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/** System-prompt section that switches OpenBot from thread mode to inline mode. */
|
|
38
|
+
export function surfaceSection(surface) {
|
|
39
|
+
return [
|
|
40
|
+
'## SURFACE',
|
|
41
|
+
`- You were asked inline from ${surface.label}, not in a thread. There is no job, thread status, title, or todo list here.`,
|
|
42
|
+
`- Task: ${surface.intent}`,
|
|
43
|
+
'- The context the surface sent is in the human message. Do this yourself — it is a small, direct ask — and use `ask_agent` only when the answer needs something you cannot see.',
|
|
44
|
+
`- Finish by calling \`${RESPOND_TOOL}\` once with the result. Do not write anything else.`,
|
|
45
|
+
].join('\n');
|
|
46
|
+
}
|
package/dist/tools/storage.js
CHANGED
|
@@ -135,13 +135,15 @@ export const storageTools = {
|
|
|
135
135
|
spec: channelSpec,
|
|
136
136
|
initialState: mergedInitial,
|
|
137
137
|
});
|
|
138
|
-
|
|
138
|
+
// The chat already renders the widget as "Created <name> space" with a link,
|
|
139
|
+
// so the model is told not to confirm the creation a second time.
|
|
140
|
+
const output = `Created #${rawChannelId}. The human already sees "Created ${displayName} space" with a link to it in the chat, so do not announce or restate the creation; only reply with a next step if there is one.`;
|
|
139
141
|
await emitCreateChannelWidget(ctx, {
|
|
140
142
|
widgetId,
|
|
141
143
|
channelId: rawChannelId,
|
|
142
144
|
displayName: displayName,
|
|
143
145
|
channelUrl,
|
|
144
|
-
body:
|
|
146
|
+
body: `Created #${rawChannelId}.`,
|
|
145
147
|
state: 'submitted',
|
|
146
148
|
});
|
|
147
149
|
return { success: true, channelId: rawChannelId, channelUrl, output };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetopenbot/openbot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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.72",
|
|
26
26
|
"ai": "^7.0.111",
|
|
27
27
|
"zod": "^4.3.5",
|
|
28
|
-
"@meetopenbot/plugin-sdk": "^1.
|
|
28
|
+
"@meetopenbot/plugin-sdk": "^1.4.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^20.10.1",
|