agentgui 1.0.540 → 1.0.542
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/CLAUDE.md +85 -0
- package/package.json +1 -1
- package/static/js/streaming-renderer.js +24 -5
package/CLAUDE.md
CHANGED
|
@@ -219,6 +219,91 @@ After update/install completes:
|
|
|
219
219
|
3. UI version display updates to show new version
|
|
220
220
|
4. Status reverts to "Installed" or "Up-to-date" accordingly
|
|
221
221
|
|
|
222
|
+
## Base64 Image Rendering in File Read Events
|
|
223
|
+
|
|
224
|
+
### Problem: Images Displaying as Raw Text
|
|
225
|
+
|
|
226
|
+
When an agent reads an image file, the streaming event may not have `type='file_read'`. It can arrive with any type (or fall through to the default case in the renderer switch). Without the `renderGeneric` fallback, the image data displays as raw base64 text instead of an `<img>` element.
|
|
227
|
+
|
|
228
|
+
### Event Structure for Image File Reads
|
|
229
|
+
|
|
230
|
+
Two nested structures are used by different agent versions. Both must be handled:
|
|
231
|
+
|
|
232
|
+
**Structure A** (nested under `source`):
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"type": "<anything>",
|
|
236
|
+
"path": "/path/to/image.png",
|
|
237
|
+
"content": {
|
|
238
|
+
"source": {
|
|
239
|
+
"type": "base64",
|
|
240
|
+
"data": "<base64-string>"
|
|
241
|
+
},
|
|
242
|
+
"media_type": "image/png"
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Structure B** (flat inside `content`):
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"type": "<anything>",
|
|
251
|
+
"path": "/path/to/image.png",
|
|
252
|
+
"content": {
|
|
253
|
+
"type": "base64",
|
|
254
|
+
"data": "<base64-string>"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Structure C** (content is raw base64 string, no wrapping object):
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"type": "<anything>",
|
|
263
|
+
"path": "/path/to/image.png",
|
|
264
|
+
"content": "iVBORw0KGgo..."
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
Structure C is detected by `detectBase64Image()` which checks magic-byte prefixes (PNG: `iVBORw0KGgo`, JPEG: `/9j/4AAQ`, WebP: `UklGRi`, GIF: `R0lGODlh`).
|
|
268
|
+
|
|
269
|
+
### Two Rendering Paths in streaming-renderer.js
|
|
270
|
+
|
|
271
|
+
**Path 1 – Direct dispatch** (`renderEvent` switch statement):
|
|
272
|
+
- `case 'file_read'` routes directly to `renderFileRead(event)`.
|
|
273
|
+
- Handles all three content structures above.
|
|
274
|
+
|
|
275
|
+
**Path 2 – Generic fallback** (`renderGeneric`):
|
|
276
|
+
- Called for any event type not matched by the switch (the `default` case).
|
|
277
|
+
- First thing it does: check for `event.content?.source?.type === 'base64'` OR `event.content?.type === 'base64'` AND `event.path` present.
|
|
278
|
+
- If true, delegates to `renderFileRead(event)` so the image renders correctly.
|
|
279
|
+
- Without this fallback, any file-read event that arrives with an unrecognised type displays as raw key-value text.
|
|
280
|
+
|
|
281
|
+
### MIME Type Resolution in renderFileRead
|
|
282
|
+
|
|
283
|
+
Priority order inside `renderFileRead`:
|
|
284
|
+
1. `event.media_type` field (explicit)
|
|
285
|
+
2. Detected from magic bytes via `detectBase64Image()` → maps `jpeg` → `image/jpeg`, others → `image/<type>`
|
|
286
|
+
3. Falls back to `application/octet-stream` (shows broken image)
|
|
287
|
+
|
|
288
|
+
Always include `media_type` on the event when possible. If absent, magic-byte detection covers PNG/JPEG/WebP/GIF automatically.
|
|
289
|
+
|
|
290
|
+
### Debugging Checklist When Images Show as Text
|
|
291
|
+
|
|
292
|
+
1. `console.log(event)` the raw event object arriving at the renderer — verify `content` structure.
|
|
293
|
+
2. Check `event.type` — if it is not `'file_read'`, the switch default fires `renderGeneric`.
|
|
294
|
+
3. Confirm `renderGeneric` has the base64 fallback guard at the top (search for `content?.source?.type === 'base64'`).
|
|
295
|
+
4. Confirm `renderFileRead` handles both `content.source.data` and `content.data` paths (both exist in the code).
|
|
296
|
+
5. Verify `event.path` is set — the fallback in `renderGeneric` requires `event.path` to delegate correctly.
|
|
297
|
+
6. If `media_type` is missing and content is not PNG/JPEG/WebP/GIF, add it to the event or extend `detectBase64Image` signatures.
|
|
298
|
+
|
|
299
|
+
### Why Two Attempts Failed Before the Fix
|
|
300
|
+
|
|
301
|
+
- Attempt 1: Modified only `renderFileRead` but the event had an unrecognised type so the switch never reached `renderFileRead`.
|
|
302
|
+
- Attempt 2: Added fallback in `renderGeneric` but checked only `event.content?.source?.type === 'base64'` — missed Structure B where data sits directly on `event.content` (no `source` nesting).
|
|
303
|
+
- Fix: `renderGeneric` now checks both structures before falling through to generic key-value rendering.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
222
307
|
## Tool Update Testing & Diagnostics
|
|
223
308
|
|
|
224
309
|
A comprehensive diagnostic page is available at `http://localhost:3000/gm/tool-update-test.html` (`static/tool-update-test.html`) with 7 interactive test sections:
|
package/package.json
CHANGED
|
@@ -1671,12 +1671,26 @@ class StreamingRenderer {
|
|
|
1671
1671
|
let html = '';
|
|
1672
1672
|
if (event.path) html += this.renderFilePath(event.path);
|
|
1673
1673
|
if (event.content) {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1674
|
+
let base64Data = null;
|
|
1675
|
+
let mimeType = event.media_type || 'application/octet-stream';
|
|
1676
|
+
if (typeof event.content === 'string') {
|
|
1677
|
+
const imageInfo = this.detectBase64Image(event.content);
|
|
1678
|
+
if (imageInfo) {
|
|
1679
|
+
base64Data = imageInfo.data;
|
|
1680
|
+
mimeType = imageInfo.type === 'jpeg' ? 'image/jpeg' : `image/${imageInfo.type}`;
|
|
1681
|
+
}
|
|
1682
|
+
} else if (typeof event.content === 'object' && event.content !== null) {
|
|
1683
|
+
if (event.content.source?.type === 'base64' && event.content.source?.data) {
|
|
1684
|
+
base64Data = event.content.source.data;
|
|
1685
|
+
} else if (event.content.type === 'base64' && event.content.data) {
|
|
1686
|
+
base64Data = event.content.data;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
if (base64Data) {
|
|
1690
|
+
html += `<div style="padding:0.5rem;display:flex;flex-direction:column;gap:0.5rem"><img src="data:${mimeType};base64,${this.escapeHtml(base64Data)}" style="max-width:100%;max-height:600px;border-radius:0.375rem;border:1px solid #334155" loading="lazy"><div style="font-size:0.7rem;color:#64748b;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;word-break:break-all">${this.escapeHtml(event.path)}</div></div>`;
|
|
1678
1691
|
} else {
|
|
1679
|
-
|
|
1692
|
+
const contentStr = typeof event.content === 'string' ? event.content : JSON.stringify(event.content, null, 2);
|
|
1693
|
+
html += `<pre style="background:#1e293b;padding:0.75rem;border-radius:0.375rem;overflow-x:auto;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.75rem;line-height:1.5;color:#e2e8f0;margin:0.5rem 0 0 0"><code class="lazy-hl">${this.escapeHtml(this.truncateContent(contentStr, 2000))}</code></pre>`;
|
|
1680
1694
|
}
|
|
1681
1695
|
}
|
|
1682
1696
|
body.innerHTML = html;
|
|
@@ -1982,6 +1996,11 @@ class StreamingRenderer {
|
|
|
1982
1996
|
* Render generic event with formatted key-value pairs
|
|
1983
1997
|
*/
|
|
1984
1998
|
renderGeneric(event) {
|
|
1999
|
+
// Check if this is actually a file read with base64 image content
|
|
2000
|
+
if ((event.content?.source?.type === 'base64' || event.content?.type === 'base64') && event.path) {
|
|
2001
|
+
return this.renderFileRead(event);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1985
2004
|
const div = document.createElement('div');
|
|
1986
2005
|
div.className = 'event-generic mb-3 p-3 bg-gray-100 dark:bg-gray-800 rounded text-sm';
|
|
1987
2006
|
div.dataset.eventId = event.id || '';
|