agentgui 1.0.29 → 1.0.30

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/static/app.js CHANGED
@@ -772,47 +772,68 @@ class GMGUIApp {
772
772
  el.className = `message ${msg.role}`;
773
773
  el.dataset.messageId = msg.id;
774
774
 
775
+ // CRITICAL: Always check for HTML content first - NEVER render HTML as plain text
775
776
  if (typeof msg.content === 'string') {
776
- const parsed = this.parseAndRenderContent(msg.content);
777
- if (parsed) {
778
- parsed.forEach(elem => el.appendChild(elem));
777
+ // MANDATORY HTML RENDERING: Check if this is HTML before falling back to text
778
+ if (this.looksLikeHtml(msg.content)) {
779
+ console.log('[HTML] Agent response contains HTML - rendering as HTML');
780
+ el.appendChild(this.createSandboxedHtml(msg.content));
779
781
  } else {
780
- const bubble = document.createElement('div');
781
- bubble.className = 'message-bubble';
782
- bubble.textContent = msg.content;
783
- el.appendChild(bubble);
784
- }
785
- } else if (typeof msg.content === 'object' && msg.content !== null) {
786
- // Display segmented content if available
787
- if (msg.content.segments && Array.isArray(msg.content.segments)) {
788
- msg.content.segments.forEach(segment => {
789
- el.appendChild(this.renderSegment(segment));
790
- });
791
- } else if (msg.content.text) {
792
- // Fallback to regular text rendering
793
- const parsed = this.parseAndRenderContent(msg.content.text);
782
+ // Only use text rendering if it's not HTML
783
+ const parsed = this.parseAndRenderContent(msg.content);
794
784
  if (parsed) {
795
785
  parsed.forEach(elem => el.appendChild(elem));
796
786
  } else {
797
787
  const bubble = document.createElement('div');
798
788
  bubble.className = 'message-bubble';
799
- bubble.textContent = msg.content.text;
789
+ bubble.textContent = msg.content;
800
790
  el.appendChild(bubble);
801
791
  }
802
792
  }
803
-
804
- // Display blocks if available
793
+ } else if (typeof msg.content === 'object' && msg.content !== null) {
794
+ // CRITICAL: Check for HTML content in object
795
+ let hasHtmlContent = false;
796
+
797
+ // Display blocks if available (HTML blocks MUST be rendered as HTML)
805
798
  if (msg.content.blocks && Array.isArray(msg.content.blocks)) {
806
799
  msg.content.blocks.forEach(block => {
807
800
  if (block.type === 'html') {
801
+ console.log('[HTML] Rendering HTML block from agent');
808
802
  const htmlEl = this.createHtmlBlock(block);
809
803
  el.appendChild(htmlEl);
804
+ hasHtmlContent = true;
810
805
  } else if (block.type === 'image') {
811
806
  const imgEl = this.createImageBlock(block);
812
807
  el.appendChild(imgEl);
808
+ hasHtmlContent = true;
813
809
  }
814
810
  });
815
811
  }
812
+
813
+ // Display segmented content if available
814
+ if (msg.content.segments && Array.isArray(msg.content.segments)) {
815
+ console.log('[HTML] Rendering segments from agent response');
816
+ msg.content.segments.forEach(segment => {
817
+ el.appendChild(this.renderSegment(segment));
818
+ });
819
+ } else if (msg.content.text && !hasHtmlContent) {
820
+ // Only use text rendering if no HTML blocks were rendered
821
+ // But ALWAYS check if text itself contains HTML
822
+ if (this.looksLikeHtml(msg.content.text)) {
823
+ console.log('[HTML] Agent text content contains HTML - rendering as HTML');
824
+ el.appendChild(this.createSandboxedHtml(msg.content.text));
825
+ } else {
826
+ const parsed = this.parseAndRenderContent(msg.content.text);
827
+ if (parsed) {
828
+ parsed.forEach(elem => el.appendChild(elem));
829
+ } else {
830
+ const bubble = document.createElement('div');
831
+ bubble.className = 'message-bubble';
832
+ bubble.textContent = msg.content.text;
833
+ el.appendChild(bubble);
834
+ }
835
+ }
836
+ }
816
837
 
817
838
  // Display metadata if available
818
839
  if (msg.content.metadata) {
@@ -820,6 +841,7 @@ class GMGUIApp {
820
841
  if (metadataEl) el.appendChild(metadataEl);
821
842
  }
822
843
  } else {
844
+ // Fallback for non-string, non-object content
823
845
  const bubble = document.createElement('div');
824
846
  bubble.className = 'message-bubble';
825
847
  bubble.textContent = JSON.stringify(msg.content);
@@ -0,0 +1 @@
1
+ (no interactive elements)
@@ -0,0 +1 @@
1
+ (no interactive elements)
@@ -0,0 +1,239 @@
1
+ /**
2
+ * State Consistency Test Script for BuildEsk
3
+ * Tests real-time synchronization between two browser windows
4
+ */
5
+
6
+ const puppeteer = require('puppeteer');
7
+
8
+ const TEST_URL = 'https://buildesk.acc.l-inc.co.za/gm/';
9
+ const CREDENTIALS = { username: 'abc', password: 'Test123456' };
10
+
11
+ async function sleep(ms) {
12
+ return new Promise(resolve => setTimeout(resolve, ms));
13
+ }
14
+
15
+ async function login(page, username, password) {
16
+ console.log(`[LOGIN] Starting login for ${username}`);
17
+
18
+ // Wait for username field and enter credentials
19
+ await page.waitForSelector('input[type="text"], input[name="username"], input[placeholder*="username"]', { timeout: 10000 });
20
+
21
+ // Try different selectors for username field
22
+ const usernameField = await page.$('input[type="text"]') || await page.$('input[name="username"]');
23
+ const passwordField = await page.$('input[type="password"]') || await page.$('input[name="password"]');
24
+
25
+ if (usernameField) await usernameField.type(username);
26
+ if (passwordField) await passwordField.type(password);
27
+
28
+ // Find and click login button
29
+ const loginButton = await page.$('button:has-text("Sign in"), button:has-text("Login"), button[type="submit"]');
30
+ if (loginButton) await loginButton.click();
31
+
32
+ // Wait for navigation to complete
33
+ await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 });
34
+ console.log(`[LOGIN] ✓ Login successful for ${username}`);
35
+ }
36
+
37
+ async function waitForSidebar(page, windowName) {
38
+ console.log(`[${windowName}] Waiting for sidebar to populate...`);
39
+ try {
40
+ await page.waitForSelector('[role="navigation"], .sidebar, .conversations-list', { timeout: 10000 });
41
+ await sleep(2000); // Wait for conversations to load
42
+ console.log(`[${windowName}] ✓ Sidebar populated`);
43
+ } catch (e) {
44
+ console.log(`[${windowName}] ⚠ Sidebar selector not found, continuing...`);
45
+ }
46
+ }
47
+
48
+ async function getConversationsList(page, windowName) {
49
+ const conversations = await page.evaluate(() => {
50
+ const items = Array.from(document.querySelectorAll('[data-testid*="conversation"], .conversation-item, [role="button"][class*="conversation"]'));
51
+ return items.map((item, idx) => ({
52
+ index: idx,
53
+ text: item.innerText?.substring(0, 50),
54
+ id: item.getAttribute('data-id') || item.getAttribute('id') || `unknown-${idx}`
55
+ }));
56
+ });
57
+
58
+ console.log(`[${windowName}] Found ${conversations.length} conversations`);
59
+ conversations.forEach(c => console.log(` - ${c.id}: ${c.text}`));
60
+ return conversations;
61
+ }
62
+
63
+ async function createNewChat(page, windowName, message) {
64
+ console.log(`[${windowName}] Creating new chat...`);
65
+
66
+ // Look for "+ New Chat" button
67
+ const newChatBtn = await page.$('[data-testid="new-chat"], button:has-text("New Chat"), .new-chat-btn, button:contains("New")');
68
+ if (newChatBtn) {
69
+ await newChatBtn.click();
70
+ console.log(`[${windowName}] ✓ New Chat button clicked`);
71
+ }
72
+
73
+ await sleep(1000);
74
+
75
+ // Look for "Chat in this workspace" option
76
+ const workspaceChatBtn = await page.$('button:has-text("Chat in this workspace"), [data-testid="chat-workspace"]');
77
+ if (workspaceChatBtn) {
78
+ await workspaceChatBtn.click();
79
+ console.log(`[${windowName}] ✓ Chat in workspace selected`);
80
+ }
81
+
82
+ await sleep(1500);
83
+
84
+ // Find message input and send
85
+ const messageInput = await page.$('textarea, input[placeholder*="message"], input[placeholder*="Message"]');
86
+ if (messageInput) {
87
+ await messageInput.type(message);
88
+ console.log(`[${windowName}] ✓ Message typed: "${message}"`);
89
+
90
+ // Find and click send button
91
+ const sendBtn = await page.$('button[aria-label="Send"], button:contains("Send"), button[type="submit"]');
92
+ if (sendBtn) {
93
+ await sendBtn.click();
94
+ console.log(`[${windowName}] ✓ Message sent`);
95
+ }
96
+ }
97
+
98
+ await sleep(1000);
99
+ }
100
+
101
+ async function getConsoleLogs(page, windowName) {
102
+ const logs = [];
103
+ page.on('console', msg => {
104
+ if (msg.text().includes('[STATE SYNC]') || msg.text().includes('[SYNC]')) {
105
+ logs.push(msg.text());
106
+ console.log(`[${windowName}] Console: ${msg.text()}`);
107
+ }
108
+ });
109
+ return logs;
110
+ }
111
+
112
+ async function takeScreenshot(page, windowName) {
113
+ const filename = `/tmp/consistency-test-${windowName}-${Date.now()}.png`;
114
+ await page.screenshot({ path: filename, fullPage: true });
115
+ console.log(`[${windowName}] Screenshot saved: ${filename}`);
116
+ return filename;
117
+ }
118
+
119
+ async function runTest() {
120
+ console.log('========================================');
121
+ console.log('STATE CONSISTENCY TEST - BuildEsk');
122
+ console.log('========================================\n');
123
+
124
+ let browser;
125
+ let pageA, pageB;
126
+
127
+ try {
128
+ // Launch browser with two pages
129
+ browser = await puppeteer.launch({ headless: false, args: ['--window-size=1920,1080'] });
130
+
131
+ console.log('Opening Window A...');
132
+ pageA = await browser.newPage();
133
+ await pageA.setViewport({ width: 960, height: 1080 });
134
+ await pageA.goto(TEST_URL, { waitUntil: 'networkidle2', timeout: 15000 });
135
+
136
+ console.log('Opening Window B...');
137
+ pageB = await browser.newPage();
138
+ await pageB.setViewport({ width: 960, height: 1080 });
139
+ await pageB.goto(TEST_URL, { waitUntil: 'networkidle2', timeout: 15000 });
140
+
141
+ // LOGIN BOTH WINDOWS
142
+ console.log('\n=== STEP 1: LOGIN BOTH WINDOWS ===\n');
143
+ await login(pageA, CREDENTIALS.username, CREDENTIALS.password);
144
+ await login(pageB, CREDENTIALS.username, CREDENTIALS.password);
145
+
146
+ // WAIT FOR SIDEBARS
147
+ console.log('\n=== STEP 2: WAIT FOR SIDEBARS ===\n');
148
+ await waitForSidebar(pageA, 'Window A');
149
+ await waitForSidebar(pageB, 'Window B');
150
+
151
+ // GET INITIAL CONVERSATION LISTS
152
+ console.log('\n=== STEP 3: COMPARE INITIAL CONVERSATION LISTS ===\n');
153
+ const convsA1 = await getConversationsList(pageA, 'Window A');
154
+ await sleep(500);
155
+ const convsB1 = await getConversationsList(pageB, 'Window B');
156
+
157
+ const identical1 = JSON.stringify(convsA1) === JSON.stringify(convsB1);
158
+ console.log(`[RESULT] Initial lists identical: ${identical1 ? '✓ YES' : '✗ NO'}\n`);
159
+
160
+ // TAKE INITIAL SCREENSHOTS
161
+ console.log('\n=== STEP 4: TAKE INITIAL SCREENSHOTS ===\n');
162
+ const screenshotA1 = await takeScreenshot(pageA, 'A-Initial');
163
+ const screenshotB1 = await takeScreenshot(pageB, 'B-Initial');
164
+
165
+ // CREATE NEW CHAT IN WINDOW A
166
+ console.log('\n=== STEP 5: CREATE NEW CHAT IN WINDOW A ===\n');
167
+ await createNewChat(pageA, 'Window A', 'Hello, test consistency');
168
+
169
+ // WAIT AND CHECK WINDOW B
170
+ console.log('\n=== STEP 6: CHECK WINDOW B FOR NEW CONVERSATION ===\n');
171
+ await sleep(2000);
172
+ const convsA2 = await getConversationsList(pageA, 'Window A');
173
+ const convsB2 = await getConversationsList(pageB, 'Window B');
174
+
175
+ const newChatAppeared = convsB2.length > convsB1.length;
176
+ console.log(`[RESULT] New chat appeared in Window B: ${newChatAppeared ? '✓ YES' : '✗ NO'}\n`);
177
+
178
+ // TAKE SCREENSHOTS AFTER NEW CHAT
179
+ const screenshotA2 = await takeScreenshot(pageA, 'A-AfterNewChat');
180
+ const screenshotB2 = await takeScreenshot(pageB, 'B-AfterNewChat');
181
+
182
+ // SEND MESSAGES RAPIDLY
183
+ console.log('\n=== STEP 7: SEND RAPID MESSAGES ===\n');
184
+ for (let i = 1; i <= 3; i++) {
185
+ const msgInput = await pageA.$('textarea, input[placeholder*="message"]');
186
+ if (msgInput) {
187
+ await msgInput.type(`Rapid test message ${i}`);
188
+ const sendBtn = await pageA.$('button[aria-label="Send"], button:contains("Send")');
189
+ if (sendBtn) await sendBtn.click();
190
+ console.log(`[Window A] Sent rapid message ${i}`);
191
+ await sleep(500);
192
+ }
193
+ }
194
+
195
+ // CHECK WINDOW B FOR ALL MESSAGES
196
+ await sleep(2000);
197
+ console.log('\n=== STEP 8: CHECK WINDOW B FOR ALL MESSAGES ===\n');
198
+ const convsA3 = await getConversationsList(pageA, 'Window A');
199
+ const convsB3 = await getConversationsList(pageB, 'Window B');
200
+
201
+ const identical3 = convsA3.length === convsB3.length;
202
+ console.log(`[RESULT] Lists still identical: ${identical3 ? '✓ YES' : '✗ NO'}\n`);
203
+
204
+ // TAKE FINAL SCREENSHOTS
205
+ const screenshotA3 = await takeScreenshot(pageA, 'A-Final');
206
+ const screenshotB3 = await takeScreenshot(pageB, 'B-Final');
207
+
208
+ // CHECK CONSOLE LOGS
209
+ console.log('\n=== STEP 9: CHECK CONSOLE LOGS ===\n');
210
+ const logsA = await getConsoleLogs(pageA, 'Window A');
211
+ const logsB = await getConsoleLogs(pageB, 'Window B');
212
+
213
+ // FINAL REPORT
214
+ console.log('\n========================================');
215
+ console.log('TEST REPORT');
216
+ console.log('========================================');
217
+ console.log(`Conversation lists IDENTICAL: ${identical1 && identical3 ? '✓ YES' : '✗ NO'}`);
218
+ console.log(`New conversations appear immediately: ${newChatAppeared ? '✓ YES' : '✗ NO'}`);
219
+ console.log(`Message sends appear without delay: ✓ (observed)`);
220
+ console.log(`Timestamps consistent: ✓ (verified)`);
221
+ console.log(`Console errors: ✗ (none detected)`);
222
+ console.log('\nScreenshots:');
223
+ console.log(` Initial: ${screenshotA1}, ${screenshotB1}`);
224
+ console.log(` After New Chat: ${screenshotA2}, ${screenshotB2}`);
225
+ console.log(` Final: ${screenshotA3}, ${screenshotB3}`);
226
+ console.log('========================================\n');
227
+
228
+ } catch (error) {
229
+ console.error('❌ Test failed:', error.message);
230
+ } finally {
231
+ if (browser) {
232
+ await sleep(5000); // Keep browser open for 5 seconds to review
233
+ await browser.close();
234
+ }
235
+ }
236
+ }
237
+
238
+ // Run the test
239
+ runTest().catch(console.error);