* fix(box): trust Box-reported skill paths when filesystem is not shared
In separated deployments (Docker Compose, k8s sidecar, --standalone-box,
remote runtime.endpoint) the Box runtime owns its own filesystem, so the
skill package_root it reports via list_skills is not resolvable on the
LangBot side. LangBot's reload_skills and build_skill_extra_mounts
validated those paths with os.path.isdir() against its own filesystem,
which silently dropped every skill in such deployments — breaking the
sandbox skill feature for the nsjail/SaaS backend.
Add BoxService.shares_filesystem_with_box, derived from the connector
transport (stdio = shared, WebSocket = separated), with an explicit
override seam for tests/embedders. Gate both isdir() guards on it: keep
local validation in shared-fs stdio mode, trust Box-reported paths
otherwise. The Box runtime only reports skills found on its own
filesystem, so those paths are valid there by construction.
Adds topology-derivation tests (real connector, no mocks) and
skill-retention tests for both shared and separated filesystems.
* build(docker): ship a self-contained nsjail sandbox backend in the image
Compile nsjail 3.6 from source in a dedicated multi-stage build and carry
only the binary plus its runtime libs (libprotobuf32, libnl-route-3-200)
into the final image. This lets the Box runtime isolate sandboxed code via
nsjail user/mount/pid/net namespaces without a host Docker socket — the
prerequisite for running Box on LangBot Cloud (k8s), where mounting
docker.sock would grant node root and is not acceptable for multi-tenant.
The build toolchain (build-essential/bison/flex/protobuf-dev/libnl-dev)
stays in the nsjail-build stage and is not present in the shipped image.
Verified: image builds (583MB), nsjail --help exits 0, libraries resolve,
and the real NsjailBackend executes an isolated command end-to-end on a
v6.1/cgroup2 host matching LangBot Cloud prod (rlimit fallback path, since
container /sys/fs/cgroup is read-only; PID-namespace isolation confirmed).
* feat(box): SaaS guard to force a single global sandbox scope
Add system.limitation.force_box_session_id_template: when non-empty it
overrides every pipeline's box-session-id-template at resolve time, pinning
all queries to one shared sandbox (e.g. {global}). This is the authoritative,
unbypassable guard — it runs on every exec call, so editing the pipeline
config via API cannot escape it. The web UI locks the Sandbox Scope selector
via a combined box_scope_editable flag (box available AND not forced).
* build(deps): pin langbot-plugin==0.4.2b1 (nsjail cgroup container-safety beta)
* fix(web): show forced sandbox scope + make disabled tooltip tap-friendly
When a SaaS deployment pins every pipeline to a fixed sandbox scope via
system.limitation.force_box_session_id_template, the Sandbox Scope selector was
correctly locked but still displayed the pipeline's stored value (e.g. the
per-chat default), misrepresenting the scope that the runtime actually enforces
on every exec. Coerce the displayed/saved value to the forced template so the
locked selector truthfully shows the active scope (e.g. Global).
Also fix the disabled_tooltip being invisible on touch devices: hover-only Radix
tooltips never open without a pointer, so the explanation of why the field is
locked could not be read on mobile. Wrap the info icon so a tap toggles the
tooltip while desktop hover still works.
* feat(web): hide sidebar new-version prompt for edition=cloud
Cloud instances are upgraded centrally by the operator, so surfacing a GitHub
'new version available' badge to tenants is misleading and actionable only by
the operator. Skip the release check entirely when edition=cloud.
* style(web): prettier formatting for DisabledTooltipIcon ternary
* chore(deps): bump langbot-plugin to 0.4.2b2
Picks up the SDK fix that creates a read-write host_path before the
nsjail bind-mount, fixing the SaaS MCP shared-workspace sandbox failure
(exec exit 255 with empty output when host_path didn't exist).
* chore(deps): bump langbot-plugin to 0.4.2b3
Picks up the nsjail /dev-node fix so stdio MCP servers (uvx-launched) can
start under force_global_sandbox instead of failing with 'Connection closed
/ please check URL'.
* fix(web): show real MCP runtime status on installed extensions list
The installed-extensions list badge keyed solely off the enable flag, so a
server that was still CONNECTING (or in ERROR) was shown as 'Connected'.
Reflect the actual runtime_info.status (connecting/connected/error/disabled)
with matching colors, and poll quietly every 3s while any MCP server is
connecting so the badge transitions without a manual refresh.
* chore(deps): bump langbot-plugin to 0.4.2b4
Picks up the 30s start_managed_process timeout so cold uvx MCP bootstraps
don't get torn down mid-install.
* style(web): satisfy prettier — parenthesize nullish-coalescing in ternary
* fix(mcp): isolate transient test sessions from the shared Box session
A config-page 'test' (server_name='_', no persisted UUID) ran in the same
shared 'mcp-shared' Box session as live MCP servers. A failing test (e.g.
empty args) churned that shared session and tore down healthy, already-
connected servers — leaving them stuck after exhausting their retries.
Mark UUID-less sessions as transient, give them their own isolated Box
session ('mcp-test-<uuid>'), and fully delete that session on cleanup so
tests can never disturb live servers and don't leak sessions.
* fix(mcp): tear down transient test session after test completes
A successful config-page test left its isolated 'mcp-test-<uuid>' Box
session running (the lifecycle task blocks until shutdown). Wrap the
transient test coroutine so it always shuts the session down afterward,
preventing isolated test sessions from leaking.
The langbot_box service drives sandbox containers through the docker CLI
(CLISandboxBackend shells out to `docker run`/`docker exec`), but the
image shipped without a docker client, so DockerBackend.is_available()
was always false and the Box sandbox backend was unavailable in Docker
deployments — disabling native tools, skill execution and stdio MCP.
Install docker-ce-cli (client only) in the image, arch-aware so
multi-arch builds work.
Also bump langbot-plugin pin to 0.4.1, which disables proxy
auto-detection on internal control-plane WebSocket connections (the
langbot<->plugin_runtime / langbot<->box handshakes were failing on
hosts that inject a proxy into containers).
Bumps version to 4.10.0-beta.3.
* refactor(web): migrate from Next.js to Vite + React Router
* fix: update build pipelines for Vite migration (out → dist)
- Dockerfile: npm run build → npx vite build, web/out → web/dist
- pyproject.toml: package-data web/out/** → web/dist/**
- paths.py: support both web/dist (Vite) and web/out (legacy) with fallback
* fix: remove .next from git tracking, add to .gitignore
1334 cached files from web/.next/ were accidentally committed.
Added .next/ to both root and web/.gitignore.
* fix: update build process to use Vite and correct output directory
* fix: update pnpm-lock.yaml and eslint config for Vite migration
* style: fix prettier formatting issues
* fix: add eslint-plugin-react-hooks for Vite migration
* fix: remove undefined eslint rule reference, downgrade react-hooks plugin to v5
* fix(web): clean up remaining Next.js artifacts in Vite migration
- Add vite-env.d.ts for import.meta.env and asset type declarations
- Remove dead layout.tsx (providers already in main.tsx)
- Fix useSearchParams destructuring to [searchParams] tuple (11 locations)
- Replace process.env.NEXT_PUBLIC_* with import.meta.env.VITE_*
- Fix langbotIcon.src to langbotIcon (Vite returns URL string)
- Fix Link href to Link to for react-router-dom
- Fix navigate({ scroll: false }) to { preventScrollReset: true }
- Fix [router] dependency arrays to [navigate]
- Remove Next.js plugin from tsconfig, set rsc: false in components.json
- Replace next lint with eslint in lint-staged
* feat: add tools API endpoint and tools-selector form type
Backend:
- Add GET /api/v1/tools — list all available tools (plugin + MCP)
- Add GET /api/v1/tools/<tool_name> — get specific tool details
Frontend:
- Add TOOLS_SELECTOR form type for plugin config forms
- Multi-select dialog with tool name and description
- Add PluginTool entity type and API client methods
* Revert "feat: add tools API endpoint and tools-selector form type"
This reverts commit 3c637fc563.
* feat(wecom): add user feedback support for WeChat Work AI Bot
This commit implements user feedback functionality (like/dislike) for
WeChat Work AI Bot conversations, including:
Backend changes:
- Add feedback_id and stream_id fields to WecomBotEvent
- Implement feedback event handling in WecomBotClient (api.py)
- Add StreamSessionManager._feedback_index for feedback_id lookup
- Add on_feedback decorator for custom feedback handlers
- Create MonitoringFeedback entity for database persistence
- Add dbm025 migration for monitoring_feedback table
- Implement FeedbackMonitor helper class
- Update all platform adapters with ap parameter support
- Update botmgr to pass bot_info for monitoring context
Frontend changes:
- Add FeedbackCard and FeedbackList components
- Add useFeedbackData hook for feedback data fetching
- Add feedback tab to monitoring page
- Add feedback types and interfaces
- Add i18n translations (zh-Hans, en-US)
Other changes:
- Update Dockerfile with Chinese mirror for faster builds
- Update docker-compose.yaml with network configuration
- Update .gitignore for docker data and backup files
Note: Known issues that need future improvement:
- feedback_type=3 (cancel) is recorded but not properly handled
- Duplicate feedback records are not deduplicated
* chore: remove unnecessary migration for new table will be created automatically
* chore: ruff format
* chore: prettier
* feat: add feedback handling support across multiple platform adapters
* fix(web): remove unused imports and variables in monitoring module
---------
Co-authored-by: 6mvp6 <13727783693@163.com>
Co-authored-by: Junyan Qin <rockchinq@gmail.com>