Skip to content

feat(logging): 声明式插件由执行方代记插件操作事实 - #1720

Merged
xiaoyumuxi merged 4 commits into
previewfrom
feat/declarative-plugin-operation-facts
Nov 4, 2026
Merged

xiaoyumuxi merged 4 commits into
previewfrom
feat/declarative-plugin-operation-facts

Conversation

@xiaoyumuxi

@xiaoyumuxi xiaoyumuxi commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

概要

纯声明式插件(Go、PHP、LLDB、Gradle 等只在 plugin.json 里声明语言服务器、调试适配器或构建命令、没有自己后端进程的插件)以前在结构化日志 v2 里没有任何插件操作事实:它们不能发 lithe/log,所以 plugins/<id>/host 通道是空的,和 Database、Docker、HTTP Client、Cargo、SVN、Maven、Lua 这些 SDK 插件不统一。

本 PR 让实际执行声明工作的一方(macOS 宿主或 Core 引擎)替插件写同一组 plugins.operation.finished / plugins.operation.failed 事件,写到同一个 plugins/<id>/host 通道。声明式插件不需要写任何日志代码。

只做 macOS 与 Core;Windows 仍是 v1,不在本 PR 范围内。

设计:一个通用结构 + 一张贡献映射表

  • Core 新增 rust/lithe-core/src/diagnostics/plugin_operations.rs:
    • 通用结构 DeclarativeOperation(插件 ID、贡献、失败阶段、耗时、可选 op)。
    • 映射表 CONTRIBUTIONS,每行只有操作名和生命周期:
      operation 贡献 生命周期
      languageServer/start languageServers[] ProcessStart
      debugAdapter/start debugAdapters[] ProcessStart
      lithe/build/model buildSystems[].model 命令来源 CommandRun
      lithe/build/dependencies buildSystems[].dependencies 命令来源 CommandRun
    • 生命周期持有“阶段 → host.* 码”表,同一个码在所有贡献里只有一个含义:
      stage ProcessStart CommandRun
      plan 拒绝 host.planFailed
      resolve host.executableUnavailable host.executableUnavailable
      launch host.launchFailed host.launchFailed
      initialize host.initializeFailed 拒绝
      timeout host.timedOut host.timedOut
      exit host.exitedEarly host.commandFailed
      decode 拒绝 host.outputInvalid
    • 以后新增声明式契约:加一行映射,并在执行方开始/结束一次 attempt;不加事件、通道或错误码。
    • 构建操作沿用 SDK 请求名,同一工作不论由声明还是后端提供,日志里名字相同。
  • 新 Core 命令 diagnostics.recordPluginOperation:{pluginId,operation,outcome,stage?,durationMs,operationId?},deny_unknown_fields,返回 data:{result:{}};任何非法输入(未知操作、生命周期到不了的阶段、finished 带 stage、failed 缺 stage、非法 op、超范围耗时、多余字段、可选字段显式写 null)统一返回新错误码 -32064 Invalid host plugin operation(一码一义,不回显被拒值)。
  • channel.rs 抽出 identity_segment / digest_segment,宿主代记与 SDK 路径、引擎身份路由共用同一映射,同一插件只会落到同一个通道。

谁执行,谁记录

  • 调试适配器启动(macOS 宿主):attempt 的 op 直接用调试会话 ID,和该会话自己的 DebugTransportFailed 等记录对得上。MacPluginDebugAdapterTransport 在解析出命令、即将 start 时推进到 launch,适配器可达后推进到 initialize:stdio 进程跑起来即可达;语言服务器托管的适配器要等 socket 连上(Core 传输层第一次进入 ready)才可达,之前端口发现失败记 launch,发现截止时间到记 timeout。会话进入 ready 记成功;只有可达过的适配器断开才记 exit;同步 start 抛错一律记失败(即使调用方任务已取消);Core 拒绝建会话(如父会话已结束)是宿主结果,取消不记录;用户停止记为取消,不记录。
  • 构建模型与依赖命令(macOS 宿主):BuildSystemService 在 plan / resolve / launch / timeout / exit / decode 处推进阶段。若构建计划实际由插件后端(provider)提供,宿主先记录自己负责的 provider 工具解析(resolve)和计划(plan)失败,请求发给后端前取消自己的 attempt,之后由 SDK 记录,避免重复。结果过期(被新请求取代、输入已变)算取消,不记录。
  • 语言服务器启动(宿主解析,Core 拉起和握手):宿主只记录启动前的失败(启动方案解析失败、找不到可执行文件、准备失败),plugin API 1 以宿主进程方式启动的服务器同样覆盖;进入启动后通过 lsp.startServer.startupOperation:{pluginId,operationId,elapsedMs} 把 attempt 交给 Core。pluginId 放在 handoff 里,因为 API 1 会话本身没有插件身份;会话有身份但与 handoff 不一致时 Core 忽略该 handoff。Core 在 spawn 失败或写 initialize 失败(launch)、初始化前退出(exit)、握手失败(initialize)、初始化超时(timeout)或 initialize 成功时记录唯一结果,耗时 = 宿主已用 + Core 计时。停止、取消、被新索引策略淘汰的启动都不记录;宿主在启动前停止服务器会取消 attempt,后续启动不会误记。
  • Swift 侧 PluginOperationReporter / PluginOperationAttempt(LitheModuleAPI/Logging):第一次终态生效;end(throwing:) 遇到 CancellationError 或任务已取消时按取消处理;handOff() 结束宿主侧 attempt 并返回交接数据。
  • 错误码只来自阶段,从不解析本地化错误文本;事实不带插件输出、路径或命令行。宿主不按插件名做任何分支。

