mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-11-01 13:25:18 +08:00
chore(merge): sync master into interactive cards
This commit is contained in:
@@ -12,3 +12,15 @@ __pycache__
|
||||
web/.next
|
||||
web/node_modules
|
||||
web/.env
|
||||
|
||||
# Documentation site dependencies and generated output
|
||||
docs/node_modules/
|
||||
docs/.next/
|
||||
docs/dist/
|
||||
docs/.fumapress/
|
||||
docs/.waku/
|
||||
docs/content/
|
||||
docs/public/
|
||||
|
||||
plugins/
|
||||
**/*.lbpkg
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/plugins export-ignore
|
||||
*.lbpkg export-ignore
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Plugin Request / 插件请求
|
||||
title: "[Plugin Request]: "
|
||||
labels: ["plugin-request"]
|
||||
description: "Request plugin or provide plugin idea / 请求特定功能插件或提供插件 idea。"
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Plugin Name / 插件名称
|
||||
description: Fill in the name of the plugin / 填写插件的名称
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Detailed description / 详细描述
|
||||
description: Detailed description, the more detailed the better / 请详细描述您所需要的插件的功能
|
||||
validations:
|
||||
required: true
|
||||
@@ -2,6 +2,9 @@ name: Build Dev Image
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "plugins/**"
|
||||
- ".github/workflows/plugins-*.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
types: [published]
|
||||
jobs:
|
||||
publish-docker-image:
|
||||
if: ${{ !startsWith(github.event.release.tag_name, 'plugins/') }}
|
||||
runs-on: ubuntu-latest
|
||||
name: Build image
|
||||
|
||||
@@ -51,4 +52,4 @@ jobs:
|
||||
if [[ "$RELEASE_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then
|
||||
tags+=(-t rockchin/langbot:beta)
|
||||
fi
|
||||
docker buildx build --build-arg LANGBOT_BUILD_REVISION=${{ github.sha }} --platform linux/arm64,linux/amd64 "${tags[@]}" . --push
|
||||
docker buildx build --build-arg LANGBOT_BUILD_REVISION=${{ github.sha }} --platform linux/arm64,linux/amd64 "${tags[@]}" . --push
|
||||
|
||||
@@ -11,6 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build-fnos-fpk:
|
||||
if: ${{ !startsWith(github.event.release.tag_name, 'plugins/') && !startsWith(github.ref, 'refs/tags/plugins/') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-artifacts:
|
||||
if: ${{ !startsWith(github.event.release.tag_name, 'plugins/') && !startsWith(github.ref, 'refs/tags/plugins/') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -37,7 +38,7 @@ jobs:
|
||||
- name: Make Temp Directory
|
||||
run: |
|
||||
mkdir -p /tmp/langbot_build_web
|
||||
cp -r . /tmp/langbot_build_web
|
||||
rsync -a --exclude='/plugins/' --exclude='*.lbpkg' . /tmp/langbot_build_web/
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
@@ -54,12 +55,16 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: langbot-${{ steps.check_version.outputs.version }}-all
|
||||
path: .
|
||||
path: |
|
||||
.
|
||||
!plugins/**
|
||||
!**/*.lbpkg
|
||||
|
||||
- name: Upload To Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_UPLOAD_GITHUB_TOKEN }}
|
||||
run: |
|
||||
# 本目录下所有文件打包成zip
|
||||
zip -r langbot-${{ steps.check_version.outputs.version }}-all.zip .
|
||||
gh release upload ${{ github.event.release.tag_name }} langbot-${{ steps.check_version.outputs.version }}-all.zip
|
||||
zip -r "$RUNNER_TEMP/langbot-${{ steps.check_version.outputs.version }}-all.zip" . -x 'plugins/*' '*.lbpkg' '.git/*'
|
||||
python3 scripts/check_core_artifacts.py "$RUNNER_TEMP/langbot-${{ steps.check_version.outputs.version }}-all.zip"
|
||||
gh release upload ${{ github.event.release.tag_name }} "$RUNNER_TEMP/langbot-${{ steps.check_version.outputs.version }}-all.zip"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Refresh Context7 docs
|
||||
|
||||
# Re-index the public documentation in Context7 whenever the English docs change.
|
||||
# Requires a repository secret CONTEXT7_API_KEY (context7.com/dashboard) and a
|
||||
# Context7 library registered for this repository; both are one-time owner steps.
|
||||
# Until the secret exists every run is a no-op instead of a failure.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- "docs/en/**"
|
||||
- "context7.json"
|
||||
- ".github/workflows/context7-refresh.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: context7-refresh
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
refresh:
|
||||
name: Refresh Context7 library
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
CONTEXT7_API_KEY: ${{ secrets.CONTEXT7_API_KEY }}
|
||||
CONTEXT7_LIBRARY: /langbot-app/langbot
|
||||
steps:
|
||||
- name: Skip when the Context7 API key is not configured
|
||||
if: env.CONTEXT7_API_KEY == ''
|
||||
run: |
|
||||
echo "CONTEXT7_API_KEY is not configured; skipping the Context7 refresh."
|
||||
echo "Add the secret and register the library, then re-run this workflow."
|
||||
|
||||
- name: Trigger Context7 refresh
|
||||
if: env.CONTEXT7_API_KEY != ''
|
||||
run: |
|
||||
set -euo pipefail
|
||||
body=$(printf '{"libraryName":"%s"}' "$CONTEXT7_LIBRARY")
|
||||
code=$(curl -sS -o "$RUNNER_TEMP/refresh.json" -w '%{http_code}' \
|
||||
-X POST https://context7.com/api/v1/refresh \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "Authorization: Bearer ${CONTEXT7_API_KEY}" \
|
||||
-d "$body")
|
||||
cat "$RUNNER_TEMP/refresh.json"
|
||||
if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then
|
||||
echo "Context7 refresh failed with HTTP $code" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Requested a refresh of ${CONTEXT7_LIBRARY}."
|
||||
@@ -0,0 +1,43 @@
|
||||
name: Core packaging boundary
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: ['plugins/**', 'src/**', 'pyproject.toml', 'MANIFEST.in', '.gitattributes', '.gitignore', '.dockerignore', 'Dockerfile', 'packaging/**', 'scripts/check_core_artifacts.py', '.github/workflows/**']
|
||||
pull_request:
|
||||
paths: ['plugins/**', 'src/**', 'pyproject.toml', 'MANIFEST.in', '.gitattributes', '.gitignore', '.dockerignore', 'Dockerfile', 'packaging/**', 'scripts/check_core_artifacts.py', '.github/workflows/**']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- name: Check repository release boundaries
|
||||
run: |
|
||||
uv run --no-project --with pyyaml python plugins/scripts/test_repository.py
|
||||
- name: Build Core sdist and wheel without installing Core dependencies
|
||||
run: |
|
||||
uv build
|
||||
python scripts/check_core_artifacts.py dist/*.whl dist/*.tar.gz
|
||||
- name: Verify Git export excludes plugin sources
|
||||
run: |
|
||||
git archive --format=tar --output="$RUNNER_TEMP/core-source.tar" HEAD
|
||||
python scripts/check_core_artifacts.py "$RUNNER_TEMP/core-source.tar"
|
||||
- name: Verify the production Docker context exclusion
|
||||
run: |
|
||||
docker build --file - --tag langbot-packaging-boundary . <<'DOCKERFILE'
|
||||
FROM scratch
|
||||
COPY . /app
|
||||
DOCKERFILE
|
||||
docker save langbot-packaging-boundary -o "$RUNNER_TEMP/core-context.tar"
|
||||
python scripts/check_core_artifacts.py --docker-archive "$RUNNER_TEMP/core-context.tar"
|
||||
@@ -46,22 +46,6 @@ jobs:
|
||||
enable-cache: true
|
||||
- name: Install locked dependencies
|
||||
run: uv sync --dev --frozen
|
||||
- name: Read official plugin source pin
|
||||
id: plugins
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
revision = json.loads(Path('tests/fixtures/legacy_plugin_identity_sources.json').read_text())['revision']
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as stream:
|
||||
stream.write(f'revision={revision}\n')
|
||||
PY
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: langbot-app/langbot-plugins
|
||||
ref: ${{ steps.plugins.outputs.revision }}
|
||||
path: .smoke-plugins
|
||||
persist-credentials: false
|
||||
- name: Prepare CLI and sandbox image
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -76,21 +60,28 @@ jobs:
|
||||
- name: Prepare real services and run the 60-second smoke
|
||||
run: |
|
||||
uv run --no-sync python tools/regression_ci.py \
|
||||
--lbctl .smoke-bin/lbctl_linux_amd64 --plugins .smoke-plugins --output .qa/core-smoke
|
||||
--lbctl .smoke-bin/lbctl_linux_amd64 --plugins plugins --output .qa/core-smoke
|
||||
- name: Publish gate result
|
||||
if: always()
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
path = Path('.qa/core-smoke/gate/gate.json')
|
||||
root = Path('.qa/core-smoke')
|
||||
path = root / 'gate/gate.json'
|
||||
manifest = root / 'plugins/manifest.json'
|
||||
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as out:
|
||||
if manifest.exists():
|
||||
for plugin in json.loads(manifest.read_text())['plugins']:
|
||||
out.write(f"Plugin: `{plugin['id']}@{plugin['version']}`; installation readback: `{plugin.get('installed', 'not confirmed')}`.\n\n")
|
||||
if path.exists():
|
||||
data = json.loads(path.read_text())
|
||||
out.write(f"Core smoke: {'PASS' if data['passed'] else 'FAIL'}; "
|
||||
f"{data.get('elapsed_seconds', 0):.2f}/60 seconds; {data.get('requests', 0)} requests.\n")
|
||||
if data.get('infrastructure_error'):
|
||||
out.write('Preparation or teardown failed; inspect service logs.\n')
|
||||
for error in data.get('coverage_errors', []):
|
||||
out.write(f"Coverage evidence error: {error}\n")
|
||||
out.write('Service/dependency preparation is outside the 60-second execution budget.\n')
|
||||
else:
|
||||
out.write('Core smoke did not produce a verdict; inspect preparation failure.\n')
|
||||
@@ -108,6 +99,12 @@ jobs:
|
||||
.qa/core-smoke/gate/suite/summary.json
|
||||
.qa/core-smoke/gate/suite/health.json
|
||||
.qa/core-smoke/gate/suite/cleanup-checks.json
|
||||
.qa/core-smoke/gate/suite/coverage.json
|
||||
.qa/core-smoke/gate/suite/coverage.md
|
||||
.qa/core-smoke/gate/suite/cycles.json
|
||||
.qa/core-smoke/gate/suite/execution.json
|
||||
.qa/core-smoke/plugins/manifest.json
|
||||
.qa/core-smoke/plugins/*-build.log
|
||||
.qa/core-smoke/*.log
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
name: Daily Endurance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 03:23 Asia/Shanghai; GitHub schedules use UTC and may start late.
|
||||
- cron: '23 19 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
scenario_groups:
|
||||
description: 'Optional local groups, space separated; empty runs only base cases'
|
||||
type: string
|
||||
default: 'faults resources lifecycle plugins'
|
||||
extra_plugins:
|
||||
description: 'Additional source plugin paths; clear with the plugins group to run only base plugins'
|
||||
type: string
|
||||
default: 'misc/EssentialCommands misc/FAQManager'
|
||||
restart_services:
|
||||
description: 'Check owned Core and Runtime restarts once before resource sampling'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: daily-endurance-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
endurance:
|
||||
name: Daily regression (30m)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Install locked dependencies
|
||||
run: uv sync --dev --frozen
|
||||
- name: Prepare CLI and sandbox image
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
mkdir -p .endurance-bin
|
||||
gh release download v0.3.0 --repo langbot-app/langbot-cli --dir .endurance-bin \
|
||||
--pattern lbctl_linux_amd64 --pattern checksums.txt
|
||||
cd .endurance-bin
|
||||
sha256sum --check --ignore-missing checksums.txt
|
||||
chmod +x lbctl_linux_amd64
|
||||
docker pull python:3.12-alpine
|
||||
- name: Run repeated scenarios and recovery checks
|
||||
env:
|
||||
SCENARIO_GROUPS: ${{ github.event_name == 'schedule' && 'faults resources lifecycle plugins' || inputs.scenario_groups }}
|
||||
EXTRA_PLUGINS: ${{ github.event_name == 'schedule' && 'misc/EssentialCommands misc/FAQManager' || inputs.extra_plugins }}
|
||||
RESTART_SERVICES: ${{ github.event_name == 'schedule' || inputs.restart_services }}
|
||||
run: |
|
||||
read -r -a groups <<< "$SCENARIO_GROUPS"
|
||||
read -r -a extras <<< "$EXTRA_PLUGINS"
|
||||
args=()
|
||||
if [[ "$RESTART_SERVICES" == true ]]; then args+=(--restart-services); fi
|
||||
uv run --no-sync python tools/regression_ci.py \
|
||||
--suite daily --scenario-groups "${groups[@]}" "${args[@]}" \
|
||||
--lbctl .endurance-bin/lbctl_linux_amd64 --plugins plugins --extra-plugins "${extras[@]}" --output .qa/daily
|
||||
- name: Publish observations
|
||||
if: always()
|
||||
run: |
|
||||
python - <<'PY_SUMMARY'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
|
||||
root = Path('.qa/daily')
|
||||
def read(path):
|
||||
source = root / path
|
||||
return json.loads(source.read_text()) if source.exists() else {}
|
||||
|
||||
gate = read('gate/gate.json')
|
||||
suite = read('gate/suite/summary.json')
|
||||
manifest = read('plugins/manifest.json')
|
||||
coverage = read('gate/suite/coverage.json')
|
||||
passed = gate.get('passed') is True and suite.get('passed') is True
|
||||
outer_failed = any(gate.get(key) for key in ('infrastructure_error', 'timed_out', 'resource_errors', 'coverage_errors'))
|
||||
outcome = 'PASS' if passed else 'INCOMPLETE' if suite.get('outcome') == 'INCOMPLETE' and not outer_failed else 'FAIL / incomplete'
|
||||
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as out:
|
||||
out.write(f"Daily regression: {outcome}\n\nCore: `{os.environ['GITHUB_SHA']}`.\n\n")
|
||||
out.write('One 1800-second scenario/resource budget, including 120 seconds reserved for drain/cleanup and 60 seconds for cooldown. The outer process guard is 1860 seconds; deployment and plugin installation precede the scenario clock.\n\n')
|
||||
out.write('Real Core/Runtime/source plugins/Box; mock generation and embedding. Throughput/P95 are observations; business, health and resource failures still fail this non-required job.\n\n')
|
||||
out.write(f"Enabled local groups: {gate.get('scenario_groups', 'not reported')}.\n\n")
|
||||
restart = gate.get('restart_checks')
|
||||
if restart:
|
||||
out.write(f"Owned-service restart checks: {restart.get('outcome', 'not reported')}; {restart.get('elapsed_seconds', 0):.2f}s, deducted from the shared budget. Resource baselines begin after restarts.\n\n")
|
||||
for error in restart.get('errors', []):
|
||||
out.write(f"Restart check error: {error}\n")
|
||||
out.write(f"Plugin selection: `{manifest.get('selection', 'not reported')}`.\n\n")
|
||||
for plugin in manifest.get('plugins', []):
|
||||
out.write(f"Plugin: `{plugin['id']}@{plugin['version']}`; installation readback: `{plugin.get('installed', 'not confirmed')}`.\n\n")
|
||||
out.write(f"Required first pass complete: {suite.get('first_pass_complete', 'not reported')}; cycles: {len(suite.get('cycles', []))}; requests: {suite.get('requests', 'not reported')}.\n\n")
|
||||
out.write(f"Final ledger reconciliation: {suite.get('ledger_closeout', {}).get('matched', 'not reported')}.\n\n")
|
||||
data = suite.get('test_data', {})
|
||||
out.write(f"Observed conversations: {data.get('observed_conversations', 'not reported')}; AgentRuns: {data.get('observed_agent_runs', 'not reported')}; group sessions: {data.get('group_sessions', 'not reported')}; owned API resources: {data.get('owned_api_resources', 'not reported')}.\n\n")
|
||||
out.write('One Workspace; each pipeline reuses one Box per CI run. This does not test cross-group Box file isolation or tenant isolation.\n\n')
|
||||
observations = suite.get('observations', {})
|
||||
if 'successful_requests_per_second' in observations:
|
||||
out.write(f"Successful throughput: {observations['successful_requests_per_second']:.3f} RPS.\n\n")
|
||||
if observations.get('step_p95_seconds'):
|
||||
out.write('| Case | Full-step P95 (s) |\n|---|---:|\n')
|
||||
for name, p95 in observations['step_p95_seconds'].items():
|
||||
out.write(f"| {name} | {p95} |\n")
|
||||
for kind, values in coverage.get('kinds', {}).items():
|
||||
out.write(f"\n{kind} behavior evidence: {values['decided']} / {values['total']}; statuses: {values['status']}.\n")
|
||||
if suite.get('resources', {}).get('targets'):
|
||||
out.write('\n| Resource | Metric | Retained growth | Allowance |\n|---|---|---:|---:|\n')
|
||||
for target, metrics in suite['resources']['targets'].items():
|
||||
for metric, values in metrics.items():
|
||||
out.write(f"| {target} | {metric} | {values.get('growth', 'unavailable')} | {values.get('allowance', 'unavailable')} |\n")
|
||||
errors = list(suite.get('errors', [])) + list(gate.get('resource_errors', [])) + list(gate.get('coverage_errors', []))
|
||||
if gate.get('timed_out'):
|
||||
errors.append('Suite exceeded its 1860-second outer process guard.')
|
||||
if gate.get('infrastructure_error'):
|
||||
errors.append(gate['infrastructure_error'])
|
||||
if not gate or not suite:
|
||||
errors.append('Missing verdict; inspect preparation and service logs.')
|
||||
for error in errors:
|
||||
out.write(f"\n- {error}\n")
|
||||
PY_SUMMARY
|
||||
- name: Preserve verdicts and observations
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: daily-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
.qa/daily/gate/gate.json
|
||||
.qa/daily/gate/resources.jsonl
|
||||
.qa/daily/gate/suite.log
|
||||
.qa/daily/gate/suite/summary.json
|
||||
.qa/daily/gate/suite/cases.json
|
||||
.qa/daily/gate/suite/health.json
|
||||
.qa/daily/gate/suite/cleanup-checks.json
|
||||
.qa/daily/gate/suite/resources.jsonl
|
||||
.qa/daily/gate/suite/cycles.json
|
||||
.qa/daily/gate/suite/execution.json
|
||||
.qa/daily/gate/suite/coverage.json
|
||||
.qa/daily/gate/suite/coverage.md
|
||||
.qa/daily/plugins/manifest.json
|
||||
.qa/daily/plugins/*-build.log
|
||||
.qa/daily/restart-checks/profile.json
|
||||
.qa/daily/restart-checks/cases.json
|
||||
.qa/daily/restart-checks/summary.json
|
||||
.qa/daily/restart-checks/coverage.json
|
||||
.qa/daily/restart-checks/coverage.md
|
||||
.qa/daily/restart-checks/cleanup-checks.json
|
||||
.qa/daily/restart-checks/owned.json
|
||||
.qa/daily/*.log
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
|
||||
api-contracts:
|
||||
name: Daily API contracts
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Install locked dependencies
|
||||
run: uv sync --dev --frozen
|
||||
- name: Prepare CLI and sandbox image
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
mkdir -p .api-bin
|
||||
gh release download v0.3.0 --repo langbot-app/langbot-cli --dir .api-bin \
|
||||
--pattern lbctl_linux_amd64 --pattern checksums.txt
|
||||
cd .api-bin
|
||||
sha256sum --check --ignore-missing checksums.txt
|
||||
chmod +x lbctl_linux_amd64
|
||||
docker pull python:3.12-alpine
|
||||
- name: Run API contracts
|
||||
run: |
|
||||
uv run --no-sync python tools/regression_ci.py \
|
||||
--suite api --api-seed "$GITHUB_RUN_ID" \
|
||||
--lbctl .api-bin/lbctl_linux_amd64 --plugins plugins --output .qa/api
|
||||
- name: Publish API result
|
||||
if: always()
|
||||
run: |
|
||||
python - <<'PY_SUMMARY'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
|
||||
root = Path('.qa/api')
|
||||
def read(path):
|
||||
source = root / path
|
||||
return json.loads(source.read_text()) if source.exists() else {}
|
||||
|
||||
gate = read('gate/gate.json')
|
||||
suite = read('gate/suite/summary.json')
|
||||
coverage = read('gate/suite/coverage.json')
|
||||
manifest = read('plugins/manifest.json')
|
||||
passed = gate.get('passed') is True and suite.get('passed') is True
|
||||
with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as out:
|
||||
out.write(f"Daily API contracts: {'PASS' if passed else 'FAIL / incomplete'}\n\n")
|
||||
out.write(f"Core: `{os.environ['GITHUB_SHA']}`; shuffle seed: `{gate.get('api_seed', os.environ['GITHUB_RUN_ID'])}`.\n\n")
|
||||
out.write('Fixed order followed by a seeded chain shuffle, with sequential operations inside each chain. The execution guard is 600 seconds; dependency and service preparation is separate. This job is not a required PR check.\n\n')
|
||||
out.write(f"Cases: {suite.get('requests', 'not reported')}; statuses: {suite.get('status', 'not reported')}; suite execution: {suite.get('elapsed_seconds', 'not reported')}s.\n\n")
|
||||
out.write(f"API behavior contracts: {coverage.get('areas', {}).get('api', 'not reported')}.\n\n")
|
||||
out.write(f"Observed HTTP route templates (including preparation): {len(coverage['http_observations']['routes']) if 'http_observations' in coverage else 'not reported'}. A route hit does not establish all behavior behind it.\n\n")
|
||||
for plugin in manifest.get('plugins', []):
|
||||
out.write(f"Plugin: `{plugin['id']}@{plugin['version']}`; installation readback: `{plugin.get('installed', 'not confirmed')}`.\n\n")
|
||||
errors = list(suite.get('errors', [])) + list(gate.get('resource_errors', [])) + list(gate.get('coverage_errors', []))
|
||||
if gate.get('timed_out'):
|
||||
errors.append('API suite exceeded its 600-second outer process guard.')
|
||||
if gate.get('infrastructure_error'):
|
||||
errors.append(gate['infrastructure_error'])
|
||||
if not gate or not suite:
|
||||
errors.append('Missing verdict; inspect preparation and service logs.')
|
||||
for error in errors:
|
||||
out.write(f"- {error}\n")
|
||||
PY_SUMMARY
|
||||
- name: Preserve API verdicts and evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: daily-api-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
.qa/api/gate/gate.json
|
||||
.qa/api/gate/resources.jsonl
|
||||
.qa/api/gate/suite.log
|
||||
.qa/api/gate/suite/summary.json
|
||||
.qa/api/gate/suite/profile.json
|
||||
.qa/api/gate/suite/cases.json
|
||||
.qa/api/gate/suite/health.json
|
||||
.qa/api/gate/suite/cleanup-checks.json
|
||||
.qa/api/gate/suite/resource-case-*.json
|
||||
.qa/api/gate/suite/coverage.json
|
||||
.qa/api/gate/suite/coverage.md
|
||||
.qa/api/plugins/manifest.json
|
||||
.qa/api/plugins/*-build.log
|
||||
.qa/api/*.log
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
@@ -48,6 +48,7 @@ jobs:
|
||||
if: >-
|
||||
github.repository == 'langbot-app/LangBot' &&
|
||||
github.event_name == 'release' && github.event.action == 'published' &&
|
||||
!startsWith(github.event.release.tag_name, 'plugins/') &&
|
||||
github.event.release.draft == false && github.event.release.prerelease == false
|
||||
needs: tests
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Docs tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths: ['docs/**', '.github/workflows/*docs*.yml', '.github/workflows/sync-*.yml']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Fumapress docs build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: npm
|
||||
cache-dependency-path: docs/package-lock.json
|
||||
- run: npm ci
|
||||
- run: npm run typecheck
|
||||
- run: npm test
|
||||
- run: npm run build
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
paths:
|
||||
- 'src/langbot/**'
|
||||
- 'tests/**'
|
||||
- 'plugins/Runner/**'
|
||||
- 'pyproject.toml'
|
||||
- 'pytest.ini'
|
||||
- 'uv.lock'
|
||||
@@ -15,6 +16,7 @@ on:
|
||||
paths:
|
||||
- 'src/langbot/**'
|
||||
- 'tests/**'
|
||||
- 'plugins/Runner/**'
|
||||
- 'pyproject.toml'
|
||||
- 'pytest.ini'
|
||||
- 'uv.lock'
|
||||
@@ -106,30 +108,6 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Read official source pin
|
||||
id: plugin-source
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
lock = json.loads(Path('tests/fixtures/legacy_plugin_identity_sources.json').read_text())
|
||||
assert lock['repository'] == 'langbot-app/langbot-plugins'
|
||||
assert re.fullmatch(r'[0-9a-f]{40}', lock['revision'])
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
|
||||
output.write(f"revision={lock['revision']}\n")
|
||||
PY
|
||||
|
||||
- name: Checkout pinned official plugin sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: langbot-app/langbot-plugins
|
||||
ref: ${{ steps.plugin-source.outputs.revision }}
|
||||
path: .contract-sources/langbot-plugins
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
@@ -143,7 +121,7 @@ jobs:
|
||||
|
||||
- name: Run required identity consumer contracts
|
||||
env:
|
||||
LANGBOT_LEGACY_PLUGIN_ROOT: ${{ github.workspace }}/.contract-sources/langbot-plugins
|
||||
LANGBOT_LEGACY_PLUGIN_ROOT: ${{ github.workspace }}/plugins
|
||||
run: |
|
||||
uv run --no-sync pytest --required-suite plugin_identity \
|
||||
-q --tb=short -rs --junitxml=plugin-identity-results.xml
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Plugins / KnowledgeEngine shared SDK verification
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- 'plugins/KnowledgeEngine/**'
|
||||
- '.github/workflows/plugins-knowledgeengine-shared-tests.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- 'plugins/KnowledgeEngine/**'
|
||||
- '.github/workflows/plugins-knowledgeengine-shared-tests.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
candidate-sdk:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
working-directory: plugins
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- name: Install released SDK and test dependencies
|
||||
run: |
|
||||
uv venv
|
||||
uv pip install 'https://files.pythonhosted.org/packages/f2/c7/c870377771cbe27e047b19778b5f249b15efef52409358901af94d13e6c0/langbot_plugin-0.7.4-py3-none-any.whl#sha256=596ecd776c13efa6f6cfe178463dc4a967653238548c9569195cdbb5aa47506e' pytest pytest-asyncio \
|
||||
-r KnowledgeEngine/LangRAG/requirements.txt \
|
||||
-r KnowledgeEngine/DifyDatasetsConnector/requirements.txt \
|
||||
-r KnowledgeEngine/FastGPTConnector/requirements.txt \
|
||||
-r KnowledgeEngine/RAGFlowConnector/requirements.txt
|
||||
- name: Behavioral regressions (fake Host and HTTP provider)
|
||||
run: .venv/bin/python -m pytest KnowledgeEngine/certification-tests -q
|
||||
- name: LangRAG legacy unit and benchmark fixtures
|
||||
working-directory: plugins/KnowledgeEngine/LangRAG
|
||||
run: ../../.venv/bin/python -m unittest discover -s tests -v
|
||||
- name: LongTermMemory regressions
|
||||
working-directory: plugins/KnowledgeEngine/LongTermMemory
|
||||
run: PYTHONPATH=. ../../.venv/bin/python -m pytest tests -q
|
||||
- name: Exact archives, real dependency prepare, discovery and RPC
|
||||
run: .venv/bin/python KnowledgeEngine/certification-tests/verify_candidates.py --evidence "$RUNNER_TEMP/ke-evidence"
|
||||
- name: Retain unsigned candidate evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: knowledgeengine-unsigned-candidates
|
||||
path: |
|
||||
${{ runner.temp }}/ke-evidence/*.json
|
||||
${{ runner.temp }}/ke-evidence/*.log
|
||||
${{ runner.temp }}/ke-evidence/packages/**/*.lbpkg
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Plugins / LangRAG tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- 'plugins/KnowledgeEngine/LangRAG/**'
|
||||
- '.github/workflows/plugins-langrag-tests.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- 'plugins/KnowledgeEngine/LangRAG/**'
|
||||
- '.github/workflows/plugins-langrag-tests.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv venv
|
||||
uv pip install \
|
||||
'https://files.pythonhosted.org/packages/f2/c7/c870377771cbe27e047b19778b5f249b15efef52409358901af94d13e6c0/langbot_plugin-0.7.4-py3-none-any.whl#sha256=596ecd776c13efa6f6cfe178463dc4a967653238548c9569195cdbb5aa47506e' \
|
||||
-r plugins/KnowledgeEngine/LangRAG/requirements.txt
|
||||
- name: Real SDK import smoke
|
||||
working-directory: plugins/KnowledgeEngine/LangRAG
|
||||
run: |
|
||||
../../../.venv/bin/python -c "import langbot_plugin; from main import LangRAG; from components.knowledge_engine.langrag import LangRAG as Engine; from components.pages.observability import LangRAGObservabilityPage; print('Real SDK:', langbot_plugin.__file__); print(LangRAG.__name__, Engine.__name__, LangRAGObservabilityPage.__name__)"
|
||||
- name: Unit and benchmark tests
|
||||
working-directory: plugins/KnowledgeEngine/LangRAG
|
||||
run: ../../../.venv/bin/python -m unittest discover -s tests -v
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Plugins / LocalAgent tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- "plugins/Runner/LocalAgent/**"
|
||||
- ".github/workflows/plugins-local-agent-tests.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- "plugins/Runner/LocalAgent/**"
|
||||
- ".github/workflows/plugins-local-agent-tests.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout plugins
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
|
||||
- name: Keep environment outside the checkout
|
||||
run: |
|
||||
printf 'UV_PROJECT_ENVIRONMENT=%s/local-agent-venv\n' "$RUNNER_TEMP" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install dependencies with the latest stable SDK
|
||||
run: uv sync --extra dev --upgrade-package langbot-plugin
|
||||
working-directory: plugins/Runner/LocalAgent
|
||||
|
||||
- name: Report SDK version and verify explicit-export contract
|
||||
run: uv run --no-sync python -c 'from importlib.metadata import version; print("langbot-plugin", version("langbot-plugin")); from inspect import signature; from langbot_plugin.api.entities.builtin.runner.context import RunnerContext; assert "paths" in signature(RunnerContext.export_box_files).parameters'
|
||||
working-directory: plugins/Runner/LocalAgent
|
||||
|
||||
- name: Ruff check
|
||||
run: uv run --no-sync ruff check .
|
||||
working-directory: plugins/Runner/LocalAgent
|
||||
|
||||
- name: Ruff format check
|
||||
run: uv run --no-sync ruff format --check .
|
||||
working-directory: plugins/Runner/LocalAgent
|
||||
|
||||
- name: Pytest
|
||||
run: uv run --no-sync pytest
|
||||
working-directory: plugins/Runner/LocalAgent
|
||||
@@ -0,0 +1,67 @@
|
||||
name: Plugins / Misc plugin tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- "plugins/misc/**"
|
||||
- ".github/workflows/plugins-misc-tests.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- "plugins/misc/**"
|
||||
- ".github/workflows/plugins-misc-tests.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
working-directory: plugins
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
|
||||
# Each misc plugin imports its own modules through sys.path tricks, so every
|
||||
# suite runs in its own interpreter and its own environment; requirements.txt
|
||||
# is the per-plugin dependency declaration (manifests do not carry them).
|
||||
- name: Run every misc plugin suite in isolation
|
||||
run: |
|
||||
set -uo pipefail
|
||||
status=0
|
||||
ran=0
|
||||
for plugin in misc/*/; do
|
||||
plugin="${plugin%/}"
|
||||
name="$(basename "$plugin")"
|
||||
if [ ! -d "$plugin/tests" ]; then
|
||||
echo "::notice::$name has no tests/ directory; skipped"
|
||||
continue
|
||||
fi
|
||||
ran=$((ran + 1))
|
||||
env_dir="$RUNNER_TEMP/venv-$name"
|
||||
echo "=== $name ==="
|
||||
uv venv "$env_dir" --python 3.11 >/dev/null
|
||||
python_bin="$env_dir/bin/python"
|
||||
install_args=(--python "$python_bin"
|
||||
'https://files.pythonhosted.org/packages/f2/c7/c870377771cbe27e047b19778b5f249b15efef52409358901af94d13e6c0/langbot_plugin-0.7.4-py3-none-any.whl#sha256=596ecd776c13efa6f6cfe178463dc4a967653238548c9569195cdbb5aa47506e'
|
||||
pytest pytest-asyncio)
|
||||
if [ -s "$plugin/requirements.txt" ]; then
|
||||
install_args+=(-r "$plugin/requirements.txt")
|
||||
fi
|
||||
uv pip install "${install_args[@]}" >/dev/null
|
||||
"$python_bin" -c 'from importlib.metadata import version; assert version("langbot-plugin") == "0.7.4"'
|
||||
if ! (cd "$plugin" && "$python_bin" -m pytest tests -q -p no:cacheprovider); then
|
||||
echo "::error::$name test suite failed"
|
||||
status=1
|
||||
fi
|
||||
done
|
||||
echo "Ran $ran misc suites"
|
||||
exit $status
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Plugins / Build package
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
plugin:
|
||||
description: 'Exact catalog path, for example Runner/LocalAgent'
|
||||
type: string
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
package:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- name: Install the plugin build CLI independently of Core
|
||||
run: |
|
||||
uv venv "$RUNNER_TEMP/plugin-build"
|
||||
uv pip install --python "$RUNNER_TEMP/plugin-build/bin/python" 'langbot-plugin==0.7.19' pyyaml
|
||||
echo "$RUNNER_TEMP/plugin-build/bin" >> "$GITHUB_PATH"
|
||||
- name: Build selected plugin without publishing
|
||||
env:
|
||||
PLUGIN_PATH: ${{ inputs.plugin }}
|
||||
run: python plugins/scripts/package_plugin.py "$PLUGIN_PATH" --output "$RUNNER_TEMP/plugin-packages"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: plugin-package-${{ github.run_id }}
|
||||
path: ${{ runner.temp }}/plugin-packages/*.lbpkg
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Plugins / Plugin catalog
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: ["plugins/**", ".github/workflows/plugins-*.yml"]
|
||||
pull_request:
|
||||
paths: ["plugins/**", ".github/workflows/plugins-*.yml"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
catalog:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: python -m pip install pyyaml
|
||||
- run: python plugins/scripts/check_catalog.py
|
||||
- run: python plugins/scripts/test_repository.py
|
||||
@@ -0,0 +1,85 @@
|
||||
name: Plugins / Runner tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- "plugins/Runner/**"
|
||||
- ".github/workflows/plugins-runner-tests.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "plugins/pytest.ini"
|
||||
- "plugins/Runner/**"
|
||||
- ".github/workflows/plugins-runner-tests.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: plugins/Runner
|
||||
steps:
|
||||
- name: Checkout plugins
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- name: Keep environment outside the checkout
|
||||
run: |
|
||||
printf 'UV_PROJECT_ENVIRONMENT=%s/runner-venv\n' "$RUNNER_TEMP" >> "$GITHUB_ENV"
|
||||
printf 'RUNNER_EVIDENCE_DIR=%s/runner-evidence\n' "$RUNNER_TEMP" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --locked --dev
|
||||
- name: Verify published SDK pin
|
||||
run: uv run --no-sync python -c 'from importlib.metadata import version; assert version("langbot-plugin") == "0.7.4"; print("langbot-plugin", version("langbot-plugin"))'
|
||||
- name: Prepare isolated certified coding Runner SDK environment
|
||||
run: |
|
||||
uv venv "$RUNNER_TEMP/coding-074"
|
||||
# PyPI's aggregate index can lag the exact 0.7.4 release endpoint.
|
||||
uv pip install --python "$RUNNER_TEMP/coding-074/bin/python" \
|
||||
'https://files.pythonhosted.org/packages/f2/c7/c870377771cbe27e047b19778b5f249b15efef52409358901af94d13e6c0/langbot_plugin-0.7.4-py3-none-any.whl#sha256=596ecd776c13efa6f6cfe178463dc4a967653238548c9569195cdbb5aa47506e' \
|
||||
-r acp-agent-runner/requirements.txt -r claude-code-agent/requirements.txt -r codex-agent/requirements.txt pytest pyyaml packaging
|
||||
"$RUNNER_TEMP/coding-074/bin/python" -c 'from importlib.metadata import version; assert version("langbot-plugin") == "0.7.4"'
|
||||
printf 'CODING_RUNNER_TEST_PYTHON=%s/coding-074/bin/python\n' "$RUNNER_TEMP" >> "$GITHUB_ENV"
|
||||
- name: Prepare isolated LocalAgent environment with the latest stable SDK
|
||||
run: |
|
||||
UV_PROJECT_ENVIRONMENT="$RUNNER_TEMP/localagent-latest" uv sync --project LocalAgent --extra dev --upgrade-package langbot-plugin
|
||||
"$RUNNER_TEMP/localagent-latest/bin/python" -c 'from importlib.metadata import version; print("langbot-plugin", version("langbot-plugin")); from inspect import signature; from langbot_plugin.api.entities.builtin.runner.context import RunnerContext; assert "paths" in signature(RunnerContext.export_box_files).parameters'
|
||||
printf 'LOCALAGENT_TEST_PYTHON=%s/localagent-latest/bin/python\n' "$RUNNER_TEMP" >> "$GITHUB_ENV"
|
||||
# Preserve the external Runner lint scope; LocalAgent has its own Ruff config.
|
||||
# RunnerDemo did not have a Ruff gate before consolidation.
|
||||
- name: Ruff check
|
||||
run: uv run --no-sync ruff check tests http-certification-tests ./*-agent acp-agent-runner
|
||||
- name: Ruff format check
|
||||
run: uv run --no-sync ruff format --check tests http-certification-tests ./*-agent acp-agent-runner
|
||||
- name: LocalAgent Ruff check
|
||||
run: uv run --no-sync ruff check LocalAgent
|
||||
- name: LocalAgent Ruff format check
|
||||
run: uv run --no-sync ruff format --check LocalAgent
|
||||
- name: All Runner tests, including isolated plugin suites (LocalAgent, RunnerDemo, shared agents)
|
||||
run: uv run --no-sync python -m pytest --junitxml="${RUNNER_EVIDENCE_DIR}/runner.xml"
|
||||
# Generic pkg/test module names require a fresh pytest interpreter.
|
||||
- name: HTTP Runner certification regressions
|
||||
run: uv run --no-sync python -m pytest http-certification-tests -q --junitxml="${RUNNER_EVIDENCE_DIR}/http-certification.xml"
|
||||
- name: Certified RunnerDemo SDK admission and package tests
|
||||
run: |
|
||||
"$RUNNER_TEMP/coding-074/bin/python" certification-tests/test_runner_demo_dependency_admission.py -v
|
||||
cd RunnerDemo
|
||||
"$RUNNER_TEMP/coding-074/bin/python" -m unittest discover -s tests -v
|
||||
- name: Upload test evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: runner-test-evidence
|
||||
path: ${{ runner.temp }}/runner-evidence
|
||||
if-no-files-found: warn
|
||||
@@ -12,6 +12,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
if: ${{ !startsWith(github.event.release.tag_name, 'plugins/') && !startsWith(inputs.source_ref, 'plugins/') && !startsWith(github.ref, 'refs/tags/plugins/') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -72,6 +73,7 @@ jobs:
|
||||
run: |
|
||||
python3 scripts/stamp_build_revision.py
|
||||
uv build
|
||||
python3 scripts/check_core_artifacts.py dist/*.whl dist/*.tar.gz
|
||||
|
||||
- name: Publish to PyPI
|
||||
run: |
|
||||
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
paths:
|
||||
- 'src/langbot/**'
|
||||
- 'tests/**'
|
||||
- 'plugins/Runner/**'
|
||||
- '.github/workflows/run-tests.yml'
|
||||
- 'pyproject.toml'
|
||||
- 'uv.lock'
|
||||
@@ -181,30 +182,6 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Read official plugin source pin
|
||||
id: plugin-source
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
lock = json.loads(Path('tests/fixtures/legacy_plugin_identity_sources.json').read_text())
|
||||
assert lock['repository'] == 'langbot-app/langbot-plugins'
|
||||
assert re.fullmatch(r'[0-9a-f]{40}', lock['revision'])
|
||||
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
|
||||
output.write(f"revision={lock['revision']}\n")
|
||||
PY
|
||||
|
||||
- name: Checkout pinned official plugin sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: langbot-app/langbot-plugins
|
||||
ref: ${{ steps.plugin-source.outputs.revision }}
|
||||
path: .contract-sources/langbot-plugins
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
@@ -244,7 +221,7 @@ jobs:
|
||||
|
||||
- name: Run required LocalAgent contracts
|
||||
env:
|
||||
LANGBOT_LEGACY_PLUGIN_ROOT: ${{ github.workspace }}/.contract-sources/langbot-plugins
|
||||
LANGBOT_LEGACY_PLUGIN_ROOT: ${{ github.workspace }}/plugins
|
||||
run: |
|
||||
uv run --no-sync pytest --required-suite local_agent \
|
||||
-q --tb=short -rs --junitxml=local-agent-results.xml
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Sync LangBot Blog articles
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '17 2 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: sync-langbot-blog-articles
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'langbot-app/LangBot' && github.ref == 'refs/heads/master' && vars.DOCS_PUBLISH_ENABLED == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: Check out main
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
# A PAT push triggers the landing and Wiki workflows; GITHUB_TOKEN does not.
|
||||
token: ${{ secrets.LANGBOT_WIKI_PUSH_TOKEN || secrets.RELEASE_UPLOAD_GITHUB_TOKEN }}
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: npm
|
||||
cache-dependency-path: docs/package-lock.json
|
||||
- run: npm ci
|
||||
- name: Check out LangBot landing page
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: langbot-app/langbot-landing-page
|
||||
token: ${{ secrets.LANGBOT_WIKI_PUSH_TOKEN || secrets.RELEASE_UPLOAD_GITHUB_TOKEN }}
|
||||
path: .article-source
|
||||
persist-credentials: false
|
||||
- name: Synchronize articles and images
|
||||
run: node scripts/sync-blog-articles.mjs --source ../.article-source
|
||||
- name: Validate generated articles
|
||||
run: |
|
||||
node scripts/sync-blog-articles.mjs --check --source ../.article-source
|
||||
node scripts/sync-blog-articles.mjs --check
|
||||
npm test
|
||||
npm run build
|
||||
- name: Commit synchronized content
|
||||
run: |
|
||||
git add docs.json en/articles zh/articles ja/articles images/articles scripts/blog-articles-manifest.json
|
||||
if git diff --cached --quiet; then
|
||||
echo 'LangBot Blog articles are already synchronized.'
|
||||
exit 0
|
||||
fi
|
||||
git config user.name 'langbot-docs-bot'
|
||||
git config user.email '[email protected]'
|
||||
git commit -m 'docs(articles): sync LangBot Blog'
|
||||
git push origin HEAD:master
|
||||
@@ -0,0 +1,90 @@
|
||||
name: Sync GitHub Wiki
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- "docs/en/**"
|
||||
- "docs/zh/**"
|
||||
- "docs/ja/**"
|
||||
- "docs/scripts/build-github-wiki.py"
|
||||
- ".github/workflows/sync-github-wiki.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/en/**"
|
||||
- "docs/zh/**"
|
||||
- "docs/ja/**"
|
||||
- "docs/scripts/build-github-wiki.py"
|
||||
- "docs/tests/test_build_github_wiki.py"
|
||||
- ".github/workflows/sync-github-wiki.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: sync-langbot-github-wiki
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
name: Build Wiki mirror
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Run converter tests
|
||||
run: python -m unittest tests/test_build_github_wiki.py
|
||||
- name: Build Wiki pages
|
||||
run: python scripts/build-github-wiki.py --output dist/github-wiki
|
||||
- name: Validate generated pages
|
||||
run: |
|
||||
test -f dist/github-wiki/Home.md
|
||||
test -f dist/github-wiki/_Sidebar.md
|
||||
test "$(find dist/github-wiki -maxdepth 1 -name '*.md' | wc -l)" -gt 250
|
||||
- name: Upload generated Wiki
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: github-wiki
|
||||
path: docs/dist/github-wiki
|
||||
if-no-files-found: error
|
||||
|
||||
sync:
|
||||
name: Publish to LangBot Wiki
|
||||
if: github.event_name != 'pull_request' && github.repository == 'langbot-app/LangBot' && github.ref == 'refs/heads/master' && vars.DOCS_PUBLISH_ENABLED == 'true'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: github-wiki
|
||||
path: generated
|
||||
- name: Clone target Wiki
|
||||
env:
|
||||
WIKI_PUSH_TOKEN: ${{ secrets.LANGBOT_WIKI_PUSH_TOKEN || secrets.RELEASE_UPLOAD_GITHUB_TOKEN }}
|
||||
run: |
|
||||
test -n "$WIKI_PUSH_TOKEN"
|
||||
git clone "https://x-access-token:${WIKI_PUSH_TOKEN}@github.com/langbot-app/LangBot.wiki.git" target
|
||||
- name: Replace and publish Wiki contents
|
||||
env:
|
||||
SOURCE_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
find target -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
|
||||
cp -a generated/. target/
|
||||
cd target
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "GitHub Wiki is already up to date."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "langbot-docs-sync[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -m "docs: sync from LangBot/docs@${SOURCE_SHA::7}"
|
||||
git push origin HEAD:master
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Sync landing docs from LangBot
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths: ['docs/**', '.github/workflows/sync-landing-submodule.yml']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: sync-landing-docs-main
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'langbot-app/LangBot' && github.ref == 'refs/heads/master' && vars.DOCS_PUBLISH_ENABLED == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: master
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Test synchronization contracts
|
||||
working-directory: docs
|
||||
run: python3 -m unittest discover -s tests -p test_sync_landing_submodule.py -v
|
||||
- name: Update landing submodule and production fork
|
||||
working-directory: docs
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.LANGBOT_WIKI_PUSH_TOKEN || secrets.RELEASE_UPLOAD_GITHUB_TOKEN }}
|
||||
run: python3 scripts/sync-landing-submodule.py
|
||||
+4
-2
@@ -4,8 +4,7 @@ __pycache__/
|
||||
database.db
|
||||
langbot.log
|
||||
/banlist.py
|
||||
/plugins/
|
||||
!/plugins/__init__.py
|
||||
*.lbpkg
|
||||
/revcfg.py
|
||||
prompts/
|
||||
logs/
|
||||
@@ -71,3 +70,6 @@ packaging/fnos/app/desktop/images/
|
||||
packaging/fnos/*.fpk
|
||||
|
||||
r.ps1
|
||||
|
||||
*.log
|
||||
*.pid
|
||||
|
||||
@@ -5,19 +5,23 @@ repos:
|
||||
hooks:
|
||||
# Run the linter of backend.
|
||||
- id: ruff
|
||||
exclude: ^(docs|plugins)/
|
||||
args: [--fix]
|
||||
# Run the formatter of backend.
|
||||
- id: ruff-format
|
||||
exclude: ^(docs|plugins)/
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: ^(docs|plugins)/
|
||||
name: prettier
|
||||
entry: npx --prefix web prettier --write --ignore-unknown
|
||||
language: system
|
||||
types_or: [javascript, jsx, ts, tsx, css, scss]
|
||||
|
||||
- id: lint-staged
|
||||
exclude: ^(docs|plugins)/
|
||||
name: lint-staged
|
||||
entry: cd web && pnpm lint-staged
|
||||
language: system
|
||||
|
||||
@@ -44,12 +44,14 @@ Run tests in Linux to match CI; on Windows, use Linux containers through `wslc`
|
||||
|
||||
## Where to Look
|
||||
|
||||
- Public documentation source: `docs/`; published at https://langbot.app/docs/. `langbot-app/langbot-docs` is a legacy archive; edit here.
|
||||
- Architecture map: `ARCHITECTURE.md`.
|
||||
- Official plugin sources: `plugins/`, with independent CI, dependencies and Marketplace releases; follow `plugins/AGENTS.md` and `plugins/RELEASING.md`. Never bundle this tree in Core artifacts.
|
||||
- Dev environment guide: https://langbot.app/docs/zh/develop/dev-config.
|
||||
- Plugin runtime / CLI / SDK debugging: https://langbot.app/docs/zh/develop/plugin-runtime.
|
||||
- API-key auth: `docs/API_KEY_AUTH.md`.
|
||||
- API-key auth: `dev-docs/API_KEY_AUTH.md`.
|
||||
- Service API CLI: [`langbot-cli`](https://github.com/langbot-app/langbot-cli) (`lbctl`) manages running Workspaces; it is separate from the SDK's `lbp` CLI.
|
||||
- Box deep-dive notes: `docs/review/box-architecture.md` and related files.
|
||||
- Box deep-dive notes: `dev-docs/review/box-architecture.md` and related files.
|
||||
- In-repo skills: `skills/` is the single source of truth for LangBot agent skills.
|
||||
- SDK repo: `../langbot-plugin-sdk/` when changing shared entities, plugin APIs, action protocol, `lbp rt`, or `lbp box`.
|
||||
|
||||
@@ -105,7 +107,7 @@ Config keys to verify in `data/config.yaml` / `src/langbot/templates/config.yaml
|
||||
- China-only adapters and configuration/field validation diagnostics do not require localization. An adapter also serving international users is not exempt merely because it supports a Chinese platform.
|
||||
- Preserve raw provider/plugin diagnostics, tracebacks, protocol values, and machine-readable error/status codes. Translate LangBot's own category or explanation around them; do not translate arbitrary errors with substring replacement. Plugin-authored messages remain the plugin's localization responsibility.
|
||||
- Keep changes small and maintainable. Reuse existing persistence and response boundaries; delete obsolete fields only after verifying they have no remaining readers, including compatibility paths.
|
||||
- When changing language behavior, verify catalog/placeholder parity, Workspace isolation and permissions, and Pipeline inheritance/custom overrides. Update affected configuration snapshots and API/MCP documentation. Implementation details and focused test locations: [Backend message language](docs/architecture/backend-i18n.md).
|
||||
- When changing language behavior, verify catalog/placeholder parity, Workspace isolation and permissions, and Pipeline inheritance/custom overrides. Update affected configuration snapshots and API/MCP documentation. Implementation details and focused test locations: [Backend message language](dev-docs/architecture/backend-i18n.md).
|
||||
|
||||
## Runtime Pitfalls
|
||||
|
||||
|
||||
+9
-4
@@ -22,6 +22,7 @@ At runtime, one LangBot process owns:
|
||||
LangBot is not a single-repo system.
|
||||
|
||||
- `LangBot/` is the main product: backend, web UI, platform adapters, pipeline engine, HTTP API, MCP server, RAG, persistence, skills integration, and the bridge code that talks to runtimes.
|
||||
- `LangBot/plugins/` contains the official plugin catalog (`Runner/`, `KnowledgeEngine/`, `misc/`). Sources share this checkout; each plugin retains independent tests, dependencies, versioning, packaging and Marketplace publication. Core wheels, sdists, Docker contexts, release ZIPs and fnOS packages exclude this tree. See `plugins/RELEASING.md` and `plugins/MIGRATION.md`.
|
||||
- `langbot-plugin-sdk/` is published as `langbot-plugin` and pinned in `LangBot/pyproject.toml`. It contains plugin developer APIs, shared entities, `lbp`, the Plugin Runtime (`lbp rt`), and the Box Runtime (`lbp box`).
|
||||
- [`langbot-cli`](https://github.com/langbot-app/langbot-cli) is a separately released `lbctl` client for managing a running Workspace through the HTTP Service API; it is not part of the LangBot server or the SDK's `lbp` CLI.
|
||||
- Plugins import SDK APIs from `langbot_plugin.*`; the LangBot main process imports the same package for shared entities and runtime protocols.
|
||||
@@ -162,7 +163,7 @@ In this repo:
|
||||
- `pkg/plugin/handler.py` exposes LangBot actions to the runtime and calls runtime actions for plugin operations.
|
||||
- `pkg/provider/tools/loaders/plugin.py` exposes plugin Tool components to LLM runners.
|
||||
- Pipeline handlers emit SDK events such as normal-message events and prompt-processing events.
|
||||
- [Certified plugin policy](docs/architecture/certified-plugins.md) defines Core's archive-fact, admission, and tenant-log-visibility boundary; the SDK remains responsible for certificate verification.
|
||||
- [Certified plugin policy](dev-docs/architecture/certified-plugins.md) defines Core's archive-fact, admission, and tenant-log-visibility boundary; the SDK remains responsible for certificate verification.
|
||||
|
||||
In `langbot-plugin-sdk`:
|
||||
|
||||
@@ -188,8 +189,8 @@ the run. Output export requires explicit normalized absolute sandbox file paths
|
||||
opaque handles scoped to that run. The selected files are validated as one batch
|
||||
before registration; successful export removes only those files, and cleanup
|
||||
failure is a warning with accepted handles retained. Writing an outbox file alone
|
||||
does not deliver it. Core and LocalAgent currently pin SDK PR #180's exact commit
|
||||
until a release includes the selected-path API.
|
||||
does not deliver it. Core and Plugin Runtime must provide the selected-path API;
|
||||
LocalAgent CI verifies this contract against the latest stable published SDK.
|
||||
`message.completed.file_ids` attaches explicitly exported files to Pipeline output;
|
||||
Agents send them explicitly with `ctx.reply_files()`, subject to event reply permission.
|
||||
The Pipeline wrapper never scans a Box. Binding ends with the run; the reusable Box
|
||||
@@ -201,7 +202,7 @@ Box is the optional sandbox subsystem used by native execution tools, stdio MCP
|
||||
In this repo:
|
||||
|
||||
- `pkg/box/service.py` is the application-facing facade for exec, sessions, managed processes, status, reconnects, quotas, generic mounts, and sandbox profiles.
|
||||
- [Outbox export failures](docs/BOX_OUTBOX_EXPORT.md) documents bounded reads, permission fallback, and cleanup after Runner acceptance.
|
||||
- [Outbox export failures](dev-docs/BOX_OUTBOX_EXPORT.md) documents bounded reads, permission fallback, and cleanup after Runner acceptance.
|
||||
- `pkg/box/connector.py` connects to the Box Runtime over stdio, Windows subprocess+WebSocket, or remote WebSocket.
|
||||
- `pkg/provider/tools/loaders/native.py` is the Core orchestration seam: the
|
||||
Skill loader supplies generic read-only mounts to Box execution. `mcp_stdio.py`
|
||||
@@ -345,3 +346,7 @@ When one of these changes, update the others if the behavior or contract changed
|
||||
- Prefer small, explicit agent surfaces over exposing every internal API.
|
||||
- Treat cross-repo contracts with the SDK as public interfaces.
|
||||
- Test behavior at the narrowest useful layer first, then add integration/e2e coverage for runtime or platform changes.
|
||||
|
||||
## Documentation repositories
|
||||
|
||||
The public documentation site lives in `docs/` and is built with Node.js 24 and Fumapress. Internal design and review notes live in `dev-docs/`. The landing site keeps its `docs` submodule path but tracks LangBot `master`, building from the nested `docs/` directory. The old `langbot-app/langbot-docs` repository can be archived; publication never writes to it. See `dev-docs/DOCS_MIGRATION.md` for publication and cutover details.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
prune plugins
|
||||
global-exclude *.lbpkg
|
||||
@@ -63,6 +63,8 @@ Click the Star and Watch buttons in the top-right corner of the repository to ge
|
||||
|
||||
## Quick Start
|
||||
|
||||
Official plugin source index: [plugins/README.md](plugins/README.md) · [machine-readable catalog](plugins/catalog.json) · [independent plugin releases](plugins/RELEASING.md). Plugins are installed separately and are not bundled in Core releases.
|
||||
|
||||
### ☁️ LangBot Cloud (Recommended)
|
||||
|
||||
[](https://space.langbot.app/cloud)
|
||||
@@ -188,3 +190,5 @@ Thanks to all [contributors](https://github.com/langbot-app/LangBot/graphs/contr
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[Documentation source](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -63,6 +63,8 @@ LangBot 是一个**开源的生产级平台**,用于构建 AI 驱动的即时
|
||||
|
||||
## 快速开始
|
||||
|
||||
官方插件源码索引:[plugins/README.md](plugins/README.md) · [机器可读目录](plugins/catalog.json) · [插件独立上架流程](plugins/RELEASING.md)。插件保持独立安装,不随 Core 发版打包。
|
||||
|
||||
### ☁️ LangBot Cloud(推荐)
|
||||
|
||||
[](https://cloud.langbot.app)
|
||||
@@ -212,3 +214,5 @@ LangBot **从设计上就对 Agent 友好** —— 你的编码 Agent(Claude C
|
||||
|
||||
If you're a code agent based on LLM, you should read [AGENTS.md](AGENTS.md) to understand the project structure and development process before you start any development.
|
||||
-->
|
||||
|
||||
[文档源码](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -186,3 +186,5 @@ Gracias a todos los [colaboradores](https://github.com/langbot-app/LangBot/graph
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[Código fuente de la documentación](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -186,3 +186,5 @@ Merci à tous les [contributeurs](https://github.com/langbot-app/LangBot/graphs/
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[Sources de la documentation](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -186,3 +186,5 @@ LangBot をより良くするために貢献してくださったすべての[
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[ドキュメントのソース](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -186,3 +186,5 @@ LangBot을 더 나은 프로젝트로 만들어 주신 모든 [기여자](https:
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[문서 소스](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -186,3 +186,5 @@ LangBot **дружелюбен к агентам по своей архитек
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[Исходники документации](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -204,3 +204,5 @@ LangBot **從設計上就對 Agent 友善** —— 你的編碼 Agent(Claude C
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[文件原始碼](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -186,3 +186,5 @@ Cảm ơn tất cả [người đóng góp](https://github.com/langbot-app/LangB
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
[Mã nguồn tài liệu](https://github.com/langbot-app/LangBot/tree/master/docs) · [langbot-docs archive](https://github.com/langbot-app/langbot-docs)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://context7.com/schema/context7.json",
|
||||
"projectTitle": "LangBot",
|
||||
"description": "Open-source platform for building AI agents and instant-messaging bots.",
|
||||
"branch": "master",
|
||||
"folders": ["docs/en"]
|
||||
}
|
||||
@@ -14,9 +14,9 @@ latest-LocalAgent Space-resolution work in PR #2632.
|
||||
## Official plugin identity boundary
|
||||
|
||||
The source lock in `tests/fixtures/legacy_plugin_identity_sources.json` records
|
||||
the official repository, immutable revision, and SHA-256 of the consumed Python
|
||||
source files. CI checks out that revision with read-only access
|
||||
and no persisted Git credentials. Tests verify file digests before executing the
|
||||
the official in-tree source root and SHA-256 of the consumed Python source files.
|
||||
CI uses `plugins/` from the same Core checkout, without a second plugin checkout.
|
||||
Tests verify file digests before executing the
|
||||
actual identity methods, their genuine configuration-error classes, and their
|
||||
real `scoped_identity` dependency. Vendor HTTP clients are not constructed.
|
||||
|
||||
@@ -25,9 +25,9 @@ from a hard-coded sibling checkout. Supported current plugins intentionally
|
||||
namespace IDs by Workspace or trusted installation, so merely provisioning that
|
||||
old test produced false failures. The current fixture is:
|
||||
|
||||
- Official repository: `langbot-app/langbot-plugins`
|
||||
- Revision: `7ad54b1a139d88113bc55128326800ed29779737`
|
||||
- DifyAgent 0.2.3; N8nAgent, CozeAgent and TboxAgent 0.2.2
|
||||
- Official repository: `langbot-app/LangBot`, source root `plugins/`
|
||||
- Revision: `in-tree`; the checkout revision selects both Core and plugins
|
||||
- Plugin versions are indexed in `plugins/catalog.json`
|
||||
- All exceed Core's current migration minimums (0.1.10; TboxAgent 0.1.8)
|
||||
|
||||
The 19 Host provenance/resume tests retain their exact raw-identity assertions.
|
||||
@@ -46,7 +46,7 @@ claim current compatibility.
|
||||
To run with an official checkout at the locked revision:
|
||||
|
||||
```bash
|
||||
export LANGBOT_LEGACY_PLUGIN_ROOT=/absolute/path/to/langbot-plugins
|
||||
export LANGBOT_LEGACY_PLUGIN_ROOT="$PWD/plugins"
|
||||
uv run --frozen pytest --required-suite plugin_identity -q -rs
|
||||
```
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Documentation publication after the repository migration
|
||||
|
||||
Public docs live in `LangBot/docs/`; internal notes live in `dev-docs/`. The
|
||||
original 27 top-level internal entries (87 files) were moved without loss.
|
||||
The unsquashed subtree import retains all 888 commits, including 452 signed
|
||||
objects, from `51dddcb3f891d60bc38645e88b2dbd515344da81`. Use
|
||||
`git log 51dddcb3f891d60bc38645e88b2dbd515344da81 -- <original-path>` to inspect
|
||||
the original authors. The import is already merged; do not rewrite that history.
|
||||
|
||||
## Publication source
|
||||
|
||||
`langbot-app/langbot-docs` is a historical archive, not a writable mirror.
|
||||
No publication step splits or pushes to that repository. The landing repository
|
||||
keeps its submodule **path** `docs`, but its **URL** is
|
||||
`https://github.com/langbot-app/LangBot.git`, tracking `master`. Consequently,
|
||||
the landing build runs the documentation package inside `docs/docs/` and copies
|
||||
`docs/docs/dist/public` to `public/docs`. The public URL stays
|
||||
`https://langbot.app/docs/`; Vite's base path and canonical site URL are unchanged.
|
||||
|
||||
The main-repository synchronization job advances only the landing gitlink to
|
||||
LangBot `master` and then synchronizes `RockChinQ/LangBot-LandingPage-Vue`.
|
||||
It checks `.gitmodules` on the exact landing revision it updates, refuses old
|
||||
repository configurations, and rejects rewinds/divergence. It never force-pushes.
|
||||
|
||||
## Cutover order
|
||||
|
||||
1. Merge the paired landing PR: change `.gitmodules`, the pinned `docs` gitlink,
|
||||
and `scripts/build-embedded-docs.mjs` together. Verify its full `pnpm build`.
|
||||
2. Merge the main-repository publication changes. Keep publishers disabled until
|
||||
both repositories are ready; the new flag is `DOCS_PUBLISH_ENABLED`, not
|
||||
`DOCS_MIRROR_ENABLED`. The old flag does not enable these jobs.
|
||||
3. Stop the old repository's Blog, Wiki and landing workflows and let any active
|
||||
writers finish. The owner can archive `langbot-app/langbot-docs` once the
|
||||
new publication chain is verified. Never write disabling commits to it as
|
||||
part of the new publication chain.
|
||||
4. Verify `LANGBOT_WIKI_PUSH_TOKEN`, or the existing `RELEASE_UPLOAD_GITHUB_TOKEN`
|
||||
fallback, has access to LangBot, landing, production fork and Wiki. No token
|
||||
value belongs in logs. Publication only updates gitlinks/content, never
|
||||
workflow files in another repository, and no docs-mirror permission is needed.
|
||||
5. Set `DOCS_PUBLISH_ENABLED=true` in LangBot. Dispatch
|
||||
`sync-landing-submodule.yml` on **master**. It also brings the production fork
|
||||
up to date; confirm the fork matches landing main and observe Cloudflare's
|
||||
resulting deployment before accepting the cutover.
|
||||
6. Dispatch `sync-github-wiki.yml` and `sync-blog-articles.yml` on **master**.
|
||||
The Blog job commits only `docs/` article paths to LangBot using a token whose
|
||||
pushes trigger subsequent landing/Wiki workflows. Wiki images and attribution
|
||||
now point to LangBot `master/docs`, so new assets do not depend on the archive.
|
||||
7. Check `/docs/`, the en/zh/ja guide pages, assets, sitemap, Markdown exports,
|
||||
Wiki content and Blog idempotency. A successful Wiki build is not proof of
|
||||
publication: inspect the separate publish job.
|
||||
|
||||
## Context7
|
||||
|
||||
The root `context7.json` follows the published Context7 schema and selects
|
||||
`master:docs/en`. Context7's current parser indexes English documentation.
|
||||
The old nested ownership configuration belonged to the legacy repository and
|
||||
has been removed; it does not transfer ownership of a Context7 library.
|
||||
The owner must add/refresh `https://github.com/langbot-app/LangBot` in Context7
|
||||
and verify a result from `docs/en` after merge. The existing
|
||||
`/langbot-app/langbot-docs` index is only a historical snapshot after archival.
|
||||
Do not report an index refresh as complete solely because this config exists.
|
||||
|
||||
## Local validation and recovery
|
||||
|
||||
Use Node.js 24. In `docs/`, run `npm ci`, `npm run typecheck`, `npm test`, and
|
||||
`npm run build`. In landing, initialize the submodule and run `pnpm install
|
||||
--frozen-lockfile`, `pnpm test`, and `pnpm build`.
|
||||
|
||||
Disable `DOCS_PUBLISH_ENABLED` to pause publication. If landing still points at
|
||||
the legacy repository or the production fork has diverged, reconcile through
|
||||
reviewed changes; do not bypass the synchronization checks. Keep the last
|
||||
working deployment available until the replacement passes verification.
|
||||
@@ -15,8 +15,8 @@
|
||||
> - `src/langbot/pkg/platform/sources/http_bot.py` — `HttpBotAdapter`
|
||||
> - `src/langbot/pkg/platform/sources/http_bot_signing.py` — HMAC helpers
|
||||
> - `src/langbot/pkg/platform/sources/http_bot.svg` — icon
|
||||
> - `docs/platforms/http-bot.md` — integration guide
|
||||
> - `docs/http-bot-openapi.json` — machine-readable contract
|
||||
> - `dev-docs/platforms/http-bot.md` — integration guide
|
||||
> - `dev-docs/http-bot-openapi.json` — machine-readable contract
|
||||
> - `examples/http-bot/` — Python + TypeScript reference clients
|
||||
>
|
||||
> **Final decisions (resolving the original open questions):**
|
||||
@@ -489,13 +489,13 @@ time so replies always have a destination even if config later changes).
|
||||
The whole point of a standalone adapter is that **integrating is pleasant**. v1
|
||||
ships:
|
||||
|
||||
1. **`docs/platforms/http-bot.md`** — task-oriented integration guide:
|
||||
1. **`dev-docs/platforms/http-bot.md`** — task-oriented integration guide:
|
||||
create the bot → copy inbound URL → set secret → stand up a callback
|
||||
endpoint → send first message → handle 1→M.
|
||||
2. **Copy-paste curl** for the first message (with a working signing one-liner).
|
||||
3. **Reference clients** (≤50 LOC each) in `examples/http-bot/`:
|
||||
`client.py` (push + a Flask/Quart callback receiver) and `client.ts`.
|
||||
4. **OpenAPI fragment** `docs/http-bot-openapi.json` describing inbound +
|
||||
4. **OpenAPI fragment** `dev-docs/http-bot-openapi.json` describing inbound +
|
||||
callback shapes, so integrators can codegen.
|
||||
5. **Local echo recipe**: a one-command callback server that prints every
|
||||
reply, so a developer sees N→1 and 1→M working in under five minutes.
|
||||
@@ -542,7 +542,7 @@ curl -sS -X POST "$BOT" \
|
||||
| 2 | `HttpBotAdapter` (inbound verify, event build, outbound queue) | `platform/sources/http_bot.py` |
|
||||
| 3 | Signing helper module (shared) | `platform/sources/` or `utils/` |
|
||||
| 4 | i18n strings (en/zh/ja) | adapter yaml + web locale |
|
||||
| 5 | Integration docs `docs/platforms/http-bot.md` | `docs/` |
|
||||
| 5 | Integration docs `dev-docs/platforms/http-bot.md` | `docs/` |
|
||||
| 6 | OpenAPI fragment + reference clients | `docs/`, `examples/http-bot/` |
|
||||
| 7 | Tests: signature verify, N→1 aggregation, 1→M ordering, retry | `tests/` |
|
||||
| 8 | (opt) SSRF guard for callback_url | adapter |
|
||||
+5
-6
@@ -6,20 +6,19 @@
|
||||
|
||||
## 1. 仓库组织
|
||||
|
||||
官方 runner 插件与 LangBot 主仓库、SDK 仓库以不同节奏迭代:LangBot 主仓库只维护宿主协议和调度,SDK 仓库维护 Runner 组件和 runtime 协议,官方 runner 插件承载业务 runner 的具体实现和第三方平台适配。
|
||||
官方 runner 插件源码现在位于 LangBot 主仓库的 `plugins/Runner/`,但测试、依赖、版本和上架节奏保持独立。Core 维护宿主协议和调度,SDK 仓库维护 Runner 组件和 runtime 协议,官方 runner 插件承载业务 runner 的具体实现和第三方平台适配。Core 发版不包含插件源码或插件包。
|
||||
|
||||
当前推荐"官方插件可独立发布,必要时共享 SDK helper"。开发期采用本地多目录布局:
|
||||
|
||||
```text
|
||||
langbot-app/
|
||||
langbot-local-agent/ # plugin:langbot-team/LocalAgent/default
|
||||
LangBot/plugins/Runner/
|
||||
LocalAgent/ # plugin:langbot-team/LocalAgent/default
|
||||
manifest.yaml
|
||||
components/runner/default.{yaml,py}
|
||||
langbot-agent-runner/ # 外部服务 runner 仓库
|
||||
acp-agent-runner/ claude-code-agent/ codex-agent/ dify-agent/ n8n-agent/ ...
|
||||
acp-agent-runner/ claude-code-agent/ codex-agent/ dify-agent/ n8n-agent/ ...
|
||||
```
|
||||
|
||||
后续可聚合进 monorepo,也可继续独立发布——这个选择不影响协议设计。重复逻辑优先沉淀到 SDK 或明确的共享 helper 包,不要把宿主私有结构泄漏给插件。旧 `src/langbot/pkg/provider/runners/*` 只作为历史行为对齐基准;当前未发布分支不提供旧内置 runner 的运行时 fallback。
|
||||
插件已经合仓,但仍独立发布;详见 `plugins/RELEASING.md`。这个选择不影响协议设计。重复逻辑优先沉淀到 SDK 或明确的共享 helper 包,不要把宿主私有结构泄漏给插件。旧 `src/langbot/pkg/provider/runners/*` 只作为历史行为对齐基准;当前未发布分支不提供旧内置 runner 的运行时 fallback。
|
||||
|
||||
## 2. 插件命名和 runner id
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Runner Pluginization Status
|
||||
|
||||
本文档是 `docs/agent-runner-pluginization/` 的状态事实源。协议 schema 仍以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) 为准;测试步骤以 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md) 为准;安全发布门槛以 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md) 为准。
|
||||
本文档是 `dev-docs/agent-runner-pluginization/` 的状态事实源。协议 schema 仍以 [PROTOCOL_V1.md](./PROTOCOL_V1.md) 为准;测试步骤以 [RUNNER_QA_GUIDE.md](./RUNNER_QA_GUIDE.md) 为准;安全发布门槛以 [SECURITY_HARDENING.md](./SECURITY_HARDENING.md) 为准。
|
||||
|
||||
状态快照日期:2026-09-05。代码基线为 LangBot `dev/4.11.x` / `a4d36aa2d` 和 SDK `dev/4.11.x` / `f1da058`;本文是本地检视快照,不代表远端最新状态或正式发布批准。
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
@@ -197,4 +197,4 @@ Pipeline 与 Agent 是长期并存、场景不同的同级处理器。Pipeline
|
||||
| **LangBot**(后端) | 适配器实现、事件路由引擎、Bot/Agent 实体、Runner Host 编排 |
|
||||
| **LangBot**(前端) | Bot 事件处理器编排面板 |
|
||||
| **langbot-wiki** | 新架构文档、插件开发指南更新、适配器开发指南 |
|
||||
| **langbot-plugin-demo** | 示例更新(使用新事件和 API) |
|
||||
| **LangBot/plugins** | 示例更新(使用新事件和 API,插件保持独立发布) |
|
||||
+28
-1
@@ -21,7 +21,7 @@ Status: partial migration. The EBA adapter structure, manifest, converters, cach
|
||||
|
||||
| Event | Evidence | Notes |
|
||||
| --- | --- | --- |
|
||||
| `message.received` | adapter-live, unit | `C2C_MESSAGE_CREATE`, `DIRECT_MESSAGE_CREATE`, `GROUP_AT_MESSAGE_CREATE`, and `AT_MESSAGE_CREATE` map to common `MessageReceivedEvent`. A real WebSocket-mode QQ Official bot reached the LangBot pipeline on `dev.rockchin.top`; plugin JSONL evidence remains pending. |
|
||||
| `message.received` | adapter-live, unit | `C2C_MESSAGE_CREATE`, `DIRECT_MESSAGE_CREATE`, `GROUP_AT_MESSAGE_CREATE`, `GROUP_MESSAGE_CREATE`, and `AT_MESSAGE_CREATE` map to common `MessageReceivedEvent`. Full-group mode has protocol regression coverage; the earlier live probe below covered the original message path. |
|
||||
| `message.reaction` | unit | `MESSAGE_REACTION_ADD` and `MESSAGE_REACTION_REMOVE` map to common `MessageReactionEvent`. Live gateway evidence is pending. |
|
||||
| `group.member_joined` | unit | `GUILD_MEMBER_ADD` and `GROUP_MEMBER_ADD` map to common `MemberJoinedEvent` when the gateway payload carries a group/guild/channel ID and member openid. |
|
||||
| `group.member_left` | unit | `GUILD_MEMBER_REMOVE` and `GROUP_MEMBER_REMOVE` map to common `MemberLeftEvent`. Live gateway evidence is pending. |
|
||||
@@ -29,6 +29,33 @@ Status: partial migration. The EBA adapter structure, manifest, converters, cach
|
||||
| `bot.removed_from_group` | unit | `GUILD_DELETE` and `GROUP_DEL_ROBOT` map to common `BotRemovedFromGroupEvent`. |
|
||||
| `platform.specific` | unit | Unmapped gateway events are emitted as structured `PlatformSpecificEvent`; live evidence is pending. |
|
||||
|
||||
## Full-group messages
|
||||
|
||||
After a group owner enables QQ's "receive all messages" permission, QQ delivers
|
||||
`GROUP_MESSAGE_CREATE`, including messages that mention the bot. Both the EBA
|
||||
and legacy adapters accept this event over WebSocket and unified Webhook, retain
|
||||
the sender's `member_openid` (with the older `openid` alias as fallback), and use
|
||||
the original message ID for passive group replies.
|
||||
|
||||
Ordinary messages and images do not receive a bot `At` component. A full-group
|
||||
message receives that component only when QQ supplies a boolean `mentions[].is_you`
|
||||
equal to `true` and a matching native `<@ID>` or `<@!ID>` token in its text. Only
|
||||
the bot's own token is removed; mentions of other members remain text. Nicknames,
|
||||
`bot: true` alone, and a text that looks like a mention do not authorize a reply.
|
||||
The EBA component targets the adapter's configured bot account so existing
|
||||
`is_at_bot` route filters continue to match.
|
||||
|
||||
Default EBA group routes requiring `is_at_bot` still exclude unmentioned group
|
||||
messages. Custom event routes or plugin subscriptions may opt into ordinary
|
||||
messages. This change does not alter QQ permissions, existing bot configuration,
|
||||
event bindings, or plugin subscriptions.
|
||||
|
||||
`tests/unit_tests/platform/test_qqofficial_full_group_messages.py` exercises the
|
||||
real adapters and shared QQ parser with external gateway/download/send transports
|
||||
substituted. It covers text, images, self/other/unverified mentions, sender aliases,
|
||||
legacy @-only mode, private messages, and passive reply targets. This is protocol
|
||||
regression coverage, not a claim of a fresh live QQ probe of the whole upstream build.
|
||||
|
||||
## Common APIs
|
||||
|
||||
| API | Evidence | Notes |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user