From e1e14e926909f790b2934ff2d1bd602464d26d25 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Mon, 22 Jun 2026 11:08:08 -0400 Subject: [PATCH 1/5] chore(deps): bump langbot-plugin to 0.4.6 --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f5327eb31..af4e06428 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ dependencies = [ "chromadb>=1.0.0,<2.0.0", "qdrant-client (>=1.15.1,<2.0.0)", "pyseekdb==1.1.0.post3", - "langbot-plugin==0.4.5", + "langbot-plugin==0.4.6", "asyncpg>=0.30.0", "line-bot-sdk>=3.19.0", "matrix-nio>=0.25.2", diff --git a/uv.lock b/uv.lock index f274e039d..18eb72f25 100644 --- a/uv.lock +++ b/uv.lock @@ -2123,7 +2123,7 @@ requires-dist = [ { name = "ebooklib", specifier = ">=0.18" }, { name = "gewechat-client", specifier = ">=0.1.5" }, { name = "html2text", specifier = ">=2024.2.26" }, - { name = "langbot-plugin", specifier = "==0.4.5" }, + { name = "langbot-plugin", specifier = "==0.4.6" }, { name = "langchain", specifier = ">=1.3.9" }, { name = "langchain-core", specifier = ">=1.3.3" }, { name = "langchain-text-splitters", specifier = ">=1.1.2" }, @@ -2187,7 +2187,7 @@ dev = [ [[package]] name = "langbot-plugin" -version = "0.4.5" +version = "0.4.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiofiles" }, @@ -2208,9 +2208,9 @@ dependencies = [ { name = "watchdog" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/db/db33ec42b3242ea7de0c93b0523a8d32a3df76b13de177fd31671db0ba3f/langbot_plugin-0.4.5.tar.gz", hash = "sha256:3cafa5694f31e9e4b4a3d870c1bc23ee7ac6e8d271a0140c5198993471f220ec", size = 326504, upload-time = "2026-06-19T14:53:51.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/6a/5fdb5365ad04aaa61344e92578d73eb1577af35783b80767c7d6c51cb8b9/langbot_plugin-0.4.6.tar.gz", hash = "sha256:838e3cd45ed795ed4c3299c73f141b217adfa05f09937a01694e7158619e4f6e", size = 334171, upload-time = "2026-06-22T15:06:56.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/92/8a08f8793de479fffa12a1906a25b6ff5b67a018520fa72d981569e1a6e4/langbot_plugin-0.4.5-py3-none-any.whl", hash = "sha256:12ab9aff0fb2459f75a11ba6999d2b5dfc753dcc7d265b078777b24e04b23c83", size = 215602, upload-time = "2026-06-19T14:53:50.021Z" }, + { url = "https://files.pythonhosted.org/packages/6b/55/7adc2e180a299ed58613e159c64195477e09c05136f949942c6cec5219e8/langbot_plugin-0.4.6-py3-none-any.whl", hash = "sha256:30eb47efc0b703818ac003a5cd67caf720d9749dd503155eb65cce0c28b194a7", size = 217434, upload-time = "2026-06-22T15:06:55.237Z" }, ] [[package]] From 2982e7c55329eaea373b2928a8f0f7bf9f71107d Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Mon, 22 Jun 2026 11:12:15 -0400 Subject: [PATCH 2/5] chore(release): bump version to 4.10.3 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index af4e06428..cb50e4822 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langbot" -version = "4.10.2" +version = "4.10.3" description = "Production-grade platform for building agentic IM bots" readme = "README.md" license-files = ["LICENSE"] diff --git a/uv.lock b/uv.lock index 18eb72f25..41d1071ee 100644 --- a/uv.lock +++ b/uv.lock @@ -2008,7 +2008,7 @@ wheels = [ [[package]] name = "langbot" -version = "4.10.2" +version = "4.10.3" source = { editable = "." } dependencies = [ { name = "aiocqhttp" }, From e3417dd20b32c583300bad30fbd429bbab4d173d Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Mon, 22 Jun 2026 21:10:33 -0400 Subject: [PATCH 3/5] fix(release): derive package version from metadata --- src/langbot/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/langbot/__init__.py b/src/langbot/__init__.py index f6a113544..272d8db55 100644 --- a/src/langbot/__init__.py +++ b/src/langbot/__init__.py @@ -1,3 +1,5 @@ """LangBot - Production-grade platform for building agentic IM bots""" -__version__ = '4.10.2' +from importlib.metadata import version + +__version__ = version('langbot') From a43978ff24a5dc6548df4cdddaf4e8e10df4e412 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Mon, 22 Jun 2026 21:15:53 -0400 Subject: [PATCH 4/5] chore(release): bump version to 4.10.4 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cb50e4822..d476e7767 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "langbot" -version = "4.10.3" +version = "4.10.4" description = "Production-grade platform for building agentic IM bots" readme = "README.md" license-files = ["LICENSE"] diff --git a/uv.lock b/uv.lock index 41d1071ee..ef3b6fddb 100644 --- a/uv.lock +++ b/uv.lock @@ -2008,7 +2008,7 @@ wheels = [ [[package]] name = "langbot" -version = "4.10.3" +version = "4.10.4" source = { editable = "." } dependencies = [ { name = "aiocqhttp" }, From 59b2a7cd51a40240e0c03703c46db167d4eb5333 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Tue, 23 Jun 2026 06:40:05 -0400 Subject: [PATCH 5/5] fix(monitoring): hide disabled box status on cloud --- .../pkg/api/http/controller/groups/box.py | 4 + .../http/controller/groups/box_visibility.py | 5 + tests/unit_tests/api/test_box_controller.py | 18 + .../overview-cards/SystemStatusCards.tsx | 345 +++++++++--------- web/src/app/infra/entities/api/index.ts | 2 + 5 files changed, 208 insertions(+), 166 deletions(-) create mode 100644 src/langbot/pkg/api/http/controller/groups/box_visibility.py create mode 100644 tests/unit_tests/api/test_box_controller.py diff --git a/src/langbot/pkg/api/http/controller/groups/box.py b/src/langbot/pkg/api/http/controller/groups/box.py index d39ced932..d8c961e7a 100644 --- a/src/langbot/pkg/api/http/controller/groups/box.py +++ b/src/langbot/pkg/api/http/controller/groups/box.py @@ -1,6 +1,9 @@ from __future__ import annotations +from langbot.pkg.utils import constants + from .. import group +from .box_visibility import should_hide_box_runtime_status @group.group_class('box', '/api/v1/box') @@ -9,6 +12,7 @@ class BoxRouterGroup(group.RouterGroup): @self.route('/status', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) async def _() -> str: status = await self.ap.box_service.get_status() + status['hidden'] = should_hide_box_runtime_status(constants.edition, status.get('enabled')) return self.success(data=status) @self.route('/sessions', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) diff --git a/src/langbot/pkg/api/http/controller/groups/box_visibility.py b/src/langbot/pkg/api/http/controller/groups/box_visibility.py new file mode 100644 index 000000000..667203026 --- /dev/null +++ b/src/langbot/pkg/api/http/controller/groups/box_visibility.py @@ -0,0 +1,5 @@ +from __future__ import annotations + + +def should_hide_box_runtime_status(edition: str, box_enabled: bool | None) -> bool: + return edition == 'cloud' and box_enabled is False diff --git a/tests/unit_tests/api/test_box_controller.py b/tests/unit_tests/api/test_box_controller.py new file mode 100644 index 000000000..d6c968978 --- /dev/null +++ b/tests/unit_tests/api/test_box_controller.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import pytest + +from langbot.pkg.api.http.controller.groups.box_visibility import should_hide_box_runtime_status + + +@pytest.mark.parametrize( + ('edition', 'box_enabled', 'expected'), + [ + ('cloud', False, True), + ('cloud', True, False), + ('cloud', None, False), + ('community', False, False), + ], +) +def test_should_hide_box_runtime_status(edition, box_enabled, expected): + assert should_hide_box_runtime_status(edition, box_enabled) is expected diff --git a/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx b/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx index 46be50a9e..872bc90fc 100644 --- a/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx +++ b/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx @@ -65,11 +65,13 @@ export default function SystemStatusCard({ const fetchStatus = useCallback(async () => { try { - const [plugin, box, sessions] = await Promise.all([ + const [plugin, box] = await Promise.all([ httpClient.getPluginSystemStatus().catch(() => null), httpClient.getBoxStatus().catch(() => null), - httpClient.getBoxSessions().catch(() => [] as BoxSessionInfo[]), ]); + const sessions = box?.hidden + ? [] + : await httpClient.getBoxSessions().catch(() => [] as BoxSessionInfo[]); setPluginStatus(plugin); setBoxStatus(box); setBoxSessions(sessions); @@ -95,6 +97,7 @@ export default function SystemStatusCard({ : 'failed' : null; const boxOk = boxStatus ? boxStatus.available : null; + const hideBoxRuntime = boxStatus?.hidden === true; // Box has three observable states: connected (ok), disabled by config // (enabled = false → distinct gray dot + "disabled" hint), and configured // but failed (red dot + connector_error). The dashboard must distinguish @@ -152,11 +155,13 @@ export default function SystemStatusCard({ {t('monitoring.pluginRuntime')} -
- - - {t('monitoring.boxRuntime')} -
+ {!hideBoxRuntime && ( +
+ + + {t('monitoring.boxRuntime')} +
+ )} @@ -214,181 +219,189 @@ export default function SystemStatusCard({ -
+ {!hideBoxRuntime && ( + <> +
- {/* Box Runtime */} -
-
- - - {t('monitoring.boxRuntime')} - -
-
-
- {boxState === 'ok' ? ( - - ) : ( - - )} - - {boxState === 'ok' - ? t('monitoring.connected') - : boxState === 'disabled' - ? t('monitoring.disabled') - : t('monitoring.disconnected')} - -
- {boxState === 'disabled' && ( -

- {t('monitoring.boxDisabled')} -

- )} - {boxState === 'failed' && boxStatus?.connector_error && ( -

- {boxStatus.connector_error} -

- )} - {boxStatus && ( -
- {boxStatus.backend && ( -

- {t('monitoring.boxBackend')}:{' '} - - {boxStatus.backend.name} - -

- )} -

- {t('monitoring.boxProfile')}:{' '} - - {boxStatus.profile} - -

- {boxOk && boxStatus.active_sessions !== undefined && ( -

- {t('monitoring.boxSandboxes')}:{' '} - - {boxStatus.active_sessions} - -

- )} + {/* Box Runtime */} +
+
+ + + {t('monitoring.boxRuntime')} +
- )} - - {/* Active Sandboxes */} - {boxSessions.length > 0 && ( -
- {boxSessions.map((session) => ( -
+
+ {boxState === 'ok' ? ( + + ) : ( + + )} + -
- - - - - {session.session_id} - - - - {session.session_id} - - -
-
-
- - - - - {session.image} - - - {session.image} - -
-
- - - {session.backend_name} + {boxState === 'ok' + ? t('monitoring.connected') + : boxState === 'disabled' + ? t('monitoring.disabled') + : t('monitoring.disconnected')} + +
+ {boxState === 'disabled' && ( +

+ {t('monitoring.boxDisabled')} +

+ )} + {boxState === 'failed' && boxStatus?.connector_error && ( +

+ {boxStatus.connector_error} +

+ )} + {boxStatus && ( +
+ {boxStatus.backend && ( +

+ {t('monitoring.boxBackend')}:{' '} + + {boxStatus.backend.name} -

-
- - - {session.cpus} CPU / {session.memory_mb} MB +

+ )} +

+ {t('monitoring.boxProfile')}:{' '} + + {boxStatus.profile} + +

+ {boxOk && boxStatus.active_sessions !== undefined && ( +

+ {t('monitoring.boxSandboxes')}:{' '} + + {boxStatus.active_sessions} -

-
- - - {session.network} - -
- {session.host_path && ( -
- +

+ )} +
+ )} + + {/* Active Sandboxes */} + {boxSessions.length > 0 && ( +
+ {boxSessions.map((session) => ( +
+
+ - - {session.host_path} : {session.mount_path}{' '} - - ({session.host_path_mode}) - + + {session.session_id} - {session.host_path} : {session.mount_path} ( - {session.host_path_mode}) + {session.session_id}
- )} -
- - - {t('monitoring.boxSessionCreated')}:{' '} - - {new Date( - session.created_at, - ).toLocaleString()} - - +
+
+ + + + + {session.image} + + + + {session.image} + + +
+
+ + + {session.backend_name} + +
+
+ + + {session.cpus} CPU / {session.memory_mb} MB + +
+
+ + + {session.network} + +
+ {session.host_path && ( +
+ + + + + {session.host_path} :{' '} + {session.mount_path}{' '} + + ({session.host_path_mode}) + + + + + {session.host_path} :{' '} + {session.mount_path} ( + {session.host_path_mode}) + + +
+ )} +
+ + + {t('monitoring.boxSessionCreated')}:{' '} + + {new Date( + session.created_at, + ).toLocaleString()} + + +
+
+ + + {t('monitoring.boxSessionLastUsed')}:{' '} + + {new Date( + session.last_used_at, + ).toLocaleString()} + + +
+
-
- - - {t('monitoring.boxSessionLastUsed')}:{' '} - - {new Date( - session.last_used_at, - ).toLocaleString()} - - -
-
+ ))}
- ))} + )}
- )} -
-
+
+ + )}
diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index 582364656..61cb33acd 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -373,6 +373,8 @@ export interface ApiRespPluginSystemStatus { export interface ApiRespBoxStatus { available: boolean; + /** UI hint: hide the Box runtime status surface for this deployment. */ + hidden?: boolean; /** Whether ``box.enabled`` is true in config. When false, the sandbox * is deliberately disabled — distinct from "configured but failed". */ enabled?: boolean;