Commit Graph

73 Commits

Author SHA1 Message Date
Acbox Liu 1680316c7f refactor(agent): remove agent gateway instead of twilight sdk (#264)
* 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
2026-03-19 13:31:54 +08:00
MoeMagicMango ebf238a568 fix(text): fix resolve emoji shown in telegram stream mode (#261)
* fix(text): resolve emoji shown in telegram stream mode

* chore(text): removing "reasoing" types in plain msg.

* feat(conversation): add function to check for tool call content in assistant outputs
2026-03-18 15:19:50 +08:00
BBQ 68745133b7 fix(inbound): use bot owner token for agent gateway callbacks (#254)
* feat(access): add guest chat ACL and simplify bot access

Unify bot chat permissions around owner and guest ACL so public access, whitelist, and blacklist share a single model. Remove unused sharing paths, add searchable platform identity controls, and normalize Feishu identities to stable open_id records.

* fix(web): format access control panel

Include the post-commit formatting changes applied to the access control UI so the branch stays clean and the PR reflects the final rendered layout.

* fix(migrations): drop legacy bot tables before bots

Ensure the init down migration removes bot_members and bot_preauth_keys before dropping bots so full rollback succeeds after the ACL refactor.

* feat(acl): add source-aware chat trigger rules

Support channel-, conversation-, and thread-scoped ACL rules while keeping allow_guest, whitelist, and blacklist compatible. Also expose observed conversation candidates and normalize channel identity rules to their own platform.

* fix(lint): resolve golangci-lint errors after rebase

- Remove unused receivers and parameters in fakeRows/Service methods
- Delete unused makeNoRow helper and toParticipantFields function
- Fix gci/gofumpt formatting

* fix(lint): fix gci import formatting in acl types and handler

* fix(acl): tighten observed group and thread selection (#245)

Use inbox plus persisted messages to discover observed group and thread routes, and lock scope fields after selecting a concrete observed target. This keeps Telegram group candidates visible and prevents contradictory private/group scope edits.

* chore: regenerate sqlc swagger and sdk after rebase onto main

* fix(inbound): use bot owner token for agent gateway callbacks

The inbound channel processor issued a JWT for the chatting user's
identity. When the agent called back into container/MCP endpoints
(e.g. /bots/{id}/tools, /bots/{id}/mcp-stdio), AuthorizeBotAccess
rejected non-owner users with HTTP 403 "bot access denied".

Resolve the bot owner via PolicyService and issue the downstream
token under the owner's identity, consistent with schedule,
heartbeat, and email gateways. The chatting user's identity is
still tracked via SourceChannelIdentityID and identity headers.
2026-03-16 23:05:23 +08:00
Ringo.Typowriter ca598bb0a5 fix: align feishu webhook verification flow with sdk behavior (#250) 2026-03-15 19:39:13 +08:00
Acbox 020c74c2bc chore(fix): lint 2026-03-15 00:58:38 +08:00
Acbox ac8a935545 refactor: remove bot type 2026-03-15 00:42:09 +08:00
Menci be3d769013 feat(channel): redact credentials from IM error messages (#240) 2026-03-14 21:27:32 +08:00
BBQ 839e63acda feat(access): add guest chat ACL (#235) 2026-03-14 17:15:41 +08:00
Fodesu b46e494d3a feat(tts): introduce TTS system (#195) 2026-03-13 02:49:52 +08:00
Acbox 9b771acaa8 fix: slash commands in group chats trigger all bots instead of targeted one
- In group chats, only process slash commands when the message is
  directed at this bot (via @mention or reply-to-bot), preventing
  all bots from responding to the same command.
- Use raw_text metadata (before quote/forward context prepending)
  for command detection so quoted content like "/fs" doesn't
  accidentally match a command.
- Fix isTelegramBotMentioned text_mention entity check to verify
  the mentioned bot matches the current bot, not just any bot.
2026-03-12 20:08:55 +08:00
Acbox e9059fddda refactor: defer user message persistence to storeRound for atomic writes
User messages from channel inbound (Telegram, Discord, Feishu, etc.)
were previously persisted before the agent runs. Now they are written
together with assistant/tool messages at the end of a conversation turn
(or on abort), matching the behavior of WebSocket and sync chat paths.
2026-03-12 18:59:33 +08:00
Acbox bb26d18757 fix(command): add missing command handler wiring and lint fixes
Wire SetCommandHandler into ChannelInboundProcessor so slash commands
are intercepted before reaching the LLM. Also apply lint fixes across
command package (strconv.Itoa, comment formatting, unused code removal)
and remove obsolete tool-call-browser.vue component.
2026-03-11 19:05:55 +08:00
Acbox 1da251885d feat(agent): add extensible tag interception system and inline reactions
Refactor the attachment tag extraction into a generic TagResolver/StreamTagExtractor
system that supports multiple custom tags. Implement <reactions> tag allowing the
agent to embed emoji reactions directly in text responses, dispatched as side-effects
through the channel reactor interface.

- Add TagResolver interface and StreamTagExtractor streaming state machine
- Refactor AttachmentsStreamExtractor as backward-compatible wrapper
- Add reactionsResolver and ReactionDeltaAction stream event
- Wire reaction dispatch in Go channel inbound processor
- Fix .gitignore to scope compiled binary patterns to repo root
2026-03-11 17:43:30 +08:00
Acbox 2debfb496c fix(channel): resolve attachment filename and prevent duplicate sends
- Derive attachment name from path basename when not explicitly set in
  parseAttachmentDelta, fixing the "file.bin" fallback on Telegram.
- Infer correct AttachmentType (image/audio/video) from MIME in
  applyAssetToAttachment instead of keeping the generic "file" type.
- Remove outboundAttachments re-attachment to final messages since
  attachments are already delivered during streaming via
  StreamEventAttachment, preventing duplicate file sends on platforms.
2026-03-11 17:00:07 +08:00
Acbox 30653fbdbf fix(agent): reject send tool when targeting the same conversation
Pass replyTarget through the full pipeline (ChatRequest → gateway
identity → agent headers → MCP session) so the send tool can detect
when the destination matches the current conversation and return an
error guiding the agent to reply directly instead.
2026-03-11 16:59:42 +08:00
Acbox a2e5c4f893 feat(channel): add quoted message context injection for Discord and Feishu
Prepend replied-to message text and attachments into the user query so
the LLM can see what is being replied to, matching the existing Telegram
behavior. Also set is_reply_to_bot metadata for Feishu reply-to-bot
detection in group chats.
2026-03-11 16:57:33 +08:00
BBQ 599bfb5ca8 fix(wecom): pass lint and typo checks
Fix WeCom adapter typos and strict Go lint findings (gosec/bodyclose/errcheck/revive) while keeping runtime behavior unchanged.
2026-03-11 02:14:00 +08:00
BBQ bc47655309 fix(wecom): align adapter with channel stream behavior
Migrate the imported WeCom adapter to current channel interfaces and stabilize stream delivery by preventing heartbeat/reply ACK timeout regressions and post-final overwrite updates.
2026-03-11 02:14:00 +08:00
Fodesu a2cb5939d7 fix(discord): rm reason in final message (#220) 2026-03-09 23:34:34 +08:00
Menci 36d50738b5 fix(channel): consistent markdown rendering across all Telegram paths (#210)
- Extract ContainsMarkdown to shared channel package
- Auto-detect markdown in normalizeOutboundMessage and MCP send tool
- Apply markdown-to-HTML conversion during streaming deltas, not just
  on the final message
- Remove resolveTelegramParseMode which incorrectly returned Telegram's
  native "Markdown" mode instead of converting to HTML
- Fix all 14 Telegram send/edit paths for consistent parse mode handling
- Reset parseMode for plain-text error messages to avoid HTML corruption
2026-03-09 13:06:44 +08:00
BBQ 3739def43f fix(text): avoid breaking UTF-8 during truncation
Use rune-aware truncation for user-facing text and log previews so multibyte content is not corrupted in memory context, Telegram messages, or diagnostics.
2026-03-09 12:43:57 +08:00
Menci 09cdb8c87f refactor(telegram): reduce code duplication and improve readability
- Extract parseTelegramTarget helper to consolidate duplicated @username
  vs numeric chat ID parsing from 6+ locations (builder functions,
  sendTelegramTextReturnMessage, sendTelegramAttachmentImpl)
- Extract Config.baseURL() to eliminate duplicate base URL resolution
  between apiEndpoint() and fileEndpoint()
- Refactor stream.go Push method: extract resetStreamState(),
  deliverFinalText(), and per-event-type sub-methods (pushDelta,
  pushFinal, pushToolCallStart, pushAttachment, pushPhaseEnd,
  pushError), reducing the 200-line switch-case to a clean dispatcher
- Use pushFinal's existing getBot() instead of duplicating parseConfig +
  getOrCreateBot
- Replace sort.SliceStable with slices.SortStableFunc + cmp.Compare
- Replace strings.Index + manual slicing with strings.Cut in
  decodeDataURLBytes, ResolveAttachment, and parseTelegramUserInput
2026-03-09 10:03:12 +08:00
Ringo.Typowriter e6a6dbe3f6 feat(channel): add QQ channel support and image message pipeline (#199)
* feat(channel): add qq adapter and outbound delivery

* feat(channel): ingest inbound qq messages

* feat(web): expose qq channel in management ui

* feat(channel): support qq attachment ingestion

* fix(mcp): fail read raw immediately for missing files

* fix(agent): parse inline image data into native image parts

* test(agent): align read_media tool tests with SDK options

* fix(channel): harden qq image delivery and reconnect loop

Avoid data URLs for qq channel images, reset reconnect backoff after healthy sessions, and fall back gracefully for malformed public image URLs.

* fix(channel): restore qq media delivery and target resolution

* fix(qq,mcp,agent): fix message/qq regressions and pass go lint

* fix(qq,agent): validate inline base64 and sync heartbeat seq

* fix(qq): validate remote voice mime for upload checks

* fix(qq): fall back intents and restore adapter wiring

* fix(qq): prevent final text leakage and dedupe persisted inbound query
2026-03-07 17:12:06 +08:00
0x24a ac405c49e2 fix(channel): return success when bind code is re-checked by same identity (#201)
* fix(channel): return success when bind code is re-checked by same identity

* style: run gofmt
2026-03-07 15:07:32 +08:00
BBQ 3feb03aca7 ci: add go lint and race test workflow (#187) 2026-03-05 11:25:33 +08:00
Fodesu 711cee7682 fix(channel): split long streaming messages at manager level (#168) (#182)
Split long AI responses into multiple platform messages during streaming
instead of truncating them. The manager counts accumulated delta runes
and opens a new stream when approaching the platform's TextChunkLimit.
Uses a soft/hard limit strategy that prefers splitting at sentence ends
or line breaks over cutting mid-sentence.

- Add pushDelta with soft (75%) / hard (100%) limit and natural break
  point detection
- Add splitStream, pushFinalAfterSplit, pushFinalWithChunking helpers
- Fix Discord adapter to use RuneCount Message Length
- Add tests for delta splitting, natural breaks, and final handling
2026-03-04 17:57:55 +08:00
Menci a124cde8e2 feat(telegram): add in-reply-to and forwarded-from header and clarify user name (#177) 2026-03-04 17:57:12 +08:00
Ringo.Typowriter 0a2a17ecc8 feat(agent): add readMedia tool for model to view the image (#165)
* feat(agent): add readMedia tool for loading local images into model
context

* feat(channel/inbound): include container attachment refs in inbound
query

* fix(agent): preserve ImagePart literal typing in buildNativeImageParts

* chore: rename tool

---------

Co-authored-by: 晨苒 <16112591+chen-ran@users.noreply.github.com>
2026-03-04 11:24:01 +08:00
Acbox f0517a3a1f fix: inbox indirectly push notidy item into context 2026-03-03 20:38:36 +08:00
Menci b1925bf2be feat(telegram): use sendMessageDraft for streaming in private chats (#174)
* feat(telegram): use sendMessageDraft for streaming in private chats

Use Telegram Bot API 9.3's sendMessageDraft to stream partial messages
with smooth animation in private chats, replacing the sendMessage +
editMessageText approach. Group/channel chats keep the existing
edit-based streaming.

- Add sendTelegramDraft() for the sendMessageDraft API
- Detect private chats via conversation_type metadata in OpenStream
- Use 300ms throttle for drafts (vs 5s for edits)
- Send permanent messages at tool call boundaries and on final event
- Reset buffer atomically in StreamEventFinal to prevent duplicate
  messages when multiple final events fire (one per assistant output)

* test(telegram): improve draft mode test assertions

Add sendTextForTest hook for sendTelegramTextReturnMessage to enable
direct assertion of send calls. Clean up residual unused variables
and replace indirect assertions with explicit mock-based verification.
2026-03-03 16:01:18 +08:00
BBQ 802dfd995f feat(telegram): support custom API base URL for reverse proxy setups (#160)
Allow configuring a custom Telegram Bot API base URL (`apiBaseURL`) per
channel, enabling users behind restricted networks to route requests
through a reverse proxy (e.g. Nginx, Cloudflare Workers).

Both API calls and file downloads respect the configured endpoint.
When omitted, the official https://api.telegram.org is used.

Closes #159
2026-03-02 15:04:20 +08:00
斬風千雪 b82444759a fix(telegram): several fixes of typing action in stream message (#136) 2026-03-01 14:11:32 +08:00
Acbox Liu fe10abf3fc refactor: inbox (#137)
* refactor: inbox

* fix: migrations

* fix: migrations
2026-02-26 20:16:02 +08:00
Fodesu 5aefccd7cc feat: support discord attacchment file, assetService 2026-02-25 16:30:43 +08:00
Fodesu 87a4be7439 fix: discord channel stream ignore Reasoning content 2026-02-25 16:29:57 +08:00
Ran 5e12b5a53f fix: ensure unifying on hardcoded /data mount path 2026-02-24 03:35:27 +08:00
Ringo.Typowriter 29e76322cc feat(feishu): add webhook inbound mode, region support, and callback/attachment enhancements (#107) 2026-02-23 21:57:34 +08:00
Fodesu df12d94171 fix: replay methion 2026-02-23 18:38:07 +08:00
Acbox Liu 17cd077f34 feat: add thinking support (#100)
* feat: add thinking support

* feat: improve thinking block render in web and filter thinking content in channels

* fix: migrate
2026-02-23 14:41:27 +08:00
Ran 5a08b280ab fix: double reply bug 2026-02-23 05:55:20 +08:00
Fodesu 51acb4b546 feat: add replay Message 2026-02-23 05:55:20 +08:00
Fodesu 77ff24c6fd feat(platforms): add discord channel support 2026-02-23 05:55:20 +08:00
Acbox 44ab3792d2 fix(channel): create new message when tool calling 2026-02-23 00:31:59 +08:00
Acbox ac929f9f44 feat: add message id in user header 2026-02-23 00:06:15 +08:00
Acbox Liu c591af14b0 feat: bot inbox (#77)
* feat: bot inbox

* feat: unified header

* fix: missing tool_call usage

* feat: add group name in header
2026-02-22 01:27:24 +08:00
Acbox 7b12fb0b0c fix: send file out of /data/media failed 2026-02-21 00:04:38 +08:00
Acbox cb5d2c5fab fix: send message in group failed 2026-02-20 22:47:02 +08:00
Acbox 1a78ba3f53 feat: add platform metadata in contacts 2026-02-20 22:19:15 +08:00
Acbox 82cc9c357f feat: support attachment send to tool send 2026-02-20 22:04:00 +08:00
Acbox 6b7c3db952 refactor: process user header in go side 2026-02-20 21:40:13 +08:00