- Add fetchRemoteBranchFromGitHub() to query https://api.github.com/repos/MythEclipse/bete for actual default_branch
- Add resolveBranch() tiered resolver: git CLI → GitHub API → env → error
- Async post-startup branch resolution upgrades version from emergency fallback to real remote branch
- normalizeBranchName() for consistent version key formatting
- Emergency fallback 'v1' logged as ERROR with repo context — not a silent dummy
- Removed static env-first approach; env is now tier 3 override, not tier 1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Create new migration 0001_add_model_version_to_cache.sql to add model_version column to text_analysis_cache table (default 'v1')
- Add composite index on source + model_version for efficient cache lookups
- Improve git branch detection in textCacheStore.ts:
* Check CACHE_MODEL_VERSION env var first (required in Docker)
* Suppress stderr with stdio pipes to eliminate spurious warnings
* Log ERROR (not warning) when git unavailable AND no env var set
* Only fall back to 'v1' as last resort, preventing silent dummy usage
- Remove unused imports (logModelVersionChange, logCacheInvalidation) and previousVersion variable
- Fixes repeated 'column model_version does not exist' errors on bot startup
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- aiAnalyzer.ts: removed unused logAnalysisSummary and logFalsePositiveDetected
- llmModerationClient.ts: removed unused logVisionAnalysis, logVisionError, logRetryAttempt
These functions are not called in the codebase.
Fix#1 (CRITICAL): Wire message_analyzed through Redis EventBroadcaster
- DG aiAnalyzer.ts: Add broadcastAnalysisCompleted() helper that publishes
to both in-memory WS broadcaster AND Redis EventBroadcaster
- DG bootstrap.ts: Pass eventBroadcaster to startPendingAIAnalysisWorker()
- Fixes broken real-time chain so analysis results appear instantly
Fix#2: Collapsible AI Analysis with Rich Formatting
- MessageCard: AI analysis now collapsible with color-coded severity border
(red/yellow/blue), summary line showing categories + confidence + severity
- Default collapsed for clean, expanded for warn/flagged
Fix#3: Toast Notifications for Flagged Content
- Wrap app in ToastProvider; ModerationAlertListener component listens for
moderation_alert custom events and shows toast with emoji + details
Fix#4: Discord-style Message Grouping
- MessageFeed: Group consecutive messages from same user within 5 min
- MessageCard: Compact variant hides avatar, reduces padding for non-first
Fix#5: Moderation Action Buttons in UI
- BE: POST /api/messages/:id/moderate endpoint + publishCommand() for Redis
- FE: Delete/Warn buttons on flagged/warned cards with confirmation dialog
Fix#6: Search Results Include Full Data
- BE analysis.service.ts: SELECT all 26 message columns instead of just 11
- Search results now render with full MessageCard including images/analysis
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sebelumnya: 'Pesan hanya berisi attachment tanpa teks yang melanggar'
Sekarang: harus deskriptif berdasarkan tipe konten:
- Text only: '[user] membahas tentang <topik>. <konteks>.'
- Image only: 'Gambar berupa <jenis>. Terlihat <isi>.'
- Text+Image: '[user] mengirim <gambar> sambil membahas <topik>.'
Tambahkan contoh baik vs buruk di OUTPUT_INSTRUCTIONS sebagai format wajib.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sebelumnya aturan 'percaya teks terlebih dahulu' membuat model abaikan
deskripsi gambar saat teks kosong. Semua image-only message di-clean.
Fix:
- SYSTEM_RULES: pisah Mode 1 (teks+gambar) dan Mode 2 (hanya gambar)
- Mode 2: deskripsi gambar jadi bukti utama, WAJIB dibaca
- Gambar terminal/chat/editor kode/casual → clean
- Gambar dengan elemen judi NYATA (chip, roulette, odds) → flag
- 2 contoh few-shot baru: terminal clean, situs judi flag
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: vision model diminta untuk 'flag' dan 'menilai' gambar,
sehingga screenshot terminal/chat biasa diklaim sebagai 'situs perjudian'.
Fix:
- vision prompt: HANYA deskripsi objektif (objek, teks, layout, jenis gambar)
- larang tegas kata 'gambling', 'judi', 'pelanggaran', 'harus dihapus'
- tambah buildGeneralImageVisionPrompt di discord-gateway stickerPrompt
- MEDIA_INSTRUCTIONS: tegaskan batch LLM adalah hakim, vision hanya saksi mata
- deskripsi netral (terminal, chat, editor kode) tidak boleh jadi dasar flag
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- discord-gateway: add redis command handlers for guilds:list, guilds:text-channels, voice:channels
- backend: replace postgres synthetic names with redis commands to gateway (with fallback)
- frontend: remove guild/channel dropdowns from messages and analytics tabs
- frontend: auto-load all channels from monitor guild via guildId query param
- frontend: show guild name in messages/analytics headers instead of selector
- live tab: keeps guild/channel selectors with real discord names
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- Check if origin mismatch → rm and re-clone fresh
- Public repo — no auth needed for clone
- docker-compose.yml from repo source of truth
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GITHUB_TOKEN can't auth git over HTTPS on VPS, so revert to inline
compose generation. nginx.conf already baked into proxy image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace build: blocks with image: from ghcr.io
- Simplify deploy: git clone/pull on VPS, then docker compose pull/up
- Remove SCP step entirely — nginx.conf baked into proxy image
- Rename containers to imphenbot-* to match existing VPS setup
- Fix backend port to 3000 for production
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add proxy to build matrix
- Replace heredoc docker-compose generation with appleboy/scp-action
- Fix nginx upstream backend port to 3000 (matches prod WEBSERVER_PORT)
- Copy docker-compose.yml and nginx.conf from repo via SCP staging
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add nginx service with /api, /ws, and / location routing
- Move traefik labels to proxy service only (port 80)
- Remove traefik labels from backend and frontend services
- Backend routes to :3001 via nginx upstream
- WebSocket upgrade enabled for /ws path
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend (real implementations, no more stubs):
- Redis pub/sub bridge: subscribes to discord-gateway events (message/attachment/voice) and broadcasts to WS clients
- Redis command channel: backend publishes voice/media commands, discord-gateway executes and replies
- Voice service: connectVoice/disconnectVoice/getVoiceStatus via Redis commands with graceful fallback
- Media service: queue/skip/stop/volume via Redis commands, reads status from Redis cache
- Messages repository: ALL 7 methods now use real PostgreSQL queries (findMany, findById, findByChannel, create, update, delete, getAttachmentsByChannel)
- Analytics: period returns {start,end} epoch millis, overview includes hourly/topics/top_users, worst_flags as string[]
- Health check: actually queries SELECT 1 against database
- VoiceStatus type fixed: {connected, activeGuildId, activeChannelId, activeChannelName}
- Guild type: includes icon: string | null
- asyncHandler: accepts Promise<unknown> instead of Promise<void>
Discord Gateway:
- CommandHandler: subscribes to 'backend:command' Redis channel, executes voice/media commands, publishes replies
- Publishes voice:status and media:status to Redis for backend caching
- Shutdown handler updated to close command handler
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace stub analytics.repository.ts with real PostgreSQL queries using pg.Pool
- Add /api/guilds, /api/config, /api/auth/login, /api/ui-state (GET/POST)
- Add /api/review, /api/recordings, /api/analysis/search
- Add /api/messages/:id/reanalyze endpoint
- Add /api/analytics/heatmap and /api/analytics/topics
- Implement media routes (stub responses, backend has no Discord voice client)
- Add WebSocket server at /ws with heartbeat and broadcast functions
- Fix analytics route paths to match frontend contract (dual paths for backward compat)
- Export getPool() from database module for raw SQL queries
- Register all new routers in app.ts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vite embeds env vars at build time, not runtime. The frontend code uses
VITE_BE_API_URL/VITE_BE_WS_URL but the workflow was setting wrong names
(VITE_API_URL/VITE_WS_URL). Now Dockerfile accepts build args and
workflow passes production URLs during docker build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vite preview blocks unknown hosts by default. Added allowedHosts for
imphnen.asepharyana.my.id and set port/host in preview config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Vite preview defaults to port 4173 but traefik routes to port 3000.
Added --port 3000 to the preview script in package.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of failing on CREATE TABLE when tables already exist, check
information_schema for all 10 schema tables. If all exist, skip
migration gracefully. This handles deployments where DB was created
by a previous deployment with different migration files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
'up --force-recreate' after 'down' causes 'No such container' race.
After down, up creates fresh containers automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After removing root package deps, discord-gateway service was missing:
- axios (used in indonesianTextNormalizer.ts)
- ws (used in broadcaster.ts)
- @types/pg (needed for TypeScript pg declarations)
- @types/ws (needed for ws types)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Container crashed with 'Can't find meta/_journal.json file' because
Drizzle ORM migration files were never generated. Generated migrations
for all 10 schema tables and updated Dockerfile to copy drizzle/ to
/app/drizzle/ where the migrator expects them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root package.json no longer a workspace member — no src/, no 38 shared
dependencies. Root is now workspace config with dev tooling only (biome,
drizzle-kit, tsx, typescript).
This means Dockerfiles only need:
- pnpm-workspace.yaml + pnpm-lock.yaml + package.json (workspace config)
- The specific service being built
- Only vendor/ packages that service actually depends on (discord-gateway → discord.js-selfbot-v13)
Before: each Dockerfile copied src/, vendor/*, packages/*, all services/
After: each Dockerfile copies only its service + needed vendor deps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The VPS has orphan containers from previous deployments that conflict with
new compose up. Running compose down --remove-orphans first cleans them up.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pnpm install --frozen-lockfile at workspace root requires ALL workspace
members to be present. Previously vendor/ packages and root src/ were
missing, causing TS2307 'Cannot find module' errors during build.
- Copy vendor/ (discord.js-selfbot-v13, discord-video-stream, better-sqlite3, drizzle-orm)
- Copy root src/ and vite.backend.config.ts
- Copy all services/ to each Dockerfile
- Copy frontend/ and public/ assets to frontend Dockerfile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root scripts point to old paths (src/, frontend/) that don't exist in the
services/ monorepo layout. Use pnpm --filter to invoke each service's
own build script (tsc) instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pnpm v10+ requires the built-in `node:sqlite` module which is only available
in Node 22+. All services now use node:22-alpine.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`github.repository_owner` returns 'MythEclipse' (mixed case) which GHCR rejects.
Hardcode lowercase 'mytheclipse' to match GHCR requirements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract services into services/{frontend,backend,discord-gateway}
- Create packages/shared/ for shared logger, errors, utils, types
- Setup Modular MVC pattern in backend (controller→service→repository)
- Setup event-driven architecture in discord-gateway with Redis pub/sub
- Move Docker files to infra/docker/ with per-service Dockerfiles
- Update docker-compose.yml to use Traefik-only routing (no port exposes)
- Update GitHub Actions deploy workflow for multi-service matrix build
- Fix all import paths and resolve type errors across all services
- All 3 services pass tsc --noEmit clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Count only flagged messages in the active users Flag column
- Coerce PostgreSQL count results to numbers before calculating violation_score
- Prevent string concatenation in flagged*3 + warned scoring
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
API provider returns 400 error for these params. Removed from all LLM calls
in llmModerationClient.ts and indonesianTextNormalizer.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove LOCAL_BADWORDS array (25 hardcoded words) and FALSE_POSITIVE_WHITELISTS
- Remove detectLocalBadwords function — all detection now goes through API pipeline
- Fix DEFERRAL_ANALYSIS_PATTERN: remove overly broad patterns (admin perlu, bisa berpotensi, maaf/sorry, saya tidak yakin)
- Expand DEFERRAL_EXCEPTION_PATTERN to catch more decisive-deferral variations
- Update tests to reflect API-only detection (local fallback removed)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add XML delimiters to prevent prompt injection (R1)
- Use JSON Schema response format instead of json_object (R2)
- Add concurrency limiter via p-limit (R3)
- Add timeout per media analysis call (R4)
- Resize images with sharp before vision API (R5)
- Split text batches when exceeding batch size limit (R6)
- Add few-shot examples to system prompt (R7)
- Modularize system prompt builder (R8)
- Enhance deferral detection regex with exception patterns (R9)
- Sanitize error messages to avoid leaking internals (R10)
New files: concurrencyLimiter.ts, imageResizer.ts, moderationPrompt.ts
Updated: llmModerationClient.ts, config.ts, package.json, tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Extract custom emoji metadata with Discord CDN URLs
- Download and send emoji images to vision model for moderation analysis
- Render custom emoji as inline images in dashboard instead of raw <:name:id> text
- Add emoji vision cache with deterministic keying by emoji ID
- Add custom emoji vision prompt for context-aware moderation
The NixOS base image lacks sed and coreutils in PATH. Native Node.js
post-install scripts (node-pre-gyp, prebuild-install) require sed and
other core utilities. Installing gnused and coreutils-full provides the
necessary binaries for native module compilation.
Also switched back to single nix profile install call (instead of
sequential per-RUN) since all packages now reference the same pinned
nixpkgs commit, eliminating version drift conflicts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The node-pre-gyp and prebuild-install wrapper scripts require sed, which
is not in PATH in the NixOS base container. Install coreutils first to
provide sed, dirname, basename, and other core utilities needed by
native module post-install scripts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pnpm's isolated store prevents native post-install scripts from finding
their dependency binaries (node-pre-gyp, prebuild-install). This causes
@discordjs/opus and @lng2004/node-datachannel to fail during pnpm install.
Enabling shamefully-hoist flattens node_modules so native scripts can
resolve their dependencies correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cacert 3.123 was installed first, but nodejs_22 pulls cacert 3.117 as a
transitive dependency, causing a file conflict on ca-bundle.crt.
Since all packages that need SSL certificates already pull cacert as a
dependency, installing it explicitly is redundant and causes conflicts.
Removing the explicit cacert install lets each package use its resolved
transitive cacert version.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues were causing Docker build failures:
1. Invalid nixpkgs commit hash (404 on GitHub archive)
2. Installing packages simultaneously caused version conflicts
(cacert 3.123 vs 3.117, git vs git-minimal)
Fix:
- Pin to valid nixos-unstable HEAD commit
- Install each package with sequential 'nix profile add' calls
so each package resolves dependencies before the next installs
- Remove explicit git (git-minimal pulled by yt-dlp is sufficient)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The nixos-unstable channel changes rapidly and causes file conflicts
between packages (cacert 3.123 vs 3.117, git vs git-minimal) because
different packages are resolved from different nixpkgs snapshots during
the same profile install.
Pinning to a specific commit ensures all packages are resolved from the
same nixpkgs snapshot, eliminating version drift and file conflicts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
yt-dlp already pulls git-minimal as a transitive dependency. Adding git
explicitly causes a file conflict on packinfo.pl. Removing git from the
install list resolves this since git-minimal provides all git functionality
needed for CI builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Updated .env.example to include Groq API configuration.
- Enhanced config schema in config.ts to support Groq API keys and settings.
- Implemented Groq moderation API call in indonesianTextNormalizer.ts as a fallback for badword detection.
- Removed the Indonesian slang normalization function and related tests to streamline moderation logic.
- Updated tests to reflect changes in moderation strategy, focusing on emoji normalization and badword detection.
fix(redis): enhance Redis connection handling with fallback to in-memory storage
chore(sql): create missing messages and attachments tables with necessary constraints and indexes
- Updated test files to use a separate test database configuration.
- Introduced a new helper module for managing test database operations.
- Added a setup file to configure the environment for tests.
- Created new database migration scripts to optimize message indexing.
- Added a sample environment file for test database configuration.
- Removed SQLite support from the configuration and database initialization logic.
- Updated database migration scripts to focus solely on PostgreSQL migrations.
- Simplified logging messages to reflect PostgreSQL usage.
- Adjusted database schema definitions to remove SQLite-specific types and structures.
- Modified tests to ensure compatibility with PostgreSQL, including changes to table creation and data types.
- Cleaned up unused imports and code related to SQLite.
- Removed "recharts" from package.json and its associated entries from pnpm-lock.yaml.
- Cleaned up unnecessary dependencies and peer dependencies related to recharts and other unused packages.
- Added new API endpoints for daily trend data and activity heatmap in analyticsRoutes.ts.
- Created new frontend components: ActivityChart, ControlBar, Heatmap, SummaryCards, TopicList, TrendChart, UserTable, and ViolatorTable for displaying analytics data.
- Implemented loading and empty states in the new components.
- Enhanced the existing moderation tests with remote fallback handling for Indonesian text normalization.
- Analytics panel now refreshes immediately when messages are created, updated, deleted, or analyzed
- WebSocket events (message_created, message_updated, message_deleted, message_analyzed) trigger custom event 'analytics_refresh'
- Analytics hook listens to 'analytics_refresh' event for instant updates
- Auto-refresh interval lowered from 60s to 5s as fallback
- Two-tier refresh: real-time push + 5s polling safety net
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove Review tab completely (was redundant with Messages flagged view)
- UI state now client-side only (localStorage) — no server API calls for tab/channel/guild selection
- Fixes dashboard crash when server is down — now loads fully client-side
- Live panel still uses server API for voice/media operations (only what needs it)
- Analytics hourly chart labels now show Jakarta time (WIB/UTC+7) instead of UTC
- Analytics formatTimeAgo uses Jakarta time reference
- Reduced tabs to 3: Live, Messages, Analytics
- Removed unused uiState API imports and server-side state fetching
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New LivePanel component combines voice bridge, media player, screen share, and recordings
- Single page layout: voice controls → audio visualizer + active speakers → now playing → music/screen/recordings tabs
- Reduced tabs from 6 to 4: Live, Messages, Analytics, Review
- Sidebar updated with new tab structure
- Default tab changed from 'voice' to 'live'
- Cleaner compact layout with icon buttons and inline controls
- Recordings sub-panel with user avatars, status badges, and download buttons
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MessageCard:
- Add sticker image preview (renders actual sticker from URL, not just text name)
- Show attachment thumbnails inline (up to 4 with overflow counter)
- Remove dead columns (ai_policy_version, ai_evidence) from UI
- Add severity color coding (critical=red, high=orange, medium=yellow, low=blue)
- Add relative time display ('2h ago' instead of full datetime)
- Better hover effects and visual hierarchy
MessagesPanel:
- Add stats bar showing total/clean/warn/flagged/error/pending/deleted/edited counts
- Add AI status filter buttons (all, clean, warn, flagged, error, pending)
- Improve search UX with inline search icon and clear button
- Show filtered count in tab labels
ImageGrid:
- Include sticker images (was only attachments + embeds before)
- Add kind badge overlay (sticker/attachment/embed)
- Show user avatar next to image caption
- Better sticker rendering (object-contain with padding)
Header:
- Add shield icon per tab
- Improve tab titles and add descriptive subtitles
API client:
- Remove dead fields from MessageRecord (ai_moderation_raw, ai_policy_version, ai_evidence)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop 3 columns that were written but never read:
- ai_moderation_raw: raw LLM JSON response (~KB per message, never consumed)
- ai_policy_version: hardcoded 'default-2026-05-30', never used for decisions
- ai_evidence: JSON evidence array, never read after write
Changes:
- schema.ts: remove columns from both Postgres and SQLite table definitions
- messageStore.ts: remove from AIAnalysisUpdate interface and SET clauses
- aiAnalyzer.ts: remove from individual fallback update calls
- aiAnalysisWorker.ts: remove raw write, add missing fields (categories, severity, etc.)
- types.ts: remove from MessageRecord interface
- analysisRoutes.ts: remove from reset analysis call
- New migration: src/database/migrations/001_drop_unused_ai_columns.sql
- Migration applied to live DB: 27 columns → 24 columns
Kept ai_error (useful for future debugging, currently 0 non-null)
Kept metadata (1.9MB total, used for AI sticker/embed evidence analysis)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>