* feat(plugin): upload debug plugin to LangBot Space with optional GitHub sync
- SDK: reusable in-memory .lbpkg packaging + git sync helper (local git by default, optional repo/token override)
- routing: space-upload config/upload/git-sync endpoints; debug plugins resolved via OSS bridge without touching get_plugin_info
- frontend: debug-card entry + upload dialog with multi-language metadata, icon upload and changelog
- i18n across 8 locales; operation-trace + action allowlist entries
* fix(plugin-space-upload): stable dialog width, single-line locale tabs, broader error surfacing
- fixed dialog width so toggling the GitHub section no longer resizes it
- language tabs render on one line (horizontal scroll when needed)
- surface the real upload/git error message (msg field) instead of [object Object]
- hint that a missing GitHub repo is auto-created when a token is provided
* fix(plugin-space-upload): narrow dialog and reset sm:max-w clamp
The base DialogContent applies sm:max-w-lg at the sm breakpoint, which overrode the unprefixed max-w-none and clamped the upload card to 32rem on desktop. Set an explicit sm:max-width and size the card to ~36rem. Locale tabs now wrap instead of scrolling horizontally.
* fix
* fix(plugin-space-upload): address CI and UX feedback
- narrow the upload dialog to ~36rem and reset the sm:max-w clamp
- guide the publisher to the Space review page after a successful upload
- drop the redundant null comparison flagged by code quality
- update the operation-action expectation and Space/LangBot Account wording
- ruff format the touched API modules
* feat(plugin-space-upload): public repo, editable author/license, card red dot
* fix(market): replace yellow download count with update available badge
* style(market): unify installed and update indicators into badges
* feat(plugin): redesign debug card banner, style debug icon, and prioritize debug plugins
* feat(plugin-space-upload): simplify upload dialog layout, add creator verification and market redirection
* fix(i18n): use LangBot Account terminology in Space upload dialog
Co-authored-by: multica-agent <[email protected]>
* style(i18n): reflow reworded locale strings for prettier
Co-authored-by: multica-agent <[email protected]>
* test(utils): stop timing out the pattern-count cap test on loaded runners
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Hyu <[email protected]>
Co-authored-by: RockChinQ <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Stdio and Windows launches always bound the SDK default debug port 5401,
so changing plugin.display_plugin_debug_url only updated the console URL
and a second Core instance hit Address already in use. Pass the configured
debug (and Windows control) ports into the Runtime CLI, and document the
behavior.
Fixes#2713
Co-authored-by: Li-john1021 <[email protected]>
The connector stored only uuid/code/message, so the ops reporter stamped every entry with its own sampling time and a stale failure looked freshly broken on each report. Failures now carry failed_at and the payload publishes it.
The shared Cloud deployment runs one Plugin Runtime for every Workspace, but
nothing reported its health: the instance only exposed two aggregate numbers on
its own health probe, and the per-installation failure registry, the desired
state registry and the reconcile outcome lived in memory with no reader.
Add RuntimeOpsReporter, which every 30 seconds reads the Runtime ``/healthz``
probe over HTTP (derived from ``plugin.runtime_ws_url``), merges it with the
connector's desired-state, failure and reconcile summaries, and posts one
sample to the Space control plane on the existing authenticated direction.
Delivery is best-effort and bounded: a single in-flight request, a ten second
timeout, dropped samples counted in ``runtime_ops`` on the instance health
probe, and a failed probe recorded as a drop so the dashboard shows staleness
instead of grading a stale sample as healthy.
Cloud-only: a non-cloud deployment never enables the reporter, and the channel
is independent of product telemetry so ``space.disable_telemetry`` cannot blind
the ops dashboard.
Resolved one conflict in src/langbot/pkg/plugin/connector.py. Master added
archive admission (`_admit_plugin_archive` + the certification digest check
before Runtime apply) at the same points this branch added install-stage
reporting, so both sides are kept:
- the archive is admitted first, then the 'inspecting plugin package' stage is
reported
- the certification digest is verified before Runtime apply, then the
'installing or starting plugin' stage is reported
Verified after merge: py_compile + ruff, tsc --noEmit, 98/98 unit tests.
Addresses review feedback on the marketplace installed-state change.
- Progress: byte-derived progress no longer has time-based drift layered on
top, and fallback drift is clamped to the current stage range, so the bar
cannot exceed the download band. 90/100 bytes at 40s elapsed used to report
61% against a declared 5-45% band; it now reports 41%. Drift is measured
from when the stage was entered rather than from task start.
- Skills: a marketplace skill is no longer reported installed from a bare
skill name. The backend names skills from their own SKILL.md and records no
publisher, so alice/review and bob/review install identically; matching the
bare name marked every publisher's skill as installed. Skill cards now
resolve to not-installed until the installed skill carries a publisher.
- Stages: "installing plugin dependencies" and "launching plugin" described
work this task context cannot observe (installation persistence ran under
the former; the runtime installs dependencies and starts the plugin inside
apply_plugin_installation under the latter). They become "persisting the
installation" and "installing or starting plugin", and the frontend maps
that combined step to the dependency stage rather than the launch stage.
- Removed the per-dependency progress fields: the backend never populated
them, so the UI could never have displayed them.
The stage mapping and progress maths move to install-progress.ts, and the
installed-state matching to a React-free marketplace-installed.ts, so both
are covered by executable tests (+9).
Verified: ruff, tsc --noEmit, prettier --check, eslint (0 errors), 98/98 unit
tests.
Marketplace cards now reflect whether an extension is already installed in
the current workspace, and the installed-extension list gains a search box.
Backend (stream install progress):
- _read_httpx_response_limited gains an optional task_context: it publishes
download_total from Content-Length before the first chunk and updates
download_current / download_speed per chunk. The marketplace download path
previously had no progress reporting; it now matches the GitHub path.
- _marketplace_get forwards task_context to that helper.
- install_plugin resets the per-install counters so re-installing the same
plugin cannot inherit stale metadata, and reports human-readable stages:
preparing -> downloading -> inspecting -> storing -> installing
dependencies -> launching -> waiting for plugin to become ready.
Frontend (installed state):
- New marketplace-installed helper normalises the sidebar identities
(plugin: author/name, mcp: author__name, skill: bare name) into one
type:author/name index and resolves a card's installed state from it.
useMarketplaceInstalledIndex memoises on the sidebar lists, so a finished
install (which refreshes the sidebar) re-evaluates the cards automatically.
- PluginMarketCardVO carries installed / hasUpdate. An installed extension
turns its download affordance into a hollow green ring with a green check
in place, instead of adding a separate badge; the count slot switches to
the installed label. Cards with an available update use amber.
- PluginMarketComponent derives the annotated list and shares the index with
RecommendationLists.
Frontend (install task UI):
- mapActionToStage matches the new connector stage strings. The pre-download
stages are checked before the generic "install" match, because
"preparing plugin install" also contains "install".
- Stage progress ranges are non-overlapping; overall progress interpolates on
real byte counts while downloading and drifts monotonically elsewhere,
capped at 99%.
- The progress dialog and task queue expose the launching stage.
Frontend (installed list search):
- The installed list had no search at all. A query box in the page header
filters by label / name / author / description, case-insensitively, applied
before grouping so grouped and flat views both honour it.
- Search misses and an empty list now show distinct empty states, with a
clear action on a search miss.
- AsyncTask entity gains the optional created_at field.
i18n: new marketplace / install / search strings across all 8 locales.
Verified: ruff format + check, tsc --noEmit, prettier --check, eslint
(0 errors), and 89/89 frontend unit tests.
Replace the three-way transport choice (stdio / sse / httpstream) for
connecting LangBot to external MCP servers with two modes: local (stdio)
and remote. Remote servers only require a URL; the runtime auto-detects
the transport (tries Streamable HTTP, falls back to SSE).
- provider/tools/loaders/mcp.py: add _init_remote_server() with
Streamable-HTTP-then-SSE probing; dispatch 'remote' lifecycle, keep
legacy sse/http branches for back-compat
- plugin/connector.py: normalize legacy http/sse marketplace modes to
'remote' on Space install, preserving connection params
- entity/persistence/mcp.py: document mode as stdio, remote (legacy: sse, http)
- alembic 0006: idempotent data migration mapping existing sse/http rows
to remote (downgrade maps back to http)
- api/http/service/mcp.py: stash runtime_info (status + tool list) into
test task metadata before tearing down the temp session
- web: collapse mode dropdown to local/remote, remote renders URL+timeout
only, edit auto-maps legacy sse/http to remote; show tools after test in
create mode from task metadata; remove dead plugins/mcp-server/ tree
- i18n: local/remote labels + mode/url hints across 8 locales
Add a Logs tab beside Documentation on the plugin detail page, showing
the output a plugin prints through the standard Python logger (per the
wiki style guide). Logs are captured from the plugin's stderr by the
plugin runtime and fetched on demand.
- Bump langbot-plugin pin to 0.4.4 (adds GET_PLUGIN_LOGS action)
- plugin_connector/handler: get_plugin_logs RPC client
- HTTP route GET /api/v1/plugins/<author>/<name>/logs (limit + level)
- Frontend: wrap detail right panel in Docs/Logs Tabs; PluginLogs
component with level filter, manual + 3s auto refresh, bottom-follow
- i18n: 7 new keys across all 8 locales
Capture the README markdown from LangBot Space when installing an MCP
server and store it on the mcp_servers record (new readme column +
alembic migration 0004). The detail page can then render docs offline,
independent of the server's runtime/connection state.