Commit Graph

127 Commits

Author SHA1 Message Date
huanghuoguoguo a64fb976c3 refactor(tools): unify tool-detail normalization in ToolManager
Drop the PluginToolLoader.get_tool() override that returned a raw
ComponentManifest, so every loader's get_tool() now returns a uniform
resource_tool.LLMTool (PluginToolLoader.get_tools() already did this
conversion). This removes the only source of tool-shape heterogeneity.

- ToolManager.get_tool_schema(): drop the ComponentManifest-vs-LLMTool branch
- ToolManager.get_tool_detail(): new host-level shape {name, description,
  human_desc, parameters}
- handler.py GET_TOOL_DETAIL: call tool_mgr.get_tool_detail(); delete the
  handler-local _build_tool_detail + _i18n_to_dict/_i18n_to_text adapters and
  the litellm TODO
- ToolLookupResult is now just LLMTool

The dropped label/spec fields were not consumed by any runner (local-agent
build_llm_tool and external harnesses use only name/description/parameters).
2026-07-19 22:41:30 +08:00
huanghuoguoguo ae0faa425a refactor(plugin): split agent-runner action handlers out of handler.py
Extract the AgentRunner Protocol v1 host-side surface from the giant
RuntimeConnectionHandler.__init__ into sibling modules using a registration-
function pattern (behavior-preserving; @h.action == @self.action):

- agent_run_support.py: shared constants + authorization/scope/projection helpers
- agent_pull_actions.py: register(h) for history/event pull APIs
- agent_runner_actions.py: register(h) for run/runtime/stats/claim lifecycle
- agent_state_actions.py: register(h) for steering/state APIs

__init__ now calls the three register(self) functions. handler.py keeps the
pre-existing plugin/llm/vector/knowledge handlers, get_prompt/call_tool/
get_tool_detail (coupled to retained helpers), shared helpers, and outbound
methods; it re-imports _validate_agent_run_session so external imports keep
working. handler.py: 4066 -> 1871 lines.

test_state_api_auth.py: repoint get_session_registry patch targets to
agent_run_support (the lookup moved modules). 385 agent unit tests pass; ruff clean.
2026-07-19 22:41:30 +08:00
huanghuoguoguo 5f65f962c5 feat(skill): unify skill activation as authorized tools
Expose skill tools (activate/register_skill/native exec) like native tools
instead of gating them behind the skill_authoring capability:
- toolmgr.get_all_tools drops include_skill_authoring; SkillToolLoader
  self-gates on sandbox + skill_mgr
- preproc drops the include_skill_authoring branch; pipeline-bound skills
  and the skills resource gate on skill_mgr presence

Persist activated skills into host.activated_skills conversation state so
they survive across runs (host writes at activate; last-write-wins); drop
the dead restore_activated_skills helper.

Prefill ToolResource.parameters host-side (tool_mgr.get_tool_schema) so
runners build LLM tools without per-tool get_tool_detail round-trips.

