Skip to content

fix(media): persist generated files, prevent duplicate delivery, and improve group media history#206

Open
vanducng wants to merge 27 commits intonextlevelbuilder:mainfrom
vanducng:fix/media-file-persistence-and-dedup
Open

fix(media): persist generated files, prevent duplicate delivery, and improve group media history#206
vanducng wants to merge 27 commits intonextlevelbuilder:mainfrom
vanducng:fix/media-file-persistence-and-dedup

Conversation

@vanducng
Copy link
Contributor

@vanducng vanducng commented Mar 15, 2026

Summary

  • Root cause: dispatch.go deleted ALL media files after channel send, including workspace-generated files (create_image, create_video, create_audio). This caused stat file: no such file errors and prevented files from persisting in workspace storage.
  • Duplicate delivery: When the LLM sent generated media via message tool (MEDIA: prefix), the same file was also sent via RunResult.Media, causing duplicate images/videos in channel chats. Previously masked by the file deletion bug.
  • Chain provider format: hasReadImageProvider only supported flat {"provider":"X"} format, not the chain format {"providers":[...]} used by media provider chain config.
  • Group media history: Discord and Zalo group chats lost media context when messages were recorded in pending history before @mention.

Changes

Fix: Media file persistence & dedup

File Change
dispatch.go Only delete temp files (/tmp/), not workspace files
loop.go Track media sent by message tool, deduplicate RunResult.Media
create_image.go Empty data guard + post-write stat verification with size logging
create_video.go Same as above
create_audio.go Same as above
send_helpers.go Log directory contents when file stat fails (diagnostics)

Feat: Chain provider format + group media history

File Change
media_tool_routing.go Support chain format {"providers":[...]} in hasReadImageProvider
history.go Add Media field to HistoryEntry, CollectMedia(), temp file cleanup on eviction/clear
discord/handler.go Record media paths in group history, collect pending media on @mention
zalo/personal/handlers.go Same pattern for Zalo group history

Test plan

  • Verified on production K8s (k3s + local-path PVC)
  • Generated image saved (847KB), persists on disk after send
  • No stat file: no such file errors in logs
  • Verify single image delivery (no duplicates) after dedup deploy
  • Verify TTS audio files from /tmp/ still cleaned up after send
  • Verify Discord/Zalo group media history works with @mention

@vanducng vanducng changed the title fix(media): persist generated files and prevent duplicate delivery fix(media): persist generated files, prevent duplicate delivery, and improve group media history Mar 15, 2026
@vanducng vanducng force-pushed the fix/media-file-persistence-and-dedup branch 3 times, most recently from 61041b4 to d8be726 Compare March 16, 2026 01:24
Root cause: dispatch.go deleted ALL media files after channel send,
including workspace-generated files (create_image, create_video, etc.).
This caused "stat file: no such file" errors on subsequent send attempts
and prevented files from persisting in workspace storage.

Additionally, when the LLM used the message tool with MEDIA: prefix to
send generated media, the same file was sent again via RunResult.Media,
causing duplicate images/videos in channel chats.

Changes:
- dispatch.go: only delete temp files (/tmp/), not workspace files
- loop.go: track media sent by message tool, deduplicate RunResult.Media
- create_image/video/audio: add empty data guard, post-write verification
  with size logging for diagnostics
- send_helpers.go: log directory contents on stat failure for debugging
…Zalo

- media_tool_routing: support chain format {"providers":[...]} in
  hasReadImageProvider (was only flat {"provider":"X"})
- history: add Media field to HistoryEntry, CollectMedia() for pending
  entries, temp file cleanup on eviction/clear
- discord/handler: record media in group history, collect on @mention
- zalo/personal/handlers: same pattern for Zalo group history
Zalo personal channel only downloaded images; non-image files (CSV, PDF,
XLSX, etc.) were replaced with a text placeholder and never persisted.
Now all attachments are downloaded, classified by MIME type, and tagged
with <media:*> for the agent pipeline (matching Telegram/Discord/Feishu).

Also remove the media bypass in InboundDebouncer so a file/image followed
by a text caption within the debounce window are merged into one agent turn.
@vanducng vanducng force-pushed the fix/media-file-persistence-and-dedup branch from d8be726 to 0d1774a Compare March 16, 2026 04:44
viettranx and others added 19 commits March 17, 2026 06:04
…hlight

