openbot 0.3.6 → 0.4.2

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 (104) hide show
  1. package/README.md +15 -16
  2. package/dist/app/agent-ids.js +4 -0
  3. package/dist/app/cli.js +1 -1
  4. package/dist/app/config.js +10 -19
  5. package/dist/app/server.js +208 -17
  6. package/dist/bus/services.js +34 -124
  7. package/dist/harness/agent-invoke-run.js +44 -0
  8. package/dist/harness/agent-turn.js +99 -0
  9. package/dist/harness/channel-participants.js +40 -0
  10. package/dist/harness/constants.js +2 -0
  11. package/dist/harness/context-meter.js +97 -0
  12. package/dist/harness/context.js +95 -47
  13. package/dist/harness/dispatch.js +144 -0
  14. package/dist/harness/dispatcher.js +45 -156
  15. package/dist/harness/history.js +177 -0
  16. package/dist/harness/index.js +109 -0
  17. package/dist/harness/orchestration.js +88 -0
  18. package/dist/harness/participants.js +22 -0
  19. package/dist/harness/run-harness.js +154 -0
  20. package/dist/harness/run.js +98 -0
  21. package/dist/harness/runtime-factory.js +0 -34
  22. package/dist/harness/runtime.js +57 -0
  23. package/dist/harness/todo-dispatch.js +51 -0
  24. package/dist/harness/todos.js +5 -0
  25. package/dist/harness/turn.js +79 -0
  26. package/dist/plugins/approval/index.js +120 -149
  27. package/dist/plugins/bash/index.js +195 -0
  28. package/dist/plugins/delegation/index.js +121 -32
  29. package/dist/plugins/memory/index.js +103 -14
  30. package/dist/plugins/memory/service.js +152 -0
  31. package/dist/plugins/openbot/context.js +125 -0
  32. package/dist/plugins/openbot/history.js +144 -0
  33. package/dist/plugins/openbot/index.js +71 -0
  34. package/dist/plugins/openbot/runtime.js +381 -0
  35. package/dist/plugins/openbot/system-prompt.js +25 -0
  36. package/dist/plugins/plugin-manager/index.js +189 -0
  37. package/dist/plugins/shell/index.js +2 -1
  38. package/dist/plugins/storage/files.js +67 -0
  39. package/dist/plugins/storage/index.js +750 -0
  40. package/dist/plugins/storage/service.js +1316 -0
  41. package/dist/plugins/storage-tools/index.js +2 -2
  42. package/dist/plugins/thread-namer/index.js +72 -0
  43. package/dist/plugins/thread-naming/generate-title.js +44 -0
  44. package/dist/plugins/thread-naming/index.js +103 -0
  45. package/dist/plugins/threads/index.js +114 -0
  46. package/dist/plugins/todo/index.js +24 -25
  47. package/dist/plugins/ui/index.js +109 -180
  48. package/dist/registry/plugins.js +3 -9
  49. package/dist/services/abort.js +43 -0
  50. package/dist/services/plugins/domain.js +1 -0
  51. package/dist/services/plugins/plugin-cache.js +9 -0
  52. package/dist/services/plugins/registry.js +112 -0
  53. package/dist/services/plugins/service.js +232 -0
  54. package/dist/services/plugins/types.js +1 -0
  55. package/dist/services/process.js +29 -0
  56. package/dist/services/storage.js +11 -10
  57. package/dist/services/thread-naming.js +81 -0
  58. package/docs/agents.md +15 -12
  59. package/docs/architecture.md +2 -2
  60. package/docs/plugins.md +29 -17
  61. package/docs/templates/AGENT.example.md +8 -14
  62. package/package.json +1 -2
  63. package/src/app/agent-ids.ts +5 -0
  64. package/src/app/cli.ts +1 -1
  65. package/src/app/config.ts +14 -31
  66. package/src/app/server.ts +243 -19
  67. package/src/app/types.ts +331 -187
  68. package/src/harness/index.ts +166 -0
  69. package/src/plugins/approval/index.ts +107 -188
  70. package/src/plugins/bash/index.ts +232 -0
  71. package/src/plugins/delegation/index.ts +139 -39
  72. package/src/plugins/memory/index.ts +112 -15
  73. package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
  74. package/src/plugins/openbot/context.ts +140 -0
  75. package/src/plugins/openbot/history.ts +158 -0
  76. package/src/plugins/openbot/index.ts +79 -0
  77. package/src/plugins/openbot/runtime.ts +478 -0
  78. package/src/plugins/openbot/system-prompt.ts +27 -0
  79. package/src/plugins/plugin-manager/index.ts +224 -0
  80. package/src/plugins/storage/files.ts +81 -0
  81. package/src/plugins/storage/index.ts +823 -0
  82. package/src/{services/storage.ts → plugins/storage/service.ts} +485 -105
  83. package/src/plugins/ui/index.ts +117 -221
  84. package/src/services/abort.ts +46 -0
  85. package/src/{bus/types.ts → services/plugins/domain.ts} +50 -8
  86. package/src/services/plugins/plugin-cache.ts +13 -0
  87. package/src/{registry/plugins.ts → services/plugins/registry.ts} +28 -28
  88. package/src/services/plugins/service.ts +318 -0
  89. package/src/{bus/plugin.ts → services/plugins/types.ts} +7 -3
  90. package/src/bus/services.ts +0 -954
  91. package/src/harness/context.ts +0 -365
  92. package/src/harness/dispatcher.ts +0 -379
  93. package/src/harness/mcp.ts +0 -78
  94. package/src/harness/runtime-factory.ts +0 -129
  95. package/src/harness/todo-advance.ts +0 -128
  96. package/src/plugins/ai-sdk/index.ts +0 -41
  97. package/src/plugins/ai-sdk/runtime.ts +0 -468
  98. package/src/plugins/ai-sdk/system-prompt.ts +0 -18
  99. package/src/plugins/mcp/index.ts +0 -128
  100. package/src/plugins/shell/index.ts +0 -123
  101. package/src/plugins/storage-tools/index.ts +0 -90
  102. package/src/plugins/todo/index.ts +0 -64
  103. package/src/services/plugins.ts +0 -133
  104. /package/src/{harness → services}/process.ts +0 -0
