diff --git a/docs/en/development/plugin-contract.md b/docs/en/development/plugin-contract.md new file mode 100644 index 000000000..042c8dc1d --- /dev/null +++ b/docs/en/development/plugin-contract.md @@ -0,0 +1,170 @@ +--- +title: Plugin-visible diagnostics contract +description: Contract for the Plugin-visible integration diagnostics RFC. +--- + +# Plugin-visible diagnostics contract + +This page records the contract introduced by the Plugin-visible integration diagnostics RFC. It covers the diagnostic +slice only; shared service state, service lifecycle, installation, and platform adapter contracts belong to their +respective RFCs and implementation slices. + +The contract currently applies to Codex, Claude Code, DeepSeek Harness (DSH), OpenClaw, Pi, and Hermes. Bub is out of +scope until it has a Plugin channel, implementation, tests, and support qualification. + +The words **MUST**, **SHOULD**, and **MAY** are normative requirements for plugin implementation and review. + +This contract is derived from [RFC 1299: Local Server availability and service installation](../rfcs/1299_local_server_availability_and_service_installation.md). +The original [RFC PR #1299](https://github.com/oceanbase/powercontext/pull/1299) is tracked by [issue #1298](https://github.com/oceanbase/powercontext/issues/1298). + +## What a plugin must report + +A plugin MUST report a PowerContext failure when a Plugin-visible operation cannot complete because of one of the +classified backend failures. The operation may be context preparation, recall, capture, flush, a direct tool or slash +command, or a health/status check. + +The plugin MUST use typed client errors to make the classification. It MUST NOT classify failures by matching text in +an exception message. + +| Outcome | Classification | +| --- | --- | +| `authentication_failed` | A typed authentication failure, normally HTTP 401. | +| `version_mismatch` | HTTP 404 from a required compatibility or availability endpoint; it MUST NOT be inferred from a direct resource lookup. | +| `server_unavailable` | Connection failure, timeout, aborted request, or HTTP 503. | +| `invalid_response` | Malformed JSON, invalid response shape, decoding/schema failure, or an otherwise unclassified HTTP failure after operation-specific domain classification. | + +An empty but valid result is not a failure diagnostic. In particular, an empty memory result MUST NOT be reported as +`server_unavailable`. + +### Operation-specific domain errors + +Direct tools and commands can receive valid domain errors after the Server has processed the request. The client MUST +classify typed domain errors before applying the Plugin-visible diagnostic mapping: + +| Domain result | Direct operation meaning | +| --- | --- | +| `not_found` | HTTP 404 for a missing Memory entry, citation, or other requested resource. | +| `conflict` | HTTP 409 for a revision, source, citation, or other operation conflict. | +| `invalid_request` | HTTP 422 for a request that violates the wire or application contract. | + +These domain results MUST be preserved in the direct operation result and MUST NOT be rewritten as +`version_mismatch` or `invalid_response`. A 404 from an explicitly identified compatibility or availability endpoint +remains `version_mismatch`; the operation identifier or endpoint contract, not the status code alone, determines that +classification. + +## Diagnostic event format + +Each diagnostic MUST be one JSON object written as one line through the Plugin's supported channel. For hook-based +hosts, the event is encoded as the top-level `systemMessage` value in the successful stdout hook JSON; the event is +not required to be rendered as a standalone stdout line. + +```json +{ + "component": "powercontext.openclaw", + "event": "context_prepare", + "outcome": "server_unavailable", + "recovery": "powercontext doctor" +} +``` + +### Fields + +| Field | Requirement | +| --- | --- | +| `component` | Stable Plugin-qualified name, such as `powercontext.dsh` or `powercontext.claude_code.recall`. | +| `event` | Short lower-snake-case event, such as `context_prepare`, `capture_source`, `tool_call`, or `status`. It MUST NOT contain a prompt, query, URL, or identifier. | +| `outcome` | One of the four outcomes defined above. | +| `http_status` | Optional integer for an HTTP response. It MUST NOT be fabricated for a transport failure. | +| `recovery` | MUST equal `powercontext doctor` for `server_unavailable`; normally omitted for other outcomes. | + +Additional fields MAY be included only when they are bounded, non-sensitive, and useful to interpret the lifecycle +event. For example, a numeric `content_bytes` or a bounded `context_status` is acceptable. + +### Examples + +```json +{"component":"powercontext.codex.recall","event":"context_prepare","outcome":"authentication_failed","http_status":401} +{"component":"powercontext.pi","event":"context_prepare","outcome":"version_mismatch","http_status":404} +{"component":"powercontext.hermes","event":"tool_call","outcome":"server_unavailable","recovery":"powercontext doctor"} +{"component":"powercontext.dsh","event":"capture_source","outcome":"invalid_response","http_status":500} +``` + +## Plugin presentation contract + +Each plugin MUST use the native Plugin channel. Diagnostics MUST NOT be inserted into model content, recalled context, +or a successful tool result. + +| Plugin | Channel | Component prefix | +| --- | --- | --- | +| Codex | Hook stdout top-level `systemMessage` | `powercontext.codex.recall` | +| Claude Code | Hook stdout top-level `systemMessage` | `powercontext.claude_code.recall` | +| DSH | Plugin logger warning | `powercontext.dsh` | +| OpenClaw | Plugin API logger warning | `powercontext.openclaw` | +| Pi | Plugin terminal warning (`console.warn`) | `powercontext.pi` | +| Hermes | Plugin logger warning | `powercontext.hermes` | + +The Plugin-facing operation result MAY remain a generic error such as `PowerContext operation failed`. The structured +diagnostic is the recovery signal; the generic result is only for Plugin/model control flow. When a hook also injects +context, its stdout JSON MUST retain `hookSpecificOutput` alongside `systemMessage`. Hook diagnostics MAY still be +written to stderr for local debugging, but stderr is not the user-visible channel for Codex or Claude Code. + +## Fail-open, privacy, and presentation bounds + +When a PowerContext operation fails: + +- recall/context preparation MUST return no recalled context rather than partial or fabricated context; +- capture and flush MUST not terminate or block the Plugin session indefinitely; +- direct tools and commands MUST return a generic failure result without exposing request details; +- diagnostic emission itself MUST be best effort and MUST NOT turn a backend failure into a Plugin failure. + +Diagnostics MUST NOT contain endpoint URLs, authorization headers, tokens, cookies, filesystem paths, prompts, queries, +captured text, recalled text, response bodies, or stack traces. + +Repeated failures MUST have bounded presentation across invocations. Long-lived plugins SHOULD deduplicate by `outcome` +for 60 seconds. Short-lived hooks MUST use a host-level or durable local state mechanism to enforce the bound across +invocations; an invocation-local set MAY provide additional deduplication. The deduplication key is the outcome, not +user input or the request payload. + +## Plugin implementation conventions for this RFC + +Every plugin implementation in this RFC MUST: + +1. Reuse the shared client error types and the outcome mapping above. +2. Attach diagnostics to every relevant failure exit, including lifecycle callbacks and direct tool/command paths. +3. Use a stable component and event name; never put user or request data in either field. +4. Keep the diagnostic formatter independent from model-facing content formatting. +5. Preserve the Plugin's normal behavior when PowerContext is unavailable. +6. Add or update documentation and tests in the same implementation slice. + +The plugin MAY choose its language and internal helper shape. It MUST preserve the observable JSON contract and the +Plugin channel listed above. + +## Required test matrix + +Each plugin PR that implements this RFC MUST test the following observable behavior: + +1. Transport failure or timeout produces `server_unavailable` and `powercontext doctor`. +2. HTTP 503 produces `server_unavailable` with `http_status: 503`. +3. HTTP 401 produces `authentication_failed`. +4. A missing compatibility or availability endpoint produces `version_mismatch`, while a direct resource 404 preserves + `not_found`. +5. Direct 409 and 422 responses preserve `conflict` and `invalid_request`; other unclassified HTTP failures and + malformed responses produce `invalid_response`. +6. Two separate hook invocations within the documented cooldown produce at most one identical diagnostic. +7. Recall, capture, flush, direct tool, slash command, and status paths remain fail-open where the Plugin exposes them. +8. The diagnostic contains no URL, token, prompt, query, response body, or stack trace. +9. The matching Plugin runner, type checker, or smoke test passes. + +Tests SHOULD assert the parsed event and the Plugin-visible channel. They SHOULD NOT freeze private call order or +internal helper names. + +## Out of scope + +This contract does not define: + +- shared service state or native service lifecycle; +- service installation, ownership, restart policy, or platform support qualification; +- a common UI for every Plugin; +- the Bub integration. + +Those decisions require their own implementation evidence and review boundary. diff --git a/docs/zh/development/plugin-contract.md b/docs/zh/development/plugin-contract.md new file mode 100644 index 000000000..ad3dcbb50 --- /dev/null +++ b/docs/zh/development/plugin-contract.md @@ -0,0 +1,155 @@ +--- +title: 插件集成约定 +description: 插件可见集成诊断 RFC 的约定。 +--- + +# 插件可见诊断约定 + +本文记录 Host-visible integration diagnostics RFC 引入的约定,只覆盖本次诊断切片。共享 service state、service lifecycle、安装方式和平台 adapter +约定属于各自的 RFC 与实现切片,不在本文定义。 + +当前约定适用于 Codex、Claude Code、DeepSeek Harness(DSH)、OpenClaw、Pi 和 Hermes。Bub 尚未具备插件通道、实现、测试和支持资格,暂不在范围内。 + +文中的“必须(MUST)”“应该(SHOULD)”和“可以(MAY)”是插件实现与评审的规范性要求。 + +本约定来源于 [RFC 1299:Local Server availability and service installation](../rfcs/1299_local_server_availability_and_service_installation.md)。 +原始 [RFC PR #1299](https://github.com/oceanbase/powercontext/pull/1299) 对应的跟踪 issue 是 [#1298](https://github.com/oceanbase/powercontext/issues/1298)。 + +## 插件必须报告什么 + +当插件可见操作因为下列已分类的 backend failure 无法完成时,插件必须报告 PowerContext failure。适用操作包括 context preparation、recall、capture、flush、 +direct tool、slash command 以及 health/status check。 + +插件必须使用 typed client error 进行分类,不能通过匹配 exception message 文本来分类。 + +| Outcome | 分类规则 | +| --- | --- | +| `authentication_failed` | typed authentication failure,通常为 HTTP 401。 | +| `version_mismatch` | 必须来自兼容性或 availability endpoint 的 HTTP 404;不能从 direct resource lookup 的状态码推断。 | +| `server_unavailable` | 连接失败、超时、请求中止或 HTTP 503。 | +| `invalid_response` | JSON 损坏、响应结构错误、解码/schema failure,或完成 operation-specific domain 分类后仍未分类的 HTTP failure。 | + +合法但为空的结果不是失败诊断。尤其是空的 memory 结果不能报告为 `server_unavailable`。 + +### Operation-specific domain error + +Direct tool 和 command 在 Server 已处理请求后,可能返回合法的 domain error。Client 必须先根据 typed domain +error 分类,再应用插件可见诊断映射: + +| Domain result | Direct operation 含义 | +| --- | --- | +| `not_found` | HTTP 404,表示 Memory entry、citation 或其他请求资源不存在。 | +| `conflict` | HTTP 409,表示 revision、source、citation 或其他 operation conflict。 | +| `invalid_request` | HTTP 422,表示 request 违反 wire 或 application contract。 | + +这些 domain result 必须保留在 direct operation result 中,不能改写成 `version_mismatch` 或 `invalid_response`。 +明确标识为兼容性或 availability endpoint 的 404 仍然是 `version_mismatch`;应由 operation identifier 或 endpoint +contract,而不是单独的 status code,决定分类。 + +## 诊断事件格式 + +每条诊断必须通过插件支持的通道写出一个单行 JSON object。对于基于 Hook 的宿主,事件编码在成功 stdout +Hook JSON 顶层的 `systemMessage` 值中,不要求作为独立的 stdout 行输出。 + +```json +{ + "component": "powercontext.openclaw", + "event": "context_prepare", + "outcome": "server_unavailable", + "recovery": "powercontext doctor" +} +``` + +### 字段 + +| 字段 | 要求 | +| --- | --- | +| `component` | 稳定且包含插件名称,例如 `powercontext.dsh` 或 `powercontext.claude_code.recall`。 | +| `event` | 简短的 lower-snake-case 事件,例如 `context_prepare`、`capture_source`、`tool_call` 或 `status`。不能包含 prompt、query、URL 或 identifier。 | +| `outcome` | 必须是上面定义的四种 outcome 之一。 | +| `http_status` | HTTP 响应可以携带的整数。传输失败不能伪造该字段。 | +| `recovery` | `server_unavailable` 时必须为 `powercontext doctor`;其他 outcome 通常省略。 | + +可以增加 bounded、非敏感且有助于解释生命周期事件的字段,例如数字类型的 `content_bytes` 或受限的 `context_status`。 + +### 示例 + +```json +{"component":"powercontext.codex.recall","event":"context_prepare","outcome":"authentication_failed","http_status":401} +{"component":"powercontext.pi","event":"context_prepare","outcome":"version_mismatch","http_status":404} +{"component":"powercontext.hermes","event":"tool_call","outcome":"server_unavailable","recovery":"powercontext doctor"} +{"component":"powercontext.dsh","event":"capture_source","outcome":"invalid_response","http_status":500} +``` + +## 插件展示约定 + +每个插件必须使用插件原生通道。诊断不能插入 model content、recalled context 或成功的 tool result。 + +| 插件 | 通道 | Component 前缀 | +| --- | --- | --- | +| Codex | Hook stdout 顶层 `systemMessage` | `powercontext.codex.recall` | +| Claude Code | Hook stdout 顶层 `systemMessage` | `powercontext.claude_code.recall` | +| DSH | 插件 logger warning | `powercontext.dsh` | +| OpenClaw | Plugin API logger warning | `powercontext.openclaw` | +| Pi | 插件终端 warning(`console.warn`) | `powercontext.pi` | +| Hermes | Plugin logger warning | `powercontext.hermes` | + +插件侧 operation result 可以继续返回 `PowerContext operation failed` 这类通用错误。结构化诊断是恢复信号,通用错误只服务于 host/model 控制流。 +Hook 同时注入 context 时,stdout JSON 必须保留 `hookSpecificOutput`,并与 `systemMessage` 并列。Hook 可以继续 +向 stderr 写本地调试信息,但 Codex 和 Claude Code 的用户可见通道不是 stderr。 + +## Fail-open、隐私和展示边界 + +PowerContext operation 失败时: + +- recall/context preparation 必须返回空的 recalled context,不能返回 partial 或伪造的 context; +- capture 和 flush 不能终止插件 session,也不能无限期阻塞插件; +- direct tool 和 command 必须返回不包含请求细节的通用 failure result; +- 诊断发送本身必须是 best effort,日志失败不能把 backend failure 变成插件 failure。 + +诊断不能包含 endpoint URL、authorization header、token、cookie、filesystem path、prompt、query、capture text、recall text、response body 或 stack trace。 + +重复失败必须在跨 invocation 的范围内有展示边界。长生命周期插件应该按 `outcome` 去重 60 秒。短生命周期 hook +必须使用宿主级或持久化本地状态跨 invocation 执行该限制;一次 invocation 内的 set 可以作为额外去重手段。去重 +key 是 outcome,不能使用 user input 或 request payload。 + +## 本 RFC 的插件实现约定 + +本 RFC 中的每个插件实现都必须: + +1. 复用 shared client 的 typed error 和上面的 outcome mapping。 +2. 在所有相关 failure exit 接入诊断,包括 lifecycle callback 和 direct tool/command path。 +3. 使用稳定的 component 和 event name;不能把用户数据或请求数据放进字段。 +4. 让 diagnostic formatter 与 model-facing content formatter 解耦。 +5. PowerContext 不可用时保持插件的正常行为。 +6. 在同一个 implementation slice 中同步更新文档和测试。 + +插件可以选择语言和内部 helper 结构,但必须保持可观察的 JSON 约定和上表中的插件通道。 + +## 必须覆盖的测试矩阵 + +实现本 RFC 的每个插件 PR 都必须测试以下可观察行为: + +1. 传输失败或超时产生 `server_unavailable` 和 `powercontext doctor`。 +2. HTTP 503 产生带有 `http_status: 503` 的 `server_unavailable`。 +3. HTTP 401 产生 `authentication_failed`。 +4. 缺失的兼容性或 availability endpoint 产生 `version_mismatch`,direct resource 404 保留 `not_found`。 +5. Direct 409 和 422 分别保留 `conflict` 与 `invalid_request`;其他未分类 HTTP failure 和 malformed response 产生 + `invalid_response`。 +6. 在约定 cooldown 内执行两次独立 Hook invocation 时,最多产生一条相同诊断。 +7. 在插件提供这些入口时,recall、capture、flush、direct tool、slash command 和 status path 都保持 fail-open。 +8. 诊断不包含 URL、token、prompt、query、response body 或 stack trace。 +9. 对应的插件 runner、type checker 或 smoke test 通过。 + +测试应该断言解析后的 event 和插件可见通道,不应该冻结 private call order 或内部 helper name。 + +## 不在范围内 + +本文不定义: + +- shared service state 或 native service lifecycle; +- service 安装、ownership、restart policy 或平台支持资格; +- 所有插件共用的 UI; +- Bub 集成。 + +这些决定需要独立的实现证据和评审边界。 diff --git a/zensical.toml b/zensical.toml index 6ddfbb4b0..b787d2e50 100644 --- a/zensical.toml +++ b/zensical.toml @@ -41,6 +41,7 @@ nav = [ ] }, { "Development" = [ { "Core Protocol" = "en/development/core-protocol.md" }, + { "Plugin Integration Contract" = "en/development/plugin-contract.md" }, { "Memory Layer" = "en/development/memory-layer.md" }, { "Pydantic AI Inference" = "en/development/pydantic-ai-inference.md" }, { "Remote Access Implementation" = "en/development/remote-access-implementation.md" }, @@ -113,6 +114,7 @@ nav = [ ] }, { "开发" = [ { "Core Protocol" = "zh/development/core-protocol.md" }, + { "插件集成约定" = "zh/development/plugin-contract.md" }, { "Memory Layer" = "zh/development/memory-layer.md" }, { "Pydantic AI 推理" = "zh/development/pydantic-ai-inference.md" }, { "远程访问实现" = "zh/development/remote-access-implementation.md" },