- Add created_at display to trace summary and span detail
- Add PreviewBlock component with copy button (top-right) and JSON
  syntax highlighting via highlight.js
- Show span start/end time inline with timezone support
- Use Badge for span duration and created_at on summary row
- StatusBadge: icon-only on mobile, text on desktop
- Double verbose trace preview limit from 100K to 200K chars
- Double preview content height on desktop (20vh → 40vh)
- formatDate() now accepts timezone param from useUiStore
- Show seconds in all timestamp displays
- Add i18n keys for all 3 locales (en, vi, zh)
- Move task dispatch from mid-turn to post-turn to prevent dependent
  tasks from completing before the current agent's run finishes
- Add team create lock to serialize list→create flows across concurrent
  group chat sessions, preventing duplicate task creation
- Require list-before-create gate: agents must call team_tasks(list)
  before creating tasks
- Make assignee required on task creation
- Add pagination (50 per page) to task list with offset support
- Slim task list/get/search responses with dedicated structs to reduce
  context token usage
- Add task board snapshot in announce messages to leader
- Workspace: allow subdirectory paths in read/delete, show directories
  in list output
- UI: reduce kanban card title font size for better visual balance
- Change WS pairing check from fail-open to fail-closed on DB error
  (router.go: previously granted RoleOperator on any IsPaired() error)
- Add "browser" to InternalChannels so it's properly excluded from
  outbound dispatch without ad-hoc helpers
- Rate-limit browser.pairing.status endpoint to prevent sender_id
  enumeration (reuses server RateLimiter via PairingMethods injection)
- Add expires_at column to paired_devices with 30-day TTL for
  defense-in-depth; IsPaired() now checks expiry, ListPaired() prunes
- Add confidence_score column to team_tasks, team_messages,
  team_task_comments
- Bump RequiredSchemaVersion to 21
…s for team workspace

Remove dedicated workspace tools in favor of making existing file tools
(read_file, write_file, list_files, edit) team-workspace-aware.

- Delete workspace_tool_read.go and workspace_tool_write.go
- Clean up workspace_dir.go: export WorkspaceDir, remove dead code
  (workspaceRelPath, sanitizeFilePath, inferMimeType, templates, etc.)
- Remove workspace tool registration from gateway_managed.go
- Remove workspace tool references from policy, subagent, MCP bridge
- Add PathAllowable/PathDenyable to types.go for interface abstraction
… tools access

- Add WithToolTeamWorkspace/ToolTeamWorkspaceFromCtx context key for
  team workspace path (accessible but not necessarily default)
- Create WorkspaceInterceptor for team-specific write validation
  (RBAC, quota, blocked extensions, event broadcasting)
- File tools (read_file, write_file, list_files, edit) allow access
  to team workspace via allowedWithTeamWorkspace() helper
- read_file/list_files hint team workspace path when file not found
- Registry detects empty tool call args and returns actionable hint
  (DashScope/Qwen large-output truncation workaround)
- Lead agents: auto-resolve team workspace as default (relative paths)
- Dispatched members: team workspace as default via req.TeamWorkspace
- Direct-chat members: own workspace default, team workspace accessible
- Add dataDir field to Loop/LoopConfig for global workspace root
- System prompt shows team workspace absolute path for model guidance
- Remove orphan task detector (superseded by post-turn dispatch)
- Log warning on OpenAI tool call argument parse failures
… WorkspaceDir callers

- status="" now returns all tasks (was active-only); add explicit "active" filter
- Reduce list/search page size from 50 to 30
- Update WorkspaceDir callers after signature change (remove unused channel param)
- Update team_tasks schema descriptions for status and page params
Squash-merge PR nextlevelbuilder#225 with security fixes:

- Fix browser pairing stuck on "Waiting for approval" (stale closure:
  useState → useRef for senderID in pairing-form)
- Fix auto-kick after pairing (RequireAuth now accepts senderID,
  onAuthFailure skips logout for paired browser sessions)
- Allow browser-paired users to access HTTP APIs via X-GoClaw-Sender-Id
  header with fail-closed IsPaired check
