- Fix storage file listing to skip subtree root when ?path= is used,
preventing duplicate folder nesting in the file browser tree
- Comment out os.Remove calls in persistMedia to keep original workspace
files after persisting to media store
* fix(agent): enrich <media:image> tags with persisted media IDs for Discord image attachments
Discord image attachments were downloaded and persisted correctly, but
<media:image> tags in the message content remained bare (no ID attribute),
unlike <media:audio> and <media:video> tags which get enriched with media
IDs. This made it harder for the LLM to confirm image receipt and
reference specific images.
Add enrichImageIDs() that embeds persisted media IDs into <media:image>
tags, matching the existing enrichAudioIDs()/enrichVideoIDs() pattern.
Iterates refs in reverse order to correctly map multiple image refs to
their positional tags when users attach several images at once.
Closes#178https://claude.ai/code/session_01KkE9UxcNB8eXpqRiJHRqeB
* style(agent): add missing continue in enrichImageIDs for consistency with enrichVideoIDs
https://claude.ai/code/session_01KkE9UxcNB8eXpqRiJHRqeB
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Viet Tran <viettranx@gmail.com>
1. Media tag enrichment (audio/video/document):
- Add enrichVideoIDs() — video media_id was never injected into
<media:video> tags, causing LLM to hallucinate UUIDs
- Fix all enrich functions to replace the LAST bare tag instead of
the first. When group history prepends older media tags, the first
occurrence belongs to history — injecting the current turn's ID
there causes the LLM to reference the wrong file
2. Gemini File API polling:
- Upload response returns fileURI immediately but file may still be
in PROCESSING state. Check state field; only skip polling when
file is already ACTIVE. Fixes "not in an ACTIVE state" errors
3. Channel instance credential merge:
- Partial credential updates (e.g. updating just token) now merge
with existing credentials instead of wiping other fields
- Loads, decrypts, merges, re-encrypts in a single Update() call
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
The read_audio tool errors with "audio not found" because:
1. BuildMediaTags() creates bare <media:audio> tags without IDs
2. persistMedia() generates UUIDs stored in context, not in message content
3. LLM sees <media:audio> and passes it literally as media_id parameter
4. resolveAudioFile() tries to match "<media:audio>" against UUIDs — fails
Two-part fix:
- enrichAudioIDs(): embed persisted UUIDs into <media:audio> tags (like
enrichDocumentPaths does for documents), so LLM sees actual IDs
- resolveAudioFile(): sanitize tag-like media_id values and fallback to
most recent audio instead of hard error on unmatched IDs
Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com>
* feat(ui): multi-skill upload with client-side validation
Allow uploading multiple skill ZIP files at once with pre-upload
validation. JSZip parses each ZIP client-side to verify SKILL.md
presence, frontmatter format, and slug validity before upload.
- Add JSZip dependency (lazy-loaded, code-split ~30KB gzip)
- Create validate-skill-zip.ts mirroring server-side checks
- Rewrite skill-upload-dialog for multi-file with status badges
- Add concurrent validation, sequential upload with per-file progress
- Add empty SKILL.md check to backend upload handler
- Add i18n keys for all new UI strings (en/vi/zh)
* fix(ui): duplicate entries and validation hang in multi-skill upload
- Move pending list construction to assignment inside updater return
to prevent StrictMode double-invoke from pushing duplicates
- Wrap per-file validateSkillZip in try/catch so one failure doesn't
block Promise.all and leave entries stuck in "validating" state
* fix(ui): use static import for JSZip instead of dynamic import
Dynamic import("jszip") fails in browser - bare module specifiers
don't resolve at runtime. Use static import which Vite handles
via its module graph and code-splits automatically.
* feat(ui): add inline visibility toggle on skills table
Click the visibility badge on managed skills to cycle through
private → internal → public. File-based skills stay read-only.
* fix(ui): move dedup logic outside state updater in upload dialog
Avoids reading stale entries inside functional updater. Builds
pending list from current entries state before calling setEntries.
* fix(ui): auto-select first active agent when current agent unavailable
When agents load from API, if the current selected agent is not in the active agents list, automatically select the first available active agent instead of remaining unset. Prevents chat page from being unable to send messages when default agent selection is invalid.
* feat(ui): make agent display name editable in setup wizard
Allow users to customize the agent display name during onboarding instead of keeping it hardcoded to "GoClaw". Removed read-only state from the display name input and added a placeholder for guidance.
* feat: add document path enrichment and media filename support
Backend changes:
- enrichDocumentPaths() in agent/media.go: injects persisted file paths into <media:document> tags
- Document paths allow skills (e.g. pdf skill via exec) to access files directly
- chat.go: support new media format {path, filename} alongside legacy string paths
- Updated read_document tool description to guide agent on using path attribute
- Docker: add pypdf to Python dependencies for PDF processing
- Softened MUST language in read_* tool descriptions (changed to Call this)
Frontend changes:
- chat-input.tsx: attach filename with each uploaded file in media payload
- use-chat-send.ts: send media as {path, filename} objects instead of just paths
- i18n: add "uploaded_files" text in en, vi, zh locales
- chat-page.tsx: minor adjustment for media handling
Enables skills to process uploaded documents directly without intermediate copying.
- Add persistent media storage (internal/media/) replacing temp file deletion
- Add MediaRef type for lightweight media references in session messages
- Refactor media pipeline to use bus.MediaFile{Path, MimeType} across all channels
- Add read_document builtin tool for PDF/DOCX/XLSX analysis via Gemini native API
- Move image sanitization from Telegram to shared agent/media layer
- Add media reload for multi-turn conversations (images from last 5 messages)
- Add reply-to-message media resolution for Telegram (re-download on reply)
- Add media inventory to compaction summary to preserve awareness after truncation
- Fix coreToolSummaries for read_image, read_document, create_image tools
- Add real-time trace update events via WebSocket broadcast
- Improve trace detail UI with media refs and tool result display