- Use Nowledge Mem Spaces for per-bot memory isolation (space name: memoh:{botID})
- Auto-ensure space on first use with sync.Map cache
- Add platform/conversation context header to stored text: (Telegram 群组「开发讨论」)
- Replace [我] with bot's actual display name [小助手]
- Thread ConversationType, ConversationName, Platform, BotName through AfterChatRequest
- Add resolveBotDisplayName to resolver for DB lookup
Add a new memory provider that delegates to a local Nowledge Mem instance
for memory storage, retrieval, and knowledge graph building.
Key design decisions:
- User messages tagged as [DisplayName], bot messages as [我]
- Per-message display name parsed from YAML front-matter headers
- Let Nowledge Mem handle entity extraction and graph building
- 6-way hybrid search (semantic + full-text + entity + community + label + graph)
New files:
- internal/memory/adapters/nowledgemem/client.go (REST API client)
- internal/memory/adapters/nowledgemem/nowledgemem.go (Provider impl)
- docs/nowledge-mem.md (design document with research and decisions)
Modified: types.go, service.go, serve.go (provider registration),
frontend (add-memory-provider.vue, types.gen.ts, i18n locales)
Add native Playwright WebSocket sessions alongside the existing curated
browser tools. Agents can now create remote sessions that expose a full
Playwright API over WebSocket, enabling advanced use cases like HttpOnly
cookie injection, storage state management, and route interception.
Key changes:
- Per-bot isolated browser processes (launchServer via Node child process)
- New session module with create/close/status/heartbeat endpoints
- New browser_remote_session agent tool (Go)
- Storage state export/import on existing browser contexts
- Bot ID plumbing through context creation for process isolation
- Inflight deduplication to prevent duplicate browser launches
- Session janitor for automatic expiry cleanup
stream-monaco sets inline height/max-height/overflow styles (default
MAX_HEIGHT=500px) that override CSS classes. Use MutationObserver to
continuously clear those inline styles so h-full takes effect.
Also disable autoScrollInitial/autoScrollOnUpdate and explicitly set
cursor position to line 1 on mount, preventing the editor from
scrolling to the bottom of the file on open.
The WebSocket handler rejected messages with empty text even when
attachments were present, while the HTTP POST endpoint correctly
used Message.IsEmpty(). Move the empty-check after attachment
parsing so only truly empty messages are rejected.
* refactor(agent): replace XML tag extraction with tool-based send/react/speak
Remove the <attachments>, <reactions>, and <speech> XML tag extraction
system from the agent streaming pipeline. Instead, the send/react/speak
tools now handle both same-conversation and cross-conversation delivery:
- send: omit target to deliver attachments in the current conversation;
specify target for cross-channel messaging
- react: omit target to react in the current conversation
- speak: omit target to speak in the current conversation
Backend changes:
- Add StreamEmitter callback to tools.SessionContext so tools can push
attachment/reaction/speech events directly into the agent stream
- Wire emitter in agent.go for both streaming and non-streaming paths
- Remove StreamTagExtractor, DefaultTagResolvers, emitTagEvents, and
delete internal/agent/tags.go entirely
- Remove StripAgentTags calls from assistant_output.go
- Add IsSameConversation detection in messaging executor; same-conv
sends pass raw paths through the emitter for downstream ingestion
- Auto-resolve relative paths (e.g. "IDENTITY.md" -> "/data/IDENTITY.md")
- Add Metadata propagation through the full attachment chain
(tools.Attachment -> agent.FileAttachment -> parseAttachmentDelta)
- Update system_chat.md and _contacts.md prompts
Frontend changes (apps/web):
- Hide send/react/speak tool_call blocks when result indicates
delivered to current conversation
- Defer attachment_delta blocks to end of message (flush on stream
completion) for consistent positioning with DB-loaded history
* fix(agent): speak tool emits synthesized audio directly as voice attachment
Instead of emitting speech_delta (which requires downstream re-synthesis),
the speak tool now emits the already-synthesized audio as an attachment_delta
with voice type. This avoids double TTS synthesis and eliminates dependency
on ttsService being configured on the inbound processor.
Also fixes speak on WebUI where ReplyTarget is empty (same fix as send).
StripAgentTags was only applied to the merged content string but not to
individual ContentParts. On channels that don't support RichText (e.g.
Telegram), buildChannelMessage joins part texts directly, causing raw
<attachments>/<reactions>/<speech> blocks to appear in the final message.
Pass :typewriter="false" on the About page and bind
:typewriter="message.streaming" in chat so that history messages
render instantly while streaming output retains the fade-in effect.
Add /settings/about page with version display, GitHub release update
checker with markdown-rendered release notes, and external links
(GitHub, Docs, Feedback). Remove version section from Profile page.
Bots can now be configured with an image generation model (must have
image-output compatibility). When set, the agent exposes a generate_image
tool that calls the model via Twilight AI SDK, saves the result to the
bot container filesystem, and returns the file path.
- Add image_model_id column to bots table (migration 0053)
- Update settings SQL queries, service, and types
- New ImageGenProvider tool provider in internal/agent/tools/
- Wire provider in both cmd/agent and cmd/memoh entry points
- Add image model selector to frontend bot settings with compat filtering
- Regenerate swagger, SDK types, and sqlc code
Pulls model list from OpenRouter API and auto-generates
conf/providers/openrouter.yaml with inferred compatibilities
(vision, tool-call, image-output, reasoning) from API metadata.
Models that lack the "tool-call" compatibility flag now run without
tools, preventing provider errors when the model does not support
function calling.
Introduce three inbound message handling modes for channel adapters:
- inject (default, /btw): when a route has an active agent stream,
inject the new user message into the running stream via the SDK's
PrepareStep hook between tool rounds. The message is interleaved at
the correct position in the persisted round.
- parallel (/now): start a new agent stream immediately, running
concurrently with any existing stream (preserves current behavior).
- queue (/next): enqueue the message and process it after the current
stream completes.
Key components:
- RouteDispatcher: per-route state management with inject channel,
task queue, and active-stream tracking.
- PrepareStep integration: drains inject channel between tool rounds,
records insertion position via InjectedRecorder for correct
persistence ordering.
- interleaveInjectedMessages: inserts injected user messages at their
actual injection position within the persisted message round.
- Parallel mode isolation: /now streams do not interact with the
dispatcher, preventing them from clearing another stream's active
state.
Rename session info endpoint from /sessions/:id/info to /sessions/:id/status
and update frontend tab label accordingly. Add /status slash command that
displays current session metrics (message count, context usage, cache hit
rate, used skills) as formatted text in any channel.
Add GET /bots/:bot_id/sessions/:session_id/info API endpoint that returns
per-session message count, latest input token usage with model context window,
aggregated KV cache hit rate, and skills invoked via use_skill tool calls.
Frontend Info tab in the right sidebar now displays this data in a compact
key-value layout with a context usage progress bar and clickable skill links.
* feat: add Supermarket integration (MCP & Skill marketplace) (#309)
* feat: add Supermarket integration (MCP & Skill marketplace)
Backend:
- Add [supermarket] config section with base_url (default: supermarket.memoh.ai)
- Add SupermarketHandler with proxy endpoints for MCPs, Skills, and Tags
- Add install endpoints: POST /bots/:id/supermarket/install-mcp (creates MCP
connection with env vars) and install-skill (downloads tar.gz, extracts to
container via gRPC)
- Register handler in FX wiring, generate Swagger docs and TypeScript SDK
Frontend:
- Add /settings/supermarket route with Store icon in sidebar
- Create supermarket page with search, tag filtering, MCP and Skill sections
- Add MCP/Skill card components with tag badges and install buttons
- Add install dialogs: MCP (bot selector + env var form), Skill (bot selector)
- Add i18n entries for en.json and zh.json
* fix: improve supermarket install UX
- Create BotSelect component with avatar + name using UI Select
- Replace NativeSelect in install dialogs and usage page with BotSelect
- Change MCP install flow: navigate to bot detail MCP tab with pre-filled
draft instead of direct install, letting users review before saving
- Move Supermarket sidebar entry between Browser and Usage
* web: remove supermarket page top tag selector bar
Drop the horizontal tag chips and getSupermarketTags fetch; keep
search and tag filter via card tag clicks with clearable badge.
* web: add homepage link to supermarket MCP and Skill cards
Show an external-link icon next to the card title when homepage is
available, opening in a new tab on click.
* refactor: move skills directory from .skills to skills and enrich prompt
- Change skills storage path from `/data/.skills` to `/data/skills`
- Add usage instructions and directory location to the Skills section
in the system prompt
* feat(web): add Activity Bar and right sidebar panel to chat page
Replace the old file manager panel with a multi-tab right sidebar system:
- Activity Bar with Terminal, Files, and Info tabs
- Resizable right panel with tab switching
- Extract shared Terminal component from bot-terminal.vue
- Add bottom preview layout mode to FileManager
- Delete session button with confirmation dialog
- Fix FileManager scroll in flex column layout (min-h-0)
* feat(web): add session-type-aware UI for chat interface
- Make IM/heartbeat/schedule/subagent sessions read-only (hide input box)
- Render heartbeat user messages as info blocks with trigger metadata and
link to heartbeat logs
- Render schedule user messages as info blocks with task metadata and
link to schedule settings
- Render subagent user messages as full-width markdown boxes
- Add clickable spawn task results to navigate to subagent sessions
* feat(web): add sidebar collapse functionality
- Add SidebarRail for edge drag-to-collapse interaction in both sidebars
- Center align icons when collapsed in bot list and settings sidebar
- Hide text labels and dropdown menus in collapsed state
- Keep create bot button in header
* fix(web): ensure secondary sidebar remains visible after page refresh
When the primary sidebar was collapsed, refreshing the page would cause
the secondary sidebar (e.g., providers list) to disappear.
The MasterDetailSidebarLayout had its own SidebarProvider but didn't
set default-open, causing it to read the same sidebar_state cookie
and follow the primary sidebar's collapsed state.
Fix by explicitly setting :default-open="true" on the secondary
SidebarProvider to ensure it always stays expanded.
---------
Co-authored-by: Acbox Liu <acbox0328@gmail.com>
Reorganize Supermarket into a tabbed UI with Skills as the first tab
and MCP as the second tab. Add a GitHub submit button in the header
linking to https://github.com/memohai/supermarket.
- Make IM/heartbeat/schedule/subagent sessions read-only (hide input box)
- Render heartbeat user messages as info blocks with trigger metadata and
link to heartbeat logs
- Render schedule user messages as info blocks with task metadata and
link to schedule settings
- Render subagent user messages as full-width markdown boxes
- Add clickable spawn task results to navigate to subagent sessions
Replace the old file manager panel with a multi-tab right sidebar system:
- Activity Bar with Terminal, Files, and Info tabs
- Resizable right panel with tab switching
- Extract shared Terminal component from bot-terminal.vue
- Add bottom preview layout mode to FileManager
- Delete session button with confirmation dialog
- Fix FileManager scroll in flex column layout (min-h-0)
- Change skills storage path from `/data/.skills` to `/data/skills`
- Add usage instructions and directory location to the Skills section
in the system prompt
* feat: add Supermarket integration (MCP & Skill marketplace)
Backend:
- Add [supermarket] config section with base_url (default: supermarket.memoh.ai)
- Add SupermarketHandler with proxy endpoints for MCPs, Skills, and Tags
- Add install endpoints: POST /bots/:id/supermarket/install-mcp (creates MCP
connection with env vars) and install-skill (downloads tar.gz, extracts to
container via gRPC)
- Register handler in FX wiring, generate Swagger docs and TypeScript SDK
Frontend:
- Add /settings/supermarket route with Store icon in sidebar
- Create supermarket page with search, tag filtering, MCP and Skill sections
- Add MCP/Skill card components with tag badges and install buttons
- Add install dialogs: MCP (bot selector + env var form), Skill (bot selector)
- Add i18n entries for en.json and zh.json
* fix: improve supermarket install UX
- Create BotSelect component with avatar + name using UI Select
- Replace NativeSelect in install dialogs and usage page with BotSelect
- Change MCP install flow: navigate to bot detail MCP tab with pre-filled
draft instead of direct install, letting users review before saving
- Move Supermarket sidebar entry between Browser and Usage
* web: remove supermarket page top tag selector bar
Drop the horizontal tag chips and getSupermarketTags fetch; keep
search and tag filter via card tag clicks with clearable badge.
* web: add homepage link to supermarket MCP and Skill cards
Show an external-link icon next to the card title when homepage is
available, opening in a new tab on click.
The fallback provider introduced in 5aeb2fd3 wrapped containerfs but did
not implement storage.ContainerFileOpener, causing IngestContainerFile to
fail with "provider does not support container file reading". This broke
outbound file attachments on all IM channels (Telegram, Discord, etc.)
because container paths like /data/xxx.xlsx were passed as-is to the
platform API instead of being ingested into the media store first.
- Add localfs storage provider as fallback when containerfs is unreachable
- Wrap media service with fallback provider in both entry points
- Fix gallery lightbox src matching by comparing pathnames only
- Add SupportsToolCall to RunConfig; only inject tools into SDK when set
- Update twilight-ai to 497ad09 which adds SSE scanner 10MB buffer
(fixes token-too-long on large image payloads) and parses the images
array from OpenAI-compatible chat completions into StreamFilePart
Large directories like node_modules/.venv could return thousands of entries,
wasting tokens and causing timeouts. Add offset/limit pagination to ListDir
RPC and collapse heavy subdirectories (>50 items) into summaries in recursive
mode. Collapsing runs at the bridge layer before pagination so the page window
reflects the collapsed view.
When messages like [assistant(tool_calls), tool, assistant(text)] are
merged into a single ChatMessage by convertMessagesToChats, the merged
message uses the first assistant's ID. The SSE /messages/events backlog
(which uses >= for the since filter) could re-deliver the final assistant
message, and hasMessageWithId failed to recognize it because the merged
ID was different. This caused the last assistant message to appear twice
after a page refresh.
Track all original server message IDs in a Set (knownServerMessageIds)
so that hasMessageWithId can catch messages whose IDs were absorbed
during merging.
Use stream-monaco's useMonaco composable instead of raw monaco-editor
to share the same theme registration system with markstream-vue.
Pass isDark prop to MarkdownRender to follow app dark mode.
Three independent bugs fixed:
1. IM channels were sending raw <attachments>/<reactions>/<speech> tag blocks
alongside file attachments. Now ExtractAssistantOutputs strips these tags
before building the outbound channel message.
2. WebUI rendered these tags as markdown after page refresh. Now
extractMessageText strips agent tags for non-user messages.
3. WebUI lost attachment blocks after refresh because convertMessagesToChats
did not call buildAssetBlocks when merging assistant messages into a
pending tool-call group. Also made LinkOutboundAssets session-aware so
assets are linked to the correct assistant message.
* Enhance Matrix bot setup documentation
Added instructions for obtaining an access token using the Matrix Client Login API and clarified the importance of keeping it secret. Updated details on bot capabilities and linked to the roadmap for future features.
* Add warning about access token security
Added important note about keeping the access token secret.
WiX MSI only supports numeric-only versions (X.Y.Z). When the tag
contains a pre-release identifier (e.g. 0.6.0-beta.5), override the
MSI version via `bundle.windows.wix.version` with the stripped numeric
portion while keeping the full semver for other bundle formats.
The SDK persists tool results as ToolResultPart with a "result" JSON field,
but extractAllToolResults was only reading "output", causing exec tool
stdout/stderr/exit_code to be lost when loading chat history.
Replace the static Globe icon with dynamic type-specific icons
(MessageSquare, HeartPulse, Clock, GitBranch) that match the
session item icons, updating both icon and color on filter change.
The child-selector border styles (*:border-b *:border-x etc.) on
CollapsibleContent caused unexpected white outlines around thinking
blocks and tool-call detail panels in chat messages.