- Remove ad-hoc IsInternalOrBrowser(), use channels.IsInternalChannel()
- Log failed HTTP pairing auth attempts for security monitoring
- Pass senderID to HttpClient for authenticated HTTP requests
- Kanban: reorder columns (blocked after pending), show blocked-by info
  on cards, clickable blocker links in task detail, framer-motion card
  animation between columns
- Dialogs: standardize scroll pattern across all modals — header fixed,
  scrollbar flush with outer edge via negative margin trick
- Remove delegation page, types, events, i18n, routes, and all references
- Fix activity_logs NULL jsonb scan error (COALESCE)
- Board header: show text labels on action buttons (desktop)
- Replace per-team loop with batch SQL (v2 filter in JOIN)
- RecoverAllStaleTasks/ForceRecoverAllTasks/MarkAllStaleTasks return
  RecoveredTaskInfo for notification routing
- Notify leaders per (teamID, channel, chatID) scope with actionable hints
- Fix notifyLeaderCycleError routing (was silently DROPPED)
- Stale threshold: 24h → 2h default
- Remove per-session RecoverStaleTasks from loop.go (ticker handles it)
- Add rows.Err() check to scanRecoveredTaskInfoRows
Remove delegate_search, evaluate_loop, handoff from:
- Seed data, system prompt, i18n keys/catalogs, channel events
- Consumer handler (handleHandoffAnnounce), handoff route lookup
- HandoffRouteData struct + PG implementation
- Protocol events, MCP bridge comment
- Web UI locale files (en/vi/zh)
- IsSharedWorkspace() reads team.settings.workspace_scope
- Shared: workspace at teams/{teamID}/ (all chats share)
- Isolated (default): workspace at teams/{teamID}/{chatID}/
- Remove _default fallback; isolated mode requires chat_id
- Update loop.go, task creation, task listing, message dispatch,
  workspace API (list/read/delete), task board snapshot
- Update UI descriptions to reflect per-conversation scoping
- Replace progress_notifications toggle with granular config:
  dispatched (on), progress (on), failed (on) + delivery mode
- Direct mode: outbound to channel, no AI processing
- Leader mode: inject into leader session with NO-ACTION instructions
- Add consumer.team-notify subscriber for event forwarding
- Enrich TeamTaskEventPayload with TaskNumber, ProgressPercent/Step
- Add auto-status system prompt section
- UI: card-select for delivery mode (Zap/Bot icons), 3 toggles
- Fix scope dropdown: cache scopes from initial "all" load so dropdown
  stays stable when filtering by chatID
- Derive scopes from file chat_id when task scopes are empty
- Remove dead files: team-workspace-tab.tsx, team-tasks-tab.tsx
  (replaced by board view and workspace dialog)
…uilder#231)

* feat(ui): improve kanban UX, fix dialog scroll, remove delegation page

- Kanban: reorder columns (blocked after pending), show blocked-by info
  on cards, clickable blocker links in task detail, framer-motion card
  animation between columns
- Dialogs: standardize scroll pattern across all modals — header fixed,
  scrollbar flush with outer edge via negative margin trick
- Remove delegation page, types, events, i18n, routes, and all references
- Fix activity_logs NULL jsonb scan error (COALESCE)
- Board header: show text labels on action buttons (desktop)

* docs: comprehensive audit and update of all documentation

- Update Go 1.25 → 1.26, PostgreSQL 15+ → 18 across all docs
- Add 10 missing internal modules to CLAUDE.md project structure
- Expand provider docs from 2 to 6 packages (Anthropic, OpenAI, DashScope, Claude CLI, ACP, Codex)
- Add 8 missing store interfaces to data model docs (22 total)
- Update bootstrap files from 7 to 13 templates
- Expand tool inventory from ~35 to 60+ tools with media/KG/credential categories
- Fix Team Task Board: add blocked status, 3 missing actions, V2 versioning, delegate restrictions
- Remove all references to removed features: handoff, delegate_search, evaluate_loop, agent_links
- Fix lane defaults (2/4/1 → 30/50/100/30), ghost file references, models.list → providers.models
- Add SecureCLI, snapshot worker, cost calculation, pairing security docs
- Comprehensive changelog catch-up
- Trim docs/03-tools-system.md to 800-line limit
…r providers and breaking validation (nextlevelbuilder#230)

* fix: prevent gemini thought_signature from leaking to other providers

* test: refine gemini model detection for robust provider multiplexing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants