openbot 0.3.0 → 0.3.1
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/dist/app/cli.js +1 -1
- package/dist/bus/services.js +116 -5
- package/dist/harness/context.js +140 -21
- package/dist/plugins/ai-sdk/runtime.js +74 -2
- package/dist/plugins/memory/index.js +71 -0
- package/dist/registry/plugins.js +2 -0
- package/dist/services/memory.js +152 -0
- package/dist/services/storage.js +6 -0
- package/docs/agents.md +15 -1
- package/package.json +1 -1
- package/src/app/cli.ts +1 -1
- package/src/app/types.ts +61 -1
- package/src/bus/services.ts +126 -6
- package/src/bus/types.ts +13 -0
- package/src/harness/context.ts +162 -29
- package/src/plugins/ai-sdk/runtime.ts +76 -2
- package/src/plugins/memory/index.ts +85 -0
- package/src/registry/plugins.ts +2 -0
- package/src/services/memory.ts +213 -0
- package/src/services/storage.ts +7 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { DEFAULT_BASE_DIR, loadConfig, resolvePath } from '../app/config.js';
|
|
5
|
+
const DEFAULT_LIMIT = 50;
|
|
6
|
+
const MAX_LIMIT = 500;
|
|
7
|
+
const getMemoryDir = () => {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
return path.join(resolvePath(config.baseDir || DEFAULT_BASE_DIR), 'memory');
|
|
10
|
+
};
|
|
11
|
+
const getLogPath = () => path.join(getMemoryDir(), 'log.jsonl');
|
|
12
|
+
const ensureDir = async () => {
|
|
13
|
+
await fs.mkdir(getMemoryDir(), { recursive: true });
|
|
14
|
+
};
|
|
15
|
+
const readLog = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const raw = await fs.readFile(getLogPath(), 'utf-8');
|
|
18
|
+
return raw
|
|
19
|
+
.split(/\r?\n/)
|
|
20
|
+
.map((line) => line.trim())
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((line) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(line);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
.filter((e) => !!e);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (e?.code === 'ENOENT')
|
|
34
|
+
return [];
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const replay = (entries) => {
|
|
39
|
+
const out = new Map();
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (entry.op === 'add') {
|
|
42
|
+
out.set(entry.record.id, entry.record);
|
|
43
|
+
}
|
|
44
|
+
else if (entry.op === 'delete') {
|
|
45
|
+
out.delete(entry.id);
|
|
46
|
+
}
|
|
47
|
+
else if (entry.op === 'update') {
|
|
48
|
+
const existing = out.get(entry.id);
|
|
49
|
+
if (!existing)
|
|
50
|
+
continue;
|
|
51
|
+
out.set(entry.id, {
|
|
52
|
+
...existing,
|
|
53
|
+
...entry.patch,
|
|
54
|
+
id: existing.id,
|
|
55
|
+
updatedAt: entry.at,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
};
|
|
61
|
+
const appendEntry = async (entry) => {
|
|
62
|
+
await ensureDir();
|
|
63
|
+
await fs.appendFile(getLogPath(), `${JSON.stringify(entry)}\n`, 'utf-8');
|
|
64
|
+
};
|
|
65
|
+
const matchesQuery = (record, query, tag) => {
|
|
66
|
+
if (tag) {
|
|
67
|
+
if (!record.tags || !record.tags.includes(tag))
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (query) {
|
|
71
|
+
const q = query.toLowerCase();
|
|
72
|
+
if (!record.content.toLowerCase().includes(q))
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
export const memoryService = {
|
|
78
|
+
appendMemory: async (args) => {
|
|
79
|
+
const now = new Date().toISOString();
|
|
80
|
+
const record = {
|
|
81
|
+
id: crypto.randomUUID(),
|
|
82
|
+
scope: args.scope,
|
|
83
|
+
content: args.content,
|
|
84
|
+
tags: args.tags?.length ? args.tags : undefined,
|
|
85
|
+
createdAt: now,
|
|
86
|
+
updatedAt: now,
|
|
87
|
+
};
|
|
88
|
+
await appendEntry({ op: 'add', record });
|
|
89
|
+
return record;
|
|
90
|
+
},
|
|
91
|
+
updateMemory: async (args) => {
|
|
92
|
+
const entries = await readLog();
|
|
93
|
+
const map = replay(entries);
|
|
94
|
+
if (!map.has(args.id))
|
|
95
|
+
return false;
|
|
96
|
+
const at = new Date().toISOString();
|
|
97
|
+
const patch = {};
|
|
98
|
+
if (args.content !== undefined)
|
|
99
|
+
patch.content = args.content;
|
|
100
|
+
if (args.tags !== undefined)
|
|
101
|
+
patch.tags = args.tags.length ? args.tags : undefined;
|
|
102
|
+
if (Object.keys(patch).length === 0)
|
|
103
|
+
return true;
|
|
104
|
+
await appendEntry({ op: 'update', id: args.id, patch, at });
|
|
105
|
+
return true;
|
|
106
|
+
},
|
|
107
|
+
deleteMemory: async (args) => {
|
|
108
|
+
const entries = await readLog();
|
|
109
|
+
const map = replay(entries);
|
|
110
|
+
if (!map.has(args.id))
|
|
111
|
+
return false;
|
|
112
|
+
await appendEntry({ op: 'delete', id: args.id, at: new Date().toISOString() });
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
listMemories: async (args = {}) => {
|
|
116
|
+
const entries = await readLog();
|
|
117
|
+
const map = replay(entries);
|
|
118
|
+
const limit = Math.min(Math.max(args.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
|
|
119
|
+
const scopeSet = (() => {
|
|
120
|
+
if (args.scope)
|
|
121
|
+
return new Set([args.scope]);
|
|
122
|
+
if (args.scopes && args.scopes.length > 0)
|
|
123
|
+
return new Set(args.scopes);
|
|
124
|
+
return null;
|
|
125
|
+
})();
|
|
126
|
+
const filtered = [];
|
|
127
|
+
for (const record of map.values()) {
|
|
128
|
+
if (scopeSet && !scopeSet.has(record.scope))
|
|
129
|
+
continue;
|
|
130
|
+
if (!matchesQuery(record, args.query, args.tag))
|
|
131
|
+
continue;
|
|
132
|
+
filtered.push(record);
|
|
133
|
+
}
|
|
134
|
+
filtered.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
|
|
135
|
+
return filtered.slice(0, limit);
|
|
136
|
+
},
|
|
137
|
+
/**
|
|
138
|
+
* Compact the log into a single `add` per surviving record. Cheap to call
|
|
139
|
+
* occasionally; not required for correctness.
|
|
140
|
+
*/
|
|
141
|
+
compact: async () => {
|
|
142
|
+
const entries = await readLog();
|
|
143
|
+
const map = replay(entries);
|
|
144
|
+
const surviving = Array.from(map.values());
|
|
145
|
+
await ensureDir();
|
|
146
|
+
const tmp = `${getLogPath()}.tmp`;
|
|
147
|
+
const body = surviving.map((record) => JSON.stringify({ op: 'add', record })).join('\n');
|
|
148
|
+
await fs.writeFile(tmp, body ? `${body}\n` : '', 'utf-8');
|
|
149
|
+
await fs.rename(tmp, getLogPath());
|
|
150
|
+
return surviving.length;
|
|
151
|
+
},
|
|
152
|
+
};
|
package/dist/services/storage.js
CHANGED
|
@@ -7,6 +7,7 @@ import { aiSdkPlugin } from '../plugins/ai-sdk/index.js';
|
|
|
7
7
|
import { AI_SDK_SYSTEM_PROMPT } from '../plugins/ai-sdk/system-prompt.js';
|
|
8
8
|
import { listBuiltInPlugins, parsePluginModule } from '../registry/plugins.js';
|
|
9
9
|
import { processService } from '../harness/process.js';
|
|
10
|
+
import { memoryService } from './memory.js';
|
|
10
11
|
import { pathToFileURL } from 'node:url';
|
|
11
12
|
const resolveBaseDir = () => {
|
|
12
13
|
const config = loadConfig();
|
|
@@ -65,6 +66,7 @@ const SYSTEM_DEFAULT_PLUGINS = [
|
|
|
65
66
|
{ id: 'delegation' },
|
|
66
67
|
// { id: 'ui' },
|
|
67
68
|
{ id: 'approval' },
|
|
69
|
+
{ id: 'memory' },
|
|
68
70
|
];
|
|
69
71
|
function getSystemAgentDetails(overrides) {
|
|
70
72
|
const defaults = {
|
|
@@ -935,6 +937,10 @@ export const storageService = {
|
|
|
935
937
|
}
|
|
936
938
|
return fs.readFile(targetFile, 'utf-8');
|
|
937
939
|
},
|
|
940
|
+
appendMemory: memoryService.appendMemory,
|
|
941
|
+
listMemories: memoryService.listMemories,
|
|
942
|
+
deleteMemory: memoryService.deleteMemory,
|
|
943
|
+
updateMemory: memoryService.updateMemory,
|
|
938
944
|
/**
|
|
939
945
|
* Hydrates the full OpenBot state from disk/storage before a run.
|
|
940
946
|
*/
|
package/docs/agents.md
CHANGED
|
@@ -47,7 +47,21 @@ plugins like `shell` or `mcp` to them has no effect. Pair tool plugins with
|
|
|
47
47
|
|
|
48
48
|
OpenBot ships a built-in `system` agent (the orchestrator) with the
|
|
49
49
|
`ai-sdk` runtime plus the standard tool plugins (storage, shell, mcp,
|
|
50
|
-
delegation, ui, approval). It cannot be deleted.
|
|
50
|
+
delegation, ui, approval, memory). It cannot be deleted.
|
|
51
|
+
|
|
52
|
+
## Memory
|
|
53
|
+
|
|
54
|
+
The `memory` plugin gives every agent three tools — `remember`, `recall`,
|
|
55
|
+
`forget` — backed by an append-only JSONL log at `~/.openbot/memory/log.jsonl`.
|
|
56
|
+
Memories are scoped:
|
|
57
|
+
|
|
58
|
+
- `global` (default) — visible to every agent everywhere.
|
|
59
|
+
- `agent` — visible only to the agent that wrote it.
|
|
60
|
+
- `channel` — visible only inside the active channel.
|
|
61
|
+
|
|
62
|
+
On every LLM turn the runtime injects matching memories into the system prompt
|
|
63
|
+
via the `MemoryProvider` in the context engine, so the model treats remembered
|
|
64
|
+
facts as ground truth without needing to call `recall` first.
|
|
51
65
|
|
|
52
66
|
## Installing community agents
|
|
53
67
|
|
package/package.json
CHANGED
package/src/app/cli.ts
CHANGED
package/src/app/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
ThreadDetails,
|
|
9
9
|
} from '../bus/types.js';
|
|
10
10
|
import type { PluginRef } from '../bus/plugin.js';
|
|
11
|
+
import type { MemoryRecord } from '../services/memory.js';
|
|
11
12
|
|
|
12
13
|
export interface OpenBotState {
|
|
13
14
|
agentId: string;
|
|
@@ -766,6 +767,59 @@ export type InstallAgentResultEvent = BaseEvent & {
|
|
|
766
767
|
};
|
|
767
768
|
};
|
|
768
769
|
|
|
770
|
+
export type MemoryScopeAlias = 'global' | 'agent' | 'channel';
|
|
771
|
+
|
|
772
|
+
export type RememberEvent = BaseEvent & {
|
|
773
|
+
type: 'action:remember';
|
|
774
|
+
data: {
|
|
775
|
+
content: string;
|
|
776
|
+
scope?: MemoryScopeAlias;
|
|
777
|
+
tags?: string[];
|
|
778
|
+
};
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
export type RememberResultEvent = BaseEvent & {
|
|
782
|
+
type: 'action:remember:result';
|
|
783
|
+
data: {
|
|
784
|
+
success: boolean;
|
|
785
|
+
record?: MemoryRecord;
|
|
786
|
+
error?: string;
|
|
787
|
+
};
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
export type RecallEvent = BaseEvent & {
|
|
791
|
+
type: 'action:recall';
|
|
792
|
+
data: {
|
|
793
|
+
query?: string;
|
|
794
|
+
tag?: string;
|
|
795
|
+
scope?: MemoryScopeAlias | 'all';
|
|
796
|
+
limit?: number;
|
|
797
|
+
};
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
export type RecallResultEvent = BaseEvent & {
|
|
801
|
+
type: 'action:recall:result';
|
|
802
|
+
data: {
|
|
803
|
+
success: boolean;
|
|
804
|
+
records: MemoryRecord[];
|
|
805
|
+
error?: string;
|
|
806
|
+
};
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
export type ForgetEvent = BaseEvent & {
|
|
810
|
+
type: 'action:forget';
|
|
811
|
+
data: { id: string };
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
export type ForgetResultEvent = BaseEvent & {
|
|
815
|
+
type: 'action:forget:result';
|
|
816
|
+
data: {
|
|
817
|
+
success: boolean;
|
|
818
|
+
deleted: boolean;
|
|
819
|
+
error?: string;
|
|
820
|
+
};
|
|
821
|
+
};
|
|
822
|
+
|
|
769
823
|
export type OpenBotEvent =
|
|
770
824
|
| UserInputEvent
|
|
771
825
|
| AgentInvokeEvent
|
|
@@ -842,4 +896,10 @@ export type OpenBotEvent =
|
|
|
842
896
|
| ListMarketplaceAgentsEvent
|
|
843
897
|
| ListMarketplaceAgentsResultEvent
|
|
844
898
|
| InstallAgentEvent
|
|
845
|
-
| InstallAgentResultEvent
|
|
899
|
+
| InstallAgentResultEvent
|
|
900
|
+
| RememberEvent
|
|
901
|
+
| RememberResultEvent
|
|
902
|
+
| RecallEvent
|
|
903
|
+
| RecallResultEvent
|
|
904
|
+
| ForgetEvent
|
|
905
|
+
| ForgetResultEvent;
|
package/src/bus/services.ts
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import { MelonyPlugin } from 'melony';
|
|
2
2
|
import { DEFAULT_MARKETPLACE_REGISTRY_URL, loadConfig } from '../app/config.js';
|
|
3
|
-
import { OpenBotEvent, OpenBotState } from '../app/types.js';
|
|
3
|
+
import { OpenBotEvent, OpenBotState, MemoryScopeAlias } from '../app/types.js';
|
|
4
4
|
import type { PluginRef } from './plugin.js';
|
|
5
5
|
import { Storage } from './types.js';
|
|
6
6
|
import { storageService } from '../services/storage.js';
|
|
7
7
|
import { pluginService } from '../services/plugins.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a scope alias to a concrete scope string. Aliases let tools accept
|
|
11
|
+
* `agent`/`channel`/`global` without knowing the active ids; the bus rewrites
|
|
12
|
+
* them using `context.state`.
|
|
13
|
+
*/
|
|
14
|
+
function resolveMemoryScope(
|
|
15
|
+
alias: MemoryScopeAlias | undefined,
|
|
16
|
+
state: OpenBotState,
|
|
17
|
+
): string {
|
|
18
|
+
switch (alias) {
|
|
19
|
+
case 'agent':
|
|
20
|
+
return `agent:${state.agentId}`;
|
|
21
|
+
case 'channel':
|
|
22
|
+
return `channel:${state.channelId}`;
|
|
23
|
+
case 'global':
|
|
24
|
+
case undefined:
|
|
25
|
+
return 'global';
|
|
26
|
+
default:
|
|
27
|
+
return 'global';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveMemoryScopeFilter(
|
|
32
|
+
alias: MemoryScopeAlias | 'all' | undefined,
|
|
33
|
+
state: OpenBotState,
|
|
34
|
+
): string[] | undefined {
|
|
35
|
+
if (alias === 'all' || alias === undefined) {
|
|
36
|
+
return ['global', `agent:${state.agentId}`, `channel:${state.channelId}`];
|
|
37
|
+
}
|
|
38
|
+
return [resolveMemoryScope(alias, state)];
|
|
39
|
+
}
|
|
40
|
+
|
|
9
41
|
/** One marketplace entry; matches `action:marketplace:list:result` agent shape. */
|
|
10
42
|
export type MarketplaceAgentListing = {
|
|
11
43
|
id: string;
|
|
@@ -156,7 +188,7 @@ export const busServicesPlugin =
|
|
|
156
188
|
yield {
|
|
157
189
|
type: 'action:create_thread:result',
|
|
158
190
|
data: { success: true, threadId, threadTitle },
|
|
159
|
-
meta: { threadId },
|
|
191
|
+
meta: { ...(event.meta || {}), threadId, agentId: context.state.agentId },
|
|
160
192
|
} as OpenBotEvent;
|
|
161
193
|
});
|
|
162
194
|
|
|
@@ -165,10 +197,13 @@ export const busServicesPlugin =
|
|
|
165
197
|
const rawChannelId = (channelId || '').trim();
|
|
166
198
|
const channelSpec = typeof spec === 'string' ? spec : '';
|
|
167
199
|
|
|
200
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
201
|
+
|
|
168
202
|
if (!rawChannelId) {
|
|
169
203
|
yield {
|
|
170
204
|
type: 'action:create_channel:result',
|
|
171
205
|
data: { success: false, channelId: '', channelUrl: '' },
|
|
206
|
+
meta: resultMeta,
|
|
172
207
|
} as OpenBotEvent;
|
|
173
208
|
return;
|
|
174
209
|
}
|
|
@@ -186,20 +221,19 @@ export const busServicesPlugin =
|
|
|
186
221
|
yield {
|
|
187
222
|
type: 'action:create_channel:result',
|
|
188
223
|
data: { success: true, channelId: rawChannelId, channelUrl },
|
|
224
|
+
meta: resultMeta,
|
|
189
225
|
} as OpenBotEvent;
|
|
190
226
|
|
|
191
227
|
yield {
|
|
192
228
|
type: 'agent:output',
|
|
193
229
|
data: { content: `Created channel \`${rawChannelId}\`.` },
|
|
194
|
-
meta:
|
|
195
|
-
...(event.meta || {}),
|
|
196
|
-
agentId: context.state.agentId,
|
|
197
|
-
},
|
|
230
|
+
meta: resultMeta,
|
|
198
231
|
} as OpenBotEvent;
|
|
199
232
|
} catch {
|
|
200
233
|
yield {
|
|
201
234
|
type: 'action:create_channel:result',
|
|
202
235
|
data: { success: false, channelId: rawChannelId, channelUrl },
|
|
236
|
+
meta: resultMeta,
|
|
203
237
|
} as OpenBotEvent;
|
|
204
238
|
}
|
|
205
239
|
});
|
|
@@ -207,11 +241,13 @@ export const busServicesPlugin =
|
|
|
207
241
|
builder.on('action:update_channel', async function* (event, context) {
|
|
208
242
|
const data = (event.data || {}) as { channelId?: string; name?: string; cwd?: string };
|
|
209
243
|
const targetChannelId = (data.channelId || context.state.channelId || '').trim();
|
|
244
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
210
245
|
|
|
211
246
|
if (!targetChannelId) {
|
|
212
247
|
yield {
|
|
213
248
|
type: 'action:update_channel:result',
|
|
214
249
|
data: { success: false, channelId: '', updatedFields: [] as string[] },
|
|
250
|
+
meta: resultMeta,
|
|
215
251
|
} as OpenBotEvent;
|
|
216
252
|
return;
|
|
217
253
|
}
|
|
@@ -242,17 +278,20 @@ export const busServicesPlugin =
|
|
|
242
278
|
yield {
|
|
243
279
|
type: 'action:update_channel:result',
|
|
244
280
|
data: { success: true, channelId: targetChannelId, updatedFields },
|
|
281
|
+
meta: resultMeta,
|
|
245
282
|
} as OpenBotEvent;
|
|
246
283
|
} catch {
|
|
247
284
|
yield {
|
|
248
285
|
type: 'action:update_channel:result',
|
|
249
286
|
data: { success: false, channelId: targetChannelId, updatedFields },
|
|
287
|
+
meta: resultMeta,
|
|
250
288
|
} as OpenBotEvent;
|
|
251
289
|
}
|
|
252
290
|
});
|
|
253
291
|
|
|
254
292
|
builder.on('action:patch_channel_details', async function* (event, context) {
|
|
255
293
|
const updatedFields: ('state' | 'spec' | 'cwd')[] = [];
|
|
294
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
256
295
|
try {
|
|
257
296
|
if ((event.data as any).state !== undefined) {
|
|
258
297
|
await storage.patchChannelState({
|
|
@@ -283,17 +322,20 @@ export const busServicesPlugin =
|
|
|
283
322
|
yield {
|
|
284
323
|
type: 'action:patch_channel_details:result',
|
|
285
324
|
data: { success: true, updatedFields },
|
|
325
|
+
meta: resultMeta,
|
|
286
326
|
};
|
|
287
327
|
} catch {
|
|
288
328
|
yield {
|
|
289
329
|
type: 'action:patch_channel_details:result',
|
|
290
330
|
data: { success: false, updatedFields },
|
|
331
|
+
meta: resultMeta,
|
|
291
332
|
};
|
|
292
333
|
}
|
|
293
334
|
});
|
|
294
335
|
|
|
295
336
|
builder.on('action:patch_thread_details', async function* (event, context) {
|
|
296
337
|
const updatedFields: ('state' | 'spec')[] = [];
|
|
338
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
297
339
|
try {
|
|
298
340
|
if (!context.state.threadId) {
|
|
299
341
|
throw new Error('Missing threadId in state for patch_thread_details');
|
|
@@ -323,11 +365,13 @@ export const busServicesPlugin =
|
|
|
323
365
|
yield {
|
|
324
366
|
type: 'action:patch_thread_details:result',
|
|
325
367
|
data: { success: true, updatedFields },
|
|
368
|
+
meta: resultMeta,
|
|
326
369
|
};
|
|
327
370
|
} catch {
|
|
328
371
|
yield {
|
|
329
372
|
type: 'action:patch_thread_details:result',
|
|
330
373
|
data: { success: false, updatedFields },
|
|
374
|
+
meta: resultMeta,
|
|
331
375
|
};
|
|
332
376
|
}
|
|
333
377
|
});
|
|
@@ -615,6 +659,82 @@ export const busServicesPlugin =
|
|
|
615
659
|
} as OpenBotEvent;
|
|
616
660
|
});
|
|
617
661
|
|
|
662
|
+
builder.on('action:remember', async function* (event, context) {
|
|
663
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
664
|
+
try {
|
|
665
|
+
const { content, scope, tags } = event.data;
|
|
666
|
+
const record = await storage.appendMemory({
|
|
667
|
+
scope: resolveMemoryScope(scope, context.state),
|
|
668
|
+
content,
|
|
669
|
+
tags,
|
|
670
|
+
});
|
|
671
|
+
yield {
|
|
672
|
+
type: 'action:remember:result',
|
|
673
|
+
data: { success: true, record },
|
|
674
|
+
meta: resultMeta,
|
|
675
|
+
} as OpenBotEvent;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
yield {
|
|
678
|
+
type: 'action:remember:result',
|
|
679
|
+
data: {
|
|
680
|
+
success: false,
|
|
681
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
682
|
+
},
|
|
683
|
+
meta: resultMeta,
|
|
684
|
+
} as OpenBotEvent;
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
builder.on('action:recall', async function* (event, context) {
|
|
689
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
690
|
+
try {
|
|
691
|
+
const { query, tag, scope, limit } = event.data;
|
|
692
|
+
const records = await storage.listMemories({
|
|
693
|
+
scopes: resolveMemoryScopeFilter(scope, context.state),
|
|
694
|
+
query,
|
|
695
|
+
tag,
|
|
696
|
+
limit,
|
|
697
|
+
});
|
|
698
|
+
yield {
|
|
699
|
+
type: 'action:recall:result',
|
|
700
|
+
data: { success: true, records },
|
|
701
|
+
meta: resultMeta,
|
|
702
|
+
} as OpenBotEvent;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
yield {
|
|
705
|
+
type: 'action:recall:result',
|
|
706
|
+
data: {
|
|
707
|
+
success: false,
|
|
708
|
+
records: [],
|
|
709
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
710
|
+
},
|
|
711
|
+
meta: resultMeta,
|
|
712
|
+
} as OpenBotEvent;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
builder.on('action:forget', async function* (event, context) {
|
|
717
|
+
const resultMeta = { ...(event.meta || {}), agentId: context.state.agentId };
|
|
718
|
+
try {
|
|
719
|
+
const deleted = await storage.deleteMemory({ id: event.data.id });
|
|
720
|
+
yield {
|
|
721
|
+
type: 'action:forget:result',
|
|
722
|
+
data: { success: true, deleted },
|
|
723
|
+
meta: resultMeta,
|
|
724
|
+
} as OpenBotEvent;
|
|
725
|
+
} catch (error) {
|
|
726
|
+
yield {
|
|
727
|
+
type: 'action:forget:result',
|
|
728
|
+
data: {
|
|
729
|
+
success: false,
|
|
730
|
+
deleted: false,
|
|
731
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
732
|
+
},
|
|
733
|
+
meta: resultMeta,
|
|
734
|
+
} as OpenBotEvent;
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
618
738
|
builder.on('action:agent:install', async function* (event) {
|
|
619
739
|
try {
|
|
620
740
|
const { agentId, name, description, instructions, plugins } = event.data;
|
package/src/bus/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OpenBotEvent } from '../app/types.js';
|
|
2
2
|
import type { PluginRef } from './plugin.js';
|
|
3
|
+
import type { MemoryRecord, ListMemoriesArgs } from '../services/memory.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Public data types exposed by the OpenBot bus.
|
|
@@ -144,4 +145,16 @@ export interface Storage {
|
|
|
144
145
|
path?: string;
|
|
145
146
|
}) => Promise<Array<{ name: string; isDirectory: boolean }>>;
|
|
146
147
|
readFile: (args: { channelId: string; path: string }) => Promise<string>;
|
|
148
|
+
/** Persist a memory record into the global memory log. */
|
|
149
|
+
appendMemory: (args: {
|
|
150
|
+
scope: string;
|
|
151
|
+
content: string;
|
|
152
|
+
tags?: string[];
|
|
153
|
+
}) => Promise<MemoryRecord>;
|
|
154
|
+
/** Read memories matching the given filter. */
|
|
155
|
+
listMemories: (args?: ListMemoriesArgs) => Promise<MemoryRecord[]>;
|
|
156
|
+
/** Soft-delete a memory by id. Returns true if a record was deleted. */
|
|
157
|
+
deleteMemory: (args: { id: string }) => Promise<boolean>;
|
|
158
|
+
/** Update a memory's content/tags by id. Returns true if a record was updated. */
|
|
159
|
+
updateMemory: (args: { id: string; content?: string; tags?: string[] }) => Promise<boolean>;
|
|
147
160
|
}
|