agentgui 1.0.92 → 1.0.93

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/database.js CHANGED
@@ -197,7 +197,8 @@ try {
197
197
  projectPath: 'TEXT',
198
198
  gitBranch: 'TEXT',
199
199
  sourcePath: 'TEXT',
200
- lastSyncedAt: 'INTEGER'
200
+ lastSyncedAt: 'INTEGER',
201
+ workingDirectory: 'TEXT'
201
202
  };
202
203
 
203
204
  let addedColumns = false;
@@ -228,18 +229,19 @@ function generateId(prefix) {
228
229
  }
229
230
 
230
231
  export const queries = {
231
- createConversation(agentId, title = null) {
232
+ createConversation(agentId, title = null, workingDirectory = null) {
232
233
  const id = generateId('conv');
233
234
  const now = Date.now();
234
235
  const stmt = db.prepare(
235
- `INSERT INTO conversations (id, agentId, title, created_at, updated_at, status) VALUES (?, ?, ?, ?, ?, ?)`
236
+ `INSERT INTO conversations (id, agentId, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?)`
236
237
  );
237
- stmt.run(id, agentId, title, now, now, 'active');
238
+ stmt.run(id, agentId, title, now, now, 'active', workingDirectory);
238
239
 
239
240
  return {
240
241
  id,
241
242
  agentId,
242
243
  title,
244
+ workingDirectory,
243
245
  created_at: now,
244
246
  updated_at: now,
245
247
  status: 'active'
@@ -258,7 +260,7 @@ export const queries = {
258
260
 
259
261
  getConversationsList() {
260
262
  const stmt = db.prepare(
261
- 'SELECT id, title, agentType, created_at, updated_at, messageCount FROM conversations WHERE status != ? ORDER BY updated_at DESC'
263
+ 'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory FROM conversations WHERE status != ? ORDER BY updated_at DESC'
262
264
  );
263
265
  return stmt.all('deleted');
264
266
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.92",
3
+ "version": "1.0.93",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -79,8 +79,8 @@ const server = http.createServer(async (req, res) => {
79
79
 
80
80
  if (pathOnly === '/api/conversations' && req.method === 'POST') {
81
81
  const body = await parseBody(req);
82
- const conversation = queries.createConversation(body.agentId, body.title);
83
- queries.createEvent('conversation.created', { agentId: body.agentId }, conversation.id);
82
+ const conversation = queries.createConversation(body.agentId, body.title, body.workingDirectory || null);
83
+ queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory }, conversation.id);
84
84
  broadcastSync({ type: 'conversation_created', conversation });
85
85
  res.writeHead(201, { 'Content-Type': 'application/json' });
86
86
  res.end(JSON.stringify({ conversation }));
@@ -377,7 +377,8 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
377
377
  try {
378
378
  debugLog(`[stream] Starting: conversationId=${conversationId}, sessionId=${sessionId}, agentId=${agentId}, skipPermissions=${skipPermissions}`);
379
379
 
380
- const cwd = '/config';
380
+ const conv = queries.getConversation(conversationId);
381
+ const cwd = conv?.workingDirectory || '/config';
381
382
  const actualAgentId = agentId || 'claude-code';
382
383
 
383
384
  debugLog(`[stream] Calling runClaudeWithStreaming with config: skipPermissions=${skipPermissions}`);
@@ -505,7 +506,8 @@ async function processMessage(conversationId, messageId, content, agentId) {
505
506
  try {
506
507
  debugLog(`[processMessage] Starting: conversationId=${conversationId}, agentId=${agentId}`);
507
508
 
508
- const cwd = '/config';
509
+ const conv = queries.getConversation(conversationId);
510
+ const cwd = conv?.workingDirectory || '/config';
509
511
  const actualAgentId = agentId || 'claude-code';
510
512
 
511
513
  // Handle both string content and object content (for structured messages)
package/static/index.html CHANGED
@@ -552,6 +552,180 @@
552
552
  }
553
553
  }
554
554
 
555
+ /* Folder Browser Modal */
556
+ .folder-modal-overlay {
557
+ display: none;
558
+ position: fixed;
559
+ top: 0;
560
+ left: 0;
561
+ width: 100%;
562
+ height: 100%;
563
+ background: rgba(0, 0, 0, 0.5);
564
+ z-index: 2000;
565
+ align-items: center;
566
+ justify-content: center;
567
+ }
568
+
569
+ .folder-modal-overlay.visible {
570
+ display: flex;
571
+ }
572
+
573
+ .folder-modal {
574
+ background: var(--color-bg-primary);
575
+ border: 1px solid var(--color-border);
576
+ border-radius: 0.5rem;
577
+ width: 500px;
578
+ max-width: 90vw;
579
+ max-height: 80vh;
580
+ display: flex;
581
+ flex-direction: column;
582
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
583
+ }
584
+
585
+ .folder-modal-header {
586
+ padding: 1rem;
587
+ border-bottom: 1px solid var(--color-border);
588
+ display: flex;
589
+ justify-content: space-between;
590
+ align-items: center;
591
+ flex-shrink: 0;
592
+ }
593
+
594
+ .folder-modal-header h3 {
595
+ margin: 0;
596
+ font-size: 1rem;
597
+ font-weight: 600;
598
+ }
599
+
600
+ .folder-modal-close {
601
+ background: none;
602
+ border: none;
603
+ font-size: 1.25rem;
604
+ cursor: pointer;
605
+ color: var(--color-text-secondary);
606
+ padding: 0.25rem;
607
+ line-height: 1;
608
+ }
609
+
610
+ .folder-modal-close:hover {
611
+ color: var(--color-text-primary);
612
+ }
613
+
614
+ .folder-breadcrumb {
615
+ padding: 0.75rem 1rem;
616
+ border-bottom: 1px solid var(--color-border);
617
+ font-size: 0.8rem;
618
+ font-family: 'Monaco', 'Menlo', monospace;
619
+ color: var(--color-text-secondary);
620
+ background: var(--color-bg-secondary);
621
+ display: flex;
622
+ align-items: center;
623
+ gap: 0.25rem;
624
+ flex-shrink: 0;
625
+ overflow-x: auto;
626
+ white-space: nowrap;
627
+ }
628
+
629
+ .folder-breadcrumb-segment {
630
+ cursor: pointer;
631
+ color: var(--color-primary);
632
+ padding: 0.125rem 0.25rem;
633
+ border-radius: 0.25rem;
634
+ }
635
+
636
+ .folder-breadcrumb-segment:hover {
637
+ background: var(--color-bg-primary);
638
+ text-decoration: underline;
639
+ }
640
+
641
+ .folder-breadcrumb-separator {
642
+ color: var(--color-text-secondary);
643
+ }
644
+
645
+ .folder-list {
646
+ flex: 1;
647
+ overflow-y: auto;
648
+ min-height: 200px;
649
+ max-height: 400px;
650
+ list-style: none;
651
+ margin: 0;
652
+ padding: 0;
653
+ }
654
+
655
+ .folder-list-item {
656
+ padding: 0.5rem 1rem;
657
+ cursor: pointer;
658
+ display: flex;
659
+ align-items: center;
660
+ gap: 0.5rem;
661
+ font-size: 0.875rem;
662
+ border-bottom: 1px solid var(--color-border);
663
+ transition: background-color 0.15s;
664
+ }
665
+
666
+ .folder-list-item:hover {
667
+ background: var(--color-bg-secondary);
668
+ }
669
+
670
+ .folder-list-item-icon {
671
+ font-size: 1rem;
672
+ flex-shrink: 0;
673
+ width: 1.25rem;
674
+ text-align: center;
675
+ }
676
+
677
+ .folder-list-item-name {
678
+ flex: 1;
679
+ overflow: hidden;
680
+ text-overflow: ellipsis;
681
+ white-space: nowrap;
682
+ }
683
+
684
+ .folder-list-empty {
685
+ padding: 2rem 1rem;
686
+ text-align: center;
687
+ color: var(--color-text-secondary);
688
+ font-size: 0.875rem;
689
+ }
690
+
691
+ .folder-list-loading {
692
+ padding: 2rem 1rem;
693
+ text-align: center;
694
+ color: var(--color-text-secondary);
695
+ font-size: 0.875rem;
696
+ }
697
+
698
+ .folder-list-error {
699
+ padding: 1rem;
700
+ text-align: center;
701
+ color: var(--color-error);
702
+ font-size: 0.875rem;
703
+ }
704
+
705
+ .folder-modal-footer {
706
+ padding: 0.75rem 1rem;
707
+ border-top: 1px solid var(--color-border);
708
+ display: flex;
709
+ justify-content: flex-end;
710
+ gap: 0.5rem;
711
+ flex-shrink: 0;
712
+ }
713
+
714
+ .folder-modal-footer .btn {
715
+ padding: 0.5rem 1rem;
716
+ font-size: 0.8rem;
717
+ }
718
+
719
+ .btn-secondary {
720
+ background: var(--color-bg-secondary);
721
+ color: var(--color-text-primary);
722
+ border: 1px solid var(--color-border);
723
+ }
724
+
725
+ .btn-secondary:hover {
726
+ background: var(--color-border);
727
+ }
728
+
555
729
  /* Utilities */
556
730
  .flex {
557
731
  display: flex;
@@ -849,6 +1023,24 @@
849
1023
  </div>
850
1024
  </div>
851
1025
 
1026
+ <!-- Folder Browser Modal -->
1027
+ <div id="folderBrowserModal" class="folder-modal-overlay">
1028
+ <div class="folder-modal">
1029
+ <div class="folder-modal-header">
1030
+ <h3>Select Working Directory</h3>
1031
+ <button class="folder-modal-close" data-folder-close>&times;</button>
1032
+ </div>
1033
+ <div id="folderBreadcrumb" class="folder-breadcrumb"></div>
1034
+ <ul id="folderList" class="folder-list">
1035
+ <li class="folder-list-loading">Loading...</li>
1036
+ </ul>
1037
+ <div class="folder-modal-footer">
1038
+ <button class="btn btn-secondary" data-folder-cancel>Cancel</button>
1039
+ <button class="btn btn-primary" data-folder-select>Select This Folder</button>
1040
+ </div>
1041
+ </div>
1042
+ </div>
1043
+
852
1044
  <!-- Scripts - Order matters! -->
853
1045
  <!-- 1. Event Processor (no dependencies) -->
854
1046
  <script src="/gm/js/event-processor.js"></script>
@@ -168,8 +168,10 @@ class AgentGUIClient {
168
168
  themeToggle.addEventListener('click', () => this.toggleTheme());
169
169
  }
170
170
 
171
- // Listen for new conversation creation
172
- window.addEventListener('create-new-conversation', () => this.createNewConversation());
171
+ window.addEventListener('create-new-conversation', (event) => {
172
+ const detail = event.detail || {};
173
+ this.createNewConversation(detail.workingDirectory, detail.title);
174
+ });
173
175
 
174
176
  // Listen for conversation selection
175
177
  window.addEventListener('conversation-selected', (event) => {
@@ -636,16 +638,17 @@ class AgentGUIClient {
636
638
  /**
637
639
  * Create a new empty conversation
638
640
  */
639
- async createNewConversation() {
641
+ async createNewConversation(workingDirectory, title) {
640
642
  try {
641
643
  const agentId = this.ui.agentSelector?.value || 'claude-code';
644
+ const convTitle = title || 'New Conversation';
645
+ const body = { agentId, title: convTitle };
646
+ if (workingDirectory) body.workingDirectory = workingDirectory;
647
+
642
648
  const response = await fetch(window.__BASE_URL + '/api/conversations', {
643
649
  method: 'POST',
644
650
  headers: { 'Content-Type': 'application/json' },
645
- body: JSON.stringify({
646
- agentId,
647
- title: 'New Conversation'
648
- })
651
+ body: JSON.stringify(body)
649
652
  });
650
653
 
651
654
  if (!response.ok) {
@@ -655,10 +658,13 @@ class AgentGUIClient {
655
658
  const { conversation } = await response.json();
656
659
  this.state.currentConversation = conversation;
657
660
 
658
- // Refresh conversation list
659
661
  await this.loadConversations();
660
662
 
661
- // Clear input for next execution
663
+ if (window.conversationManager) {
664
+ window.conversationManager.loadConversations();
665
+ window.conversationManager.select(conversation.id);
666
+ }
667
+
662
668
  if (this.ui.messageInput) {
663
669
  this.ui.messageInput.value = '';
664
670
  this.ui.messageInput.focus();
@@ -690,10 +696,11 @@ class AgentGUIClient {
690
696
  // Clear output and display conversation header
691
697
  const outputEl = document.getElementById('output');
692
698
  if (outputEl) {
699
+ const wdInfo = conversation.workingDirectory ? ` • ${this.escapeHtml(conversation.workingDirectory)}` : '';
693
700
  outputEl.innerHTML = `
694
701
  <div class="conversation-header">
695
702
  <h2>${this.escapeHtml(conversation.title || 'Conversation')}</h2>
696
- <p class="text-secondary">${conversation.agentType || 'unknown'} • ${new Date(conversation.created_at).toLocaleDateString()}</p>
703
+ <p class="text-secondary">${conversation.agentType || 'unknown'} • ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
697
704
  </div>
698
705
  <div class="conversation-messages">
699
706
  ${this.renderMessages(messagesData.messages || [])}
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Conversations Module
3
3
  * Manages conversation list sidebar with real-time updates
4
+ * Includes folder browser for selecting working directory on new conversation
4
5
  */
5
6
 
6
7
  class ConversationManager {
@@ -12,23 +13,180 @@ class ConversationManager {
12
13
  this.newBtn = document.querySelector('[data-new-conversation]');
13
14
  this.sidebarEl = document.querySelector('[data-sidebar]');
14
15
 
16
+ this.folderBrowser = {
17
+ modal: null,
18
+ listEl: null,
19
+ breadcrumbEl: null,
20
+ currentPath: '~',
21
+ homePath: '~'
22
+ };
23
+
15
24
  if (!this.listEl) return;
16
25
 
17
26
  this.init();
18
27
  }
19
28
 
20
29
  async init() {
21
- this.newBtn?.addEventListener('click', () => this.createNew());
30
+ this.newBtn?.addEventListener('click', () => this.openFolderBrowser());
22
31
  this.loadConversations();
23
32
  this.setupWebSocketListener();
33
+ this.setupFolderBrowser();
24
34
 
25
- // Auto-refresh every 30 seconds
26
35
  setInterval(() => this.loadConversations(), 30000);
27
36
  }
28
37
 
38
+ setupFolderBrowser() {
39
+ this.folderBrowser.modal = document.getElementById('folderBrowserModal');
40
+ this.folderBrowser.listEl = document.getElementById('folderList');
41
+ this.folderBrowser.breadcrumbEl = document.getElementById('folderBreadcrumb');
42
+
43
+ if (!this.folderBrowser.modal) return;
44
+
45
+ const closeBtn = this.folderBrowser.modal.querySelector('[data-folder-close]');
46
+ const cancelBtn = this.folderBrowser.modal.querySelector('[data-folder-cancel]');
47
+ const selectBtn = this.folderBrowser.modal.querySelector('[data-folder-select]');
48
+
49
+ closeBtn?.addEventListener('click', () => this.closeFolderBrowser());
50
+ cancelBtn?.addEventListener('click', () => this.closeFolderBrowser());
51
+ selectBtn?.addEventListener('click', () => this.confirmFolderSelection());
52
+
53
+ this.folderBrowser.modal.addEventListener('click', (e) => {
54
+ if (e.target === this.folderBrowser.modal) this.closeFolderBrowser();
55
+ });
56
+
57
+ this.fetchHomePath();
58
+ }
59
+
60
+ async fetchHomePath() {
61
+ try {
62
+ const res = await fetch((window.__BASE_URL || '') + '/api/home');
63
+ if (res.ok) {
64
+ const data = await res.json();
65
+ this.folderBrowser.homePath = data.home || '~';
66
+ }
67
+ } catch (e) {
68
+ console.error('Failed to fetch home path:', e);
69
+ }
70
+ }
71
+
72
+ openFolderBrowser() {
73
+ if (!this.folderBrowser.modal) {
74
+ this.createNew();
75
+ return;
76
+ }
77
+ this.folderBrowser.currentPath = '~';
78
+ this.folderBrowser.modal.classList.add('visible');
79
+ this.loadFolders('~');
80
+ }
81
+
82
+ closeFolderBrowser() {
83
+ this.folderBrowser.modal?.classList.remove('visible');
84
+ }
85
+
86
+ async loadFolders(dirPath) {
87
+ this.folderBrowser.currentPath = dirPath;
88
+ this.renderBreadcrumb(dirPath);
89
+
90
+ if (!this.folderBrowser.listEl) return;
91
+ this.folderBrowser.listEl.innerHTML = '<li class="folder-list-loading">Loading...</li>';
92
+
93
+ try {
94
+ const res = await fetch((window.__BASE_URL || '') + '/api/folders', {
95
+ method: 'POST',
96
+ headers: { 'Content-Type': 'application/json' },
97
+ body: JSON.stringify({ path: dirPath })
98
+ });
99
+
100
+ if (!res.ok) {
101
+ const errData = await res.json().catch(() => ({}));
102
+ throw new Error(errData.error || `HTTP ${res.status}`);
103
+ }
104
+
105
+ const data = await res.json();
106
+ const folders = data.folders || [];
107
+
108
+ this.folderBrowser.listEl.innerHTML = '';
109
+
110
+ if (dirPath !== '~' && dirPath !== '/' && dirPath !== this.folderBrowser.homePath) {
111
+ const parentPath = this.getParentPath(dirPath);
112
+ const upItem = document.createElement('li');
113
+ upItem.className = 'folder-list-item';
114
+ upItem.innerHTML = '<span class="folder-list-item-icon">..</span><span class="folder-list-item-name">Parent Directory</span>';
115
+ upItem.addEventListener('click', () => this.loadFolders(parentPath));
116
+ this.folderBrowser.listEl.appendChild(upItem);
117
+ }
118
+
119
+ if (folders.length === 0 && this.folderBrowser.listEl.children.length === 0) {
120
+ this.folderBrowser.listEl.innerHTML = '<li class="folder-list-empty">No subdirectories</li>';
121
+ return;
122
+ }
123
+
124
+ for (const folder of folders) {
125
+ const li = document.createElement('li');
126
+ li.className = 'folder-list-item';
127
+ li.innerHTML = `<span class="folder-list-item-icon">&#128193;</span><span class="folder-list-item-name">${this.escapeHtml(folder.name)}</span>`;
128
+ li.addEventListener('click', () => {
129
+ const expandedBase = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
130
+ const newPath = expandedBase + '/' + folder.name;
131
+ this.loadFolders(newPath);
132
+ });
133
+ this.folderBrowser.listEl.appendChild(li);
134
+ }
135
+ } catch (err) {
136
+ console.error('Failed to load folders:', err);
137
+ this.folderBrowser.listEl.innerHTML = `<li class="folder-list-error">Error: ${this.escapeHtml(err.message)}</li>`;
138
+ }
139
+ }
140
+
141
+ getParentPath(dirPath) {
142
+ const expanded = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
143
+ const parts = expanded.split('/').filter(Boolean);
144
+ if (parts.length <= 1) return '/';
145
+ parts.pop();
146
+ return '/' + parts.join('/');
147
+ }
148
+
149
+ renderBreadcrumb(dirPath) {
150
+ if (!this.folderBrowser.breadcrumbEl) return;
151
+
152
+ const expanded = dirPath === '~' ? this.folderBrowser.homePath : dirPath;
153
+ const parts = expanded.split('/').filter(Boolean);
154
+
155
+ let html = '';
156
+ html += '<span class="folder-breadcrumb-segment" data-path="/">/ </span>';
157
+
158
+ let accumulated = '';
159
+ for (let i = 0; i < parts.length; i++) {
160
+ accumulated += '/' + parts[i];
161
+ const isLast = i === parts.length - 1;
162
+ html += '<span class="folder-breadcrumb-separator">/</span>';
163
+ html += `<span class="folder-breadcrumb-segment${isLast ? '' : ''}" data-path="${this.escapeHtml(accumulated)}">${this.escapeHtml(parts[i])}</span>`;
164
+ }
165
+
166
+ this.folderBrowser.breadcrumbEl.innerHTML = html;
167
+
168
+ this.folderBrowser.breadcrumbEl.querySelectorAll('.folder-breadcrumb-segment').forEach(seg => {
169
+ seg.addEventListener('click', () => {
170
+ const p = seg.dataset.path;
171
+ if (p) this.loadFolders(p);
172
+ });
173
+ });
174
+ }
175
+
176
+ confirmFolderSelection() {
177
+ const currentPath = this.folderBrowser.currentPath;
178
+ const expanded = currentPath === '~' ? this.folderBrowser.homePath : currentPath;
179
+ this.closeFolderBrowser();
180
+
181
+ const dirName = expanded.split('/').filter(Boolean).pop() || 'root';
182
+ window.dispatchEvent(new CustomEvent('create-new-conversation', {
183
+ detail: { workingDirectory: expanded, title: dirName }
184
+ }));
185
+ }
186
+
29
187
  async loadConversations() {
30
188
  try {
31
- const res = await fetch('/gm/api/conversations');
189
+ const res = await fetch((window.__BASE_URL || '') + '/api/conversations');
32
190
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
33
191
 
34
192
  const data = await res.json();
@@ -51,7 +209,6 @@ class ConversationManager {
51
209
  this.listEl.innerHTML = '';
52
210
  this.emptyEl.style.display = 'none';
53
211
 
54
- // Sort by most recent first
55
212
  const sorted = [...this.conversations].sort((a, b) =>
56
213
  new Date(b.createdAt || 0) - new Date(a.createdAt || 0)
57
214
  );
@@ -71,10 +228,13 @@ class ConversationManager {
71
228
  const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
72
229
  const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
73
230
  const agent = conv.agentType || 'unknown';
231
+ const wd = conv.workingDirectory ? conv.workingDirectory.split('/').pop() : '';
232
+ const metaParts = [agent, timestamp];
233
+ if (wd) metaParts.push(wd);
74
234
 
75
235
  li.innerHTML = `
76
236
  <div class="conversation-item-title">${this.escapeHtml(title)}</div>
77
- <div class="conversation-item-meta">${agent} ${timestamp}</div>
237
+ <div class="conversation-item-meta">${metaParts.join(' \u2022 ')}</div>
78
238
  `;
79
239
 
80
240
  li.addEventListener('click', () => this.select(conv.id));
@@ -84,7 +244,6 @@ class ConversationManager {
84
244
  select(convId) {
85
245
  this.activeId = convId;
86
246
 
87
- // Update active indicator
88
247
  document.querySelectorAll('.conversation-item').forEach(item => {
89
248
  item.classList.remove('active');
90
249
  });
@@ -92,7 +251,6 @@ class ConversationManager {
92
251
  const active = document.querySelector(`[data-conv-id="${convId}"]`);
93
252
  if (active) active.classList.add('active');
94
253
 
95
- // Emit event for client.js to handle
96
254
  window.dispatchEvent(new CustomEvent('conversation-selected', {
97
255
  detail: { conversationId: convId }
98
256
  }));
@@ -110,7 +268,6 @@ class ConversationManager {
110
268
  }
111
269
 
112
270
  addConversation(conv) {
113
- // Add to beginning (most recent)
114
271
  this.conversations.unshift(conv);
115
272
  this.render();
116
273
  }
@@ -149,7 +306,6 @@ class ConversationManager {
149
306
  }
150
307
  }
151
308
 
152
- // Initialize when DOM is ready
153
309
  if (document.readyState === 'loading') {
154
310
  document.addEventListener('DOMContentLoaded', () => {
155
311
  window.conversationManager = new ConversationManager();