ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

插件通信框架设计:四层桥接与JSON-RPC 2.0实战解析

插件通信框架设计:四层桥接与JSON-RPC 2.0实战解析

1. 项目概述:一个插件通信框架的诞生背景

最近在折腾一个跨进程、跨语言的插件系统,目标是让一个核心服务(比如一个AI代码生成引擎)能够动态加载和执行来自不同语言、不同环境的插件。这听起来像是微服务架构,但粒度更细,对延迟和状态管理的要求也更刁钻。市面上现成的RPC框架很多,gRPC、Thrift、甚至HTTP RESTful API,但它们要么太重,要么对插件这种“轻量级、高动态性”的场景支持不够友好。插件可能随时被加载、卸载,它的生命周期、错误处理、以及长时间运行任务的状态反馈,都需要一套更精细的机制来管理。

这就是codex-plugin-cc这个项目试图解决的问题。它不是一个具体的业务插件,而是一个插件间通信的底层框架。从它的标题“四层桥接 + JSON-RPC 2.0 + JSONL + 后台任务状态机”就能看出,它把通信这件事拆解得非常透彻。这四层桥接,我理解是物理隔离(进程/线程)、协议抽象、数据序列化和状态管理。JSON-RPC 2.0是通信协议,JSONL(JSON Lines)是数据流格式,后台任务状态机则是为了管理那些不能立即返回结果的长时间操作。

这个框架的核心价值在于,它定义了一套标准化的“对话”方式。让核心服务(Host)和插件(Plugin)之间,不仅能进行简单的请求-响应(比如调用一个函数),还能处理流式输出(比如实时日志)和监控长时间任务的状态(比如一个代码重构任务)。这对于构建一个健壮、可扩展的插件化系统至关重要。如果你也在设计类似的系统,或者对底层通信机制感兴趣,那么拆解codex-plugin-cc的设计思路,会是一次非常过瘾的“庖丁解牛”。

2. 四层桥接架构:从物理隔离到逻辑抽象

“四层桥接”是这个框架的骨架,它清晰地划分了通信的层次,每一层解决一个特定维度的问题。这种分层设计的好处是解耦,任何一层都可以被替换或升级,而不影响其他层。下面我们来逐层拆解。

2.1 第一层:传输层桥接(物理与协议)

这一层解决的是最基础的“连接”问题:数据如何从A点移动到B点。codex-plugin-cc在这里必须足够灵活,以支持多种场景。

  • 进程间通信(IPC):这是最典型的场景。插件作为一个独立的子进程运行。框架需要选择一种IPC机制。在Unix/Linux下,匿名管道(Anonymous Pipes)Unix Domain Sockets是高效的选择。Windows下则可能是命名管道(Named Pipes)。这一层的桥接器负责创建这些通道,并管理它们的生命周期(打开、关闭、异常处理)。
  • 标准输入/输出(stdio):这是一种特殊但非常通用的IPC形式。插件进程通过stdin接收请求,通过stdout返回响应,stderr用于输出日志或错误。这种方式兼容性极好,几乎所有编程语言和运行时环境都支持。桥接器在这里需要处理好缓冲、阻塞和非阻塞I/O的问题。
  • 网络套接字(TCP/WebSocket):当插件需要部署在远程机器,或者希望以服务形式存在时,就需要网络层桥接。TCP提供可靠的字节流,WebSocket则在TCP之上提供了全双工、基于消息的通信模式,更适合实时性要求高的场景。

注意:传输层的选择直接影响性能和复杂度。对于本地插件,Unix Domain Sockets或管道通常延迟最低;对于远程或浏览器环境,WebSocket是更自然的选择。框架通常会提供多种桥接器实现,并根据配置动态选择。

这一层的接口非常薄,核心就是send(bytes)receive() -> bytes两个操作,它不关心字节内容的意义,只负责可靠地传输。

2.2 第二层:协议层桥接(JSON-RPC 2.0)

当字节流稳定传输后,我们需要赋予这些字节以结构化的意义。这就是协议层。codex-plugin-cc选择了JSON-RPC 2.0作为应用层协议,这是一个非常明智的选择。

