@pixelbyte-software/pixcode 1.30.1 → 1.31.0

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 (205) hide show
  1. package/LICENSE +718 -718
  2. package/README.de.md +248 -248
  3. package/README.ja.md +240 -240
  4. package/README.ko.md +240 -240
  5. package/README.md +295 -285
  6. package/README.ru.md +248 -248
  7. package/README.tr.md +250 -250
  8. package/README.zh-CN.md +240 -240
  9. package/dist/api-docs.html +879 -879
  10. package/dist/assets/index-BRRJ47XQ.css +32 -0
  11. package/dist/assets/index-EQohwyiC.js +837 -0
  12. package/dist/clear-cache.html +85 -85
  13. package/dist/convert-icons.md +52 -52
  14. package/dist/favicon.png +0 -0
  15. package/dist/favicon.svg +7 -8
  16. package/dist/generate-icons.js +48 -48
  17. package/dist/icons/codex-white.svg +3 -3
  18. package/dist/icons/codex.svg +3 -3
  19. package/dist/icons/cursor-white.svg +11 -11
  20. package/dist/icons/icon-128x128.png +0 -0
  21. package/dist/icons/icon-128x128.svg +9 -12
  22. package/dist/icons/icon-144x144.png +0 -0
  23. package/dist/icons/icon-144x144.svg +9 -12
  24. package/dist/icons/icon-152x152.png +0 -0
  25. package/dist/icons/icon-152x152.svg +9 -12
  26. package/dist/icons/icon-192x192.png +0 -0
  27. package/dist/icons/icon-192x192.svg +9 -12
  28. package/dist/icons/icon-384x384.png +0 -0
  29. package/dist/icons/icon-384x384.svg +9 -12
  30. package/dist/icons/icon-512x512.png +0 -0
  31. package/dist/icons/icon-512x512.svg +9 -12
  32. package/dist/icons/icon-72x72.png +0 -0
  33. package/dist/icons/icon-72x72.svg +9 -12
  34. package/dist/icons/icon-96x96.png +0 -0
  35. package/dist/icons/icon-96x96.svg +9 -12
  36. package/dist/icons/icon-template.svg +9 -12
  37. package/dist/icons/qwen-ai-icon.png +0 -0
  38. package/dist/index.html +59 -49
  39. package/dist/logo.png +0 -0
  40. package/dist/logo.svg +11 -16
  41. package/dist/manifest.json +60 -60
  42. package/dist/sw.js +124 -124
  43. package/dist-server/server/cli.js +100 -97
  44. package/dist-server/server/cli.js.map +1 -1
  45. package/dist-server/server/daemon/manager.js +33 -33
  46. package/dist-server/server/daemon-manager.js +62 -62
  47. package/dist-server/server/database/db.js +114 -22
  48. package/dist-server/server/database/db.js.map +1 -1
  49. package/dist-server/server/database/schema.js +122 -89
  50. package/dist-server/server/database/schema.js.map +1 -1
  51. package/dist-server/server/gemini-cli.js +6 -1
  52. package/dist-server/server/gemini-cli.js.map +1 -1
  53. package/dist-server/server/index.js +234 -65
  54. package/dist-server/server/index.js.map +1 -1
  55. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +29 -2
  56. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  57. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +22 -2
  58. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -1
  59. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +2 -2
  60. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
  61. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +14 -2
  62. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -1
  63. package/dist-server/server/modules/providers/list/qwen/qwen-auth.provider.js +132 -0
  64. package/dist-server/server/modules/providers/list/qwen/qwen-auth.provider.js.map +1 -0
  65. package/dist-server/server/modules/providers/list/qwen/qwen-mcp.provider.js +87 -0
  66. package/dist-server/server/modules/providers/list/qwen/qwen-mcp.provider.js.map +1 -0
  67. package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js +201 -0
  68. package/dist-server/server/modules/providers/list/qwen/qwen-sessions.provider.js.map +1 -0
  69. package/dist-server/server/modules/providers/list/qwen/qwen.provider.js +19 -0
  70. package/dist-server/server/modules/providers/list/qwen/qwen.provider.js.map +1 -0
  71. package/dist-server/server/modules/providers/provider.registry.js +2 -0
  72. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  73. package/dist-server/server/modules/providers/provider.routes.js +310 -1
  74. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  75. package/dist-server/server/projects.js +197 -6
  76. package/dist-server/server/projects.js.map +1 -1
  77. package/dist-server/server/qwen-code-cli.js +350 -0
  78. package/dist-server/server/qwen-code-cli.js.map +1 -0
  79. package/dist-server/server/qwen-response-handler.js +70 -0
  80. package/dist-server/server/qwen-response-handler.js.map +1 -0
  81. package/dist-server/server/routes/commands.js +25 -25
  82. package/dist-server/server/routes/git.js +17 -17
  83. package/dist-server/server/routes/network.js +116 -0
  84. package/dist-server/server/routes/network.js.map +1 -0
  85. package/dist-server/server/routes/projects.js +43 -0
  86. package/dist-server/server/routes/projects.js.map +1 -1
  87. package/dist-server/server/routes/qwen.js +23 -0
  88. package/dist-server/server/routes/qwen.js.map +1 -0
  89. package/dist-server/server/routes/taskmaster.js +419 -419
  90. package/dist-server/server/routes/telegram.js +119 -0
  91. package/dist-server/server/routes/telegram.js.map +1 -0
  92. package/dist-server/server/services/external-access.js +228 -0
  93. package/dist-server/server/services/external-access.js.map +1 -0
  94. package/dist-server/server/services/install-jobs.js +394 -0
  95. package/dist-server/server/services/install-jobs.js.map +1 -0
  96. package/dist-server/server/services/notification-orchestrator.js +19 -5
  97. package/dist-server/server/services/notification-orchestrator.js.map +1 -1
  98. package/dist-server/server/services/provider-credentials.js +154 -0
  99. package/dist-server/server/services/provider-credentials.js.map +1 -0
  100. package/dist-server/server/services/provider-models.js +218 -0
  101. package/dist-server/server/services/provider-models.js.map +1 -0
  102. package/dist-server/server/services/telegram/bot.js +259 -0
  103. package/dist-server/server/services/telegram/bot.js.map +1 -0
  104. package/dist-server/server/services/telegram/translations.js +160 -0
  105. package/dist-server/server/services/telegram/translations.js.map +1 -0
  106. package/dist-server/server/utils/port-access.js +196 -0
  107. package/dist-server/server/utils/port-access.js.map +1 -0
  108. package/dist-server/shared/modelConstants.js +18 -0
  109. package/dist-server/shared/modelConstants.js.map +1 -1
  110. package/package.json +177 -168
  111. package/scripts/fix-node-pty.js +67 -67
  112. package/server/claude-sdk.js +834 -834
  113. package/server/cli.js +940 -937
  114. package/server/constants/config.js +4 -4
  115. package/server/cursor-cli.js +342 -342
  116. package/server/daemon/manager.js +564 -564
  117. package/server/daemon-manager.js +920 -920
  118. package/server/database/db.js +696 -593
  119. package/server/database/schema.js +138 -102
  120. package/server/gemini-cli.js +475 -469
  121. package/server/gemini-response-handler.js +79 -79
  122. package/server/index.js +2730 -2557
  123. package/server/load-env.js +34 -34
  124. package/server/middleware/auth.js +132 -132
  125. package/server/modules/providers/list/claude/claude-auth.provider.ts +145 -123
  126. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  127. package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
  128. package/server/modules/providers/list/claude/claude.provider.ts +15 -15
  129. package/server/modules/providers/list/codex/codex-auth.provider.ts +115 -100
  130. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  131. package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
  132. package/server/modules/providers/list/codex/codex.provider.ts +15 -15
  133. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
  134. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  135. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
  136. package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
  137. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +163 -151
  138. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
  139. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
  140. package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
  141. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -0
  142. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -0
  143. package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +218 -0
  144. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -0
  145. package/server/modules/providers/provider.registry.ts +38 -36
  146. package/server/modules/providers/provider.routes.ts +583 -217
  147. package/server/modules/providers/services/mcp.service.ts +94 -94
  148. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  149. package/server/modules/providers/services/sessions.service.ts +45 -45
  150. package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
  151. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  152. package/server/modules/providers/tests/mcp.test.ts +293 -293
  153. package/server/openai-codex.js +426 -426
  154. package/server/projects.js +2993 -2792
  155. package/server/qwen-code-cli.js +392 -0
  156. package/server/qwen-response-handler.js +73 -0
  157. package/server/routes/agent.js +1245 -1245
  158. package/server/routes/auth.js +134 -134
  159. package/server/routes/codex.js +19 -19
  160. package/server/routes/commands.js +554 -554
  161. package/server/routes/cursor.js +52 -52
  162. package/server/routes/gemini.js +24 -24
  163. package/server/routes/git.js +1488 -1488
  164. package/server/routes/mcp-utils.js +31 -31
  165. package/server/routes/messages.js +61 -61
  166. package/server/routes/network.js +128 -0
  167. package/server/routes/plugins.js +307 -307
  168. package/server/routes/projects.js +675 -627
  169. package/server/routes/qwen.js +27 -0
  170. package/server/routes/settings.js +286 -286
  171. package/server/routes/taskmaster.js +1471 -1471
  172. package/server/routes/telegram.js +125 -0
  173. package/server/routes/user.js +123 -123
  174. package/server/services/external-access.js +240 -0
  175. package/server/services/install-jobs.js +410 -0
  176. package/server/services/notification-orchestrator.js +242 -227
  177. package/server/services/provider-credentials.js +151 -0
  178. package/server/services/provider-models.js +225 -0
  179. package/server/services/telegram/bot.js +280 -0
  180. package/server/services/telegram/translations.js +170 -0
  181. package/server/services/vapid-keys.js +35 -35
  182. package/server/sessionManager.js +225 -225
  183. package/server/shared/interfaces.ts +54 -54
  184. package/server/shared/types.ts +172 -172
  185. package/server/shared/utils.ts +193 -193
  186. package/server/tsconfig.json +36 -36
  187. package/server/utils/colors.js +21 -21
  188. package/server/utils/commandParser.js +303 -303
  189. package/server/utils/frontmatter.js +18 -18
  190. package/server/utils/gitConfig.js +34 -34
  191. package/server/utils/mcp-detector.js +147 -147
  192. package/server/utils/plugin-loader.js +457 -457
  193. package/server/utils/plugin-process-manager.js +184 -184
  194. package/server/utils/port-access.js +209 -0
  195. package/server/utils/runtime-paths.js +37 -37
  196. package/server/utils/taskmaster-websocket.js +128 -128
  197. package/server/utils/url-detection.js +71 -71
  198. package/server/vite-daemon.js +78 -78
  199. package/shared/modelConstants.js +117 -97
  200. package/shared/networkHosts.js +22 -22
  201. package/dist/assets/index-C2c9QNwK.css +0 -32
  202. package/dist/assets/index-DyXDZED-.js +0 -1277
  203. package/dist-server/server/routes/cli-auth.js +0 -25
  204. package/dist-server/server/routes/cli-auth.js.map +0 -1
  205. package/server/routes/cli-auth.js +0 -27
