openbot 0.2.12 → 0.2.14

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.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/AGENTS.md +68 -0
  3. package/CONTRIBUTING.md +74 -0
  4. package/LICENSE +21 -0
  5. package/README.md +117 -14
  6. package/dist/agents/system.js +106 -0
  7. package/dist/app/cli.js +27 -0
  8. package/dist/app/config.js +64 -0
  9. package/dist/app/server.js +237 -0
  10. package/dist/app/utils.js +35 -0
  11. package/dist/harness/agent-harness.js +45 -0
  12. package/dist/harness/mcp.js +61 -0
  13. package/dist/harness/orchestrator.js +273 -0
  14. package/dist/harness/process.js +7 -0
  15. package/dist/plugins/ai-sdk.js +141 -0
  16. package/dist/plugins/delegation.js +52 -0
  17. package/dist/plugins/mcp.js +140 -0
  18. package/dist/plugins/storage.js +502 -0
  19. package/dist/plugins/ui.js +47 -0
  20. package/dist/registry/plugins.js +73 -0
  21. package/dist/services/storage.js +724 -0
  22. package/docs/README.md +7 -0
  23. package/docs/agents.md +83 -0
  24. package/docs/architecture.md +34 -0
  25. package/docs/plugins.md +77 -0
  26. package/logo-black.png +0 -0
  27. package/{dist/assets/logo.js → logo-black.svg} +24 -24
  28. package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
  29. package/package.json +6 -5
  30. package/src/agents/system.ts +112 -0
  31. package/src/app/cli.ts +38 -0
  32. package/src/app/config.ts +104 -0
  33. package/src/app/server.ts +284 -0
  34. package/src/app/types.ts +476 -0
  35. package/src/app/utils.ts +43 -0
  36. package/src/assets/icon.svg +1 -0
  37. package/src/harness/agent-harness.ts +58 -0
  38. package/src/harness/mcp.ts +78 -0
  39. package/src/harness/orchestrator.ts +342 -0
  40. package/src/harness/process.ts +9 -0
  41. package/src/harness/types.ts +34 -0
  42. package/src/plugins/ai-sdk.ts +197 -0
  43. package/src/plugins/delegation.ts +60 -0
  44. package/src/plugins/mcp.ts +154 -0
  45. package/src/plugins/storage.ts +725 -0
  46. package/src/plugins/ui.ts +57 -0
  47. package/src/registry/plugins.ts +85 -0
  48. package/src/services/storage.ts +957 -0
  49. package/tsconfig.json +18 -0
  50. package/dist/agents/agent-creator.js +0 -74
  51. package/dist/agents/browser-agent.js +0 -31
  52. package/dist/agents/os-agent.js +0 -32
  53. package/dist/agents/planner-agent.js +0 -32
  54. package/dist/agents/topic-agent.js +0 -46
  55. package/dist/architecture/execution-engine.js +0 -151
  56. package/dist/architecture/intent-classifier.js +0 -26
  57. package/dist/architecture/planner.js +0 -106
  58. package/dist/automation-worker.js +0 -121
  59. package/dist/automations.js +0 -52
  60. package/dist/cli.js +0 -279
  61. package/dist/config.js +0 -53
  62. package/dist/core/agents.js +0 -41
  63. package/dist/core/delegation.js +0 -230
  64. package/dist/core/manager.js +0 -96
  65. package/dist/core/plugins.js +0 -74
  66. package/dist/core/router.js +0 -191
  67. package/dist/handlers/init.js +0 -29
  68. package/dist/handlers/session-change.js +0 -21
  69. package/dist/handlers/settings.js +0 -47
  70. package/dist/handlers/tab-change.js +0 -14
  71. package/dist/installers.js +0 -156
  72. package/dist/marketplace.js +0 -80
  73. package/dist/model-catalog.js +0 -132
  74. package/dist/model-defaults.js +0 -25
  75. package/dist/models.js +0 -47
  76. package/dist/open-bot.js +0 -51
  77. package/dist/orchestrator/direct-invocation.js +0 -13
  78. package/dist/orchestrator/events.js +0 -36
  79. package/dist/orchestrator/state.js +0 -54
  80. package/dist/orchestrator.js +0 -422
  81. package/dist/plugins/agent/index.js +0 -81
  82. package/dist/plugins/approval/index.js +0 -100
  83. package/dist/plugins/brain/identity.js +0 -77
  84. package/dist/plugins/brain/index.js +0 -204
  85. package/dist/plugins/brain/memory.js +0 -120
  86. package/dist/plugins/brain/prompt.js +0 -46
  87. package/dist/plugins/brain/types.js +0 -45
  88. package/dist/plugins/brain/ui.js +0 -7
  89. package/dist/plugins/browser/index.js +0 -629
  90. package/dist/plugins/browser/ui.js +0 -13
  91. package/dist/plugins/file-system/index.js +0 -171
  92. package/dist/plugins/file-system/ui.js +0 -6
  93. package/dist/plugins/llm/context-budget.js +0 -139
  94. package/dist/plugins/llm/context-shaping.js +0 -177
  95. package/dist/plugins/llm/index.js +0 -380
  96. package/dist/plugins/memory/index.js +0 -220
  97. package/dist/plugins/memory/memory.js +0 -122
  98. package/dist/plugins/memory/prompt.js +0 -55
  99. package/dist/plugins/memory/types.js +0 -45
  100. package/dist/plugins/meta-agent/index.js +0 -570
  101. package/dist/plugins/meta-agent/ui.js +0 -11
  102. package/dist/plugins/shell/index.js +0 -100
  103. package/dist/plugins/shell/ui.js +0 -6
  104. package/dist/plugins/skills/index.js +0 -286
  105. package/dist/plugins/skills/types.js +0 -50
  106. package/dist/plugins/skills/ui.js +0 -12
  107. package/dist/registry/agent-registry.js +0 -35
  108. package/dist/registry/index.js +0 -2
  109. package/dist/registry/plugin-loader.js +0 -499
  110. package/dist/registry/plugin-registry.js +0 -44
  111. package/dist/registry/ts-agent-loader.js +0 -82
  112. package/dist/registry/yaml-agent-loader.js +0 -246
  113. package/dist/runtime/execution-trace.js +0 -41
  114. package/dist/runtime/intent-routing.js +0 -26
  115. package/dist/runtime/openbot-runtime.js +0 -354
  116. package/dist/server.js +0 -890
  117. package/dist/session.js +0 -179
  118. package/dist/ui/block.js +0 -12
  119. package/dist/ui/header.js +0 -52
  120. package/dist/ui/layout.js +0 -26
  121. package/dist/ui/navigation.js +0 -15
  122. package/dist/ui/settings.js +0 -106
  123. package/dist/ui/skills.js +0 -7
  124. package/dist/ui/thread.js +0 -16
  125. package/dist/ui/widgets/action-list.js +0 -2
  126. package/dist/ui/widgets/approval-card.js +0 -9
  127. package/dist/ui/widgets/code-snippet.js +0 -2
  128. package/dist/ui/widgets/data-block.js +0 -2
  129. package/dist/ui/widgets/data-table.js +0 -2
  130. package/dist/ui/widgets/delegation.js +0 -29
  131. package/dist/ui/widgets/empty-state.js +0 -2
  132. package/dist/ui/widgets/index.js +0 -23
  133. package/dist/ui/widgets/inquiry.js +0 -7
  134. package/dist/ui/widgets/key-value.js +0 -2
  135. package/dist/ui/widgets/progress-step.js +0 -2
  136. package/dist/ui/widgets/resource-card.js +0 -2
  137. package/dist/ui/widgets/status.js +0 -2
  138. package/dist/ui/widgets/todo-list.js +0 -2
  139. package/dist/version.js +0 -62
  140. /package/dist/{types.js → app/types.js} +0 -0
  141. /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
