@meetopenbot/openbot 1.2.20 → 1.3.0

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 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
- const threadState = state.threadDetails?.state;
22
- sections.push(...threadNamingHint(threadState));
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 = await storage.getEvents({
235
- channelId: state.channelId ?? '',
236
- threadId,
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) {
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/openbot",
3
- "version": "1.2.20",
3
+ "version": "1.3.0",
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.3.0"
28
+ "@meetopenbot/plugin-sdk": "^1.4.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.10.1",