Compare commits

...

12 Commits

Author SHA1 Message Date
Hyu b121c66f18 Merge pull request #2425 from langbot-app/fix/pin-plugin-sdk-hotfix
fix: pin Plugin Runtime SDK 0.5.3
2026-08-13 18:44:49 +08:00
Chan 536534865e fix: pin plugin runtime SDK 0.5.3 2026-08-13 10:39:51 +00:00
Hyu ff528e8e0b Merge pull request #2424 from langbot-app/fix/pin-plugin-sdk-052
chore(runtime): pin plugin SDK 0.5.2
2026-08-13 17:36:18 +08:00
Chan f7914a8900 test: remove obsolete private deploy workflow assertion 2026-08-13 09:31:45 +00:00
Chan c18fc9dfe3 chore(runtime): pin plugin SDK 0.5.2 2026-08-13 09:27:50 +00:00
Hyu bc40418bda Merge pull request #2422 from langbot-app/fix/remove-cloud-v2-deploy
chore: remove private Cloud deployment configuration
2026-08-13 16:18:27 +08:00
Chan 45ed6efceb chore: remove private Cloud deployment configuration 2026-08-13 08:14:40 +00:00
Hyu c67a503532 feat(health): expose plugin runtime readiness (#2420)
Co-authored-by: Chan <dadachann@users.noreply.github.com>
2026-08-12 19:49:56 +08:00
Hyu 8b14d2ab8e fix(runtime): allow shared plugin reconcile to finish (#2419)
Co-authored-by: Chan <dadachann@users.noreply.github.com>
2026-08-12 19:31:23 +08:00
Hyu 97428310b9 fix(runtime): require Cloud control secrets (#2418)
Co-authored-by: Chan <dadachann@users.noreply.github.com>
2026-08-12 18:42:23 +08:00
Hyu 338ee733cb fix(deploy): preserve recovered Cloud adapter pin (#2417)
Co-authored-by: Chan <dadachann@users.noreply.github.com>
2026-08-12 18:06:34 +08:00
Hyu c3299fd1a6 fix(wizard): remove redundant Space CTA (#2416)
Co-authored-by: Chan <dadachann@users.noreply.github.com>
2026-08-12 16:56:25 +08:00
22 changed files with 60 additions and 422 deletions
-59
View File
@@ -1,59 +0,0 @@
name: Build and deploy production
on:
push:
branches: [deploy/prod]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: langbot-production
cancel-in-progress: false
env:
CORE_IMAGE: ${{ secrets.DOCKER_USERNAME }}/langbot
CLOUD_IMAGE: ${{ secrets.DOCKER_USERNAME }}/langbot-cloud-core
SPACE_REF: 58253c53933f95d81b035fbe2efedb55b6c1a82b
jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build exact Core image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.CORE_IMAGE }}:prod-${{ github.sha }}
${{ env.CORE_IMAGE }}:deploy-prod
cache-from: type=gha,scope=core-prod
cache-to: type=gha,mode=max,scope=core-prod
- name: Checkout production Cloud adapter
uses: actions/checkout@v4
with:
repository: langbot-app/langbot-space
ref: ${{ env.SPACE_REF }}
token: ${{ secrets.CLA_PAT }}
path: .space
- name: Build exact Cloud Core image
uses: docker/build-push-action@v6
with:
context: .space
file: .space/Dockerfile.cloud
push: true
build-args: LANGBOT_CORE_IMAGE=${{ env.CORE_IMAGE }}:prod-${{ github.sha }}
tags: |
${{ env.CLOUD_IMAGE }}:prod-${{ github.sha }}
${{ env.CLOUD_IMAGE }}:deploy-prod
cache-from: type=gha,scope=cloud-core-prod
cache-to: type=gha,mode=max,scope=cloud-core-prod
-97
View File
@@ -1,97 +0,0 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd /opt/langbot-cloud-prod
TAG=${1:?usage: deploy.sh prod-<40-char-sha>}
[[ "$TAG" =~ ^prod-[0-9a-f]{40}$ ]] || { echo 'invalid immutable image tag' >&2; exit 2; }
[[ -s .env ]] || { echo '/opt/langbot-cloud-prod/.env is missing' >&2; exit 3; }
rendered_compose=$(docker compose config)
grep -Fq 'LANGBOT_SPACE_CONTROL_PLANE_URL: https://space.langbot.app' <<<"$rendered_compose" || {
echo 'Cloud control-plane URL must be https://space.langbot.app' >&2
exit 4
}
grep -Fq 'SPACE__URL: https://space.langbot.app' <<<"$rendered_compose" || {
echo 'Cloud user-facing Space URL must be https://space.langbot.app' >&2
exit 5
}
grep -Eq 'LANGBOT_TELEMETRY_INGEST_TOKEN: .+' <<<"$rendered_compose" || {
echo 'Cloud telemetry ingest token must be configured' >&2
exit 6
}
update_env() {
local key=$1 value=$2
python3 - "$key" "$value" <<'PY'
from pathlib import Path
import os
import sys
path = Path('.env')
key, value = sys.argv[1:]
lines = path.read_text().splitlines()
updated = False
for index, line in enumerate(lines):
if line.startswith(f'{key}='):
lines[index] = f'{key}={value}'
updated = True
break
if not updated:
lines.append(f'{key}={value}')
temporary = Path('.env.tmp')
temporary.write_text('\n'.join(lines) + '\n')
os.chmod(temporary, 0o600)
temporary.replace(path)
PY
}
update_env LANGBOT_IMAGE_TAG "$TAG"
set -a
. ./.env
set +a
: "${CLOUD_V2_CONTROL_PLANE_TOKEN:?CLOUD_V2_CONTROL_PLANE_TOKEN is required}"
for attempt in 1 2 3 4 5; do
if docker compose pull postgres redis migrate plugin-runtime core; then
break
fi
if [ "$attempt" -eq 5 ]; then
echo "docker compose pull failed after $attempt attempts" >&2
exit 1
fi
delay=$((attempt * 10))
echo "docker compose pull failed (attempt $attempt/5); retrying in ${delay}s" >&2
sleep "$delay"
done
docker compose up -d postgres redis
for _ in $(seq 1 60); do
if docker compose exec -T postgres pg_isready -U langbot_operator -d langbot >/dev/null 2>&1; then break; fi
sleep 2
done
docker compose exec -T postgres pg_isready -U langbot_operator -d langbot >/dev/null
docker compose exec -T postgres psql -v ON_ERROR_STOP=1 -U langbot_operator -d langbot \
-v runtime_password="$POSTGRES_RUNTIME_PASSWORD" <<'SQL'
SELECT format('CREATE ROLE langbot_runtime LOGIN PASSWORD %L', :'runtime_password')
WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'langbot_runtime')\gexec
ALTER ROLE langbot_runtime PASSWORD :'runtime_password';
GRANT CONNECT ON DATABASE langbot TO langbot_runtime;
REVOKE CREATE ON SCHEMA public FROM PUBLIC, langbot_runtime;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM langbot_runtime;
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM langbot_runtime;
ALTER DEFAULT PRIVILEGES FOR ROLE langbot_operator IN SCHEMA public REVOKE ALL ON TABLES FROM langbot_runtime;
ALTER DEFAULT PRIVILEGES FOR ROLE langbot_operator IN SCHEMA public REVOKE ALL ON SEQUENCES FROM langbot_runtime;
GRANT USAGE ON SCHEMA public TO langbot_runtime;
SQL
docker compose --profile tools run --rm migrate
docker compose up -d --remove-orphans plugin-runtime core
for _ in $(seq 1 90); do
if docker compose exec -T core python -c 'import urllib.request; urllib.request.urlopen("http://127.0.0.1:5300/healthz", timeout=3)' >/dev/null 2>&1; then
docker compose ps
exit 0
fi
sleep 2
done
docker compose logs --tail=200 core plugin-runtime >&2
exit 1
-162
View File
@@ -1,162 +0,0 @@
services:
postgres:
image: pgvector/pgvector:pg17
container_name: langbot-cloud-postgres
restart: unless-stopped
environment:
POSTGRES_DB: langbot
POSTGRES_USER: langbot_operator
POSTGRES_PASSWORD: ${POSTGRES_OPERATOR_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: [CMD-SHELL, "pg_isready -U langbot_operator -d langbot"]
interval: 5s
timeout: 5s
retries: 30
networks: [internal]
redis:
image: redis:7.4-alpine
container_name: langbot-cloud-redis
restart: unless-stopped
command: [redis-server, --appendonly, "yes", --requirepass, "${REDIS_PASSWORD}"]
volumes:
- redis-data:/data
healthcheck:
test: [CMD-SHELL, "redis-cli -a \"$${REDIS_PASSWORD}\" ping | grep PONG"]
interval: 5s
timeout: 5s
retries: 20
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
networks: [internal]
migrate:
image: rockchin/langbot-cloud-core:${LANGBOT_IMAGE_TAG}
profiles: [tools]
command: [uv, run, langbot, migrate, --cloud]
environment: &core-env
TZ: Asia/Shanghai
SYSTEM__INSTANCE_ID: ${CLOUD_V2_INSTANCE_UUID}
SYSTEM__EDITION: cloud
SYSTEM__RECOVERY_KEY: ${SYSTEM_RECOVERY_KEY}
SYSTEM__JWT__SECRET: ${JWT_SECRET}
SYSTEM__LIMITATION__MAX_BOTS: "2"
SYSTEM__LIMITATION__MAX_PIPELINES: "3"
SYSTEM__LIMITATION__MAX_EXTENSIONS: "3"
SYSTEM__LIMITATION__MAX_KNOWLEDGE_BASES: "2"
API__WEBHOOK_PREFIX: https://cloud.langbot.app
API__WEBUI_URL: https://cloud.langbot.app
WORKSPACE__INVITATIONS__PUBLIC_WEB_URL: https://cloud.langbot.app
DATABASE__USE: postgresql
DATABASE__POSTGRESQL__URL: postgresql+asyncpg://langbot_runtime:${POSTGRES_RUNTIME_PASSWORD}@postgres:5432/langbot
DATABASE__CLOUD_MIGRATION__OPERATOR_DSN_ENV: LANGBOT_CLOUD_MIGRATION_DSN
LANGBOT_CLOUD_MIGRATION_DSN: postgresql://langbot_operator:${POSTGRES_OPERATOR_PASSWORD}@postgres:5432/langbot
VDB__USE: pgvector
VDB__PGVECTOR__USE_BUSINESS_DATABASE: "true"
VDB__PGVECTOR__ALLOWED_DIMENSIONS: "384,512,768,1024,1536"
PLUGIN__ENABLE: "true"
PLUGIN__RUNTIME_WS_URL: ws://plugin-runtime:5400/control/ws
PLUGIN__DISPLAY_PLUGIN_DEBUG_URL: wss://cloud.langbot.app/plugin/debug/ws
PLUGIN__WORKER__MAX_CPUS: "0.25"
PLUGIN__WORKER__MAX_MEMORY_MB: "256"
PLUGIN__WORKER__MAX_PIDS: "128"
PLUGIN__WORKER__MAX_WORKERS: "16"
PLUGIN__WORKER__MAX_TOTAL_CPUS: "4.0"
PLUGIN__WORKER__MAX_TOTAL_MEMORY_MB: "4096"
PLUGIN__WORKER__REQUIRE_HARD_LIMITS: "true"
LANGBOT_PLUGIN_RUNTIME_CONTROL_TOKEN: ${PLUGIN_RUNTIME_CONTROL_TOKEN}
# Cloud v2 currently grants no managed Box capability. Keep the shared
# runtime deployed but disable Core integration until a hard-quota-capable
# backend can satisfy the fail-closed Cloud readiness contract.
BOX__ENABLED: "false"
BOX__BACKEND: nsjail
BOX__RUNTIME__ENDPOINT: ws://box:5410
BOX__ADMISSION__REQUIRED: "true"
BOX__ADMISSION__LOGICAL_SESSION_ID: global
BOX__ADMISSION__REQUIRED_BACKEND: nsjail
BOX__ADMISSION__MAX_SESSIONS: "1"
BOX__ADMISSION__MAX_MANAGED_PROCESSES: "0"
BOX__ADMISSION__CPUS: "0.25"
BOX__ADMISSION__MEMORY_MB: "256"
BOX__ADMISSION__WORKSPACE_QUOTA_MB: "256"
BOX__LOCAL__HOST_ROOT: /app/data/box
BOX__LOCAL__DEFAULT_WORKSPACE: /app/data/box
BOX__LOCAL__ALLOWED_MOUNT_ROOTS: /app/data/box
LANGBOT_BOX_CONTROL_TOKEN: ${BOX_CONTROL_TOKEN}
MCP__STDIO__ENABLED: "false"
LANGBOT_SPACE_CONTROL_PLANE_URL: https://space.langbot.app
LANGBOT_SPACE_CONTROL_PLANE_TOKEN: ${CLOUD_V2_CONTROL_PLANE_TOKEN}
LANGBOT_TELEMETRY_INGEST_TOKEN: ${CLOUD_V2_CONTROL_PLANE_TOKEN}
LANGBOT_SPACE_CONTROL_PLANE_PUBLIC_KEY: ${CLOUD_V2_MANIFEST_PUBLIC_KEY}
LANGBOT_SPACE_CONTROL_PLANE_KEY_ID: ${CLOUD_V2_MANIFEST_KEY_ID}
SPACE__URL: https://space.langbot.app
depends_on:
postgres: {condition: service_healthy}
networks: [internal]
plugin-runtime:
image: rockchin/langbot:${LANGBOT_IMAGE_TAG}
container_name: langbot-cloud-plugin-runtime
restart: unless-stopped
command: [uv, run, python, -m, langbot_plugin.cli.__init__, rt]
environment:
LANGBOT_PLUGIN_RUNTIME_CONTROL_TOKEN: ${PLUGIN_RUNTIME_CONTROL_TOKEN}
volumes:
- plugin-data:/app/data
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroup: host
privileged: true
expose: ["5400"]
networks: [internal]
box:
image: rockchin/langbot:${LANGBOT_IMAGE_TAG}
container_name: langbot-cloud-box
restart: unless-stopped
command: [uv, run, lbp, box, --host, 0.0.0.0, --ws-control-port, "5410"]
environment:
LANGBOT_BOX_CONTROL_TOKEN: ${BOX_CONTROL_TOKEN}
LANGBOT_BOX_ROOT: /app/data/box
volumes:
- box-data:/app/data/box
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroup: host
privileged: true
expose: ["5410"]
networks: [internal]
core:
image: rockchin/langbot-cloud-core:${LANGBOT_IMAGE_TAG}
container_name: langbot-cloud-core
restart: unless-stopped
environment: *core-env
volumes:
- core-data:/app/data
- box-data:/app/data/box
depends_on:
postgres: {condition: service_healthy}
redis: {condition: service_healthy}
plugin-runtime: {condition: service_started}
box: {condition: service_started}
expose: ["5300"]
healthcheck:
test: [CMD-SHELL, "python -c 'import urllib.request; urllib.request.urlopen(\"http://127.0.0.1:5300/healthz\", timeout=3)'" ]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
networks: [internal, shared-network]
networks:
internal:
shared-network:
external: true
volumes:
postgres-data:
redis-data:
plugin-data:
box-data:
core-data:
+1 -1
View File
@@ -71,7 +71,7 @@ dependencies = [
"chromadb>=1.0.0,<2.0.0",
"qdrant-client (>=1.15.1,<2.0.0)",
"pyseekdb==1.1.0.post3",
"langbot-plugin @ git+https://github.com/langbot-app/langbot-plugin-sdk.git@7b559da430a50f80a7d30c9d3d66f088503ddbb3",
"langbot-plugin @ git+https://github.com/langbot-app/langbot-plugin-sdk.git@555a58e5db3de28e977b08dd4cd116b332848a19",
"asyncpg>=0.30.0",
"line-bot-sdk>=3.19.0",
"matrix-nio>=0.25.2",
+4
View File
@@ -368,6 +368,10 @@ class BoxRuntimeConnector(ManagedRuntimeConnector):
if not self._control_token and allow_generate:
self._control_token = secrets.token_urlsafe(48)
if not self._control_token:
if getattr(getattr(self.ap, 'deployment', None), 'mode', 'oss') == 'cloud':
raise BoxRuntimeUnavailableError(
f'{BOX_CONTROL_TOKEN_ENV} must be configured with a strong shared secret for a Cloud Box runtime'
)
return ''
try:
self._control_token = validate_control_token(self._control_token)
+4
View File
@@ -249,6 +249,10 @@ class Application:
{},
)
),
'plugin_runtime_connected': bool(
self.plugin_connector is not None
and getattr(self.plugin_connector, '_runtime_available', lambda: False)()
),
}
mcp_loader = getattr(self.tool_mgr, 'mcp_tool_loader', None)
runtime_stats.update(
+5
View File
@@ -264,6 +264,11 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
if not self._control_token and allow_generate:
self._control_token = secrets.token_urlsafe(48)
if not self._control_token:
if self.runtime_profile == 'shared':
raise PluginRuntimeNotConnectedError(
f'{PLUGIN_RUNTIME_CONTROL_TOKEN_ENV} must be configured with a strong shared secret '
'for a Cloud Plugin Runtime'
)
return {}
try:
self._control_token = validate_runtime_secret(
+1 -1
View File
@@ -1579,7 +1579,7 @@ class RuntimeConnectionHandler(handler.Handler):
return await self.call_action(
LangBotToRuntimeAction.RECONCILE_PLUGIN_INSTALLATIONS,
request.model_dump(),
timeout=120,
timeout=300,
)
async def apply_plugin_installation(
+10 -1
View File
@@ -24,7 +24,7 @@ from langbot.pkg.box.connector import BoxRuntimeConnector
_CONTROL_TOKEN = 'box-control-token-that-is-longer-than-32-bytes'
def make_app(logger: Mock, runtime_endpoint: str = ''):
def make_app(logger: Mock, runtime_endpoint: str = '', *, cloud: bool = False):
return SimpleNamespace(
logger=logger,
workspace_service=SimpleNamespace(instance_uuid='instance-a'),
@@ -42,6 +42,7 @@ def make_app(logger: Mock, runtime_endpoint: str = ''):
}
}
),
deployment=SimpleNamespace(mode='cloud' if cloud else 'oss'),
)
@@ -315,6 +316,14 @@ def test_external_box_runtime_control_headers_are_tokenless_when_secret_is_unset
assert connector.get_control_headers() == {BOX_INSTANCE_HEADER: 'instance-a'}
def test_cloud_box_runtime_rejects_missing_control_secret(monkeypatch: pytest.MonkeyPatch):
monkeypatch.delenv(BOX_CONTROL_TOKEN_ENV, raising=False)
connector = BoxRuntimeConnector(make_app(Mock(), runtime_endpoint='http://box-runtime:5410', cloud=True))
with pytest.raises(BoxRuntimeUnavailableError, match=BOX_CONTROL_TOKEN_ENV):
connector.get_control_headers()
def test_external_box_runtime_rejects_invalid_configured_control_token(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv(BOX_CONTROL_TOKEN_ENV, 'too-short')
connector = BoxRuntimeConnector(make_app(Mock(), runtime_endpoint='http://box-runtime:5410'))
+5 -1
View File
@@ -87,7 +87,10 @@ async def test_runtime_resource_stats_are_aggregate_and_constant_time() -> None:
app.platform_mgr = SimpleNamespace(_bots_by_key={})
app.pipeline_mgr = SimpleNamespace(_pipelines_by_key={})
app.rag_mgr = SimpleNamespace(knowledge_bases={})
app.plugin_connector = SimpleNamespace(_known_desired_states={'installation': object()})
app.plugin_connector = SimpleNamespace(
_known_desired_states={'installation': object()},
_runtime_available=lambda: True,
)
app.persistence_mgr = SimpleNamespace(
get_resource_stats=lambda: {
'configured_capacity': 20,
@@ -140,3 +143,4 @@ async def test_runtime_resource_stats_are_aggregate_and_constant_time() -> None:
}
assert stats['models']['providers'] == 1
assert stats['runtimes']['plugin_installations'] == 1
assert stats['runtimes']['plugin_runtime_connected'] is True
+10 -1
View File
@@ -15,7 +15,7 @@ from langbot_plugin.runtime.security import (
)
def make_connector() -> PluginRuntimeConnector:
def make_connector(*, cloud: bool = False) -> PluginRuntimeConnector:
app = SimpleNamespace(
logger=Mock(),
instance_config=SimpleNamespace(
@@ -34,6 +34,7 @@ def make_connector() -> PluginRuntimeConnector:
'space': {'url': ''},
}
),
deployment=SimpleNamespace(mode='cloud' if cloud else 'oss'),
)
return PluginRuntimeConnector(app, AsyncMock())
@@ -332,6 +333,14 @@ def test_external_runtime_control_headers_are_empty_when_secret_is_unset(monkeyp
assert connector._control_headers(allow_generate=False) == {}
def test_cloud_runtime_rejects_missing_control_secret(monkeypatch):
monkeypatch.delenv(PLUGIN_RUNTIME_CONTROL_TOKEN_ENV, raising=False)
connector = make_connector(cloud=True)
with pytest.raises(PluginRuntimeNotConnectedError, match=PLUGIN_RUNTIME_CONTROL_TOKEN_ENV):
connector._control_headers(allow_generate=False)
def test_local_runtime_control_headers_generate_ephemeral_secret(monkeypatch):
monkeypatch.delenv(PLUGIN_RUNTIME_CONTROL_TOKEN_ENV, raising=False)
connector = make_connector()
+16 -2
View File
@@ -9,8 +9,8 @@ from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, Mock
import pytest
from langbot_plugin.entities.io.actions.enums import PluginToRuntimeAction
from langbot_plugin.entities.io.context import ActionContext, InstallationBinding
from langbot_plugin.entities.io.actions.enums import LangBotToRuntimeAction, PluginToRuntimeAction
from langbot_plugin.entities.io.context import ActionContext, InstallationBinding, PluginInstallationDesiredState
def make_handler(app):
@@ -67,6 +67,20 @@ def make_handler(app):
return runtime_handler
@pytest.mark.asyncio
async def test_reconcile_plugin_installations_allows_cloud_cold_start_to_finish():
app = SimpleNamespace()
runtime_handler = make_handler(app)
runtime_handler.call_action = AsyncMock(return_value={})
binding = next(iter(runtime_handler._installation_bindings.values()))[0]
desired = PluginInstallationDesiredState(binding=binding, enabled=True)
await runtime_handler.reconcile_plugin_installations((desired,))
assert runtime_handler.call_action.await_args.args[0] == LangBotToRuntimeAction.RECONCILE_PLUGIN_INSTALLATIONS
assert runtime_handler.call_action.await_args.kwargs['timeout'] == 300
class TestHandlerQueryVariables:
"""Tests for handler query variable logic."""
Generated
+4 -4
View File
@@ -1,5 +1,5 @@
version = 1
revision = 2
revision = 3
requires-python = ">=3.11, <4.0"
resolution-markers = [
"python_full_version >= '3.14' and sys_platform == 'win32'",
@@ -2125,7 +2125,7 @@ requires-dist = [
{ name = "ebooklib", specifier = ">=0.18" },
{ name = "gewechat-client", specifier = ">=0.1.5" },
{ name = "html2text", specifier = ">=2024.2.26" },
{ name = "langbot-plugin", git = "https://github.com/langbot-app/langbot-plugin-sdk.git?rev=7b559da430a50f80a7d30c9d3d66f088503ddbb3" },
{ name = "langbot-plugin", git = "https://github.com/langbot-app/langbot-plugin-sdk.git?rev=555a58e5db3de28e977b08dd4cd116b332848a19" },
{ name = "langchain", specifier = ">=1.3.9" },
{ name = "langchain-core", specifier = ">=1.3.3" },
{ name = "langchain-text-splitters", specifier = ">=1.1.2" },
@@ -2191,8 +2191,8 @@ dev = [
[[package]]
name = "langbot-plugin"
version = "0.5.1"
source = { git = "https://github.com/langbot-app/langbot-plugin-sdk.git?rev=7b559da430a50f80a7d30c9d3d66f088503ddbb3#7b559da430a50f80a7d30c9d3d66f088503ddbb3" }
version = "0.5.3"
source = { git = "https://github.com/langbot-app/langbot-plugin-sdk.git?rev=555a58e5db3de28e977b08dd4cd116b332848a19#555a58e5db3de28e977b08dd4cd116b332848a19" }
dependencies = [
{ name = "aiofiles" },
{ name = "aiohttp" },
-47
View File
@@ -16,7 +16,6 @@ import {
import { httpClient } from '@/app/infra/http/HttpClient';
import {
userInfo,
systemInfo,
bootstrapWorkspaceSession,
initializeSystemInfo,
@@ -490,24 +489,6 @@ export default function WizardPage() {
t,
]);
// ---- Space auth redirect ----
const handleSpaceAuth = useCallback(async () => {
try {
const callbackUrl = `${window.location.origin}/auth/space/callback`;
const resp = await httpClient.getSpaceAuthorizeUrl(callbackUrl);
window.location.href = resp.authorize_url;
} catch (err) {
console.error('Failed to get space authorize URL', err);
toast.error(t('wizard.spaceAuthError'));
}
}, [t]);
// ---- Check if local account ----
// Re-evaluated after remote data fetch (when userInfo is populated)
const isLocalAccount =
!isLoading && (!userInfo || userInfo.account_type === 'local');
// ---- Skip handler ----
const [showSkipConfirm, setShowSkipConfirm] = useState(false);
const [isSkipping, setIsSkipping] = useState(false);
@@ -662,8 +643,6 @@ export default function WizardPage() {
runnerOptions={runnerOptions}
selected={selectedRunner}
onSelect={handleSelectRunner}
isLocalAccount={isLocalAccount}
onSpaceAuth={handleSpaceAuth}
runnerConfigItems={selectedRunnerConfigItems}
runnerConfigValues={runnerConfig}
onRunnerConfigChange={setRunnerConfig}
@@ -1006,8 +985,6 @@ function StepAIEngine({
runnerOptions,
selected,
onSelect,
isLocalAccount,
onSpaceAuth,
runnerConfigItems,
runnerConfigValues,
onRunnerConfigChange,
@@ -1015,8 +992,6 @@ function StepAIEngine({
runnerOptions: { name: string; label: { en_US: string; zh_Hans: string } }[];
selected: string | null;
onSelect: (name: string) => void;
isLocalAccount: boolean;
onSpaceAuth: () => void;
runnerConfigItems: IDynamicFormItemSchema[];
runnerConfigValues: Record<string, unknown>;
onRunnerConfigChange: (v: Record<string, unknown>) => void;
@@ -1127,28 +1102,6 @@ function StepAIEngine({
</Card>
);
})}
{/* Space promotion banner */}
{selected === 'local-agent' && isLocalAccount && (
<div className="animate-in fade-in slide-in-from-left-2 duration-300">
<div className="relative rounded-lg p-[2px] bg-gradient-to-r from-purple-500 via-pink-500 to-orange-500">
<div className="rounded-[calc(0.5rem-2px)] bg-background p-3 flex flex-col items-center gap-2 text-center">
<Sparkles className="w-6 h-6 text-purple-500 shrink-0" />
<p className="text-xs font-medium">
{t('wizard.spaceBanner.message')}
</p>
<Button
variant="outline"
size="sm"
onClick={onSpaceAuth}
className="w-full"
>
{t('wizard.spaceBanner.action')}
</Button>
</div>
</div>
</div>
)}
</div>
</div>
-6
View File
@@ -1807,7 +1807,6 @@ const enUS = {
botCreateSuccess: 'Bot created successfully!',
botSaveSuccess: 'Bot configuration saved and enabled!',
createError: 'Failed to create resources',
spaceAuthError: 'Failed to initiate Space authorization',
skipSaveError: 'Failed to save skip status. Please try again.',
completeSaveError: 'Failed to save completion status. Please try again.',
step: {
@@ -1836,11 +1835,6 @@ const enUS = {
description:
"Choose the AI engine that will power your bot's intelligence.",
},
spaceBanner: {
message:
'Connect to LangBot Space for free trial model credits and zero-config instant setup!',
action: 'Authorize with Space',
},
config: {
botInfo: 'Bot Information',
botNamePlaceholder: 'Enter bot name',
-6
View File
@@ -1672,7 +1672,6 @@ const esES = {
botCreateSuccess: '¡Bot creado correctamente!',
botSaveSuccess: '¡Configuración del Bot guardada y activada!',
createError: 'Error al crear los recursos',
spaceAuthError: 'Error al iniciar la autorización de Space',
skipSaveError:
'Error al guardar el estado de omisión. Por favor, inténtalo de nuevo.',
completeSaveError:
@@ -1705,11 +1704,6 @@ const esES = {
description:
'Elige el motor de IA que impulsará la inteligencia de tu Bot.',
},
spaceBanner: {
message:
'¡Conéctate a LangBot Space para obtener créditos de prueba gratuitos y configuración instantánea sin esfuerzo!',
action: 'Autorizar con Space',
},
config: {
botInfo: 'Información del Bot',
botNamePlaceholder: 'Introduce el nombre del Bot',
-6
View File
@@ -1722,7 +1722,6 @@ const jaJP = {
botCreateSuccess: 'ボットが正常に作成されました!',
botSaveSuccess: 'ボット設定が保存され、有効になりました!',
createError: 'リソースの作成に失敗しました',
spaceAuthError: 'Space 認証の開始に失敗しました',
skipSaveError: 'スキップ状態の保存に失敗しました。もう一度お試しください。',
completeSaveError: '完了状態の保存に失敗しました。もう一度お試しください。',
step: {
@@ -1753,11 +1752,6 @@ const jaJP = {
description:
'ボットのインテリジェンスを駆動するAIエンジンを選択してください。',
},
spaceBanner: {
message:
'LangBot Spaceに接続して、無料トライアルモデルクレジットとゼロ設定の即時セットアップを入手!',
action: 'Spaceで認証',
},
config: {
botInfo: 'ボット情報',
botNamePlaceholder: 'ボット名を入力',
-6
View File
@@ -1645,7 +1645,6 @@ const ruRU = {
botCreateSuccess: 'Бот успешно создан!',
botSaveSuccess: 'Конфигурация бота сохранена и включена!',
createError: 'Не удалось создать ресурсы',
spaceAuthError: 'Не удалось инициировать авторизацию через Space',
skipSaveError: 'Не удалось сохранить статус пропуска. Повторите попытку.',
completeSaveError:
'Не удалось сохранить статус завершения. Повторите попытку.',
@@ -1675,11 +1674,6 @@ const ruRU = {
description:
'Выберите ИИ-движок, который будет управлять интеллектом вашего бота.',
},
spaceBanner: {
message:
'Подключитесь к LangBot Space для бесплатных пробных кредитов и мгновенной настройки!',
action: 'Авторизация через Space',
},
config: {
botInfo: 'Информация о боте',
botNamePlaceholder: 'Введите имя бота',
-6
View File
@@ -1612,7 +1612,6 @@ const thTH = {
botCreateSuccess: 'สร้าง Bot สำเร็จ!',
botSaveSuccess: 'บันทึกและเปิดใช้งาน Bot สำเร็จ!',
createError: 'ไม่สามารถสร้างทรัพยากรได้',
spaceAuthError: 'ไม่สามารถเริ่มต้นการยืนยันสิทธิ์ Space ได้',
skipSaveError: 'ไม่สามารถบันทึกสถานะการข้ามได้ กรุณาลองใหม่',
completeSaveError: 'ไม่สามารถบันทึกสถานะการเสร็จสิ้นได้ กรุณาลองใหม่',
step: {
@@ -1640,11 +1639,6 @@ const thTH = {
title: 'เลือกเครื่องมือ AI',
description: 'เลือกเครื่องมือ AI ที่จะขับเคลื่อนความฉลาดของ Bot',
},
spaceBanner: {
message:
'เชื่อมต่อกับ LangBot Space เพื่อรับเครดิตทดลองใช้โมเดลฟรีและตั้งค่าทันทีโดยไม่ต้องกำหนดค่า!',
action: 'ยืนยันสิทธิ์กับ Space',
},
config: {
botInfo: 'ข้อมูล Bot',
botNamePlaceholder: 'กรอกชื่อ Bot',
-6
View File
@@ -1638,7 +1638,6 @@ const viVN = {
botCreateSuccess: 'Tạo Bot thành công!',
botSaveSuccess: 'Cấu hình Bot đã lưu và bật!',
createError: 'Tạo tài nguyên thất bại',
spaceAuthError: 'Khởi tạo ủy quyền Space thất bại',
skipSaveError: 'Lưu trạng thái bỏ qua thất bại. Vui lòng thử lại.',
completeSaveError: 'Lưu trạng thái hoàn tất thất bại. Vui lòng thử lại.',
step: {
@@ -1666,11 +1665,6 @@ const viVN = {
title: 'Chọn công cụ AI',
description: 'Chọn công cụ AI sẽ cung cấp trí tuệ cho Bot của bạn.',
},
spaceBanner: {
message:
'Kết nối với LangBot Space để nhận tín dụng dùng thử mô hình miễn phí và thiết lập tức thì không cần cấu hình!',
action: 'Ủy quyền với Space',
},
config: {
botInfo: 'Thông tin Bot',
botNamePlaceholder: 'Nhập tên Bot',
-5
View File
@@ -1730,7 +1730,6 @@ const zhHans = {
botCreateSuccess: '机器人创建成功!',
botSaveSuccess: '机器人配置已保存并启用!',
createError: '创建资源失败',
spaceAuthError: '无法发起 Space 授权',
skipSaveError: '保存跳过状态失败,请重试。',
completeSaveError: '保存完成状态失败,请重试。',
step: {
@@ -1756,10 +1755,6 @@ const zhHans = {
title: '选择 AI 引擎',
description: '选择驱动机器人智能的 AI 引擎。',
},
spaceBanner: {
message: '接入 LangBot Space,获取免费试用模型额度,零配置极速开箱!',
action: '前往授权登录',
},
config: {
botInfo: '机器人信息',
botNamePlaceholder: '请输入机器人名称',
-5
View File
@@ -1566,7 +1566,6 @@ const zhHant = {
botCreateSuccess: '機器人建立成功!',
botSaveSuccess: '機器人配置已儲存並啟用!',
createError: '建立資源失敗',
spaceAuthError: '無法發起 Space 授權',
skipSaveError: '儲存跳過狀態失敗,請重試。',
completeSaveError: '儲存完成狀態失敗,請重試。',
step: {
@@ -1592,10 +1591,6 @@ const zhHant = {
title: '選擇 AI 引擎',
description: '選擇驅動機器人智慧的 AI 引擎。',
},
spaceBanner: {
message: '接入 LangBot Space,取得免費試用模型額度,零配置極速開箱!',
action: '前往授權登入',
},
config: {
botInfo: '機器人資訊',
botNamePlaceholder: '請輸入機器人名稱',