antri_cli 1.50.0 → 1.52.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.
@@ -0,0 +1,188 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ const CHATS_DIR = path.join(os.homedir(), '.antri', 'chats');
5
+ const ACTIVE_SESSION_FILE = path.join(CHATS_DIR, '.active_session');
6
+ export class SessionManager {
7
+ chatsDir;
8
+ activeSessionId = '';
9
+ constructor(customDir) {
10
+ this.chatsDir = customDir || CHATS_DIR;
11
+ this.ensureDirectory();
12
+ this.initActiveSession();
13
+ }
14
+ ensureDirectory() {
15
+ if (!fs.existsSync(this.chatsDir)) {
16
+ fs.mkdirSync(this.chatsDir, { recursive: true });
17
+ }
18
+ }
19
+ initActiveSession() {
20
+ try {
21
+ if (fs.existsSync(ACTIVE_SESSION_FILE)) {
22
+ const id = fs.readFileSync(ACTIVE_SESSION_FILE, 'utf-8').trim();
23
+ if (id && fs.existsSync(this.getSessionFilePath(id))) {
24
+ this.activeSessionId = id;
25
+ return;
26
+ }
27
+ }
28
+ }
29
+ catch { }
30
+ // Pick first existing session or create a default session
31
+ const sessions = this.listSessions();
32
+ if (sessions.length > 0) {
33
+ this.activeSessionId = sessions[0].id;
34
+ }
35
+ else {
36
+ const newSession = this.createSession('New Chat');
37
+ this.activeSessionId = newSession.id;
38
+ }
39
+ this.saveActiveSessionId();
40
+ }
41
+ getSessionFilePath(id) {
42
+ const cleanId = id.replace(/[^a-zA-Z0-9_-]/g, '_');
43
+ return path.join(this.chatsDir, `${cleanId}.json`);
44
+ }
45
+ saveActiveSessionId() {
46
+ try {
47
+ this.ensureDirectory();
48
+ fs.writeFileSync(ACTIVE_SESSION_FILE, this.activeSessionId, 'utf-8');
49
+ }
50
+ catch { }
51
+ }
52
+ getActiveSessionId() {
53
+ return this.activeSessionId;
54
+ }
55
+ setActiveSessionId(id) {
56
+ const session = this.getSession(id);
57
+ if (session) {
58
+ this.activeSessionId = id;
59
+ this.saveActiveSessionId();
60
+ return session;
61
+ }
62
+ return null;
63
+ }
64
+ createSession(title = 'New Chat') {
65
+ this.ensureDirectory();
66
+ const id = `chat_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
67
+ const session = {
68
+ id,
69
+ title,
70
+ createdAt: Date.now(),
71
+ updatedAt: Date.now(),
72
+ messages: [],
73
+ };
74
+ this.saveSession(session);
75
+ this.activeSessionId = id;
76
+ this.saveActiveSessionId();
77
+ return session;
78
+ }
79
+ getSession(id) {
80
+ const filePath = this.getSessionFilePath(id);
81
+ if (fs.existsSync(filePath)) {
82
+ try {
83
+ const raw = fs.readFileSync(filePath, 'utf-8');
84
+ return JSON.parse(raw);
85
+ }
86
+ catch { }
87
+ }
88
+ return null;
89
+ }
90
+ getActiveSession() {
91
+ const session = this.getSession(this.activeSessionId);
92
+ if (session)
93
+ return session;
94
+ return this.createSession('New Chat');
95
+ }
96
+ saveSession(session) {
97
+ this.ensureDirectory();
98
+ session.updatedAt = Date.now();
99
+ const filePath = this.getSessionFilePath(session.id);
100
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2), 'utf-8');
101
+ }
102
+ addMessageToActiveSession(message) {
103
+ const session = this.getActiveSession();
104
+ session.messages.push({
105
+ ...message,
106
+ timestamp: message.timestamp || Date.now(),
107
+ });
108
+ // Auto-generate title on first user message if title is "New Chat"
109
+ if (session.title === 'New Chat' && message.role === 'user') {
110
+ const cleanPrompt = message.content.replace(/\[Attached File:[^\]]+\]/g, '').trim();
111
+ if (cleanPrompt) {
112
+ session.title = cleanPrompt.length > 35 ? `${cleanPrompt.slice(0, 32)}...` : cleanPrompt;
113
+ }
114
+ }
115
+ this.saveSession(session);
116
+ return session;
117
+ }
118
+ listSessions() {
119
+ this.ensureDirectory();
120
+ try {
121
+ const files = fs.readdirSync(this.chatsDir).filter((f) => f.endsWith('.json') && !f.startsWith('.'));
122
+ const summaries = [];
123
+ for (const file of files) {
124
+ try {
125
+ const filePath = path.join(this.chatsDir, file);
126
+ const raw = fs.readFileSync(filePath, 'utf-8');
127
+ const data = JSON.parse(raw);
128
+ if (data && data.id) {
129
+ const lastUserMsg = data.messages?.filter((m) => m.role === 'user').pop();
130
+ summaries.push({
131
+ id: data.id,
132
+ title: data.title || 'Untitled Chat',
133
+ createdAt: data.createdAt || 0,
134
+ updatedAt: data.updatedAt || 0,
135
+ messageCount: data.messages?.length || 0,
136
+ preview: lastUserMsg?.content?.slice(0, 80) || '',
137
+ isActive: data.id === this.activeSessionId,
138
+ });
139
+ }
140
+ }
141
+ catch { }
142
+ }
143
+ return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
144
+ }
145
+ catch {
146
+ return [];
147
+ }
148
+ }
149
+ deleteSession(id) {
150
+ const filePath = this.getSessionFilePath(id);
151
+ try {
152
+ if (fs.existsSync(filePath)) {
153
+ fs.unlinkSync(filePath);
154
+ }
155
+ if (this.activeSessionId === id) {
156
+ const remaining = this.listSessions();
157
+ if (remaining.length > 0) {
158
+ this.activeSessionId = remaining[0].id;
159
+ }
160
+ else {
161
+ const fresh = this.createSession('New Chat');
162
+ this.activeSessionId = fresh.id;
163
+ }
164
+ this.saveActiveSessionId();
165
+ }
166
+ return true;
167
+ }
168
+ catch {
169
+ return false;
170
+ }
171
+ }
172
+ renameSession(id, newTitle) {
173
+ const session = this.getSession(id);
174
+ if (session) {
175
+ session.title = newTitle.trim() || 'Untitled Chat';
176
+ this.saveSession(session);
177
+ return true;
178
+ }
179
+ return false;
180
+ }
181
+ clearActiveSessionMessages() {
182
+ const session = this.getActiveSession();
183
+ session.messages = [];
184
+ this.saveSession(session);
185
+ }
186
+ }
187
+ export const sessionManager = new SessionManager();
188
+ //# sourceMappingURL=sessionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionManager.js","sourceRoot":"","sources":["../../src/core/sessionManager.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  export declare class Updater {
2
2
  static readonly PACKAGE_NAME = "antri_cli";
3
- static readonly CURRENT_VERSION = "1.50.0";
3
+ static readonly CURRENT_VERSION = "1.52.0";
4
4
  /**
5
5
  * Fetches latest release version directly from registry.npmjs.org (cache-free)
6
6
  */
@@ -5,7 +5,7 @@ import ora from 'ora';
5
5
  const execPromise = util.promisify(exec);
6
6
  export class Updater {
7
7
  static PACKAGE_NAME = 'antri_cli';
8
- static CURRENT_VERSION = '1.50.0';
8
+ static CURRENT_VERSION = '1.52.0';
9
9
  /**
10
10
  * Fetches latest release version directly from registry.npmjs.org (cache-free)
11
11
  */
@@ -19,6 +19,7 @@ document.addEventListener('DOMContentLoaded', async () => {
19
19
  await checkAuthStatus();
20
20
  await loadStatus();
21
21
  await loadCommands();
22
+ await loadChatSessions();
22
23
  await loadProfiles();
23
24
  await loadSkills();
24
25
  await loadMemory();
@@ -401,6 +402,146 @@ function handleInputKey(event) {
401
402
  }
402
403
  }
403
404
 
405
+ // ==========================================
406
+ // Multi-Chat Session Management
407
+ // ==========================================
408
+ let currentActiveChatId = '';
409
+ let allChatSessions = [];
410
+
411
+ async function loadChatSessions(renderMessages = true) {
412
+ try {
413
+ const res = await fetch('/api/chats');
414
+ const data = await res.json();
415
+ allChatSessions = data.sessions || [];
416
+ currentActiveChatId = data.activeId || (allChatSessions[0]?.id || '');
417
+
418
+ const select = document.getElementById('select-chat-session');
419
+ if (select) {
420
+ select.innerHTML = '';
421
+ allChatSessions.forEach((s) => {
422
+ const opt = document.createElement('option');
423
+ opt.value = s.id;
424
+ opt.textContent = `${s.title} (${s.messageCount || 0})`;
425
+ if (s.id === currentActiveChatId) opt.selected = true;
426
+ select.appendChild(opt);
427
+ });
428
+ }
429
+
430
+ if (renderMessages && data.activeSession) {
431
+ renderActiveSessionMessages(data.activeSession.messages || []);
432
+ }
433
+ } catch (err) {
434
+ console.error('Failed to load chat sessions:', err);
435
+ }
436
+ }
437
+
438
+ function renderActiveSessionMessages(messages) {
439
+ const container = document.getElementById('chat-messages');
440
+ if (!container) return;
441
+ container.innerHTML = '';
442
+
443
+ if (!messages || messages.length === 0) {
444
+ container.innerHTML = `
445
+ <div class="welcome-card" id="chat-welcome-card">
446
+ <h2>ANTRI Control Plane</h2>
447
+ <p>Minimalist environment for autonomous coding, architectural planning, and self-refinement. State, memory, profiles, and skills are synchronized across CLI and Desktop.</p>
448
+ <div class="quick-action-pills">
449
+ <button onclick="setPrompt('Plan the architecture for a real-time collaborative code editor')">Plan Editor Architecture</button>
450
+ <button onclick="setPrompt('/debate What are the trade-offs between WebSockets vs Server-Sent Events?')">Start Dialectic Debate</button>
451
+ <button onclick="setPrompt('/goal Implement a zero-dependency LRU cache with TTL in TypeScript')">Run Goal Loop</button>
452
+ </div>
453
+ </div>
454
+ `;
455
+ return;
456
+ }
457
+
458
+ messages.forEach((msg) => {
459
+ if (msg.role === 'user' || msg.role === 'assistant') {
460
+ appendMessage(msg.role, msg.content);
461
+ }
462
+ });
463
+ }
464
+
465
+ async function createNewChatSession() {
466
+ try {
467
+ const res = await fetch('/api/chats/new', {
468
+ method: 'POST',
469
+ headers: { 'Content-Type': 'application/json' },
470
+ body: JSON.stringify({ title: 'New Chat' }),
471
+ });
472
+ const data = await res.json();
473
+ if (data.success && data.session) {
474
+ currentActiveChatId = data.session.id;
475
+ await loadChatSessions(true);
476
+ showToast('Started new chat session.');
477
+ }
478
+ } catch (err) {
479
+ console.error('Failed to create new chat session:', err);
480
+ }
481
+ }
482
+
483
+ async function onChatSessionSelect(id) {
484
+ try {
485
+ const res = await fetch('/api/chats/select', {
486
+ method: 'POST',
487
+ headers: { 'Content-Type': 'application/json' },
488
+ body: JSON.stringify({ id }),
489
+ });
490
+ const data = await res.json();
491
+ if (data.success && data.session) {
492
+ currentActiveChatId = data.session.id;
493
+ renderActiveSessionMessages(data.session.messages || []);
494
+ await loadChatSessions(false);
495
+ }
496
+ } catch (err) {
497
+ console.error('Failed to select chat session:', err);
498
+ }
499
+ }
500
+
501
+ async function clearCurrentChatContext() {
502
+ if (!confirm('Clear all conversation messages in this chat session?')) return;
503
+ const container = document.getElementById('chat-messages');
504
+ if (container) {
505
+ container.innerHTML = `
506
+ <div class="welcome-card" id="chat-welcome-card">
507
+ <h2>ANTRI Control Plane</h2>
508
+ <p>Minimalist environment for autonomous coding, architectural planning, and self-refinement. State, memory, profiles, and skills are synchronized across CLI and Desktop.</p>
509
+ <div class="quick-action-pills">
510
+ <button onclick="setPrompt('Plan the architecture for a real-time collaborative code editor')">Plan Editor Architecture</button>
511
+ <button onclick="setPrompt('/debate What are the trade-offs between WebSockets vs Server-Sent Events?')">Start Dialectic Debate</button>
512
+ <button onclick="setPrompt('/goal Implement a zero-dependency LRU cache with TTL in TypeScript')">Run Goal Loop</button>
513
+ </div>
514
+ </div>
515
+ `;
516
+ }
517
+ await fetch('/api/chat', {
518
+ method: 'POST',
519
+ headers: { 'Content-Type': 'application/json' },
520
+ body: JSON.stringify({ prompt: '/clear' }),
521
+ }).catch(() => {});
522
+ await loadChatSessions(false);
523
+ showToast('Chat context cleared.');
524
+ }
525
+
526
+ async function deleteCurrentChatSession() {
527
+ if (!currentActiveChatId) return;
528
+ if (!confirm('Are you sure you want to delete this chat session?')) return;
529
+ try {
530
+ const res = await fetch('/api/chats/delete', {
531
+ method: 'POST',
532
+ headers: { 'Content-Type': 'application/json' },
533
+ body: JSON.stringify({ id: currentActiveChatId }),
534
+ });
535
+ const data = await res.json();
536
+ if (data.success) {
537
+ await loadChatSessions(true);
538
+ showToast('Chat session deleted.');
539
+ }
540
+ } catch (err) {
541
+ console.error('Failed to delete chat session:', err);
542
+ }
543
+ }
544
+
404
545
  // Chat Prompt Submission with Real-Time Word-by-Word Side-by-Side Streaming
405
546
  async function submitPrompt() {
406
547
  const input = document.getElementById('prompt-input');
@@ -419,6 +560,12 @@ async function submitPrompt() {
419
560
  input.value = '';
420
561
  hidePalette();
421
562
 
563
+ // Remove welcome card if present
564
+ const welcomeCard = document.getElementById('chat-welcome-card');
565
+ if (welcomeCard) {
566
+ welcomeCard.remove();
567
+ }
568
+
422
569
  // Intercept /debate or /goal inside chat
423
570
  if (prompt.startsWith('/debate')) {
424
571
  showTab('dialectic');
@@ -502,6 +649,7 @@ async function submitPrompt() {
502
649
  } finally {
503
650
  sendBtn.disabled = false;
504
651
  sendBtn.innerHTML = '<span>Send</span>';
652
+ await loadChatSessions(false);
505
653
  }
506
654
  }
507
655
 
@@ -920,7 +1068,7 @@ async function deleteActiveProfile() {
920
1068
  alert('Cannot delete default profile_1.');
921
1069
  return;
922
1070
  }
923
- if (!confirm(`Are you sure you want to delete profile '${target}.md'?`)) return;
1071
+ if (!confirm(`Are you sure you want to delete profile '${target}.md' from both your device and the cloud? It will not be pulled again.`)) return;
924
1072
 
925
1073
  const res = await fetch('/api/profile/delete', {
926
1074
  method: 'POST',
@@ -931,7 +1079,7 @@ async function deleteActiveProfile() {
931
1079
  if (data.success) {
932
1080
  currentViewedProfile = 'profile_1';
933
1081
  await loadProfiles();
934
- showToast(`Profile '${target}' deleted.`);
1082
+ showToast(`Profile '${target}.md' deleted from device and cloud.`);
935
1083
  }
936
1084
  }
937
1085
 
@@ -15,7 +15,7 @@
15
15
  <header class="app-header">
16
16
  <div class="brand-zone">
17
17
  <span class="logo-mark">ANTRI</span>
18
- <span class="version-tag">v1.50.0</span>
18
+ <span class="version-tag">v1.52.0</span>
19
19
  </div>
20
20
 
21
21
  <!-- Mode Toggle -->
@@ -109,8 +109,22 @@
109
109
  <!-- TAB 1: Chat & Agent Studio -->
110
110
  <section id="tab-chat" class="tab-panel active">
111
111
  <div class="chat-container">
112
+ <!-- Chat Session Switcher Header -->
113
+ <div class="chat-session-header">
114
+ <div class="chat-session-selector-group">
115
+ <button class="new-chat-btn" onclick="createNewChatSession()">+ New Chat</button>
116
+ <select id="select-chat-session" class="chat-session-select" onchange="onChatSessionSelect(this.value)">
117
+ <!-- Populated dynamically -->
118
+ </select>
119
+ </div>
120
+ <div class="chat-session-actions">
121
+ <button class="chat-session-action-btn" title="Clear conversation context" onclick="clearCurrentChatContext()">🧹 Clear</button>
122
+ <button class="chat-session-action-btn btn-danger" title="Delete this chat" onclick="deleteCurrentChatSession()">🗑️</button>
123
+ </div>
124
+ </div>
125
+
112
126
  <div id="chat-messages" class="chat-messages">
113
- <div class="welcome-card">
127
+ <div class="welcome-card" id="chat-welcome-card">
114
128
  <h2>ANTRI Control Plane</h2>
115
129
  <p>Minimalist environment for autonomous coding, architectural planning, and self-refinement. State, memory, profiles, and skills are synchronized across CLI and Desktop.</p>
116
130
  <div class="quick-action-pills">
@@ -286,6 +286,85 @@ body.antri-app {
286
286
  position: relative;
287
287
  }
288
288
 
289
+ .chat-session-header {
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: space-between;
293
+ padding: 0.6rem 1rem;
294
+ background: var(--bg-card);
295
+ border: 1px solid var(--border-main);
296
+ border-radius: var(--radius-sm);
297
+ margin-bottom: 1rem;
298
+ }
299
+
300
+ .chat-session-selector-group {
301
+ display: flex;
302
+ align-items: center;
303
+ gap: 0.75rem;
304
+ flex: 1;
305
+ }
306
+
307
+ .new-chat-btn {
308
+ background: var(--accent-primary);
309
+ color: var(--text-inverted);
310
+ border: none;
311
+ padding: 0.45rem 0.9rem;
312
+ border-radius: var(--radius-xs);
313
+ font-family: var(--font-sans);
314
+ font-size: 0.8rem;
315
+ font-weight: 700;
316
+ cursor: pointer;
317
+ white-space: nowrap;
318
+ transition: all 0.15s ease;
319
+ }
320
+
321
+ .new-chat-btn:hover {
322
+ opacity: 0.9;
323
+ }
324
+
325
+ .chat-session-select {
326
+ background: var(--bg-subtle);
327
+ border: 1px solid var(--border-main);
328
+ color: var(--text-primary);
329
+ font-family: var(--font-sans);
330
+ font-size: 0.82rem;
331
+ font-weight: 600;
332
+ padding: 0.4rem 0.75rem;
333
+ border-radius: var(--radius-xs);
334
+ cursor: pointer;
335
+ max-width: 320px;
336
+ width: 100%;
337
+ outline: none;
338
+ }
339
+
340
+ .chat-session-actions {
341
+ display: flex;
342
+ align-items: center;
343
+ gap: 0.4rem;
344
+ }
345
+
346
+ .chat-session-action-btn {
347
+ background: transparent;
348
+ border: 1px solid var(--border-main);
349
+ padding: 0.35rem 0.65rem;
350
+ border-radius: var(--radius-xs);
351
+ font-size: 0.78rem;
352
+ color: var(--text-secondary);
353
+ cursor: pointer;
354
+ transition: all 0.15s ease;
355
+ }
356
+
357
+ .chat-session-action-btn:hover {
358
+ background: var(--bg-hover);
359
+ color: var(--text-primary);
360
+ }
361
+
362
+ .chat-session-action-btn.btn-danger:hover {
363
+ background: #fee2e2;
364
+ border-color: #fca5a5;
365
+ color: #dc2626;
366
+ }
367
+
289
368
  .chat-messages {
290
369
  flex: 1;
291
370
  overflow-y: auto;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/desktop/server.ts"],"names":[],"mappings":"AAqCA,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,kBAAkB,CAAiD;IAC3E,OAAO,CAAC,gBAAgB,CAAqD;;IAO7E,OAAO,CAAC,sBAAsB;IA2BjB,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAqExB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAUpB,SAAS;WAidH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBnD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/desktop/server.ts"],"names":[],"mappings":"AAqCA,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,kBAAkB,CAAiD;IAC3E,OAAO,CAAC,gBAAgB,CAAqD;;IAO7E,OAAO,CAAC,sBAAsB;IA2BjB,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAqExB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAUpB,SAAS;WAsgBH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBnD"}
@@ -227,6 +227,16 @@ export class DesktopServer {
227
227
  res.end(JSON.stringify(memoryStatus));
228
228
  return;
229
229
  }
230
+ // GET /api/chats (List multi-chat sessions)
231
+ if (pathname === '/api/chats' && req.method === 'GET') {
232
+ const { sessionManager } = await import('../core/sessionManager.js');
233
+ const sessions = sessionManager.listSessions();
234
+ const activeId = sessionManager.getActiveSessionId();
235
+ const activeSession = sessionManager.getActiveSession();
236
+ res.writeHead(200, { 'Content-Type': 'application/json' });
237
+ res.end(JSON.stringify({ sessions, activeId, activeSession }));
238
+ return;
239
+ }
230
240
  // Read Body for POST requests
231
241
  let body = '';
232
242
  req.on('data', (chunk) => {
@@ -477,6 +487,8 @@ export class DesktopServer {
477
487
  // POST /api/profile/delete
478
488
  if (pathname === '/api/profile/delete' && req.method === 'POST') {
479
489
  const ok = profileManager.deleteProfile(payload.name);
490
+ const { FirestoreSyncManager } = await import('../cloud/firestore.js');
491
+ FirestoreSyncManager.deleteFromFirestore(payload.name).catch(() => { });
480
492
  res.writeHead(200, { 'Content-Type': 'application/json' });
481
493
  res.end(JSON.stringify({ success: ok }));
482
494
  return;
@@ -504,6 +516,42 @@ export class DesktopServer {
504
516
  res.end(JSON.stringify({ success: true, skill }));
505
517
  return;
506
518
  }
519
+ // POST /api/chats/new (Create new chat session)
520
+ if (pathname === '/api/chats/new' && req.method === 'POST') {
521
+ const { sessionManager } = await import('../core/sessionManager.js');
522
+ const session = sessionManager.createSession(payload.title || 'New Chat');
523
+ this.activeAgent.syncHistoryFromActiveSession();
524
+ res.writeHead(200, { 'Content-Type': 'application/json' });
525
+ res.end(JSON.stringify({ success: true, session }));
526
+ return;
527
+ }
528
+ // POST /api/chats/select (Switch active chat session)
529
+ if (pathname === '/api/chats/select' && req.method === 'POST') {
530
+ const { sessionManager } = await import('../core/sessionManager.js');
531
+ const session = sessionManager.setActiveSessionId(payload.id);
532
+ this.activeAgent.syncHistoryFromActiveSession();
533
+ res.writeHead(200, { 'Content-Type': 'application/json' });
534
+ res.end(JSON.stringify({ success: !!session, session }));
535
+ return;
536
+ }
537
+ // POST /api/chats/delete (Delete chat session)
538
+ if (pathname === '/api/chats/delete' && req.method === 'POST') {
539
+ const { sessionManager } = await import('../core/sessionManager.js');
540
+ const ok = sessionManager.deleteSession(payload.id);
541
+ this.activeAgent.syncHistoryFromActiveSession();
542
+ const activeSession = sessionManager.getActiveSession();
543
+ res.writeHead(200, { 'Content-Type': 'application/json' });
544
+ res.end(JSON.stringify({ success: ok, activeSession }));
545
+ return;
546
+ }
547
+ // POST /api/chats/rename (Rename chat session)
548
+ if (pathname === '/api/chats/rename' && req.method === 'POST') {
549
+ const { sessionManager } = await import('../core/sessionManager.js');
550
+ const ok = sessionManager.renameSession(payload.id, payload.title);
551
+ res.writeHead(200, { 'Content-Type': 'application/json' });
552
+ res.end(JSON.stringify({ success: ok }));
553
+ return;
554
+ }
507
555
  // POST /api/skill/save
508
556
  if (pathname === '/api/skill/save' && req.method === 'POST') {
509
557
  const ok = skillManager.saveSkill(payload.id, payload.content);