openbot 0.4.6 → 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/cli.js CHANGED
@@ -16,7 +16,7 @@ function checkNodeVersion() {
16
16
  }
17
17
  }
18
18
  checkNodeVersion();
19
- program.name('openbot').description('OpenBot CLI').version('0.4.6');
19
+ program.name('openbot').description('OpenBot CLI').version('0.4.7');
20
20
  program
21
21
  .command('start')
22
22
  .description('Start the OpenBot harness')
@@ -2,6 +2,17 @@ import { ORCHESTRATOR_AGENT_ID } from './agent-ids.js';
2
2
  import { storageService } from '../plugins/storage/service.js';
3
3
  /** Thread `state.json` key for the sticky responding agent id. */
4
4
  export const THREAD_RESPONDING_AGENT_ID_KEY = 'respondingAgentId';
5
+ /** Publish events that continue a pending UI interaction rather than a new user turn. */
6
+ export const CONTINUATION_EVENT_TYPES = new Set(['client:ui:widget:response']);
7
+ const readMetaAgentId = (meta) => {
8
+ if (!meta || typeof meta !== 'object')
9
+ return undefined;
10
+ const value = meta.agentId;
11
+ if (typeof value !== 'string')
12
+ return undefined;
13
+ const trimmed = value.trim();
14
+ return trimmed || undefined;
15
+ };
5
16
  const readBoundAgentId = (state) => {
6
17
  if (!state || typeof state !== 'object')
7
18
  return undefined;
@@ -46,3 +57,24 @@ export async function resolveRespondingAgentId(options) {
46
57
  });
47
58
  return { agentId: fallback, bound: true, overridden: false };
48
59
  }
60
+ /**
61
+ * Resolves the agent that should handle a publish event.
62
+ * UI continuations route to the widget origin agent; everything else uses sticky thread binding.
63
+ */
64
+ export async function resolvePublishTargetAgentId(options) {
65
+ const { eventType, channelId, threadId, requestedAgentId, eventMeta, bindIfUnbound } = options;
66
+ if (CONTINUATION_EVENT_TYPES.has(eventType)) {
67
+ const originAgentId = readMetaAgentId(eventMeta) || requestedAgentId?.trim();
68
+ if (!originAgentId) {
69
+ return { error: 'agentId_required' };
70
+ }
71
+ return { agentId: originAgentId };
72
+ }
73
+ const resolved = await resolveRespondingAgentId({
74
+ channelId,
75
+ threadId,
76
+ requestedAgentId,
77
+ bindIfUnbound,
78
+ });
79
+ return { agentId: resolved.agentId };
80
+ }
@@ -16,7 +16,7 @@ import { storageService } from '../plugins/storage/service.js';
16
16
  import { buildWorkspaceFileUrl, getPublicBaseUrl, openChannelFileStream, } from '../plugins/storage/files.js';
17
17
  import { ensureEventId, openBotEventFromQuery } from './utils.js';
18
18
  import { abortRegistry, abortKey } from '../services/abort.js';
19
- import { resolveRespondingAgentId } from './responding-agent.js';
19
+ import { resolvePublishTargetAgentId, resolveRespondingAgentId } from './responding-agent.js';
20
20
  import { DEFAULT_UNCATEGORIZED_SPEC, UNCATEGORIZED_CHANNEL_ID, } from './channel-ids.js';
21
21
  export async function startServer(options = {}) {
22
22
  const publishEventSchema = z
@@ -457,15 +457,21 @@ export async function startServer(options = {}) {
457
457
  });
458
458
  }
459
459
  const bindIfUnbound = event.type === 'agent:invoke';