其他改动

  • 修复 RustCoreBridge.LogFailure 的字段名:Core 日志域错误返回的是 {code,reason},旧结构按 message 解码会导致域错误解析失败。
  • 日志事件目录 shared/logging/events/plugins.json 更新 plugin / operation / duration / code 字段说明以覆盖声明式代记,重新生成 catalog_generated.rs。
  • fixture shared/fixtures/diagnostics/engine-logging-v2.json 增加 hostOperations:diagnostics.recordPluginOperation 输入到事件、通道和 host.* 码的对照样例(Go 语言服务器、LLDB 调试适配器、Gradle 构建模型)。
  • 契约:shared/contracts/rust-core-api.md 记录命令、两张表、交接语义与 -32064;shared/contracts/plugins/v1/README.md 的日志一节说明声明式插件会得到哪些事实和码。
  • 事件目录说明 shared/logging/README.md 的“引擎宿主”一节列出宿主当前代记的四种声明式贡献(operation、声明来源、记录方、可能的失败阶段),以及新增声明式贡献时要同步做的事。docs(skills): 新增 create-lithe-plugin Skill,规定插件日志接线 #1713 的 create-lithe-plugin Skill 规定以这里为准查询“宿主替哪些声明式贡献记录结果”。
  • Agent Note 2026-11-03-structured-logging-channels.md 新增“声明式插件:由执行方代记同一组操作事实”,并记录被否方案(单独的监听服务进程、每贡献一个事件或封闭大枚举、解析错误消息判断阶段)。
  • 功能矩阵 diagnostics-export.json 补证据和验证说明;Windows 仍为 missing。

评审修复(/code-review)

采纳并修复:

  • LSP 交接边界(Core):写 initialize 失败现在记 launch(原先漏记);被新索引策略淘汰的启动放弃 attempt,不再误记失败;handoff 只在 LanguageServer 角色、且会话 pluginId 缺省或与 handoff 一致时接受。
  • LSP 覆盖面(宿主):plugin API 1 以宿主进程方式启动的服务器也开始/交接 attempt,handoff 改为携带 pluginId;宿主在启动前停止服务器会取消 attempt,后续启动不会误记。
  • 构建计划由插件后端提供时:宿主仍记录自己负责的 provider 工具解析(resolve)与计划(plan)失败,发请求前才取消 attempt,交给 SDK 记录。
  • 调试适配器阶段:托管适配器按实际到达的阶段记录:端口发现失败 launch、发现截止 timeout、socket 连上后断开才是 exit;stdio 进程跑起来即算可达。
  • 调试 op 关联:attempt 的 op 改为调试会话 ID。
  • 调试结果归属:Core 拒绝建会话是宿主结果,取消不记录;同步 start 抛错即使调用方任务已取消也记失败。
  • 通道身份:调试日志的适配器段改用与插件事实同一个 identity_segment,同一 ID 只落一个通道(删掉重复的 debug_log_adapter)。
  • 输入校验:diagnostics.recordPluginOperation 的可选字段显式写 null 返回 -32064。

经核实未改:

  • 过期构建结果:过期一定会取消任务,end(throwing:) 把 Task.isCancelled 当取消处理,不会误记。
  • 宿主 sink 可能为空:MacServiceContainer 始终注入 reporter 的记录函数。
  • 调试重启不触发状态变化:Core create_session 总是从 Initializing 开始,重启一定会经过状态变化。

验证