@@ -1,35 +1,35 @@
1
- import webPush from 'web-push';
2
- import { db } from '../database/db.js';
3
-
4
- let cachedKeys = null;
5
-
6
- function ensureVapidKeys() {
7
- if (cachedKeys) return cachedKeys;
8
-
9
- const row = db.prepare('SELECT public_key, private_key FROM vapid_keys ORDER BY id DESC LIMIT 1').get();
10
- if (row) {
11
- cachedKeys = { publicKey: row.public_key, privateKey: row.private_key };
12
- return cachedKeys;
13
- }
14
-
15
- const keys = webPush.generateVAPIDKeys();
16
- db.prepare('INSERT INTO vapid_keys (public_key, private_key) VALUES (?, ?)').run(keys.publicKey, keys.privateKey);
17
- cachedKeys = keys;
18
- return cachedKeys;
19
- }
20
-
21
- function getPublicKey() {
22
- return ensureVapidKeys().publicKey;
23
- }
24
-
25
- function configureWebPush() {
26
- const keys = ensureVapidKeys();
27
- webPush.setVapidDetails(
28
- 'mailto:noreply@pixcode.local',
29
- keys.publicKey,
30
- keys.privateKey
31
- );
32
- console.log('Web Push notifications configured');
33
- }
34
-
35
- export { ensureVapidKeys, getPublicKey, configureWebPush };
1
+ import webPush from 'web-push';
2
+ import { db } from '../database/db.js';
3
+
4
+ let cachedKeys = null;
5
+
6
+ function ensureVapidKeys() {
7
+ if (cachedKeys) return cachedKeys;
8
+
9
+ const row = db.prepare('SELECT public_key, private_key FROM vapid_keys ORDER BY id DESC LIMIT 1').get();
10
+ if (row) {
11
+ cachedKeys = { publicKey: row.public_key, privateKey: row.private_key };
12
+ return cachedKeys;
13
+ }
14
+
15
+ const keys = webPush.generateVAPIDKeys();
16
+ db.prepare('INSERT INTO vapid_keys (public_key, private_key) VALUES (?, ?)').run(keys.publicKey, keys.privateKey);
17
+ cachedKeys = keys;
18
+ return cachedKeys;
19
+ }
20
+
21
+ function getPublicKey() {
22
+ return ensureVapidKeys().publicKey;
23
+ }
24
+
25
+ function configureWebPush() {
26
+ const keys = ensureVapidKeys();
27
+ webPush.setVapidDetails(
28
+ 'mailto:noreply@pixcode.local',
29
+ keys.publicKey,
30
+ keys.privateKey
31
+ );
32
+ console.log('Web Push notifications configured');
33
+ }
34
+
35
+ export { ensureVapidKeys, getPublicKey, configureWebPush };
@@ -1,226 +1,226 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
3
- import os from 'os';
4
-
5
- class SessionManager {
6
- constructor() {
7
- // Store sessions in memory with conversation history
8
- this.sessions = new Map();
9
- this.maxSessions = 100;
10
- this.sessionsDir = path.join(os.homedir(), '.gemini', 'sessions');
11
- this.ready = this.init();
12
- }
13
-
14
- async init() {
15
- await this.initSessionsDir();
16
- await this.loadSessions();
17
- }
18
-
19
- async initSessionsDir() {
20
- try {
21
- await fs.mkdir(this.sessionsDir, { recursive: true });
22
- } catch (error) {
23
- // console.error('Error creating sessions directory:', error);
24
- }
25
- }
26
-
27
- // Create a new session
28
- createSession(sessionId, projectPath) {
29
- const session = {
30
- id: sessionId,
31
- projectPath: projectPath,
32
- messages: [],
33
- createdAt: new Date(),
34
- lastActivity: new Date()
35
- };
36
-
37
- // Evict oldest session from memory if we exceed limit
38
- if (this.sessions.size >= this.maxSessions) {
39
- const oldestKey = this.sessions.keys().next().value;
40
- if (oldestKey) this.sessions.delete(oldestKey);
41
- }
42
-
43
- this.sessions.set(sessionId, session);
44
- this.saveSession(sessionId);
45
-
46
- return session;
47
- }
48
-
49
- // Add a message to session
50
- addMessage(sessionId, role, content) {
51
- let session = this.sessions.get(sessionId);
52
-
53
- if (!session) {
54
- // Create session if it doesn't exist
55
- session = this.createSession(sessionId, '');
56
- }
57
-
58
- const message = {
59
- role: role, // 'user' or 'assistant'
60
- content: content,
61
- timestamp: new Date()
62
- };
63
-
64
- session.messages.push(message);
65
- session.lastActivity = new Date();
66
-
67
- this.saveSession(sessionId);
68
-
69
- return session;
70
- }
71
-
72
- // Get session by ID
73
- getSession(sessionId) {
74
- return this.sessions.get(sessionId);
75
- }
76
-
77
- // Get all sessions for a project
78
- getProjectSessions(projectPath) {
79
- const sessions = [];
80
-
81
- for (const [id, session] of this.sessions) {
82
- if (session.projectPath === projectPath) {
83
- sessions.push({
84
- id: session.id,
85
- summary: this.getSessionSummary(session),
86
- messageCount: session.messages.length,
87
- lastActivity: session.lastActivity
88
- });
89
- }
90
- }
91
-
92
- return sessions.sort((a, b) =>
93
- new Date(b.lastActivity) - new Date(a.lastActivity)
94
- );
95
- }
96
-
97
- // Get session summary
98
- getSessionSummary(session) {
99
- if (session.messages.length === 0) {
100
- return 'New Session';
101
- }
102
-
103
- // Find first user message
104
- const firstUserMessage = session.messages.find(m => m.role === 'user');
105
- if (firstUserMessage) {
106
- const content = firstUserMessage.content;
107
- return content.length > 50 ? content.substring(0, 50) + '...' : content;
108
- }
109
-
110
- return 'New Session';
111
- }
112
-
113
- // Build conversation context for Gemini
114
- buildConversationContext(sessionId, maxMessages = 10) {
115
- const session = this.sessions.get(sessionId);
116
-
117
- if (!session || session.messages.length === 0) {
118
- return '';
119
- }
120
-
121
- // Get last N messages for context
122
- const recentMessages = session.messages.slice(-maxMessages);
123
-
124
- let context = 'Here is the conversation history:\n\n';
125
-
126
- for (const msg of recentMessages) {
127
- if (msg.role === 'user') {
128
- context += `User: ${msg.content}\n`;
129
- } else {
130
- context += `Assistant: ${msg.content}\n`;
131
- }
132
- }
133
-
134
- context += '\nBased on the conversation history above, please answer the following:\n';
135
-
136
- return context;
137
- }
138
-
139
- // Prevent path traversal
140
- _safeFilePath(sessionId) {
141
- const safeId = String(sessionId).replace(/[/\\]|\.\./g, '');
142
- return path.join(this.sessionsDir, `${safeId}.json`);
143
- }
144
-
145
- // Save session to disk
146
- async saveSession(sessionId) {
147
- const session = this.sessions.get(sessionId);
148
- if (!session) return;
149
-
150
- try {
151
- const filePath = this._safeFilePath(sessionId);
152
- await fs.writeFile(filePath, JSON.stringify(session, null, 2));
153
- } catch (error) {
154
- // console.error('Error saving session:', error);
155
- }
156
- }
157
-
158
- // Load sessions from disk
159
- async loadSessions() {
160
- try {
161
- const files = await fs.readdir(this.sessionsDir);
162
-
163
- for (const file of files) {
164
- if (file.endsWith('.json')) {
165
- try {
166
- const filePath = path.join(this.sessionsDir, file);
167
- const data = await fs.readFile(filePath, 'utf8');
168
- const session = JSON.parse(data);
169
-
170
- // Convert dates
171
- session.createdAt = new Date(session.createdAt);
172
- session.lastActivity = new Date(session.lastActivity);
173
- session.messages.forEach(msg => {
174
- msg.timestamp = new Date(msg.timestamp);
175
- });
176
-
177
- this.sessions.set(session.id, session);
178
- } catch (error) {
179
- // console.error(`Error loading session ${file}:`, error);
180
- }
181
- }
182
- }
183
-
184
- // Enforce eviction after loading to prevent massive memory usage
185
- while (this.sessions.size > this.maxSessions) {
186
- const oldestKey = this.sessions.keys().next().value;
187
- if (oldestKey) this.sessions.delete(oldestKey);
188
- }
189
- } catch (error) {
190
- // console.error('Error loading sessions:', error);
191
- }
192
- }
193
-
194
- // Delete a session
195
- async deleteSession(sessionId) {
196
- this.sessions.delete(sessionId);
197
-
198
- try {
199
- const filePath = this._safeFilePath(sessionId);
200
- await fs.unlink(filePath);
201
- } catch (error) {
202
- // console.error('Error deleting session file:', error);
203
- }
204
- }
205
-
206
- // Get session messages for display
207
- getSessionMessages(sessionId) {
208
- const session = this.sessions.get(sessionId);
209
- if (!session) return [];
210
-
211
- return session.messages.map(msg => ({
212
- type: 'message',
213
- message: {
214
- role: msg.role,
215
- content: msg.content
216
- },
217
- timestamp: msg.timestamp.toISOString()
218
- }));
219
- }
220
- }
221
-
222
- // Singleton instance
223
- const sessionManager = new SessionManager();
224
-
225
- export const ready = sessionManager.ready;
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ class SessionManager {
6
+ constructor() {
7
+ // Store sessions in memory with conversation history
8
+ this.sessions = new Map();
9
+ this.maxSessions = 100;
10
+ this.sessionsDir = path.join(os.homedir(), '.gemini', 'sessions');
11
+ this.ready = this.init();
12
+ }
13
+
14
+ async init() {
15
+ await this.initSessionsDir();
16
+ await this.loadSessions();
17
+ }
18
+
19
+ async initSessionsDir() {
20
+ try {
21
+ await fs.mkdir(this.sessionsDir, { recursive: true });
22
+ } catch (error) {
23
+ // console.error('Error creating sessions directory:', error);
24
+ }
25
+ }
26
+
27
+ // Create a new session
28
+ createSession(sessionId, projectPath) {
29
+ const session = {
30
+ id: sessionId,
31
+ projectPath: projectPath,
32
+ messages: [],
33
+ createdAt: new Date(),
34
+ lastActivity: new Date()
35
+ };
36
+
37
+ // Evict oldest session from memory if we exceed limit
38
+ if (this.sessions.size >= this.maxSessions) {
39
+ const oldestKey = this.sessions.keys().next().value;
40
+ if (oldestKey) this.sessions.delete(oldestKey);
41
+ }
42
+
43
+ this.sessions.set(sessionId, session);
44
+ this.saveSession(sessionId);
45
+
46
+ return session;
47
+ }
48
+
49
+ // Add a message to session
50
+ addMessage(sessionId, role, content) {
51
+ let session = this.sessions.get(sessionId);
52
+
53
+ if (!session) {
54
+ // Create session if it doesn't exist
55
+ session = this.createSession(sessionId, '');
56
+ }
57
+
58
+ const message = {
59
+ role: role, // 'user' or 'assistant'
60
+ content: content,
61
+ timestamp: new Date()
62
+ };
63
+
64
+ session.messages.push(message);
65
+ session.lastActivity = new Date();
66
+
67
+ this.saveSession(sessionId);
68
+
69
+ return session;
70
+ }
71
+
72
+ // Get session by ID
73
+ getSession(sessionId) {
74
+ return this.sessions.get(sessionId);
75
+ }
76
+
77
+ // Get all sessions for a project
78
+ getProjectSessions(projectPath) {
79
+ const sessions = [];
80
+
81
+ for (const [id, session] of this.sessions) {
82
+ if (session.projectPath === projectPath) {
83
+ sessions.push({
84
+ id: session.id,
85
+ summary: this.getSessionSummary(session),
86
+ messageCount: session.messages.length,
87
+ lastActivity: session.lastActivity
88
+ });
89
+ }
90
+ }
91
+
92
+ return sessions.sort((a, b) =>
93
+ new Date(b.lastActivity) - new Date(a.lastActivity)
94
+ );
95
+ }
96
+
97
+ // Get session summary
98
+ getSessionSummary(session) {
99
+ if (session.messages.length === 0) {
100
+ return 'New Session';
101
+ }
102
+
103
+ // Find first user message
104
+ const firstUserMessage = session.messages.find(m => m.role === 'user');
105
+ if (firstUserMessage) {
106
+ const content = firstUserMessage.content;
107
+ return content.length > 50 ? content.substring(0, 50) + '...' : content;
108
+ }
109
+
110
+ return 'New Session';
111
+ }
112
+
113
+ // Build conversation context for Gemini
114
+ buildConversationContext(sessionId, maxMessages = 10) {
115
+ const session = this.sessions.get(sessionId);
116
+
117
+ if (!session || session.messages.length === 0) {
118
+ return '';
119
+ }
120
+
121
+ // Get last N messages for context
122
+ const recentMessages = session.messages.slice(-maxMessages);
123
+
124
+ let context = 'Here is the conversation history:\n\n';
125
+
126
+ for (const msg of recentMessages) {
127
+ if (msg.role === 'user') {
128
+ context += `User: ${msg.content}\n`;
129
+ } else {
130
+ context += `Assistant: ${msg.content}\n`;
131
+ }
132
+ }
133
+
134
+ context += '\nBased on the conversation history above, please answer the following:\n';
135
+
136
+ return context;
137
+ }
138
+
139
+ // Prevent path traversal
140
+ _safeFilePath(sessionId) {
141
+ const safeId = String(sessionId).replace(/[/\\]|\.\./g, '');
142
+ return path.join(this.sessionsDir, `${safeId}.json`);
143
+ }
144
+
145
+ // Save session to disk
146
+ async saveSession(sessionId) {
147
+ const session = this.sessions.get(sessionId);
148
+ if (!session) return;
149
+
150
+ try {
151
+ const filePath = this._safeFilePath(sessionId);
152
+ await fs.writeFile(filePath, JSON.stringify(session, null, 2));
153
+ } catch (error) {
154
+ // console.error('Error saving session:', error);
155
+ }
156
+ }
157
+
158
+ // Load sessions from disk
159
+ async loadSessions() {
160
+ try {
161
+ const files = await fs.readdir(this.sessionsDir);
162
+
163
+ for (const file of files) {
164
+ if (file.endsWith('.json')) {
165
+ try {
166
+ const filePath = path.join(this.sessionsDir, file);
167
+ const data = await fs.readFile(filePath, 'utf8');
168
+ const session = JSON.parse(data);
169
+
170
+ // Convert dates
171
+ session.createdAt = new Date(session.createdAt);
172
+ session.lastActivity = new Date(session.lastActivity);
173
+ session.messages.forEach(msg => {
174
+ msg.timestamp = new Date(msg.timestamp);
175
+ });
176
+
177
+ this.sessions.set(session.id, session);
178
+ } catch (error) {
179
+ // console.error(`Error loading session ${file}:`, error);
180
+ }
181
+ }
182
+ }
183
+
184
+ // Enforce eviction after loading to prevent massive memory usage
185
+ while (this.sessions.size > this.maxSessions) {
186
+ const oldestKey = this.sessions.keys().next().value;
187
+ if (oldestKey) this.sessions.delete(oldestKey);
188
+ }
189
+ } catch (error) {
190
+ // console.error('Error loading sessions:', error);
191
+ }
192
+ }
193
+
194
+ // Delete a session
195
+ async deleteSession(sessionId) {
196
+ this.sessions.delete(sessionId);
197
+
198
+ try {
199
+ const filePath = this._safeFilePath(sessionId);
200
+ await fs.unlink(filePath);
201
+ } catch (error) {
202
+ // console.error('Error deleting session file:', error);
203
+ }
204
+ }
205
+
206
+ // Get session messages for display
207
+ getSessionMessages(sessionId) {
208
+ const session = this.sessions.get(sessionId);
209
+ if (!session) return [];
210
+
211
+ return session.messages.map(msg => ({
212
+ type: 'message',
213
+ message: {
214
+ role: msg.role,
215
+ content: msg.content
216
+ },
217
+ timestamp: msg.timestamp.toISOString()
218
+ }));
219
+ }
220
+ }
221
+
222
+ // Singleton instance
223
+ const sessionManager = new SessionManager();
224
+
225
+ export const ready = sessionManager.ready;
226
226
  export default sessionManager;
