These two fields controlled history context window (time-based) and token-based
trimming. They are no longer needed — the resolver now always uses the hardcoded
24-hour default and skips token-based history trimming.
- Add `enable` column (default false) to search_providers and tts_providers tables
- Auto-create default entries for all provider types on startup (disabled by default)
- Add enable/disable Switch toggle in frontend for both search and TTS providers
- Show green status dot in sidebar for enabled providers, sort enabled first
- Filter bot settings dropdowns to only show enabled providers
Backend
- New subject kinds: all / channel_identity / channel_type
- Source scope fields on bot_acl_rules: source_channel,
source_conversation_type, source_conversation_id, source_thread_id
- Fix source_scope_check constraint: resolve source_channel server-side
(channel_type → subject_channel_type; channel_identity → DB lookup)
- Add GET /bots/:id/acl/channel-types/:type/conversations to list
observed conversations by platform type
- ListObservedConversations: include private/DM chats, normalise
conversation_type; COALESCE(name, handle) for display name
- enrichConversationAvatar: persist entry.Name → conversation_name
(keeps Telegram group titles current on every message)
- Unify Priority type to int32 across Go types to match DB INTEGER;
remove all int/int32 casts in service layer
- Fix duplicate nil guard in Evaluate; drop dead SourceScope.Channel field
- Migration 0048_acl_redesign
Frontend
- Drag-and-drop rule priority reordering (SortableJS/useSortable);
fix reorder: compute new order from oldIndex/newIndex directly,
not from the array (which useSortable syncs after onEnd)
- Conversation scope selector: searchable popover backed by observed
conversations (by identity or platform type); collapsible manual-ID fallback
- Display: name as primary label, stable channel·type·id always shown
as subtitle for verification
- bot-terminal: accessibility fix on close-tab button (keyboard events)
- i18n: drag-to-reorder, conversation source, manual IDs (en/zh)
Tests: update fakeChatACL to Evaluate interface; fix SourceScope literals.
SDK/spec regenerated.
* feat(web): add provider oauth management ui
* feat: add OAuth callback support on port 1455
* feat: enhance reasoning effort options and support for OpenAI Codex OAuth
* feat: update twilight-ai dependency to v0.3.4
* refactor: promote openai-codex to first-class client_type, remove auth_type
Replace the previous openai-responses + metadata auth_type=openai-codex-oauth
combo with a dedicated openai-codex client_type. OAuth requirement is now
determined solely by client_type, eliminating the auth_type concept from the
LLM provider domain entirely.
- Add openai-codex to DB CHECK constraint (migration 0047) with data migration
- Add ClientTypeOpenAICodex constant and dedicated SDK/probe branches
- Remove AuthType from SDKModelConfig, ModelCredentials, TriggerConfig, etc.
- Simplify supportsOAuth to check client_type == openai-codex
- Add conf/providers/codex.yaml preset with Codex catalog models
- Frontend: replace auth_type selector with client_type-driven OAuth UI
---------
Co-authored-by: Acbox <acbox0328@gmail.com>
* feat(channel): add Matrix adapter support
* fix(channel): prevent reasoning leaks in Matrix replies
* fix(channel): persist Matrix sync cursors
* fix(channel): improve Matrix markdown rendering
* fix(channel): support Matrix attachments and multimodal history
* fix(channel): expand Matrix reply media context
* fix(handlers): allow media downloads for chat-access bots
* fix(channel): classify Matrix DMs as direct chats
* fix(channel): auto-join Matrix room invites
* fix(channel): resolve Matrix room aliases for outbound send
* fix(web): use Matrix brand icon in channel badges
Replace the generic Matrix hashtag badge with the official brand asset so channel badges feel recognizable and fit the circular mask cleanly.
* fix(channel): add Matrix room whitelist controls
Let Matrix bots decide whether to auto-join invites and restrict inbound activity to allowed rooms or aliases. Expose the new controls in the web settings UI with line-based whitelist input so access rules stay explicit.
* fix(channel): stabilize Matrix multimodal follow-ups and settings
* fix(flow): avoid gosec panic on byte decoding
* fix: fix golangci-lint
* fix(channel): remove Matrix built-in ACL
* fix(channel): preserve Matrix image captions
* fix(channel): validate Matrix homeserver and sync access
Fail Matrix connections early when the homeserver, access token, or /sync capability is misconfigured so bot health checks surface actionable errors.
* fix(channel): preserve optional toggles and relax Matrix startup validation
* fix(channel): tighten Matrix mention fallback parsing
* fix(flow): skip structured assistant tool-call outputs
* fix(flow): resolve merged resolver duplication
Keep the internal agent resolver implementation after merging main so split helper files do not redeclare flow symbols. Restore user message normalization in sanitize and persistence paths to keep flow tests and command packages building.
* fix(flow): remove unused merged resolver helper
Drop the leftover truncate helper and import from the resolver merge fix so golangci-lint passes again without affecting flow behavior.
---------
Co-authored-by: Acbox Liu <acbox0328@gmail.com>
* refactor: replace persistent subagents with ephemeral spawn tool (#subagent)
- Drop subagents table, remove all persistent subagent infrastructure
- Add 'subagent' session type with parent_session_id on bot_sessions
- Rewrite subagent tool as single 'spawn' tool with parallel execution
- Create system_subagent.md prompt, add _subagent.md include for chat
- Limit subagent tools to file, exec, web_search, web_fetch only
- Merge subagent token usage into parent chat session in reporting
- Remove frontend subagent management page, update chat UI for spawn
- Fix UTF-8 truncation in session title, fix query not passed to agent
* refactor: remove history message page
* refactor: move client_type to provider, replace model fields with config JSONB
- Move `client_type` from `models` to `llm_providers` table
- Add `icon` field to `llm_providers`
- Replace `dimensions`, `input_modalities`, `supports_reasoning` on `models`
with a single `config` JSONB column containing `dimensions`,
`compatibilities` (vision, tool-call, image-output, reasoning),
and `context_window`
- Auto-imported models default to vision + tool-call + reasoning
- Update all backend consumers (agent, flow resolver, handlers, memory)
- Regenerate sqlc, swagger, and TypeScript SDK
- Update frontend forms, display, and i18n for new schema
* ui: show provider icon avatar in sidebar and detail header, remove icon input
* feat: add built-in provider registry with YAML definitions and enable toggle
- Add `enable` column to llm_providers (default true, backward-compatible)
- Create internal/registry package to load YAML provider/model definitions
on startup and upsert into database (new providers disabled by default)
- Add conf/providers/ with OpenAI, Anthropic, Google YAML definitions
- Add RegistryConfig to TOML config (providers_dir, default conf/providers)
- Model listing APIs and conversation flow now filter by enabled providers
- Frontend: enable switch in provider form, green status dot in sidebar,
enabled providers sorted to top
* fix: make 0041 migration idempotent for fresh databases
Guard data migration steps with column-existence checks so the
migration succeeds on databases created from the updated init schema.
When input tokens exceed a configurable threshold after a conversation round,
the system asynchronously compacts older messages into a summary. Cascading
compactions reference prior summaries via <prior_context> tags to maintain
conversational continuity without duplicating content.
- Add bot_history_message_compacts table and compact_id on messages
- Add compaction_enabled, compaction_threshold, compaction_model_id to bots
- Implement compaction service (internal/compaction) with LLM summarization
- Integrate into conversation flow: replace compacted messages with summaries
wrapped in <summary> tags during context loading
- Add REST API endpoints (GET/DELETE /bots/:bot_id/compaction/logs)
- Add frontend Compaction tab with settings and log viewer
- Wire compaction service into both dev (cmd/agent) and prod (cmd/memoh) entry points
- Update test mocks to include new GetBotByID columns
- Rewrite SQL queries to join bot_history_messages with bot_sessions,
supporting chat/heartbeat/schedule usage from a single source
- Update Go handler and CLI command to use unified queries
- Fix daily chart stacking: each session type gets its own bar group
- Add total input/output trend lines to the daily token chart
- Fix summary cards reactivity by restricting aggregation to allDays range
- Fix cache chart reactive dependency tracking by inlining data access
- Add i18n keys for schedule, totalInput, totalOutput
- Default time range changed to 7 days
- Regenerate sqlc, swagger, and SDK
* refactor: introduce multi-session chat support (#session)
Replace the single-context-per-bot model with multiple chat sessions.
Database:
- Add bot_sessions table (route_id, channel_type, title, metadata, soft delete)
- Migrate bot_history_messages from (route_id, channel_type) to session_id
- Add active_session_id to bot_channel_routes
- Migration 0036 handles data migration from existing messages
Backend:
- New internal/session service for session CRUD
- Update message service/types to use session_id instead of route_id
- Update conversation flow (resolver, history, store) for session context
- Channel inbound auto-creates/retrieves active session via SessionEnsurer
- New REST endpoints: /bots/:bot_id/sessions (CRUD)
- WebSocket and message handlers accept optional session_id
- Wire session service into FX dependency graph (agent + memoh)
Frontend:
- Refactor chat store: sessions replaces chats, sessionId replaces chatId
- Session-aware message loading, sending, and pagination
- WebSocket sends include session_id
- New session sidebar component with select/delete
- Chat area header shows active session title + new session button
- API layer updated: fetchSessions, createSession, deleteSession
- i18n strings for session management (en + zh)
SDK:
- Regenerated TypeScript SDK and Swagger docs with session endpoints
* fix: update tests for session refactoring (RouteID → SessionID)
Remove references to removed RouteID and Platform fields from
PersistInput/Message in channel_test.go and service_integration_test.go.
* fix: restore accidentally deleted SDK files and guard migration 0032
- Restore packages/sdk/src/container-stream.ts and extra/index.ts that
were accidentally removed during SDK regeneration
- Wrap migration 0032 route_id index creation in a column existence check
to avoid failure on fresh databases where 0001_init.up.sql no longer
has route_id
* fix: guard migration 0036 data steps for fresh databases
Wrap steps 3-7 (which reference route_id/channel_type on
bot_history_messages) in a column existence check so the migration
is safe on fresh databases where 0001_init.up.sql already reflects
the final schema without those columns.
* feat: add title model setting and auto-generate session titles on user input
- Add title_model_id to bots table (migration 0037) and bot settings API
- Implement async title generation triggered at user message time (not after
assistant response) for faster title availability
- Publish session_title_updated events via SSE event hub for real-time
frontend updates without page refresh
- Fix SSE message event parsing: use direct JSON.parse instead of
normalizeStreamEvent which silently dropped non-chat-stream event types
- Add title model selector in bot settings UI with i18n support
* fix: session-scoped message filtering and URL-based chat routing
- Filter realtime SSE messages by session_id to prevent cross-session
message leakage after page refresh
- Add /chat/:sessionId? route with bidirectional URL ↔ store sync
- Visiting /chat shows a clean state with no bot or session pre-selected
- Visiting /chat/:sessionId loads the specific session directly
- Session switches from sidebar automatically update the URL
- Fix stale RouteID field in dedupe test (removed during session refactor)
* fix: skip cross-channel stream events to prevent session leakage
The bot-level web stream pushes events from all channels (Telegram,
Discord, etc.) without session_id context. Previously these were
rendered inline in the current chat view regardless of session.
Now cross-channel events are ignored in handleLocalStreamEvent;
persisted messages arrive via the SSE message events stream with
proper session_id filtering through appendRealtimeMessage.
* feat: show IM avatars and platform badges on session sidebar
- Add sender_avatar_url to route metadata from identity resolution
- Resolve group avatar and handle via directory adapter for group chats
- JOIN bot_channel_routes in ListSessionsByBot to return route metadata
- Display avatar with ChannelBadge on IM session items (group avatar
for groups, sender avatar for private chats)
- Show @groupname or @username as session sub-label
* fix: clean up RunConfig unused fields, fix skill system and copy bug
- Remove unused RunConfig fields: Tools, Channels, CurrentChannel,
ActiveContextTime
- Remove unused SessionContext fields: DisplayName, ConversationType
- Fix EnabledSkillNames copy bug: make([]string, 0, n) + copy copies
zero elements; changed to make([]string, n)
- Fix prepareRunConfig dead code: remove no-op loop over
CurrentPlatform runes; compute supportsImageInput from model's
InputModalities
- Fix EnabledSkills always nil in system prompt: resolve enabled skill
entries from EnabledSkillNames + Skills
- Fix use_skill tool returning empty response: now returns full skill
content (description + instructions) so LLM gets it in the same turn
- Skip use_skill tool registration when no skills are available
- Conditionally render Skills section in system prompt (hidden when
no skills exist)
* feat: add session type field and bind sessions to heartbeat/schedule executions
- Add `type` column to `bot_sessions` (chat | heartbeat | schedule)
- Add `session_id` to `bot_heartbeat_logs` for per-execution session tracking
- Create `schedule_logs` table binding schedule_id + session_id
- Heartbeat and schedule runs now create independent sessions and persist
agent messages via storeRound, enabling full conversation replay
- Add schedule logs API endpoints (list by bot, list by schedule, delete)
- Update Triggerer interfaces to return TriggerResult with status/usage/model
* refactor: modular system prompts per session type (chat/heartbeat/schedule)
Split the monolithic system.md into three type-specific system prompts
with shared fragments via {{include:_xxx}} syntax, so each session type
gets a focused prompt without irrelevant instructions.
* fix: prevent message duplication after task completion
message_created events from Persist() had an empty platform field because
toMessageFromCreate() didn't extract it from the session. This caused
appendRealtimeMessage to fail the platform === 'web' guard, and
hasMessageWithId to fail because local IDs differ from server UUIDs,
resulting in all messages being appended as duplicates.
- Extract platform from metadata in toMessageFromCreate so published events
carry the correct value
- Pass channel_type: 'web' when creating sessions from the web frontend so
List queries return the correct platform via the session JOIN
* fix: use per-message usage from SDK instead of misaligned step-level usages
Previously, token usage was stored via a separate per-step usages array
that didn't align with messages (off-by-one from prepending user message,
step count != message count). This caused:
- User messages incorrectly receiving usage data
- Usage values shifted across messages in multi-step rounds
- Last assistant message getting the accumulated total instead of its own step usage
- InputTokenDetails/OutputTokenDetails lost during manual accumulation
Now each sdk.Message carries its own per-step Usage (set by the SDK in
buildStepMessages), which is extracted in sdkMessagesToModelMessages and
stored directly via ModelMessage.Usage. The storeRound/storeMessages path
no longer needs external usage/usages parameters.
Also fixes the totalUsage accumulation in runStream to include all detail
fields (InputTokenDetails, OutputTokenDetails).
* feat: add /new slash command to create a new active session from IM channels
Users in Telegram/Discord/Feishu can now send /new to start a fresh
conversation, resetting the session context for the current chat thread.
The command resolves the channel route, creates a new session, sets it as
the active session on the route, and replies with a confirmation message.
* feat: distinguish heartbeat and schedule sessions with dedicated icons in sidebar
Heartbeat sessions show a heart-pulse icon (rose), schedule sessions
show a clock icon (amber), and both display a type label beneath the
session title.
* refactor: remove enabledSkills system prompt injection, keep sorted skill listing
use_skill now returns skill content directly as tool output, so there is
no need to inject enabled skill body text into the system prompt. Remove
the entire enabledSkills tracking chain (RunConfig.EnabledSkillNames,
StreamEvent.Skills, GenerateResult.Skills, ChatRequest/Response.Skills,
enableSkill closures in runStream/runGenerate, prepareRunConfig matching).
Keep a lightweight skills listing (name + description only) in the system
prompt so the model knows which skills are available. Sort entries by name
to guarantee deterministic ordering and maximize KV cache reuse.
* refactor: remove inbox system, persist passive messages directly to history
Replace the bot_inbox table and service with direct writes to
bot_history_messages for group conversations where the bot is not
@mentioned. Trigger-path messages continue to be persisted after the
agent responds (unchanged).
- Drop bot_inbox table and max_inbox_items column (migration 0039)
- Delete internal/inbox/, handlers/inbox.go, command/inbox.go,
agent/tools/inbox.go and the MCP message provider
- Add persistPassiveMessage() in channel inbound to write user
messages into the active session immediately
- Rewrite ListObservedConversationsByChannelIdentity to query
bot_history_messages + bot_sessions instead of bot_inbox
- Extract shared send/react logic into internal/messaging/executor.go;
agent/tools/message.go is now a thin SDK adapter
- Clean up all inbox references from agent prompts, flow resolver,
email trigger, settings, commands, DI wiring, and frontend
- Regenerate sqlc, swagger, and SDK
* feat: add list_sessions and search_messages agent tools
Provide agents with the ability to query session metadata and search
message history across all sessions. search_messages supports filtering
by time range, keyword (JSONB-aware ILIKE), session, contact, and role,
with a default 7-day lookback when no start_time is given.
* feat: inject last_heartbeat time and improve heartbeat search guidance
Query the previous heartbeat's started_at timestamp and pass it through
TriggerPayload into the heartbeat prompt template. Update system prompt
and HEARTBEAT.md checklist to guide agents to use search_messages with
start_time=last_heartbeat for efficient cross-session message review.
* fix: pass BridgeProvider to FSClient and store full heartbeat prompt
FSClient was always created with nil provider, causing all container
file reads (IDENTITY.md, SOUL.md, MEMORY.md, HEARTBEAT.md, etc.) to
silently return empty strings. Expose Agent.BridgeProvider() and wire
it into Resolver. Also fix heartbeat trigger to store the full prompt
template as the user message instead of the literal "heartbeat" string.
* feat: add line numbers to container file read output
Move line-number formatting from the bridge gRPC server to the agent
tool layer so that the raw content stored and transmitted via gRPC
remains clean, while the read_file tool output includes numbered lines
for easier reference by the agent.
* chore(deps): update twilight-ai to v0.3.2
* fix: lint, test
* refactor(agent): replace TypeScript agent gateway with in-process Go agent using twilight-ai SDK
- Remove apps/agent (Bun/Elysia gateway), packages/agent (@memoh/agent),
internal/bun runtime manager, and all embedded agent/bun assets
- Add internal/agent package powered by twilight-ai SDK for LLM calls,
tool execution, streaming, sential logic, tag extraction, and prompts
- Integrate ToolGatewayService in-process for both built-in and user MCP
tools, eliminating HTTP round-trips to the old gateway
- Update resolver to convert between sdk.Message and ModelMessage at the
boundary (resolver_messages.go), keeping agent package free of
persistence concerns
- Prepend user message before storeRound since SDK only returns output
messages (assistant + tool)
- Clean up all Docker configs, TOML configs, nginx proxy, Dockerfile.agent,
and Go config structs related to the removed agent gateway
- Update cmd/agent and cmd/memoh entry points with setter-based
ToolGateway injection to avoid FX dependency cycles
* fix(web): move form declaration before computed properties that reference it
The `form` reactive object was declared after computed properties like
`selectedMemoryProvider` and `isSelectedMemoryProviderPersisted` that
reference it, causing a TDZ ReferenceError during setup.
* fix: prevent UTF-8 character corruption in streaming text output
StreamTagExtractor.Push() used byte-level string slicing to hold back
buffer tails for tag detection, which could split multi-byte UTF-8
characters. After json.Marshal replaced invalid bytes with U+FFFD,
the corruption became permanent — causing garbled CJK characters (�)
in agent responses.
Add safeUTF8SplitIndex() to back up split points to valid character
boundaries. Also fix byte-level truncation in command/formatter.go
and command/fs.go to use rune-aware slicing.
* fix: add agent error logging and fix Gemini tool schema validation
- Log agent stream errors in both SSE and WebSocket paths with bot/model context
- Fix send tool `attachments` parameter: empty `items` schema rejected by
Google Gemini API (INVALID_ARGUMENT), now specifies `{"type": "string"}`
- Upgrade twilight-ai to d898f0b (includes raw body in API error messages)
* chore(ci): remove agent gateway from Docker build and release pipelines
Agent gateway has been replaced by in-process Go agent; remove the
obsolete Docker image matrix entry, Bun/UPX CI steps, and agent-binary
build logic from the release script.
* fix: preserve attachment filename, metadata, and container path through persistence
- Add `name` column to `bot_history_message_assets` (migration 0034) to
persist original filenames across page refreshes.
- Add `metadata` JSONB column (migration 0035) to store source_path,
source_url, and other context alongside each asset.
- Update SQL queries, sqlc-generated code, and all Go types (MessageAsset,
AssetRef, OutboundAssetRef, FileAttachment) to carry name and metadata
through the full lifecycle.
- Extract filenames from path/URL in AttachmentsResolver before clearing
raw paths; enrich streaming event metadata with name, source_path, and
source_url in both the WebSocket and channel inbound ingestion paths.
- Implement `LinkAssets` on message service and `LinkOutboundAssets` on
flow resolver so WebSocket-streamed bot attachments are persisted to the
correct assistant message after streaming completes.
- Frontend: update MessageAsset type with metadata field, pass metadata
through to attachment items, and reorder attachment-block.vue template
so container files (identified by metadata.source_path) open in the
sidebar file manager instead of triggering a download.
* refactor(agent): decouple built-in tools from MCP, load via ToolProvider interface
Migrate all 13 built-in tool providers from internal/mcp/providers/ to
internal/agent/tools/ using the twilight-ai sdk.Tool structure. The agent
now loads tools through a ToolProvider interface instead of the MCP
ToolGatewayService, which is simplified to only manage external federation
sources. This enables selective tool loading and removes the coupling
between business tools and the MCP protocol layer.
* refactor(flow): split monolithic resolver.go into focused modules
Break the 1959-line resolver.go into 12 files organized by concern:
- resolver.go: core orchestration (Resolver struct, resolve, Chat, prepareRunConfig)
- resolver_stream.go: streaming (StreamChat, StreamChatWS, tryStoreStream)
- resolver_trigger.go: schedule/heartbeat triggers
- resolver_attachments.go: attachment routing, inlining, encoding
- resolver_history.go: message loading, deduplication, token trimming
- resolver_store.go: persistence (storeRound, storeMessages, asset linking)
- resolver_memory.go: memory provider integration
- resolver_model_selection.go: model selection and candidate matching
- resolver_identity.go: display name and channel identity resolution
- resolver_settings.go: bot settings, loop detection, inbox
- user_header.go: YAML front-matter formatting
- resolver_util.go: shared utilities (sanitize, normalize, dedup, UUID)
* fix(agent): enable Anthropic extended thinking by passing ReasoningConfig to provider
Anthropic's thinking requires WithThinking() at provider creation time,
unlike OpenAI which uses per-request ReasoningEffort. The config was
never wired through, so Claude models could not trigger thinking.
* refactor(agent): extract prompts into embedded markdown templates
Move inline prompt strings from prompt.go into separate .md files under
internal/agent/prompts/, using {{key}} placeholders and a simple render
engine. Remove obsolete SystemPromptParams fields (Language,
MaxContextLoadTime, Channels, CurrentChannel) and their call-site usage.
* fix: lint
* feat(container): add explicit data workflows and snapshot rollback
Make container upgrades and recreation data-safe by adding explicit preserve, export, import, restore, and rollback flows across the backend, SDK, and web UI.
* fix(container): resolve go lint issues
Fix formatting and lint violations introduced by the container data workflow changes so the Go CI lint job passes cleanly.
Replace the host bind-mount + containerd exec approach with a per-bot
in-container gRPC server (ContainerService, port 9090). All file I/O,
exec, and MCP stdio sessions now go through gRPC instead of running
shell commands or reading host-mounted directories.
Architecture changes:
- cmd/mcp: rewritten as a gRPC server (ContainerService) with full
file and exec API (ReadFile, WriteFile, ListDir, ReadRaw, WriteRaw,
Exec, Stat, Mkdir, Rename, DeleteFile)
- internal/mcp/mcpcontainer: protobuf definitions and generated stubs
- internal/mcp/mcpclient: gRPC client wrapper with connection pool
(Pool) and Provider interface for dependency injection
- mcp.Manager: add per-bot IP cache, gRPC connection pool, and
SetContainerIP/MCPClient methods; remove DataDir/Exec helpers
- containerd.Service: remove ExecTask/ExecTaskStreaming; network setup
now returns NetworkResult{IP} for pool routing
- internal/fs/service.go: deleted (replaced by mcpclient)
- handlers/fs.go: deleted; MCP stdio session logic moved to mcp_stdio.go
- container provider Executor: all tools (read/write/list/edit/exec)
now call gRPC client instead of running shell via exec
- storefs, containerfs, media, skills, memory: all I/O ported to
mcpclient.Provider
Database:
- migration 0022: drop host_path column from containers table
One-time data migration:
- migrateBindMountData: on first Start() after upgrade, copies old
bind-mount data into the container via gRPC, then renames src dir
to prevent re-migration; runs in background goroutine
Bug fixes:
- mcp_stdio: callRaw now returns full JSON-RPC envelope
{"jsonrpc","id","result"|"error"} matching protocol spec;
explicit "initialize" call now advances session init state to
prevent duplicate handshake on next non-initialize call
- mcpclient Pool: properly evict stale gRPC connection after snapshot
replace (container process recreated); use SetContainerIP instead
of direct map write so IP changes always evict pool entry
- migrateBindMountData: walkErr on directories now counted as failure
so partially-walked trees don't get incorrectly marked as migrated
- cmd/mcp/Dockerfile: removed dead file (docker/Dockerfile.mcp is the
canonical production build)
Tests:
- provider_test.go: restored with bufconn in-process gRPC mock
(fakeContainerService + staticProvider), 14 cases covering all 5
tools plus edge cases
- mcp_session_test.go: new, covers JSON-RPC envelope, init state
machine, pending cleanup on cancel/close, readLoop cancel
- storefs/service_test.go: restored (pure function roundtrip tests)
* feat: add email service with multi-adapter support
Implement a full-stack email service with global provider management,
per-bot bindings with granular read/write permissions, outbox audit
storage, and MCP tool integration for direct mailbox access.
Backend:
- Email providers: CRUD with dynamic config schema (generic SMTP/IMAP, Mailgun)
- Generic adapter: go-mail (SMTP) + go-imap/v2 (IMAP IDLE real-time push via
UnilateralDataHandler + UID-based tracking + periodic check fallback)
- Mailgun adapter: mailgun-go/v5 with dual inbound mode (webhook + poll)
- Bot email bindings: per-bot provider binding with independent r/w permissions
- Outbox: outbound email audit log with status tracking
- Trigger: inbound emails push notification to bot_inbox (from/subject only,
LLM reads full content on demand via MCP tools)
- MailboxReader interface: on-demand IMAP queries for listing/reading emails
- MCP tools: email_accounts, email_send, email_list (paginated mailbox),
email_read (by UID) — all with multi-binding and provider_id selection
- Webhook: /email/mailgun/webhook/:config_id (JWT-skipped, signature-verified)
- DB migration: 0019_add_email (email_providers, bot_email_bindings, email_outbox)
Frontend:
- Email Providers page: /email-providers with MasterDetailSidebarLayout
- Dynamic config form rendered from ordered provider meta schema with i18n keys
- Bot detail: Email tab with bindings management + outbox audit table
- Sidebar navigation entry
- Full i18n support (en + zh)
- Auto-generated SDK from Swagger
Closes#17
* feat(email): trigger bot conversation immediately on inbound email
Instead of only storing an inbox item and waiting for the next chat,
the email trigger now proactively invokes the conversation resolver
so the bot processes new emails right away — aligned with the
schedule/heartbeat trigger pattern.
* fix: lint
---------
Co-authored-by: Acbox <acbox0328@gmail.com>
Add incremental migration for existing databases to create the
search_providers table and bots.search_provider_id column introduced
in the search provider feature.
The ::text cast on search_providers.id prevented sqlc from inferring
nullability via LEFT JOIN, generating a non-nullable string field that
crashes when the bot has no search provider bound.
Propagate conversation type (direct/group/thread) from channel adapters
all the way to the agent prompt. Store conversation_type on bot_channel_routes
so the bot knows whether a message originates from a p2p chat, group, or thread.
Schema changes are folded into the 0001 init migration (destructive update).
- Accept standard mcpServers item format (command/args/env/url/headers)
- Auto-infer connection type: command -> stdio, url -> http/sse
- Add PUT /bots/:bot_id/mcp/import for batch import from mcpServers dict
- Add GET /bots/:bot_id/mcp/export for standard format export
- Add UpsertMCPConnectionByName SQL for import upsert by name
- Preserve is_active state on import upsert