openbot 0.2.11 → 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.
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 +10 -9
  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 -275
  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,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
- }