460
- const resolved = await resolveRespondingAgentId({
460
+ const target = await resolvePublishTargetAgentId({
461
+ eventType: event.type,
461
462
  channelId,
462
463
  threadId,
463
464
  requestedAgentId: agentId,
465
+ eventMeta: event.meta,
464
466
  bindIfUnbound,
465
467
  });
468
+ if ('error' in target) {
469
+ res.status(400).json({ error: 'agentId is required for widget response' });
470
+ return;
471
+ }
466
472
  await runAgent({
467
473
  runId,
468
- agentId: resolved.agentId,
474
+ agentId: target.agentId,
469
475
  event,
470
476
  channelId,
471
477
  threadId,
@@ -45,7 +45,8 @@ export const openbotPlugin = {
45
45
  ...memoryPlugin.toolDefinitions,
46
46
  ...storagePlugin.toolDefinitions,
47
47
  ...delegationPlugin.toolDefinitions,
48
- ...uiPlugin.toolDefinitions,
48
+ // this is the capability to render UI widgets to the user. We dont need it for now.
49
+ // ...uiPlugin.toolDefinitions,
49
50
  },
50
51
  factory: (context) => (builder) => {
51
52
  const { config, storage, tools, abortSignal } = context;
@@ -15,7 +15,7 @@ export const OPENBOT_SYSTEM_PROMPT = [
15
15
  '- **Bash Tool Usage**: You should use the `bash` tool very rarely. Only use it when the user explicitly requests a command to be run or when it is absolutely necessary for a task that no other agent can handle.',
16
16
  '- **Context Awareness**: Use the provided ENVIRONMENT, CHANNEL SPECIFICATION, and MEMORIES to maintain continuity. Do not ask for information already present in these sections.',
17
17
  '- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.',
18
- '- **Structured Interaction**: Use the `render_widget` tool to collect information via forms, offer choices, or display lists. This is preferred over asking multiple separate questions in plain text.',
18
+ '- **Hub-and-Spoke**: Specialized agents cannot communicate directly; as coordinator, you must pass relevant data from one agent to another.',
19
19
  '',
20
20
  '# COMMUNICATION STYLE',
21
21
  '- Be always concise, professional, and proactive.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbot",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
package/src/app/cli.ts CHANGED
@@ -25,7 +25,7 @@ function checkNodeVersion() {
25
25
 
26
26
  checkNodeVersion();
27
27
 
28
- program.name('openbot').description('OpenBot CLI').version('0.4.6');
28
+ program.name('openbot').description('OpenBot CLI').version('0.4.7');
29
29
 
30
30
  program
31
31
  .command('start')
@@ -4,6 +4,9 @@ import { storageService } from '../plugins/storage/service.js';
4
4
  /** Thread `state.json` key for the sticky responding agent id. */
5
5
  export const THREAD_RESPONDING_AGENT_ID_KEY = 'respondingAgentId';
6
6
 
7
+ /** Publish events that continue a pending UI interaction rather than a new user turn. */
8
+ export const CONTINUATION_EVENT_TYPES = new Set(['client:ui:widget:response']);
9
+
7
10
  export type ResolveRespondingAgentOptions = {
8
11
  channelId: string;
9
12
  threadId?: string;
@@ -20,6 +23,14 @@ export type ResolveRespondingAgentResult = {
20
23
  overridden: boolean;
21
24
  };
22
25
 
26
+ const readMetaAgentId = (meta: unknown): string | undefined => {
27
+ if (!meta || typeof meta !== 'object') return undefined;
28
+ const value = (meta as Record<string, unknown>).agentId;
29
+ if (typeof value !== 'string') return undefined;
30
+ const trimmed = value.trim();
31
+ return trimmed || undefined;
32
+ };
33
+
23
34
  const readBoundAgentId = (state: unknown): string | undefined => {
24
35
  if (!state || typeof state !== 'object') return undefined;
25
36
  const value = (state as Record<string, unknown>)[THREAD_RESPONDING_AGENT_ID_KEY];
@@ -72,3 +83,38 @@ export async function resolveRespondingAgentId(
72
83
 
73
84
  return { agentId: fallback, bound: true, overridden: false };
74
85
  }
86
+
87
+ export type ResolvePublishTargetAgentOptions = {
88
+ eventType: string;
89
+ channelId: string;
90
+ threadId?: string;
91
+ requestedAgentId?: string;
92
+ eventMeta?: unknown;
93
+ bindIfUnbound?: boolean;
94
+ };
95
+
96
+ /**
97
+ * Resolves the agent that should handle a publish event.
98
+ * UI continuations route to the widget origin agent; everything else uses sticky thread binding.
99
+ */
100
+ export async function resolvePublishTargetAgentId(
101
+ options: ResolvePublishTargetAgentOptions,
102
+ ): Promise<{ agentId: string } | { error: 'agentId_required' }> {
103
+ const { eventType, channelId, threadId, requestedAgentId, eventMeta, bindIfUnbound } = options;
104
+
105
+ if (CONTINUATION_EVENT_TYPES.has(eventType)) {
106
+ const originAgentId = readMetaAgentId(eventMeta) || requestedAgentId?.trim();
107
+ if (!originAgentId) {
108
+ return { error: 'agentId_required' };
109
+ }
110
+ return { agentId: originAgentId };
111
+ }
112
+
113
+ const resolved = await resolveRespondingAgentId({
114
+ channelId,
115
+ threadId,
116
+ requestedAgentId,
117
+ bindIfUnbound,
118
+ });
119
+ return { agentId: resolved.agentId };
120
+ }
package/src/app/server.ts CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  } from '../plugins/storage/files.js';
22
22
  import { ensureEventId, openBotEventFromQuery } from './utils.js';
23
23
  import { abortRegistry, abortKey } from '../services/abort.js';
24
- import { resolveRespondingAgentId } from './responding-agent.js';
24
+ import { resolvePublishTargetAgentId, resolveRespondingAgentId } from './responding-agent.js';
25
25
  import {
26
26
  DEFAULT_UNCATEGORIZED_SPEC,
27
27
  UNCATEGORIZED_CHANNEL_ID,
@@ -561,16 +561,23 @@ export async function startServer(options: ServerOptions = {}) {
561
561
  }
562
562
 
563
563
  const bindIfUnbound = event.type === 'agent:invoke';
564
- const resolved = await resolveRespondingAgentId({
564
+ const target = await resolvePublishTargetAgentId({
565
+ eventType: event.type,
565
566
  channelId,
566
567
  threadId,
567
568
  requestedAgentId: agentId,
569
+ eventMeta: event.meta,
568
570
  bindIfUnbound,
569
571
  });
570
572
 
573
+ if ('error' in target) {
574
+ res.status(400).json({ error: 'agentId is required for widget response' });
575
+ return;
576
+ }
577
+
571
578
  await runAgent({
572
579
  runId,
573
- agentId: resolved.agentId,
580
+ agentId: target.agentId,
574
581
  event,
575
582
  channelId,
576
583
  threadId,
package/src/app/types.ts CHANGED
@@ -730,6 +730,11 @@ export type UIWidgetResponseEvent = BaseEvent & {
730
730
  values?: Record<string, unknown>;
731
731
  metadata?: Record<string, unknown>;
732
732
  };
733
+ meta?: {
734
+ agentId?: string;
735
+ threadId?: string;
736
+ [key: string]: unknown;
737
+ };
733
738
  };
734
739
 
735
740
  export type BashEvent = BaseEvent & {
@@ -49,7 +49,8 @@ export const openbotPlugin: Plugin = {
49
49
  ...memoryPlugin.toolDefinitions,
50
50
  ...storagePlugin.toolDefinitions,
51
51
  ...delegationPlugin.toolDefinitions,
52
- ...uiPlugin.toolDefinitions,
52
+ // this is the capability to render UI widgets to the user. We dont need it for now.
53
+ // ...uiPlugin.toolDefinitions,
53
54
  },
54
55
  factory: (context) => (builder) => {
55
56
  const { config, storage, tools, abortSignal } = context;
@@ -15,7 +15,7 @@ export const OPENBOT_SYSTEM_PROMPT = [
15
15
  '- **Bash Tool Usage**: You should use the `bash` tool very rarely. Only use it when the user explicitly requests a command to be run or when it is absolutely necessary for a task that no other agent can handle.',
16
16
  '- **Context Awareness**: Use the provided ENVIRONMENT, CHANNEL SPECIFICATION, and MEMORIES to maintain continuity. Do not ask for information already present in these sections.',
17
17
  '- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.',
18
- '- **Structured Interaction**: Use the `render_widget` tool to collect information via forms, offer choices, or display lists. This is preferred over asking multiple separate questions in plain text.',
18
+ '- **Hub-and-Spoke**: Specialized agents cannot communicate directly; as coordinator, you must pass relevant data from one agent to another.',
19
19
  '',
20
20
  '# COMMUNICATION STYLE',
21
21
  '- Be always concise, professional, and proactive.',