@@ -1,54 +1,54 @@
1
- import type {
2
- FetchHistoryOptions,
3
- FetchHistoryResult,
4
- LLMProvider,
5
- McpScope,
6
- NormalizedMessage,
7
- ProviderAuthStatus,
8
- ProviderMcpServer,
9
- UpsertProviderMcpServerInput,
10
- } from '@/shared/types.js';
11
-
12
- /**
13
- * Main provider contract for CLI and SDK integrations.
14
- *
15
- * Each concrete provider owns its MCP/auth handlers plus the provider-specific
16
- * logic for converting native events/history into the app's normalized shape.
17
- */
18
- export interface IProvider {
19
- readonly id: LLMProvider;
20
- readonly mcp: IProviderMcp;
21
- readonly auth: IProviderAuth;
22
- readonly sessions: IProviderSessions;
23
- }
24
-
25
-
26
- /**
27
- * Auth contract for one provider.
28
- */
29
- export interface IProviderAuth {
30
- /**
31
- * Checks whether the provider is installed and has usable credentials.
32
- */
33
- getStatus(): Promise<ProviderAuthStatus>;
34
- }
35
-
36
- /**
37
- * MCP contract for one provider.
38
- */
39
- export interface IProviderMcp {
40
- listServers(options?: { workspacePath?: string }): Promise<Record<McpScope, ProviderMcpServer[]>>;
41
- listServersForScope(scope: McpScope, options?: { workspacePath?: string }): Promise<ProviderMcpServer[]>;
42
- upsertServer(input: UpsertProviderMcpServerInput): Promise<ProviderMcpServer>;
43
- removeServer(
44
- input: { name: string; scope?: McpScope; workspacePath?: string },
45
- ): Promise<{ removed: boolean; provider: LLMProvider; name: string; scope: McpScope }>;
46
- }
47
-
48
- /**
49
- * Session/history contract for one provider.
50
- */
51
- export interface IProviderSessions {
52
- normalizeMessage(raw: unknown, sessionId: string | null): NormalizedMessage[];
53
- fetchHistory(sessionId: string, options?: FetchHistoryOptions): Promise<FetchHistoryResult>;
54
- }
1
+ import type {
2
+ FetchHistoryOptions,
3
+ FetchHistoryResult,
4
+ LLMProvider,
5
+ McpScope,
6
+ NormalizedMessage,
7
+ ProviderAuthStatus,
8
+ ProviderMcpServer,
9
+ UpsertProviderMcpServerInput,
10
+ } from '@/shared/types.js';
11
+
12
+ /**
13
+ * Main provider contract for CLI and SDK integrations.
14
+ *
15
+ * Each concrete provider owns its MCP/auth handlers plus the provider-specific
16
+ * logic for converting native events/history into the app's normalized shape.
17
+ */
18
+ export interface IProvider {
19
+ readonly id: LLMProvider;
20
+ readonly mcp: IProviderMcp;
21
+ readonly auth: IProviderAuth;
22
+ readonly sessions: IProviderSessions;
23
+ }
24
+
25
+
26
+ /**
27
+ * Auth contract for one provider.
28
+ */
29
+ export interface IProviderAuth {
30
+ /**
31
+ * Checks whether the provider is installed and has usable credentials.
32
+ */
33
+ getStatus(): Promise<ProviderAuthStatus>;
34
+ }
35
+
36
+ /**
37
+ * MCP contract for one provider.
38
+ */
39
+ export interface IProviderMcp {
40
+ listServers(options?: { workspacePath?: string }): Promise<Record<McpScope, ProviderMcpServer[]>>;
41
+ listServersForScope(scope: McpScope, options?: { workspacePath?: string }): Promise<ProviderMcpServer[]>;
42
+ upsertServer(input: UpsertProviderMcpServerInput): Promise<ProviderMcpServer>;
43
+ removeServer(
44
+ input: { name: string; scope?: McpScope; workspacePath?: string },
45
+ ): Promise<{ removed: boolean; provider: LLMProvider; name: string; scope: McpScope }>;
46
+ }
47
+
48
+ /**
49
+ * Session/history contract for one provider.
50
+ */
51
+ export interface IProviderSessions {
52
+ normalizeMessage(raw: unknown, sessionId: string | null): NormalizedMessage[];
53
+ fetchHistory(sessionId: string, options?: FetchHistoryOptions): Promise<FetchHistoryResult>;
54
+ }