@@ -0,0 +1,232 @@
1
+ import { MelonyPlugin } from 'melony';
2
+ import { z } from 'zod';
3
+ import { spawn, ChildProcess } from 'node:child_process';
4
+ import { randomUUID } from 'node:crypto';
5
+ import type { Plugin } from '../../services/plugins/types.js';
6
+ import { OpenBotEvent, OpenBotState } from '../../app/types.js';
7
+ import { resolvePath } from '../../app/config.js';
8
+
9
+ const bashToolDefinitions = {
10
+ bash: {
11
+ description:
12
+ 'Execute a bash command in a stateful session. The working directory and environment variables persist between calls. Use this for all system tasks, file operations, and running development servers.',
13
+ inputSchema: z.object({
14
+ command: z.string().describe('The bash command to execute.'),
15
+ restart: z
16
+ .boolean()
17
+ .optional()
18
+ .describe('Restart the bash session before running the command.'),
19
+ }),
20
+ },
21
+ bash_stop: {
22
+ description: 'Stop the bash session for the current or specified channel.',
23
+ inputSchema: z.object({
24
+ channelId: z.string().optional().describe('The channel ID to stop the session for.'),
25
+ }),
26
+ },
27
+ bash_list_sessions: {
28
+ description: 'List all active bash sessions.',
29
+ inputSchema: z.object({}),
30
+ },
31
+ };
32
+
33
+ interface BashSession {
34
+ process: ChildProcess;
35
+ cwd: string;
36
+ lastActivity: number;
37
+ }
38
+
39
+ const sessions = new Map<string, BashSession>();
40
+
41
+ const getSession = (channelId: string, initialCwd: string): BashSession => {
42
+ let session = sessions.get(channelId);
43
+ if (!session) {
44
+ const childProcess = spawn('bash', ['--login'], {
45
+ cwd: initialCwd,
46
+ env: { ...process.env, PS1: '' },
47
+ stdio: ['pipe', 'pipe', 'pipe'],
48
+ });
49
+
50
+ session = {
51
+ process: childProcess,
52
+ cwd: initialCwd,
53
+ lastActivity: Date.now(),
54
+ };
55
+ sessions.set(channelId, session);
56
+
57
+ // Basic error handling for the process
58
+ childProcess.on('error', (err: Error) => {
59
+ console.error(`[bash] Session error for channel ${channelId}:`, err);
60
+ sessions.delete(channelId);
61
+ });
62
+
63
+ childProcess.on('exit', () => {
64
+ sessions.delete(channelId);
65
+ });
66
+ }
67
+ return session;
68
+ };
69
+
70
+ const bashPluginRuntime = (): MelonyPlugin<OpenBotState, OpenBotEvent> => (builder) => {
71
+ builder.on('action:bash', async function* (event, context) {
72
+ const { command, restart } = event.data;
73
+ const channelId = context.state.channelId;
74
+ const initialCwd = resolvePath(context.state.channelDetails?.cwd || process.cwd());
75
+
76
+ if (restart) {
77
+ const oldSession = sessions.get(channelId);
78
+ if (oldSession) {
79
+ oldSession.process.kill();
80
+ sessions.delete(channelId);
81
+ }
82
+ }
83
+
84
+ const session = getSession(channelId, initialCwd);
85
+ session.lastActivity = Date.now();
86
+
87
+ try {
88
+ const result = await new Promise<{
89
+ exitCode: number | null;
90
+ stdout: string;
91
+ stderr: string;
92
+ timedOut: boolean;
93
+ }>((resolve) => {
94
+ let stdout = '';
95
+ let stderr = '';
96
+ let timedOut = false;
97
+ const sentinel = `__OPENBOT_BASH_DONE_${Math.random().toString(36).substring(7)}__`;
98
+
99
+ const timeoutMs = 60000; // 1 minute timeout for tool calls
100
+ const timer = setTimeout(() => {
101
+ timedOut = true;
102
+ // We don't kill the session on timeout, just return what we have
103
+ resolve({ exitCode: null, stdout, stderr, timedOut });
104
+ }, timeoutMs);
105
+
106
+ const onStdout = (data: Buffer) => {
107
+ const str = data.toString();
108
+ if (str.includes(sentinel)) {
109
+ const parts = str.split(sentinel);
110
+ stdout += parts[0];
111
+ const exitCodeMatch = parts[1].match(/EXIT:(\d+)/);
112
+ const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[1], 10) : 0;
113
+
114
+ cleanup();
115
+ resolve({ exitCode, stdout, stderr, timedOut: false });
116
+ } else {
117
+ stdout += str;
118
+ }
119
+ };
120
+
121
+ const onStderr = (data: Buffer) => {
122
+ stderr += data.toString();
123
+ };
124
+
125
+ const cleanup = () => {
126
+ clearTimeout(timer);
127
+ session.process.stdout?.removeListener('data', onStdout);
128
+ session.process.stderr?.removeListener('data', onStderr);
129
+ };
130
+
131
+ session.process.stdout?.on('data', onStdout);
132
+ session.process.stderr?.on('data', onStderr);
133
+
134
+ // Execute command and then echo the sentinel with exit code
135
+ session.process.stdin?.write(`${command}\necho "${sentinel}EXIT:$?"\n`);
136
+ });
137
+
138
+ yield {
139
+ type: 'action:bash:result',
140
+ data: {
141
+ success: result.exitCode === 0 && !result.timedOut,
142
+ exitCode: result.exitCode,
143
+ stdout: result.stdout.trim(),
144
+ stderr: result.stderr.trim(),
145
+ timedOut: result.timedOut,
146
+ output: result.stderr.trim() ? result.stderr.trim() : result.stdout.trim(),
147
+ },
148
+ meta: event.meta,
149
+ } as OpenBotEvent;
150
+ } catch (error) {
151
+ const message = error instanceof Error ? error.message : 'Unknown bash error';
152
+ yield {
153
+ type: 'action:bash:result',
154
+ data: {
155
+ success: false,
156
+ exitCode: -1,
157
+ stdout: '',
158
+ stderr: message,
159
+ timedOut: false,
160
+ error: message,
161
+ output: message,
162
+ },
163
+ meta: event.meta,
164
+ } as OpenBotEvent;
165
+ }
166
+ });
167
+
168
+ // Add a tool to stop/kill the session
169
+ builder.on('action:bash_stop', async function* (event, context) {
170
+ const channelId = event.data?.channelId || context.state.channelId;
171
+ const session = sessions.get(channelId);
172
+ if (session) {
173
+ session.process.kill();
174
+ sessions.delete(channelId);
175
+ }
176
+ yield {
177
+ type: 'action:bash_stop:result',
178
+ data: { success: true, output: `Bash session for channel ${channelId} stopped.` },
179
+ meta: event.meta,
180
+ } as OpenBotEvent;
181
+ });
182
+
183
+ // Add a tool to list all active sessions
184
+ builder.on('action:bash_list_sessions', async function* (event, context) {
185
+ const activeSessions = Array.from(sessions.entries()).map(([channelId, session]) => ({
186
+ channelId,
187
+ cwd: session.cwd,
188
+ lastActivity: session.lastActivity,
189
+ }));
190
+
191
+ yield {
192
+ type: 'client:ui:widget',
193
+ data: {
194
+ widgetId: randomUUID(),
195
+ kind: 'list',
196
+ title: 'Active Bash Sessions',
197
+ description: `Found ${activeSessions.length} active bash session${activeSessions.length === 1 ? '' : 's'}.`,
198
+ items: activeSessions.map((s) => ({
199
+ id: s.channelId,
200
+ label: s.channelId,
201
+ description: `CWD: ${s.cwd}`,
202
+ status: 'done',
203
+ metadata: {
204
+ cwd: s.cwd,
205
+ lastActivity: s.lastActivity,
206
+ },
207
+ })),
208
+ },
209
+ meta: event.meta,
210
+ } as OpenBotEvent;
211
+
212
+ yield {
213
+ type: 'action:bash_list_sessions:result',
214
+ data: {
215
+ success: true,
216
+ sessions: activeSessions,
217
+ output: JSON.stringify(activeSessions),
218
+ },
219
+ meta: event.meta,
220
+ } as OpenBotEvent;
221
+ });
222
+ };
223
+
224
+ export const bashPlugin: Plugin = {
225
+ id: 'bash',
226
+ name: 'Bash',
227
+ description: 'Stateful bash session for the channel.',
228
+ toolDefinitions: bashToolDefinitions,
229
+ factory: () => bashPluginRuntime(),
230
+ };
231
+
232
+ export default bashPlugin;
@@ -1,51 +1,151 @@
1
- import { MelonyPlugin } from 'melony';
2
- import z from 'zod';
3
- import type { Plugin } from '../../bus/plugin.js';
4
- import { OpenBotEvent, OpenBotState } from '../../app/types.js';
5
-
6
- const handoffToolDefinitions = {
7
- handoff: {
8
- description:
9
- 'Transfer control to another agent. The target agent continues the task in this thread.',
1
+ import { z } from 'zod';
2
+ import { generateId } from 'melony';
3
+ import type { Plugin } from '../../services/plugins/types.js';
4
+ import {
5
+ OpenBotEvent,
6
+ DelegateTaskEvent,
7
+ } from '../../app/types.js';
8
+
9
+ /**
10
+ * `delegation` — allows agents to delegate tasks to other agents.
11
+ *
12
+ * Only the 'system' agent is allowed to delegate by default.
13
+ * It uses runAgent to execute the delegated agent in its own isolated runtime,
14
+ * bridging events back to the caller's stream.
15
+ */
16
+
17
+ const delegationToolDefinitions = {
18
+ delegate_task: {
19
+ description: 'Delegate a specific task or question to another specialized agent.',
10
20
  inputSchema: z.object({
11
- agentId: z.string().describe('The ID of the target agent.'),
12
- content: z.string().describe('The message or task to hand off.'),
21
+ agentId: z.string().describe('The ID of the agent to delegate to (e.g., "researcher", "coder").'),
22
+ prompt: z.string().describe('The instructions or question for the delegated agent.'),
13
23
  }),
14
24
  },
15
25
  };
16
26
 
17
- const handoffPluginRuntime = (): MelonyPlugin<OpenBotState, OpenBotEvent> => (builder) => {
18
- builder.on('action:handoff', async function* (event, context) {
19
- const { agentId, content } = event.data;
27
+ export const delegationPlugin: Plugin = {
28
+ id: 'delegation',
29
+ name: 'Delegation',
30
+ description: 'Allows agents to call upon other agents to solve sub-tasks.',
31
+ toolDefinitions: delegationToolDefinitions,
32
+ factory: (pluginContext) => (builder) => {
20
33
 
21
- yield {
22
- type: 'agent:output',
23
- data: { content: `Handing off to **${agentId}**: ${content}` },
24
- meta: { ...(event.meta || {}), agentId: context.state.agentId },
25
- };
34
+ // Handle the tool execution
35
+ builder.on('action:delegate_task', async function* (event, context) {
36
+ const delegateEvent = event as DelegateTaskEvent;
26
37
 
27
- yield {
28
- type: 'handoff:request',
29
- data: { agentId, content },
30
- meta: { ...(event.meta || {}), agentId: context.state.agentId },
31
- };
38
+ // POLICY: Only the 'system' agent can delegate
39
+ if (context.state.agentId !== 'system') {
40
+ yield {
41
+ type: 'action:delegate_task:result',
42
+ data: {
43
+ success: false,
44
+ error: 'Only the system agent can delegate.'
45
+ },
46
+ meta: delegateEvent.meta,
47
+ } as OpenBotEvent;
48
+ return;
49
+ }
32
50
 
33
- if (event.meta?.toolCallId) {
34
- yield {
35
- type: 'action:handoff:result',
36
- data: { success: true, agentId, accepted: true },
37
- meta: { ...(event.meta || {}), agentId: context.state.agentId },
38
- };
39
- }
40
- });
41
- };
51
+ const { agentId, prompt } = delegateEvent.data;
52
+ const toolCallId = delegateEvent.meta?.toolCallId;
42
53
 
43
- export const delegationPlugin: Plugin = {
44
- id: 'delegation',
45
- name: 'Handoff',
46
- description: 'Hand off tasks to other agents on the bus.',
47
- toolDefinitions: handoffToolDefinitions,
48
- factory: () => handoffPluginRuntime(),
54
+ if (!toolCallId) return;
55
+
56
+ // Break circular dependency by dynamic import
57
+ const { runAgent } = await import('../../harness/index.js');
58
+
59
+ const runId = `dg_${generateId()}`;
60
+ let lastAgentOutput = '';
61
+
62
+ // Queue to bridge the async onEvent callback to this generator
63
+ const eventQueue: OpenBotEvent[] = [];
64
+ let resolveNext: (() => void) | null = null;
65
+ let isFinished = false;
66
+
67
+ // Start the delegated agent in its own runtime.
68
+ // We don't await this immediately so we can yield events as they arrive.
69
+ const runPromise = runAgent({
70
+ runId,
71
+ agentId,
72
+ event: {
73
+ type: 'agent:invoke',
74
+ data: {
75
+ role: 'user',
76
+ content: prompt,
77
+ agentId: agentId,
78
+ },
79
+ meta: {
80
+ threadId: context.state.threadId,
81
+ parentAgentId: context.state.agentId,
82
+ parentToolCallId: toolCallId,
83
+ },
84
+ } as OpenBotEvent,
85
+ channelId: context.state.channelId,
86
+ threadId: context.state.threadId,
87
+ publicBaseUrl: pluginContext.publicBaseUrl,
88
+ onEvent: async (outEvent) => {
89
+ // Enrich events with parent metadata so the UI can track the hierarchy
90
+ const enrichedEvent = {
91
+ ...outEvent,
92
+ meta: {
93
+ ...outEvent.meta,
94
+ parentAgentId: context.state.agentId,
95
+ parentToolCallId: toolCallId,
96
+ }
97
+ };
98
+
99
+ eventQueue.push(enrichedEvent);
100
+
101
+ if (outEvent.type === 'agent:output') {
102
+ lastAgentOutput = outEvent.data.content;
103
+ }
104
+
105
+ // Wake up the generator loop if it's waiting
106
+ if (resolveNext) {
107
+ resolveNext();
108
+ resolveNext = null;
109
+ }
110
+ }
111
+ }).catch(error => {
112
+ console.error(`[delegation] Error in delegated run ${runId}:`, error);
113
+ }).finally(() => {
114
+ isFinished = true;
115
+ if (resolveNext) {
116
+ resolveNext();
117
+ resolveNext = null;
118
+ }
119
+ });
120
+
121
+ // Yield events from the delegated agent as they arrive
122
+ while (!isFinished || eventQueue.length > 0) {
123
+ if (eventQueue.length === 0) {
124
+ await new Promise<void>(r => { resolveNext = r; });
125
+ }
126
+ while (eventQueue.length > 0) {
127
+ yield eventQueue.shift()!;
128
+ }
129
+ }
130
+
131
+ // Ensure the run is fully complete (though isFinished already implies this)
132
+ await runPromise;
133
+
134
+ // Yield the result back to our own LLM runtime.
135
+ yield {
136
+ type: 'action:delegate_task:result',
137
+ data: {
138
+ success: true,
139
+ output: lastAgentOutput,
140
+ },
141
+ meta: {
142
+ ...delegateEvent.meta,
143
+ agentId: context.state.agentId,
144
+ toolCallId: toolCallId,
145
+ },
146
+ } as OpenBotEvent;
147
+ });
148
+ },
49
149
  };
50
150
 
51
151
  export default delegationPlugin;
@@ -1,19 +1,42 @@
1
1
  import z from 'zod';
2
- import type { Plugin } from '../../bus/plugin.js';
2
+ import type { Plugin } from '../../services/plugins/types.js';
3
+ import { OpenBotEvent, MemoryScopeAlias } from '../../app/types.js';
3
4
 
4
5
  /**
5
- * `memory` exposes the global memory store as agent tools.
6
- *
7
- * The actual handlers live in `bus/services.ts` because memory is platform
8
- * infrastructure (shared across every agent on the bus); this plugin only
9
- * contributes the tool definitions so a runtime plugin (e.g. `ai-sdk`) can
10
- * surface them to the LLM.
11
- *
12
- * Scopes
13
- * ------
14
- * - `global` (default) — visible to every agent and channel.
15
- * - `agent` — visible only to the agent that wrote it.
16
- * - `channel` — visible only inside the active channel.
6
+ * Resolve a scope alias to a concrete scope string. Aliases let tools accept
7
+ * `agent`/`channel`/`global` without knowing the active ids; the bus rewrites
8
+ * them using `context.state`.
9
+ */
10
+ function resolveMemoryScope(
11
+ alias: MemoryScopeAlias | undefined,
12
+ state: any,
13
+ ): string {
14
+ switch (alias) {
15
+ case 'agent':
16
+ return `agent:${state.agentId}`;
17
+ case 'channel':
18
+ return `channel:${state.channelId}`;
19
+ case 'global':
20
+ case undefined:
21
+ return 'global';
22
+ default:
23
+ return 'global';
24
+ }
25
+ }
26
+
27
+ function resolveMemoryScopeFilter(
28
+ alias: MemoryScopeAlias | 'all' | undefined,
29
+ state: any,
30
+ ): string[] | undefined {
31
+ if (alias === 'all' || alias === undefined) {
32
+ return ['global', `agent:${state.agentId}`, `channel:${state.channelId}`];
33
+ }
34
+ return [resolveMemoryScope(alias, state)];
35
+ }
36
+
37
+ /**
38
+ * `memory` — exposes the global memory store as agent tools and provides
39
+ * platform-level memory handlers.
17
40
  */
18
41
  const memoryToolDefinitions = {
19
42
  remember: {
@@ -77,8 +100,82 @@ export const memoryPlugin: Plugin = {
77
100
  description:
78
101
  'Global long-term memory: remember/recall/forget facts across runs and agents.',
79
102
  toolDefinitions: memoryToolDefinitions,
80
- factory: () => () => {
81
- // Handlers live in bus/services.ts; this plugin only contributes tool definitions.
103
+ factory: ({ storage }) => (builder) => {
104
+ builder.on('action:remember', async function* (event, context) {
105
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
106
+ try {
107
+ const { content, scope, tags } = event.data;
108
+ const record = await storage.appendMemory({
109
+ scope: resolveMemoryScope(scope, context.state),
110
+ content,
111
+ tags,
112
+ });
113
+ yield {
114
+ type: 'action:remember:result',
115
+ data: { success: true, record },
116
+ meta: resultMeta,
117
+ } as OpenBotEvent;
118
+ } catch (error) {
119
+ yield {
120
+ type: 'action:remember:result',
121
+ data: {
122
+ success: false,
123
+ error: error instanceof Error ? error.message : 'Unknown error',
124
+ },
125
+ meta: resultMeta,
126
+ } as OpenBotEvent;
127
+ }
128
+ });
129
+
130
+ builder.on('action:recall', async function* (event, context) {
131
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
132
+ try {
133
+ const { query, tag, scope, limit } = event.data;
134
+ const records = await storage.listMemories({
135
+ scopes: resolveMemoryScopeFilter(scope, context.state),
136
+ query,
137
+ tag,
138
+ limit,
139
+ });
140
+ yield {
141
+ type: 'action:recall:result',
142
+ data: { success: true, records },
143
+ meta: resultMeta,
144
+ } as OpenBotEvent;
145
+ } catch (error) {
146
+ yield {
147
+ type: 'action:recall:result',
148
+ data: {
149
+ success: false,
150
+ records: [],
151
+ error: error instanceof Error ? error.message : 'Unknown error',
152
+ },
153
+ meta: resultMeta,
154
+ } as OpenBotEvent;
155
+ }
156
+ });
157
+
158
+ builder.on('action:forget', async function* (event, context) {
159
+ const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
160
+ try {
161
+ const deleted = await storage.deleteMemory({ id: event.data.id });
162
+ yield {
163
+ type: 'action:forget:result',
164
+ data: { success: true, deleted },
165
+ meta: resultMeta,
166
+ } as OpenBotEvent;
167
+ } catch (error) {
168
+ yield {
169
+ type: 'action:forget:result',
170
+ data: {
171
+ success: false,
172
+ deleted: false,
173
+ error: error instanceof Error ? error.message : 'Unknown error',
174
+ },
175
+ meta: resultMeta,
176
+ } as OpenBotEvent;
177
+ }
178
+ });
82
179
  },
83
180
  };
84
181
 
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
- import { DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../app/config.js';
4
+ import { DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../../app/config.js';
5
5
 
6
6
  /**
7
7
  * Global memory service.