本地(macOS,已 rebase 到 preview 9e1a270):

  • cargo test --manifest-path rust/Cargo.toml -p lithe-core --lib:1733 通过,0 失败(含 plugin_operations 的映射表、阶段→码、-32064 拒绝(含显式 null)、通道路由测试,以及 LSP 引擎交接、写 initialize 失败、被索引策略淘汰、身份不一致的交接测试)。
  • 评审修复后重跑受影响的 Swift 测试组,全部通过、无失败,两轮脚本内的 runtime bundle immutability 检查均通过:
    • DebugModuleTests、LitheLanguageIntelligenceModuleTests、LanguageServerStatusTests、RustLanguageServerLaunchTests、JavaLanguageServerRuntimeTests、DiagnosticsWorkflowTests、CorePayloadDecodingTests、RunConfigurationIntegrationTests、RealJavaDebugIntegrationTests:3 个测试二进制分别 162 / 46 / 162 个通过。
    • PluginDebugAdapterTests、TCPDebugAdapterTests、MacNativeProviderDebugIntegrationTests、BuildSystemServiceTests、PluginLanguageServerLaunchResolverTests、MacBuildProviderSessionsTests、LanguageExtensionProcessLifecycleTests、MacLogStoreTests、LanguageFeatureProviderTests:125 + 86 个通过。
  • ./.agents/skills/write-stable-tests/scripts/test-stability-macos.sh -- --filter '<本 PR 新增的 24 个测试>':通过,24 个测试均有单独计时。最慢的是 stoppedDeclarativeModelCommandRecordsNothing(534 ms),因为要用 500 ms 的有界窗口证明停止后不会补记,和同文件已有的“不会发布”断言写法一致;其余都在 0.1 s 以内(托管适配器三种结局共 42 ms)。
  • 托管适配器“端口发现永不返回”的测试用可取消的等待模拟,由测试触发的发现截止时间关闭传输并取消;该行带 test-stability: allow(swift-unbounded-wait) 并写明上界来源,没有真实 sleep。
  • verify-test-stability.sh、generate-log-events.mjs --check、verify-log-usage.mjs、verify-agent-notes.sh、verify-shared-contracts.sh、verify-service-boundaries.sh、verify-platform-feature-matrix.sh、verify-rust-core-comments.sh、verify-windows-boundaries.sh、verify-app-language-localization.mjs、generate-i18n.mjs --check:全部通过。
  • rustfmt:本 PR 改到的 Rust 代码已格式化。cargo fmt --check 仍会报 preview 上已有的格式差异(lithe-agent-host/src/claude_desktop.rs、lithe-core/src/git/mod.rs、dispatcher.rs 的两个 Git 命令分支),本 PR 没有动这些代码。

未做:完整 macOS 测试套件;在真实应用里用 Go/PHP/LLDB/Gradle 插件逐个查看日志窗口的插件通道。

合并后需要做的

  • 本 PR 修改了 shared/contracts/plugins/v1/README.md,合并后需要在 lithe-plugin-docs 运行 node scripts/contract.mjs sync --revision <合并后的 preview commit> 并提交。
  • 未修改插件后端协议(plugin_function.rs 等),SDK 无需改动。

配套插件 PR(README 改为说明宿主代记的操作与码)

限制

  • 只覆盖 macOS;Windows 日志仍是 v1。
  • 尚未在真实应用里用 Go/PHP/LLDB/Gradle 插件在日志窗口逐个验收插件通道。
  • 调试适配器启动 attempt 的推进和结果归属目前写在 Swift 的 CoreDebugAdapterProtocolSession 里,和该会话已有的初始化截止时间、DebugTransportFailed 记录在同一层。按“通用逻辑进 Rust Core”的规则,后续应把整个调试启动生命周期(截止时间、传输失败事实、attempt)一起迁到 Core,像 LSP 一样在建会话时交接;只迁 attempt 会把同一个生命周期拆到两层。

没有插件进程的语言服务器、调试适配器和构建命令来源,以前在 plugins/<id>/host
通道没有任何操作事实,和 SDK 插件不统一。

- Core 新增 diagnostics.recordPluginOperation 与贡献映射表 CONTRIBUTIONS:
  languageServer/start、debugAdapter/start(ProcessStart)与
  lithe/build/model、lithe/build/dependencies(CommandRun),失败阶段统一映射到
  host.* 码;非法输入统一 -32064。
- macOS 新增 PluginOperationReporter / PluginOperationAttempt:调试适配器启动、
  构建模型与依赖命令由宿主记录;语言服务器启动前失败由宿主记录,之后通过
  lsp.startServer.startupOperation 交给 Core 记录唯一结果。