@@ -1,246 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
3
- import matter from "gray-matter";
4
- import { llmPlugin } from "../plugins/llm/index.js";
5
- import { PluginRegistry } from "./plugin-registry.js";
6
- import { createModel } from "../models.js";
7
- import { loadPluginsFromDir } from "./plugin-loader.js";
8
- import { resolvePath, DEFAULT_AGENT_MD } from "../config.js";
9
- /**
10
- * Recursively resolve tilde paths in a configuration object.
11
- */
12
- function resolveConfigPaths(config) {
13
- if (typeof config === "string") {
14
- return resolvePath(config);
15
- }
16
- if (Array.isArray(config)) {
17
- return config.map(resolveConfigPaths);
18
- }
19
- if (config !== null && typeof config === "object") {
20
- const resolved = {};
21
- for (const [key, value] of Object.entries(config)) {
22
- resolved[key] = resolveConfigPaths(value);
23
- }
24
- return resolved;
25
- }
26
- return config;
27
- }
28
- /**
29
- * Read and parse an agent configuration from AGENT.md with frontmatter.
30
- */
31
- export async function readAgentConfig(agentDir) {
32
- const mdPath = path.join(agentDir, "AGENT.md");
33
- const folderName = path.basename(agentDir);
34
- let mdContent = "";
35
- try {
36
- mdContent = await fs.readFile(mdPath, "utf-8");
37
- }
38
- catch {
39
- // Fallback to a default template if AGENT.md is missing
40
- mdContent = DEFAULT_AGENT_MD.replace("name: Agent", `name: ${folderName}`);
41
- }
42
- const parsed = matter(mdContent);
43
- const config = (parsed.data || {});
44
- return {
45
- name: config.name || folderName,
46
- description: config.description || `The ${folderName} agent`,
47
- model: config.model,
48
- plugins: config.plugins || [],
49
- systemPrompt: parsed.content.trim() || "",
50
- subscribe: config.subscribe,
51
- };
52
- }
53
- /**
54
- * Discover YAML-defined agents from a directory without loading plugins.
55
- *
56
- * @param agentsDir Absolute path to the agents directory (e.g. ~/.openbot/agents)
57
- * @returns Array of agent metadata
58
- */
59
- export async function listYamlAgents(agentsDir) {
60
- const agents = [];
61
- const seenNames = new Set();
62
- try {
63
- const entries = await fs.readdir(agentsDir, { withFileTypes: true });
64
- for (const entry of entries) {
65
- if (!entry.isDirectory())
66
- continue;
67
- if (entry.name.startsWith(".") || entry.name.startsWith("_"))
68
- continue;
69
- const agentDir = path.join(agentsDir, entry.name);
70
- // Check if it's a TS agent (has package.json but NO AGENT.md)
71
- const hasMd = await fs.access(path.join(agentDir, "AGENT.md")).then(() => true).catch(() => false);
72
- const hasPkg = await fs.access(path.join(agentDir, "package.json")).then(() => true).catch(() => false);
73
- if (!hasMd && hasPkg) {
74
- let description = "TypeScript Agent (editable via files only)";
75
- let name = entry.name;
76
- try {
77
- const pkg = JSON.parse(await fs.readFile(path.join(agentDir, "package.json"), "utf-8"));
78
- if (pkg.description)
79
- description = pkg.description;
80
- if (pkg.name)
81
- name = pkg.name;
82
- }
83
- catch {
84
- // Ignore
85
- }
86
- if (!seenNames.has(name)) {
87
- agents.push({
88
- name,
89
- description,
90
- folder: agentDir,
91
- isTs: true,
92
- });
93
- seenNames.add(name);
94
- }
95
- continue;
96
- }
97
- try {
98
- const config = await readAgentConfig(agentDir);
99
- if (config.name && config.description && !seenNames.has(config.name)) {
100
- agents.push({
101
- name: config.name,
102
- description: config.description,
103
- folder: agentDir,
104
- });
105
- seenNames.add(config.name);
106
- }
107
- }
108
- catch {
109
- // Skip invalid agents
110
- }
111
- }
112
- }
113
- catch {
114
- // Agents directory doesn't exist
115
- }
116
- return agents;
117
- }
118
- /**
119
- * Discover and load YAML-defined agents from a directory.
120
- *
121
- * Scans `agentsDir` for subdirectories containing an `AGENT.md` file,
122
- * parses each one, and composes a Melony plugin from the referenced plugins.
123
- *
124
- * @param agentsDir Absolute path to the agents directory (e.g. ~/.openbot/agents)
125
- * @param pluginRegistry Registry of available plugins
126
- * @param defaultModel Language model to use for agent LLMs if not specified in YAML
127
- * @param options Optional API keys for creating specific models
128
- * @returns Array of discovered agent entries ready for registration
129
- */
130
- export async function discoverYamlAgents(agentsDir, pluginRegistry, defaultModel, options) {
131
- const agents = [];
132
- const seenNames = new Set();
133
- // Ensure the agents directory exists
134
- try {
135
- await fs.mkdir(agentsDir, { recursive: true });
136
- }
137
- catch {
138
- // Best effort
139
- }
140
- try {
141
- const entries = await fs.readdir(agentsDir, { withFileTypes: true });
142
- for (const entry of entries) {
143
- if (!entry.isDirectory())
144
- continue;
145
- if (entry.name.startsWith(".") || entry.name.startsWith("_"))
146
- continue;
147
- const agentDir = path.join(agentsDir, entry.name);
148
- // Skip TS agents (they don't need AGENT.md)
149
- const hasMd = await fs.access(path.join(agentDir, "AGENT.md")).then(() => true).catch(() => false);
150
- const hasPkg = await fs.access(path.join(agentDir, "package.json")).then(() => true).catch(() => false);
151
- if (!hasMd && hasPkg)
152
- continue;
153
- try {
154
- const config = await readAgentConfig(agentDir);
155
- // Validate required fields and avoid duplicates
156
- if (!config.name || !config.description || seenNames.has(config.name)) {
157
- continue;
158
- }
159
- seenNames.add(config.name);
160
- const agentModel = config.model
161
- ? createModel({ ...options, model: config.model })
162
- : defaultModel;
163
- // 1. Load local plugins from agents/<name>/plugins/
164
- const localPluginsDir = path.join(agentDir, "plugins");
165
- const localPlugins = await loadPluginsFromDir(localPluginsDir);
166
- // 2. Create a scoped registry for this agent: global + local
167
- const scopedRegistry = new PluginRegistry();
168
- // Add all global plugins
169
- for (const p of pluginRegistry.getAll()) {
170
- scopedRegistry.register(p);
171
- }
172
- // Add local plugins (overwriting globals if names conflict)
173
- for (const p of localPlugins) {
174
- scopedRegistry.register(p);
175
- }
176
- // Initialize AGENT.md if it doesn't exist (using the template)
177
- const agentMdPath = path.join(agentDir, "AGENT.md");
178
- try {
179
- await fs.access(agentMdPath);
180
- }
181
- catch {
182
- const content = DEFAULT_AGENT_MD.replace("name: Agent", `name: ${config.name}`);
183
- await fs.writeFile(agentMdPath, content, "utf-8");
184
- console.log(`[agents] Initialized ${config.name}/AGENT.md`);
185
- }
186
- const { plugin, toolDefinitions } = composeAgentFromConfig(config, scopedRegistry, agentModel);
187
- agents.push({
188
- name: config.name,
189
- description: config.description,
190
- plugin,
191
- capabilities: Object.fromEntries(Object.entries(toolDefinitions).map(([name, def]) => [
192
- name,
193
- def.description,
194
- ])),
195
- subscribe: config.subscribe,
196
- });
197
- console.log(`[agents] Loaded: ${config.name} — ${config.description}${config.model ? ` (model: ${config.model})` : ""}`);
198
- }
199
- catch (err) {
200
- // Skip invalid agents
201
- if (err.code !== 'ENOENT') {
202
- console.warn(`[agents] Error loading "${entry.name}":`, err);
203
- }
204
- }
205
- }
206
- }
207
- catch {
208
- // Agents directory doesn't exist or can't be read — that's fine
209
- }
210
- return agents;
211
- }
212
- /**
213
- * Compose a Melony plugin from an agent configuration.
214
- *
215
- * Resolves each plugin name against the registry, collects their tool definitions,
216
- * and wires them with an agent-scoped LLM plugin.
217
- */
218
- function composeAgentFromConfig(config, pluginRegistry, model) {
219
- const allToolDefinitions = {};
220
- const pluginFactories = [];
221
- for (const pluginItem of config.plugins) {
222
- const isString = typeof pluginItem === "string";
223
- const pluginName = isString ? pluginItem : pluginItem.name;
224
- const pluginConfig = isString ? {} : (pluginItem.config || {});
225
- const resolvedConfig = resolveConfigPaths(pluginConfig);
226
- const entry = pluginRegistry.get(pluginName);
227
- if (!entry) {
228
- console.warn(`[agents] "${config.name}": plugin "${pluginName}" not found in registry — skipping`);
229
- continue;
230
- }
231
- pluginFactories.push({ factory: entry.factory, config: resolvedConfig });
232
- Object.assign(allToolDefinitions, entry.toolDefinitions);
233
- }
234
- const plugin = (builder) => {
235
- for (const { factory, config: resolvedConfig } of pluginFactories) {
236
- builder.use(factory({ ...resolvedConfig, model }));
237
- }
238
- // Wire up the LLM with agent-scoped event channels
239
- builder.use(llmPlugin({
240
- model,
241
- system: config.systemPrompt,
242
- toolDefinitions: allToolDefinitions,
243
- }));
244
- };
245
- return { plugin, toolDefinitions: allToolDefinitions };
246
- }
@@ -1,41 +0,0 @@
1
- function nowIso() {
2
- return new Date().toISOString();
3
- }
4
- export function createTraceId(runId) {
5
- return `trace_${runId}_${Date.now()}`;
6
- }
7
- export function hasPendingApprovals(state) {
8
- const agentStates = state.agentStates || {};
9
- return Object.values(agentStates).some((agentState) => !!agentState.pendingApprovals &&
10
- Object.keys(agentState.pendingApprovals).length > 0);
11
- }
12
- export function setExecutionState(state, patch) {
13
- const hasCurrentStepId = Object.prototype.hasOwnProperty.call(patch, "currentStepId");
14
- const hasError = Object.prototype.hasOwnProperty.call(patch, "error");
15
- const hasIntentType = Object.prototype.hasOwnProperty.call(patch, "intentType");
16
- const hasPlanSteps = Object.prototype.hasOwnProperty.call(patch, "planSteps");
17
- const next = {
18
- traceId: patch.traceId ?? state.execution?.traceId ?? `trace_${Date.now()}`,
19
- state: patch.state ?? state.execution?.state ?? "RECEIVED",
20
- currentStepId: hasCurrentStepId ? patch.currentStepId : state.execution?.currentStepId,
21
- error: hasError ? patch.error : state.execution?.error,
22
- intentType: hasIntentType ? patch.intentType : state.execution?.intentType,
23
- planSteps: hasPlanSteps ? patch.planSteps : state.execution?.planSteps,
24
- updatedAt: nowIso(),
25
- };
26
- state.execution = next;
27
- return next;
28
- }
29
- export function executionStateEvent(trace) {
30
- return {
31
- type: "execution:state",
32
- data: {
33
- traceId: trace?.traceId,
34
- state: trace?.state,
35
- currentStepId: trace?.currentStepId,
36
- error: trace?.error,
37
- intentType: trace?.intentType,
38
- planSteps: trace?.planSteps,
39
- },
40
- };
41
- }
@@ -1,26 +0,0 @@
1
- const TASK_HINT_REGEX = /\b(create|build|write|fix|update|implement|run|execute|open|edit|delete|list|plan|analyze|research|refactor|install|configure|debug|deploy)\b/i;
2
- export function parseDirectAgent(content, knownAgents) {
3
- const raw = content.trim();
4
- if (!raw || (!raw.startsWith("/") && !raw.startsWith("@")))
5
- return null;
6
- const firstSpace = raw.indexOf(" ");
7
- const candidate = firstSpace === -1 ? raw.slice(1) : raw.slice(1, firstSpace);
8
- if (!knownAgents.has(candidate))
9
- return null;
10
- const task = firstSpace === -1 ? "" : raw.slice(firstSpace + 1).trim();
11
- return { agentName: candidate, task };
12
- }
13
- export function classifyIntent(content, knownAgents) {
14
- const raw = content.trim();
15
- const direct = parseDirectAgent(raw, knownAgents);
16
- if (direct) {
17
- return { type: "agent_direct", targetAgent: direct.agentName };
18
- }
19
- if (!raw) {
20
- return { type: "chat" };
21
- }
22
- if (TASK_HINT_REGEX.test(raw)) {
23
- return { type: "task" };
24
- }
25
- return { type: "chat" };
26
- }
@@ -1,354 +0,0 @@
1
- import { melony } from "melony";
2
- import { classifyIntent, parseDirectAgent } from "./intent-routing.js";
3
- import { createTraceId, executionStateEvent, hasPendingApprovals, setExecutionState, } from "./execution-trace.js";
4
- const AGENT_TEXT_TYPES = new Set(["assistant:text-delta", "assistant:text"]);
5
- const MAX_DELEGATIONS_PER_MANAGER_RUN = 6;
6
- const DIRECT_TITLE_CONTEXT_LIMIT = 20;
7
- function isAgentTextEvent(event) {
8
- return AGENT_TEXT_TYPES.has(event.type);
9
- }
10
- export class OpenBotRuntime {
11
- constructor(options) {
12
- this.managerPlugin = options.managerPlugin;
13
- this.agents = new Map(options.agents.map((a) => [a.name, a]));
14
- }
15
- buildManagerRuntime() {
16
- const builder = melony();
17
- builder.use(this.managerPlugin);
18
- builder.on("action:taskResult", async function* (event) {
19
- yield { type: "manager:result", data: event.data };
20
- });
21
- return builder.build();
22
- }
23
- buildAgentRuntime(agent) {
24
- const builder = melony();
25
- builder.use(agent.plugin);
26
- const name = agent.name;
27
- builder.on("action:taskResult", async function* (event) {
28
- yield { type: `agent:${name}:result`, data: event.data };
29
- });
30
- return builder.build();
31
- }
32
- getAgentState(agentName, sessionState) {
33
- if (!sessionState.agentStates)
34
- sessionState.agentStates = {};
35
- if (!sessionState.agentStates[agentName]) {
36
- sessionState.agentStates[agentName] = {};
37
- }
38
- const agentState = sessionState.agentStates[agentName];
39
- if (!agentState.cwd)
40
- agentState.cwd = sessionState.cwd;
41
- return agentState;
42
- }
43
- appendSessionMessage(sessionState, message) {
44
- if (!sessionState.messages)
45
- sessionState.messages = [];
46
- sessionState.messages.push(message);
47
- if (sessionState.messages.length > DIRECT_TITLE_CONTEXT_LIMIT) {
48
- sessionState.messages = sessionState.messages.slice(-DIRECT_TITLE_CONTEXT_LIMIT);
49
- }
50
- }
51
- async *triggerTopicRefresh(sessionState, runId) {
52
- const runtime = this.buildManagerRuntime();
53
- for await (const yielded of runtime.run({
54
- type: "manager:completion",
55
- data: { content: "" },
56
- }, { state: sessionState, runId })) {
57
- yield yielded;
58
- }
59
- }
60
- estimatePlanSteps(planText) {
61
- const trimmed = planText.trim();
62
- if (!trimmed)
63
- return undefined;
64
- try {
65
- const parsed = JSON.parse(trimmed);
66
- if (Array.isArray(parsed?.steps))
67
- return parsed.steps.length;
68
- }
69
- catch {
70
- // Best-effort parse only.
71
- }
72
- return undefined;
73
- }
74
- buildManagerTaskInput(userIntent, planText) {
75
- return `User intent:\n${userIntent}\n\nPlanner output (treat as proposed execution plan):\n${planText}\n\nExecute this pragmatically. Delegate concrete subtasks to relevant specialist agents when needed. Keep user-facing updates concise.`;
76
- }
77
- async *runPlannerAgent(content, attachments, sessionState, runId) {
78
- const plannerName = "planner-agent";
79
- if (!this.agents.has(plannerName)) {
80
- return "";
81
- }
82
- let plannerOutput = "";
83
- for await (const yielded of this.runAgentInternal(plannerName, content, attachments, sessionState, runId)) {
84
- if (yielded.type === `agent:${plannerName}:output`) {
85
- plannerOutput = yielded.data.content || "";
86
- }
87
- if (!isAgentTextEvent(yielded) && yielded.type !== `agent:${plannerName}:output`) {
88
- yield yielded;
89
- }
90
- }
91
- return plannerOutput;
92
- }
93
- async *run(event, options) {
94
- const { state, runId = `run_${Date.now()}` } = options;
95
- yield event;
96
- if (event.type === "action:approve" || event.type === "action:deny") {
97
- const trace = setExecutionState(state, {
98
- state: "EXECUTING",
99
- error: undefined,
100
- });
101
- yield executionStateEvent(trace);
102
- yield* this.routeApproval(event, state, runId);
103
- return;
104
- }
105
- if (event.type === "user:text" || event.type === "user:multimodal") {
106
- const rawContent = event.data.content;
107
- const content = typeof rawContent === "string" ? rawContent.trim() : "";
108
- const attachments = Array.isArray(event.data.attachments)
109
- ? event.data.attachments
110
- : undefined;
111
- const knownAgents = new Set(this.agents.keys());
112
- const direct = parseDirectAgent(content, knownAgents);
113
- const intent = classifyIntent(content, knownAgents);
114
- let trace = setExecutionState(state, {
115
- traceId: createTraceId(runId),
116
- state: "RECEIVED",
117
- intentType: intent.type,
118
- error: undefined,
119
- currentStepId: undefined,
120
- planSteps: undefined,
121
- });
122
- yield executionStateEvent(trace);
123
- try {
124
- if (direct) {
125
- this.appendSessionMessage(state, {
126
- role: "user",
127
- content,
128
- attachments,
129
- });
130
- trace = setExecutionState(state, {
131
- state: "EXECUTING",
132
- currentStepId: `delegate:${direct.agentName}`,
133
- });
134
- yield executionStateEvent(trace);
135
- yield* this.runAgentDirect(direct.agentName, direct.task, attachments, state, runId);
136
- const finalTrace = setExecutionState(state, {
137
- state: hasPendingApprovals(state) ? "WAITING_APPROVAL" : "COMPLETED",
138
- currentStepId: hasPendingApprovals(state)
139
- ? `delegate:${direct.agentName}`
140
- : undefined,
141
- error: undefined,
142
- });
143
- yield executionStateEvent(finalTrace);
144
- return;
145
- }
146
- trace = setExecutionState(state, {
147
- state: "EXECUTING",
148
- currentStepId: intent.type === "task" ? "planner" : "manager",
149
- });
150
- yield executionStateEvent(trace);
151
- let managerInput = content;
152
- if (intent.type === "task" && this.agents.has("planner-agent")) {
153
- const plannerResult = yield* this.runPlannerAgent(content, attachments, state, runId);
154
- if (plannerResult.trim()) {
155
- managerInput = this.buildManagerTaskInput(content, plannerResult);
156
- trace = setExecutionState(state, {
157
- currentStepId: "manager",
158
- planSteps: this.estimatePlanSteps(plannerResult),
159
- });
160
- yield executionStateEvent(trace);
161
- }
162
- }
163
- yield* this.runManagerLoop({
164
- type: "manager:input",
165
- data: { content: managerInput, attachments },
166
- }, state, runId);
167
- const waiting = hasPendingApprovals(state);
168
- const finalTrace = setExecutionState(state, {
169
- state: waiting ? "WAITING_APPROVAL" : "COMPLETED",
170
- currentStepId: waiting ? (state.execution?.currentStepId ?? "manager") : undefined,
171
- error: undefined,
172
- });
173
- yield executionStateEvent(finalTrace);
174
- return;
175
- }
176
- catch (error) {
177
- const finalTrace = setExecutionState(state, {
178
- state: "FAILED",
179
- error: error instanceof Error ? error.message : String(error),
180
- });
181
- yield executionStateEvent(finalTrace);
182
- throw error;
183
- }
184
- }
185
- yield* this.runManagerLoop(event, state, runId);
186
- }
187
- async *runManagerLoop(event, state, runId) {
188
- const runtime = this.buildManagerRuntime();
189
- const delegationSignatures = new Set();
190
- let delegationCount = 0;
191
- let nextManagerEvent = event;
192
- while (nextManagerEvent) {
193
- const managerEvent = nextManagerEvent;
194
- nextManagerEvent = undefined;
195
- for await (const yielded of runtime.run(managerEvent, { state, runId })) {
196
- if (yielded.type !== "action:delegateTask") {
197
- yield yielded;
198
- continue;
199
- }
200
- const { agent: agentName, task, attachments, toolCallId } = yielded.data;
201
- const normalizedTask = typeof task === "string"
202
- ? task.replace(/\s+/g, " ").trim().toLowerCase()
203
- : "";
204
- const signature = `${agentName}::${normalizedTask}`;
205
- if (delegationCount >= MAX_DELEGATIONS_PER_MANAGER_RUN ||
206
- (normalizedTask && delegationSignatures.has(signature))) {
207
- nextManagerEvent = {
208
- type: "manager:result",
209
- data: {
210
- action: "delegateTask",
211
- toolCallId,
212
- result: `Error: delegation loop detected for agent "${agentName}". Summarize current progress and stop delegating.`,
213
- },
214
- };
215
- break;
216
- }
217
- if (!this.agents.has(agentName)) {
218
- nextManagerEvent = {
219
- type: "manager:result",
220
- data: {
221
- action: "delegateTask",
222
- toolCallId,
223
- result: `Error: Agent "${agentName}" not found`,
224
- },
225
- };
226
- break;
227
- }
228
- delegationCount += 1;
229
- if (normalizedTask)
230
- delegationSignatures.add(signature);
231
- if (!state.pendingAgentTasks)
232
- state.pendingAgentTasks = {};
233
- state.pendingAgentTasks[agentName] = { toolCallId };
234
- let agentOutput = "";
235
- let agentCompleted = false;
236
- try {
237
- for await (const agentEvent of this.runAgentInternal(agentName, task, attachments, state, runId)) {
238
- if (agentEvent.type === `agent:${agentName}:output`) {
239
- agentOutput = agentEvent.data.content;
240
- agentCompleted = true;
241
- }
242
- if (!isAgentTextEvent(agentEvent)) {
243
- yield agentEvent;
244
- }
245
- }
246
- }
247
- catch (error) {
248
- agentOutput = `Error: ${error instanceof Error ? error.message : String(error)}`;
249
- agentCompleted = true;
250
- }
251
- if (agentCompleted) {
252
- delete state.pendingAgentTasks[agentName];
253
- nextManagerEvent = {
254
- type: "manager:result",
255
- data: {
256
- action: "delegateTask",
257
- toolCallId,
258
- result: agentOutput,
259
- },
260
- };
261
- }
262
- break;
263
- }
264
- }
265
- }
266
- async *runAgentInternal(agentName, task, attachments, sessionState, runId) {
267
- const agent = this.agents.get(agentName);
268
- const agentState = this.getAgentState(agentName, sessionState);
269
- const runtime = this.buildAgentRuntime(agent);
270
- const inputEvent = {
271
- type: `agent:${agentName}:input`,
272
- data: { content: task, attachments },
273
- };
274
- for await (const yielded of runtime.run(inputEvent, {
275
- state: agentState,
276
- runId,
277
- })) {
278
- yield yielded;
279
- }
280
- }
281
- async *runAgentDirect(agentName, task, attachments, sessionState, runId) {
282
- for await (const yielded of this.runAgentInternal(agentName, task, attachments, sessionState, runId)) {
283
- if (yielded.type === `agent:${agentName}:output`) {
284
- const content = yielded.data.content;
285
- this.appendSessionMessage(sessionState, {
286
- role: "assistant",
287
- content,
288
- });
289
- yield {
290
- type: "assistant:text",
291
- data: { content },
292
- meta: { agent: agentName },
293
- };
294
- yield* this.triggerTopicRefresh(sessionState, runId);
295
- }
296
- else {
297
- yield yielded;
298
- }
299
- }
300
- }
301
- async *routeApproval(event, state, runId) {
302
- const approvalId = event.data.id;
303
- const agentStates = state.agentStates || {};
304
- let targetAgent;
305
- for (const [name, agentState] of Object.entries(agentStates)) {
306
- if (agentState.pendingApprovals?.[approvalId]) {
307
- targetAgent = name;
308
- break;
309
- }
310
- }
311
- if (!targetAgent) {
312
- console.warn("[openbot-runtime] No agent found for approval event:", approvalId);
313
- return;
314
- }
315
- const agent = this.agents.get(targetAgent);
316
- const agentState = this.getAgentState(targetAgent, state);
317
- const runtime = this.buildAgentRuntime(agent);
318
- let agentOutput = "";
319
- let agentCompleted = false;
320
- const isDelegation = !!state.pendingAgentTasks?.[targetAgent];
321
- for await (const yielded of runtime.run(event, {
322
- state: agentState,
323
- runId,
324
- })) {
325
- if (yielded.type === `agent:${targetAgent}:output`) {
326
- agentOutput = yielded.data.content;
327
- agentCompleted = true;
328
- }
329
- if (isDelegation && isAgentTextEvent(yielded)) {
330
- continue;
331
- }
332
- yield yielded;
333
- }
334
- if (agentCompleted && state.pendingAgentTasks?.[targetAgent]) {
335
- const { toolCallId } = state.pendingAgentTasks[targetAgent];
336
- delete state.pendingAgentTasks[targetAgent];
337
- yield* this.runManagerLoop({
338
- type: "manager:result",
339
- data: {
340
- action: "delegateTask",
341
- toolCallId,
342
- result: agentOutput,
343
- },
344
- }, state, runId);
345
- }
346
- const waiting = hasPendingApprovals(state);
347
- const trace = setExecutionState(state, {
348
- state: waiting ? "WAITING_APPROVAL" : "COMPLETED",
349
- currentStepId: waiting ? state.execution?.currentStepId : undefined,
350
- error: undefined,
351
- });
352
- yield executionStateEvent(trace);
353
- }
354
- }