openbot 0.2.12 → 0.2.13
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/.prettierrc +8 -0
- package/AGENTS.md +68 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE +21 -0
- package/README.md +117 -14
- package/dist/agents/system.js +106 -0
- package/dist/app/cli.js +27 -0
- package/dist/app/config.js +64 -0
- package/dist/app/server.js +237 -0
- package/dist/app/utils.js +35 -0
- package/dist/harness/agent-harness.js +45 -0
- package/dist/harness/mcp.js +61 -0
- package/dist/harness/orchestrator.js +273 -0
- package/dist/harness/process.js +7 -0
- package/dist/plugins/ai-sdk.js +141 -0
- package/dist/plugins/delegation.js +52 -0
- package/dist/plugins/mcp.js +140 -0
- package/dist/plugins/storage.js +502 -0
- package/dist/plugins/ui.js +47 -0
- package/dist/registry/plugins.js +73 -0
- package/dist/services/storage.js +724 -0
- package/docs/README.md +7 -0
- package/docs/agents.md +83 -0
- package/docs/architecture.md +34 -0
- package/docs/plugins.md +77 -0
- package/logo-black.png +0 -0
- package/{dist/assets/logo.js → logo-black.svg} +24 -24
- package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
- package/package.json +10 -9
- package/src/agents/system.ts +112 -0
- package/src/app/cli.ts +38 -0
- package/src/app/config.ts +104 -0
- package/src/app/server.ts +284 -0
- package/src/app/types.ts +476 -0
- package/src/app/utils.ts +43 -0
- package/src/assets/icon.svg +1 -0
- package/src/harness/agent-harness.ts +58 -0
- package/src/harness/mcp.ts +78 -0
- package/src/harness/orchestrator.ts +342 -0
- package/src/harness/process.ts +9 -0
- package/src/harness/types.ts +34 -0
- package/src/plugins/ai-sdk.ts +197 -0
- package/src/plugins/delegation.ts +60 -0
- package/src/plugins/mcp.ts +154 -0
- package/src/plugins/storage.ts +725 -0
- package/src/plugins/ui.ts +57 -0
- package/src/registry/plugins.ts +85 -0
- package/src/services/storage.ts +957 -0
- package/tsconfig.json +18 -0
- package/dist/agents/agent-creator.js +0 -74
- package/dist/agents/browser-agent.js +0 -31
- package/dist/agents/os-agent.js +0 -32
- package/dist/agents/planner-agent.js +0 -32
- package/dist/agents/topic-agent.js +0 -46
- package/dist/architecture/execution-engine.js +0 -151
- package/dist/architecture/intent-classifier.js +0 -26
- package/dist/architecture/planner.js +0 -106
- package/dist/automation-worker.js +0 -121
- package/dist/automations.js +0 -52
- package/dist/cli.js +0 -279
- package/dist/config.js +0 -53
- package/dist/core/agents.js +0 -41
- package/dist/core/delegation.js +0 -230
- package/dist/core/manager.js +0 -96
- package/dist/core/plugins.js +0 -74
- package/dist/core/router.js +0 -191
- package/dist/handlers/init.js +0 -29
- package/dist/handlers/session-change.js +0 -21
- package/dist/handlers/settings.js +0 -47
- package/dist/handlers/tab-change.js +0 -14
- package/dist/installers.js +0 -156
- package/dist/marketplace.js +0 -80
- package/dist/model-catalog.js +0 -132
- package/dist/model-defaults.js +0 -25
- package/dist/models.js +0 -47
- package/dist/open-bot.js +0 -51
- package/dist/orchestrator/direct-invocation.js +0 -13
- package/dist/orchestrator/events.js +0 -36
- package/dist/orchestrator/state.js +0 -54
- package/dist/orchestrator.js +0 -422
- package/dist/plugins/agent/index.js +0 -81
- package/dist/plugins/approval/index.js +0 -100
- package/dist/plugins/brain/identity.js +0 -77
- package/dist/plugins/brain/index.js +0 -204
- package/dist/plugins/brain/memory.js +0 -120
- package/dist/plugins/brain/prompt.js +0 -46
- package/dist/plugins/brain/types.js +0 -45
- package/dist/plugins/brain/ui.js +0 -7
- package/dist/plugins/browser/index.js +0 -629
- package/dist/plugins/browser/ui.js +0 -13
- package/dist/plugins/file-system/index.js +0 -171
- package/dist/plugins/file-system/ui.js +0 -6
- package/dist/plugins/llm/context-budget.js +0 -139
- package/dist/plugins/llm/context-shaping.js +0 -177
- package/dist/plugins/llm/index.js +0 -380
- package/dist/plugins/memory/index.js +0 -220
- package/dist/plugins/memory/memory.js +0 -122
- package/dist/plugins/memory/prompt.js +0 -55
- package/dist/plugins/memory/types.js +0 -45
- package/dist/plugins/meta-agent/index.js +0 -570
- package/dist/plugins/meta-agent/ui.js +0 -11
- package/dist/plugins/shell/index.js +0 -100
- package/dist/plugins/shell/ui.js +0 -6
- package/dist/plugins/skills/index.js +0 -286
- package/dist/plugins/skills/types.js +0 -50
- package/dist/plugins/skills/ui.js +0 -12
- package/dist/registry/agent-registry.js +0 -35
- package/dist/registry/index.js +0 -2
- package/dist/registry/plugin-loader.js +0 -499
- package/dist/registry/plugin-registry.js +0 -44
- package/dist/registry/ts-agent-loader.js +0 -82
- package/dist/registry/yaml-agent-loader.js +0 -246
- package/dist/runtime/execution-trace.js +0 -41
- package/dist/runtime/intent-routing.js +0 -26
- package/dist/runtime/openbot-runtime.js +0 -354
- package/dist/server.js +0 -890
- package/dist/session.js +0 -179
- package/dist/ui/block.js +0 -12
- package/dist/ui/header.js +0 -52
- package/dist/ui/layout.js +0 -26
- package/dist/ui/navigation.js +0 -15
- package/dist/ui/settings.js +0 -106
- package/dist/ui/skills.js +0 -7
- package/dist/ui/thread.js +0 -16
- package/dist/ui/widgets/action-list.js +0 -2
- package/dist/ui/widgets/approval-card.js +0 -9
- package/dist/ui/widgets/code-snippet.js +0 -2
- package/dist/ui/widgets/data-block.js +0 -2
- package/dist/ui/widgets/data-table.js +0 -2
- package/dist/ui/widgets/delegation.js +0 -29
- package/dist/ui/widgets/empty-state.js +0 -2
- package/dist/ui/widgets/index.js +0 -23
- package/dist/ui/widgets/inquiry.js +0 -7
- package/dist/ui/widgets/key-value.js +0 -2
- package/dist/ui/widgets/progress-step.js +0 -2
- package/dist/ui/widgets/resource-card.js +0 -2
- package/dist/ui/widgets/status.js +0 -2
- package/dist/ui/widgets/todo-list.js +0 -2
- package/dist/version.js +0 -62
- /package/dist/{types.js → app/types.js} +0 -0
- /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
package/dist/orchestrator.js
DELETED
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
import { melony } from "melony";
|
|
2
|
-
const AGENT_TEXT_TYPES = new Set(["assistant:text-delta", "assistant:text"]);
|
|
3
|
-
const MAX_DELEGATIONS_PER_MANAGER_RUN = 6;
|
|
4
|
-
const DIRECT_TITLE_CONTEXT_LIMIT = 20;
|
|
5
|
-
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;
|
|
6
|
-
function isAgentTextEvent(event) {
|
|
7
|
-
return AGENT_TEXT_TYPES.has(event.type);
|
|
8
|
-
}
|
|
9
|
-
function createTraceId(runId) {
|
|
10
|
-
return `trace_${runId}_${Date.now()}`;
|
|
11
|
-
}
|
|
12
|
-
function nowIso() {
|
|
13
|
-
return new Date().toISOString();
|
|
14
|
-
}
|
|
15
|
-
function hasPendingApprovals(state) {
|
|
16
|
-
const agentStates = state.agentStates || {};
|
|
17
|
-
return Object.values(agentStates).some((agentState) => !!agentState.pendingApprovals &&
|
|
18
|
-
Object.keys(agentState.pendingApprovals).length > 0);
|
|
19
|
-
}
|
|
20
|
-
function parseDirectAgent(content, knownAgents) {
|
|
21
|
-
const raw = content.trim();
|
|
22
|
-
if (!raw || (!raw.startsWith("/") && !raw.startsWith("@")))
|
|
23
|
-
return null;
|
|
24
|
-
const firstSpace = raw.indexOf(" ");
|
|
25
|
-
const candidate = firstSpace === -1 ? raw.slice(1) : raw.slice(1, firstSpace);
|
|
26
|
-
if (!knownAgents.has(candidate))
|
|
27
|
-
return null;
|
|
28
|
-
const task = firstSpace === -1 ? "" : raw.slice(firstSpace + 1).trim();
|
|
29
|
-
return { agentName: candidate, task };
|
|
30
|
-
}
|
|
31
|
-
function classifyIntent(content, knownAgents) {
|
|
32
|
-
const raw = content.trim();
|
|
33
|
-
const direct = parseDirectAgent(raw, knownAgents);
|
|
34
|
-
if (direct) {
|
|
35
|
-
return { type: "agent_direct", targetAgent: direct.agentName };
|
|
36
|
-
}
|
|
37
|
-
if (!raw) {
|
|
38
|
-
return { type: "chat" };
|
|
39
|
-
}
|
|
40
|
-
if (TASK_HINT_REGEX.test(raw)) {
|
|
41
|
-
return { type: "task" };
|
|
42
|
-
}
|
|
43
|
-
return { type: "chat" };
|
|
44
|
-
}
|
|
45
|
-
export class Orchestrator {
|
|
46
|
-
constructor(options) {
|
|
47
|
-
this.managerPlugin = options.managerPlugin;
|
|
48
|
-
this.agents = new Map(options.agents.map((a) => [a.name, a]));
|
|
49
|
-
}
|
|
50
|
-
buildManagerRuntime() {
|
|
51
|
-
const builder = melony();
|
|
52
|
-
builder.use(this.managerPlugin);
|
|
53
|
-
builder.on("action:taskResult", async function* (event) {
|
|
54
|
-
yield { type: "manager:result", data: event.data };
|
|
55
|
-
});
|
|
56
|
-
return builder.build();
|
|
57
|
-
}
|
|
58
|
-
buildAgentRuntime(agent) {
|
|
59
|
-
const builder = melony();
|
|
60
|
-
builder.use(agent.plugin);
|
|
61
|
-
const name = agent.name;
|
|
62
|
-
builder.on("action:taskResult", async function* (event) {
|
|
63
|
-
yield { type: `agent:${name}:result`, data: event.data };
|
|
64
|
-
});
|
|
65
|
-
return builder.build();
|
|
66
|
-
}
|
|
67
|
-
getAgentState(agentName, sessionState) {
|
|
68
|
-
if (!sessionState.agentStates)
|
|
69
|
-
sessionState.agentStates = {};
|
|
70
|
-
if (!sessionState.agentStates[agentName]) {
|
|
71
|
-
sessionState.agentStates[agentName] = {};
|
|
72
|
-
}
|
|
73
|
-
const agentState = sessionState.agentStates[agentName];
|
|
74
|
-
if (!agentState.cwd)
|
|
75
|
-
agentState.cwd = sessionState.cwd;
|
|
76
|
-
return agentState;
|
|
77
|
-
}
|
|
78
|
-
appendSessionMessage(sessionState, message) {
|
|
79
|
-
if (!sessionState.messages)
|
|
80
|
-
sessionState.messages = [];
|
|
81
|
-
sessionState.messages.push(message);
|
|
82
|
-
if (sessionState.messages.length > DIRECT_TITLE_CONTEXT_LIMIT) {
|
|
83
|
-
sessionState.messages = sessionState.messages.slice(-DIRECT_TITLE_CONTEXT_LIMIT);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
setExecutionState(state, patch) {
|
|
87
|
-
const hasCurrentStepId = Object.prototype.hasOwnProperty.call(patch, "currentStepId");
|
|
88
|
-
const hasError = Object.prototype.hasOwnProperty.call(patch, "error");
|
|
89
|
-
const hasIntentType = Object.prototype.hasOwnProperty.call(patch, "intentType");
|
|
90
|
-
const hasPlanSteps = Object.prototype.hasOwnProperty.call(patch, "planSteps");
|
|
91
|
-
const next = {
|
|
92
|
-
traceId: patch.traceId ?? state.execution?.traceId ?? `trace_${Date.now()}`,
|
|
93
|
-
state: patch.state ?? state.execution?.state ?? "RECEIVED",
|
|
94
|
-
currentStepId: hasCurrentStepId ? patch.currentStepId : state.execution?.currentStepId,
|
|
95
|
-
error: hasError ? patch.error : state.execution?.error,
|
|
96
|
-
intentType: hasIntentType ? patch.intentType : state.execution?.intentType,
|
|
97
|
-
planSteps: hasPlanSteps ? patch.planSteps : state.execution?.planSteps,
|
|
98
|
-
updatedAt: nowIso(),
|
|
99
|
-
};
|
|
100
|
-
state.execution = next;
|
|
101
|
-
return next;
|
|
102
|
-
}
|
|
103
|
-
executionStateEvent(trace) {
|
|
104
|
-
return {
|
|
105
|
-
type: "execution:state",
|
|
106
|
-
data: {
|
|
107
|
-
traceId: trace?.traceId,
|
|
108
|
-
state: trace?.state,
|
|
109
|
-
currentStepId: trace?.currentStepId,
|
|
110
|
-
error: trace?.error,
|
|
111
|
-
intentType: trace?.intentType,
|
|
112
|
-
planSteps: trace?.planSteps,
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
async *triggerTopicRefresh(sessionState, runId) {
|
|
117
|
-
const runtime = this.buildManagerRuntime();
|
|
118
|
-
for await (const yielded of runtime.run({
|
|
119
|
-
type: "manager:completion",
|
|
120
|
-
data: { content: "" },
|
|
121
|
-
}, { state: sessionState, runId })) {
|
|
122
|
-
yield yielded;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
estimatePlanSteps(planText) {
|
|
126
|
-
const trimmed = planText.trim();
|
|
127
|
-
if (!trimmed)
|
|
128
|
-
return undefined;
|
|
129
|
-
try {
|
|
130
|
-
const parsed = JSON.parse(trimmed);
|
|
131
|
-
if (Array.isArray(parsed?.steps))
|
|
132
|
-
return parsed.steps.length;
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
// Best-effort parsing only.
|
|
136
|
-
}
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
-
buildManagerTaskInput(userIntent, planText) {
|
|
140
|
-
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.`;
|
|
141
|
-
}
|
|
142
|
-
async *runPlannerAgent(content, attachments, sessionState, runId) {
|
|
143
|
-
const plannerName = "planner-agent";
|
|
144
|
-
if (!this.agents.has(plannerName)) {
|
|
145
|
-
return "";
|
|
146
|
-
}
|
|
147
|
-
let plannerOutput = "";
|
|
148
|
-
for await (const yielded of this.runAgentInternal(plannerName, content, attachments, sessionState, runId)) {
|
|
149
|
-
if (yielded.type === `agent:${plannerName}:output`) {
|
|
150
|
-
plannerOutput = yielded.data.content || "";
|
|
151
|
-
}
|
|
152
|
-
// Planner output is consumed by OpenBot orchestration and should not be
|
|
153
|
-
// streamed directly to the user.
|
|
154
|
-
if (!isAgentTextEvent(yielded) && yielded.type !== `agent:${plannerName}:output`) {
|
|
155
|
-
yield yielded;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return plannerOutput;
|
|
159
|
-
}
|
|
160
|
-
async *run(event, options) {
|
|
161
|
-
const { state, runId = `run_${Date.now()}` } = options;
|
|
162
|
-
yield event;
|
|
163
|
-
if (event.type === "action:approve" || event.type === "action:deny") {
|
|
164
|
-
const trace = this.setExecutionState(state, {
|
|
165
|
-
state: "EXECUTING",
|
|
166
|
-
error: undefined,
|
|
167
|
-
});
|
|
168
|
-
yield this.executionStateEvent(trace);
|
|
169
|
-
yield* this.routeApproval(event, state, runId);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (event.type === "user:text" || event.type === "user:multimodal") {
|
|
173
|
-
const rawContent = event.data.content;
|
|
174
|
-
const content = typeof rawContent === "string" ? rawContent.trim() : "";
|
|
175
|
-
const attachments = Array.isArray(event.data.attachments)
|
|
176
|
-
? event.data.attachments
|
|
177
|
-
: undefined;
|
|
178
|
-
const knownAgents = new Set(this.agents.keys());
|
|
179
|
-
const direct = parseDirectAgent(content, knownAgents);
|
|
180
|
-
const intent = classifyIntent(content, knownAgents);
|
|
181
|
-
let trace = this.setExecutionState(state, {
|
|
182
|
-
traceId: createTraceId(runId),
|
|
183
|
-
state: "RECEIVED",
|
|
184
|
-
intentType: intent.type,
|
|
185
|
-
error: undefined,
|
|
186
|
-
currentStepId: undefined,
|
|
187
|
-
planSteps: undefined,
|
|
188
|
-
});
|
|
189
|
-
yield this.executionStateEvent(trace);
|
|
190
|
-
try {
|
|
191
|
-
if (direct) {
|
|
192
|
-
this.appendSessionMessage(state, {
|
|
193
|
-
role: "user",
|
|
194
|
-
content,
|
|
195
|
-
attachments,
|
|
196
|
-
});
|
|
197
|
-
trace = this.setExecutionState(state, {
|
|
198
|
-
state: "EXECUTING",
|
|
199
|
-
currentStepId: `delegate:${direct.agentName}`,
|
|
200
|
-
});
|
|
201
|
-
yield this.executionStateEvent(trace);
|
|
202
|
-
yield* this.runAgentDirect(direct.agentName, direct.task, attachments, state, runId);
|
|
203
|
-
const finalTrace = this.setExecutionState(state, {
|
|
204
|
-
state: hasPendingApprovals(state) ? "WAITING_APPROVAL" : "COMPLETED",
|
|
205
|
-
currentStepId: hasPendingApprovals(state)
|
|
206
|
-
? `delegate:${direct.agentName}`
|
|
207
|
-
: undefined,
|
|
208
|
-
error: undefined,
|
|
209
|
-
});
|
|
210
|
-
yield this.executionStateEvent(finalTrace);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
trace = this.setExecutionState(state, {
|
|
214
|
-
state: "EXECUTING",
|
|
215
|
-
currentStepId: intent.type === "task" ? "planner" : "manager",
|
|
216
|
-
});
|
|
217
|
-
yield this.executionStateEvent(trace);
|
|
218
|
-
let managerInput = content;
|
|
219
|
-
if (intent.type === "task" && this.agents.has("planner-agent")) {
|
|
220
|
-
const plannerResult = yield* this.runPlannerAgent(content, attachments, state, runId);
|
|
221
|
-
if (plannerResult.trim()) {
|
|
222
|
-
managerInput = this.buildManagerTaskInput(content, plannerResult);
|
|
223
|
-
const planSteps = this.estimatePlanSteps(plannerResult);
|
|
224
|
-
trace = this.setExecutionState(state, {
|
|
225
|
-
currentStepId: "manager",
|
|
226
|
-
planSteps,
|
|
227
|
-
});
|
|
228
|
-
yield this.executionStateEvent(trace);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
yield* this.runManagerLoop({
|
|
232
|
-
type: "manager:input",
|
|
233
|
-
data: { content: managerInput, attachments },
|
|
234
|
-
}, state, runId);
|
|
235
|
-
const waiting = hasPendingApprovals(state);
|
|
236
|
-
const finalTrace = this.setExecutionState(state, {
|
|
237
|
-
state: waiting ? "WAITING_APPROVAL" : "COMPLETED",
|
|
238
|
-
currentStepId: waiting ? (state.execution?.currentStepId ?? "manager") : undefined,
|
|
239
|
-
error: undefined,
|
|
240
|
-
});
|
|
241
|
-
yield this.executionStateEvent(finalTrace);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
const finalTrace = this.setExecutionState(state, {
|
|
246
|
-
state: "FAILED",
|
|
247
|
-
error: error instanceof Error ? error.message : String(error),
|
|
248
|
-
});
|
|
249
|
-
yield this.executionStateEvent(finalTrace);
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
yield* this.runManagerLoop(event, state, runId);
|
|
254
|
-
}
|
|
255
|
-
async *runManagerLoop(event, state, runId) {
|
|
256
|
-
const runtime = this.buildManagerRuntime();
|
|
257
|
-
const delegationSignatures = new Set();
|
|
258
|
-
let delegationCount = 0;
|
|
259
|
-
let nextManagerEvent = event;
|
|
260
|
-
while (nextManagerEvent) {
|
|
261
|
-
const managerEvent = nextManagerEvent;
|
|
262
|
-
nextManagerEvent = undefined;
|
|
263
|
-
for await (const yielded of runtime.run(managerEvent, { state, runId })) {
|
|
264
|
-
if (yielded.type !== "action:delegateTask") {
|
|
265
|
-
yield yielded;
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
const { agent: agentName, task, attachments, toolCallId } = yielded.data;
|
|
269
|
-
const normalizedTask = typeof task === "string"
|
|
270
|
-
? task.replace(/\s+/g, " ").trim().toLowerCase()
|
|
271
|
-
: "";
|
|
272
|
-
const signature = `${agentName}::${normalizedTask}`;
|
|
273
|
-
if (delegationCount >= MAX_DELEGATIONS_PER_MANAGER_RUN ||
|
|
274
|
-
(normalizedTask && delegationSignatures.has(signature))) {
|
|
275
|
-
nextManagerEvent = {
|
|
276
|
-
type: "manager:result",
|
|
277
|
-
data: {
|
|
278
|
-
action: "delegateTask",
|
|
279
|
-
toolCallId,
|
|
280
|
-
result: `Error: delegation loop detected for agent "${agentName}". Summarize current progress and stop delegating.`,
|
|
281
|
-
},
|
|
282
|
-
};
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
if (!this.agents.has(agentName)) {
|
|
286
|
-
nextManagerEvent = {
|
|
287
|
-
type: "manager:result",
|
|
288
|
-
data: {
|
|
289
|
-
action: "delegateTask",
|
|
290
|
-
toolCallId,
|
|
291
|
-
result: `Error: Agent "${agentName}" not found`,
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
delegationCount += 1;
|
|
297
|
-
if (normalizedTask)
|
|
298
|
-
delegationSignatures.add(signature);
|
|
299
|
-
if (!state.pendingAgentTasks)
|
|
300
|
-
state.pendingAgentTasks = {};
|
|
301
|
-
state.pendingAgentTasks[agentName] = { toolCallId };
|
|
302
|
-
let agentOutput = "";
|
|
303
|
-
let agentCompleted = false;
|
|
304
|
-
try {
|
|
305
|
-
for await (const agentEvent of this.runAgentInternal(agentName, task, attachments, state, runId)) {
|
|
306
|
-
if (agentEvent.type === `agent:${agentName}:output`) {
|
|
307
|
-
agentOutput = agentEvent.data.content;
|
|
308
|
-
agentCompleted = true;
|
|
309
|
-
}
|
|
310
|
-
if (!isAgentTextEvent(agentEvent)) {
|
|
311
|
-
yield agentEvent;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
catch (error) {
|
|
316
|
-
agentOutput = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
317
|
-
agentCompleted = true;
|
|
318
|
-
}
|
|
319
|
-
if (agentCompleted) {
|
|
320
|
-
delete state.pendingAgentTasks[agentName];
|
|
321
|
-
nextManagerEvent = {
|
|
322
|
-
type: "manager:result",
|
|
323
|
-
data: {
|
|
324
|
-
action: "delegateTask",
|
|
325
|
-
toolCallId,
|
|
326
|
-
result: agentOutput,
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
async *runAgentInternal(agentName, task, attachments, sessionState, runId) {
|
|
335
|
-
const agent = this.agents.get(agentName);
|
|
336
|
-
const agentState = this.getAgentState(agentName, sessionState);
|
|
337
|
-
const runtime = this.buildAgentRuntime(agent);
|
|
338
|
-
const inputEvent = {
|
|
339
|
-
type: `agent:${agentName}:input`,
|
|
340
|
-
data: { content: task, attachments },
|
|
341
|
-
};
|
|
342
|
-
for await (const yielded of runtime.run(inputEvent, {
|
|
343
|
-
state: agentState,
|
|
344
|
-
runId,
|
|
345
|
-
})) {
|
|
346
|
-
yield yielded;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
async *runAgentDirect(agentName, task, attachments, sessionState, runId) {
|
|
350
|
-
for await (const yielded of this.runAgentInternal(agentName, task, attachments, sessionState, runId)) {
|
|
351
|
-
if (yielded.type === `agent:${agentName}:output`) {
|
|
352
|
-
const content = yielded.data.content;
|
|
353
|
-
this.appendSessionMessage(sessionState, {
|
|
354
|
-
role: "assistant",
|
|
355
|
-
content,
|
|
356
|
-
});
|
|
357
|
-
yield {
|
|
358
|
-
type: "assistant:text",
|
|
359
|
-
data: { content },
|
|
360
|
-
meta: { agent: agentName },
|
|
361
|
-
};
|
|
362
|
-
yield* this.triggerTopicRefresh(sessionState, runId);
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
yield yielded;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
async *routeApproval(event, state, runId) {
|
|
370
|
-
const approvalId = event.data.id;
|
|
371
|
-
const agentStates = state.agentStates || {};
|
|
372
|
-
let targetAgent;
|
|
373
|
-
for (const [name, agentState] of Object.entries(agentStates)) {
|
|
374
|
-
if (agentState.pendingApprovals?.[approvalId]) {
|
|
375
|
-
targetAgent = name;
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (!targetAgent) {
|
|
380
|
-
console.warn("[orchestrator] No agent found for approval event:", approvalId);
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
const agent = this.agents.get(targetAgent);
|
|
384
|
-
const agentState = this.getAgentState(targetAgent, state);
|
|
385
|
-
const runtime = this.buildAgentRuntime(agent);
|
|
386
|
-
let agentOutput = "";
|
|
387
|
-
let agentCompleted = false;
|
|
388
|
-
const isDelegation = !!state.pendingAgentTasks?.[targetAgent];
|
|
389
|
-
for await (const yielded of runtime.run(event, {
|
|
390
|
-
state: agentState,
|
|
391
|
-
runId,
|
|
392
|
-
})) {
|
|
393
|
-
if (yielded.type === `agent:${targetAgent}:output`) {
|
|
394
|
-
agentOutput = yielded.data.content;
|
|
395
|
-
agentCompleted = true;
|
|
396
|
-
}
|
|
397
|
-
if (isDelegation && isAgentTextEvent(yielded)) {
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
yield yielded;
|
|
401
|
-
}
|
|
402
|
-
if (agentCompleted && state.pendingAgentTasks?.[targetAgent]) {
|
|
403
|
-
const { toolCallId } = state.pendingAgentTasks[targetAgent];
|
|
404
|
-
delete state.pendingAgentTasks[targetAgent];
|
|
405
|
-
yield* this.runManagerLoop({
|
|
406
|
-
type: "manager:result",
|
|
407
|
-
data: {
|
|
408
|
-
action: "delegateTask",
|
|
409
|
-
toolCallId,
|
|
410
|
-
result: agentOutput,
|
|
411
|
-
},
|
|
412
|
-
}, state, runId);
|
|
413
|
-
}
|
|
414
|
-
const waiting = hasPendingApprovals(state);
|
|
415
|
-
const trace = this.setExecutionState(state, {
|
|
416
|
-
state: waiting ? "WAITING_APPROVAL" : "COMPLETED",
|
|
417
|
-
currentStepId: waiting ? state.execution?.currentStepId : undefined,
|
|
418
|
-
error: undefined,
|
|
419
|
-
});
|
|
420
|
-
yield this.executionStateEvent(trace);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { streamText } from "ai";
|
|
2
|
-
import { ui } from "@melony/ui-kit";
|
|
3
|
-
/**
|
|
4
|
-
* Builds a simple history summary from recent messages.
|
|
5
|
-
* Keeps the last N messages as simple role/content pairs.
|
|
6
|
-
*/
|
|
7
|
-
function getRecentHistory(messages, maxMessages) {
|
|
8
|
-
return messages.slice(-maxMessages);
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* AI SDK Plugin for Melony.
|
|
12
|
-
* Automatically handles text events and routes them through an LLM using Vercel AI SDK.
|
|
13
|
-
* It can also automatically trigger events based on tool calls.
|
|
14
|
-
*/
|
|
15
|
-
export const agentPlugin = (options) => (builder) => {
|
|
16
|
-
const { model, system, toolDefinitions = {}, actionEventPrefix = "action:", promptInputType = "user:text", actionResultInputType = "action:result", completionEventType } = options;
|
|
17
|
-
async function* routeToLLM(newMessage, context) {
|
|
18
|
-
const state = context.state;
|
|
19
|
-
if (!state.messages) {
|
|
20
|
-
state.messages = [];
|
|
21
|
-
}
|
|
22
|
-
// Add new message to history
|
|
23
|
-
state.messages.push(newMessage);
|
|
24
|
-
// Evaluate dynamic system prompt if it's a function
|
|
25
|
-
const systemPrompt = typeof system === "function" ? await system(context) : system;
|
|
26
|
-
const result = streamText({
|
|
27
|
-
model,
|
|
28
|
-
system: systemPrompt,
|
|
29
|
-
messages: getRecentHistory(state.messages, 20).map(m => m.role === "system" ? { role: "user", content: `System: ${m.content}` } : m),
|
|
30
|
-
tools: toolDefinitions,
|
|
31
|
-
});
|
|
32
|
-
let assistantText = "";
|
|
33
|
-
for await (const delta of result.textStream) {
|
|
34
|
-
assistantText += delta;
|
|
35
|
-
yield {
|
|
36
|
-
type: "assistant:text-delta",
|
|
37
|
-
data: { delta },
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
// Wait for tool calls to complete
|
|
41
|
-
const toolCalls = await result.toolCalls;
|
|
42
|
-
// Store assistant response as simple text
|
|
43
|
-
if (assistantText) {
|
|
44
|
-
state.messages.push({
|
|
45
|
-
role: "assistant",
|
|
46
|
-
content: assistantText,
|
|
47
|
-
});
|
|
48
|
-
if (completionEventType) {
|
|
49
|
-
yield {
|
|
50
|
-
type: completionEventType,
|
|
51
|
-
data: { content: assistantText },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const usage = await result.usage;
|
|
56
|
-
yield ui.event(ui.status(`Usage: ${usage.totalTokens} tokens`, "info"));
|
|
57
|
-
// Emit tool call events
|
|
58
|
-
for (const call of toolCalls) {
|
|
59
|
-
yield {
|
|
60
|
-
type: `${actionEventPrefix}${call.toolName}`,
|
|
61
|
-
data: {
|
|
62
|
-
...call.input,
|
|
63
|
-
toolCallId: call.toolCallId,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Handle user text input
|
|
69
|
-
builder.on(promptInputType, async function* (event, context) {
|
|
70
|
-
const content = event.data.content;
|
|
71
|
-
yield* routeToLLM({ role: "user", content }, context);
|
|
72
|
-
});
|
|
73
|
-
// Feed action results back to the LLM as user messages (with a System prefix)
|
|
74
|
-
// We use "user" role instead of "system" to avoid errors with providers like Anthropic
|
|
75
|
-
// that don't support multiple system messages or system messages after the first turn.
|
|
76
|
-
builder.on(actionResultInputType, async function* (event, context) {
|
|
77
|
-
const { action, result } = event.data;
|
|
78
|
-
const summary = typeof result === "string" ? result : JSON.stringify(result);
|
|
79
|
-
yield* routeToLLM({ role: "user", content: `System: Action "${action}" completed: ${summary}` }, context);
|
|
80
|
-
});
|
|
81
|
-
};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { generateId } from "melony";
|
|
2
|
-
import { uiEvent } from "../../ui/block.js";
|
|
3
|
-
import { widgets } from "../../ui/widgets/index.js";
|
|
4
|
-
function getActionName(eventType) {
|
|
5
|
-
return eventType.startsWith("action:")
|
|
6
|
-
? eventType.slice("action:".length)
|
|
7
|
-
: eventType;
|
|
8
|
-
}
|
|
9
|
-
function buildApprovalData(event, rule) {
|
|
10
|
-
const actionName = getActionName(String(event.type));
|
|
11
|
-
const toolCallId = event?.data?.toolCallId;
|
|
12
|
-
const data = event?.data ?? {};
|
|
13
|
-
return {
|
|
14
|
-
summary: rule.message ??
|
|
15
|
-
"The agent requested a protected action. Approve to continue or deny to block it.",
|
|
16
|
-
details: [
|
|
17
|
-
{ label: "Action", value: actionName },
|
|
18
|
-
...(toolCallId ? [{ label: "Tool call", value: String(toolCallId) }] : []),
|
|
19
|
-
],
|
|
20
|
-
rawPayload: JSON.stringify(data, null, 2),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Minimal approval gate:
|
|
25
|
-
* - Intercept protected action events.
|
|
26
|
-
* - Suspend current request and show Approve/Deny UI.
|
|
27
|
-
* - Resume only when user sends action:approve or action:deny.
|
|
28
|
-
*/
|
|
29
|
-
export const approvalPlugin = (options) => (builder) => {
|
|
30
|
-
const rules = options?.rules ?? [];
|
|
31
|
-
builder.intercept(async (event, { state, suspend }) => {
|
|
32
|
-
const type = String(event.type ?? "");
|
|
33
|
-
const meta = event.meta ?? {};
|
|
34
|
-
// Never intercept internal approval events or already-approved replays.
|
|
35
|
-
if (type === "action:approve" || type === "action:deny" || meta.approved === true) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const rule = rules.find((r) => type.startsWith(r.action));
|
|
39
|
-
if (!rule)
|
|
40
|
-
return;
|
|
41
|
-
const approvalId = `approve_${generateId()}`;
|
|
42
|
-
state.pendingApprovals ?? (state.pendingApprovals = {});
|
|
43
|
-
state.pendingApprovals[approvalId] = {
|
|
44
|
-
originalEvent: event,
|
|
45
|
-
createdAt: Date.now(),
|
|
46
|
-
};
|
|
47
|
-
suspend({
|
|
48
|
-
type: "suspend",
|
|
49
|
-
data: {
|
|
50
|
-
reason: "approval",
|
|
51
|
-
id: approvalId,
|
|
52
|
-
event: uiEvent(widgets.approvalCard("Approval Required", buildApprovalData(event, rule), { type: "action:approve", data: { id: approvalId } }, { type: "action:deny", data: { id: approvalId } }, { placement: "attention", id: approvalId })),
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
builder.on("action:approve", async function* (event, { state }) {
|
|
57
|
-
const id = event?.data?.id;
|
|
58
|
-
if (!id)
|
|
59
|
-
return;
|
|
60
|
-
const pending = state.pendingApprovals?.[id];
|
|
61
|
-
if (!pending) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
delete state.pendingApprovals[id];
|
|
65
|
-
// Re-emit the original action with approval marker.
|
|
66
|
-
yield {
|
|
67
|
-
...pending.originalEvent,
|
|
68
|
-
meta: {
|
|
69
|
-
...(pending.originalEvent?.meta ?? {}),
|
|
70
|
-
approved: true,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
});
|
|
74
|
-
builder.on("action:deny", async function* (event, { state }) {
|
|
75
|
-
const id = event?.data?.id;
|
|
76
|
-
if (!id)
|
|
77
|
-
return;
|
|
78
|
-
const pending = state.pendingApprovals?.[id];
|
|
79
|
-
if (!pending) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
delete state.pendingApprovals[id];
|
|
83
|
-
yield uiEvent(widgets.status("Action denied", "error", { placement: "attention", id }));
|
|
84
|
-
const originalEvent = pending.originalEvent;
|
|
85
|
-
const toolCallId = originalEvent?.data?.toolCallId;
|
|
86
|
-
if (toolCallId) {
|
|
87
|
-
yield {
|
|
88
|
-
type: "action:result",
|
|
89
|
-
data: {
|
|
90
|
-
action: getActionName(String(originalEvent.type ?? "")),
|
|
91
|
-
toolCallId,
|
|
92
|
-
result: { error: "Action denied by user", denied: true },
|
|
93
|
-
success: false,
|
|
94
|
-
halt: true,
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
};
|
|
100
|
-
export default approvalPlugin;
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
// --- Defaults ---
|
|
4
|
-
const DEFAULT_SOUL = `# Soul
|
|
5
|
-
|
|
6
|
-
## Core Values
|
|
7
|
-
- Be helpful, honest, and harmless
|
|
8
|
-
- Respect user privacy and data
|
|
9
|
-
- Learn and improve continuously
|
|
10
|
-
- Be transparent about capabilities and limitations
|
|
11
|
-
|
|
12
|
-
## Ethical Guidelines
|
|
13
|
-
- Never assist with harmful or illegal activities
|
|
14
|
-
- Protect sensitive information
|
|
15
|
-
- Acknowledge uncertainty when unsure
|
|
16
|
-
- Prioritize user well-being
|
|
17
|
-
`;
|
|
18
|
-
const DEFAULT_IDENTITY = `# Identity
|
|
19
|
-
|
|
20
|
-
I am the Manager Agent and central orchestrator of this AI system. My name and specific personality are defined by the user in this IDENTITY.md file.
|
|
21
|
-
|
|
22
|
-
## Personality
|
|
23
|
-
- Friendly and approachable
|
|
24
|
-
- Technically competent
|
|
25
|
-
- Eager to learn and adapt
|
|
26
|
-
- Professional manager and delegator
|
|
27
|
-
|
|
28
|
-
## Capabilities
|
|
29
|
-
- Task Orchestration & Delegation
|
|
30
|
-
- Long-term Memory & Knowledge Management
|
|
31
|
-
- Executing specialized tasks via expert agents (Web, OS, etc.)
|
|
32
|
-
- Self-modification and learning
|
|
33
|
-
`;
|
|
34
|
-
// --- Factory ---
|
|
35
|
-
export function createIdentityModule(baseDir) {
|
|
36
|
-
const soulPath = path.join(baseDir, "SOUL.md");
|
|
37
|
-
const identityPath = path.join(baseDir, "IDENTITY.md");
|
|
38
|
-
return {
|
|
39
|
-
async initialize() {
|
|
40
|
-
try {
|
|
41
|
-
await fs.access(soulPath);
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
await fs.writeFile(soulPath, DEFAULT_SOUL, "utf-8");
|
|
45
|
-
}
|
|
46
|
-
try {
|
|
47
|
-
await fs.access(identityPath);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
await fs.writeFile(identityPath, DEFAULT_IDENTITY, "utf-8");
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
async getSoul() {
|
|
54
|
-
try {
|
|
55
|
-
return (await fs.readFile(soulPath, "utf-8")).trim();
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return "";
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
async getIdentity() {
|
|
62
|
-
try {
|
|
63
|
-
return (await fs.readFile(identityPath, "utf-8")).trim();
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return "";
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
async updateIdentity(content) {
|
|
70
|
-
await fs.writeFile(identityPath, content, "utf-8");
|
|
71
|
-
},
|
|
72
|
-
async readFile(file) {
|
|
73
|
-
const filePath = file === "SOUL.md" ? soulPath : identityPath;
|
|
74
|
-
return await fs.readFile(filePath, "utf-8");
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|