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
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
import { streamText, Output } from "ai";
|
|
2
|
-
async function toInlineDataUrl(url, mimeType) {
|
|
3
|
-
const response = await fetch(url);
|
|
4
|
-
if (!response.ok) {
|
|
5
|
-
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
|
6
|
-
}
|
|
7
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
8
|
-
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
|
9
|
-
return `data:${mimeType};base64,${base64}`;
|
|
10
|
-
}
|
|
11
|
-
async function toModelMessages(messages) {
|
|
12
|
-
return Promise.all(messages.map(async (message) => {
|
|
13
|
-
if (message.role === "tool") {
|
|
14
|
-
return {
|
|
15
|
-
role: "tool",
|
|
16
|
-
content: Array.isArray(message.content)
|
|
17
|
-
? message.content.map((c) => {
|
|
18
|
-
const result = c.result ?? c.output?.value ?? c.output;
|
|
19
|
-
return {
|
|
20
|
-
type: "tool-result",
|
|
21
|
-
toolCallId: c.toolCallId,
|
|
22
|
-
toolName: c.toolName,
|
|
23
|
-
output: typeof result === "string"
|
|
24
|
-
? { type: "text", value: result }
|
|
25
|
-
: { type: "json", value: result },
|
|
26
|
-
};
|
|
27
|
-
})
|
|
28
|
-
: [],
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
if (message.role === "assistant") {
|
|
32
|
-
if (Array.isArray(message.content)) {
|
|
33
|
-
return {
|
|
34
|
-
role: "assistant",
|
|
35
|
-
content: message.content.map((c) => {
|
|
36
|
-
if (c.type === "tool-call") {
|
|
37
|
-
return {
|
|
38
|
-
type: "tool-call",
|
|
39
|
-
toolCallId: c.toolCallId,
|
|
40
|
-
toolName: c.toolName,
|
|
41
|
-
input: c.input,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
if (c.type === "text") {
|
|
45
|
-
return c;
|
|
46
|
-
}
|
|
47
|
-
// Fallback for character spread bug fix
|
|
48
|
-
if (typeof c === "string") {
|
|
49
|
-
return { type: "text", text: c };
|
|
50
|
-
}
|
|
51
|
-
return c;
|
|
52
|
-
}),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
role: "assistant",
|
|
57
|
-
content: message.content,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
if (message.role === "user") {
|
|
61
|
-
if (message.attachments && message.attachments.length > 0) {
|
|
62
|
-
const attachmentParts = await Promise.all(message.attachments.map(async (a) => {
|
|
63
|
-
if (a.mimeType.startsWith("image/")) {
|
|
64
|
-
try {
|
|
65
|
-
return {
|
|
66
|
-
type: "image",
|
|
67
|
-
image: await toInlineDataUrl(a.url, a.mimeType),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
console.warn(`Failed to inline image attachment (${a.name}). Falling back to URL: ${a.url}`, error);
|
|
72
|
-
return {
|
|
73
|
-
type: "image",
|
|
74
|
-
image: a.url,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
type: "file",
|
|
80
|
-
data: a.url,
|
|
81
|
-
mimeType: a.mimeType,
|
|
82
|
-
};
|
|
83
|
-
}));
|
|
84
|
-
return {
|
|
85
|
-
role: "user",
|
|
86
|
-
content: [
|
|
87
|
-
{ type: "text", text: message.content },
|
|
88
|
-
...attachmentParts,
|
|
89
|
-
],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
role: "user",
|
|
94
|
-
content: message.content,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
role: message.role,
|
|
99
|
-
content: message.content,
|
|
100
|
-
};
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
// Helper to find pending tool calls in history
|
|
104
|
-
function getPendingToolCalls(messages) {
|
|
105
|
-
const toolResults = new Set();
|
|
106
|
-
let lastAssistantWithTools = null;
|
|
107
|
-
for (let i = 0; i < messages.length; i++) {
|
|
108
|
-
const msg = messages[i];
|
|
109
|
-
if (msg.role === "tool" && Array.isArray(msg.content)) {
|
|
110
|
-
msg.content.forEach((c) => {
|
|
111
|
-
if (c.toolCallId)
|
|
112
|
-
toolResults.add(c.toolCallId);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
116
|
-
if (msg.content.some((c) => c.type === "tool-call")) {
|
|
117
|
-
lastAssistantWithTools = msg;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (lastAssistantWithTools) {
|
|
122
|
-
return lastAssistantWithTools.content.filter((c) => c.type === "tool-call" && !toolResults.has(c.toolCallId));
|
|
123
|
-
}
|
|
124
|
-
return [];
|
|
125
|
-
}
|
|
126
|
-
// Helper to insert tool result message in correct position (immediately after corresponding assistant msg)
|
|
127
|
-
function insertToolResult(messages, toolResultMsg) {
|
|
128
|
-
if (toolResultMsg.role !== "tool" || !Array.isArray(toolResultMsg.content))
|
|
129
|
-
return;
|
|
130
|
-
const toolCallId = toolResultMsg.content[0]?.toolCallId;
|
|
131
|
-
if (!toolCallId)
|
|
132
|
-
return;
|
|
133
|
-
// Find the assistant message that called this tool
|
|
134
|
-
let assistantIdx = -1;
|
|
135
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
136
|
-
const msg = messages[i];
|
|
137
|
-
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
138
|
-
if (msg.content.some((c) => c.toolCallId === toolCallId)) {
|
|
139
|
-
assistantIdx = i;
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (assistantIdx !== -1) {
|
|
145
|
-
// Find if there's already a tool message after this assistant message
|
|
146
|
-
let toolMsgIdx = -1;
|
|
147
|
-
for (let i = assistantIdx + 1; i < messages.length; i++) {
|
|
148
|
-
if (messages[i].role === "tool") {
|
|
149
|
-
toolMsgIdx = i;
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
if (messages[i].role === "assistant")
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
if (toolMsgIdx !== -1) {
|
|
156
|
-
// Merge into existing tool message
|
|
157
|
-
const toolMsg = messages[toolMsgIdx];
|
|
158
|
-
if (Array.isArray(toolMsg.content)) {
|
|
159
|
-
toolMsg.content.push(...toolResultMsg.content);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
// Insert new tool message after assistant
|
|
164
|
-
messages.splice(assistantIdx + 1, 0, toolResultMsg);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
// Fallback: push to end
|
|
169
|
-
messages.push(toolResultMsg);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* LLM Plugin for Melony.
|
|
174
|
-
* Automatically handles text events and routes them through an LLM using Vercel AI SDK.
|
|
175
|
-
* It can also automatically trigger events based on tool calls.
|
|
176
|
-
*/
|
|
177
|
-
export const llmPlugin = (options) => (builder) => {
|
|
178
|
-
const { model, system, toolDefinitions = {}, actionEventPrefix = "action:", promptInputType = "agent:input", actionResultInputType = "action:result", completionEventType = "agent:output", usageEventType = "usage:update", usageScope = "default", modelId, outputSchema, } = options;
|
|
179
|
-
async function* routeToLLM(newMessage, context, silent = false) {
|
|
180
|
-
const state = context.state;
|
|
181
|
-
if (!state.messages) {
|
|
182
|
-
state.messages = [];
|
|
183
|
-
}
|
|
184
|
-
// 1. Add new message to history with correct positioning and unblocking logic.
|
|
185
|
-
if (newMessage) {
|
|
186
|
-
if (newMessage.role === "tool") {
|
|
187
|
-
insertToolResult(state.messages, newMessage);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
// If a new user message arrives while tools are pending, we unblock by failing the tools.
|
|
191
|
-
if (newMessage.role === "user") {
|
|
192
|
-
const pending = getPendingToolCalls(state.messages);
|
|
193
|
-
if (pending.length > 0) {
|
|
194
|
-
console.warn(`User message received while tools are pending. Failing pending tools to unblock: ${pending
|
|
195
|
-
.map((p) => p.toolName)
|
|
196
|
-
.join(", ")}`);
|
|
197
|
-
for (const p of pending) {
|
|
198
|
-
insertToolResult(state.messages, {
|
|
199
|
-
role: "tool",
|
|
200
|
-
content: [
|
|
201
|
-
{
|
|
202
|
-
type: "tool-result",
|
|
203
|
-
toolCallId: p.toolCallId,
|
|
204
|
-
toolName: p.toolName,
|
|
205
|
-
output: {
|
|
206
|
-
type: "text",
|
|
207
|
-
value: "Tool failed to respond in time (unblocked by user message).",
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
state.messages.push(newMessage);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// 2. Check for pending tool calls. We MUST have results for all tool calls before continuing.
|
|
219
|
-
const pending = getPendingToolCalls(state.messages);
|
|
220
|
-
if (pending.length > 0) {
|
|
221
|
-
console.log(`Waiting for ${pending.length} pending tool results: ${pending
|
|
222
|
-
.map((p) => p.toolName)
|
|
223
|
-
.join(", ")}`);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
// Evaluate dynamic system prompt if it's a function
|
|
227
|
-
const systemPrompt = typeof system === "function" ? await system(context) : system;
|
|
228
|
-
const modelMessages = await toModelMessages(state.messages);
|
|
229
|
-
// Initialize an empty assistant message to be populated as we stream
|
|
230
|
-
const assistantMessage = { role: "assistant", content: "" };
|
|
231
|
-
state.messages.push(assistantMessage);
|
|
232
|
-
// console.log("messages:::::", JSON.stringify(state.messages, null, 2));
|
|
233
|
-
// console.log("modelMessages:::::", JSON.stringify(modelMessages, null, 2));
|
|
234
|
-
const result = streamText({
|
|
235
|
-
model,
|
|
236
|
-
system: systemPrompt,
|
|
237
|
-
messages: modelMessages,
|
|
238
|
-
tools: toolDefinitions,
|
|
239
|
-
output: outputSchema ? Output.object({ schema: outputSchema }) : undefined,
|
|
240
|
-
onError: (error) => {
|
|
241
|
-
console.error("streamText error:::::", JSON.stringify(error, null, 2));
|
|
242
|
-
},
|
|
243
|
-
});
|
|
244
|
-
if (outputSchema) {
|
|
245
|
-
for await (const delta of result.partialOutputStream) {
|
|
246
|
-
if (!silent) {
|
|
247
|
-
yield {
|
|
248
|
-
type: "agent:output-delta",
|
|
249
|
-
data: { delta: "", content: JSON.stringify(delta) },
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const finalObject = await result.output;
|
|
254
|
-
assistantMessage.content = JSON.stringify(finalObject);
|
|
255
|
-
if (completionEventType && !silent) {
|
|
256
|
-
yield {
|
|
257
|
-
type: completionEventType,
|
|
258
|
-
data: finalObject,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
for await (const delta of result.textStream) {
|
|
264
|
-
assistantMessage.content += delta;
|
|
265
|
-
if (!silent) {
|
|
266
|
-
yield {
|
|
267
|
-
type: "agent:output-delta",
|
|
268
|
-
data: { delta, content: assistantMessage.content },
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const assistantText = assistantMessage.content;
|
|
274
|
-
// Wait for tool calls to complete
|
|
275
|
-
const toolCalls = await result.toolCalls;
|
|
276
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
277
|
-
const parts = [];
|
|
278
|
-
if (assistantText) {
|
|
279
|
-
parts.push({ type: "text", text: assistantText });
|
|
280
|
-
}
|
|
281
|
-
parts.push(...toolCalls.map((c) => ({
|
|
282
|
-
type: "tool-call",
|
|
283
|
-
toolCallId: c.toolCallId,
|
|
284
|
-
toolName: c.toolName,
|
|
285
|
-
input: c.input,
|
|
286
|
-
})));
|
|
287
|
-
assistantMessage.content = parts;
|
|
288
|
-
}
|
|
289
|
-
// Remove the message if it's empty (e.g. only tool calls)
|
|
290
|
-
if (!assistantText && (!toolCalls || toolCalls.length === 0)) {
|
|
291
|
-
state.messages = state.messages.filter((m) => m !== assistantMessage);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
if (completionEventType && !silent) {
|
|
295
|
-
// If it's structured output, we already yielded the final object
|
|
296
|
-
if (!outputSchema) {
|
|
297
|
-
yield {
|
|
298
|
-
type: completionEventType,
|
|
299
|
-
data: { content: assistantText },
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const usage = await result.usage;
|
|
305
|
-
if (!state.usage) {
|
|
306
|
-
state.usage = {
|
|
307
|
-
inputTokens: 0,
|
|
308
|
-
outputTokens: 0,
|
|
309
|
-
totalTokens: 0,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
state.usage.inputTokens += usage.inputTokens ?? 0;
|
|
313
|
-
state.usage.outputTokens += usage.outputTokens ?? 0;
|
|
314
|
-
state.usage.totalTokens += usage.totalTokens ?? 0;
|
|
315
|
-
if (!silent) {
|
|
316
|
-
yield {
|
|
317
|
-
type: usageEventType,
|
|
318
|
-
data: {
|
|
319
|
-
scope: usageScope,
|
|
320
|
-
model: modelId,
|
|
321
|
-
turn: {
|
|
322
|
-
inputTokens: usage.inputTokens ?? 0,
|
|
323
|
-
outputTokens: usage.outputTokens ?? 0,
|
|
324
|
-
totalTokens: usage.totalTokens ?? 0,
|
|
325
|
-
},
|
|
326
|
-
session: {
|
|
327
|
-
inputTokens: state.usage.inputTokens,
|
|
328
|
-
outputTokens: state.usage.outputTokens,
|
|
329
|
-
totalTokens: state.usage.totalTokens,
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
// Emit tool call events
|
|
335
|
-
for (const call of toolCalls) {
|
|
336
|
-
yield {
|
|
337
|
-
type: `${actionEventPrefix}${call.toolName}`,
|
|
338
|
-
data: {
|
|
339
|
-
...call.input,
|
|
340
|
-
toolCallId: call.toolCallId,
|
|
341
|
-
},
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
// Handle user text input
|
|
346
|
-
builder.on(promptInputType, async function* (event, context) {
|
|
347
|
-
const content = typeof event.data?.content === "string" ? event.data.content : "";
|
|
348
|
-
const attachments = Array.isArray(event.data?.attachments) ? event.data.attachments : undefined;
|
|
349
|
-
yield* routeToLLM({ role: "user", content, attachments }, context);
|
|
350
|
-
});
|
|
351
|
-
// Feed action results back as system-role feedback to the model.
|
|
352
|
-
builder.on(actionResultInputType, async function* (event, context) {
|
|
353
|
-
const { action, result, toolCallId, halt } = event.data;
|
|
354
|
-
const normalizedAction = typeof action === "string" ? action : "unknown";
|
|
355
|
-
const summary = typeof result === "string" ? result : JSON.stringify(result);
|
|
356
|
-
const toolResultMessage = {
|
|
357
|
-
role: "tool",
|
|
358
|
-
content: [{
|
|
359
|
-
type: "tool-result",
|
|
360
|
-
toolCallId,
|
|
361
|
-
toolName: normalizedAction,
|
|
362
|
-
output: {
|
|
363
|
-
type: "text",
|
|
364
|
-
value: summary,
|
|
365
|
-
},
|
|
366
|
-
}],
|
|
367
|
-
};
|
|
368
|
-
// Hard-stop mode: record tool result to unblock pending calls, but do not
|
|
369
|
-
// continue the autonomous tool loop in this turn.
|
|
370
|
-
if (halt === true) {
|
|
371
|
-
const state = context.state;
|
|
372
|
-
if (!state.messages) {
|
|
373
|
-
state.messages = [];
|
|
374
|
-
}
|
|
375
|
-
insertToolResult(state.messages, toolResultMessage);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
yield* routeToLLM(toolResultMessage, context);
|
|
379
|
-
});
|
|
380
|
-
};
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { uiEvent } from "../../ui/block.js";
|
|
2
|
-
import * as fs from "node:fs/promises";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { createMemoryModule } from "./memory.js";
|
|
5
|
-
import { buildMemoryPrompt } from "./prompt.js";
|
|
6
|
-
import { statusWidget } from "../../ui/widgets/status.js";
|
|
7
|
-
// Re-exports
|
|
8
|
-
export { memoryToolDefinitions } from "./types.js";
|
|
9
|
-
export { buildMemoryPrompt } from "./prompt.js";
|
|
10
|
-
function expandPath(p) {
|
|
11
|
-
if (p.startsWith("~/")) {
|
|
12
|
-
return path.join(process.env.HOME || "", p.slice(2));
|
|
13
|
-
}
|
|
14
|
-
return p;
|
|
15
|
-
}
|
|
16
|
-
const DEFAULT_AGENT_MD = `# Agent Profile
|
|
17
|
-
|
|
18
|
-
You are the Manager Agent, the central orchestrator of this AI system.
|
|
19
|
-
Your role is to analyze user intent, manage long-term memory, and coordinate specialized agents to solve complex tasks.
|
|
20
|
-
|
|
21
|
-
## Persona
|
|
22
|
-
- Professional yet approachable
|
|
23
|
-
- Highly organized and efficient
|
|
24
|
-
- Focused on providing clear, actionable results
|
|
25
|
-
`;
|
|
26
|
-
/**
|
|
27
|
-
* Create a prompt-builder function bound to a baseDir.
|
|
28
|
-
* Returns the memory's portion of the system prompt (agent definition + memory).
|
|
29
|
-
*/
|
|
30
|
-
export function createMemoryPromptBuilder(baseDir) {
|
|
31
|
-
const expandedBase = expandPath(baseDir);
|
|
32
|
-
const modules = {
|
|
33
|
-
memory: createMemoryModule(expandedBase),
|
|
34
|
-
};
|
|
35
|
-
return async (context) => buildMemoryPrompt(expandedBase, modules, context);
|
|
36
|
-
}
|
|
37
|
-
// --- Plugin ---
|
|
38
|
-
/**
|
|
39
|
-
* Memory Plugin for Melony
|
|
40
|
-
*
|
|
41
|
-
* Provides the bot's "memory": agent definition and long-term memory with recall.
|
|
42
|
-
* Skills are managed by the separate skills plugin.
|
|
43
|
-
*/
|
|
44
|
-
export const memoryPlugin = (options) => (builder) => {
|
|
45
|
-
const { baseDir } = options;
|
|
46
|
-
const expandedBase = expandPath(baseDir);
|
|
47
|
-
// Create sub-modules
|
|
48
|
-
const memory = createMemoryModule(expandedBase);
|
|
49
|
-
// ─── Initialization ───────────────────────────────────────────────
|
|
50
|
-
builder.on("init", async function* (_event, _context) {
|
|
51
|
-
yield {
|
|
52
|
-
type: "memory:status",
|
|
53
|
-
data: { message: "Initializing memory..." },
|
|
54
|
-
};
|
|
55
|
-
await fs.mkdir(expandedBase, { recursive: true, mode: 0o700 });
|
|
56
|
-
// Initialize AGENT.md if it doesn't exist
|
|
57
|
-
const agentPath = path.join(expandedBase, "AGENT.md");
|
|
58
|
-
try {
|
|
59
|
-
await fs.access(agentPath);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
await fs.writeFile(agentPath, DEFAULT_AGENT_MD, "utf-8");
|
|
63
|
-
}
|
|
64
|
-
await memory.initialize();
|
|
65
|
-
yield {
|
|
66
|
-
type: "memory:status",
|
|
67
|
-
data: { message: "Memory initialized", severity: "success" },
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
// ─── Memory: Remember ─────────────────────────────────────────────
|
|
71
|
-
builder.on("action:remember", async function* (event) {
|
|
72
|
-
const { content, tags = [], toolCallId } = event.data;
|
|
73
|
-
try {
|
|
74
|
-
const entry = await memory.store(content, tags);
|
|
75
|
-
yield {
|
|
76
|
-
type: "memory:status",
|
|
77
|
-
data: { message: "Remembered", severity: "success" },
|
|
78
|
-
};
|
|
79
|
-
yield {
|
|
80
|
-
type: "action:result",
|
|
81
|
-
data: {
|
|
82
|
-
action: "remember",
|
|
83
|
-
toolCallId,
|
|
84
|
-
result: {
|
|
85
|
-
success: true,
|
|
86
|
-
memoryId: entry.id,
|
|
87
|
-
message: `Stored in memory with id ${entry.id}`,
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
yield {
|
|
94
|
-
type: "memory:status",
|
|
95
|
-
data: {
|
|
96
|
-
message: `Failed to remember: ${error.message}`,
|
|
97
|
-
severity: "error",
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
yield {
|
|
101
|
-
type: "action:result",
|
|
102
|
-
data: {
|
|
103
|
-
action: "remember",
|
|
104
|
-
toolCallId,
|
|
105
|
-
result: { error: error.message },
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
// ─── Memory: Recall ────────────────────────────────────────────────
|
|
111
|
-
builder.on("action:recall", async function* (event) {
|
|
112
|
-
const { query, tags, limit, toolCallId } = event.data;
|
|
113
|
-
try {
|
|
114
|
-
const results = await memory.recall(query, { tags, limit });
|
|
115
|
-
yield {
|
|
116
|
-
type: "action:result",
|
|
117
|
-
data: {
|
|
118
|
-
action: "recall",
|
|
119
|
-
toolCallId,
|
|
120
|
-
result: {
|
|
121
|
-
count: results.length,
|
|
122
|
-
memories: results.map((e) => ({
|
|
123
|
-
id: e.id,
|
|
124
|
-
content: e.content,
|
|
125
|
-
tags: e.tags,
|
|
126
|
-
createdAt: e.createdAt,
|
|
127
|
-
})),
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
yield {
|
|
134
|
-
type: "action:result",
|
|
135
|
-
data: {
|
|
136
|
-
action: "recall",
|
|
137
|
-
toolCallId,
|
|
138
|
-
result: { error: error.message },
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
// ─── Memory: Forget ────────────────────────────────────────────────
|
|
144
|
-
builder.on("action:forget", async function* (event) {
|
|
145
|
-
const { memoryId, toolCallId } = event.data;
|
|
146
|
-
try {
|
|
147
|
-
const removed = await memory.forget(memoryId);
|
|
148
|
-
yield {
|
|
149
|
-
type: "memory:status",
|
|
150
|
-
data: {
|
|
151
|
-
message: removed ? "Memory removed" : "Memory not found",
|
|
152
|
-
severity: removed ? "success" : "error",
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
yield {
|
|
156
|
-
type: "action:result",
|
|
157
|
-
data: {
|
|
158
|
-
action: "forget",
|
|
159
|
-
toolCallId,
|
|
160
|
-
result: {
|
|
161
|
-
success: removed,
|
|
162
|
-
message: removed
|
|
163
|
-
? "Memory removed"
|
|
164
|
-
: `Memory "${memoryId}" not found`,
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
yield {
|
|
171
|
-
type: "action:result",
|
|
172
|
-
data: {
|
|
173
|
-
action: "forget",
|
|
174
|
-
toolCallId,
|
|
175
|
-
result: { error: error.message },
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
// ─── Memory: Journal ───────────────────────────────────────────────
|
|
181
|
-
builder.on("action:journal", async function* (event) {
|
|
182
|
-
const { content, toolCallId } = event.data;
|
|
183
|
-
try {
|
|
184
|
-
await memory.addJournalEntry(content);
|
|
185
|
-
yield {
|
|
186
|
-
type: "memory:status",
|
|
187
|
-
data: { message: "Journal entry added", severity: "success" },
|
|
188
|
-
};
|
|
189
|
-
yield {
|
|
190
|
-
type: "action:result",
|
|
191
|
-
data: {
|
|
192
|
-
action: "journal",
|
|
193
|
-
toolCallId,
|
|
194
|
-
result: { success: true, message: "Journal entry added" },
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
yield {
|
|
200
|
-
type: "memory:status",
|
|
201
|
-
data: {
|
|
202
|
-
message: `Failed to journal: ${error.message}`,
|
|
203
|
-
severity: "error",
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
yield {
|
|
207
|
-
type: "action:result",
|
|
208
|
-
data: {
|
|
209
|
-
action: "journal",
|
|
210
|
-
toolCallId,
|
|
211
|
-
result: { error: error.message },
|
|
212
|
-
},
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
builder.on("memory:status", async function* (event) {
|
|
217
|
-
yield uiEvent(statusWidget(event.data.message, event.data.severity));
|
|
218
|
-
});
|
|
219
|
-
};
|
|
220
|
-
export default memoryPlugin;
|