docs: stateless certified plugins

Comprehensive documentation for stateless singleton component model supporting SDK 0.7.0 and certificate v2 envelope.
This commit is contained in:
RockChinQ
2026-09-28 13:25:36 +08:00
committed by GitHub
parent 6f1614b494
commit f6b6a635c2
15 changed files with 164 additions and 85 deletions
+46 -15
View File
@@ -1,27 +1,58 @@
---
title: "Certified Plugins"
description: "How LangBot identifies reviewed plugin releases and how the allowance works in Cloud."
title: "Certified plugins and stateless components"
description: "Why certified plugins share workers and how to implement stateless-v1 components."
---
A **Certified Plugin** is a specific plugin release that LangBot has reviewed for the certified-plugin requirements. Certification belongs to the exact published version, not to the author account or plugin name alone.
A **Certified Plugin** badge grants a specific immutable plugin artifact eligibility for cross-tenant shared execution. Installations of the same artifact digest may share one Worker, one `BasePlugin` object and one instance of each component. Each installation contributes only lightweight configuration, authority and lifecycle context. “Each component” means one instance for every declared component manifest across EventListener, Tool, Command, KnowledgeEngine, Parser, Page and Runner.
## What you see
## Declaration
Certified Plugin releases show a **Certified Plugin** badge in the marketplace and plugin details. A plugin without the badge is still a normal plugin; it is not treated as unsafe simply because it is not certified.
```yaml
execution:
python:
path: main.py
attr: Plugin
sharedRuntime: shared-runtime-v1
componentModel: stateless-v1
```
In LangBot Cloud, Certified Plugins and other plugins use separate allowances. The install screen shows which allowance applies before you install. Existing installations are not removed just because an older release does not carry certification.
Both fields are required. The declaration is an application, not approval: advisory source review, explicit human approval and a signature over the exact artifact remain mandatory.
## Using a Certified Plugin
## Stateless component contract
1. Open the plugin in the marketplace and select a version carrying the Certified Plugin badge.
2. Install or update it from LangBot's plugin management page.
3. Configure it for your workspace as usual.
4. Use the plugin log view for workspace-scoped diagnostic records when available.
- Plugin and component `initialize()` methods are process-scoped and run once per Worker.
- Component objects must be re-entrant because calls from multiple Workspaces can overlap.
- On `BasePlugin`, read installation configuration with `self.get_config()`. In a component, use `self.get_plugin_config()` or `self.plugin.get_config()`.
- Never retain configuration, credentials, Workspace/installation identity or request data in instance fields or module globals.
- Do not capture tenant configuration in `initialize()`.
- Use SDK Host APIs for storage, files, credentials, logs, models and other platform capabilities. Authority comes from the task-local invocation context.
- Durable tenant state belongs in Host services. Process caches may contain artifact-level public data only, or must use the complete installation binding as their key and support revocation cleanup.
- Tenant-bearing detached tasks and threads are forbidden in `stateless-v1`; the SDK does not yet expose a plugin-facing ownership/revocation API.
## For plugin authors
The contract intentionally preserves serverless-ready semantics, but current releases still run long-lived Workers and do not offer a serverless deployment mode.
Certification is requested by declaring the supported certified-runtime mode in the plugin manifest and submitting a new immutable release. The declaration is only a request: LangBot Space performs an advisory automated review, a human reviewer makes the publication decision, and certification is issued only for the exact reviewed package.
## Configuration example
Authors should keep configuration, storage, logs, background work, and credentials scoped to the active installation and workspace. Do not rely on mutable process-global state or raw process logs. If a release cannot meet those requirements, publish it as a normal dedicated plugin instead.
```python
class LookupTool(Tool):
async def initialize(self):
self.transport = StatelessHTTPTransport()
See [publishing to the marketplace](/en/plugin/dev/publish/market) for submission guidance.
async def call(self, params, **kwargs):
config = self.get_plugin_config()
return await self.transport.lookup(config["endpoint"], params)
```
Do not retain tenant data during process initialization:
```python
async def initialize(self):
self.endpoint = self.plugin.config["endpoint"]
self.api_key = self.plugin.config["api_key"]
```
Shared initialization has no tenant config, so this reads empty/stale data or fails. Any attempt to retain invocation config on the shared object blocks certification.
## Legacy compatibility
Legacy source and unsigned packages remain compatible on dedicated Workers. In OSS, packages without a trusted `stateless-v1` v2 certificate stay dedicated according to admission policy. Cloud can reject packages that do not meet its certificate policy before installation; existing installations are not proof of new eligibility. Changing only the manifest does not satisfy certification review.
+3
View File
@@ -28,6 +28,9 @@ execution:
python:
path: main.py # Do not modify
attr: HelloPlugin # Do not modify
# Add only after migrating to the stateless singleton certification contract:
# sharedRuntime: shared-runtime-v1
# componentModel: stateless-v1
```
Plugin multilingual support follows the [RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646) standard. Currently supported languages include:
+9 -12
View File
@@ -79,10 +79,9 @@ class DashboardPage(Page):
# request.body: Request body (parsed JSON, or None)
if request.endpoint == '/stats' and request.method == 'GET':
# Access shared plugin state via self.plugin
return PageResponse.ok({
'total': len(self.plugin.entries),
})
# Read installation-scoped data from a Host storage API.
entries = await self.load_entries_for_current_installation()
return PageResponse.ok({'total': len(entries)})
return PageResponse.fail(f'Unknown endpoint: {request.endpoint}')
```
@@ -200,24 +199,22 @@ from langbot_plugin.api.definition.components.page import Page, PageRequest, Pag
class ManagerPage(Page):
async def handle_api(self, request: PageRequest) -> PageResponse:
plugin = self.plugin
if request.endpoint == '/entries' and request.method == 'GET':
return PageResponse.ok({'entries': plugin.entries})
entries = await self.load_entries_for_current_installation()
return PageResponse.ok({'entries': entries})
if request.endpoint == '/entries' and request.method == 'POST':
question = (request.body or {}).get('question', '').strip()
answer = (request.body or {}).get('answer', '').strip()
if not question or not answer:
return PageResponse.fail('question and answer are required')
entry = plugin.add_entry(question, answer)
await plugin.persist()
entry = {'question': question, 'answer': answer}
await self.save_entry_for_current_installation(entry)
return PageResponse.ok({'entry': entry})
if request.endpoint == '/entries' and request.method == 'DELETE':
entry_id = (request.body or {}).get('id', '')
if plugin.delete_entry(entry_id):
await plugin.persist()
if await self.delete_entry_for_current_installation(entry_id):
return PageResponse.ok({'deleted': entry_id})
return PageResponse.fail('entry not found')
@@ -242,6 +239,6 @@ Full example code is available at [FAQManager plugin](https://github.com/langbot
## Notes
- Pages run inside a `sandbox="allow-scripts allow-forms"` iframe and cannot open popups or navigate the parent page
- Access shared plugin state via `self.plugin` — Page and Tool components can share data
- Certified Pages must keep tenant data in installation-scoped Host storage. `self.plugin` is process-wide and must not hold tenant entries. The storage helper names above are illustrative; use the current SDK Host storage APIs available to your component.
- Use `PageResponse.ok()` and `PageResponse.fail()` to construct responses for consistent formatting
- The Page SDK `<script>` tag must be placed before any code that uses the `langbot` object
+1 -1
View File
@@ -80,7 +80,7 @@ You can now check your plugin submission task at [LangBot Plugin Marketplace](ht
## Request Certified Plugin review
If your release is designed for the Certified Plugin requirements, declare the supported certified runtime mode in `manifest.yaml` and submit a **new version**. This declaration requests review; it does not grant a badge or change runtime behavior by itself.
If your release is designed for certification, declare both `execution.sharedRuntime: shared-runtime-v1` and `execution.componentModel: stateless-v1` in `manifest.yaml`, then submit a **new version**. The advisory review verifies singleton components are stateless, re-entrant, read config per invocation and retain no tenant data. The declaration alone grants no badge or runtime privilege.
LangBot Space checks the exact submitted package, runs an advisory code review, and requires a human approval before a Certified Plugin badge can be issued. Certification is bound to the reviewed package version and checksum. Keep workspace configuration, storage, credentials, logs, and background work scoped to the active installation. Do not rely on mutable process-global state.
+2
View File
@@ -15,3 +15,5 @@ logger.info('This is an info message') # Use logger.info to record info level l
logger.warning('This is a warning message')
logger.error('This is an error message')
```
For Certified Plugins, raw process logs cannot be safely attributed to one Workspace and are operator-only. Runner code may use `RunnerContext.log()` for installation-scoped tenant-visible logs. Other component kinds do not yet have a generic tenant-visible logging API; their raw process logs are operator-only. Never emit tenant configuration, credentials or request data from module-global background work.
+25 -16
View File
@@ -1,27 +1,36 @@
---
title: "認証プラグイン"
description: "LangBot が審査済みのプラグインリリースを示す方法と、Cloud の利用枠について説明します。"
title: "認証プラグインとステートレスコンポーネント"
description: "認証プラグインの共有 Worker と stateless-v1 実装規約。"
---
**認証プラグイン**は、LangBot の認証要件を満たした特定のプラグインリリースです。認証は作者アカウントやプラグイン名ではなく、公開済みの正確なバージョンに紐付きます。
**認証プラグイン**は単なる安全バッジではありません。変更不能な特定の artifact に対して、テナントをまたぐ共有実行を許可する資格です。同じ artifact digest の installation は、1 つの Worker、1 つの `BasePlugin`、各種コンポーネントの単一インスタンスを共有します。installation ごとに保持するのは、軽量な設定・権限・ライフサイクルコンテキストだけです。「各コンポーネント」は EventListener、Tool、Command、KnowledgeEngine、Parser、Page、Runner の宣言済み manifest ごとに 1 インスタンスという意味です。
## 表示される内容
## 宣言
認証済みリリースには、マーケットプレイスとプラグイン詳細に**認証プラグイン**の表示が出ます。表示がないプラグインも通常のプラグインであり、未認証だからといって安全でないことを意味しません。
```yaml
execution:
python:
path: main.py
attr: Plugin
sharedRuntime: shared-runtime-v1
componentModel: stateless-v1
```
LangBot Cloud では、認証プラグインとその他のプラグインで利用枠が分かれています。インストール前に、どちらの利用枠を使用するかが表示されます。以前の認証がないバージョンをすでに利用していても、自動的に削除されることはありません。
2 つのフィールドは必須です。宣言だけでは認証されません。ソースの補助レビュー、人による明示的な承認、正確な artifact への署名が必要です。
## 認証プラグインを使う
## ステートレス規約
1. マーケットプレイスでプラグインを開き、認証プラグイン表示のあるバージョンを選びます。
2. LangBot のプラグイン管理画面からインストールまたは更新します。
3. ワークスペースに合わせて通常どおり設定します。
4. 利用できる場合は、プラグインログで現在のワークスペースに関連する診断記録を確認します。
- プラグインとコンポーネントの `initialize()` は Worker ごとに一度だけ実行します。
- 異なる Workspace の呼び出しが同じオブジェクト上で並行するため、コンポーネントは再入可能にします。
- `BasePlugin` では `self.get_config()`、コンポーネントでは `self.get_plugin_config()` または `self.plugin.get_config()` で現在の installation 設定を取得します。
- 設定、認証情報、Workspace/installation ID、リクエストデータをインスタンス属性やモジュールグローバルに保存しません。
- `initialize()` でテナント設定を読み取り、保持してはいけません。
- ストレージ、ファイル、認証情報、ログ、モデルなどは SDK の Host API を使います。権限は task-local invocation context から解決されます。
- 永続的なテナント状態は Host サービスに置きます。プロセス内キャッシュは artifact 共通データだけにするか、完全な installation binding をキーにし、失効時に解放できるようにします。
- `stateless-v1` ではテナントデータを持つ detached task/thread を禁止します。SDK はまだプラグイン向けの所有権・失効 API を提供していません。
## プラグイン作者向け
この規約は将来の serverless 実行に適した意味論を保ちますが、現在は長寿命 Worker を使用しており、serverless 配備モードは提供していません。
作者は manifest で認証ランタイムモードの対応を宣言し、新しい不変リリースを提出できます。宣言は申請に過ぎません。LangBot Space は補助的なコードレビューを行い、人が公開を承認します。認証されるのは審査済みの正確なパッケージだけです。
## 旧プラグインとの互換性
設定、ストレージ、ログ、バックグラウンド処理、資格情報は、現在のインストールとワークスペースに必ず紐付けてください。可変なプロセスグローバル状態や生のプロセスログに依存しないでください。要件を満たせないリリースは、通常の専用プラグインとして公開できます。
提出方法は[マーケットプレイスへの公開](/ja/plugin/dev/publish/market)を参照してください。
既存のソースと未署名パッケージは dedicated Worker で互換性を維持します。OSS では、信頼済み `stateless-v1` v2 証明書がないパッケージはポリシーに従って dedicated になります。Cloud は証明書ポリシーを満たさないパッケージをインストール前に拒否する場合があります。manifest だけの変更では認証要件を満たしません。
+3
View File
@@ -28,6 +28,9 @@ execution:
python:
path: main.py # 変更しないでください
attr: HelloPlugin # 変更しないでください
# ステートレス単一インスタンス規約へ移行し、認証を申請する場合のみ追加:
# sharedRuntime: shared-runtime-v1
# componentModel: stateless-v1
```
プラグインの多言語サポートは[RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646)標準に従います。現在サポートされている言語は次のとおりです:
+9 -12
View File
@@ -79,10 +79,9 @@ class DashboardPage(Page):
# request.body: リクエストボディ(解析済みJSON、またはNone)
if request.endpoint == '/stats' and request.method == 'GET':
# self.pluginでプラグインインスタンスの共有状態にアクセス
return PageResponse.ok({
'total': len(self.plugin.entries),
})
# Host storage から現在の installation のデータを読む
entries = await self.load_entries_for_current_installation()
return PageResponse.ok({'total': len(entries)})
return PageResponse.fail(f'不明なエンドポイント: {request.endpoint}')
```
@@ -200,24 +199,22 @@ from langbot_plugin.api.definition.components.page import Page, PageRequest, Pag
class ManagerPage(Page):
async def handle_api(self, request: PageRequest) -> PageResponse:
plugin = self.plugin
if request.endpoint == '/entries' and request.method == 'GET':
return PageResponse.ok({'entries': plugin.entries})
entries = await self.load_entries_for_current_installation()
return PageResponse.ok({'entries': entries})
if request.endpoint == '/entries' and request.method == 'POST':
question = (request.body or {}).get('question', '').strip()
answer = (request.body or {}).get('answer', '').strip()
if not question or not answer:
return PageResponse.fail('質問と回答は必須です')
entry = plugin.add_entry(question, answer)
await plugin.persist()
entry = {'question': question, 'answer': answer}
await self.save_entry_for_current_installation(entry)
return PageResponse.ok({'entry': entry})
if request.endpoint == '/entries' and request.method == 'DELETE':
entry_id = (request.body or {}).get('id', '')
if plugin.delete_entry(entry_id):
await plugin.persist()
if await self.delete_entry_for_current_installation(entry_id):
return PageResponse.ok({'deleted': entry_id})
return PageResponse.fail('エントリが見つかりません')
@@ -242,6 +239,6 @@ await langbot.api('/entries', { id: '...' }, 'DELETE');
## 注意事項
- ページは`sandbox="allow-scripts allow-forms"`のiframe内で実行され、ポップアップを開いたり親ページをナビゲートすることはできません
- `self.plugin`でプラグインインスタンスの共有状態にアクセスできます — PageとToolコンポーネントはデータを共有できます
- 認証 Page のテナントデータは installation-scoped Host storage に保存します。`self.plugin` はプロセス全体で共有されるため、テナントのエントリを保持してはいけません。上記 helper 名は説明用です。現在の SDK Host storage API を使用してください。
- `PageResponse.ok()`と`PageResponse.fail()`を使用してレスポンスを構築し、一貫したフォーマットを確保してください
- Page SDKの`<script>`タグは、`langbot`オブジェクトを使用するコードの前に配置する必要があります
+1 -1
View File
@@ -80,7 +80,7 @@ Plugin built successfully: dist/tmp/RockChinQ-HelloPlugin-0.1.0.lbpkg
## 認証プラグイン審査を申請する
認証プラグイン要件に対応するリリースでは、`manifest.yaml` に対応する認証ランタイムモードを宣言し、**新しいバージョン**を提出します。宣言は審査申請であり、表示の付与やランタイム動作の変更を単独で行うものではありません。
認証を申請するリリースでは、`manifest.yaml` に `execution.sharedRuntime: shared-runtime-v1` と `execution.componentModel: stateless-v1` を同時に宣言し、**新しいバージョン**を提出します。補助レビューでは、単一インスタンスのコンポーネントがステートレスかつ再入可能で、呼び出しごとに設定を取得し、テナントデータを保持しないことを確認します。宣言だけで認証や共有実行権限が付与されることはありません。
LangBot Space は提出された正確なパッケージを確認し、補助的なコードレビューを実行したうえで、人による承認後にのみ認証プラグインの表示を発行します。認証は審査済みのパッケージバージョンとチェックサムに紐付きます。ワークスペース設定、ストレージ、資格情報、ログ、バックグラウンド処理は、現在のインストールに必ず紐付けてください。可変なプロセスグローバル状態に依存しないでください。
+2
View File
@@ -15,3 +15,5 @@ logger.info('This is an info message') # logger.info を使用して info レ
logger.warning('This is a warning message')
logger.error('This is an error message')
```
認証プラグインでは、生のプロセスログを 1 つの Workspace に安全に帰属できないため、運用診断専用です。Runner は `RunnerContext.log()` で installation-scoped なテナント表示ログを記録できます。その他のコンポーネントには汎用テナントログ API がまだないため、生のプロセスログは運用者専用です。モジュールグローバルなバックグラウンド処理から設定、認証情報、リクエストデータを出力してはいけません。
+48 -15
View File
@@ -1,27 +1,60 @@
---
title: "认证插件"
description: "了解 LangBot 如何标识已审核的插件版本,以及 Cloud 中的额度使用方式。"
title: "认证插件与无状态组件"
description: "认证插件为什么共享 Worker,以及 stateless-v1 组件规范。"
---
**认证插件**是通过 LangBot 审核要求的某个具体插件版本。认证绑定到已发布的精确版本,而不是作者账号或插件名称。
**认证插件**不是普通安全徽章,而是平台对某个精确、不可变插件版本授予的跨租户共享执行资格。相同 artifact digest 的安装可以共享一个 Worker、一个 `BasePlugin` 实例和一组组件实例;每个安装只保留轻量的配置、权限与生命周期上下文。这里的“一组组件”指每个已声明组件 manifest 各一个实例,包括 EventListener、Tool、Command、KnowledgeEngine、Parser、Page 和 Runner。
## 你会看到什么
## 声明
认证版本会在扩展市场和插件详情中显示**认证插件**标识。没有该标识的插件仍是正常插件;未认证不等于不安全。
```yaml
execution:
python:
path: main.py
attr: Plugin
sharedRuntime: shared-runtime-v1
componentModel: stateless-v1
```
在 LangBot Cloud 中,认证插件与其他插件使用不同的额度。安装前,界面会明确提示本次安装使用哪一类额度。已有安装不会因为旧版本没有认证而被自动删除。
两个字段必须同时出现。声明只是申请,不能代替辅助审查、人工批准和精确 artifact 签名。
## 使用认证插件
## 无状态组件规范
1. 在市场打开插件,选择带有认证插件标识的版本。
2. 从 LangBot 的插件管理页面安装或更新。
3. 按工作区正常配置并使用。
4. 可用时,通过插件日志查看与当前工作区相关的诊断记录。
- `BasePlugin.initialize()` 和组件 `initialize()` 是进程级初始化,每个 Worker 只运行一次。
- 所有组件实例都可能同时处理不同工作区的调用,必须可重入。
- 在 `BasePlugin` 中使用 `self.get_config()`;在组件中使用 `self.get_plugin_config()` 或 `self.plugin.get_config()` 读取当前安装配置。
- 不得在实例字段或模块全局变量中保存配置、凭据、Workspace/installation ID 或请求数据。
- 不得在 `initialize()` 中读取并保存租户配置。
- 存储、文件、凭据、日志、模型及其他平台能力必须通过 SDK Host API 访问;SDK 从当前 task-local invocation context 取得权限。
- 持久租户状态必须保存在 Host 服务中。进程内 cache 只能保存 artifact 级公共数据,或以完整 installation binding 为 key 并支持撤销清理。
- `stateless-v1` 当前禁止携带租户数据的 detached 后台任务或线程;SDK 尚未提供插件侧所有权/撤销 API。
## 给插件作者
这些约束为未来 serverless 组件执行保留接口语义;当前版本仍运行长生命周期 Worker,并未提供 serverless 部署模式。
作者可在 manifest 中声明希望支持的认证运行模式,并提交新的不可变版本。声明只是申请:LangBot Space 会先进行辅助代码审查,再由人工审核发布;只有经过审核的精确插件包才会获得认证。
## 配置访问示例
请让配置、存储、日志、后台工作和凭据都与当前安装和工作区绑定。不要依赖可变的进程全局状态或原始进程日志。无法满足这些要求的版本,仍可作为普通专属插件发布。
```python
class LookupTool(Tool):
async def initialize(self):
self.transport = StatelessHTTPTransport()
提交说明请参阅[发布到插件市场](/zh/plugin/dev/publish/market)。
async def call(self, params, **kwargs):
config = self.get_plugin_config()
return await self.transport.lookup(config["endpoint"], params)
```
不要这样写:
```python
async def initialize(self):
self.endpoint = self.plugin.config["endpoint"]
self.api_key = self.plugin.config["api_key"]
```
共享初始化没有租户配置,因此这段代码会读取空值/旧值或失败;任何把调用配置保存到共享对象的做法都是认证 blocker。
## 兼容旧插件
旧源码和未签名插件包仍兼容 dedicated Worker。在 OSS 中,没有受信任 `stateless-v1` v2 证书的插件按准入策略保持 dedicated;Cloud 可能在安装前拒绝不满足证书策略的插件。已有安装不代表获得新共享资格。仅修改 manifest 而不迁移代码不会通过认证审查。
认证流程仍包括辅助代码审查和人工批准,并将插件身份、版本、digest、`shared-runtime-v1` 与 `stateless-v1` 一起签入证书。提交方式参阅[发布到插件市场](/zh/plugin/dev/publish/market)。
+3
View File
@@ -28,6 +28,9 @@ execution:
python:
path: main.py # 请勿修改
attr: HelloPlugin # 请勿修改
# 仅在申请认证且代码已迁移为无状态单例时添加:
# sharedRuntime: shared-runtime-v1
# componentModel: stateless-v1
```
插件多语言遵循 [RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646) 标准,目前支持的语言有:
+9 -12
View File
@@ -79,10 +79,9 @@ class DashboardPage(Page):
# request.body: 请求体(已解析的 JSON,或 None)
if request.endpoint == '/stats' and request.method == 'GET':
# 通过 self.plugin 访问插件实例的共享状态
return PageResponse.ok({
'total': len(self.plugin.entries),
})
# 从 Host 存储读取当前 installation 的数据
entries = await self.load_entries_for_current_installation()
return PageResponse.ok({'total': len(entries)})
return PageResponse.fail(f'Unknown endpoint: {request.endpoint}')
```
@@ -200,24 +199,22 @@ from langbot_plugin.api.definition.components.page import Page, PageRequest, Pag
class ManagerPage(Page):
async def handle_api(self, request: PageRequest) -> PageResponse:
plugin = self.plugin
if request.endpoint == '/entries' and request.method == 'GET':
return PageResponse.ok({'entries': plugin.entries})
entries = await self.load_entries_for_current_installation()
return PageResponse.ok({'entries': entries})
if request.endpoint == '/entries' and request.method == 'POST':
question = (request.body or {}).get('question', '').strip()
answer = (request.body or {}).get('answer', '').strip()
if not question or not answer:
return PageResponse.fail('question and answer are required')
entry = plugin.add_entry(question, answer)
await plugin.persist()
entry = {'question': question, 'answer': answer}
await self.save_entry_for_current_installation(entry)
return PageResponse.ok({'entry': entry})
if request.endpoint == '/entries' and request.method == 'DELETE':
entry_id = (request.body or {}).get('id', '')
if plugin.delete_entry(entry_id):
await plugin.persist()
if await self.delete_entry_for_current_installation(entry_id):
return PageResponse.ok({'deleted': entry_id})
return PageResponse.fail('entry not found')
@@ -242,6 +239,6 @@ await langbot.api('/entries', { id: '...' }, 'DELETE');
## 注意事项
- 页面运行在 `sandbox="allow-scripts allow-forms"` 的 iframe 中,不能弹窗或导航父页面
- 通过 `self.plugin` 访问插件实例的共享状态,Page 和 Tool 组件可以共享数据
- 认证 Page 必须把租户数据放在 installation-scoped Host 存储中;`self.plugin` 是进程级共享对象,不能保存租户条目。上面的存储 helper 名仅为示意,请使用当前组件可用的 SDK Host 存储 API。
- 使用 `PageResponse.ok()` 和 `PageResponse.fail()` 构造响应,确保格式一致
- Page SDK 的 `<script>` 标签必须放在使用 `langbot` 对象的代码之前
+1 -1
View File
@@ -80,7 +80,7 @@ Plugin built successfully: dist/tmp/RockChinQ-HelloPlugin-0.1.0.lbpkg
## 申请认证插件审核
如果该版本准备满足认证插件要求,请在 `manifest.yaml` 中声明支持的认证运行模式,并提交一个**新版本**。声明只是申请,不会自行获得标识,也不会自行改变运行行为。
如果该版本准备申请认证,请在 `manifest.yaml` 中同时声明 `execution.sharedRuntime: shared-runtime-v1` 和 `execution.componentModel: stateless-v1`,并提交一个**新版本**。辅助审查会确认单例组件无状态、可重入、按调用读取配置且不持有租户数据。声明本身不会授予认证标识或共享运行权限。
LangBot Space 会检查本次提交的精确插件包,进行辅助代码审查,并在人工批准后才签发认证插件标识。认证绑定到已审核的插件版本与校验和。请让工作区配置、存储、凭据、日志和后台工作都与当前安装绑定,不要依赖可变的进程全局状态。
+2
View File
@@ -15,3 +15,5 @@ logger.info('This is an info message') # 使用 logger.info 记录信息级别
logger.warning('This is a warning message')
logger.error('This is an error message')
```
认证插件中,原始进程日志无法安全归属到某个工作区,只用于运维诊断。Runner 可在调用中使用 `RunnerContext.log()` 记录安装作用域、租户可见日志。其他组件目前没有通用租户日志 API,其原始进程日志仅供运维;不要在模块全局后台线程中输出租户配置、凭据或请求数据。