chore(merge): sync master into interactive cards

This commit is contained in:
fdc310
2026-10-30 02:03:05 +08:00
2254 changed files with 247681 additions and 641 deletions
+12
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
/plugins export-ignore
*.lbpkg export-ignore
+17
View File
@@ -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
+3
View File
@@ -2,6 +2,9 @@ name: Build Dev Image
on:
push:
paths-ignore:
- "plugins/**"
- ".github/workflows/plugins-*.yml"
workflow_dispatch:
jobs:
+2 -1
View File
@@ -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
+1
View File
@@ -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"
+54
View File
@@ -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}."
+43
View File
@@ -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"
+15 -18
View File
@@ -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
+251
View File
@@ -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
+1
View File
@@ -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
+29
View File
@@ -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
+3 -25
View File
@@ -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
+67
View File
@@ -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
+39
View File
@@ -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
+2
View File
@@ -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: |
+2 -25
View File
@@ -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
+62
View File
@@ -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
+90
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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
+5 -3
View File
@@ -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
View File
@@ -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.
+2
View File
@@ -0,0 +1,2 @@
prune plugins
global-exclude *.lbpkg
+4
View File
@@ -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)
[![Deploy on LangBot Cloud](res/langbot-cloud.svg)](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)
+4
View File
@@ -63,6 +63,8 @@ LangBot 是一个**开源的生产级平台**,用于构建 AI 驱动的即时
## 快速开始
官方插件源码索引:[plugins/README.md](plugins/README.md) · [机器可读目录](plugins/catalog.json) · [插件独立上架流程](plugins/RELEASING.md)。插件保持独立安装,不随 Core 发版打包。
### ☁️ LangBot Cloud(推荐)
[![Deploy on LangBot Cloud](res/langbot-cloud.svg)](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)
+2
View File
@@ -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)
+2
View File
@@ -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)
+2
View File
@@ -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)
+2
View File
@@ -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)
+2
View File
@@ -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)
+2
View File
@@ -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)
+2
View File
@@ -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)
+7
View File
@@ -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
```
+72
View File
@@ -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 |
@@ -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,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,插件保持独立发布) |
@@ -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