@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.
@@ -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
- const memory = {
31
- memory: new InMemoryChatMemory(),
43
+ this.memories.push({
32
44
  chatId: chat.id,
33
- };
34
- this.memories.push(memory);
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 = ['run.ts', 'cmd.ts'];
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
  }));
@@ -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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wabot-dev/framework",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Framework for IA Chat Bots",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",