Align agent-runner-pluginization design docs to the all-tool model.
2026-07-19 22:41:30 +08:00
huanghuoguoguo cb9930c9e4 feat(agent-runner): add plugin runner host integration 2026-07-19 22:41:30 +08:00
Junyan Qin 5ee0d53060 test(api): adapt bot service webhook tests to manifest-driven detection 2026-07-19 22:40:16 +08:00
wangcham 1167d9e951 feat(kook): add eba adapter 2026-07-19 22:40:16 +08:00
WangCham 7b2698f476 feat(platform): add slack eba adapter 2026-07-19 22:40:16 +08:00
WangCham adfd88e477 feat(platform): add qqofficial eba adapter 2026-07-19 22:40:16 +08:00
WangCham 67e2d19459 feat(officialaccount): add eba adapter 2026-07-19 22:40:16 +08:00
WangCham 377d96ea4f feat(platform): add wecom customer service eba adapter 2026-07-19 22:40:16 +08:00
WangCham 1caabd4193 feat(platform): add wecom eba adapters 2026-07-19 22:40:16 +08:00
Junyan Qin 1919733daf feat(platform): add lark eba adapter 2026-07-19 22:39:15 +08:00
Junyan Qin 9161ed6ae2 feat(platform): add dingtalk eba adapter 2026-07-19 22:39:15 +08:00
Junyan Qin f105343efe feat: complete eba adapter acceptance path 2026-07-19 22:38:10 +08:00
Junyan Qin 3f7a97e541 feat: migrate aiocqhttp adapter to eba 2026-07-19 22:38:10 +08:00
Junyan Qin e04c8b65a5 feat: add discord eba adapter 2026-07-19 22:38:10 +08:00
Junyan Qin 13365c7d5b test: cover telegram upload file capability 2026-07-19 22:38:10 +08:00
Junyan Qin 7a12f93019 test: expand telegram eba api coverage 2026-07-19 22:38:10 +08:00
Junyan Qin 6c7d3cd204 feat: route telegram eba events to plugins 2026-07-19 22:38:10 +08:00
Junyan Qin 11d721d255 fix: handle telegram eba non-message updates 2026-07-19 22:38:10 +08:00
Hyu 48952206db fix(lark): keep connection lookup off event loop
Move the Lark SDK synchronous connection URL lookup off the main asyncio loop, serialize reconnects, and cover the incident with a non-blocking regression test.
2026-07-15 19:53:48 +08:00
DongXiaoming c53b800267 fix(provider): strip think tags for MiniMax-M3 and other OpenAI-compatible models (#2330)
* fix(provider): strip think tags for MiniMax-M3 and other OpenAI-compatible models

MiniMax-M3 (and other OpenAI-compatible providers) emit chain-of-thought
reasoning directly in the content field wrapped in  tags, instead
of using a separate reasoning_content field or the legacy CRETIRE_REASONING
markers. The existing remove_think logic only handled CRETIRE_* tags, so
think blocks leaked into user-visible output even when remove_think was enabled.

- Add _ThinkStripState: a stateful filter that correctly handles  tags
  split across streaming chunk boundaries.
- Add _strip_think classmethod with regex patterns for both  and
  CRETIRE_* tags.
- Wire think_state into invoke_llm_stream so deltas are filtered before
  reaching the accumulator.
- Add remove_think safety net in _StreamAccumulator so the final message
  from tool-call rounds also gets stripped.
- Fix remove_think resolution to use defensive nested .get() so
  pipelines missing output.misc don't raise AttributeError.

* fix(litellmchat): add missing _CLOSE_TAG class attribute on _ThinkStripState

* fix(provider): handle think stripping across LiteLLM paths

---------

Co-authored-by: WangCham <651122857@qq.com>
2026-07-15 10:54:41 +08:00
RockChinQ bd35b793e1 fix(embed): isolate browser chat sessions (#2335)
* fix(embed): isolate browser chat sessions

* fix(embed): harden reset and reconnect recovery
2026-07-13 15:08:45 +08:00
Stevenqin 2c3e52c16c fix(aiocqhttp): correct listener lifecycle handling (#2336)
* fix(aiocqhttp): correct listener lifecycle handling

* docs(aiocqhttp): add listener lifecycle evidence
2026-07-13 13:50:08 +08:00
Dongchuan Fu 0755beebcd feat: add supports for dify hitl (#2226)
* feat: Implement workflow form handling for paused workflows

- Added module-level storage for pending forms to manage state across sessions.
- Introduced functions to set, get, and clear pending forms with expiration handling.
- Enhanced DifyServiceAPIRunner to support resuming paused workflows via form actions.
- Implemented logic to yield human input requests and display appropriate messages.
- Updated workflow submission methods to handle paused states and resume actions.
- Ensured proper merging of pending form actions with user inputs for seamless interaction.

* feat: Add '_routed_by_rule' variable to form action in Lark and Telegram adapters

* feat: Enhance Lark and Telegram adapters with new form handling for paused workflows

* feat: Enhance TelegramAdapter to handle form action buttons and message threading

* feat: Improve TelegramAdapter message handling with enhanced error management and draft message support

* feat: Add the function for formatting human input text to support adapters without rich UI.

* feat(dingtalk): implement human input card support and card action handling

- Add a new module `card_callback.py` to handle card action button clicks from DingTalk.
- Introduce `DingTalkCardActionHandler` to process card action callbacks and extract parameters.
- Update `DingTalkAdapter` to manage card state and handle form input through a single card template.
- Add configuration for `human_input_card_template_id` in `dingtalk.yaml` to specify the template for human input.
- Create a new card template `dingtalk_human_input_card.json` for rendering human input prompts and buttons.

* feat(dingtalk): enhance human input card functionality with streaming support and active turn management

- Updated the DingTalk card template to enable streaming mode and multi-update configuration.
- Removed the obsolete delete_card method from DingTalkClient to streamline card management.
- Enhanced DingTalkAdapter to manage active turn cards and accumulated streaming text, ensuring a seamless user experience during human input prompts.
- Modified the create_message_card method to utilize existing active cards for resumed workflows, preventing duplication.
- Improved the _paint_form_on_card method to update existing cards with human input prompts and buttons dynamically.
- Updated the dingtalk_human_input_card.json template to reflect the new streaming capabilities and configuration options.

* feat(wecom): implement Dify human input pause handling with button interaction support

* feat(qqofficial): implement Dify human input button interaction handling and markdown keyboard support

* feat(qqofficial): implement one-click QR binding and enhance localization support

* feat(discord): implement Discord form view with button interactions for Dify actions

* fix(telegram): correct group chat type check and handle oversized callback data for Telegram actions
fix(difysvapi): ensure safe access to remove-think configuration in pipeline settings

* feat(dify): add support for chatflow app type and enhance human input handling

* feat(telegram): add action title feedback for user selections in Telegram messages

* feat(lark): enhance LarkAdapter to store form content for resume notices

* feat(dingtalk): update display formatting for card content with HTML line breaks

* feat(dingtalk): add feedback functionality to cards with 👍/👎 buttons

- Implemented feedback state management for cards, allowing users to provide feedback via thumbs up/down buttons.
- Enhanced card rendering to include feedback buttons when appropriate.
- Registered feedback listeners to handle feedback events and update card states accordingly.
- Updated the card template to support dynamic button rendering for feedback actions.
- Improved error handling and logging for feedback actions and card updates.

* fix: add Avatar component to dingtalk_human_input_card.json for enhanced user interaction

* feat(wecom): add optional source block to interactive template cards for enhanced branding

* feat(wecom): add functions for template card action extraction and update, enhance button interaction handling

* feat(qqofficial): synchronize passive-reply counter with inbound message sequence

* feat(qqofficial): add method to identify invisible form placeholder chunks in messages

* feat(dingtalk): add download link for human input card template and enhance dynamic form configuration

* feat(telegram): enhance message handling with group stream deletion and form placeholder detection

* Add unit tests for DingTalk, Lark, WeComBot, and Dify service API runners

- Implement tests for DingTalk adapter helper functions including form content cleaning, input extraction, and completed input lines.
- Create unit tests for Lark adapter helper functions focusing on input extraction and completed input lines.
- Add tests for WeComBot template card functionalities, including event extraction and payload building for human input.
- Enhance Dify service API runner tests to cover human input forms, including input collection, action handling, and form snapshot extraction.

* feat: Enhance Telegram and QQ Official adapters with select field handling and form action processing

- Added support for select fields in Telegram adapter, including option extraction and callback handling.
- Implemented form action processing for Telegram callbacks, improving user interaction feedback.
- Introduced new helper functions for building keyboards and resolving select button actions in QQ Official adapter.
- Enhanced DifyServiceAPIRunner to handle cumulative streaming responses and improve error handling during workflow resumes.
- Added unit tests for new functionalities in Telegram and QQ Official adapters, ensuring robust behavior for select fields and form actions.

* feat(lark): add functions for current input definitions and visible form content handling
feat(qqofficial): update fallback text handling for non-streaming scenarios
feat(difysvapi): enhance form content processing for interactive fields and actions
test: add unit tests for Lark and QQ Official adapter functionalities

* Add tests for DingTalk adapter content processing and markdown formatting

- Updated the assertion in `test_dingtalk_completed_input_lines_include_text_and_select_values` to remove unnecessary markdown formatting.
- Added new tests to verify that `_dingtalk_clean_form_content` maintains the order of prompts and completed values in various scenarios.
- Introduced `test_dingtalk_card_markdown_preserves_internal_line_breaks` to ensure internal line breaks are correctly converted to HTML line breaks.

* feat: Refactor input handling and feedback messages across multiple adapters

* feat: Update the human-computer interaction template cards, and optimize the prompt information and content display.

* feat: Refactor pending form handling to isolate by bot and pipeline

* feat: Enhance error handling and caching for Dify and WeCom interactions

* feat: Enhance select input handling and validation in Dify API runner and Telegram adapter

* feat: Add missing completed input lines handling in DingTalk adapter

* feat: Add pipeline_uuid handling across multiple adapters and update related tests
2026-07-13 00:42:46 +08:00
RockChinQ 3ddebd26ae fix: skip valkey-glide on Windows (#2333)
Skip the unsupported valkey-glide dependency on Windows while preserving automatic installation on supported platforms. Keep missing-client runtime and test paths safe, and update the Valkey integration documentation.
2026-07-13 00:22:12 +08:00
Guanchao Wang 2a3f7f8059 fix(mcp): harden remote SSE fallback (#2324)
* fix(mcp): harden remote SSE fallback

* fix(mcp): preserve transport errors during fallback

---------

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>
2026-07-11 22:08:37 +08:00
Daria Korenieva 0c405901d2 feat(vector): add Valkey Search vector database backend (#2276)
* feat(vector): add Valkey Search vector database backend

Add a new opt-in VectorDatabase backend backed by the Valkey Search module
(valkey/valkey-bundle), accessed via the official valkey-glide client's native
ft command namespace.

- Implements the full VectorDatabase ABC: VECTOR, FULL_TEXT and HYBRID search,
  all 8 metadata filter operators, and pagination with exact totals.
- HYBRID uses filter-then-KNN (no app-side weighted fusion); vector_weight is
  accepted for interface parity but NOT honored (docstring + one-time warning +
  docs caveat).
- Lazy connect so a down Valkey never blocks boot; mandatory
  client_name=langbot_vector_client; optional auth + TLS (never logged).
- Registered via a single elif branch in vector/mgr.py; disabled by default
  (vdb.use stays chroma) for toC compatibility.
- Adds valkey-glide>=2.4.1,<3.0.0; no protobuf/pydantic downgrade; no ORM
  change so no Alembic migration.
- Unit tests (fast lane, no server) + slow-gated integration tests
  (TEST_VALKEY_URL, valkey/valkey-bundle:9.1.0) + integration doc.

* fix(vector): paginate Valkey Search deletes and guard delete_by_filter

Address self-review follow-ups for the Valkey Search VDB backend:

- _search_keys now paginates through the full result set in batches of
  _DELETE_SCAN_BATCH instead of capping at a single hard-coded 10000-key
  page, so delete_by_file_id / delete_by_filter fully remove files and
  filters that match more than one page of chunks (no orphaned vectors).
- Add unit regression tests for the delete_by_filter mass-deletion guard:
  a filter referencing only non-indexed fields must skip and return 0
  (never fall back to match-all), and a supported filter still deletes
  matching keys.

* refactor(vector): harden Valkey Search backend and add adversarial tests

Address the self-review NICE-TO-HAVE items for the Valkey Search VDB backend:
- Guard the username-without-password credential edge (skip auth + warn
  instead of building ServerCredentials(password=None, ...), which glide
  rejects).
- Add an async close() teardown that closes the glide client and resets
  cached state (re-init is safe via the existing None guard).
- Hoist 'import json' to module top (was imported inside three methods).
- Document the FT TAG literal-brace limitation in _escape_tag (fails closed,
  never widens).

Tests:
- Add an adversarial-input integration test proving crafted file_id /
  query_text cannot break out of or widen a query (fail-closed on braces).
- Add unit tests for close() and the credential-build guard.

Signed-off-by: Daria Korenieva <daric2612@gmail.com>

* fix(vector): make Valkey Search file_id TAG support arbitrary characters

Valkey Search's FT TAG query parser cannot handle '{', '}' or '*' even when
backslash-escaped, so a file_id containing those characters previously
produced an unparseable query (it failed closed / raised). Percent-encode
exactly those FT-unsafe characters (plus '%' for reversibility) in the
file_id TAG value, applied identically at write time and query time, so an
arbitrary file_id round-trips. For normal UUID/hash ids this is a no-op and
the stored value is unchanged; the original file_id is always preserved
verbatim in metadata_json.

Strengthen the adversarial integration test to assert a brace/star-bearing
file_id matches and deletes exactly its own row (no widening, no raise), and
add unit tests for _encode_file_id and the filter encoding.

Signed-off-by: Daria Korenieva <daric2612@gmail.com>

* refactor(vector): address Valkey Search review feedback

- Add configurable request_timeout (default 5000ms; glide default 250ms is
  too low for KNN); expose in config.yaml + docs table
- Validate embedding dimension consistency in add_embeddings (fail fast on
  mixed lengths to avoid silent KNN corruption)
- Use ft.info (O(1)) instead of ft.list (O(n)) for index existence checks in
  the query hot path; also closes the check-then-create TOCTOU window
- Pipeline HSETs via a non-atomic Batch instead of N sequential awaits
- Extract shared _iter_reply_docs to deduplicate reply parsing between
  _reply_to_chroma and list_by_filter
- Parenthesize multi-condition pre-filters before the => KNN clause
- Fail closed when a username is configured without a password
- Catch only RequestError on ft.dropindex (let connection/auth errors surface)
- Bound the delete_collection SCAN loop with a safety cap
- Add VectorDatabase.close() (no-op default) + VectorDBManager.shutdown()
- Simplify _MATCH_ALL literal; normalize typing to builtin generics

* fix(vector/valkey_search): address round-2 review feedback

- Serialize lazy client creation with an asyncio.Lock (double-checked) so
  concurrent first-use callers don't construct and leak duplicate clients.
- Make the filter operator chain exhaustive: raise on an unhandled op rather
  than silently dropping the condition (which could widen delete_by_filter).
- Cast numeric range (///) values to float, failing closed on
  non-numeric input and pre-empting a future NUMERIC-field injection surface.

* refactor(vector): remove shutdown/close from base ABC per maintainer feedback Per maintainer request, interface changes to VectorDatabase ABC and VectorDBManager should be in a separate PR with implementation across all backends. The ValkeySearchVectorDatabase.close() method remains but does not override an ABC method.

Signed-off-by: Daria Korenieva <daric2612@gmail.com>

* docs(test): list valkey_search in vdb coverage exclusions Add valkey_search to the documented vector/vdbs/ coverage-exclusion list, matching the existing chroma/milvus/pgvector/qdrant/seekdb entries. These adapters require a live database instance and are covered by env-gated integration tests instead of unit tests.

Signed-off-by: Daria Korenieva <daric2612@gmail.com>

---------

Signed-off-by: Daria Korenieva <daric2612@gmail.com>
2026-07-08 06:59:16 +08:00
Hyu 209706b0b9 feat(mcp): add box.default_memory_mb config for nsjail memory limit (#2318)
Operators can now set a global default memory limit for all stdio MCP
servers in config.yaml or via environment variable:

  config.yaml:
    box:
      default_memory_mb: 2048  # default: 1536

  env:
    BOX__DEFAULT_MEMORY_MB=2048

The default is raised from 1024 to 1536 MB — a safer floor for
Node.js V8 + WASM (undici llhttp) under nsjail cgroup limits.
Individual MCP servers can still override via their own box.memory_mb.

Previously the fallback was hardcoded to 1024 MB, causing OOM kills
(return_code=137) on node/npx MCP servers that need more RAM.

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-07-04 14:24:06 +08:00
Hyu bf3c96026b fix(mcp): fix cold-start connection drop by using lexically-intact owner exit stack (#2307)
The previous _TransferredStack approach broke anyio lexical context:
websocket_client/ClientSession use anyio task groups whose cancel scope is
bound to the frame that entered them. Deferring their aclose via a transferred
exit stack left the underlying memory streams closed once initialize() returned,
so the very next request (refresh -> list_tools) failed with Connection closed.

New design:
- Attach on the owner exit stack (same task as the serve loop, lexically intact)
- A cold-starting process makes initialize() fail; signal _ColdStartRetry up to
  the outer retry loop, which reuses the live process without consuming retry budget
- _lifecycle_loop_with_retry handles _ColdStartRetry like _TransportReconnect:
  preserves process, no fatal budget, backs off 2s and retries
- Two new unit tests: cold-start raises _ColdStartRetry (not fatal) when process
  is alive; raises fatal error when process has actually exited

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-07-03 14:58:40 +08:00
Hyu ed9343c686 fix(mcp): retry the stdio handshake during slow (npx) cold starts (#2306)
A node/npx stdio MCP server (e.g. firecrawl-mcp via npx -y) failed on first
connect with Connection closed / Failed after 4 attempts, even though the
process was fine. An npx cold start downloads+installs the package before the
server can answer the MCP handshake (measured ~27s for a simple official
server; longer for heavier ones). The old code attached the WS and called
session.initialize() the instant the process was started, so the handshake ran
before the process could answer and failed; the outer lifecycle retry then
rebuilt the process, churning it in a loop.

Verified decisively: attaching + initialize() against a mid-cold-start process
times out on attempt 1 (process still installing) but SUCCEEDS at t+0.6s on
attempt 2 once the process is ready. So the fix is to retry the handshake in
place, not to rebuild the process.

Changes (mcp_stdio.initialize):
- Start the managed process ONCE, then loop attach WS -> ClientSession ->
  initialize() within the startup_timeout budget, tearing down each failed
  attempt cleanly, until the handshake succeeds or the budget elapses. A
  successful transport/session is transferred into the owner exit stack via a
  small _TransferredStack adapter.
- Bound each attempt with asyncio.wait_for(initialize, _HANDSHAKE_ATTEMPT_TIMEOUT_SEC=10s)
  so a cold-starting process fails fast and retries instead of hanging until
  the transport drops.
- Stop retrying ONLY when the process has DEFINITIVELY exited: new
  _managed_process_has_exited() (checks EXITED status) replaces the previous
  not-_managed_process_is_running() test, which false-negatived on a
  just-spawned process that had not yet reported RUNNING and made the loop bail
  to the outer rebuild path (relay then rejected the early re-attach with HTTP 400).

Adds a unit test that fails the first two handshakes with the process alive and
asserts the loop retries to success while starting the process exactly once.

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-07-03 14:16:57 +08:00
Hyu 9a8cdde86c refactor(mcp): make MCP test reuse the shared Box session (near-instant tests) (#2304)
* refactor(mcp): make MCP test reuse the shared Box session instead of a per-test session

Testing an MCP server (config-page "test" button) previously spun up a fresh
isolated mcp-test-<uuid> Box session every time: cold-start the container, run
the dependency bootstrap, probe, then tear the whole session down. That is slow
(tens of seconds) and, on an already-hosted server, wasteful — the server is
already running in the shared session.

Change the test to reuse the shared session / live process:

- _build_box_session_id: transient tests now use mcp-shared, the same Box
  session as live servers, so a test reuses the running container (and, for an
  existing server, its live managed process) instead of a cold per-test session.
- cleanup_session: a transient test no longer deletes the whole session (which
  under the shared model would kill every other MCP server in the container). It
  stops only its own process_id, exactly like a live server. Isolation is now at
  the process level (distinct process_id per server/test), not the session level.
- test_mcp_server (persisted server): reuse the live connection with a real
  list_tools refresh/probe; only fall back to a full start() when there is no
  live connection to probe or the refresh fails, instead of an ERROR->start()
  rebuild.

Trade-off: a failing test now shares the container with live servers rather than
a throwaway session. Accepted deliberately in favour of near-instant tests;
process-level isolation keeps a test from stopping another server's process.

* chore(deps): pin langbot-plugin 0.4.9 for the nsjail RLIMIT_AS node/npx MCP fix

---------

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-07-03 13:14:14 +08:00
RockChinQ 04a199a3b2 fix: preserve MCP server ids with slashes
Fixes #2301
2026-07-03 00:17:29 +08:00
Hyu f85278f98b Fix monitoring CI regressions 2026-07-02 17:22:33 +08:00
Stevenqin 13dba887d5 fix(wecomcs): implement proactive text sends (#2300) 2026-07-02 14:39:05 +08:00
彼方 cc7a13158e fix(aiocqhttp): resolve group member metadata (#2298)
* fix(aiocqhttp): resolve group member metadata

* style: ruff format
2026-07-01 20:00:47 +08:00
advancer-young 096ec1a8ce feat(mcp): support mcp resources (#2215)
* feat(mcp): support mcp resources

* feat(web): split MCP resources into tab

* docs: add MCP resources PR review

* feat(mcp): productionize resource support

* feat(mcp): scope local agent tools and resources

* fix(web): gate space embedding models behind login

* fix(web): prevent clipped space model CTA

* test: update preproc resource tool expectations

* fix(web): expose skill authoring tools in selector

---------

Co-authored-by: yang.xiang <yang.xiang@advancegroup.com>
Co-authored-by: Hyu <chenhyu@proton.me>
Co-authored-by: Junyan Qin <rockchinq@gmail.com>
2026-06-30 19:16:30 +08:00
彼方 5c8c0eb17b test: use package import paths (#2297) 2026-06-30 10:51:34 +08:00
彼方 ccc51522cf fix(aiocqhttp): normalize base64 media payloads (#2296)
* fix(aiocqhttp): normalize image and voice base64 payloads

* fix(aiocqhttp): support file messages from base64 payloads

* test(aiocqhttp): use package import path
2026-06-30 10:51:09 +08:00
Hyu b97cc800d3 feat(platform): migrate bot admins from config.yaml to database (#2289)
* feat(platform): migrate bot admins from config.yaml to database

- Add BotAdmin ORM model (bot_admins table) scoped per bot_uuid
- Add Alembic migration 0007 to create table and migrate legacy config admins
- Remove top-level admins key from config.yaml template
- Add GET/POST/DELETE /api/v1/platform/bots/<uuid>/admins endpoints
- Update cmdmgr privilege check to query bot_admins table (bot-scoped)
- Add BotAdminsPanel frontend component in bot detail sessions tab
- Add i18n keys (zh-Hans, en-US)

* fix(ci): ruff/prettier format, fix test_importutil assertion

* fix(ci): prettier format BackendClient.ts and i18n locales

* fix(ci): eslint-prettier fix BackendClient.ts single-param formatting

* refactor: move admin management into session monitor, fix session_id enum format

- Remove standalone admins tab, replace with BotAdminsDialog in session monitor
- Add admin toggle button inline in chat header next to Active status
- Add BotAdminsDialog component with useBotAdmins hook
- Fix session_id written as LauncherTypes.PERSON_xxx instead of person_xxx
  (monitoring_helper.py x4, pipelinemgr.py x1 missing .value on launcher_type)
- Fix duplicate platform/person label in session list and chat header
- Migrate existing malformed session_id records in DB

---------

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
2026-06-27 17:18:19 +08:00
彼方 48905ea080 feat(plugin): report deferred response delivery failures (#2287)
* feat(plugin): report deferred response delivery failures

* style: fix ruff format issues in plugin_diagnostics and test_handler_actions

---------

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>
2026-06-26 23:45:10 +08:00
RockChinQ 9c22a1521c fix(box): defer separated workspace ownership to runtime 2026-06-25 00:09:40 -04:00
RockChinQ 59b2a7cd51 fix(monitoring): hide disabled box status on cloud 2026-06-23 06:40:05 -04:00
RockChinQ 85743cc75f fix(tests): make Postgres migration head test revision-agnostic
The PostgreSQL migration test had the same hardcoded 0005 head
assertion as the SQLite one; resolve the actual head from the Alembic
ScriptDirectory so 0006 (and future migrations) don't break it.
2026-06-21 12:10:20 -04:00
RockChinQ c689b10c0d fix(mcp): ruff format remote-mode files; make migration head test revision-agnostic
CI follow-up to the local/remote MCP work:

- Apply ruff format to provider/tools/loaders/mcp.py and the 0006
  normalize-remote-mode migration (Lint job failed on formatting).
- test_migrations.py hardcoded the head revision as 0005_*, which broke
  once 0006 landed. Resolve the actual head from the Alembic
  ScriptDirectory so future migrations don't require editing the test.
2026-06-21 12:04:37 -04:00
Junyan Chin e9dd584792 feat: MCP server + in-repo skills (agent-friendly platform) (#2269)
* feat(api): support global API key from config.yaml (api.global_api_key)

Accept a config-defined global API key anywhere a web-UI key is accepted
(X-API-Key / Bearer), with no login session and no DB record. Useful for
automated deployments and AI agents (HTTP API + MCP). Defaults to empty
(disabled); does not require the lbk_ prefix.

- templates/config.yaml: add api.global_api_key with security notes
- service/apikey.py: verify_api_key checks global key first (constant-time)
- docs/API_KEY_AUTH.md: document the global key + security guidance
- tests: cover global-key match, prefix-free, fallback-to-db, disabled

* feat(mcp): expose LangBot management as an MCP server at /mcp

Add an MCP (Model Context Protocol) server so external AI agents can manage a
LangBot instance. Reuses the same API-key auth as the HTTP API (including the
config.yaml global API key).

- pkg/api/mcp/server.py: FastMCP server wrapping the service layer; 21 curated
  tools across system/bots/pipelines/models/knowledge/mcp-servers/skills
- pkg/api/mcp/mount.py: ASGI dispatcher fronting Quart; authenticates /mcp
  requests with an API key, runs the streamable-HTTP session manager lifespan
- controller/main.py: serve the wrapped ASGI app via hypercorn (was run_task)
- web: new 'MCP' tab in the API integration dialog showing endpoint, auth, and
  client config; i18n for 8 locales
- tests/manual/mcp_smoke.py: e2e check (401 unauth, list tools, call tools)

Tool surface is intentionally curated (not all ~25 route groups) to keep the
agent surface small, safe, and maintainable. Extend deliberately.

* feat(skills): add in-repo skills/ as the single source of truth

Migrate the agent skills + QA/e2e test harness from the (now archived)
langbot-app/langbot-skills repo into LangBot/skills/, and add four new skills.

Migrated:
- langbot-plugin-dev, langbot-testing (e2e), langbot-env-setup,
  langbot-skills-maintenance, langbot-eba-adapter-dev
- the bin/lbs CLI (src/, test/, scripts/, schemas/, qa-agent-docs/)

New:
- langbot-dev      core backend + web development
- langbot-deploy   Docker/K8s deployment + config.yaml + global API key
- langbot-mcp-ops  operating the LangBot MCP server (/mcp)
- langbot-space-ops operating the Space marketplace MCP server

- src/cli.ts repoRoot(): recognize the skills assets root (skills.index.json +
  bin/lbs) so the CLI works when nested inside the LangBot repo
- README.md: unified skill catalog; skills.index.json regenerated

Parity with source verified: bin/lbs validate + node test suite match the
source repo (only the uncommitted .lbpkg build-artifact fixture differs).

* docs(agents): document agent-facing surfaces + API/MCP/skills sync rule

* docs(readme): add 'Built for AI Agents' section across all locales

Highlight MCP server, in-repo skills (single source of truth), AGENTS.md
sync rule, and llms.txt. Cross-link LangBot Space MCP marketplace.

* style(mcp): fix ruff format + prettier lint in MCP server and API panel

* style(web): prettier format MCP i18n locale entries

* docs(skills): note MCP instance control in dev/testing skills

All development-guidance skills now point to the LangBot instance MCP
server (/mcp) and the Space marketplace MCP server, reusing API keys.
2026-06-20 15:14:47 +08:00
huanghuoguoguo acfac42107 fix(litellmchat): preserve provider_specific_fields for Gemini thought_signature (#2265)
Update _normalize_stream_tool_calls to preserve provider_specific_fields
(including thought_signature) from streaming tool call chunks. Also preserve
provider_specific_fields from delta in invoke_llm_stream.

This ensures Gemini's thought_signature is round-tripped correctly:
1. LiteLLM extracts thought_signature from Gemini response
2. It's preserved in Message/ToolCall entities (via SDK changes)
3. _convert_messages includes it in the next request

Also add unit tests for provider_specific_fields round-tripping.

Fixes: langbot-app/LangBot#1899
2026-06-19 23:26:12 +08:00
huanghuoguoguo 492827ea75 Add plugin rerank invocation action (#2242) 2026-06-19 23:25:54 +08:00
Junyan Chin 3d5b70cc5d fix(modelmgr): keep id-less streamed tool calls (Ollama) (#2262)
Ollama's OpenAI-compatible streaming endpoint emits a tool-call delta
carrying an `index` and a `function` payload but never an OpenAI-style
`id`. `_normalize_stream_tool_calls` dropped any tool call without an
`id`, so a tool-only turn yielded neither content nor a tool call: the
stream "completed" with 0 chars, the tool never ran, and the chat
appeared stuck. Models on standard OpenAI APIs (e.g. SiliconFlow) were
unaffected because they always send a `call_...` id.

Synthesize a stable per-index id (`call_<index>`) when the provider
omits one but a function name is present. Providers that do send ids
keep theirs, and parallel id-less calls keep distinct ids.

Adds regression tests for the single and multi id-less tool-call cases.

Fixes #2261
2026-06-19 18:07:25 +08:00
RockChinQ 83623f6afe fix(box): always advertise outbox path in exec guidance
Outbound attachment collection (pipeline wrapper) runs on every turn
regardless of inbound files, but the agent was only told the per-query
outbox path inside the inbound-attachment note in LocalAgentRunner. So on
pure-generation turns (e.g. "generate a QR code"/chart/mermaid where the
user sent no file), the agent never learned the outbox path or the
query_id, wrote the generated file nowhere deliverable, and it was
silently dropped.

Move the outbox instruction into BoxService.get_system_guidance(query_id),
which is injected as a system message on every turn the exec tool is
available. The inbound note keeps its own (now redundant but harmless)
outbox line. Add unit tests asserting the outbox path is present with a
query_id and absent without one.
2026-06-19 04:09:45 -04:00