JSON-RPC 2.0是一个轻量级的远程过程调用协议。它规范了请求和响应的格式:

  • 请求{"jsonrpc": "2.0", "method": "methodName", "params": {...}, "id": 1}
  • 成功响应{"jsonrpc": "2.0", "result": {...}, "id": 1}
  • 错误响应{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": 1}

协议层桥接器的职责是:

  1. 编码/解码:将内存中的方法调用请求(方法名、参数)序列化成符合JSON-RPC 2.0规范的JSON字符串,再通过传输层发送;反过来,接收到的字节流反序列化成JSON对象,并解析出结果或错误。
  2. ID管理:为每个请求生成唯一的id,用于匹配请求和响应。这在异步通信中至关重要。
  3. 错误处理:定义和识别标准的JSON-RPC错误(如-32601方法不存在,-32602无效参数)以及自定义的业务错误。

使用JSON-RPC 2.0的好处是标准化和语言无关性。任何支持JSON的语言都可以轻松实现客户端或服务端,极大地降低了插件的开发门槛。这一层桥接,将原始的字节流提升为了结构化的“远程调用”语义。

2.3 第三层:数据流桥接(JSON Lines for Streaming)

传统的JSON-RPC请求-响应模型对于一次性调用是完美的,但对于插件场景,我们经常需要处理流式数据。例如:

  • 插件执行一个耗时命令,需要实时将输出日志传回主机。
  • 一个代码生成插件,边生成边返回代码片段。
  • 大模型推理时的Token流式返回。

如果为每一行日志或每一个Token都发起一次完整的JSON-RPC请求,开销巨大且不合理。这就是引入JSON Lines(JSONL)的原因。JSONL是一种简单的格式,每行都是一个独立的、有效的JSON对象,用换行符\n分隔。

{"type": "log", "content": "开始处理任务...", "level": "info"} {"type": "data_chunk", "chunk": "function hello() {"} {"type": "data_chunk", "chunk": " console.log('world');"} {"type": "data_chunk", "chunk": "}"} {"type": "task_progress", "progress": 100}

数据流桥接器的工作是:

  • 在传输层之上,建立一个独立的“流通道”(可能复用主通道,通过协议字段区分,或使用独立的管道)。
  • 将需要流式传输的数据,按照JSONL格式进行封装和写入。
  • 在接收端,按行读取、解析,并将解析后的JSON对象分发给对应的处理器。

这一层桥接,在请求-响应模型之外,开辟了一条持续的数据流水线,非常适合实时状态更新和流式内容交付。

2.4 第四层:状态机桥接(后台任务生命周期管理)

这是最具业务特色的一层。当插件端接收到一个诸如“重构整个项目”的请求时,它不可能立即返回结果。这个任务会在后台运行,其状态会经历PENDING->RUNNING->SUCCEEDED/FAILED/CANCELLED等变迁。

状态机桥接器就是将这个生命周期模型标准化、并暴露给主机端。它通常包含以下组件:

  1. 任务管理器:在插件端创建、存储和跟踪所有后台任务实例。
  2. 状态定义:一套明确的任务状态枚举和转换规则(哪些状态可以切换到哪些状态)。
  3. 状态查询接口:通过JSON-RPC暴露getTaskStatus(taskId)等方法。
  4. 状态推送:结合数据流桥接(JSONL),主动将任务状态变更(如进度更新)推送给主机。
  5. 控制接口:暴露cancelTask(taskId)等方法,允许主机中断长时间运行的任务。

这一层桥接,将异步、长时间运行的操作封装成了可以被查询、监控和控制的“一等公民”,使得主机能够以同步的方式管理异步任务,用户体验和系统健壮性都得到极大提升。

3. JSON-RPC 2.0 协议在插件通信中的实战细节

选择了JSON-RPC 2.0,就要把它用对、用透。在实际实现中,有许多细节决定了框架的健壮性和易用性。

3.1 方法发现与路由机制

主机如何知道插件提供了哪些方法?一个简单的做法是约定一个特殊方法,比如__listMethods,插件实现它并返回所有可用方法名和签名。更动态的方式是利用反射(如果语言支持),在插件启动时自动注册所有标记了特定注解(如@RpcMethod)的函数到路由表中。

路由器的核心是一个Map<String, MethodInvoker>。当收到一个请求时,根据method字段找到对应的Invoker,这个Invoker负责:

  1. 参数校验与绑定:将JSONparams(可能是数组或对象)转换为目标函数期望的参数列表或结构体。
  2. 异常捕获:调用目标函数,捕获所有异常,并将其转换为标准的JSON-RPC错误响应。业务异常可以映射为自定义错误码,系统异常(如空指针)则映射为内部错误(-32603)。
  3. 结果序列化:将函数返回值序列化为JSON。

3.2 通知(Notification)与请求(Request)的处理

JSON-RPC 2.0区分了“请求”(带id,需要响应)和“通知”(不带id,不需要响应)。在插件框架中,这很有用。

  • 主机对插件的通知:例如,host/configurationChanged。主机告诉插件配置已更新,插件无需回复。这简化了通信。
  • 插件对主机的通知:例如,心跳、或通过JSONL流发送的实时事件。这通常通过独立的流通道实现,但逻辑上也是一种通知。

框架必须正确处理不带id的消息,调用相应方法后,不发送任何响应回传输层。

3.3 错误处理的标准化与扩展

JSON-RPC 2.0定义了一系列标准错误码(-32700 到 -32000)。框架必须实现这些基础错误(如解析错误、无效请求、方法未找到、参数错误、内部错误)。但更重要的是业务错误扩展

插件框架应该定义一套自己的错误码范围(例如从 -32099 到 -32000 的预留范围,或自定义负数范围)。例如:

  • -32001: TASK_NOT_FOUND
  • -32002: TASK_ALREADY_RUNNING
  • -32003: PLUGIN_INIT_FAILURE

错误响应中的error.data字段可以用来携带更详细的上下文信息,比如堆栈跟踪(在调试模式下)、错误的业务数据等。统一的错误处理能让主机端以一致的方式处理所有插件的异常。

3.4 批处理(Batch)的支持

JSON-RPC 2.0支持批处理,即一个请求包含多个方法调用。这对于某些优化场景很有用,比如主机启动时一次性查询插件的元信息、能力列表等。批处理可以节省网络往返开销(在IPC中也有意义)。框架需要支持解析批请求,按顺序或并发执行每个子请求,然后将结果数组作为响应返回。需要注意的是,批处理中某个调用的失败不应影响其他调用的执行。

4. JSON Lines 数据流的设计与实现权衡

JSONL看起来简单,但在工程实现上需要考虑几个关键点。

4.1 流通道的复用与分离

流数据通过什么通道传输?有两种主流设计:

  1. 复用主RPC通道:在JSON-RPC协议上“打补丁”。例如,定义一种特殊的“流开始”请求,其响应头中包含一个stream: true标志,后续的数据包则通过同一连接,以JSONL格式持续发送,直到流结束。这种方式连接管理简单,但需要更复杂的协议解析器来区分普通响应和流数据块。
  2. 独立流通道:在建立主RPC连接后,主机和插件再协商建立一条独立的、专用于流数据的通道(比如另一个Socket连接,或主通道的双工模式)。主RPC请求返回一个streamId和流通道的连接信息。这种方式职责清晰,主通道专用于控制,流通道专用于数据,但增加了连接管理的复杂度。

codex-plugin-cc这类框架可能会采用第二种,因为它更干净,符合“单一职责”原则。主通道保持请求-响应语义,流通道则是单向或双向的“数据 firehose”。

4.2 消息边界与缓冲

JSONL依赖换行符\n作为消息边界。这就要求:

  • 序列化保证:在将JSON对象写入流时,必须确保对象本身不包含未转义的换行符。标准的JSON序列化库会处理这个问题。
  • 读取策略:接收端需要按行读取。但要注意,操作系统对管道或Socket的读写是有缓冲的。不能假设一次read调用就能拿到完整的一行。必须实现一个缓冲读取器,不断读取数据直到遇到换行符,然后将累积的缓冲区内容作为一个完整消息进行解析。
  • 心跳与保活:如果流长时间没有数据,连接可能会被中间设备或操作系统超时关闭。可能需要定期发送空行或特定的心跳消息(如{"type": "heartbeat"})来保持连接活跃。

4.3 流控(Backpressure)考虑

当插件生成数据的速度快于主机消费的速度时,就会产生背压。如果不处理,可能导致内存耗尽。在简单的IPC场景中,由于管道缓冲区有限,操作系统内核会自然地进行流控(写满的管道会阻塞写入进程)。但在网络或更复杂的场景中,需要应用层协议支持。 一种常见的模式是窗口机制。主机在开始接收流时,告知插件一个窗口大小(例如,最多缓存10条消息)。插件发送数据后递减窗口,主机处理完数据后发送一个ACKWINDOW_UPDATE消息来增加窗口,插件才能继续发送。JSONL协议本身不包含这个,需要在上层约定。

5. 后台任务状态机的建模与持久化

状态机是管理复杂性的利器。对于一个后台任务,我们需要一个清晰的模型。

5.1 任务状态定义与转换

一个典型的状态枚举可能是这样的:

class TaskStatus(Enum): PENDING = "pending" # 已创建,等待资源执行 RUNNING = "running" # 正在执行 PAUSED = "paused" # 已暂停(可选) SUCCEEDED = "succeeded" # 执行成功,有结果 FAILED = "failed" # 执行失败,有错误信息 CANCELLED = "cancelled" # 被用户取消

状态转换必须有严格的规则,通常用状态转换图来定义。例如:

  • PENDING->RUNNING(开始执行)
  • RUNNING->SUCCEEDED(正常结束)
  • RUNNING->FAILED(执行出错)
  • RUNNING->CANCELLED(收到取消请求)
  • PAUSED->RUNNING(恢复执行)
  • RUNNING->PAUSED(暂停请求)

任何非法转换(如SUCCEEDED->RUNNING)都应该被状态机拒绝并抛出错误。

5.2 任务上下文与结果存储

每个任务实例除了状态,还需要一个上下文对象来存储:

  • 任务ID:全局唯一标识符。
  • 创建时间/启动时间/结束时间:用于监控和统计。
  • 进度信息:一个0-100的数值,或更结构化的信息(如{current: 5, total: 10})。
  • 输入参数:触发该任务的原始请求参数。
  • 输出结果:任务成功后的产物。这可能是任意大的数据(如生成的代码文件路径、分析报告),所以通常存储为引用(如文件路径、数据库ID)而非直接内嵌。
  • 错误信息:如果失败,详细的错误对象。
  • 元数据:创建者、所属会话等。

这些数据需要被持久化,以防插件进程崩溃重启后能恢复任务状态。简单的实现可以用内存存储加定期快照(序列化到磁盘),复杂的可以用嵌入式数据库(如SQLite)。

5.3 状态同步:拉取与推送结合

主机如何获取任务状态?两种模式结合使用:

  1. 拉取(Polling):主机定期调用插件的getTaskStatus方法。实现简单,但实时性差,且有无效查询的开销。
  2. 推送(Pub/Sub):当任务状态发生变化时,插件通过JSONL流主动向主机发送状态更新事件。这是实时性最好的方式。

最佳实践是以推送为主,拉取为辅。主机启动时订阅插件的状态流。只要连接保持,所有状态变更都能实时收到。当连接中断后重连时,主机可以通过一次拉取来同步所有任务的最新状态。状态更新消息可以设计为:

{"type": "task_updated", "taskId": "task_123", "status": "RUNNING", "progress": 30, "timestamp": 1625097600000}

5.4 任务取消与资源清理

允许取消长时间运行的任务是良好用户体验的关键。取消不是简单的把状态设为CANCELLED,它涉及:

  1. 中断执行:向任务执行的线程或进程发送中断信号。在Python中可能是Thread.interrupt(),在子进程中可能是发送SIGTERM
  2. 资源回收:任务可能打开了文件、网络连接或占用了其他资源,必须在取消时进行清理。这通常要求任务代码在关键点检查取消标志,并实现清理逻辑。
  3. 状态最终化:清理完成后,将任务状态最终置为CANCELLED,并可能存储一个“用户取消”的错误信息。

框架需要提供一个标准的取消接口,并尽可能为常见的任务模式(如执行外部命令、循环处理)提供包装器,这些包装器内置了取消检查点。

6. 实战集成:从插件开发到主机调用的完整链路

理解了原理,我们来看一个从零开始的完整流程,假设我们为一个代码分析工具编写一个插件。

6.1 插件侧实现

首先,插件需要依赖codex-plugin-cc的SDK。以Python为例,插件代码可能长这样:

# my_linter_plugin.py from codex_plugin_sdk import Plugin, rpc_method, background_task class MyLinterPlugin(Plugin): def __init__(self): super().__init__() # 初始化插件自己的资源,如规则库 @rpc_method def get_capabilities(self): """主机发现插件能力的方法""" return {"supportedLanguages": ["python", "javascript"], "version": "1.0"} @rpc_method def lint_file(self, file_path: str, ruleset: str = "default"): """同步的RPC方法:检查单个文件""" # ... 执行代码检查 ... issues = self._run_linter(file_path, ruleset) return {"file": file_path, "issues": issues, "count": len(issues)} @background_task def lint_project(self, project_root: str, ruleset: str): """后台任务:检查整个项目""" task = self.current_task() # 获取当前任务上下文 task.update_progress(0, "开始扫描项目结构...") all_files = self._find_source_files(project_root) total_files = len(all_files) all_issues = [] for i, file_path in enumerate(all_files): # 检查任务是否已被取消 if task.is_cancelled(): task.set_cancelled("用户请求取消") return task.update_progress(int((i / total_files) * 80), f"正在检查 {file_path}") issues = self._run_linter(file_path, ruleset) all_issues.extend(issues) # 通过流实时发送发现的问题(可选) self.send_stream("issue_batch", {"file": file_path, "issues": issues}) task.update_progress(90, "生成最终报告...") report = self._generate_report(all_issues) task.update_progress(100, "完成") # 任务成功,设置结果 task.set_succeeded({"totalFiles": total_files, "totalIssues": len(all_issues), "report": report}) def _run_linter(self, file_path, ruleset): # 实际的代码检查逻辑 # 返回一个issue列表 pass # 插件入口点 if __name__ == "__main__": plugin = MyLinterPlugin() plugin.run() # 这会启动通信循环,监听stdin或Socket

插件开发者只需要用装饰器标记方法,并处理好任务生命周期即可。SDK会处理所有通信、序列化、路由和状态机管理的脏活累活。

6.2 主机侧集成

主机端使用codex-plugin-cc的客户端来与插件交互。

# host_integration.py from codex_plugin_client import PluginClient class MyCodeAnalysisHost: def __init__(self): self.plugin_client = PluginClient(plugin_path="python", args=["my_linter_plugin.py"]) # 或者连接到已运行的插件进程 # self.plugin_client = PluginClient.connect_to_socket("/tmp/plugin_socket") async def analyze_project(self, project_path): # 1. 启动插件(如果未启动) await self.plugin_client.start() # 2. 可选:发现插件能力 capabilities = await self.plugin_client.call("get_capabilities") print(f"插件支持: {capabilities}") # 3. 调用同步方法 single_file_result = await self.plugin_client.call("lint_file", "/path/to/file.py") print(f"单个文件问题: {single_file_result['count']}") # 4. 调用后台任务方法,并监听状态 task_id = await self.plugin_client.call("lint_project", project_path, "strict") # 5. 订阅任务状态流 async for update in self.plugin_client.subscribe_task_updates(task_id): print(f"任务更新: {update['status']}, 进度: {update.get('progress', 0)}%") if update["status"] in ["SUCCEEDED", "FAILED", "CANCELLED"]: break # 6. 获取最终结果 final_status = await self.plugin_client.call("getTaskStatus", task_id) if final_status["status"] == "SUCCEEDED": report = final_status["result"] print(f"分析完成!共检查{report['totalFiles']}个文件,发现{report['totalIssues']}个问题。") else: print(f"任务失败或取消: {final_status.get('error')}") # 7. 可以停止插件 await self.plugin_client.stop()

主机端的API被设计成异步的,这非常适合处理长时间任务和流式数据。subscribe_task_updates方法内部就是通过JSONL流来接收实时状态更新。

6.3 错误处理与超时控制

在实际集成中,必须考虑网络和插件的不稳定性。

  • 连接超时:启动插件或建立连接时设置超时。
  • 调用超时:对于call方法,特别是同步调用,必须设置超时时间。如果插件无响应,主机应能抛出超时异常,并尝试重试或标记插件为不健康。
  • 流中断重连:JSONL流可能因为网络波动中断。客户端需要有心跳检测和自动重连机制,重连后需要重新同步任务状态。
  • 插件崩溃处理:如果插件进程意外退出,主机端的客户端应该能检测到(例如管道断开),清理相关资源,并可能尝试重启插件或上报错误。

7. 性能优化与高级特性探讨

在基础功能之上,一个成熟的框架还需要考虑性能和扩展性。

7.1 传输层性能优化

  • 二进制序列化:JSON虽然易读,但序列化/反序列化开销和传输体积较大。对于性能敏感的插件,可以在JSON-RPC层之下引入可选的二进制编码(如MessagePack、CBOR)。框架可以设计成在握手阶段协商编码格式。
  • 连接池:如果主机需要频繁调用多个插件或同一个插件的不同方法,为每个调用创建新连接(进程)开销很大。可以实现一个轻量级的连接池或保持长连接。
  • 批处理优化:如前所述,利用JSON-RPC的批处理功能,将多个小请求打包发送,减少IPC或网络往返次数。

7.2 插件沙箱与安全隔离

插件系统的一大挑战是安全。不受信任的插件代码不能拥有和主机相同的权限。

  • 进程隔离:这是最基本也是最重要的隔离。插件运行在独立的进程中,其崩溃不会导致主机崩溃。
  • 权限限制:通过操作系统机制(如Linux的Namespaces、Cgroups,Docker容器)或语言运行时机制(如Python的restricted环境,已弃用;或使用seccomp)限制插件的文件系统访问、网络访问和系统调用。
  • 资源配额:限制插件进程的内存、CPU使用量和运行时间,防止恶意或错误插件耗尽资源。
  • 通信验证:所有来自插件的数据在反序列化后都应进行严格的验证,防止注入攻击。

7.3 插件热加载与热升级

一个高可用的系统希望能在不重启主机的情况下更新插件。

  1. 版本协商:插件启动时向主机报告版本。主机可以决定是否兼容。
  2. 优雅退出:主机向插件发送“准备卸载”通知。插件收到后,应完成正在执行的后台任务(或等待其完成),停止接受新任务,清理资源,然后主动退出。主机等待插件退出后,加载新版本。
  3. 状态迁移:对于有状态的任务,热升级更复杂。可能需要将任务状态持久化到共享存储,新插件进程启动后从中恢复。codex-plugin-cc的后台任务状态机如果配合外部存储(如数据库),就能支持这种高级场景。

7.4 监控与可观测性

框架应该为运维提供钩子。

  • 指标暴露:插件SDK可以自动收集并暴露指标,如:RPC方法调用次数、平均耗时、错误率;后台任务数量、各状态任务分布;流数据吞吐量。这些指标可以通过一个特殊的RPC方法(如__metrics)查询,或推送到主机的监控系统。
  • 分布式追踪:为每个跨进程的请求注入追踪ID,可以在日志中串联起主机和插件的完整调用链,对于调试复杂问题至关重要。
  • 结构化日志:插件框架应鼓励或强制使用结构化日志(输出为JSONL格式),方便日志收集系统(如ELK)进行解析和索引。

8. 总结与个人踩坑心得

拆解完codex-plugin-cc这样一个四层桥接的框架,最大的感受是:好的基础设施设计,都是在复杂性中寻找简洁性。它没有发明全新的协议,而是巧妙地组合了JSON-RPC 2.0和JSONL这两个现有、简单、通用的标准,再辅以状态机的抽象,就构建出了一个能力强大且易于理解的插件通信模型。

在实际实现类似系统时,我踩过几个印象深刻的坑:第一个坑是关于错误处理的粒度。早期我把所有异常都笼统地归为“内部错误”。这给调试带来了巨大困难。后来我严格区分了框架错误(如序列化失败)、协议错误(如方法不存在)和业务错误(如“文件未找到”),并为业务错误设计了丰富的错误码和data字段。调试效率立刻提升了一个数量级。

第二个坑是流控的忽视。最初我的JSONL流没有背压控制,一个日志狂打的插件瞬间就能把主机的内存撑爆。后来引入了简单的“确认-继续”机制:主机处理完一批数据后,发送一个ACK,插件才发送下一批。虽然增加了些许延迟,但系统稳定性得到了质的飞跃。

第三个坑是状态机的持久化。一开始状态机只存在内存里,插件一崩溃,所有运行中的任务状态全丢,用户看到的任务莫名其妙消失了。后来引入了WAL(Write-Ahead Logging),每次状态变更都先写日志,再更新内存。插件重启后可以从日志中恢复状态机。这个改动让系统变得真正可靠。

最后,给想要深入实现的开发者一个建议:不要试图一步到位实现所有特性。可以先实现最核心的“传输层+JSON-RPC”的请求-响应,让插件调用跑起来。然后加入JSONL流处理日志。最后再引入后台任务状态机来管理长时间操作。每完成一层,你都会对整体架构有更深的理解,也能更早地获得可用的系统并进行验证。codex-plugin-cc这样的分层设计,本身就为这种渐进式实现提供了完美的蓝图。

返回列表