diff --git a/.agents/notes/proposed/architecture/2026-10-13-public-extension-contract-and-provider-api.md b/.agents/notes/proposed/architecture/2026-10-13-public-extension-contract-and-provider-api.md index 95e8f1015b..8293df7b1e 100644 --- a/.agents/notes/proposed/architecture/2026-10-13-public-extension-contract-and-provider-api.md +++ b/.agents/notes/proposed/architecture/2026-10-13-public-extension-contract-and-provider-api.md @@ -32,6 +32,8 @@ PR #37 是第一阶段 UI Contribution(插件声明的界面入口)基础设 内置模块和插件尽量实现相同领域 Provider 契约。Build/Run/Test/Debug 等采用类型化 Registry(按能力类型登记和查询实现的目录),组合根负责装配通用适配器,不按新增插件名追加分支。先盘点并复用已有语言服务器、构建工具、运行/测试和 DAP(调试适配器协议)链路;上游拥有的项目、任务和依赖事实仍由原工具提供,避免再造一套解释器、工程模型或调试 UI。 +Debug 的统一内核、三层能力、插件声明与迁移验收见[统一调试内核与调试适配器 Registry](2026-10-25-unified-debug-engine-and-adapter-registry.md),跟踪当前仓库的 [#1210](https://github.com/1lck/Lithe-IDEA/issues/1210)。寄存器、内存、反汇编等可选能力另见[可选调试扩展](2026-10-25-optional-debug-extensions.md),插件依赖与导出另见[平台级独立提案](2026-10-25-plugin-dependencies-and-exports.md);两者都不作为 #1210 的前置条件。这些提案没有开放新字段,也不改变其他领域 Provider 的设计。 + ### 通用 Services 与资源所有权 Services 接入容器、Spring Boot、数据库和本地服务。后续统一设计 ServiceProvider/ServiceDescriptor/Instance/Status/Actions/Logs 的语义;这些名称是设计目标,当前没有公布字段全集或承诺已经实现。 diff --git a/.agents/notes/proposed/architecture/2026-10-25-optional-debug-extensions.md b/.agents/notes/proposed/architecture/2026-10-25-optional-debug-extensions.md new file mode 100644 index 0000000000..85940b4a89 --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-10-25-optional-debug-extensions.md @@ -0,0 +1,112 @@ +# Agent 笔记:可选调试扩展 + +状态:提议中 + +## 先说结论 + +除断点、线程与调用栈、变量这些必选能力外,调试功能都是**可选扩展**:寄存器、内存、反汇编、反编译源码、条件断点、子会话等。每个扩展都挂在[统一调试内核](2026-10-25-unified-debug-engine-and-adapter-registry.md)上,由三部分组成:内核请求处理器、它允许内核声明的客户端能力、平台界面入口。扩展各自排期、各自验收,都不是 #1210 的关闭条件。 + +某个功能是否可用,由三个条件同时决定:适配器在 DAP 协商中声明支持、本平台已注册对应扩展、操作已获授权。插件清单不逐项声明这些功能,最多用 `capabilities.disabled` 关掉一项。 + +## 问题 + +GDB、LLDB 可以调试 C、C++、Rust 和汇编,但二进制不一定有源码或行号;Java 调试器可以返回反编译源码;js-debug 会发起子会话。这些功能在 DAP 里都是可选的,不同适配器支持的组合各不相同。如果为了显示它们去改必选层的数据结构,或者按适配器名称写分支,每接一个调试器就要改一次宿主。 + +现在 Rust Core 已经实现 `setVariable`、各类断点(含数据断点)、`stepBack`、`runInTerminal`;尚未实现 `readMemory`、`writeMemory`、`disassemble`、`setInstructionBreakpoints`、指令粒度单步、`modules`、`loadedSources` 和 `startDebugging`。macOS 的 Node 子会话目前在 Swift 会话里实现。 + +## 提案 + +### 扩展分组 + +| 分组 | DAP 请求与能力 | 内核允许声明的客户端能力 | 界面入口 | +| --- | --- | --- | --- | +| 断点增强 | 条件断点、命中次数、日志点(`supportsConditionalBreakpoints`、`supportsHitConditionalBreakpoints`、`supportsLogPoints`);`setFunctionBreakpoints`;`setExceptionBreakpoints` 与过滤项;`dataBreakpointInfo` / `setDataBreakpoints` | — | 断点编辑、异常过滤、监视点 | +| 表达式与变量 | `setVariable`、`setExpression`、`completions`;变量的 `presentationHint`(只读、懒加载) | `supportsVariableType` | 变量编辑、控制台补全 | +| 执行控制增强 | `stepBack` / `reverseContinue`、`restart`、`restartFrame`、`gotoTargets` / `goto`、`stepInTargets` | — | 工具栏与栈帧菜单 | +| 无源码与生成代码 | `source`(`sourceReference`);`loadedSources` | — | 只读源码标签页 | +| 原生调试 | 寄存器(scope 的 `presentationHint: "registers"`);`readMemory` / `writeMemory`;`disassemble`;`setInstructionBreakpoints`;`granularity: "instruction"` 单步 | `supportsMemoryReferences`、`supportsInvalidatedEvent`、`supportsMemoryEvent` | 寄存器、内存、反汇编视图 | +| 进程与模块 | `modules`;`startDebugging` 子会话;`runInTerminal`;`attach` 到进程列表 | `supportsStartDebuggingRequest`、`supportsRunInTerminalRequest`、`supportsProgressReporting` | 模块列表、子会话树、终端 | +| 语言专属 | Java 热替换(`redefineClasses`)等非标准请求 | 由内置 Provider 描述列出 | 由对应扩展提供 | + +### 反编译与反汇编是两件事 + +- **反编译**由适配器完成:例如 Java 调试器从 jar 里的 class 生成源码,通过 `sourceReference` 返回文本。宿主只用只读标签页显示它,不自己反编译。 +- **反汇编**是 `disassemble` 请求,返回指令列表,属于原生调试分组。 + +两者使用不同的界面,也分别验收。 + +### 无源码位置 + +- 栈帧可以没有 `source`、`line`、`column`,只有名称和 `instructionPointerReference`。界面显示“无源码”,有反汇编扩展时可以跳到反汇编视图;不伪造文件,也不跳到第 1 行。 +- 源码身份三选一:工作区相对路径、会话内 `sourceReference`、已授权的外部路径。会话结束后 `sourceReference` 失效,标签页转为只读快照或关闭。 +- 地址和引用(`instructionPointerReference`、`memoryReference`、`address`)都是不透明字符串,宿主不做算术。确实需要偏移时,由内核按字符串解析为 128 位以内的整数,再生成新字符串。 + +### 寄存器 + +寄存器直接使用适配器给的 scope:按 `presentationHint: "registers"` 筛选,适配器没给提示时按普通 scope 显示。宿主不内置任何 CPU 的寄存器表,也不按架构名写分支。 + +### 写操作单独授权 + +`writeMemory`、`setVariable`、`setExpression`、热替换都会修改被调试程序。读取能力不代表允许写入:写入类扩展各有独立的授权与确认规则,且只有对应的用例通过后才开放。具体权限名在实施时按能力版本规则申请。 + +### 引用失效 + +栈帧、变量、内存、反汇编结果都绑定会话 ID 和暂停代次。继续执行、单步、重启后,界面重新获取;收到 `invalidated` 或 `memory` 事件时,只刷新受影响的区域。晚到的旧结果直接丢弃。 + +### 实现顺序建议 + +1. 断点增强、表达式与变量:Rust Core 已有实现,迁到新内核后补界面即可。 +2. 无源码与生成代码:Java 反编译源码和 Node eval 代码依赖这一组,同时也是原生调试的前提。 +3. 进程与模块:`startDebugging` 要先于删除 Swift 会话完成,否则 Node 子会话会回退。 +4. 原生调试:寄存器 → 反汇编 → 只读内存 → 指令断点与单步 → 写内存。 + +第 3 项实际上是 P5 删除旧路径的前置条件:Node 子会话迁到新内核之前,不能删 Swift 会话。 + +### 正确做法 + +- 新增反汇编:内核注册 `disassemble` 处理器,两端增加视图,入口只看能力快照里的 `supportsDisassembleRequest`。GDB、LLDB 自动获得,插件不改。 +- 断在没有符号的函数里:调用栈显示函数名和地址,编辑器保持原位;打开反汇编视图后定位到当前指令。 + +### 不要这样做 + +- 为了让旧解码器能用,给无源码帧填一个假路径和第 1 行。 +- 在宿主里内置 x86 / ARM 寄存器表,或者按 `adapterID` 判断是否显示内存视图。 +- 已经能读内存,就顺带允许写内存。 +- 适配器没有声明支持时仍显示入口,等用户点了再报错。 + +## 考虑过的备选方案 + +- **把原生调试放回 #1210:** 会把 Registry 接入和新界面、权限、多架构验证绑在一起,拖慢必选层落地。已拆出。 +- **插件用函数实现扩展:** 插件函数不能实现领域操作,而且会让同一个 DAP 请求出现两条执行路径。扩展只做宿主内核模块。 +- **自研反汇编器或内存解析:** 重复上游已有的能力,并且受架构限制。只显示适配器返回的结果。 + +## 验收标准 + +- 每个分组单独验收,至少用一个声明支持的真实适配器在两个平台各跑一次;不支持的适配器上,界面入口隐藏,或明确显示为不可用。 +- 未注册扩展时,initialize 不声明对应的客户端能力,适配器也不会发来相关引用。 +- 无源码帧、超过 2^53 的地址、`sourceReference` 会话结束后失效、继续执行后引用失效、未知 `presentationHint`,都有共享 fixtures,三端解码一致;不出现假跳转。 +- 写入类操作有独立的授权和确认用例,拒绝时不发出请求。 +- 进程与模块分组通过 Node 子会话的现有基线后,才允许删除 Swift 会话。 + +## 风险 + +- GDB 是否带 DAP 取决于版本和构建方式;系统调试授权(macOS 的调试权限、Windows 的进程访问权限)和指令集差异会限制原生调试,未验证的组合保持不可用。 +- 适配器声明了能力但实现不完整时,要按请求失败显示错误,不能让会话卡住;所有扩展请求都有期限,并且可以取消。 + +## 验证 + +本次只修改文档,运行 `./scripts/verify-agent-notes.sh`,不提供运行时证据。实施时加载 `write-stable-tests`,增加 `shared/fixtures/debug/*-v2.json`,运行 `./scripts/verify-shared-contracts.sh`、`./scripts/verify-rust-core.sh` 和对应平台测试;更新功能矩阵,真实调试流程结束后清理本次启动的进程和资源。 + +## 适用范围 + +- `rust/lithe-core/src/debug/` +- `shared/fixtures/debug/` +- `shared/proposals/debug-adapters-1210/` +- `macos/Sources/LitheCoreContracts/Debug/` +- `macos/Sources/LitheDebugModule/` +- `windows/tauri/src/features/debugger/` + +## 依据 + +- [统一调试内核与调试适配器 Registry](2026-10-25-unified-debug-engine-and-adapter-registry.md) +- [DAP 规范](https://microsoft.github.io/debug-adapter-protocol/specification)、[GDB DAP](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html)、[LLDB DAP](https://lldb.llvm.org/use/lldbdap.html) diff --git a/.agents/notes/proposed/architecture/2026-10-25-plugin-dependencies-and-exports.md b/.agents/notes/proposed/architecture/2026-10-25-plugin-dependencies-and-exports.md new file mode 100644 index 0000000000..a64ae1ef4f --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-10-25-plugin-dependencies-and-exports.md @@ -0,0 +1,95 @@ +# Agent 笔记:插件依赖、导出与跨包授权的独立设计范围 + +状态:提议中 + +## 先说结论 + +插件之间复用运行时、JSON 声明和计算能力属于整个插件平台,不应由某个 Debug 格式顺带定义。本提案从 [Debug #1210](https://github.com/1lck/Lithe-IDEA/issues/1210) 拆出依赖、导出和授权,挂在[公共扩展契约](2026-10-13-public-extension-contract-and-provider-api.md)下;Debug 的 Registry 与第三方 DAP 接入不依赖它。 + +方向仍是插件好写、共享逻辑尽量复用,但安装闭包(某个插件及其全部间接依赖)、版本冲突和信任必须先有明确规则。以下只是待单独实施的决策边界,不开放 `pluginDependencies`、`exports`、跨包函数或工作流字段,也不更改现有私有函数契约。 + +## 问题 + +上一版 Debug 草案把依赖、导出、跨包函数和顺序工作流放进专用注册格式,却没有回答安装和卸载如何影响其他插件。仅校验引用存在不能处理菱形冲突(两个依赖分支要求同一个插件的不兼容版本),也不能证明社区调用方有权启动官方依赖里的程序。 + +现有市场已经负责平台、版本、来源、签名和安装确认,插件函数已经规定私有归属。新的复用机制必须扩展这些所有者,而不是增加第二个安装器、进程表或函数路由器。 + +## 提案 + +### 1. 先解决安装和失效,再开放导出调用 + +| 主题 | 拟采用的规则 | +| --- | --- | +| 插件身份与来源 | 依赖绑定插件 ID、允许版本范围及获准来源/发布者;不能仅按 ID 从任意市场寻找同名替代 | +| 安装解析 | 市场先按平台、宿主能力及信任策略过滤版本,再求整个依赖闭包;下载前展示统一计划,验证包内依赖与索引一致 | +| 单版本约束 | 同一插件 ID 只安装一个版本。多个范围取交集;交集为空就拒绝本次安装或更新,并显示两条冲突路径,不自动降级或替换其他已安装插件 | +| 固定结果 | 安装计划固定每个包的版本、来源和内容哈希;执行前重查目录快照。闭包全部准备、校验完成后一起发布,失败恢复旧闭包,不留下半安装状态 | +| 更新 | 显式更新重新计算所有受影响的反向依赖;旧约束不满足时整笔拒绝。用户可以另行批准一组兼容更新,不暗中更新无关插件 | +| 禁用与卸载 | 先算反向依赖闭包,提示受影响的插件。默认拒绝破坏仍启用的依赖方;用户确认后先停依赖方操作和资源,再禁用/卸载目标 | +| 启动时发现缺失 | 保留依赖方安装记录,标记阻塞及具体原因,不启动后端;恢复依赖后重新校验再允许激活 | +| 循环与预算 | 安装依赖和声明导出引用分别检查循环、深度及总量;具体上限、稳定错误码、并发安装锁与崩溃恢复日志是正式契约的前置验收 | + +第一版先不加入弱依赖、可选依赖和并存多版本,避免不能证明的解析语义。运行时动态缺失不是静默选择其他依赖版本的理由。 + +### 2. 信任不沿依赖关系传递 + +每个包独立验证签名、哈希、来源和 namespace。社区插件依赖官方包不会因此变为官方插件,官方包的签名也不是对调用方的背书。安装确认展示闭包新增包、各自权限、来源以及哪些公开导出将被调用;不能把间接依赖藏在顶层插件的一行名称后面。 + +若未来导出可以执行程序,审查必须同时展示调用者、资源所有者和实际执行的包/命令。被依赖方拥有可执行资源与进程生命周期,宿主依据导出用途、本次用户动作、工作区信任和双方授权批准执行;依赖方不能通过传路径替资源所有者更换程序。依赖更新后的资源也要重做来源、签名、权限和计划代次检查。 + +权限不做简单并集,也不能简单取交集后假装已解释语义:每个导出必须定义可接受的调用上下文及所需操作授权。某个导出没有这种定义时,不允许跨包调用,即使两个包都已安装。 + +### 3. 导出是明确选择的契约,不是公开全部函数 + +复用对象可以是只读声明、运行时描述或有类型的操作,但不能直接公开依赖包的私有函数表。包版本回答“装哪版”,导出契约版本回答“交换什么输入和结果”;不可兼容的变更需要新导出契约。 + +若需要跨包资源引用,正式契约使用显式带标签的类型,例如专门的 backend/lease 变体;不靠普通参数恰好叫 `backendRef` 来识别。宿主签发的租约记录资源所有者、调用链、操作与失效代次,跨包只传受限引用;`${pluginDir}` 永远按声明资源的包解析。用户参数里的同名字符串和对象仍然只是数据。 + +跨包操作只能由宿主在已授权的导出边界调度到资源所属会话,不能将调用方 pluginID 写入目标会话。后端回调宿主重新进入注册表的禁止规则保留;否则等待中的处理器可能等待自己。如何将新授权模型加入函数路由、如何区分私有与公开调用,需要单独版本化,不能删除旧 `wrongPlugin` 或 `recursiveCall` 检查来实现。 + +### 4. 不在此处偷改已实现决策 + +[插件函数契约](../../implemented/architecture/2026-10-20-plugin-function-contract.md)仍是当前行为真源:函数私有,领域结果走类型化契约。此提案没有修改它。若以后采用函数传输领域结果或依赖导出,先在实现 PR 中修订对应决策,给出输入/结果、授权、错误优先级与兼容 fixtures,再改 Core 和双端宿主。 + +新的字段属于统一 `plugin.json` 的能力演进;基础权限或身份语义变化需要新的基础 API。没有 Debug draftVersion,也不要求现有 Build/VCS/UI/语言插件迁移。现有 `backend: 1` 的全局、网页触发行为不变;新的进程共享方式另行版本化。 + +通用 workflow 尚无证明必要的真实输入,不在这个依赖提案里重新引入 steps 或取值语言。先通过实际导出契约完成复用,再判断是否有需要由宿主解释的公共流程。 + +### 正确做法与反例 + +正确做法:两个插件依赖同一个运行时,安装器先求兼容版本并展示共同资源的来源与权限,宿主只对获准导出执行调用。 + +反例:社区插件依赖官方运行时后,自动继承官方身份和全部权限;或冲突时静默换掉另一个已安装插件的依赖。签名和版本检查都不能支持这种行为。 + +## 考虑过的备选方案 + +- 每个领域自己定义依赖和安装:短期好实现,但同一插件的版本、信任和卸载会出现多套答案,不采用。 +- 同 ID 多版本并存:可缓解菱形冲突,但影响设置、存储、市场、UI 身份和进程共享,第一版不承担这一范围。 +- 依赖即授权、所有函数自动导出:声明短,但让社区包借到依赖方权限,破坏现有私有契约,不采用。 +- 只允许声明复用,不允许动态导出:更易验证,可作为第一阶段;是否增加动态操作须由真实插件证明,不为了“完整”预先开放。 + +## 验收标准 + +- 独立任务/PR 完成安装解析、版本冲突、来源固定、哈希校验、事务恢复及并发安装规则,之后才公布新能力。 +- 有共同兼容版本与不兼容菱形两组标准用例,冲突报告带确定排序的两条依赖路径;失败不修改原安装闭包。 +- 禁用/卸载、启动时依赖缺失、升级失效及恢复分别验证反向依赖与资源清理;任何失败不误停不相关插件。 +- 社区依赖官方、同名包来自不同来源、签名变化和导出撤回均有拒绝输入;旧私有函数的归属和递归拒绝语义不变。 +- 如涉及函数协议,在 `lithe-plugin-docs` 验证 Node/Java SDK;正式契约与 Core 一起落地后再同步副本。 +- #1210 的基础 Registry 和第三方 DAP 验收可以完全不安装依赖包而完成。 + +## 风险 + +依赖安装事务和信任会扩大影响面,必须单独评审,不能靠一个 Debug schema 推定已解决。单版本第一版会拒绝部分组合,但比静默替换可靠;多版本共存、导出目录及资源授权的完整 wire 形状仍待后续设计,不属于本次交付。 + +## 验证 + +本次只运行 `./scripts/verify-agent-notes.sh` 和文档检查,没有依赖解析器或跨包调用实现,不能声称安装或授权用例通过。实施时使用 `write-stable-tests`、`./scripts/verify-shared-contracts.sh`、`./scripts/verify-rust-core.sh`,并分别验证两个宿主和函数 SDK;新增资源时遵守工作树复用与安装目录只读要求。 + +## 适用范围 + +- `rust/lithe-core/src/plugins/` +- `rust/lithe-core/src/lsp/interface/plugin_function.rs` +- `shared/contracts/plugins/v1/` +- `macos/Sources/Lithe/Platform/MacOS/` +- `windows/tauri/src-tauri/src/plugin_host/` +- [插件契约与 SDK 仓库](https://github.com/Lithe-IDEA/lithe-plugin-docs) diff --git a/.agents/notes/proposed/architecture/2026-10-25-unified-debug-engine-and-adapter-registry.md b/.agents/notes/proposed/architecture/2026-10-25-unified-debug-engine-and-adapter-registry.md new file mode 100644 index 0000000000..2dc94db3ee --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-10-25-unified-debug-engine-and-adapter-registry.md @@ -0,0 +1,207 @@ +# Agent 笔记:统一调试内核与调试适配器 Registry + +状态:提议中 + +## 先说结论 + +[#1210](https://github.com/1lck/Lithe-IDEA/issues/1210) 不在现有两套调试会话上继续打补丁,而是**重新设计一个统一调试内核**。Rust Core 里只保留一个 DAP 客户端(DAP,Debug Adapter Protocol:编辑器与调试后端之间的标准协议),macOS 和 Windows 都通过 `debug.*` 命令使用它;平台只负责进程、socket、终端和界面。内置适配器和第三方插件进入同一个类型化 Registry(按稳定身份查找调试适配器的目录),插件只需声明“怎么启动这个调试器”。 + +调试能力分三层。断点、线程与调用栈、变量是**必选层**:每个适配器、两个平台都必须做到,也是 #1210 的关闭条件。监视表达式与调试控制台是**推荐层**。寄存器、内存、反汇编、反编译源码、子会话等是**可选扩展**:按适配器协商的结果显示,各自单独推进,见[可选调试扩展](2026-10-25-optional-debug-extensions.md)。可选功能可以晚做,但新内核第一版的数据模型就要为它们留好位置,以后只做增量,不改已有结构。 + +## 问题 + +### 现在是两套内核,按语言接线 + +以下盘点基于 `preview` 提交 `e854cfd98e4a52fe5abacff2002efd9f68082da2`。 + +| 现状 | 位置 | 带来的问题 | +| --- | --- | --- | +| Rust Core `debug.*` 内核:DAP 分帧、请求序列、断点、变量分页、取消、`runInTerminal`、Java 热替换 | `rust/lithe-core/src/debug/` | Windows 的全部调试和 macOS 的 Java 走这里 | +| Swift `DebugAdapterProtocolSession`:通用 stdio、Go、Node(含子会话) | `macos/Sources/LitheDebugModule/Runtime/` | 同一个功能要在 Swift 和 Rust 各写一遍,两边行为容易分叉 | +| 按语言装配的工厂 | `macos/Sources/Lithe/Services/Debug/DebugAdapterRuntimeFactory.swift`、`MacServiceContainer.swift` | 每接一种调试器都要改组合根(Composition Root,负责组装依赖的地方) | +| 以源码为中心的投影 | `rust/lithe-core/src/debug/types.rs`:`DebugStackFrame.line/column` 是必填 `i64`,`DebugScope` 没有 `presentationHint`;initialize 固定声明 `supportsMemoryReferences: false` | 无源码栈帧、寄存器、内存都没有地方表达;以后再放宽必填字段,就是 Rust、Swift、TypeScript 三端同时的破坏性变更 | + +### 为什么重写,而不是继续扩展 + +上一版的方案是“Registry 先接入,内核整合以后在 Provider 背后慢慢做”。这样 Registry 会把两套内核的差异一起固定下来;之后每加一个能力,都要实现两遍、解码三遍。Lithe 的目标是兼容 GDB、LLDB、debugpy、Delve、js-debug 等多种调试器,难点在于 DAP 客户端是否完整,不在插件怎么声明。所以先把内核做成一个,再让所有适配器接到它上面。 + +### 哪些东西不重写 + +- **不自研调试器。** 符号解析、ptrace / Mach 异常端口 / Windows Debug API、各 CPU 架构的断点和单步,都由 GDB、LLDB 等上游负责。GCC 是编译器;GCC 编出来的程序用 GDB 或 LLDB 调试。 +- **不重建项目模型。** Java 的主类和 classpath 来自 JDT,Python 解释器来自 Run,C/C++/Rust 产物来自 Build/Run。 +- **不另造插件协议。** 启动声明复用 `serverProcess`、启动模板、能力版本和安装审查,详见[声明草案](../../../../shared/proposals/debug-adapters-1210/README.md)。 +- **现有行为是迁移基线,不能丢。** 包括 Java 热替换、JUnit/TestNG、调试使用 Run 选中的 JDK、Node 子会话与 source map、断点先于 launch 入队、停止与清理顺序。 + +## 提案 + +### 1. 内核、平台、插件、调试器各管什么 + +| 角色 | 负责 | 不负责 | +| --- | --- | --- | +| 统一内核(Rust Core) | DAP 分帧和请求关联;会话状态机;能力协商;断点存储与位置修正;暂停代次(每次暂停递增的编号)与引用失效;请求处理器注册表;规范化事件与投影;父子会话关系 | 启动进程、打开 socket、创建终端、读文件、画界面 | +| 平台(macOS / Windows) | 按内核给出的计划启动进程和传输;PTY / ConPTY 终端;路径映射与文件授权;界面展示 | 解析 DAP、维护断点或变量状态、按语言写分支 | +| 插件 | 声明调试器怎么启动:进程、传输、可选的参数映射和能力禁用 | 实现 DAP 状态,或声明自己支持哪些标准能力 | +| 调试器(上游) | 语言和平台语义 | — | + +平台和内核之间沿用现有 JSON 信封与 C ABI:平台调用 `debug.createSession`,把返回的 `outboundFrames` 写给适配器,再把适配器的输出交给 `debug.receive`。Windows 已经这样工作;macOS 的 Swift 会话在迁移完成后删除。 + +### 2. 会话模型 v2:第一版就为扩展留位置 + +新内核发布投影版本 2,草案见 [`debug-session-projection.draft.schema.json`](../../../../shared/proposals/debug-adapters-1210/debug-session-projection.draft.schema.json) 和[样例](../../../../shared/proposals/debug-adapters-1210/projection-v2-examples.draft.json)。与 v1 相比: + +| 预留 | 规则 | 为什么第一版就要定 | +| --- | --- | --- | +| 栈帧的 `source`、`line`、`column` 可选 | 无源码帧不伪造文件,也不填第 1 行;可以带 `instructionPointerReference` | v1 的必填 `line` 分不清“位置未知”和“第 1 行” | +| 源码身份三选一 | 工作区相对路径 / 会话内 `sourceReference` / 已授权的外部路径 | 反编译出来的源码、eval 生成的代码、系统库源码都能表达 | +| 地址与引用一律用字符串 | `instructionPointerReference`、`memoryReference` 等都是不透明字符串 | 64 位地址超出 JavaScript 的安全整数范围 | +| scope 透传 `presentationHint` | `arguments`、`locals`、`registers`、`returnValue` 及未知值原样保留 | 寄存器视图直接按提示筛选,不假定所有 CPU 都有同一张寄存器表 | +| 引用绑定会话和暂停代次 | 继续、单步、重启后,旧的 `variablesReference` 和栈帧 ID 失效 | 晚到的结果不会写进新的暂停状态 | +| 能力快照 | 有效支持 = 适配器协商结果 ∧ 本平台已注册的扩展 ∧ 授权;带递增 `revision` | 界面只看快照,不看插件名或清单 | +| 未知值不丢弃 | DAP 新增的枚举值、未知 `presentationHint`、未知停止原因原样保留 | 上游升级时不必先改三端解码器 | + +v1 投影和命令保留到两端都迁到 v2 为止,然后在同一个 PR 里删除 v1、Swift 会话和按语言的工厂。Rust、Swift、TypeScript 共用 `shared/fixtures/debug/*-v2.json`。 + +### 3. 能力分三层 + +**必选层。** 这些请求和事件 DAP 规范本来就要求,不靠 capability 开关。每个注册的适配器都要跑通,两个平台都必须实现: + +| 类别 | 内容 | +| --- | --- | +| 会话生命周期 | `initialize`;`launch` 或 `attach` 至少一种;`configurationDone`;`disconnect` / `terminate`;停止与资源清理 | +| 断点 | 行断点 `setBreakpoints`;断点确认与位置修正(`verified`、移到有效行) | +| 执行控制 | `continue`、`pause`、`next`、`stepIn`、`stepOut` | +| 线程与调用栈 | `threads`(调用栈按线程获取)、`stackTrace` | +| 变量 | `scopes`、`variables`(嵌套展开与分页) | +| 事件 | `stopped`(含停止原因)、`continued`、`exited` / `terminated`、`output` | + +**推荐层。** `evaluate` 的 `watch`、`hover`、`repl` 三种上下文,也就是监视表达式、悬停求值和调试控制台。它们不是 #1210 的关闭条件,但应先于可选扩展完成:用户通常把它们当作“变量追踪”的一部分,GDB/LLDB 的大量功能也要靠控制台命令。 + +**可选扩展。** 断点增强、表达式与变量、执行控制增强、无源码代码(含反编译)、原生调试(寄存器、内存、反汇编、指令断点与单步)、进程与模块、语言专属扩展。分组和各自的验收见[可选调试扩展](2026-10-25-optional-debug-extensions.md)。 + +Registry 注册时不启动程序做验证。一个适配器在接入验收中跑通必选层的标准流程后,才能标记为可用。 + +### 4. 扩展机制:内核模块,不是插件 JSON + +每个可选扩展是一组宿主代码,包括三部分: + +1. 在内核里注册请求处理器和投影解码器; +2. 声明它允许内核在 initialize 里告诉适配器的客户端能力,例如 `supportsMemoryReferences`、`supportsStartDebuggingRequest`、`supportsInvalidatedEvent`; +3. 提供平台界面入口。 + +具体规则: + +- 内核只声明已注册扩展对应的客户端能力。没有内存扩展时,继续声明 `supportsMemoryReferences: false`,适配器就不会发来内存引用。 +- 界面入口只看能力快照,不看适配器名称。 +- 扩展不写进插件清单。插件最多用 `capabilities.disabled` 关掉某个标准能力。 +- 语言专属扩展(如 Java 热替换的 `redefineClasses`)挂在内置 Provider 的描述上:Provider 描述列出它使用的扩展,组合根不按名字判断。第三方的非标准请求不在本提案中开放。 + +### 5. Registry 与插件声明 + +完整字段和拒绝规则见[声明草案](../../../../shared/proposals/debug-adapters-1210/README.md),这里只记录决策: + +- 插件只写一个 `plugin.json.debugAdapters` 块,必填 `id`、`displayName`、`launch`。`launch` 由两部分组成:复用 `serverProcess` 的进程,加上 `stdio`、`tcpAnnounced` 或 `tcpPort` 三种传输之一。 +- 拟定 API 4、`requires.debugAdapters: 1`、`debug.launch` / `debug.attach` 权限。需要 Run 选中的运行时时,使用 `serverProcess: 2` 的 `runSelectedRuntime`。 +- 标准能力跟随 DAP 协商,清单只能禁用。没有依赖、导出、工作流、resolver 或 `hostIntegration` 字段;插件依赖另见[独立平台提案](2026-10-25-plugin-dependencies-and-exports.md)。 +- 内置 Provider(Java、Go、Node、Python、LLDB)和第三方声明转成同一种描述后进入 Registry。身份由插件 ID 加局部 ID 组成,内置 Provider 使用保留空间。 +- 选择顺序为:配置里的显式选择 → 工作区已保存的关联 → 唯一匹配的候选。多个候选时由用户选择,插件不能自报优先级。 +- Run 配置 ID、三层覆盖规则和导入的 `launch.json` 参数保持不变;目标映射只补缺失的值,不覆盖显式写出的 `program`、`args`。 + +### 6. 资源与生命周期 + +- 每次准备和连接都绑定工作区、Provider 注册代次和操作,并设有期限;调试会话本身不设总时长。 +- 禁用与启动同时发生时,执行前再确认一次;已经创建的资源进入同一条清理链。取消、停止、失败后丢弃晚到的结果;重复点击停止共享同一个完成状态。 +- adapter 进程、被调试程序、终端、共享语言服务器和外部 attach 目标分别记录所有权。`launch` 结束自己启动的程序,`attach` 默认只断开;结束 Java 调试不终止 JDT LS。 +- 插件包和安装目录只读。日志、缓存、锁文件和临时状态由平台存储 adapter 写到可写目录,不影响代码签名和 Sparkle 增量更新。 + +### 7. 分阶段交付 + +| 阶段 | 交付与退出条件 | +| --- | --- | +| P0 行为基线 | 把现有两套内核的行为录成 v1 基线 fixtures:启动序列、断点先于 launch、断点位置修正、变量分页、停止与断开策略、`runInTerminal`、Node 子会话、Java 热替换 / 测试 / Run JDK、取消与晚到事件 | +| P1 新内核与 v2 投影 | 新内核实现必选层和推荐层、v2 投影、能力快照、扩展注册表;三端都能解码 v2 fixtures;通过全部 P0 基线 | +| P2 内置迁移与 Registry | 内置适配器逐个注册为 Provider 并切到新内核,建议顺序:通用 stdio → Go → Node → Python → Java。每个都有可回退的开关,同一目标只启用一个实现;Windows 同步迁移 | +| P3 第三方声明契约 | 正式 schema、Core 语义校验、版本号、权限、安装审查一起落地;声明草案里的拒绝 fixtures 接上 Core 测试 | +| P4 第三方纵向验收 | 两个平台各用一个真实第三方适配器(GDB DAP `gdb -i=dap` 或 `lldb-dap`)完成安装、选择、launch / attach、必选层全流程和资源回收 | +| P5 删除旧路径 | 所有消费者迁到 v2 之后,在同一个 PR 里删除 Swift 会话、按语言的工厂和 v1 投影 | + +P0–P5 完成后关闭 #1210。可选扩展、插件依赖和私有 resolver 都不是关闭条件。 + +### 正确做法 + +- 接入 GDB:插件声明 `systemCommand: ["gdb"]`、参数 `["-i=dap"]`、`stdio` 传输。断点、线程、栈、变量直接出现在现有调试窗口里,插件不列任何能力。 +- 新增内存查看:在内核注册 `readMemory` 处理器,允许内核声明 `supportsMemoryReferences`,再在两端加界面入口。GDB 和 LLDB 都会自动获得这项功能,插件不需要改动。 +- 调试一个没有符号的汇编程序:栈帧只有名称和 `instructionPointerReference`,界面显示“无源码”,不跳到某个文件的第 1 行。 + +### 不要这样做 + +- 在 Swift 和 Rust 里分别补同一个能力,或者在组合根按语言名再加一个工厂分支。 +- 给无源码帧填一个假文件和第 1 行,让旧解码器“看起来能用”。 +- 让插件在清单里逐项声明 `supportsX`,或者用插件函数实现标准 DAP 请求。 +- 在 P0 基线全部通过之前删除旧会话,或者为了切换方便先去掉 Java 测试、热替换、子会话等现有功能。 + +## 考虑过的备选方案 + +### 在现有两套内核上增量扩展 + +这是上一版的方案。它的吸引力是迁移量小,Registry 可以先上线。不采用的原因:每加一个能力都要实现两遍、解码三遍;以源码为中心的 v1 模型迟早要做破坏性修改,越晚改受影响的消费者越多。 + +### 每个平台各自实现 DAP 客户端 + +在 Swift 和 TypeScript 里各写一套,可以最贴近各自平台的界面框架。不采用的原因:状态机、断点修正、暂停代次这些语义会在两端分叉,现有 Windows 已经证明 Rust 内核加平台传输的分工可行。 + +### 直接引入第三方 DAP 客户端库 + +可以省去手写协议类型。不采用整套库,因为会话状态机、暂停代次、资源所有权、取消与晚到结果是 Lithe 自己的语义,现成的库不提供。可以只复用协议类型:从 DAP 官方 JSON 定义生成,减少手写错误。 + +### 自研调试器 + +可以完全掌控功能。不采用的原因:要重复实现 GDB/LLDB 几十年积累的符号解析、平台调试接口和架构细节,还要处理系统调试授权,偏离本任务。 + +### 插件 JSON 工作流与插件依赖 + +更早一版曾在 Debug 声明里加入依赖、导出、跨插件函数和 `steps` 流程。已撤回:这些属于插件平台,涉及市场依赖解析、版本冲突和信任传递;而且示例里没有证明必要的真实流程。依赖部分移到[独立平台提案](2026-10-25-plugin-dependencies-and-exports.md)。 + +## 验收标准 + +- macOS 和 Windows 都只使用统一内核;Swift 会话、按语言工厂和 v1 投影在 P5 中删除,删除前全部 P0 基线 fixtures 通过。 +- 必选层的每一项都有共享 fixtures,覆盖正常流程、取消、晚到事件和引用失效;推荐层有独立用例。 +- v2 fixtures 至少包含:有源码帧、无源码帧、`sourceReference` 源码、`registers` scope、超过 2^53 的地址字符串、未知 `presentationHint`;三端解码结果一致。 +- 能力快照只在适配器协商、扩展注册和授权都满足时报告 supported;未注册扩展时,initialize 不声明对应的客户端能力。 +- 内置与第三方共用 Registry;显式选择、工作区关联、候选歧义、重复身份、禁用失效都有标准输入和结果;组合根里没有按插件名或语言名的分支。 +- 至少一个真实第三方适配器在两个平台上各自完成安装、选择、launch 与 attach、必选层全流程和停止;结束后本次启动的 Lithe、适配器和被调试程序都已退出,外部 attach 目标保留。 +- API 1/2/3、Run 配置、Build/VCS/语言/UI/设置、私有函数的现有兼容结果保持不变;Java 测试、Run JDK、热替换,以及 Node 子会话和终端不回退。 + +## 风险 + +- **重写周期长,期间两套内核并存。** P2 按适配器用开关切换,未迁移的继续走旧路径;同一个目标从不同时启用两个实现。 +- **基线漏录导致功能回退。** P0 以现有测试和真实流程为来源逐项列清单,评审时检查清单,而不只检查 fixture 数量。 +- **v1 和 v2 并存的维护成本。** 只在迁移期并存,P5 统一删除;迁移期间新功能只加在 v2 上。 +- **后端差异。** GDB 是否带 DAP 取决于版本和构建方式(需要 GDB 14 及以上),Windows 上要实测 MinGW 版本;未验证的组合不标记为可用。 +- **拟定版本号冲突。** 实施前重新核对能力表,只取下一个可用编号,不改变已发布编号的含义。 + +## 验证 + +本阶段只修改设计文档和草案 schema。运行 `./scripts/verify-agent-notes.sh`、`./scripts/verify-shared-contracts.sh`、`./scripts/verify-runtime-bundle-immutability.sh` 和 `git diff --check`,并用 Draft 2020-12 离线校验草案 schema 与样例。这些检查不构成运行时证据。 + +实施阶段加载 `write-stable-tests`,按改动范围运行 `./scripts/verify-rust-core-comments.sh`、`./scripts/verify-rust-core.sh`、`./scripts/test-macos.sh`、`./scripts/verify-service-boundaries.sh` 和 `./scripts/verify-windows-boundaries.sh`;Windows 另外执行 `./scripts/build-windows.ps1 -Configuration Release`、宿主 Rust 测试和前端测试。所有等待都有上限,失败时也释放资源,结束后确认本次启动的进程都已退出。平台行为变化时同步功能矩阵,运行 `./scripts/verify-platform-feature-matrix.sh`。正式契约与 Core 一起修改,合并后按[两仓库发布规则](../../implemented/architecture/2026-10-23-plugin-contract-publishing.md)同步 `lithe-plugin-docs`。 + +## 适用范围 + +- `rust/lithe-core/src/debug/` +- `rust/lithe-core/src/plugins/` +- `shared/contracts/plugins/v1/` +- `shared/contracts/rust-core-api.md` +- `shared/proposals/debug-adapters-1210/` +- `shared/fixtures/debug/` +- `macos/Sources/LitheCoreContracts/Debug/` +- `macos/Sources/LitheDebugModule/` +- `macos/Sources/Lithe/Services/Debug/` +- `macos/Sources/Lithe/Platform/MacOS/MacServiceContainer.swift` +- `windows/tauri/src/features/debugger/` +- `windows/tauri/src-tauri/src/debug.rs` + +## 依据 + +- [公共扩展契约](2026-10-13-public-extension-contract-and-provider-api.md)、[能力版本](../../implemented/architecture/2026-10-17-plugin-capability-versions.md)、[插件函数契约](../../implemented/architecture/2026-10-20-plugin-function-contract.md)。 +- [Java 构建与启动边界](../../implemented/architecture/2026-09-18-java-project-build-and-launch-boundary.md)、[Java Debug 使用 Run 的 JDK](../../implemented/bug-fix/2026-10-14-java-debug-uses-the-run-jdk.md)、[Windows Java 调试生命周期](../../implemented/bug-fix/2026-10-07-windows-java-debug-lifecycle.md)。 +- [DAP 标准](https://microsoft.github.io/debug-adapter-protocol/overview)、[DAP 定义](https://github.com/microsoft/debug-adapter-protocol/blob/main/debugAdapterProtocol.json)、[GDB DAP](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Debugger-Adapter-Protocol.html)、[LLDB DAP](https://lldb.llvm.org/use/lldbdap.html)。 diff --git a/shared/proposals/debug-adapters-1210/README.md b/shared/proposals/debug-adapters-1210/README.md new file mode 100644 index 0000000000..9e148fc9ad --- /dev/null +++ b/shared/proposals/debug-adapters-1210/README.md @@ -0,0 +1,117 @@ +# 调试适配器声明评审草案(#1210) + +本目录评审 #1210 中两份**跨端契约草案**: + +1. 插件怎么声明调试适配器:放在现有 `plugin.json.debugAdapters`,不是独立注册文件; +2. 统一调试内核交给两个平台的会话投影 v2。 + +调试内核本身怎么重写、能力怎么分层,以 Agent Note 为准;本目录不重复这部分内容。 + +当前宿主不接受这些新增字段。本 PR 只提交计划,不改变正式契约或产品行为。 + +## 评审材料 + +- [统一调试内核与调试适配器 Registry](../../../.agents/notes/proposed/architecture/2026-10-25-unified-debug-engine-and-adapter-registry.md):内核重写、必选 / 推荐 / 可选三层能力、P0–P5 迁移与验收。 +- [可选调试扩展](../../../.agents/notes/proposed/architecture/2026-10-25-optional-debug-extensions.md):寄存器、内存、反汇编、反编译源码、子会话等,各自单独验收。 +- [能力块 schema](debug-adapters.draft.schema.json):只校验从完整清单提取的 `debugAdapters` 段。 +- [拒绝 fixtures](semantic-rejections.draft.json):合成的契约验收输入,不是可安装插件;区分 schema 能拒绝和待 Core 实现的语义拒绝。 +- [会话投影 v2 schema](debug-session-projection.draft.schema.json) 与[样例](projection-v2-examples.draft.json):可选的源码位置、字符串地址、`presentationHint` 透传、暂停代次与能力快照。 + +插件依赖、导出与跨包授权移到[独立平台提案](../../../.agents/notes/proposed/architecture/2026-10-25-plugin-dependencies-and-exports.md),不是 #1210 的依赖或关闭条件。 + +插件声明只描述“怎么启动调试器”。断点、调用栈、变量等标准能力由新内核与适配器通过 DAP 协商,清单不逐项声明;寄存器、内存等扩展是宿主内核模块,不出现在清单里。 + +## 版本与清单位置 + +以下是本次选择的**拟定版本**,不是已分配、已实现的正式版本。落地前核对并行契约,若编号被占用,整体调整而非改变已发布版本的含义。 + +| 完整 `plugin.json` 中的位置 | 本提案的选择 | +| --- | --- | +| `schemaVersion`、`id`、`version`、`vendor` 等 | 沿用现有清单;不在 Debug 段重复插件身份 | +| `pluginAPIVersion` | 拟定 `4`:基础权限集合新增 Debug 授权;API 1/2/3 不接受新权限或 Debug 段 | +| `requires.debugAdapters` | 拟定 `1`:固定进程/传输声明与 Registry 接入 | +| `requires.serverProcess` | 普通进程为 `1`;使用 `runSelectedRuntime` 为拟定的 `2`。后者由共享进程契约拥有,不在版本 1 偷加 Python | +| `permissions` | 拟定 `debug.launch`:运行 DAP 适配器、控制本次 launch 目标;声明 attach 还需 `debug.attach` | +| `debugAdapters[]` | 本目录唯一 schema 评审的能力块 | +| `activationEvents` | 不新增事件。发现只读清单;用户选择并开始 Debug 时才启动适配器 | + +没有 UI 或语言也可注册。`debug.launch` 不暗含 Build、测试、网络下载或其他插件的调用权限;attach 必须展示独立风险并获准。调试本身可能修改目标状态,安装审查必须如实说明,不把它包装成只读权限。未来内存写入等新操作另行评审。 + +`requires` 继续恰好列出所用能力。Core 校验、磁盘安装、市场过滤、安装确认和运行前检查使用同一能力表;宿主实际实现后才公布 `hostCapabilities`。本目录的 schema 不替代完整清单校验,特别是版本、权限、签名和 `requires` 的交叉检查。 + +拒绝顺序沿用能力版本规则:基础格式/API 支持范围 → `requires` 的能力及宿主支持 → Debug 段结构与语义 → 权限及包内资源 → 运行前目标与代次。API 3 携带 Debug 能力时,支持 API 4 的新 Core 在 `requires.debugAdapters` 拒绝;旧 Core 保留自身原来的拒绝位置和优先级,不为草案改写历史结果。 + +## 必需与可选 + +每个适配器只必填 `id`、`displayName`、`launch`;`launch` 只描述一个进程和一种传输。`process` 复用共享 `serverProcess` 的启动字段、模板、安装审查及校验,排除只适用于 LSP 的 `initializationOptions`。不要求后端、依赖、函数、流程或参数 schema。 + +| 可选项 | 省略时的含义 | +| --- | --- | +| `adapterID` | DAP initialize 使用局部 `id`;Registry 身份始终是插件 ID 加局部 ID | +| `requests` | 只允许 `launch`;attach 必须显式开启 | +| `selectors` | 仅供显式选择,不自动匹配全部目标 | +| `configurationTypes` | 不认领导入配置的 `type` 别名 | +| `configurations.launch/attach` | 不提供额外默认参数、目标映射或参数 schema | +| `capabilities.disabled` | 标准能力默认全部跟随 DAP 的默认值、initialize 与后续 capabilities 事件 | + +局部适配器 ID 统一使用 `^[a-z][a-z0-9._-]{0,63}$`;插件 ID、语言 ID 沿用各自已有规则,它们不是同一种标识。身份、语言、DAP adapterID、导入 type、Run 配置 ID 和 sessionID 不相互替代。 + +## 固定启动声明,不引入工作流 + +`launch` 固定包含 `process` 和 `transport`: + +- `stdio`:适配器进程的标准输入输出直接传 DAP,stderr 仅记录诊断。 +- `tcpAnnounced`:从明确指定的 stdout/stderr 读取有界 UTF-8 行。字面量 `prefix` 后只接受端口或 `host:port`;IPv6 使用方括号,必须是 loopback。限制单行 4 KiB、总启动输出 1 MiB、连接准备 30 秒;未完整解出合法端点就不连接。没有 `jsDebugStandalone` 等按产品命名的解码档位。 +- `tcpPort`:连接本次适配器进程的固定 loopback 端口;进程先启动,端口占用则失败,不悄悄改端口。它不是 DAP attach 参数里的被调试程序端口。若无法证明连接对应本次启动的进程,平台应拒绝该传输,不公布支持。 + +TCP 启动期限只约束端口发现与连接,不限制整个调试会话。关闭、失败、取消和禁用都会关闭连接、清理本次创建的进程;已有共享服务和外部 attach 目标不属于这条进程清理链。 + +普通 process 沿用 `packaged`、`systemCommand`、Java/Node `runtime`;`runSelectedRuntime` 是共享进程版本 2 的新增分支,先支持 Python、Java、Node,取得 **Run 对同一目标选中的运行时**,然后执行声明的参数。没有独立 Debug 解释器偏好、隐式安装或静默切到 PATH。没有显式选择时沿用 Run 自己的发现链。一个包只声明一个 `serverProcess` 版本:包含该分支时为 2,并继续接受该包内其他进程的旧启动形式;完全不用新形式仍声明 1,避免无谓过滤旧宿主。 + +已有 `${pluginDir}` 等启动模板保持原规则;此处永远指**声明该进程的当前包**。路径字段、模板展开及参数数组仍由共享解析器处理。`configuration.arguments` 是普通 JSON,不把其中的 `backendRef`、`$lease` 等同名字段解释为资源。此版没有资源引用输入、跨包租约或用户 JSON 中的隐藏句柄。 + +debugpy、lldb-dap 可以走 stdio,Delve/js-debug 可按真实启动方式选择 TCP;是否能完整调试仍要核对后端版本、端口输出和目标参数。Java 先由内置 Provider 接入 Registry,继续使用已有 JDT 集成,不把宿主服务暴露为第三方 JSON 字段。一个模型统一接入方式,不保证所有后端功能相同。 + +## 配置与能力 + +保留现有 `DebugLaunchConfiguration`、Run 配置 ID、三层覆盖和导入参数。处理顺序是:适配器默认参数 → 现有 Run/导入/用户覆盖链 → 只补缺失的目标字段 → 可选参数 schema → Core 规范化。schema 的 `default` 是文档提示,实际默认值由 Core 实现。 + +`targetBindings` 限定为 `program ← target.path`、`processId ← target.processID`、`cwd ← target.workingDirectory`;不允许把进程号当路径,也不引入任意表达式。如果所选目标缺少必需事实且显式参数也未提供,准备阶段失败,不启动进程。文件目标不会自动产生 `program`,插件需要选择映射或由现有 Run 配置提供参数;不能仅声明 selector 就声称能启动。 + +路径身份和执行路径分开:工作区内源码的持久化身份仍为相对路径;Build/Run 可以提供用户已选择或授权的外部产物引用,平台在执行时解析为绝对路径。源码外构建和工作区外可执行文件无需伪造相对路径。本提案不新增 `workspacePath`、`configuration.id` 或 `target.configurationID` 的重复结构。 + +`capabilities.disabled` 只接受标准 DAP capability 名称或 `exceptionBreakpointFilters`,Core 按固定版本的 DAP 定义检查名称并映射到对应请求/数据。结构 schema 只检查名称形状,不能证明名称存在。completions、steppingGranularity、cancel 等随 DAP,而不是再维护一份静态支持列表。省略不隐藏功能,声明也不能让尚未实现该操作的宿主点亮按钮;会话取消和清理始终保留,不能用 suppression 关闭。 + +此版没有标准操作的 `source: function`,也没有公开扩展函数。Java 热替换保留现有类型化 `redefineClasses` 链路。将来只有先定义输入、输出、会话归属与错误语义的扩展才可能绑定函数,不能给任意标准能力填一个函数名。 + +## 动态 resolver 明确后置 + +本次**不开放 `resolver`、`backendRef`、`hostIntegration`、`preparation` 字段**。复杂内置适配器通过同一个内部 Provider 契约返回计划,不等于第三方可凭 JSON 调用 JDT。 + +本包私有 resolver 比跨插件调用范围小,但仍改变了“领域结果不走插件函数”的已落地决策。它是独立的后续 `debugAdapters` 版本:先修订函数决策、定义固定的 Debug 输入/结果及本包授权,再复用 `lithe/function/invoke`。旧 `backend: 1` 仍是全局、网页触发的进程,不能为了 Debug 改成工作区后端或要求空网页。 + +如需重新准备,后续结果只能引用宿主已解析的 Build/Run action 身份及版本,不接受任意字符串、脚本或再次进入 Registry 的命令。宿主执行成功后最多重入解析一次,第二次要求准备即拒绝;项目事实随后重新从拥有它的引擎读取。这个后续门槛不阻塞固定声明的 P3–P4(见主 Note 的分阶段交付)。 + +## Schema 与 Core 校验分界 + +`semantic-rejections.draft.json` 以一个合成基线加 `set`/`remove` 描述单个拒绝输入,沿用已有 `invalid-manifests.json` 的 JSON Pointer fixture 约定,不新建编辑语法;路径只用于构造测试数据,**不是插件工作流语法**。每条记录标注校验层和预期稳定错误码,权限缺失沿用已有 `plugin.permission.undeclared`,新增 Debug 错误码仍为拟定;`expected.field` 是标准化后的错误位置,重复 ID 固定指向第二个条目。Core 计划也须验证输入目标快照与字段类型;无法从参数 schema 推断的后端必需字段,仍由 DAP 返回明确错误。无关基础清单字段按正式契约补足,不假装这些投影能安装。 + +| 规则 | 必须拒绝的情况 | Fixture | +| --- | --- | --- | +| S01 关闭新字段集合 | 旧依赖/导出/steps、resolver 或 hostIntegration 回流 | `removed-platform-fields`、`removed-resolver`、`removed-host-integration`、`removed-workflow` | +| S02 已知映射及实现 | program 绑定进程号,或标准能力绑定函数 | `program-from-process-id`、`function-for-step-back` | +| C01 版本入口 | API 3 使用 Debug 或新权限;Core/宿主不支持所需版本 | `old-api-debug`、`missing-host-capability` | +| C02 requires 精确匹配 | 少列 Debug/进程能力,或普通进程虚报版本 2 | `missing-debug-requirement`、`missing-process-requirement`、`unused-process-v2` | +| C03 身份唯一 | 同一插件重复 adapter id | `duplicate-adapter-id` | +| C04 请求与授权一致 | 缺 debug.launch、attach 缺授权、配置声明不支持的请求 | `missing-launch-permission`、`attach-without-permission`、`unsupported-request-config` | +| C05 运行时契约 | runSelectedRuntime 却声明 serverProcess 1,或选择结果失效 | `selected-runtime-v1`、`stale-selected-runtime` | +| C06 名称存在 | 拼写形似 DAP,但不是固定定义里的能力 | `unknown-dap-capability` | +| C07 目标事实完整 | program 映射缺路径,且合并后也无显式 program | `missing-target-path` | +| C08 包内资源归属 | 参数 schema 的符号链接逃出包 | `escaping-parameters-schema` | +| C09 运行前再确认 | 插件注册代次或计划已过期 | `stale-registration` | + +移除后的跨依赖和 step 引用不再进入语义解释器,直接因未知字段被拒绝;并不代表实现了依赖图或流程引用检查。 + +**验证口径:**离线检查只能证明本草案 schema 合法,且结构层 fixture 有预期结果。C01–C09 的输入已列出,但没有对应新增 Core 实现或执行证据;不得把它们计为“已通过的 Core 拒绝用例”。正式落地时把这些 fixtures 迁到正式契约并接上 Core 测试,每条校验错误码、字段、优先级及无副作用;双端运行验证另外提供。 + +本次没有启动应用、后端或调试程序。契约与 Core 同批落地后,按仓库规则同步 `lithe-plugin-docs`;教程、SDK、示例只在该仓库维护,主仓库保留正式契约和回归 fixtures。 diff --git a/shared/proposals/debug-adapters-1210/debug-adapters.draft.schema.json b/shared/proposals/debug-adapters-1210/debug-adapters.draft.schema.json new file mode 100644 index 0000000000..9612ed4af2 --- /dev/null +++ b/shared/proposals/debug-adapters-1210/debug-adapters.draft.schema.json @@ -0,0 +1,207 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://lithe.invalid/proposals/1210/debug-adapters.draft.schema.json", + "title": "Proposed plugin.json debugAdapters capability block", + "description": "Review-only projection of plugin.json.debugAdapters, not a second manifest. API 4, debugAdapters 1 and serverProcess 2 are proposed, not implemented. Full-manifest permissions, requires and semantic validation are specified in README.md.", + "type": "object", + "additionalProperties": false, + "required": ["debugAdapters"], + "properties": { + "debugAdapters": { + "type": "array", + "minItems": 1, + "maxItems": 16, + "items": { "$ref": "#/$defs/adapter" } + } + }, + "$defs": { + "localID": { "type": "string", "pattern": "^[a-z][a-z0-9._-]{0,63}$" }, + "packagePath": { + "description": "Read-only package-relative path. Core must additionally reject escaping symlinks and platform-specific path escapes.", + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)[.]{1,2}(?:/|$))[^\\\\:\\u0000]+$" + }, + "serverProcessV1": { + "$ref": "../../contracts/plugins/v1/plugin-manifest.schema.json#/$defs/serverProcess", + "description": "Reuse existing launch fields and validation. LSP initializationOptions have no meaning for a DAP transport and are excluded.", + "propertyNames": { "enum": ["launch", "arguments", "validationArguments", "runtimeDependencies"] }, + "unevaluatedProperties": false + }, + "runSelectedRuntimeProcess": { + "description": "Proposed serverProcess 2 branch. Resolve through Run's selected runtime for this target, not a plugin dependency or a separate Debug runtime preference.", + "type": "object", + "additionalProperties": false, + "required": ["launch"], + "properties": { + "launch": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "tool"], + "properties": { + "kind": { "const": "runSelectedRuntime" }, + "tool": { "enum": ["python", "java", "node"] } + } + }, + "arguments": { "$ref": "../../contracts/plugins/v1/plugin-manifest.schema.json#/$defs/serverProcess/properties/arguments" }, + "validationArguments": { "$ref": "../../contracts/plugins/v1/plugin-manifest.schema.json#/$defs/serverProcess/properties/validationArguments" } + } + }, + "process": { + "oneOf": [ + { "$ref": "#/$defs/serverProcessV1" }, + { "$ref": "#/$defs/runSelectedRuntimeProcess" } + ] + }, + "launch": { + "type": "object", + "required": ["process", "transport"], + "properties": { "process": { "$ref": "#/$defs/process" } }, + "oneOf": [ + { + "properties": { "transport": { "const": "stdio" } } + }, + { + "required": ["endpoint"], + "properties": { + "transport": { "const": "tcpAnnounced" }, + "endpoint": { + "description": "A bounded line decoder, not an executable-name-specific format or a regex engine. Only loopback endpoints are accepted.", + "type": "object", + "additionalProperties": false, + "required": ["stream", "prefix", "value"], + "properties": { + "stream": { "enum": ["stdout", "stderr"] }, + "prefix": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "^[^\\r\\n]+$" }, + "value": { "enum": ["port", "hostPort"] } + } + } + } + }, + { + "required": ["endpoint"], + "properties": { + "transport": { "const": "tcpPort" }, + "endpoint": { + "description": "A fixed loopback port for this adapter process, not the debuggee's attach port. Conflicts fail explicitly; no automatic port substitution.", + "type": "object", + "additionalProperties": false, + "required": ["port"], + "properties": { "port": { "type": "integer", "minimum": 1, "maximum": 65535 } } + } + } + } + ], + "unevaluatedProperties": false + }, + "selector": { + "description": "Optional automatic matching: OR between selectors, AND between fields. Omission means explicit selection only, not a wildcard registration.", + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "targetKinds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "enum": ["file", "executable", "runTarget", "process", "connection"] } + }, + "languageIDs": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "pattern": "^[a-z0-9+_-]+$" } + } + } + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "arguments": { + "description": "Adapter-owned default DAP arguments. Ordinary JSON data; no embedded references, expressions or resource handles are interpreted.", + "type": "object" + }, + "parametersSchema": { + "description": "Optional packaged schema for final merged DAP arguments, with local read-only refs only. Not a new Debug configuration file.", + "$ref": "#/$defs/packagePath" + }, + "targetBindings": { + "description": "Typed projections into known DAP fields. They only fill missing arguments; explicit values retain their existing precedence.", + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "program": { "const": "target.path" }, + "processId": { "const": "target.processID" }, + "cwd": { "const": "target.workingDirectory" } + } + } + } + }, + "capabilities": { + "description": "Optional suppression only. Disabled entries are standard DAP initialize capability names plus exceptionBreakpointFilters. Omission follows DAP; true values and function implementations are not accepted.", + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "disabled": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^(supports[A-Z][A-Za-z0-9]*|exceptionBreakpointFilters)$" + } + } + } + }, + "adapter": { + "type": "object", + "additionalProperties": false, + "required": ["id", "displayName", "launch"], + "properties": { + "id": { "$ref": "#/$defs/localID" }, + "displayName": { "type": "string", "minLength": 1, "maxLength": 128 }, + "adapterID": { + "description": "DAP initialize adapterID. Defaults to the local adapter id; never substitutes for the qualified Registry identity.", + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "requests": { + "description": "Defaults to launch only. Attach must be explicitly declared and separately authorized.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "enum": ["launch", "attach"] }, + "default": ["launch"] + }, + "selectors": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/selector" } + }, + "configurationTypes": { + "description": "Optional imported launch.json type aliases. Ambiguity requires selection; aliases do not override another provider.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "configurations": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "launch": { "$ref": "#/$defs/configuration" }, + "attach": { "$ref": "#/$defs/configuration" } + } + }, + "capabilities": { "$ref": "#/$defs/capabilities" }, + "launch": { "$ref": "#/$defs/launch" } + } + } + } +} diff --git a/shared/proposals/debug-adapters-1210/debug-session-projection.draft.schema.json b/shared/proposals/debug-adapters-1210/debug-session-projection.draft.schema.json new file mode 100644 index 0000000000..ee7af63fbd --- /dev/null +++ b/shared/proposals/debug-adapters-1210/debug-session-projection.draft.schema.json @@ -0,0 +1,176 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://lithe.invalid/proposals/1210/debug-session-projection.draft.schema.json", + "title": "Proposed debug session projection version 2", + "description": "Review-only shape of the normalized projections the unified Rust Core debug engine returns to macOS and Windows. Version 2 reserves room for optional extensions (no-source frames, session sources, registers, memory) so they can be added later without breaking changes. Not implemented; v1 stays in place until every consumer migrates.", + "type": "object", + "additionalProperties": false, + "required": ["projectionVersion", "sessionID", "stopGeneration", "projection"], + "properties": { + "projectionVersion": { "const": 2 }, + "sessionID": { "$ref": "#/$defs/opaqueID" }, + "stopGeneration": { + "description": "Incremented on every stop. References from an older generation are invalid after continue, step or restart; late results carrying them are dropped.", + "type": "integer", + "minimum": 0 + }, + "projection": { + "oneOf": [ + { "$ref": "#/$defs/stackTrace" }, + { "$ref": "#/$defs/scopes" }, + { "$ref": "#/$defs/capabilities" } + ] + } + }, + "$defs": { + "opaqueID": { "type": "string", "minLength": 1, "maxLength": 256 }, + "opaqueReference": { + "description": "Adapter-owned address or reference kept as a string. Hosts never do arithmetic on it; 64-bit addresses exceed the JavaScript safe integer range.", + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "openString": { + "description": "DAP enumerations are open; unknown values are preserved instead of rejected.", + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "source": { + "description": "Exactly one source identity.", + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["kind", "path"], + "properties": { + "kind": { "const": "workspace" }, + "path": { + "description": "Workspace-relative path; absolute paths and parent segments are rejected.", + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?![A-Za-z]:)(?!(.*/)?\\.\\.(/|$)).+$" + }, + "name": { "type": "string" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["kind", "sourceReference", "name"], + "properties": { + "kind": { "const": "session" }, + "sourceReference": { + "description": "Session-bound DAP sourceReference, fetched with the source request. Invalid after the session ends.", + "type": "integer", + "minimum": 1, + "maximum": 2147483647 + }, + "name": { "type": "string", "minLength": 1 }, + "origin": { "type": "string" } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["kind", "grantID", "name"], + "properties": { + "kind": { "const": "external" }, + "grantID": { + "description": "Host-issued grant for an authorized path outside the workspace; the raw path is not exposed to the UI layer.", + "$ref": "#/$defs/opaqueID" + }, + "name": { "type": "string", "minLength": 1 } + } + } + ] + }, + "stackFrame": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name"], + "properties": { + "id": { "$ref": "#/$defs/opaqueID" }, + "name": { "type": "string" }, + "source": { "$ref": "#/$defs/source" }, + "line": { "type": "integer", "minimum": 1 }, + "column": { "type": "integer", "minimum": 1 }, + "endLine": { "type": "integer", "minimum": 1 }, + "endColumn": { "type": "integer", "minimum": 1 }, + "instructionPointerReference": { "$ref": "#/$defs/opaqueReference" }, + "moduleID": { "type": "string" }, + "presentationHint": { "$ref": "#/$defs/openString" }, + "isFiltered": { "type": "boolean" } + }, + "dependentRequired": { + "line": ["source"], + "column": ["line"], + "endLine": ["line"], + "endColumn": ["endLine"] + } + }, + "stackTrace": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "threadID", "frames"], + "properties": { + "kind": { "const": "stackTrace" }, + "threadID": { "type": "integer" }, + "frames": { "type": "array", "items": { "$ref": "#/$defs/stackFrame" } }, + "totalFrames": { "type": "integer", "minimum": 0 } + } + }, + "scope": { + "type": "object", + "additionalProperties": false, + "required": ["name", "variablesReference", "expensive"], + "properties": { + "name": { "type": "string" }, + "presentationHint": { + "description": "Passed through as reported: arguments, locals, registers, returnValue or any unknown value.", + "$ref": "#/$defs/openString" + }, + "variablesReference": { "type": "integer", "minimum": 0 }, + "namedVariables": { "type": "integer", "minimum": 0 }, + "indexedVariables": { "type": "integer", "minimum": 0 }, + "expensive": { "type": "boolean" }, + "source": { "$ref": "#/$defs/source" } + } + }, + "scopes": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "frameID", "scopes"], + "properties": { + "kind": { "const": "scopes" }, + "frameID": { "$ref": "#/$defs/opaqueID" }, + "scopes": { "type": "array", "items": { "$ref": "#/$defs/scope" } } + } + }, + "capabilityState": { + "type": "object", + "additionalProperties": false, + "required": ["adapter", "extension", "authorized", "supported"], + "properties": { + "adapter": { "type": "boolean", "description": "Reported by the adapter in initialize or a capabilities event." }, + "extension": { "type": "boolean", "description": "The host extension handling it is registered on this platform." }, + "authorized": { "type": "boolean" }, + "supported": { "type": "boolean", "description": "Must equal adapter AND extension AND authorized; Core enforces this." } + } + }, + "capabilities": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "revision", "capabilities"], + "properties": { + "kind": { "const": "capabilities" }, + "revision": { "type": "integer", "minimum": 0 }, + "capabilities": { + "type": "object", + "propertyNames": { "pattern": "^supports[A-Z][A-Za-z0-9]*$" }, + "additionalProperties": { "$ref": "#/$defs/capabilityState" } + } + } + } + } +} diff --git a/shared/proposals/debug-adapters-1210/projection-v2-examples.draft.json b/shared/proposals/debug-adapters-1210/projection-v2-examples.draft.json new file mode 100644 index 0000000000..0551a34988 --- /dev/null +++ b/shared/proposals/debug-adapters-1210/projection-v2-examples.draft.json @@ -0,0 +1,151 @@ +{ + "description": "Synthetic projection v2 inputs for review. accept entries must validate against debug-session-projection.draft.schema.json; reject entries must fail schema validation. Semantic rules (supported equals the conjunction, stale generations are dropped) belong to Core tests.", + "accept": [ + { + "name": "source frame and no-source assembly frame on one thread", + "value": { + "projectionVersion": 2, + "sessionID": "s-1", + "stopGeneration": 3, + "projection": { + "kind": "stackTrace", + "threadID": 1, + "frames": [ + { + "id": "f-0", + "name": "parse_header", + "source": { "kind": "workspace", "path": "src/parse.c" }, + "line": 42, + "column": 5, + "instructionPointerReference": "0x0000000100003f2c" + }, + { + "id": "f-1", + "name": "memcpy", + "instructionPointerReference": "0xffffffff81002a10", + "presentationHint": "subtle" + } + ], + "totalFrames": 2 + } + } + }, + { + "name": "session-bound decompiled source", + "value": { + "projectionVersion": 2, + "sessionID": "s-2", + "stopGeneration": 1, + "projection": { + "kind": "stackTrace", + "threadID": 17, + "frames": [ + { + "id": "f-0", + "name": "ArrayList.add", + "source": { "kind": "session", "sourceReference": 1001, "name": "ArrayList.class", "origin": "decompiled" }, + "line": 467, + "column": 1 + } + ] + } + } + }, + { + "name": "registers scope and unknown presentation hint", + "value": { + "projectionVersion": 2, + "sessionID": "s-1", + "stopGeneration": 3, + "projection": { + "kind": "scopes", + "frameID": "f-1", + "scopes": [ + { "name": "Locals", "presentationHint": "locals", "variablesReference": 11, "expensive": false }, + { "name": "General Purpose Registers", "presentationHint": "registers", "variablesReference": 12, "namedVariables": 33, "expensive": true }, + { "name": "Vector", "presentationHint": "x-vendor-simd", "variablesReference": 13, "expensive": true } + ] + } + } + }, + { + "name": "capability snapshot without memory extension", + "value": { + "projectionVersion": 2, + "sessionID": "s-1", + "stopGeneration": 0, + "projection": { + "kind": "capabilities", + "revision": 2, + "capabilities": { + "supportsConfigurationDoneRequest": { "adapter": true, "extension": true, "authorized": true, "supported": true }, + "supportsDisassembleRequest": { "adapter": true, "extension": false, "authorized": true, "supported": false }, + "supportsReadMemoryRequest": { "adapter": true, "extension": false, "authorized": true, "supported": false } + } + } + } + } + ], + "reject": [ + { + "name": "address as JSON number loses precision", + "value": { + "projectionVersion": 2, + "sessionID": "s-1", + "stopGeneration": 3, + "projection": { + "kind": "stackTrace", + "threadID": 1, + "frames": [{ "id": "f-1", "name": "memcpy", "instructionPointerReference": 18446744071562078736 }] + } + } + }, + { + "name": "line without source fakes a location", + "value": { + "projectionVersion": 2, + "sessionID": "s-1", + "stopGeneration": 3, + "projection": { + "kind": "stackTrace", + "threadID": 1, + "frames": [{ "id": "f-1", "name": "memcpy", "line": 1 }] + } + } + }, + { + "name": "two source identities at once", + "value": { + "projectionVersion": 2, + "sessionID": "s-2", + "stopGeneration": 1, + "projection": { + "kind": "stackTrace", + "threadID": 1, + "frames": [{ "id": "f-0", "name": "m", "source": { "kind": "workspace", "path": "a.java", "sourceReference": 3 } }] + } + } + }, + { + "name": "workspace path escapes the workspace", + "value": { + "projectionVersion": 2, + "sessionID": "s-2", + "stopGeneration": 1, + "projection": { + "kind": "stackTrace", + "threadID": 1, + "frames": [{ "id": "f-0", "name": "m", "source": { "kind": "workspace", "path": "src/../../etc/passwd" }, "line": 1 }] + } + } + }, + { + "name": "projection without stop generation", + "value": { + "projectionVersion": 2, + "sessionID": "s-1", + "projection": { "kind": "scopes", "frameID": "f-0", "scopes": [] } + } + } + ] +} diff --git a/shared/proposals/debug-adapters-1210/semantic-rejections.draft.json b/shared/proposals/debug-adapters-1210/semantic-rejections.draft.json new file mode 100644 index 0000000000..5969e75842 --- /dev/null +++ b/shared/proposals/debug-adapters-1210/semantic-rejections.draft.json @@ -0,0 +1,204 @@ +{ + "description": "Synthetic rejection specifications; edits use the same JSON Pointer fixture convention as invalid-manifests.json. This is not plugin syntax or an executable Core test suite.", + "baseline": { + "block": { + "debugAdapters": [ + { + "id": "synthetic", + "displayName": "Synthetic contract input", + "launch": { + "process": { + "launch": { + "kind": "packaged", + "path": "bin/synthetic" + } + }, + "transport": "stdio" + } + } + ] + }, + "context": { + "manifestMetadata": { + "pluginAPIVersion": 4, + "requires": { + "debugAdapters": 1, + "serverProcess": 1 + }, + "permissions": [ + "debug.launch" + ] + }, + "hostCapabilities": { + "debugAdapters": [ + 1 + ], + "serverProcess": [ + 1, + 2 + ] + }, + "registration": { + "generation": 1, + "currentGeneration": 1 + }, + "runtimeSelection": { + "generation": 1, + "currentGeneration": 1 + }, + "target": { + "kind": "file" + }, + "packageResources": [] + } + }, + "cases": [ + { + "id": "removed-platform-fields", + "layer": "schema", + "edits": [{"op": "set", "path": "/block/pluginDependencies", "value": {"missing": {"pluginID": "dev.lithe.fixture.absent"}}}], + "expected": {"code": "plugin.manifest.invalid", "field": "pluginDependencies"}, + "executionStatus": "offlineShapeOnly" + }, + { + "id": "removed-resolver", + "layer": "schema", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/resolver", "value": {"backendRef": "absent", "functionID": "debug.resolve"}}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].resolver"}, + "executionStatus": "offlineShapeOnly" + }, + { + "id": "removed-host-integration", + "layer": "schema", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/hostIntegration", "value": {"integrationID": "jdt"}}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].hostIntegration"}, + "executionStatus": "offlineShapeOnly" + }, + { + "id": "removed-workflow", + "layer": "schema", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/launch/steps", "value": [{"id": "same"}, {"id": "same"}]}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].launch.steps"}, + "executionStatus": "offlineShapeOnly" + }, + { + "id": "program-from-process-id", + "layer": "schema", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/configurations", "value": {"launch": {"targetBindings": {"program": "target.processID"}}}}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].configurations.launch.targetBindings.program"}, + "executionStatus": "offlineShapeOnly" + }, + { + "id": "function-for-step-back", + "layer": "schema", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/capabilities", "value": {"stepBack": {"source": "function", "functionID": "synthetic.stepBack"}}}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].capabilities.stepBack"}, + "executionStatus": "offlineShapeOnly" + }, + { + "id": "old-api-debug", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/context/manifestMetadata/pluginAPIVersion", "value": 3}], + "expected": {"code": "plugin.api.unsupported", "field": "requires.debugAdapters"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "missing-host-capability", + "layer": "coreManifest", + "edits": [{"op": "remove", "path": "/context/hostCapabilities/debugAdapters"}], + "expected": {"code": "plugin.api.unsupported", "field": "requires.debugAdapters"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "missing-debug-requirement", + "layer": "coreManifest", + "edits": [{"op": "remove", "path": "/context/manifestMetadata/requires/debugAdapters"}], + "expected": {"code": "plugin.manifest.invalid", "field": "requires.debugAdapters"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "missing-process-requirement", + "layer": "coreManifest", + "edits": [{"op": "remove", "path": "/context/manifestMetadata/requires/serverProcess"}], + "expected": {"code": "plugin.manifest.invalid", "field": "requires.serverProcess"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "unused-process-v2", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/context/manifestMetadata/requires/serverProcess", "value": 2}], + "expected": {"code": "plugin.manifest.invalid", "field": "requires.serverProcess"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "duplicate-adapter-id", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/block/debugAdapters", "value": [{"id": "synthetic", "displayName": "Synthetic contract input", "launch": {"process": {"launch": {"kind": "packaged", "path": "bin/synthetic"}}, "transport": "stdio"}}, {"id": "synthetic", "displayName": "Different title, same identity", "launch": {"process": {"launch": {"kind": "packaged", "path": "bin/synthetic"}}, "transport": "stdio"}}]}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[1].id"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "missing-launch-permission", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/context/manifestMetadata/permissions", "value": []}], + "expected": {"code": "plugin.permission.undeclared", "field": "permissions"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "attach-without-permission", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/requests", "value": ["launch", "attach"]}], + "expected": {"code": "plugin.permission.undeclared", "field": "permissions"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "unsupported-request-config", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/configurations", "value": {"attach": {"arguments": {"port": 30000}}}}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].configurations.attach"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "selected-runtime-v1", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/launch/process", "value": {"launch": {"kind": "runSelectedRuntime", "tool": "python"}}}], + "expected": {"code": "plugin.manifest.invalid", "field": "requires.serverProcess"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "stale-selected-runtime", + "layer": "corePlan", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/launch/process", "value": {"launch": {"kind": "runSelectedRuntime", "tool": "python"}}}, {"op": "set", "path": "/context/manifestMetadata/requires/serverProcess", "value": 2}, {"op": "set", "path": "/context/runtimeSelection/currentGeneration", "value": 2}], + "expected": {"code": "debug.runtime.stale", "field": "runtimeSelection"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "unknown-dap-capability", + "layer": "coreManifest", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/capabilities", "value": {"disabled": ["supportsSyntheticUnknownRequest"]}}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].capabilities.disabled[0]"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "missing-target-path", + "layer": "corePlan", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/configurations", "value": {"launch": {"targetBindings": {"program": "target.path"}}}}], + "expected": {"code": "debug.target.invalid", "field": "target.path"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "escaping-parameters-schema", + "layer": "coreInstall", + "edits": [{"op": "set", "path": "/block/debugAdapters/0/configurations", "value": {"launch": {"parametersSchema": "schemas/parameters.json"}}}, {"op": "set", "path": "/context/packageResources", "value": [{"path": "schemas/parameters.json", "kind": "symlink", "target": "../../../outside/parameters.json"}]}], + "expected": {"code": "plugin.manifest.invalid", "field": "debugAdapters[0].configurations.launch.parametersSchema"}, + "executionStatus": "pendingCoreImplementation" + }, + { + "id": "stale-registration", + "layer": "corePlan", + "edits": [{"op": "set", "path": "/context/registration/currentGeneration", "value": 2}], + "expected": {"code": "debug.provider.stale", "field": "registration"}, + "executionStatus": "pendingCoreImplementation" + } + ] +}