pi-web Web 界面架构解读:一条消息如何走进 .jsonl 会话文件
pi-web Web 界面架构解读一条消息如何走进 .jsonl 会话文件【免费下载链接】pi-webWeb UI for the pi coding agent项目地址: https://gitcode.com/GitHub_Trending/pi/pi-web你在浏览器里敲下一句指令pi-web 要做的第一件事不是调云端接口而是把这句话写进本地一个 .jsonl 文件之后再从同一个文件把历史读回来。它就是给 pi coding agent 做的 Next.js Web 界面会话管理全部建立在这套本地文件上。先看入口浏览器侧只有两条线app/page.tsx渲染AppShell它管布局和 URL 状态聊天在ChatWindow和ChatInput里。你按下发送请求最终汇到hooks/useAgentSession.ts它把命令交给 lib/agent-client.tsconst res await fetch(/api/agent/${encodeURIComponent(sessionId)}, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(command), }); // ……这段说明浏览器侧没有第二套协议所有 Agent 命令都是打向/api/agent/[id]的 JSON POSTsendAgentCommand只是把重复 13 次的 fetch 块收敛成一行。服务端路由只是一层薄壳新会话走POST /api/agent/newapp/api/agent/new/route.ts。路由只做三件事校验 cwd、起会话、把新 cwd 登记进文件白名单const tempKey __new__${randomUUID()}; const { session, realSessionId } await startRpcSession(tempKey, , cwd, { ...(toolNames ? { toolNames } : {}), // …… }); allowFileRoot(cwd); invalidateSessionListCache();这段说明路由自身不持有状态它只是开门。allowFileRoot值得注意新 cwd 必须当场成为/api/files的可读根目录否则刚建好的会话里文件预览会 403 到缓存过期为止。AgentSession 为什么挂在 globalThis 上startRpcSession在 lib/rpc-manager.ts 里。每个会话对应一个AgentSessionWrapper存在globalThis.__piSessions而不是模块级 Mapconst existing registry.get(sessionId); if (existing?.isAlive()) return { session: existing, realSessionId: sessionId }; const inflight locks.get(sessionId); if (inflight) return inflight;两个设计动机Next.js 热更新会销毁模块级变量globalThis活得够久__piStartLocks让并发请求发消息和开 SSE 同时到达共享同一次启动而不是各起一个进程内会话。空闲 10 分钟后 wrapper 被回收下次请求从会话文件重新加载。会话文件是唯一事实来源pi 把会话存在~/.pi/agent/sessions/编码后的cwd/时间戳_uuid.jsonl每条记录追加一行{type:session,version:3,id:uuid,cwd:/path,parentSession:...} {type:message,id:8hex,parentId:null,message:{role:user,content:...}} {type:message,id:8hex,parentId:8hex,message:{role:assistant,content:[...]}}这段格式说明parentId把消息串成树Edit from here 的分支就是多个条目共享同一个parentId。关键取舍浏览会话完全不创建 AgentSession——lib/session-reader.ts 的listAllSessions()直接读文件。Web 端因此能和 pi 的 TUI 共用同一批会话文件随时打开浏览器续上。结果怎么回到界面SSE 加轮询对账事件走GET /api/agent/[id]/eventsapp/api/agent/[id]/events/route.ts服务端把session.onEvent()转成 SSE 帧推给浏览器。刷新页面时客户端先查一次会话状态isStreaming为真就自动重连 SSE侧边栏再每 2.5 秒轮询/api/agent/running兜底防止后台标签页漏掉结束事件。主通道和轮询互为冗余这是针对浏览器连接不可靠这一现实的设计。按职责给代码库分四层交互层components/AppShell、SessionSidebar、ChatWindow、ChatInput、MessageView、TabBar。输入是状态和事件输出是渲染URL 被当作状态存储收藏的链接能直接定位到某个会话。会话与状态层lib/hooks/rpc-manager.ts管进程内会话session-reader.ts管文件读取useAgentSession.ts管流式渲染和分叉。normalize.ts里的normalizeToolCalls()处理文件里name/arguments与类型里toolName/input的字段错位读文件和收流两条路都要过它。文件与 Git 层/api/files刻意不是通用文件浏览器允许根来自会话 cwd 和项目根由 lib/path-security.ts 的isPathWithinRoots()做唯一安全判定lib/worktree.ts负责 git worktree 的切换把同一仓库的多个工作树在侧边栏里归成一组。扩展与配置层app/api/下auth/、models-config/、skills/、plugins/、subagents/全部直接读写~/.pi/agent/下的配置和 TUI 共享同一份浏览器里改的模型和密钥对终端立刻生效。上手路径第一份该读的文件是 AGENTS.md开头有架构图和文件地图后半部分是踩坑记录比如 fork 之后为什么必须立刻销毁 wrapper、SSE 为什么不能一见agent_end就关。再按lib/rpc-manager.ts到hooks/useAgentSession.ts的顺序读前者是服务端单例后者是客户端状态机。新增一个 API 分三步在app/api/name/route.ts写处理器把业务逻辑挪进lib/单独成文件浏览器侧用 fetch 调用agent 命令走sendAgentCommand。想扩展技能看lib/skills-service.ts和app/api/skills/模型配置看app/api/models-config/。pi-web 的结构可以压成一句话薄路由 进程内单例 与 TUI 共享的 JSONL 会话文件。浏览只读文件发消息才起 AgentSession配置直接落在~/.pi/agent/里——这也是它敢叫 Web UI 而不是 另一个应用 的原因。【免费下载链接】pi-webWeb UI for the pi coding agent项目地址: https://gitcode.com/GitHub_Trending/pi/pi-web创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考