- 取消、停止、过期结果与由插件后端提供的构建计划不记录;从不解析错误文本。
- 修复 RustCoreBridge.LogFailure 按 reason 解码 Core 日志域错误。
- 更新事件目录、fixture、Core API 与插件契约、Agent Note 和功能矩阵。
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 4, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 4, 2026 06:43
create-lithe-plugin Skill(#1713)规定,宿主替哪些声明式贡献记录结果,以 shared/logging/README.md 和 shared/logging/events/ 为准。这里在“引擎宿主”一节补上 diagnostics.recordPluginOperation 的语义、当前代记的四种贡献表(operation、声明来源、记录方、可能的失败阶段),以及新增声明式贡献时要同步做的事。
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 4, 2026 06:51
@lithe-code-review

Copy link
Copy Markdown

Lithe 运行验收

版本:1080b72827b88476580beeae608b7a8ad554cd4a;状态:partial。
本报告独立于代码审查;只报告实际执行的场景,不代表所有问题均已修复。

对照:共同祖先 4c696215f39923b47b1a54b7d364af3f715d2832 → PR head;当前目标分支合并适用性:未运行。

  • 条件不足,未验证:Validate diagnostics.recordPluginOperation, the four contribution mappings, lifecycle error codes, strict rejection and privacy, response envelopes, duration/op preservation, and shared plugin-channel routing.
    • The allowed inventory supplies Cargo and rustc, but no native C compiler/linker and Windows SDK toolchain, nor a revision-matched Core DLL. Core's locked dependencies include tree-sitter and ring, which require native compilation. These prerequisites cannot be assumed or installed automatically.
  • 条件不足,未验证:Verify Core ownership after LSP startup handoff, terminal-stage attribution, accumulated duration, exactly one outcome, cancellation suppression, and compatibility for legacy and SDK-owned sessions.
    • Requires the native Core build prerequisites missing from the allowed inventory. No matching compiled library or production command driver is supplied. Node can support a deterministic local protocol peer, but cannot execute the Rust engine by itself.
  • 条件不足,未验证:Verify Swift reporter lifecycle semantics and production wiring for LSP prelaunch, DAP startup, build model/dependencies, provider deduplication, stale/cancelled work, LogFailure decoding, and runtime resource immutability.
    • Requires macOS and the repository's Swift/Xcode toolchain, native adapters, and revision-specific Core library. Headless Windows cannot execute these Swift host paths.

未覆盖:

  • All three selected scenarios remain unverified: Core ingress validation/mapping/routing, native LSP handoff lifecycle, and macOS host/reporter/bridge behavior.
  • Real Go/PHP language-server, LLDB adapter and Gradle model/dependency workflows remain uncovered; suitable pinned plugin packages, executables and project fixtures are not supplied. Java is explicitly unavailable for Gradle.
  • Genuine log-window acceptance requires capability desktop, which is unavailable: plugin-channel visibility/filtering, displayed fields and operation correlation. The target product paths also require macOS.
  • Queue overflow/rejected engine-fact drop accounting and concurrent terminal races are not independently covered by the selected lifecycle plan.
  • Actual macOS writer wake/drain/flush/shutdown delivery, SDK/declarative channel coexistence and packaged-resource hash invariance remain blocked; Core API evidence alone cannot establish them.
  • Generated catalog consistency, shared fixture/documentation parity and feature-matrix accuracy were not validated by execution. Post-merge synchronization to lithe-plugin-docs remains external follow-up.
  • Windows v1 compatibility remains unverified; Windows logging v2 implementation is explicitly outside this PR's scope.

工作副本与运行状态已清理;可复用编译缓存按 12 GiB / 7 天预算保留。
详细步骤和原始证据保存在服务器受限目录(14 天、总计不超过 1 GiB);当前没有公开下载链接。

- LSP:写 initialize 失败记 launch;被新索引策略淘汰的启动放弃
  attempt;handoff 携带 pluginId,只在 LanguageServer 角色且会话
  身份缺省或一致时接受;plugin API 1 宿主进程启动的服务器也覆盖;
  启动前停止会取消 attempt。
- 构建:计划由插件后端提供时,宿主仍记录 provider 工具解析和计划
  失败,发请求前再取消。
- 调试:op 用调试会话 ID;托管适配器按到达阶段记录(端口发现失败
  launch、发现截止 timeout、连上后断开才是 exit);Core 拒绝建会话
  取消不记录;同步 start 抛错即使任务已取消也记失败。
- 调试日志适配器段改用 identity_segment,与插件事实同一映射。
- diagnostics.recordPluginOperation 拒绝可选字段显式 null(-32064)。
- 同步契约、事件目录说明和 Agent Note。
@xiaoyumuxi
xiaoyumuxi merged commit f8d1fb8 into preview Nov 4, 2026
10 of 18 checks passed
xiaoyumuxi added a commit to Mucheen/Lithe-IDEA that referenced this pull request Nov 4, 2026
Resolve the outputPlan conflict with 1lck#1720 plugin operation stages, and record the run's own model load in runTask as a buildModel attempt so captureOutput receives one.

Keep the Windows host working after the macOS-only split: an IDE host that sends no toolExposure on open now serves the whole catalog (the preview behavior) instead of being refused, while macOS keeps declaring and enforcing its selection. Contracts updated accordingly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant