@wabot-dev/framework 0.9.9 → 0.9.10
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/src/addon/chat-bot/in-memory/InMemoryChatMemory.js +54 -0
- package/dist/src/addon/chat-bot/in-memory/InMemoryChatRepository.js +91 -6
- package/dist/src/addon/chat-controller/cmd/CmdChannel.js +2 -2
- package/dist/src/addon/chat-controller/cmd/cmdChannelSocketPath.js +1 -1
- package/dist/src/feature/project-runner/ProjectRunner.js +4 -8
- package/dist/src/index.d.ts +12 -0
- package/package.json +1 -1
|
@@ -1,13 +1,67 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
4
|
+
|
|
5
|
+
const PERSIST_LIMIT = 32;
|
|
6
|
+
const PERSIST_DIR = '.wabot/in-memory';
|
|
7
|
+
const logger = new Logger('wabot:in-memory-chat-memory');
|
|
1
8
|
class InMemoryChatMemory {
|
|
2
9
|
memory = [];
|
|
10
|
+
filePath;
|
|
11
|
+
onActivity;
|
|
12
|
+
constructor(chatId, onActivity) {
|
|
13
|
+
this.filePath = chatId
|
|
14
|
+
? path.resolve(process.cwd(), PERSIST_DIR, `${chatId}.json`)
|
|
15
|
+
: null;
|
|
16
|
+
this.onActivity = onActivity ?? null;
|
|
17
|
+
this.load();
|
|
18
|
+
}
|
|
3
19
|
async findLastItems(count) {
|
|
4
20
|
return this.memory.slice(-count);
|
|
5
21
|
}
|
|
6
22
|
async create(item) {
|
|
7
23
|
this.memory.push(item);
|
|
24
|
+
if (this.memory.length > PERSIST_LIMIT) {
|
|
25
|
+
this.memory = this.memory.slice(-PERSIST_LIMIT);
|
|
26
|
+
}
|
|
27
|
+
this.persist();
|
|
28
|
+
this.onActivity?.();
|
|
8
29
|
}
|
|
9
30
|
async clearMemory() {
|
|
10
31
|
this.memory = [];
|
|
32
|
+
if (this.filePath && fs.existsSync(this.filePath)) {
|
|
33
|
+
try {
|
|
34
|
+
fs.unlinkSync(this.filePath);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
logger.warn(`Failed to delete ${this.filePath}:`, err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
load() {
|
|
42
|
+
if (!this.filePath || !fs.existsSync(this.filePath))
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
if (Array.isArray(parsed)) {
|
|
48
|
+
this.memory = parsed.slice(-PERSIST_LIMIT);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.warn(`Failed to load ${this.filePath}:`, err);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
persist() {
|
|
56
|
+
if (!this.filePath)
|
|
57
|
+
return;
|
|
58
|
+
try {
|
|
59
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
60
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.memory, null, 2), 'utf-8');
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
logger.warn(`Failed to persist ${this.filePath}:`, err);
|
|
64
|
+
}
|
|
11
65
|
}
|
|
12
66
|
}
|
|
13
67
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { __decorate } from 'tslib';
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
2
4
|
import { v4 } from 'uuid';
|
|
3
5
|
import { InMemoryChatMemory } from './InMemoryChatMemory.js';
|
|
4
6
|
import { singleton } from '../../../core/injection/index.js';
|
|
7
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
8
|
+
import { Chat } from '../../../feature/chat-bot/Chat.js';
|
|
5
9
|
import '../../../feature/chat-bot/ChatAdapterRegistry.js';
|
|
6
10
|
import '../../../feature/chat-bot/ChatBot.js';
|
|
7
11
|
import { ChatOperator } from '../../../feature/chat-bot/ChatOperator.js';
|
|
@@ -10,14 +14,23 @@ import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
|
10
14
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
11
15
|
import '../../../core/error/setupErrorHandlers.js';
|
|
12
16
|
|
|
17
|
+
const CHATS_FILE = '.wabot/in-memory/chats.json';
|
|
18
|
+
const MEMORY_DIR = '.wabot/in-memory';
|
|
19
|
+
const MAX_CHATS = 10;
|
|
20
|
+
const logger = new Logger('wabot:in-memory-chat-repository');
|
|
13
21
|
let InMemoryChatRepository = class InMemoryChatRepository {
|
|
22
|
+
// Ordered least-recently-active first, most-recent last.
|
|
14
23
|
items = [];
|
|
15
24
|
memories = [];
|
|
25
|
+
constructor() {
|
|
26
|
+
this.load();
|
|
27
|
+
}
|
|
16
28
|
async update(chat) {
|
|
17
29
|
if (!chat.wasCreated()) {
|
|
18
30
|
throw new Error('Chat wat not created');
|
|
19
31
|
}
|
|
20
32
|
chat.validate();
|
|
33
|
+
this.persist();
|
|
21
34
|
}
|
|
22
35
|
async create(chat) {
|
|
23
36
|
if (chat.wasCreated()) {
|
|
@@ -27,11 +40,12 @@ let InMemoryChatRepository = class InMemoryChatRepository {
|
|
|
27
40
|
chat['data'].createdAt = new Date().getTime();
|
|
28
41
|
chat.validate();
|
|
29
42
|
this.items.push(chat);
|
|
30
|
-
|
|
31
|
-
memory: new InMemoryChatMemory(),
|
|
43
|
+
this.memories.push({
|
|
32
44
|
chatId: chat.id,
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
memory: new InMemoryChatMemory(chat.id, () => this.touch(chat.id)),
|
|
46
|
+
});
|
|
47
|
+
this.enforceLimit();
|
|
48
|
+
this.persist();
|
|
35
49
|
}
|
|
36
50
|
async findByConnection(query) {
|
|
37
51
|
return this.items.find((item) => item.hasConnection(query)) ?? null;
|
|
@@ -55,9 +69,80 @@ let InMemoryChatRepository = class InMemoryChatRepository {
|
|
|
55
69
|
getMemory(chatId) {
|
|
56
70
|
return this.memories.find((r) => r.chatId === chatId) ?? null;
|
|
57
71
|
}
|
|
72
|
+
touch(chatId) {
|
|
73
|
+
const idx = this.items.findIndex((c) => c.id === chatId);
|
|
74
|
+
if (idx === -1 || idx === this.items.length - 1) {
|
|
75
|
+
if (idx !== -1)
|
|
76
|
+
this.persist();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const [chat] = this.items.splice(idx, 1);
|
|
80
|
+
this.items.push(chat);
|
|
81
|
+
this.persist();
|
|
82
|
+
}
|
|
83
|
+
enforceLimit() {
|
|
84
|
+
while (this.items.length > MAX_CHATS) {
|
|
85
|
+
const evicted = this.items.shift();
|
|
86
|
+
const memIdx = this.memories.findIndex((m) => m.chatId === evicted.id);
|
|
87
|
+
if (memIdx !== -1)
|
|
88
|
+
this.memories.splice(memIdx, 1);
|
|
89
|
+
this.deleteMemoryFile(evicted.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
deleteMemoryFile(chatId) {
|
|
93
|
+
const file = path.resolve(process.cwd(), MEMORY_DIR, `${chatId}.json`);
|
|
94
|
+
if (!fs.existsSync(file))
|
|
95
|
+
return;
|
|
96
|
+
try {
|
|
97
|
+
fs.unlinkSync(file);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger.warn(`Failed to delete ${file}:`, err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
chatsFilePath() {
|
|
104
|
+
return path.resolve(process.cwd(), CHATS_FILE);
|
|
105
|
+
}
|
|
106
|
+
load() {
|
|
107
|
+
const file = this.chatsFilePath();
|
|
108
|
+
if (!fs.existsSync(file))
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
112
|
+
const parsed = JSON.parse(raw);
|
|
113
|
+
if (!Array.isArray(parsed))
|
|
114
|
+
return;
|
|
115
|
+
for (const data of parsed) {
|
|
116
|
+
if (!data?.id)
|
|
117
|
+
continue;
|
|
118
|
+
const chat = new Chat(data);
|
|
119
|
+
this.items.push(chat);
|
|
120
|
+
this.memories.push({
|
|
121
|
+
chatId: chat.id,
|
|
122
|
+
memory: new InMemoryChatMemory(chat.id, () => this.touch(chat.id)),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
this.enforceLimit();
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
logger.warn(`Failed to load ${file}:`, err);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
persist() {
|
|
132
|
+
const file = this.chatsFilePath();
|
|
133
|
+
try {
|
|
134
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
135
|
+
const data = this.items.map((c) => c['data']);
|
|
136
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf-8');
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
logger.warn(`Failed to persist ${file}:`, err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
58
142
|
};
|
|
59
143
|
InMemoryChatRepository = __decorate([
|
|
60
|
-
singleton()
|
|
144
|
+
singleton(),
|
|
145
|
+
__metadata("design:paramtypes", [])
|
|
61
146
|
], InMemoryChatRepository);
|
|
62
147
|
|
|
63
148
|
export { InMemoryChatRepository };
|
|
@@ -65,10 +65,10 @@ let CmdChannel = class CmdChannel {
|
|
|
65
65
|
return this.config.route.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
66
66
|
}
|
|
67
67
|
chatIdPath() {
|
|
68
|
-
return path.join('.cmd-channel', this.routeSlug(), 'id.json');
|
|
68
|
+
return path.join('.wabot', 'cmd-channel', this.routeSlug(), 'id.json');
|
|
69
69
|
}
|
|
70
70
|
authInfoPath() {
|
|
71
|
-
return path.join('.cmd-channel', this.routeSlug(), 'auth-info.json');
|
|
71
|
+
return path.join('.wabot', 'cmd-channel', this.routeSlug(), 'auth-info.json');
|
|
72
72
|
}
|
|
73
73
|
ensureChatId() {
|
|
74
74
|
if (this.chatId !== undefined)
|
|
@@ -10,7 +10,7 @@ function cmdChannelSocketPath() {
|
|
|
10
10
|
.slice(0, 12);
|
|
11
11
|
return `\\\\.\\pipe\\wabot-cmd-channel-${cwdHash}`;
|
|
12
12
|
}
|
|
13
|
-
return path.resolve(process.cwd(), '.cmd-channel/socket');
|
|
13
|
+
return path.resolve(process.cwd(), '.wabot/cmd-channel/socket');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export { cmdChannelSocketPath };
|
|
@@ -36,7 +36,7 @@ import { RepositoryAdapterRegistry } from '../repository/RepositoryAdapterRegist
|
|
|
36
36
|
|
|
37
37
|
const logger = new Logger('wabot:project-runner');
|
|
38
38
|
const TEST_FILE_PATTERNS = /\.(test|spec|unit|integration|e2e|multiprocess)\.(ts|js)$/;
|
|
39
|
-
const DEFAULT_EXCLUDE = ['
|
|
39
|
+
const DEFAULT_EXCLUDE = ['_run_.ts', '_cmd_.ts'];
|
|
40
40
|
const MODULE_EXT = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
|
|
41
41
|
const DEFAULT_CHAT_ADAPTERS = [
|
|
42
42
|
{
|
|
@@ -279,21 +279,17 @@ class ProjectRunner {
|
|
|
279
279
|
}
|
|
280
280
|
async resolveDefaultChatAdapters() {
|
|
281
281
|
const results = await Promise.all(DEFAULT_CHAT_ADAPTERS.map(async ({ path, name, apiKeyEnv }) => {
|
|
282
|
-
if (!process.env[apiKeyEnv])
|
|
283
|
-
logger.info(`Skipping ${name}: ${apiKeyEnv} is not set`);
|
|
282
|
+
if (!process.env[apiKeyEnv])
|
|
284
283
|
return null;
|
|
285
|
-
}
|
|
286
284
|
try {
|
|
287
285
|
const mod = await import(path);
|
|
288
286
|
const adapter = mod[name];
|
|
289
|
-
if (!adapter)
|
|
290
|
-
logger.warn(`Skipping ${name}: module loaded but no '${name}' export found`);
|
|
287
|
+
if (!adapter)
|
|
291
288
|
return null;
|
|
292
|
-
}
|
|
289
|
+
logger.info(`Using ${name}`);
|
|
293
290
|
return adapter;
|
|
294
291
|
}
|
|
295
292
|
catch {
|
|
296
|
-
logger.warn(`Skipping ${name}: missing peer dependency`);
|
|
297
293
|
return null;
|
|
298
294
|
}
|
|
299
295
|
}));
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1931,20 +1931,32 @@ declare class PgChatMemory extends PgCrudRepository<ChatItem> implements IChatMe
|
|
|
1931
1931
|
|
|
1932
1932
|
declare class InMemoryChatMemory implements IChatMemory {
|
|
1933
1933
|
private memory;
|
|
1934
|
+
private readonly filePath;
|
|
1935
|
+
private readonly onActivity;
|
|
1936
|
+
constructor(chatId?: string, onActivity?: () => void);
|
|
1934
1937
|
findLastItems(count: number): Promise<ChatItem[]>;
|
|
1935
1938
|
create(item: ChatItem): Promise<void>;
|
|
1936
1939
|
clearMemory(): Promise<void>;
|
|
1940
|
+
private load;
|
|
1941
|
+
private persist;
|
|
1937
1942
|
}
|
|
1938
1943
|
|
|
1939
1944
|
declare class InMemoryChatRepository implements IChatRepository {
|
|
1940
1945
|
private items;
|
|
1941
1946
|
private memories;
|
|
1947
|
+
constructor();
|
|
1942
1948
|
update(chat: Chat): Promise<void>;
|
|
1943
1949
|
create(chat: Chat): Promise<void>;
|
|
1944
1950
|
findByConnection(query: IChatConnection): Promise<Chat | null>;
|
|
1945
1951
|
findMemory(chatId: string): Promise<IChatMemory | null>;
|
|
1946
1952
|
findOperator(chatId: string): Promise<ChatOperator | null>;
|
|
1947
1953
|
private getMemory;
|
|
1954
|
+
private touch;
|
|
1955
|
+
private enforceLimit;
|
|
1956
|
+
private deleteMemoryFile;
|
|
1957
|
+
private chatsFilePath;
|
|
1958
|
+
private load;
|
|
1959
|
+
private persist;
|
|
1948
1960
|
}
|
|
1949
1961
|
|
|
1950
1962
|
declare class WabotChatAdapter implements IChatAdapter {
|