The TagsFilter Select trigger used the default cursor; add cursor-pointer so the
tag filter is clearly clickable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The installed-extensions grouping is by extension format (plugin / MCP / skill),
so rename the toggle label accordingly across all 8 locales (key unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
check-i18n surfaced that market.componentName.*, market.filterByComponentHint
and the addExtension.install* keys existed only in en-US/zh-Hans. Backfill
them for es-ES, ja-JP, ru-RU, th-TH, vi-VN and zh-Hant (reusing each locale's
existing component-name translations) and align the filterByComponent label
with the new "Plugin Component" wording. check-i18n now passes for all locales.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the redundant "插件组件是" lead-in and mention that components extend
LangBot's capabilities; mirror the wording in en-US.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The market component-filter list on this branch was a diverged rewrite that
dropped the Page component kind master had added. The i18n key
(market.componentName.Page) already existed; re-add the Page entry to the
componentOptions list so plugins providing Page components can be filtered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename the in-app plugin market component filter label to "插件组件" /
"Plugin Component"
- Add an Info icon tooltip explaining what plugin components are (Tool /
Command / EventListener, etc.)
- Persist filter selections (type / component / tags / sort) in localStorage
so they survive reloads; restored on mount (URL type param still wins)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add a refresh button to the installed-extensions category header in the
sidebar; it re-fetches plugins + MCP servers + skills and spins while
loading.
- The sidebar item tooltip now shows the extension's full name (with the
description below when present), so truncated MCP/extension names are
readable on hover.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The marketplace component filter (and component badges) used
market.componentName.{Tool,Command,EventListener,KnowledgeEngine,Parser,Page}
but those keys only existed under plugins.componentName, so the market UI
showed raw keys. Add a componentName block to the market namespace (zh-Hans +
en-US; other locales fall back to zh-Hans).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The progress dialog showed plugin-only stages (download + dependency install)
for every type. MCP/skill have no such steps, so show a single
"installing → done/failed" row for them (MCP: adding & connecting the server;
skill: installing the package) while keeping the detailed download/deps
stages for plugins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
installIconURL was computed above the useState declaration of installInfo,
causing "Cannot access 'installInfo' before initialization" (500) on the
add-extension page. Move the computation below the state declarations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The install dialog already states the type in its title, so the "类型" row was
redundant. Replace the info box with the extension's icon (avatar), display
name, author/name id + version, and description — built from the PluginV4 for
in-app installs and from the icon endpoint by type for the one-click deep link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The marketplace install confirm dialog was hardcoded to "安装插件 / 确定要安装
插件 X 吗" for every type. Make it type-aware (plugin / MCP / skill) and show
more info: type chip, author/name id, and version when present.
Also refresh all sidebar extension lists (plugins, MCP servers, skills) when
an install task completes, so the newly-installed extension appears
immediately regardless of type (previously only refreshPlugins ran).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MCP status UI rendered "连接失败" for any non-connected state, so during a
normal connection attempt the subtitle showed "连接失败" while the status pill
below it showed "连接中..." — contradictory.
Only treat an explicit ERROR (or box-unavailable) status as failed; a
CONNECTING or initial/unresolved status now shows "连接中". Applied to the MCP
detail form (subtitle + StatusDisplay) and the MCP server card.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- On connecting to the plugin runtime, push the configured space.url via the
new SET_RUNTIME_CONFIG action so the runtime downloads plugins from the same
Space, instead of relying on its own CLOUD_SERVICE_URL env/default. Wrapped
in try/except so an older SDK without the action degrades gracefully.
- web: the plugin market fetched recommendation lists (and listings) via the
sync cloud client before its baseURL was resolved from system info, so it
hit the default space.langbot.app. Await getCloudServiceClient() before the
initial fetches and for the recommendation list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Accept a deep link from LangBot Space's one-click install:
/home/add-extension?install=1&extension_type=<plugin|mcp|skill>&author=&name=&version=
On mount, populate the install info, open the confirm dialog directly, and
strip the params from the URL. Reuses the existing marketplace install flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The marketplace recommendation lists (curated rows from Space) were never
mounted in the plugin market page. Wire them in:
- fetch recommendation lists on mount and render them above the extension
grid, only when no search/filter is active.
Recommendation lists now mix plugins, MCPs and skills, so resolve each
card's icon by type (plugin / mcp / skill marketplace icon URL) instead of
always using the plugin icon endpoint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI on feat/sandbox failed across Unit Tests, Lint and Build Dev Image.
Root causes and fixes:
- pyproject.toml had a [tool.uv.sources] editable override pinning
langbot-plugin to ../langbot-plugin-sdk. That path only exists in a
paired local checkout, so `uv sync` failed on every CI runner
("Distribution not found"). Remove the override and regenerate uv.lock
so langbot-plugin==0.4.0b1 resolves from PyPI, matching master.
- tests/integration/api/test_pipelines.py: the pipeline extensions
endpoint now calls ap.skill_service.list_skills(); add the missing
skill_service mock to the fake_pipeline_app fixture (the test came
from master, the endpoint change from feat/sandbox).
- Apply ruff format to three src files and prettier to three web files
that had committed formatting drift, failing `ruff format --check`
and `pnpm lint`.
When Box is configured but the runtime reports its backend is dead
(e.g. ``box.backend = nsjail`` but the binary is missing, or Docker
daemon crashed), the backend now returns a structured
``connector_error`` like ``Configured sandbox backend "nsjail" is
unavailable``. The previous notice only said "Box sandbox is
unavailable" + a generic "enable Box" hint, hiding the actionable
detail.
- ``useBoxStatus``: derive ``reason`` from ``status.connector_error``.
Only exposed for the failed-state (``hint === 'boxUnavailable'``),
since the disabled-by-config message already carries its reason
- ``BoxUnavailableNotice``: insert the reason as a small monospaced
line between the state message and the action hint. The disabled
variant is unchanged (operator chose the state)
- Wire ``reason`` through every existing call site (Skills page +
detail, PipelineExtension, both MCP forms). Old unused ``context``
prop dropped
Net layout (3 lines, still compact):
⚠ Box sandbox is unavailable — sandbox tools, skill add/edit, ...
Configured sandbox backend "nsjail" is unavailable
This feature requires the Box runtime. Enable it in config ...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Fix provider type select showing blank when editing: await
loadRequesters() before loadProvider() to ensure options are
populated before setting the selected value.
2. Split 'Add Model' into two separate entries: a '+ Add Model'
button for manual add and a Radar icon button for scan. Each
opens its own popover with only one layer of tabs (model type
for manual, no tabs for scan since types are auto-detected).
3. Fix popover position: side='bottom' instead of 'left'.
4. Fix popover scroll: model type tabs stay fixed at top, content
area scrolls independently when it overflows.
5. Scan mode now fetches all model types at once (no modelType
filter), and routes each scanned model to the correct API
based on its own type field.
When Box is disabled in config (``box.enabled = false``) or unreachable,
saving a new MCP server in stdio mode produced one that could never
start — the user would only learn that from the runtime error on the
detail page. Stop the user before they save instead.
Both MCP forms (the page-level ``MCPForm.tsx`` and the older dialog
``MCPFormDialog.tsx``) now:
- Disable the ``stdio`` option in the mode select when Box is
unavailable, with a small "(requires Box)" suffix so the reason is
obvious. Existing stdio configs still display their current value
- Show ``BoxUnavailableNotice`` inline under the mode select when the
currently-selected mode is stdio and Box is unavailable, so editing
a stale stdio config makes the cause visible
- Disable the Save / Submit button while stdio is selected under that
condition. ``MCPForm`` exposes a new ``onSaveBlockedChange`` prop
so the parent ``MCPDetailContent`` can disable both its Submit and
Save buttons. ``MCPFormDialog`` disables its Save button locally
- Refuse the submit handler too (Enter-key path) with a toast carrying
the same i18n message
i18n: ``mcp.boxRequired`` (short tag in the disabled option) and
``mcp.stdioBlockedByBoxToast`` added to all 8 locales.
Backend runtime gate (``_init_stdio_python_server`` refusal +
``BOX_UNAVAILABLE`` error_phase + retry short-circuit) stays in place
as the last line of defence for API bypass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the MCP detail dialog dumped the raw RuntimeError text from
``_init_stdio_python_server`` — English-only, prefixed with "Failed
after 4 attempts", and exposing internal config names. The retry
wrapper also kept retrying a refusal that is deterministically going
to fail again, polluting logs.
Replace the raw text with a structured signal:
- New ``MCPSessionErrorPhase.BOX_UNAVAILABLE`` enum value. The stdio
refusal path sets it before raising and uses a short opaque
discriminator (``box_disabled_in_config`` / ``box_unavailable``) as
the message body — never user-facing
- ``_lifecycle_loop_with_retry`` short-circuits on
``BOX_UNAVAILABLE``: surfaces the error immediately, no retries,
no "Failed after N attempts" prefix. Silences the warning storm
seen during smoke-testing
- ``MCPServerRuntimeInfo`` (TS type) now declares ``error_phase``,
``retry_count``, ``box_session_id``, ``box_enabled`` to match what
the backend already returns in get_runtime_info_dict()
- Both MCP detail forms (``mcp/components/mcp-form/MCPForm.tsx`` and
``plugins/mcp-server/mcp-form/MCPFormDialog.tsx``) detect
``error_phase === 'box_unavailable'`` and render a two-line
localized notice: state line ("Box disabled / unreachable") plus
remediation line ("enable Box or switch to http/sse")
- 8 locale files (en/zh-Hans/zh-Hant/ja/ru/vi/th/es) get
``mcp.boxDisabledStdioRefused``, ``mcp.boxUnavailableStdioRefused``,
``mcp.boxStdioRefusedSuggestion``
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit hard-coded a BoxUnavailableNotice banner above the
``local-agent`` stage card. That works, but it shouts at the user about
every field in that stage when in reality only one field —
``box-session-id-template`` — depends on the sandbox.
Use the dynamic-form schema's existing variable-injection mechanism
(``__system.*`` references via ``systemContext``) and add a sibling to
``show_if``: ``disable_if`` + ``disabled_tooltip``. The field stays
visible, becomes inert, and an info icon next to its label exposes the
reason on hover. The rest of the AI tab is left untouched.
- entities/form/dynamic.ts: extend IDynamicFormItemSchema with
``disable_if: IShowIfCondition`` and ``disabled_tooltip: I18nObject``
- DynamicFormComponent: evaluate disable_if with the same resolver as
show_if; OR the result into isFieldDisabled; render an Info tooltip
trigger next to the label when the condition matches
- ai.yaml metadata: attach disable_if (__system.box_available eq false)
and a localized disabled_tooltip to box-session-id-template
- PipelineFormComponent: drop the BoxUnavailableNotice import and the
per-stage banner; pass ``systemContext={ box_available: boxAvailable }``
only for the local-agent stage so other stages aren't paying the
re-render cost
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When Box is disabled in config (``box.enabled = false``) or fails to
connect, every dependent UI surface now degrades visibly:
- ``useBoxStatus`` hook: shared, polled 30s, exposes ``available``,
``disabled`` (config-off) and a single ``hint`` key so callers don't
have to re-derive the three states
- ``BoxUnavailableNotice`` reusable Alert banner driven by that hint
- Dashboard SystemStatusCards: three-state dot + label
(connected / disabled-gray / disconnected-red); disabled state shows
the ``boxDisabled`` hint, failed state continues to show the connector
error. Plugin block kept untouched
- Skills page (create view) and SkillDetailContent (edit view):
Save button disabled and banner inserted above the form when Box is
unavailable — matches the backend gate added in the previous commit
- PipelineExtension skill section: ``enable_all_skills`` switch, Add
Skill button and Remove buttons all gate on Box availability;
banner inline under the section header
- PipelineFormComponent: banner above the ``local-agent`` stage card
when Box is unavailable, since that stage carries the sandbox-bound
``box-session-id-template`` field
- Box status payload type (``ApiRespBoxStatus.enabled``) and 8 locale
files updated with ``boxDisabled`` / ``boxUnavailable`` /
``boxRequiredHint`` strings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix nested directory display in skill file tree (preserve root entries)
- Fix file content display when clicking files in skill browser
- Add skill manager and tool manager as proper package modules
- Separate fileContent state to allow editing non-SKILL.md files
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add API endpoints for listing/reading/writing skill files
- Add FileTree component in SkillForm for directory browsing
- Users can now view scripts/, references/, assets/ directories
- Files can be selected and edited in the instructions textarea
- Add translations for new file browsing features
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>