mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
refactor: 0.1.0-beta
This commit is contained in:
@@ -1,356 +0,0 @@
|
||||
---
|
||||
name: coding-guidance
|
||||
description: 本地模式实现指南
|
||||
---
|
||||
当用户在处理本地模式的时候,使用此指南
|
||||
|
||||
先遵守这个约束:
|
||||
|
||||
- 不要修改已安装的 Cursor 客户端代码、bundle 或 app 副本。
|
||||
- 允许且推荐读取、搜索、比对和分析客户端 bundle、日志、协议与仓库代码。
|
||||
- 如果用户提到“临时 patch 客户端做 e2e”,也要改成只读排查:核对实际运行副本、采集证据、对照仓库实现,然后把修复落在本仓库代码或输出明确结论。
|
||||
|
||||
如果问题已经涉及以下任一事项,请同时读取 `../cursor-client-e2e-debugging/SKILL.md`:
|
||||
|
||||
- 需要只读核对已安装的 Cursor 客户端 bundle、日志或运行副本
|
||||
- 需要确认当前到底是哪一个 app 副本在运行
|
||||
- 需要同时排查客户端 bundle 与本仓库 forwarder 的协同问题
|
||||
- 需要对照已安装客户端行为与本仓库实现差异
|
||||
|
||||
本地模式协议需要优先核对这些文件:
|
||||
- proto/agent_v1.proto
|
||||
- proto/aiserver_v1.proto
|
||||
客户端是:/Users/leokun/Library/Application\ Support/Cursor
|
||||
客户端 bundle 是:/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-always-local/dist/main.js
|
||||
|
||||
## 可选抓包调试工具
|
||||
|
||||
仓库提供了独立的 Cursor 协议抓包调试器。开发者在手动排查协议问题时,可以运行:
|
||||
|
||||
```bash
|
||||
go run ./cmd/cursor-proxy-debugger
|
||||
```
|
||||
|
||||
默认代理地址是 `http://127.0.0.1:9090`,调试界面是 `http://127.0.0.1:9091`。该工具可以辅助查看:
|
||||
|
||||
- `agent.v1.AgentService/RunSSE`
|
||||
- `aiserver.v1.BidiService/BidiAppend`
|
||||
- Connect 帧、gzip 压缩内容、Protobuf 解码结果和原始二进制数据
|
||||
- 同一 `request_id` 对应的上下行消息
|
||||
|
||||
开发者启动工具后,需要自行完成以下配置:
|
||||
|
||||
1. 在 Cursor 的代理设置中,将代理修改为工具启动时显示的代理地址,默认是 `http://127.0.0.1:9090`。
|
||||
2. 在 Cursor 的 Network 设置中开启 HTTP/1.1。
|
||||
3. 从 `http://127.0.0.1:9091/api/ca.crt` 下载代理 CA 证书,并确保 Cursor 信任该证书。
|
||||
|
||||
这只是供开发者手动使用的辅助工具,不属于自动化 Debug 流程。不要因为加载此指南就自动启动代理、修改 Cursor 或系统设置、安装证书,或操作 Cursor 发起请求。只有开发者明确表示已经启用抓包时,才把调试界面中的数据作为当前运行证据。调试结束后,提醒开发者恢复原来的 Cursor 代理和 Network 设置。
|
||||
|
||||
## Cursor 客户端格式化快照
|
||||
|
||||
- 如果用户要求提取、格式化、刷新或规范化 Cursor.app 快照流程,使用 `cursor-app-formatted` skill。
|
||||
- 如果本仓库存在 `.cursor-app-formatted/`,排查客户端 bundle 时优先读取这里的格式化副本。
|
||||
- `.cursor-app-formatted/` 是从 `/Applications/Cursor.app/Contents/Resources/app` 只读提取后格式化生成的本地快照;它不应写回、替换或影响已安装的 Cursor.app。
|
||||
- 常用格式化路径:
|
||||
- `.cursor-app-formatted/extensions/cursor-always-local/dist/main.js`
|
||||
- `.cursor-app-formatted/extensions/cursor-agent-exec/dist/main.js`
|
||||
- `.cursor-app-formatted/extensions/cursor-agent-worker/dist/main.js`
|
||||
- `.cursor-app-formatted/out/vs/workbench/workbench.desktop.main.js`
|
||||
- `.cursor-app-formatted/out/vs/workbench/api/node/extensionHostProcess.js`
|
||||
- 如果 `.cursor-app-formatted/` 不存在、明显过期,或需要核对真实安装包 hash,再只读读取 `/Applications/Cursor.app` 原始 bundle。
|
||||
|
||||
## 本仓库已固定的会话承接规则
|
||||
|
||||
- 下一轮真实请求给 LLM 的历史承接,以 `history/<conversationId>/state.json` + `history/<conversationId>/context.json` 为持久化事实源。
|
||||
- `state.json` 保存会话元数据和当前状态,例如 `next_turn_seq`、`next_entry_seq`、`context_version`、`current_todos`、`current_plans`、`latest_request_prefix`、`last_provider_call`。
|
||||
- `context.json.items` 保存 append-only 的语义历史 entries;provider messages 不是主存储事实,而是由 `ProjectPromptReplay()` 从 entries 投影出来。
|
||||
- 模型渠道唯一性不再由 `modelID` 决定;当前规范化渠道 ID 是 `baseURL + modelID + apiKey + displayName + openAIEndpoint` 的短 `SHA-256` hash,resolver 仍兼容 legacy `baseURL + modelID + apiKey + displayName`。
|
||||
- 可 replay 的历史应以 entry 顺序稳定追加,不能把已发送给模型且仍需保留的历史移动到新位置。
|
||||
- 最新态、易变态,例如 active todo、current plan、最新编辑保护和动态 reminder,应优先作为 `state.json` 状态或本轮 latest-only suffix;不要无意持久化成会在后续轮次无限 replay 的历史。
|
||||
- 新一轮 `run_request` 到来时,服务端应通过 `LoadConversation()` 读取 `state.json + context.json`,再由 projector 投影 prompt replay;客户端带回来的 checkpoint/replay 不参与历史承接真相判定。
|
||||
- `summary.json`、`replay.json`、`runtime.json`、`request.json`、`conversation.json`、`entries.jsonl`、`turns/` 和数字 turn 目录都属于旧持久化产物,会被 history maintenance 当 legacy artifact 清理。
|
||||
- 如果发现请求历史与本地状态不一致,优先检查 `context.json.items` 是否缺失、重复、顺序异常,以及 `state.json` 的 `next_entry_seq`、`next_turn_seq`、`context_version`、当前状态字段是否与 entries 派生结果一致;不要再按旧 `summary.json` 路径排查。
|
||||
|
||||
# 已确认结论
|
||||
|
||||
## 1. `AgentServerMessage` 不是统一都要“回复完成”
|
||||
|
||||
要按 `oneof message` 分类看:
|
||||
|
||||
- `exec_server_message`
|
||||
- 这是服务端发给客户端的“执行请求”。
|
||||
- 客户端需要显式回 `ExecClientMessage`。
|
||||
- 流式/异常场景下还会回 `ExecClientControlMessage`,常见是:
|
||||
- `stream_close`
|
||||
- `throw`
|
||||
- `heartbeat`
|
||||
- `interaction_query`
|
||||
- 这是服务端发给客户端的“交互请求”。
|
||||
- 客户端需要显式回 `InteractionResponse`。
|
||||
- `interaction_update`
|
||||
- 这是展示/状态更新消息,通常不需要客户端回包。
|
||||
- `conversation_checkpoint_update`
|
||||
- 这是 checkpoint 同步消息,通常不需要客户端回包。
|
||||
- `kv_server_message`
|
||||
- 这是 KV 同步消息,通常不需要客户端回包。
|
||||
- `exec_server_control_message`
|
||||
- 这是服务端对执行桥的控制消息(例如 abort),客户端要按控制语义处理,但不是通用“完成 ack”。
|
||||
|
||||
## 2. Cursor 客户端没有“收到任意 `ServerMessage` 自动回 ack”的通用层
|
||||
|
||||
在 `cursor-always-local/dist/main.js` 里,`BidiTransport.startYieldingInputsToTheServer` 只会把“客户端主动产出的消息”送到 `BidiAppend`:
|
||||
|
||||
- 它对输入 iterable 做 `p.value.toBinary()` 后 hex 编码,再发 `BidiAppendRequest.data`
|
||||
- 说明只有客户端业务逻辑主动产出的 `AgentClientMessage` 才会上行
|
||||
- 没有发现“收到一个 `AgentServerMessage` 就自动回 completed/ack”的统一机制
|
||||
|
||||
因此客户端是否回包,取决于上层业务逻辑有没有因为某个下行消息而主动构造新的 `AgentClientMessage`。
|
||||
|
||||
## 2.1 更具体的客户端侧结论
|
||||
|
||||
从 `cursor-always-local/dist/main.js` 里能直接确认:
|
||||
|
||||
- `AgentServerMessage` 的下行类型里有:
|
||||
- `interaction_update`
|
||||
- `exec_server_message`
|
||||
- `exec_server_control_message`
|
||||
- `conversation_checkpoint_update`
|
||||
- `interaction_query`
|
||||
- `AgentClientMessage` 的上行类型里有:
|
||||
- `run_request`
|
||||
- `exec_client_message`
|
||||
- `exec_client_control_message`
|
||||
- `interaction_response`
|
||||
|
||||
这意味着本地模式不是“server message -> 通用 ack”模型,而是:
|
||||
|
||||
- `exec_server_message`
|
||||
-> 客户端执行本地工具
|
||||
-> 产出 `exec_client_message`
|
||||
-> 以及可选 `exec_client_control_message`
|
||||
- `interaction_query`
|
||||
-> 客户端展示或处理交互
|
||||
-> 产出 `interaction_response`
|
||||
- 其他下行消息
|
||||
-> 一般只更新 UI / checkpoint /流状态
|
||||
-> 不会自然地产生一个“完成 ack”
|
||||
|
||||
## 2.2 `exec_server_message` 常见的客户端回包形态
|
||||
|
||||
客户端协议模型里已确认这些回包类型:
|
||||
|
||||
- `ExecClientMessage`
|
||||
- 正常结果面
|
||||
- 包括 `read_result` / `write_result` / `grep_result` / `ls_result` / `diagnostics_result` / `mcp_result` / `shell_stream` 等
|
||||
- `ExecClientControlMessage`
|
||||
- 控制面
|
||||
- 包括:
|
||||
- `stream_close`
|
||||
- `throw`
|
||||
- `heartbeat`
|
||||
|
||||
所以调查本地模式 exec 问题时,不要只盯 `ExecClientMessage`:
|
||||
|
||||
- 有些工具只回一次结果面消息
|
||||
- shell 之类的流式工具会混合回:
|
||||
- 多次 `shell_stream`
|
||||
- 以及控制消息(例如 `stream_close` / `heartbeat`)
|
||||
|
||||
## 2.3 `exec_server_message` 的完整回包形态
|
||||
|
||||
事实依据:
|
||||
|
||||
- `proto/agent_v1.proto`
|
||||
- `ExecServerMessage.oneof message`
|
||||
- `ExecClientMessage.oneof message`
|
||||
- `ExecClientControlMessage.oneof message`
|
||||
- `cursor-always-local/dist/main.js`
|
||||
- bundle 内含同名 proto 模型
|
||||
- `BidiTransport.startYieldingInputsToTheServer` 说明客户端上行消息来自业务逻辑主动构造,不存在通用自动 ack
|
||||
|
||||
### 结果面回包:`ExecServerMessage` -> `ExecClientMessage`
|
||||
|
||||
`ExecServerMessage` 的 `message` 分支与 `ExecClientMessage` 的 `message` 分支是一一对应的:
|
||||
|
||||
- `shell_args`
|
||||
-> `shell_result`
|
||||
- `write_args`
|
||||
-> `write_result`
|
||||
- `delete_args`
|
||||
-> `delete_result`
|
||||
- `grep_args`
|
||||
-> `grep_result`
|
||||
- `read_args`
|
||||
-> `read_result`
|
||||
- `ls_args`
|
||||
-> `ls_result`
|
||||
- `diagnostics_args`
|
||||
-> `diagnostics_result`
|
||||
- `request_context_args`
|
||||
-> `request_context_result`
|
||||
- `mcp_args`
|
||||
-> `mcp_result`
|
||||
- `shell_stream_args`
|
||||
-> `shell_stream`
|
||||
- `background_shell_spawn_args`
|
||||
-> `background_shell_spawn_result`
|
||||
- `list_mcp_resources_exec_args`
|
||||
-> `list_mcp_resources_exec_result`
|
||||
- `read_mcp_resource_exec_args`
|
||||
-> `read_mcp_resource_exec_result`
|
||||
- `fetch_args`
|
||||
-> `fetch_result`
|
||||
- `record_screen_args`
|
||||
-> `record_screen_result`
|
||||
- `computer_use_args`
|
||||
-> `computer_use_result`
|
||||
- `write_shell_stdin_args`
|
||||
-> `write_shell_stdin_result`
|
||||
- `execute_hook_args`
|
||||
-> `execute_hook_result`
|
||||
- `subagent_args`
|
||||
-> `subagent_result`
|
||||
|
||||
所有这些结果面回包都带:
|
||||
|
||||
- `id`
|
||||
- `exec_id`
|
||||
|
||||
服务端匹配时通常优先用:
|
||||
|
||||
1. `exec_id`
|
||||
2. `id`
|
||||
|
||||
### 控制面回包:`ExecServerMessage` -> `ExecClientControlMessage`
|
||||
|
||||
除了结果面回包外,客户端还可能回控制面消息:
|
||||
|
||||
- `stream_close`
|
||||
- 表示当前 exec 流已关闭
|
||||
- 只有 `id`
|
||||
- `throw`
|
||||
- 表示执行异常
|
||||
- 只有 `id` + `error` + 可选 `stack_trace`
|
||||
- `heartbeat`
|
||||
- 表示执行过程中的心跳
|
||||
- 只有 `id`
|
||||
|
||||
### 关键理解
|
||||
|
||||
- `ExecClientControlMessage` 不是某个单独 `ExecServerMessage` 分支的“专属结果类型”
|
||||
- 它是跨 exec 通用的控制面回包
|
||||
- 因此调查时必须同时看两类上行:
|
||||
- `ExecClientMessage`
|
||||
- `ExecClientControlMessage`
|
||||
|
||||
### 调查规则
|
||||
|
||||
对于任意 `exec_server_message`,至少要确认以下之一是否发生:
|
||||
|
||||
- 收到对应的 `ExecClientMessage`
|
||||
- 或收到 `ExecClientControlMessage.throw`
|
||||
- 对流式 exec,还要看:
|
||||
- 是否有多次增量 `ExecClientMessage`
|
||||
- 是否最终有 `stream_close`
|
||||
|
||||
如果只看到 started / pending,没有任何结果面或控制面回包,服务端 pending 大概率不会收口。
|
||||
|
||||
排查时优先搜索这些关键字:
|
||||
|
||||
## 3. forwarder 状态机实现规则
|
||||
|
||||
在本仓库修本地模式 forwarder 时,默认遵守下面这些稳定约束,避免再次引入“工具晚到污染当前轮”或“同一 request 在 `[DONE]` 后又续跑一轮”的问题。
|
||||
|
||||
### 3.1 resume 必须按 provider pass 隔离
|
||||
|
||||
- `request_id` 不是 provider 调用代次;同一个 request 可以合法包含多次 provider pass。
|
||||
- `scheduleProviderResume` 不能只依赖 request 级布尔态(例如单个 `ResumePending`)。
|
||||
- resume 请求必须带来源 pass,至少要能区分:
|
||||
- 当前 pass 的结果触发的合法续跑
|
||||
- 上一轮工具终态晚到造成的陈旧 resume
|
||||
- `driveProvider` 开始与结束时都要显式清理上一轮的 resume 状态,不能让旧状态跨 pass 残留。
|
||||
|
||||
### 3.2 工具晚到是常态,只能影响所属 pass
|
||||
|
||||
- `ExecClientMessage` / `ExecClientControlMessage` 晚于 provider `[DONE]` 到达是正常现象。
|
||||
- 晚到结果只能驱动其所属 pass 的 checkpoint / history / resume 判定,不能影响后续 pass。
|
||||
- 非流式 exec 的 `stream_close` synthetic recovery 也必须沿用原工具的来源 pass,不能按 request 级全局状态续跑。
|
||||
|
||||
### 3.3 pending exec 必须严格按 id 匹配
|
||||
|
||||
- `selectPendingExec` / `selectPendingExecByControl` 只允许按:
|
||||
- `exec_id`
|
||||
- `message_id`
|
||||
进行匹配。
|
||||
- 不允许再用“当前只有一个 pending,就直接返回它”的兜底逻辑。
|
||||
- 迟到的 result / `stream_close` / `throw` 如果 pending 已不存在:
|
||||
- 优先看 `RecentCompletedExecs` 做幂等忽略
|
||||
- 不要把它重新落到当前轮的 pending 上
|
||||
|
||||
### 3.4 看到这些现象时,优先怀疑 stale resume / stale exec
|
||||
|
||||
如果出现下面任一现象,先查 forwarder 状态机,不要先怪客户端:
|
||||
|
||||
- 同一个 `request_id` 在 `[DONE]` 后又出现新的 `model_call_id`
|
||||
- `turns/<n+1>/request.json` 与 `turns/<n>/request.json` messages 几乎完全相同
|
||||
- 上一轮工具 `grepResult/readResult/...` 晚于上一轮 `[DONE]`
|
||||
- 晚到的 `stream_close` 恰好跨到下一轮 provider 已经启动之后
|
||||
|
||||
优先核对:
|
||||
|
||||
- `ProviderPassCount`
|
||||
- resume 请求的来源 pass
|
||||
- `PendingExec.ProviderPass`
|
||||
- `selectPendingExec` 是否存在跨轮误匹配
|
||||
|
||||
- `startYieldingInputsToTheServer`
|
||||
- `bidiAppend({requestId:A,appendSeqno`
|
||||
- `ExecServerMessage`
|
||||
- `ExecClientMessage`
|
||||
- `ExecClientControlMessage`
|
||||
- `InteractionQuery`
|
||||
- `InteractionResponse`
|
||||
|
||||
## 3. 对本地模式最重要的协议理解
|
||||
|
||||
- `exec_server_message` / `interaction_query` 属于“请求型下行消息”
|
||||
- 如果客户端不回对应结果,服务端 pending 不会收口
|
||||
- 后续重连后可能出现 “No tool output found for function call ...” 这类 provider 400
|
||||
- `interaction_update` / `conversation_checkpoint_update` 属于“通知型下行消息”
|
||||
- 它们用于 UI 展示、同一 backend 进程内的 live checkpoint 同步、状态同步
|
||||
- 一般不要求客户端再回一个“完成”消息
|
||||
|
||||
## 4. 调查本地模式时的优先顺序
|
||||
|
||||
1. 先确认收到的 `AgentServerMessage` 是哪一类
|
||||
2. 如果是 `exec_server_message`
|
||||
- 查客户端是否回了 `ExecClientMessage`
|
||||
- 查是否只回了 `stream_close` 但没有真正结果
|
||||
- 查 `exec_id` / `id` 是否匹配
|
||||
3. 如果是 `interaction_query`
|
||||
- 查客户端是否回了 `InteractionResponse`
|
||||
4. 如果是 `conversation_checkpoint_update`
|
||||
- 重点查里面的 `pending_tool_calls` / `root_prompt_messages_json` / `turns`
|
||||
- 不要误以为它本身需要回 ack
|
||||
|
||||
## 5. 对服务端实现的直接要求
|
||||
|
||||
- 服务端必须区分“请求型下行”和“通知型下行”
|
||||
- 服务端不能把 `ServerMessage` 统一建模成“发出去就等一个完成 ack”
|
||||
- 对请求型消息,必须在本地状态机里维护 pending:
|
||||
- `PendingExec`
|
||||
- `PendingInteraction`
|
||||
- 同一 backend 进程内的 `RunSSE` 重连,要优先看 checkpoint / `pending_tool_calls` 里的 live pending
|
||||
- backend 重启后,不要把 checkpoint 当持久恢复点;跨轮承接与持久恢复只看 `history/<conversationId>/state.json` + `history/<conversationId>/context.json`
|
||||
|
||||
### 5.1 checkpoint 投影必须幂等且只有一个事实源
|
||||
|
||||
- 把 checkpoint 当作 `state.json + context.json` 的纯投影,不要把它写成第二套语义历史。
|
||||
- 不要创建或维护 `checkpoint.json`、checkpoint history、独立 checkpoint entry 序列等持久化事实源。
|
||||
- 允许在当前 stream 内存中保留 latest checkpoint 供 retry/resume 使用;进程重启后必须能从唯一事实源重新投影。
|
||||
- 对同一份 semantic history 重复投影时,要求 state、turn 顺序、blob ID 和 blob 内容在语义上完全一致;投影函数不得修改输入 history。
|
||||
- 把重复发送视为同一快照的幂等覆盖,不要追加一条新的会话历史;内容寻址 blob 的重复写入必须可安全忽略。
|
||||
- 将 `turns` 投影为 UI 可恢复的完整结构,保留所有需要展示的 `ThinkingMessage`、`ToolCall` 和工具结果;不要为了模型 prompt 过滤而删除 UI step。
|
||||
- 将 `root_prompt_messages_json` 单独投影为模型 replay;只在这条投影上应用 provider/context 过滤,不能反向改变 `turns`。
|
||||
- 将工具完成结果合并回同一 `ToolCall`,保留开始态的 `args`、调用 ID 和开始时间,再补齐 `result` 与完成时间;不要制造协议不存在的独立 `ToolResult` step。
|
||||
- 用 TDD 覆盖至少这些性质:重复投影相等、投影不修改 history、开始态字段在结果合并后仍存在、UI turns 保留思考/工具内容而模型 replay 仍遵守独立过滤规则。
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "本地模式实现指南"
|
||||
short_description: "当用户在尝试解决本地模式问题时,使用此技能"
|
||||
default_prompt: "使用 $coding-guidance 来解决本地模式问题。"
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
name: cursor-app-formatted
|
||||
description: Use when extracting, formatting, refreshing, or investigating a read-only formatted snapshot of the installed Cursor.app bundle under .cursor-app-formatted; includes git-ignore rules, snapshot generation workflow, and the rule to inspect formatted code without patching either the snapshot code or the installed app.
|
||||
---
|
||||
|
||||
# Cursor App Formatted Snapshot
|
||||
|
||||
Use this skill whenever a task involves reading, searching, formatting, refreshing, or relying on a formatted copy of the installed Cursor client bundle.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Never modify `/Applications/Cursor.app`, any installed app bundle, signatures, or app copies.
|
||||
- Never patch bundled code under `.cursor-app-formatted/` as a fix target. It is an ignored investigation snapshot only.
|
||||
- If `.cursor-app-formatted/` is stale or wrong, regenerate it from the installed app instead of hand-editing its code.
|
||||
- Fixes should land in this repository's real source code, scripts, or docs, not in formatted snapshot code.
|
||||
- Keep `.cursor-app-formatted/` git-ignored. Do not stage or commit generated snapshot contents.
|
||||
|
||||
## Preferred Investigation Flow
|
||||
|
||||
1. If `.cursor-app-formatted/` exists, search and read that formatted snapshot first.
|
||||
2. Use `/Applications/Cursor.app` only for read-only authenticity checks, hash comparison, or when the snapshot is missing or stale.
|
||||
3. Prefer stable formatted paths for line references and control-flow reading:
|
||||
- `.cursor-app-formatted/extensions/cursor-always-local/dist/main.js`
|
||||
- `.cursor-app-formatted/extensions/cursor-agent-exec/dist/main.js`
|
||||
- `.cursor-app-formatted/extensions/cursor-agent-worker/dist/main.js`
|
||||
- `.cursor-app-formatted/out/vs/workbench/workbench.desktop.main.js`
|
||||
- `.cursor-app-formatted/out/vs/workbench/api/node/extensionHostProcess.js`
|
||||
4. When investigating installed-client behavior, compare formatted findings back to original source hashes or original bundle content only as needed.
|
||||
|
||||
## Git Ignore Rule
|
||||
|
||||
Ensure `.gitignore` contains:
|
||||
|
||||
```gitignore
|
||||
.cursor-app-formatted/
|
||||
```
|
||||
|
||||
If the entry is missing and the user asked to create or refresh the snapshot, add it before generating the snapshot.
|
||||
|
||||
## Snapshot Generation Workflow
|
||||
|
||||
Run from the repository root. This workflow copies only from the installed app into the ignored snapshot, then formats the copy.
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
SNAPSHOT=.cursor-app-formatted
|
||||
SOURCE=/Applications/Cursor.app/Contents/Resources/app
|
||||
|
||||
rm -rf "$SNAPSHOT"
|
||||
mkdir -p "$SNAPSHOT"
|
||||
|
||||
/usr/bin/ditto "$SOURCE/extensions" "$SNAPSHOT/extensions"
|
||||
mkdir -p "$SNAPSHOT/out/vs/workbench/api/node"
|
||||
/usr/bin/ditto "$SOURCE/out/vs/workbench/workbench.desktop.main.js" "$SNAPSHOT/out/vs/workbench/workbench.desktop.main.js"
|
||||
/usr/bin/ditto "$SOURCE/out/vs/workbench/api/node/extensionHostProcess.js" "$SNAPSHOT/out/vs/workbench/api/node/extensionHostProcess.js"
|
||||
|
||||
/usr/bin/shasum -a 256 \
|
||||
"$SOURCE/out/vs/workbench/workbench.desktop.main.js" \
|
||||
"$SOURCE/out/vs/workbench/api/node/extensionHostProcess.js" \
|
||||
> "$SNAPSHOT/source-sha256.txt"
|
||||
|
||||
/usr/bin/find "$SOURCE/extensions" -type f \( -name '*.js' -o -name '*.json' -o -name '*.css' \) -print \
|
||||
| /usr/bin/sed "s#^$SOURCE/##" \
|
||||
| while IFS= read -r rel; do
|
||||
/usr/bin/shasum -a 256 "$SOURCE/$rel"
|
||||
done >> "$SNAPSHOT/source-sha256.txt"
|
||||
```
|
||||
|
||||
Format large JS bundles with `js-beautify`; Prettier can OOM on very large Cursor bundles and also skips ignored paths unless forced.
|
||||
|
||||
```bash
|
||||
find .cursor-app-formatted -type f \( -name '*.js' -o -name '*.mjs' -o -name '*.cjs' \) -size +1M -print \
|
||||
| while IFS= read -r file; do
|
||||
npx --yes js-beautify --type js --indent-size 2 --end-with-newline --replace --quiet "$file"
|
||||
done
|
||||
|
||||
find .cursor-app-formatted -type f \( -name '*.js' -o -name '*.mjs' -o -name '*.cjs' \) ! -size +1M -print \
|
||||
| while IFS= read -r file; do
|
||||
npx --yes js-beautify --type js --indent-size 2 --end-with-newline --replace --quiet "$file"
|
||||
done
|
||||
|
||||
EMPTY_IGNORE="$(mktemp)"
|
||||
trap 'rm -f "$EMPTY_IGNORE"' EXIT
|
||||
find .cursor-app-formatted -type f \( -name '*.json' -o -name '*.css' \) -print0 \
|
||||
| xargs -0 -n 25 npx --yes prettier --ignore-path "$EMPTY_IGNORE" --with-node-modules --write --log-level warn
|
||||
```
|
||||
|
||||
Optionally add a small `.cursor-app-formatted/README.md` describing the source path, observed Cursor version, and that the snapshot is read-only.
|
||||
|
||||
## Validation
|
||||
|
||||
After generation, verify the snapshot is ignored and key files are readable:
|
||||
|
||||
```bash
|
||||
git status --short --ignored | rg '\.cursor-app-formatted'
|
||||
wc -l \
|
||||
.cursor-app-formatted/out/vs/workbench/workbench.desktop.main.js \
|
||||
.cursor-app-formatted/extensions/cursor-always-local/dist/main.js \
|
||||
.cursor-app-formatted/extensions/cursor-agent-exec/dist/main.js
|
||||
```
|
||||
|
||||
Useful investigation check:
|
||||
|
||||
```bash
|
||||
rg -n 'localMode|runLocalAgent|localProvider|BidiTransport|startYieldingInputsToTheServer' .cursor-app-formatted
|
||||
```
|
||||
@@ -1,97 +0,0 @@
|
||||
---
|
||||
name: cursor-client-e2e-debugging
|
||||
description: Use when debugging Cursor client agent/local-mode/tool/backend-store/provider-replay failures in this repo, especially after the state/context history-store refactor, when triaging installed app bundles read-only, correlating installed-client behavior with repo code, mapping a user-provided id to conversation/request/model-call evidence, replaying provider requests from debug logs, or locating the current client/backend/protocol/log files quickly.
|
||||
---
|
||||
|
||||
当用户反馈 Cursor agent、本地模式、工具调用、协议桥接、客户端 bundle 行为异常,或需要只读核对已安装客户端与仓库实现/日志差异时,使用此技能。
|
||||
|
||||
当用户只给一个 UUID / id,希望反查它是 `conversationId`、`requestId`、`modelCallId`、`toolCallId` 还是其它运行期 id,并继续定位对应的会话 history、provider 调用状态或协议日志时,也使用此技能。
|
||||
|
||||
当用户遇到 provider 400/参数错误、SSE `event: error`、需要从 `debug/provider.jsonl` 抽取最终 provider body 并用 curl 独立复现时,也使用此技能。
|
||||
|
||||
## 首要约束
|
||||
|
||||
- 不要修改已安装的 Cursor 客户端代码、bundle、签名或 app 副本。
|
||||
- 允许且推荐读取、搜索、比对和分析客户端 bundle、日志、协议事件与本仓库实现。
|
||||
- 如果本仓库存在 `.cursor-app-formatted/`,优先读取这个格式化快照来搜索和引用客户端 bundle;只有在快照缺失、过期或需要 hash/真实性核对时,才只读读取 `/Applications/Cursor.app`。
|
||||
- 如果用户要求提取、格式化、刷新或规范化 Cursor.app 快照流程,使用 `cursor-app-formatted` skill;调查时可以读格式化代码,但不要 patch 格式化快照里的 bundle 代码。
|
||||
- 如果用户要求 patch 客户端做 e2e,要改成只读证据采集与差异定位,不执行客户端修改。
|
||||
- 当前本仓库已经重构为 `state.json + context.json` history-store;不要沿用旧 `data.sqlite`、`conversation.json`、`turns/<n>/request.json|sse.jsonl|summary.json` 排查路径。
|
||||
|
||||
## 先做路由判断
|
||||
|
||||
- `history + logs` 反查层
|
||||
- 现象:用户发来一个 id,要判断它是 `conversationId`、`requestId`、`modelCallId`、`toolCallId`;需要从 `history/<conversationId>/state.json` 与 `history/<conversationId>/context.json` 追运行状态和语义历史。
|
||||
- 先读 [references/backend-store-log-tracing.md](references/backend-store-log-tracing.md)
|
||||
- `provider replay / debug` 层
|
||||
- 现象:provider 返回 400/参数错误、SSE `event: error`、需要验证最终出站 provider body 是否能被独立 curl 复现。
|
||||
- 先读 [references/provider-replay-debugging.md](references/provider-replay-debugging.md),必要时使用 [scripts/provider-replay.sh](scripts/provider-replay.sh)
|
||||
- `cursor-agent` 层
|
||||
- 现象:`CursorAgentProvider`、`ClaudeSDKClient`、`AnthropicProxy`、`registerAgentProvider`、`InteractionUpdate` 映射、模型桥接异常。
|
||||
- 先读 [references/file-map.md](references/file-map.md) 和 [references/search-patterns.md](references/search-patterns.md)
|
||||
- `cursor-always-local` / 本地模式协议层
|
||||
- 现象:`BidiAppend`、`RunSSE`、`AgentServerMessage`、`ExecClientMessage`、`InteractionResponse`、live checkpoint / pending 收口异常。
|
||||
- 先读 [references/file-map.md](references/file-map.md) 和 [references/search-patterns.md](references/search-patterns.md)
|
||||
- 客户端 bundle 只读定位层
|
||||
- 现象:需要核对已安装 app bundle、确认实际运行副本、只读验证行为是否命中,并判断差异来自客户端还是本仓库。
|
||||
- 先读 [references/installed-client-readonly-validation.md](references/installed-client-readonly-validation.md)
|
||||
|
||||
如果问题同时涉及多层,优先从最靠近故障表象的一层开始,不要一开始就同时追所有链路。
|
||||
|
||||
## 当前工作流
|
||||
|
||||
1. 如果用户给了一个 id,先用 `history/` 目录、`context.json.items`、`state.json` 和 `logs/app.log` 判断它属于哪类 id;不要假设它一定是 `requestId`。
|
||||
2. 一旦拿到 `conversationId`,同时看两份事实源:
|
||||
- `history/<conversationId>/state.json`:会话元数据和当前状态,例如 loop、token、current todo/plan、`latest_request_prefix`、`last_provider_call`。
|
||||
- `history/<conversationId>/context.json`:append-only 的语义历史 entries;prompt replay 由 `ProjectPromptReplay()` 从这里投影。
|
||||
3. 不要去找旧 provider 调用工件:当前 `RecordLLMRequest` 不再落 `request.json`,`AppendLLMResponseChunk` 是 no-op,`RecordLLMSummary` 只补齐内存态并更新 `state.latest_request_prefix` / usage。
|
||||
4. 再确认故障主要落在 `cursor-agent`、`cursor-always-local`,还是本仓库 `internal/backend` 的协议兼容层。
|
||||
5. 用 references 里的固定搜索词快速找到入口函数、协议消息和桥接点。
|
||||
6. 如果 provider 返回 400/参数错误、SSE `event: error`,或需要验证最终出站 provider body:
|
||||
- 先读 [references/provider-replay-debugging.md](references/provider-replay-debugging.md)。
|
||||
- 通过 id 反查拿到 `conversationId`、`requestId`、`modelCallId`,再定位 `history/<conversationId>/debug/provider.jsonl`。
|
||||
- 必要时运行 [scripts/provider-replay.sh](scripts/provider-replay.sh),只保存 replay 产物,不把 API key 或完整 request body 写进技能/回复。
|
||||
7. 如果用户要核对 prefix cache / cache hit:
|
||||
- 优先运行 `go run ./scripts/historymetrics [conversationId|path]`
|
||||
- 它读取当前 `history/<conversationId>/state.json` 与 `history/<conversationId>/context.json`,并结合 `history/usage.json` 统计。
|
||||
- 关注 `cache_read_tokens / prompt_tokens_total`,并检查 `context.json.items` 是否缺失、重复或顺序异常。
|
||||
8. 如果需要对照已安装 app 与仓库行为:
|
||||
- 只做只读核对与证据采集,不修改客户端 bundle / app 副本 / 签名。
|
||||
- 优先用 `.cursor-app-formatted/` 中的格式化副本定位符号、行号和控制流;再按需只读核对 `/Applications/Cursor.app` 原始文件 hash 或运行副本。
|
||||
- 先确认实际运行的 app 副本和目标 bundle 路径。
|
||||
- 再读取 bundle 内容、日志、端口与 history 状态,并与本仓库实现对照。
|
||||
9. 如果证据显示问题更像是客户端 bundle 行为差异:
|
||||
- 记录具体文件、符号、日志和协议证据链。
|
||||
- 继续判断本仓库是否可以兼容、绕过,或直接输出分析结论。
|
||||
- 不要对已安装 Cursor 客户端做 patch、重签名、替换文件或写入式验证。
|
||||
|
||||
## 约束
|
||||
|
||||
- 不要修改已安装的 Cursor 客户端代码、bundle、签名或 app 副本。
|
||||
- 不要默认复刻整套 Cursor backend;先确认是不是只需要改模型桥接层。
|
||||
- 不要先假设用户给的是 `requestId`;必须同时考虑 `conversationId`、`requestId`、`modelCallId`、`toolCallId`。
|
||||
- 不要把 `history/<conversationId>/state.json` 和 `history/<conversationId>/context.json` 混为一谈:前者是元数据与当前状态,后者是 replayable 语义历史。
|
||||
- 不要再依赖 `agent_request_runs`、`agent_conversations`、`agent_history_entries`、`protocol_traces`、`data.sqlite`;当前实现已经不支持 DB-backed store / trace debug UI。
|
||||
- 不要把当前排查进度、临时结论、一次性的 request_id / 端口 / token 写进技能。
|
||||
- 技能里只保留稳定流程、固定入口、可复用搜索词和只读验证规则。
|
||||
|
||||
## 模型渠道规则
|
||||
|
||||
- 模型渠道唯一性不再由 `modelID` 决定。
|
||||
- 当前规范化渠道 ID 是 `baseURL + modelID + apiKey + displayName + openAIEndpoint` 的短 `SHA-256` hash(前 16 个十六进制字符)。
|
||||
- resolver 仍兼容 legacy 渠道 ID:`baseURL + modelID + apiKey + displayName`。
|
||||
- `modelID` 只表示 provider model;排查选择器、默认模型和命中渠道时,要优先看渠道 ID 和 `openAIEndpoint`。
|
||||
|
||||
## 参考加载规则
|
||||
|
||||
- `history + logs` 路径、id 反查、state/context 生成链路:读 [references/backend-store-log-tracing.md](references/backend-store-log-tracing.md)
|
||||
- provider 400/参数错误、SSE `event: error`、最终出站 provider body curl 重放:读 [references/provider-replay-debugging.md](references/provider-replay-debugging.md)
|
||||
- 文件地图:读 [references/file-map.md](references/file-map.md)
|
||||
- 搜索词与判断树:读 [references/search-patterns.md](references/search-patterns.md)
|
||||
- 已安装客户端的只读核对、进程确认、行为验证:读 [references/installed-client-readonly-validation.md](references/installed-client-readonly-validation.md)
|
||||
|
||||
## 自带脚本
|
||||
|
||||
- 统计 prefix cache / cache hit:运行 `go run ./scripts/historymetrics [conversationId|path]`
|
||||
- 兼容壳脚本:运行 [scripts/cache-hit-rate.mjs](scripts/cache-hit-rate.mjs)
|
||||
- provider curl 重放:运行 [scripts/provider-replay.sh](scripts/provider-replay.sh),必填 `REQUEST_LOG`、`REQUEST_ID`、`MODEL_CALL_ID`
|
||||
@@ -1,177 +0,0 @@
|
||||
# backend/store 日志反查
|
||||
|
||||
当用户只给一个 id,或明确让你从本地记录里反查一次请求、会话、模型调用、工具调用或 provider 错误时,优先读这份参考。
|
||||
|
||||
## 当前固定路径
|
||||
|
||||
当前用户机器上的固定助手根目录:
|
||||
|
||||
- `~/.cursor-local-assistant-v2`
|
||||
|
||||
当前最关键的是三类内容:
|
||||
|
||||
- `history/<conversationId>/state.json`
|
||||
- 会话元数据与当前状态。
|
||||
- 重点字段:`request_id` `conversation_id`、`root_conversation_id`、`parent_conversation_id`、`parent_tool_call_id`、`mode`、`current_loop_status`、`current_request_id`、`current_turn_seq`、`context_version`、`next_turn_seq`、`next_entry_seq`、`latest_request_prefix`、`last_provider_call`、`current_todos`、`current_plans`、token / compaction 字段。
|
||||
- `history/<conversationId>/context.json`
|
||||
- append-only 语义历史。
|
||||
- 重点字段:`version`、`items[]`。
|
||||
- `items[]` 中每个 entry 通常有 `seq`、`turn_seq`、`request_id`、`role`、`kind`、`tool_call_id`、`parent_tool_call_id`、`payload`、`created_at`。
|
||||
- `history/usage.json`
|
||||
- provider call 与 turn usage 聚合。
|
||||
- 重点字段:`totals`、`daily`、`recent_events`、`event_index`。
|
||||
|
||||
`logs/app.log` 是运行日志;它只用于补充运行时证据,不是会话事实源。
|
||||
|
||||
当前实现不再支持:
|
||||
|
||||
- DB-backed store / searchable conversation memory
|
||||
- HTTP/protocol trace debug UI
|
||||
- `data/data.sqlite` / `protocol_traces`
|
||||
- `history/<conversationId>/conversation.json`
|
||||
- `history/<conversationId>/turns/<n>/request.json|sse.jsonl|summary.json`
|
||||
- 根目录或会话目录下的旧 `latest.json`、`summary.json`、`replay.json`、`runtime.json`、`request.json`、`recovery.json`、`entries.jsonl`、数字 turn 目录
|
||||
|
||||
这些旧产物会被 `internal/backend/forwarder/history_maintenance.go` 清理。不要把它们当成当前事实源。
|
||||
|
||||
## 用户发来一个 id 时的固定步骤
|
||||
|
||||
### 1. 先判断 id 类型
|
||||
|
||||
不要先假设它是 `requestId`。按这个顺序缩小范围:
|
||||
|
||||
1. 是否是 `conversationId`
|
||||
- 检查 `history/<id>/state.json` 和 `history/<id>/context.json` 是否存在。
|
||||
2. 是否是 `requestId`
|
||||
- 在 `history/*/state.json` 中查 `current_request_id`、`latest_request_prefix.request_id`、`last_provider_call.request_id`。
|
||||
- 在 `history/*/context.json` 的 `items[].request_id` 中查。
|
||||
- 在 `logs/app.log` 中查。
|
||||
3. 是否是 `modelCallId`
|
||||
- 在 `state.json` 中查 `latest_request_prefix.model_call_id`、`last_provider_call.model_call_id`。
|
||||
- 在 `context.json.items[].payload` 中查 `model_call_id`。
|
||||
- 在 `logs/app.log` 中查 `model_call_id=<id>`。
|
||||
4. 是否是 `toolCallId` / `exec_id`
|
||||
- 在 `context.json.items[].tool_call_id`、`items[].payload` 中查。
|
||||
- 在协议/工具相关日志中查。
|
||||
|
||||
可以用本地脚本或 `rg` 做只读反查。不要再用 SQLite 查询模板。
|
||||
|
||||
### 2. 拿到 `conversationId` 后看两份事实源
|
||||
|
||||
```bash
|
||||
HISTORY_ROOT="$HOME/.cursor-local-assistant-v2/history"
|
||||
CONV_ID="<conversation-id>"
|
||||
|
||||
ls -la "$HISTORY_ROOT/$CONV_ID"
|
||||
```
|
||||
|
||||
重点检查:
|
||||
|
||||
- `state.json`
|
||||
- `current_loop_status`:`idle`、`running`、`waiting_tool`、`completed`、`canceled`、`provider_error`、`failed`
|
||||
- `current_request_id`、`current_turn_seq`
|
||||
- `latest_request_prefix`:最近一次 provider 请求的 provider/model/openai_endpoint/model_call_id/prompt token 摘要
|
||||
- `last_provider_call`:最近 provider 状态与错误文本
|
||||
- `next_entry_seq`、`next_turn_seq`、`context_version`
|
||||
- `current_todos`、`current_plans`
|
||||
- `context.json`
|
||||
- `version` 是否与 `state.context_version` 对齐
|
||||
- `items[]` 是否按 `seq` 稳定递增
|
||||
- 同一 `turn_seq` 下是否有预期的 user/request_context/prompt_context/assistant/tool_result/metadata entries
|
||||
- 是否有重复、缺失或顺序异常
|
||||
- `usage.json`
|
||||
- 通过 `event_index` 或 `recent_events` 查 request/model-call 相关 usage
|
||||
- `totals.cache_read_tokens / (totals.cache_read_tokens + totals.input_tokens)` 可粗略看 cache hit
|
||||
|
||||
### 3. Provider 调用证据现在在哪里
|
||||
|
||||
当前 provider artifact recorder 的行为:
|
||||
|
||||
- `RecordLLMRequest(...)`
|
||||
- 只缓存当前 provider call 的请求摘要。
|
||||
- 如果 payload 可解析 provider/model/openai_endpoint,会更新 `state.latest_request_prefix`。
|
||||
- 不再写 `request.json`。
|
||||
- `AppendLLMResponseChunk(...)`
|
||||
- 当前是 no-op。
|
||||
- 不再写 `sse.jsonl`。
|
||||
- `RecordLLMSummary(...)`
|
||||
- 只补齐当前 provider call summary,并更新 `state.latest_request_prefix.prompt_tokens_total`。
|
||||
- usage 聚合写入 `history/usage.json`。
|
||||
- 不再写 `summary.json`。
|
||||
|
||||
所以 provider 错误排查应优先看:
|
||||
|
||||
- `state.last_provider_call`
|
||||
- `state.latest_request_prefix`
|
||||
- `context.json.items` 里的 `metadata/provider_error/turn_completed` 等 payload
|
||||
- `history/usage.json`
|
||||
- `logs/app.log`
|
||||
- `internal/backend/agent/model/openai.go` / `anthropic.go` 的请求构造和错误解析
|
||||
|
||||
## 这些文件是怎么生成的
|
||||
|
||||
### 根路径
|
||||
|
||||
- `internal/appdata/paths.go`
|
||||
- `RootDir()` 固定为 `~/.cursor-local-assistant-v2`
|
||||
- `HistoryRootPath()` 为 `~/.cursor-local-assistant-v2/history`
|
||||
- `UsageFilePath()` 为 `~/.cursor-local-assistant-v2/history/usage.json`
|
||||
- `LogsRootPath()` 为 `~/.cursor-local-assistant-v2/logs`
|
||||
|
||||
### `state.json + context.json`
|
||||
|
||||
来源链路:
|
||||
|
||||
- `internal/backend/forwarder/file_store.go`
|
||||
- `CreateConversation`
|
||||
- `LoadConversation`
|
||||
- `AppendEntries`
|
||||
- `SaveConversationWithEntries`
|
||||
- `UpdateConversationMeta`
|
||||
- `ReplaceEntries`
|
||||
- `internal/backend/forwarder/service.go`
|
||||
- `handleRunIntent` 开始新 loop / turn
|
||||
- `appendConversationEntries` 追加语义事件
|
||||
- `internal/backend/forwarder/projector.go`
|
||||
- `ProjectPromptReplay()` 把 `context.json.items` 投影为 provider messages
|
||||
|
||||
稳定结论:
|
||||
|
||||
- `state.json` 是当前状态和可变元数据。
|
||||
- `context.json.items` 是 replayable 语义历史。
|
||||
- 发给 LLM 的历史由 projector 从 `context.json.items` 投影,不是从 provider artifacts 重放。
|
||||
- `state.json.entries` 只是内存结构 `ConversationFile` 的字段;落盘时可投影历史在 `context.json.items`。
|
||||
|
||||
### `usage.json`
|
||||
|
||||
来源链路:
|
||||
|
||||
- `internal/backend/forwarder/usage_store.go`
|
||||
- `UsageFileStore.UpsertEvent`
|
||||
- `UsageFileStore.LookupEvent`
|
||||
- `internal/backend/forwarder/token_usage.go`
|
||||
- `internal/historymetrics/`
|
||||
|
||||
稳定结论:
|
||||
|
||||
- `usage.json` 是全局 usage 聚合,不属于单个 conversation 的语义历史。
|
||||
- `recent_events` 只保留最近有限数量事件;长期总量看 `totals` / `daily`。
|
||||
|
||||
### legacy 清理
|
||||
|
||||
来源链路:
|
||||
|
||||
- `internal/backend/forwarder/history_maintenance.go`
|
||||
|
||||
稳定结论:
|
||||
|
||||
- `turns/`、`conversation.json`、`entries.jsonl`、`request.json`、`summary.json` 等都是 legacy artifact。
|
||||
- 启动后的 history maintenance 会清理这些旧产物。
|
||||
|
||||
## 快速判断规则
|
||||
|
||||
- 用户只发一个 id 时,先查 `history/<id>/state.json` 是否存在;不存在再扫 `state.json/context.json/logs`。
|
||||
- 请求失败时,先看 `state.last_provider_call`、`context.json.items` 的错误 metadata、`logs/app.log`;不要找 `turns/<n>/summary.json`。
|
||||
- pending / 工具不收口时,先看同一 `turn_seq` 的 tool call 和 tool result entries,再对照协议上行 `exec_client_message` / `exec_client_control_message` / `interaction_response`。
|
||||
- prefix cache 异常时,先看 `context.json.items` 的稳定追加顺序和 `usage.json` 的 cache token 字段。
|
||||
- 如果 history 与日志冲突,优先相信当前仍在更新的 `state.json/context.json`,再用日志解释运行时经过了哪条路径。
|
||||
@@ -1,147 +0,0 @@
|
||||
# 文件地图
|
||||
|
||||
## 已安装客户端 bundle
|
||||
|
||||
优先核对这些实际运行中的客户端文件:
|
||||
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/out/vs/workbench/api/node/extensionHostProcess.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-always-local/dist/main.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-always-local/dist/gitWorker.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-exec/dist/*.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-worker/dist/main.js`
|
||||
|
||||
当前安装包里 `cursor-agent` 已拆成 split bundle;旧路径 `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent/dist/main.js` 通常不存在。不要按旧路径下结论;需要先列出 `extensions/`,再确认实际存在的 `cursor-agent-exec`、`cursor-agent-worker`、`cursor-always-local` 及其 `dist/` 文件。
|
||||
|
||||
大致归属:
|
||||
|
||||
- `out/vs/workbench/workbench.desktop.main.js`:主 UI、agent window / titlebar、feature flag、用户可点击入口和只读/禁用态。
|
||||
- `cursor-always-local/dist/main.js`:本地模式协议、`BidiAppend`、`RunSSE`、`AgentServerMessage` / `AgentClientMessage` 桥接。
|
||||
- `cursor-agent-exec/dist/main.js` 与同目录数字 chunk:agent 执行侧、SDK/canvas runtime、工具执行、proto 消息定义与拆分 chunk。当前构建中 `411.js` 可能包含 agent 执行链路关键片段,但 chunk 编号不是稳定接口,先用 `dist/*.js` 搜索。
|
||||
- `cursor-agent-worker/dist/main.js`:agent worker 侧后台逻辑。
|
||||
|
||||
用户机器上可能还存在其它 app 副本,例如:
|
||||
|
||||
- `~/Applications/Cursor Hooked.app`
|
||||
- `/Applications/Cursor Patched.app`
|
||||
|
||||
不要假设哪一个在跑,先看进程路径。
|
||||
|
||||
## 当前 backend/store 与 history
|
||||
|
||||
当前用户机器上的固定助手目录:
|
||||
|
||||
- `~/.cursor-local-assistant-v2/`
|
||||
|
||||
重点看:
|
||||
|
||||
- `~/.cursor-local-assistant-v2/config.yaml`
|
||||
- `~/.cursor-local-assistant-v2/data/ca.crt`
|
||||
- `~/.cursor-local-assistant-v2/data/ads/`
|
||||
- `~/.cursor-local-assistant-v2/history/usage.json`
|
||||
- `~/.cursor-local-assistant-v2/history/<conversationId>/state.json`
|
||||
- `~/.cursor-local-assistant-v2/history/<conversationId>/context.json`
|
||||
- `~/.cursor-local-assistant-v2/history/<conversationId>/conversation.lock`
|
||||
- `~/.cursor-local-assistant-v2/logs/app.log`
|
||||
|
||||
其中:
|
||||
|
||||
- `state.json` 是会话元数据、loop 状态、latest provider/request prefix、当前 todos/plans、token/compaction 状态。
|
||||
- `context.json.items` 是 append-only 语义历史,也是 prompt replay 的事实源。
|
||||
- `usage.json` 是全局 provider call / turn usage 聚合。
|
||||
- `conversation.lock` 是会话级文件锁。
|
||||
- checkpoint 只表示同一 backend 进程内的 live state,不是持久化恢复事实源。
|
||||
- legacy artifacts:`conversation.json`、`entries.jsonl`、`turns/`、`request.json`、`summary.json`、`sse.jsonl`、`replay.json`、`runtime.json`、`latest.json`、数字 turn 目录,当前会被 history maintenance 清理。
|
||||
|
||||
这些内容的生成入口主要在:
|
||||
|
||||
- `internal/appdata/paths.go`
|
||||
- `internal/backend/host.go`
|
||||
- `internal/backend/README.md`
|
||||
- `internal/backend/forwarder/file_store.go`
|
||||
- `internal/backend/forwarder/history_maintenance.go`
|
||||
- `internal/backend/forwarder/usage_store.go`
|
||||
- `internal/backend/forwarder/token_usage.go`
|
||||
- `internal/backend/forwarder/artifacts.go`
|
||||
|
||||
## 本仓库协议与本地模式实现
|
||||
|
||||
协议定义:
|
||||
|
||||
- `proto/agent_v1.proto`
|
||||
- `proto/aiserver_v1.proto`
|
||||
- `proto/from_extensions/agent_v1.proto`
|
||||
- `proto/from_extensions/aiserver_v1.proto`
|
||||
|
||||
扩展快照与提取:
|
||||
|
||||
- `proto/extensions-cursor-app/cursor-always-local/package.json`
|
||||
- `proto/extract_extensions_proto.sh`
|
||||
- `proto/ext_tool/main.go`
|
||||
|
||||
本地后端入口:
|
||||
|
||||
- `internal/backend/host.go`
|
||||
- `internal/backend/server/route.go`
|
||||
- `internal/backend/server/policy.go`
|
||||
- `internal/backend/server/local.go`
|
||||
- `internal/backend/server/config/types.go`
|
||||
- `internal/backend/server/config/manager.go`
|
||||
- `internal/backend/server/config/resolver.go`
|
||||
|
||||
forwarder 主链路:
|
||||
|
||||
- `internal/backend/forwarder/module.go`
|
||||
- `internal/backend/forwarder/service.go`
|
||||
- `internal/backend/forwarder/actor.go`
|
||||
- `internal/backend/forwarder/broker.go`
|
||||
- `internal/backend/forwarder/events.go`
|
||||
- `internal/backend/forwarder/compiler.go`
|
||||
- `internal/backend/forwarder/projector.go`
|
||||
- `internal/backend/forwarder/provider.go`
|
||||
- `internal/backend/forwarder/checkpoint_memory.go`
|
||||
- `internal/backend/forwarder/runtime_summary.go`
|
||||
|
||||
协议解码:
|
||||
|
||||
- `internal/backend/agent/protocol/inbound.go`
|
||||
|
||||
执行桥 / 交互桥:
|
||||
|
||||
- `internal/backend/agent/bridge/exec/bridge.go`
|
||||
- `internal/backend/agent/bridge/interaction/bridge.go`
|
||||
|
||||
模型适配:
|
||||
|
||||
- `internal/backend/agent/model/router.go`
|
||||
- `internal/backend/agent/model/openai.go`
|
||||
- `internal/backend/agent/model/anthropic.go`
|
||||
- `internal/backend/agent/model/artifacts.go`
|
||||
- `internal/backend/agent/model/http_error.go`
|
||||
- `internal/backend/agent/model/tool_call_id.go`
|
||||
- `internal/modelchannel/identity.go`
|
||||
- `internal/runtime/local_runtime.go`
|
||||
|
||||
Prompt / replay:
|
||||
|
||||
- `internal/backend/agent/prompt/engine.go`
|
||||
- `internal/backend/agent/prompt/replay.go`
|
||||
- `internal/backend/agent/prompt/content_parts.go`
|
||||
- `internal/backend/forwarder/prompt_context.go`
|
||||
- `internal/backend/forwarder/request_context.go`
|
||||
- `internal/backend/forwarder/reminders.go`
|
||||
- `internal/backend/forwarder/prompt_guard.go`
|
||||
|
||||
## 构建相关参考(只读)
|
||||
|
||||
仓库内已有 macOS 构建与签名相关文件,可用于理解产物结构或历史处理方式,但不要把它们当成修改已安装 Cursor 客户端的操作指南:
|
||||
|
||||
- `Taskfile.yml`
|
||||
- `build/darwin/Taskfile.yml`
|
||||
- `build/dmg-extras/提示损坏?点我.command`
|
||||
|
||||
重点看:
|
||||
|
||||
- `build/darwin/Taskfile.yml` 中的 `codesign:adhoc`
|
||||
- `build/dmg-extras/提示损坏?点我.command` 中的 `xattr -cr`
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
# 已安装客户端只读核对与验证
|
||||
|
||||
首要原则:
|
||||
|
||||
- 不要修改已安装的 Cursor 客户端代码、bundle、签名或 app 副本。
|
||||
- 允许且推荐读取、搜索、比对和分析客户端 bundle、日志、端口和 history 状态。
|
||||
- 目标是定位差异、收集证据、判断问题归属,而不是 patch 客户端。
|
||||
|
||||
## 1. 先确认实际运行的 app 副本
|
||||
|
||||
优先用非交互命令核对:
|
||||
|
||||
```bash
|
||||
pgrep -fal 'Cursor Hooked|Cursor Patched|/Contents/MacOS/Cursor'
|
||||
ps -axo pid,ppid,command | rg 'Cursor(.app)?/Contents/MacOS/Cursor|extension-host'
|
||||
```
|
||||
|
||||
不要在没确认实际运行副本前就下结论,也不要修改客户端文件。
|
||||
|
||||
## 2. 只读定位目标 bundle 与关键文件
|
||||
|
||||
优先定位并读取这些文件,而不是改写它们:
|
||||
|
||||
```bash
|
||||
ls -l "/absolute/path/Target.app/Contents/Resources/app/extensions"
|
||||
shasum -a 256 "/absolute/path/Target.app/Contents/Resources/app/extensions/cursor-always-local/dist/main.js"
|
||||
```
|
||||
|
||||
重点关注:
|
||||
|
||||
- `out/vs/workbench/workbench.desktop.main.js`
|
||||
- `out/vs/workbench/api/node/extensionHostProcess.js`
|
||||
- `cursor-always-local/dist/main.js`
|
||||
- `cursor-always-local/dist/gitWorker.js`
|
||||
- `cursor-agent-exec/dist/main.js`
|
||||
- `cursor-agent-exec/dist/*.js`
|
||||
- `cursor-agent-worker/dist/main.js`
|
||||
|
||||
当前安装包里旧路径 `cursor-agent/dist/main.js` 通常不存在;先确认 `extensions/` 里的实际扩展名和 `dist/` 文件,再选择 `workbench` / `cursor-agent-exec` / `cursor-agent-worker` / `cursor-always-local` 对应排查。
|
||||
|
||||
## 3. 只读读取 bundle 内容
|
||||
|
||||
常用定位关键词:
|
||||
|
||||
```bash
|
||||
rg -n 'BidiTransport|ExecClientMessage|InteractionResponse|conversation_checkpoint_update' "/absolute/path/Target.app/Contents/Resources/app/extensions/cursor-always-local/dist/main.js"
|
||||
rg -n 'CursorAgentProvider|AnthropicProxy|ANTHROPIC_BASE_URL|InteractionUpdate|checkpoint|agent window' "/absolute/path/Target.app/Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js" "/absolute/path/Target.app/Contents/Resources/app/extensions/cursor-agent-exec/dist"/*.js "/absolute/path/Target.app/Contents/Resources/app/extensions/cursor-agent-worker/dist/main.js"
|
||||
rg -n 'agent window|open_agent_window|NameAgent|UpdateConversationMetadata|shouldShowAgentWindowTitleHelperText' "/absolute/path/Target.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js" "/absolute/path/Target.app/Contents/Resources/app/extensions/cursor-agent-exec/dist"/*.js
|
||||
```
|
||||
|
||||
读取具体文件内容时,优先用读取工具按需查看相关片段,不要修改 bundle。
|
||||
|
||||
如果需要和仓库实现对照,优先同时打开:
|
||||
|
||||
- `proto/agent_v1.proto`
|
||||
- `proto/aiserver_v1.proto`
|
||||
- `internal/backend/...`
|
||||
- `internal/runtime/local_runtime.go`
|
||||
|
||||
## 4. 验证行为是否命中目标副本
|
||||
|
||||
至少做其中两项:
|
||||
|
||||
- 进程路径是否是目标 app
|
||||
- 目标扩展 host 是否起来
|
||||
- 本地监听端口是否存在
|
||||
- `~/.cursor-local-assistant-v2/logs/app.log` 是否更新
|
||||
- `~/.cursor-local-assistant-v2/history/<conversationId>/state.json` / `context.json` 是否更新
|
||||
- 请求/协议事件是否真的经过你正在分析的 bundle 文件
|
||||
|
||||
常用验证:
|
||||
|
||||
```bash
|
||||
pgrep -fal '/absolute/path/Target.app/Contents/MacOS/Cursor'
|
||||
lsof -nP -iTCP -sTCP:LISTEN | rg 'Cursor|127.0.0.1'
|
||||
```
|
||||
|
||||
## 5. 记录证据并输出归因
|
||||
|
||||
如果确认“已安装 app 行为”和“仓库代码理解”存在差异,优先记录:
|
||||
|
||||
1. 实际运行的 app 路径
|
||||
2. 命中的 bundle 文件路径与关键符号
|
||||
3. 对应日志、端口、`history/state.json`、`history/context.json`、`usage.json` 证据
|
||||
4. 仓库里对应实现的位置
|
||||
|
||||
如果结论指向客户端侧,也停留在分析和归因,不要继续 patch、重签名、替换文件或做写入式验证。
|
||||
@@ -1,153 +0,0 @@
|
||||
# Provider replay / debug
|
||||
|
||||
当现象是 provider 返回错误、SSE 里只有 `event: error`、需要确认最终出站 provider body 是否能被独立复现时,优先读这份参考。
|
||||
|
||||
这套流程只用于还原“后端最终发给 provider 的请求形状”和“provider 对该请求的真实响应”。它不是语义 history,也不是客户端输入事实源。
|
||||
|
||||
## 证据边界
|
||||
|
||||
- `history/<conversationId>/debug/provider.jsonl`
|
||||
- 最接近 provider 出站边界。
|
||||
- `event=llm_request` 的 `payload.body` 是最终 provider request body。
|
||||
- 用它做 curl replay,判断问题是否已经出现在出站请求形状。
|
||||
- `history/<conversationId>/state.json`
|
||||
- 当前状态和最近 provider 摘要。
|
||||
- 重点看 `latest_request_prefix`、`last_provider_call`。
|
||||
- `history/<conversationId>/context.json`
|
||||
- replayable 语义历史。
|
||||
- 用来解释为什么会形成这次 prompt,不用来直接重放 provider HTTP 请求。
|
||||
- `history/<conversationId>/debug/bidi.raw.jsonl`
|
||||
- 客户端原始上行字节证据。
|
||||
- `history/<conversationId>/debug/bidi.decoded.jsonl`
|
||||
- 当前 known-schema 解码后的客户端上行证据。
|
||||
- `history/<conversationId>/debug/runtime.jsonl`
|
||||
- 后端把哪些字段挂到 active request / stream 上。
|
||||
- `history/<conversationId>/debug/runsse.jsonl`
|
||||
- 后端尝试发回客户端的消息。
|
||||
|
||||
不要把这些证据混用:provider replay 只能证明最终 provider HTTP 请求与响应,不能单独证明客户端原始上传了什么,也不能替代 `context.json` 的语义历史。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 请求发生时 `config.yaml` 中 `log: true`,或已有 `history/<conversationId>/debug/provider.jsonl`。
|
||||
- 已经通过 id 反查拿到:
|
||||
- `conversationId`
|
||||
- `requestId`
|
||||
- `modelCallId`
|
||||
- 已经确认要测的是 Anthropic-compatible `/v1/messages` 请求。
|
||||
|
||||
如果没有 debug 文件,先回到 `state.json`、`context.json`、`usage.json`、`logs/app.log` 做推断,并明确“没有直接 provider body 证据”。
|
||||
|
||||
## 最小流程
|
||||
|
||||
1. 定位 provider debug 文件:
|
||||
|
||||
```bash
|
||||
ROOT="$HOME/.cursor-local-assistant-v2"
|
||||
CONV="<conversationId>"
|
||||
REQ="<requestId>"
|
||||
MODEL_CALL="<modelCallId>"
|
||||
REQUEST_LOG="$ROOT/history/$CONV/debug/provider.jsonl"
|
||||
```
|
||||
|
||||
2. 确认 `llm_request` 存在:
|
||||
|
||||
```bash
|
||||
jq -c --arg req "$REQ" --arg mc "$MODEL_CALL" '
|
||||
select(.event == "llm_request" and .request_id == $req and .model_call_id == $mc)
|
||||
| {at, conversation_id, request_id, model_call_id, provider: .payload.provider, model: .payload.body.model}
|
||||
' "$REQUEST_LOG"
|
||||
```
|
||||
|
||||
3. 执行通用重放脚本:
|
||||
|
||||
```bash
|
||||
REQUEST_LOG="$REQUEST_LOG" \
|
||||
REQUEST_ID="$REQ" \
|
||||
MODEL_CALL_ID="$MODEL_CALL" \
|
||||
CHANNEL_NAME="GLM" \
|
||||
OUT_DIR="/tmp/cursor-provider-replay-$REQ" \
|
||||
.agents/skills/cursor-client-e2e-debugging/scripts/provider-replay.sh
|
||||
```
|
||||
|
||||
也可以直接传入 provider 配置,避免读取 `config.yaml`:
|
||||
|
||||
```bash
|
||||
REQUEST_LOG="$REQUEST_LOG" \
|
||||
REQUEST_ID="$REQ" \
|
||||
MODEL_CALL_ID="$MODEL_CALL" \
|
||||
BASE_URL="<provider-base-url>" \
|
||||
API_KEY="<provider-api-key>" \
|
||||
OUT_DIR="/tmp/cursor-provider-replay-$REQ" \
|
||||
.agents/skills/cursor-client-e2e-debugging/scripts/provider-replay.sh
|
||||
```
|
||||
|
||||
4. 查看产物:
|
||||
|
||||
- `request.body.json`:抽取出的最终 provider body。
|
||||
- `response.headers`:HTTP 响应头。
|
||||
- `response.sse`:SSE 响应体。
|
||||
- `replay.meta.json`:本次重放引用的 id 和 provider log 路径。
|
||||
|
||||
## 结果判断
|
||||
|
||||
- `curl_exit_code != 0`
|
||||
- 网络、TLS、超时、连接或本机 curl 问题。
|
||||
- 先看 stderr、`response.headers` 是否存在,再判断是否真的到达 provider。
|
||||
- HTTP 非 2xx
|
||||
- provider 网关或鉴权层拒绝。
|
||||
- 优先看 `response.headers` 和 provider 错误体。
|
||||
- HTTP 2xx 但 SSE 中有 `event: error`
|
||||
- provider 已接受连接,但认为请求参数不合法或模型侧拒绝。
|
||||
- 这种情况下重点比对 `request.body.json` 的消息结构、tool schema、thinking/reasoning 参数、model 名称和 endpoint 兼容性。
|
||||
- SSE 正常流式输出
|
||||
- 原始 provider 请求形状基本可用。
|
||||
- 如果客户端仍失败,回到 `runsse.jsonl`、forwarder 状态机或客户端协议层继续查。
|
||||
|
||||
## 常见收敛方向
|
||||
|
||||
provider 参数错误时,优先检查:
|
||||
|
||||
- `model` 是否是目标 endpoint 支持的名称。
|
||||
- `messages` 是否符合 Anthropic-compatible 形态。
|
||||
- `system` 是否被目标 provider 支持,或需要改成 message。
|
||||
- `tools` / `tool_choice` 是否符合目标 provider 方言。
|
||||
- `thinking` / `reasoning` 字段是否被目标 provider 支持。
|
||||
- 图片、文件、cache_control、metadata 等扩展字段是否超出 provider 兼容范围。
|
||||
- `max_tokens`、`temperature`、`top_p`、`stop_sequences` 是否落在 provider 允许范围内。
|
||||
|
||||
## 敏感信息规则
|
||||
|
||||
- 不把 API key 写进 skill、reference、脚本默认值或提交内容。
|
||||
- 回复用户时不要粘贴完整 API key;最多说明“已使用用户提供的 key / config 中的 key”。
|
||||
- 不把完整 `request.body.json` 大段贴给用户;只摘和结论相关的字段形状。
|
||||
- 不把一次性 `conversationId/requestId/modelCallId` 写进 skill 文档。
|
||||
- 临时 replay 产物默认放 `/tmp`;如果需要保留,明确说明路径和原因。
|
||||
|
||||
## 脚本参数
|
||||
|
||||
`scripts/provider-replay.sh` 使用环境变量控制:
|
||||
|
||||
- 必填:
|
||||
- `REQUEST_LOG`
|
||||
- `REQUEST_ID`
|
||||
- `MODEL_CALL_ID`
|
||||
- 可选:
|
||||
- `BASE_URL`
|
||||
- `API_KEY`
|
||||
- `GLM_BASE_URL`
|
||||
- `GLM_API_KEY`
|
||||
- `ANTHROPIC_BASE_URL`
|
||||
- `ANTHROPIC_API_KEY`
|
||||
- `CONFIG_FILE`,默认 `~/.cursor-local-assistant-v2/config.yaml`
|
||||
- `CHANNEL_NAME`,默认 `GLM`
|
||||
- `OUT_DIR`,默认 `/tmp/cursor-provider-replay-<requestId>`
|
||||
- `MAX_TIME`,默认 `240`
|
||||
- `ENDPOINT_PATH`,默认 `/v1/messages`
|
||||
|
||||
脚本输出四个稳定产物:
|
||||
|
||||
- `request.body.json`
|
||||
- `response.headers`
|
||||
- `response.sse`
|
||||
- `replay.meta.json`
|
||||
@@ -1,223 +0,0 @@
|
||||
# 搜索词与判断树
|
||||
|
||||
## 先判断层级
|
||||
|
||||
### `history + logs` / id 反查层
|
||||
|
||||
当现象是“用户只给了一个 id”“需要判断它是 `conversationId`、`requestId`、`modelCallId`、`toolCallId`”“要从本地 history 和日志追状态”:
|
||||
|
||||
优先搜索:
|
||||
|
||||
- `state.json`
|
||||
- `context.json`
|
||||
- `usage.json`
|
||||
- `logs/app.log`
|
||||
- `conversation_id`
|
||||
- `current_request_id`
|
||||
- `request_id`
|
||||
- `model_call_id`
|
||||
- `tool_call_id`
|
||||
- `latest_request_prefix`
|
||||
- `last_provider_call`
|
||||
- `current_loop_status`
|
||||
- `context_version`
|
||||
- `next_entry_seq`
|
||||
- `next_turn_seq`
|
||||
- `LoadConversation`
|
||||
- `CreateConversation`
|
||||
- `SaveConversationWithEntries`
|
||||
- `AppendEntries`
|
||||
- `UpdateConversationMeta`
|
||||
- `ReplaceEntries`
|
||||
- `ProjectPromptReplay`
|
||||
- `UsageFileStore`
|
||||
- `UpsertEvent`
|
||||
- `LookupEvent`
|
||||
|
||||
不要再优先搜索或依赖:
|
||||
|
||||
- `data.sqlite`
|
||||
- `protocol_traces`
|
||||
- `agent_request_runs`
|
||||
- `conversation.json`
|
||||
- `entries.jsonl`
|
||||
- `turns/<n>`
|
||||
- `request.json`
|
||||
- `sse.jsonl`
|
||||
- `summary.json`
|
||||
|
||||
这些是旧实现或 legacy artifact 相关线索,只在排查迁移/清理逻辑时作为历史背景。
|
||||
|
||||
### `cursor-agent-exec` / `cursor-agent-worker` 层
|
||||
|
||||
当现象涉及 agent 主循环、模型桥接、`InteractionUpdate` 映射、工具 started/completed、session/provider 状态:
|
||||
|
||||
优先在已安装客户端 split bundle 中搜索:
|
||||
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-exec/dist/*.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-worker/dist/main.js`
|
||||
|
||||
优先搜索:
|
||||
|
||||
- `registerAgentProvider`
|
||||
- `CursorAgentProvider`
|
||||
- `CursorAgentProviderHandle`
|
||||
- `ClaudeSDKClient`
|
||||
- `streamInteractionUpdates`
|
||||
- `handlePartialMessage`
|
||||
- `AnthropicProxy`
|
||||
- `getAnthropicProxyPort`
|
||||
- `getAnthropicProxyAuthToken`
|
||||
- `ANTHROPIC_BASE_URL`
|
||||
- `ANTHROPIC_API_KEY`
|
||||
- `InteractionUpdate`
|
||||
- `checkpoint`
|
||||
|
||||
旧路径 `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent/dist/main.js` 可能不存在。先确认实际 `extensions/` 结构,再按 `cursor-agent-exec` / `cursor-agent-worker` / `cursor-always-local` 分层排查。
|
||||
|
||||
### agent window / conversation metadata UI 层
|
||||
|
||||
当现象涉及 agent window 标题、窗口信息、titlebar 按钮、是否可点击修改、会话名/metadata 更新:
|
||||
|
||||
优先在已安装客户端主 UI 和 split bundle 中搜索:
|
||||
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-agent-exec/dist/*.js`
|
||||
- `/Applications/Cursor.app/Contents/Resources/app/extensions/cursor-always-local/dist/main.js`
|
||||
|
||||
优先搜索:
|
||||
|
||||
- `shouldShowAgentWindowTitleHelperText`
|
||||
- `glass_open_agents_titlebar_button`
|
||||
- `open_agent_window_top`
|
||||
- `open_agent_window_bottom_convo`
|
||||
- `glass.enable_open_agent_in_window`
|
||||
- `NameAgentRequest`
|
||||
- `NameAgentResponse`
|
||||
- `UpdateConversationMetadataRequest`
|
||||
- `UpdateConversationMetadataResponse`
|
||||
- `CreateTranscriptOverviewRequest`
|
||||
- `createTranscriptOverview`
|
||||
- `updateConversationMetadata`
|
||||
- `conversation_checkpoint_update`
|
||||
|
||||
判断规则:
|
||||
|
||||
- `ConversationStateStructure` / `conversation_checkpoint_update` 是 UI 同步快照,不应作为持久化修改入口。
|
||||
- 如果客户端调用 `UpdateConversationMetadata` / `NameAgent`,要继续确认本地后端是否显式注册对应 `/agent.v1.AgentService/*` 路由;不能只看 proto message 存在。
|
||||
- 本地模式修改会话名/metadata 时,应落到 `history/<conversationId>/state.json` 或等价持久化会话元数据,再 publish checkpoint 同步 UI。
|
||||
|
||||
### `cursor-always-local` / 协议层
|
||||
|
||||
当现象涉及本地模式、客户端没有回包、pending 不收口、同一 backend 进程内的 live checkpoint 重连错乱:
|
||||
|
||||
优先搜索:
|
||||
|
||||
- `BidiTransport`
|
||||
- `startYieldingInputsToTheServer`
|
||||
- `BidiAppend`
|
||||
- `RunSSE`
|
||||
- `AgentServerMessage`
|
||||
- `AgentClientMessage`
|
||||
- `ExecServerMessage`
|
||||
- `ExecClientMessage`
|
||||
- `ExecClientControlMessage`
|
||||
- `InteractionQuery`
|
||||
- `InteractionResponse`
|
||||
- `conversation_checkpoint_update`
|
||||
|
||||
### 本仓库 forwarder 层
|
||||
|
||||
当现象涉及本地后端收发、provider 继续/暂停、exec/interaction 桥接、history 投影:
|
||||
|
||||
优先搜索:
|
||||
|
||||
- `handleRunIntent`
|
||||
- `driveProvider`
|
||||
- `startStreamActor`
|
||||
- `streamCommandEnvelope`
|
||||
- `handleToolInvocation`
|
||||
- `handleExecResult`
|
||||
- `handleExecControl`
|
||||
- `publishCheckpoint`
|
||||
- `CheckpointConversation`
|
||||
- `snapshotCheckpointConversation`
|
||||
- `appendConversationEntries`
|
||||
- `OpenExec`
|
||||
- `OpenQuery`
|
||||
- `StartStream`
|
||||
- `deriveConversationLoopState`
|
||||
- `historyEntryToolCallID`
|
||||
- `recordProviderUsage`
|
||||
- `recordTurnUsage`
|
||||
|
||||
### provider / 模型适配层
|
||||
|
||||
当现象是 provider 400/500、thinking/reasoning、tool_call_id、OpenAI/Anthropic 请求形状、usage/cache 不对:
|
||||
|
||||
优先搜索:
|
||||
|
||||
- `StartStream`
|
||||
- `StreamRequest`
|
||||
- `ResolvedChannelID`
|
||||
- `ResolvedChannelName`
|
||||
- `ProviderModelID`
|
||||
- `ThinkingEnabled`
|
||||
- `buildAnthropicThinkingConfig`
|
||||
- `normalizeAnthropicProviderMessages`
|
||||
- `normalizeOpenAIProviderMessages`
|
||||
- `normalizeOpenAIResponsesInput`
|
||||
- `reasoning_content`
|
||||
- `ReasoningContent`
|
||||
- `ReasoningSignature`
|
||||
- `RecordLLMRequest`
|
||||
- `RecordLLMSummary`
|
||||
- `http_error`
|
||||
- `namespaceToolCallID`
|
||||
|
||||
## 快速判断规则
|
||||
|
||||
- 如果问题是“给你一个 id,让你先判断是什么 ID,再找日志”,先看 `history/<id>/state.json` 是否存在,再扫 `history/*/state.json`、`history/*/context.json` 和 `logs/app.log`。
|
||||
- 如果问题是“模型输出语义不对”,先看 `context.json.items` 到 `ProjectPromptReplay()` 的投影,再看 provider request normalization。
|
||||
- 如果问题是“provider 报 400/参数错误”,先看模型适配层请求构造、`state.latest_request_prefix`、`state.last_provider_call`、`logs/app.log`。
|
||||
- 如果问题是“客户端没回某个工具结果 / pending 不收口”,先看 `cursor-always-local` 与 forwarder,同时核对同一 `turn_seq` 是否有 `tool_result` 或控制面错误 entry。
|
||||
- 如果问题是“backend 重启后为什么 checkpoint 没法继续恢复 pending”,不要找磁盘 checkpoint;checkpoint 是 live state,重启后的事实源是 `state.json + context.json`。
|
||||
- 如果问题是“为什么同一个 `modelID` 还能出现多个渠道”,先检查渠道 ID:规范化后 `baseURL + modelID + apiKey + displayName + openAIEndpoint` 的短 SHA-256;resolver 仍兼容 legacy `baseURL + modelID + apiKey + displayName`。
|
||||
- 如果问题是“只想桥接到其他 LLM”,优先看模型桥接层,不要默认深入整套 local runtime。
|
||||
- 如果问题是“已安装 app 行为和仓库代码不一致”,先核对实际运行 bundle,再做只读比对;不要 patch 客户端。
|
||||
|
||||
## 协议关键词
|
||||
|
||||
上行:
|
||||
|
||||
- `run_request`
|
||||
- `exec_client_message`
|
||||
- `exec_client_control_message`
|
||||
- `interaction_response`
|
||||
|
||||
下行:
|
||||
|
||||
- `interaction_update`
|
||||
- `exec_server_message`
|
||||
- `exec_server_control_message`
|
||||
- `interaction_query`
|
||||
- `conversation_checkpoint_update`
|
||||
|
||||
如果只看到下行请求,没有对应上行结果或控制消息,优先排查:
|
||||
|
||||
- `exec_id`
|
||||
- `id`
|
||||
- `tool_call_id`
|
||||
- `request_id`
|
||||
- `model_call_id`
|
||||
- pending 收口逻辑
|
||||
|
||||
如果用户给的是一个裸 id,不要直接把它当成 `request_id`。先同时查:
|
||||
|
||||
- `history/<id>/state.json`
|
||||
- `history/*/state.json` 的 `current_request_id`、`latest_request_prefix`、`last_provider_call`
|
||||
- `history/*/context.json` 的 `items[].request_id`、`items[].tool_call_id`、`items[].payload`
|
||||
- `history/usage.json` 的 `event_index` / `recent_events`
|
||||
- `logs/app.log`
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptPath = fileURLToPath(import.meta.url);
|
||||
const skillRoot = path.resolve(path.dirname(scriptPath), "..");
|
||||
const repoRoot = path.resolve(skillRoot, "../../..");
|
||||
const args = ["run", "./scripts/historymetrics", ...process.argv.slice(2)];
|
||||
|
||||
const child = spawn("go", args, {
|
||||
cwd: repoRoot,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
child.on("error", (error) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(`cache-hit-rate.mjs failed: ${message}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (typeof code === "number") {
|
||||
process.exitCode = code;
|
||||
return;
|
||||
}
|
||||
if (signal) {
|
||||
console.error(`cache-hit-rate.mjs terminated by signal: ${signal}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -1,194 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
REQUEST_LOG=<path/to/provider.jsonl> REQUEST_ID=<request-id> MODEL_CALL_ID=<model-call-id> \
|
||||
[BASE_URL=<provider-base-url>] [API_KEY=<provider-api-key>] [CONFIG_FILE=<config.yaml>] \
|
||||
[OUT_DIR=<output-dir>] [MAX_TIME=240] provider-replay.sh
|
||||
|
||||
Required:
|
||||
REQUEST_LOG Path to history/<conversationId>/debug/provider.jsonl
|
||||
REQUEST_ID Provider request_id to replay
|
||||
MODEL_CALL_ID Provider model_call_id to replay
|
||||
|
||||
Optional:
|
||||
BASE_URL Provider base URL. Falls back to GLM_BASE_URL or ANTHROPIC_BASE_URL.
|
||||
API_KEY Provider API key. Falls back to ANTHROPIC_API_KEY or GLM_API_KEY.
|
||||
CONFIG_FILE Defaults to ~/.cursor-local-assistant-v2/config.yaml.
|
||||
CHANNEL_NAME Display name to read from config.yaml when BASE_URL/API_KEY is missing. Defaults to GLM.
|
||||
OUT_DIR Output directory. Defaults to /tmp/cursor-provider-replay-<request-id>.
|
||||
MAX_TIME curl max-time seconds. Defaults to 240.
|
||||
ENDPOINT_PATH Provider path. Defaults to /v1/messages.
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REQUEST_LOG="${REQUEST_LOG:-}"
|
||||
REQUEST_ID="${REQUEST_ID:-}"
|
||||
MODEL_CALL_ID="${MODEL_CALL_ID:-}"
|
||||
CONFIG_FILE="${CONFIG_FILE:-$HOME/.cursor-local-assistant-v2/config.yaml}"
|
||||
CHANNEL_NAME="${CHANNEL_NAME:-GLM}"
|
||||
BASE_URL="${BASE_URL:-${GLM_BASE_URL:-${ANTHROPIC_BASE_URL:-}}}"
|
||||
API_KEY="${API_KEY:-${ANTHROPIC_API_KEY:-${GLM_API_KEY:-}}}"
|
||||
ENDPOINT_PATH="${ENDPOINT_PATH:-/v1/messages}"
|
||||
MAX_TIME="${MAX_TIME:-240}"
|
||||
OUT_DIR="${OUT_DIR:-/tmp/cursor-provider-replay-${REQUEST_ID:-unknown}}"
|
||||
BODY_FILE="$OUT_DIR/request.body.json"
|
||||
RESP_FILE="$OUT_DIR/response.sse"
|
||||
HEADER_FILE="$OUT_DIR/response.headers"
|
||||
META_FILE="$OUT_DIR/replay.meta.json"
|
||||
|
||||
require_value() {
|
||||
local name="$1"
|
||||
local value="$2"
|
||||
if [[ -z "$value" ]]; then
|
||||
echo "缺少 $name。运行 --help 查看用法。" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
read_channel_config() {
|
||||
local field="$1"
|
||||
python3 - "$CONFIG_FILE" "$CHANNEL_NAME" "$field" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
config_path = Path(sys.argv[1]).expanduser()
|
||||
channel_name = sys.argv[2]
|
||||
field = sys.argv[3]
|
||||
if not config_path.exists():
|
||||
raise SystemExit
|
||||
|
||||
lines = config_path.read_text(encoding="utf-8").splitlines()
|
||||
in_channel = False
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("- displayName:"):
|
||||
in_channel = stripped.split(":", 1)[1].strip().strip('"') == channel_name
|
||||
continue
|
||||
if in_channel and stripped.startswith(field + ":"):
|
||||
print(stripped.split(":", 1)[1].strip().strip('"'))
|
||||
raise SystemExit
|
||||
PY
|
||||
}
|
||||
|
||||
require_value "REQUEST_LOG" "$REQUEST_LOG"
|
||||
require_value "REQUEST_ID" "$REQUEST_ID"
|
||||
require_value "MODEL_CALL_ID" "$MODEL_CALL_ID"
|
||||
|
||||
if [[ ! -f "$REQUEST_LOG" ]]; then
|
||||
echo "REQUEST_LOG 不存在: $REQUEST_LOG" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "$BASE_URL" ]]; then
|
||||
BASE_URL="$(read_channel_config baseURL || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "$API_KEY" ]]; then
|
||||
API_KEY="$(read_channel_config apiKey || true)"
|
||||
fi
|
||||
|
||||
require_value "BASE_URL/GLM_BASE_URL/ANTHROPIC_BASE_URL 或 config[$CHANNEL_NAME].baseURL" "$BASE_URL"
|
||||
require_value "API_KEY/ANTHROPIC_API_KEY/GLM_API_KEY 或 config[$CHANNEL_NAME].apiKey" "$API_KEY"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
: > "$HEADER_FILE"
|
||||
: > "$RESP_FILE"
|
||||
|
||||
python3 - "$REQUEST_LOG" "$REQUEST_ID" "$MODEL_CALL_ID" "$BODY_FILE" "$META_FILE" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
log_path = Path(sys.argv[1]).expanduser()
|
||||
request_id = sys.argv[2]
|
||||
model_call_id = sys.argv[3]
|
||||
body_path = Path(sys.argv[4])
|
||||
meta_path = Path(sys.argv[5])
|
||||
|
||||
body = None
|
||||
meta = None
|
||||
with log_path.open(encoding="utf-8") as f:
|
||||
for raw in f:
|
||||
if not raw.strip():
|
||||
continue
|
||||
row = json.loads(raw)
|
||||
if row.get("event") != "llm_request":
|
||||
continue
|
||||
if row.get("request_id") != request_id:
|
||||
continue
|
||||
if row.get("model_call_id") != model_call_id:
|
||||
continue
|
||||
payload = row.get("payload") or {}
|
||||
body = payload.get("body")
|
||||
meta = {
|
||||
"at": row.get("at"),
|
||||
"event": row.get("event"),
|
||||
"conversation_id": row.get("conversation_id"),
|
||||
"request_id": row.get("request_id"),
|
||||
"model_call_id": row.get("model_call_id"),
|
||||
"provider_log": str(log_path),
|
||||
}
|
||||
break
|
||||
|
||||
if body is None:
|
||||
raise SystemExit(f"未找到 llm_request: request_id={request_id} model_call_id={model_call_id}")
|
||||
|
||||
body_path.write_text(json.dumps(body, ensure_ascii=False, separators=(",", ":")), encoding="utf-8")
|
||||
meta_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
PY
|
||||
|
||||
url="${BASE_URL%/}${ENDPOINT_PATH}"
|
||||
|
||||
set +e
|
||||
curl --no-buffer --silent --show-error \
|
||||
--connect-timeout 30 \
|
||||
--max-time "$MAX_TIME" \
|
||||
--request POST "$url" \
|
||||
--header "content-type: application/json" \
|
||||
--header "anthropic-version: 2023-06-01" \
|
||||
--header "User-Agent: claude-cli/1.0.25" \
|
||||
--header "x-api-key: $API_KEY" \
|
||||
--header "Authorization: Bearer $API_KEY" \
|
||||
--data-binary "@$BODY_FILE" \
|
||||
--dump-header "$HEADER_FILE" \
|
||||
--output "$RESP_FILE"
|
||||
code=$?
|
||||
set -e
|
||||
|
||||
echo "curl_exit_code=$code"
|
||||
echo "body=$BODY_FILE"
|
||||
echo "headers=$HEADER_FILE"
|
||||
echo "response=$RESP_FILE"
|
||||
echo "meta=$META_FILE"
|
||||
|
||||
echo "--- response headers ---"
|
||||
if [[ -f "$HEADER_FILE" ]]; then
|
||||
python3 - "$HEADER_FILE" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
for line in Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace").splitlines()[:40]:
|
||||
print(line)
|
||||
PY
|
||||
fi
|
||||
|
||||
echo "--- response first 120 lines ---"
|
||||
if [[ -f "$RESP_FILE" ]]; then
|
||||
python3 - "$RESP_FILE" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
for line in Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace").splitlines()[:120]:
|
||||
print(line)
|
||||
PY
|
||||
else
|
||||
echo "响应文件不存在。"
|
||||
fi
|
||||
|
||||
exit "$code"
|
||||
@@ -1,176 +0,0 @@
|
||||
---
|
||||
name: cursor-debug-log
|
||||
description: 当需要调查 Cursor 本地模式 debug/log 证据时使用:config.yaml 的 log 热加载、history/<conversationId>/debug JSONL 文件、Bidi 原始/解码记录、RunSSE 记录、runtime/provider debug 记录、debug 文件缺失原因,或解释这些 debug 文件如何生成与如何查询。
|
||||
---
|
||||
|
||||
# Cursor Debug Log
|
||||
|
||||
使用这个技能来解释和检查本地 debug log 体系。目标是在不修改已安装 Cursor 客户端、不依赖旧版 legacy artifact 的前提下,还原一次请求附近发生了什么。
|
||||
|
||||
## 作用定位
|
||||
|
||||
debug log 是本地模式请求链路的可选证据层。它和模型可见历史是分开的:
|
||||
|
||||
- 用来回答“客户端到底发了什么”。
|
||||
- 用来回答“后端解码后认为这是什么请求”。
|
||||
- 用来回答“哪些字段被挂到了当前 active request 上”。
|
||||
- 用来回答“最终 provider request body 是什么样”。
|
||||
- 用来回答“RunSSE 实际给客户端发送了什么”。
|
||||
- 不要把它当成 replay history、prompt 输入或状态事实源。
|
||||
|
||||
稳定事实源仍然是:
|
||||
|
||||
- `history/<conversationId>/state.json`
|
||||
- `history/<conversationId>/context.json`
|
||||
- `history/usage.json`
|
||||
- `logs/app.log`
|
||||
|
||||
debug 文件是在这些事实源之外,补充原始或近原始链路证据。
|
||||
|
||||
## 固定路径
|
||||
|
||||
- 助手根目录:`~/.cursor-local-assistant-v2`
|
||||
- 配置文件:`~/.cursor-local-assistant-v2/config.yaml`
|
||||
- history 根目录:`~/.cursor-local-assistant-v2/history`
|
||||
- app 日志:`~/.cursor-local-assistant-v2/logs/app.log`
|
||||
- 会话 debug 目录:`history/<conversationId>/debug/`
|
||||
- 孤儿 debug 目录:`history/_debug/orphan/<requestId>/`
|
||||
|
||||
通过配置开启 debug logging:
|
||||
|
||||
```yaml
|
||||
log: true
|
||||
```
|
||||
|
||||
当前实现会用轻量文件快照检查热加载 `config.yaml`。改完 `log` 后,预留大约 500ms,再期待下一次请求事件使用新值。旧二进制可能仍然需要重启。
|
||||
|
||||
## 文件如何生成
|
||||
|
||||
debug 层随着请求穿过后端边界逐步落盘:
|
||||
|
||||
1. `BidiAppend` 收到客户端上行数据。
|
||||
- 原始 hex 写入 `bidi.raw.jsonl`。
|
||||
- 解码后的 known-schema protobuf 与后端提取出的 intent 写入 `bidi.decoded.jsonl`。
|
||||
2. forwarder 把解码结果转成 active runtime state。
|
||||
- stream/request 状态决策写入 `runtime.jsonl`。
|
||||
3. provider pass 被准备并执行。
|
||||
- adapter 前的请求摘要、`model_call_id`、`provider_pass` 等写入 `provider.jsonl`。
|
||||
- provider artifact callback 追加最终 request/summary payload 到 `provider.jsonl`。
|
||||
4. `RunSSE` 把后端输出流式发送给客户端。
|
||||
- 已发送消息、终态事件、发送错误、断连和 heartbeat 写入 `runsse.jsonl`。
|
||||
|
||||
如果某条消息到达时后端还不知道 `conversationId`,早期事件可能写到 `_debug/orphan/<requestId>/`。后续一旦知道 `conversationId`,新事件应进入 `history/<conversationId>/debug/`。还原早期或乱序请求时,两处都要查。
|
||||
|
||||
## Debug 文件含义
|
||||
|
||||
`bidi.raw.jsonl`
|
||||
|
||||
- 方向:客户端到后端。
|
||||
- 包含 `request_id`、可选 `conversation_id`、`append_seqno`、`status`、原始 `data_hex`。
|
||||
- 当需要精确确认客户端上传字节时先看它。
|
||||
|
||||
`bidi.decoded.jsonl`
|
||||
|
||||
- 方向:客户端到后端,protobuf 解码后。
|
||||
- 当前 schema v2 包含完整的 known-schema `AgentClientMessage` protojson:`message`。
|
||||
- 同时包含后端从上行包提取出的 intent:`intent`,其中会展开相关 proto 子对象,例如 `client_message`、`user_message`、`request_context`、`conversation_state`、exec/interaction/kv 回包等。
|
||||
- 还包含 `message_case`、`requested_model`、`conversation_action` 等检索索引;这些索引只方便搜索,不是完整证据本体。
|
||||
- 当需要确认后端如何理解客户端请求时看它。若要证明客户端原始上传字节,仍以 `bidi.raw.jsonl` 为准。
|
||||
- 旧二进制或旧日志可能只有 schema v1 摘要,未必展开 `message` 和 `intent` 里的完整字段。
|
||||
|
||||
`runtime.jsonl`
|
||||
|
||||
- 方向:后端内部 runtime。
|
||||
- 包含状态流转,以及挂到 active stream/request 上的字段。
|
||||
- 当需要把 decoded input 和后续 provider 行为串起来时看它。
|
||||
|
||||
`provider.jsonl`
|
||||
|
||||
- 方向:后端到 provider adapter/provider。
|
||||
- 包含 provider pass 元数据、`model_call_id`、request knobs、最终 provider request artifact、provider summary artifact。
|
||||
- 当最终出站 provider body 或 provider summary 是关键证据时看它。
|
||||
|
||||
`runsse.jsonl`
|
||||
|
||||
- 方向:后端到客户端。
|
||||
- 包含解码后的 `AgentServerMessage` 发送、终态事件、发送错误、断连和 heartbeat。
|
||||
- 用来检查后端尝试返回给客户端的内容。它是解码后的消息证据,不是原始 HTTP/SSE framing。
|
||||
|
||||
## 查询流程
|
||||
|
||||
1. 先判断 id 类型。
|
||||
- 先查 `history/<id>/state.json`,确认它是不是 `conversationId`。
|
||||
- 再在 `history/*/{state.json,context.json}`、`history/usage.json`、`logs/app.log` 里搜索 request/model-call/tool id。
|
||||
2. 拿到 `conversationId` 后,列出 debug 目录。
|
||||
- `ls -la "$HOME/.cursor-local-assistant-v2/history/<conversationId>/debug"`
|
||||
3. 如果 debug 目录不存在,确认请求发生时 debug 是否已开启。
|
||||
- 读取 `config.yaml`。
|
||||
- 对比 `config.yaml`、`state.json`、`context.json` 的 mtime。
|
||||
- 搜索 `logs/app.log` 里的 config hot reload 或 provider start 记录。
|
||||
4. 按时间顺序读 JSONL,并用这些字段串联:
|
||||
- `request_id`
|
||||
- `conversation_id`
|
||||
- `model_call_id`
|
||||
- `provider_pass`
|
||||
- `append_seqno`
|
||||
- event timestamp
|
||||
5. 最终回复只总结结论所需字段。不要粘贴 secret、API key、完整 provider body 或大段原始 payload。
|
||||
|
||||
常用命令:
|
||||
|
||||
```bash
|
||||
ROOT="$HOME/.cursor-local-assistant-v2"
|
||||
REQ="<requestId>"
|
||||
CONV="<conversationId>"
|
||||
|
||||
rg -n "$REQ" "$ROOT/history" "$ROOT/logs/app.log"
|
||||
find "$ROOT/history" -path "*/debug/*" -type f | sort
|
||||
rg -n "$REQ|model_call_id|provider_request_prepared|llm_request" "$ROOT/history/$CONV/debug"
|
||||
```
|
||||
|
||||
紧凑查看 JSONL:
|
||||
|
||||
```bash
|
||||
jq -c 'select(.request_id == "<requestId>")' "$ROOT/history/$CONV/debug/provider.jsonl"
|
||||
jq -c 'select(.request_id == "<requestId>") | {append_seqno, message_case, conversation_action, message, intent}' "$ROOT/history/$CONV/debug/bidi.decoded.jsonl"
|
||||
```
|
||||
|
||||
## 证据怎么用
|
||||
|
||||
根据问题选择对应文件:
|
||||
|
||||
- 客户端原始上行问题:先看 `bidi.raw.jsonl`。这是精确原始包证据。
|
||||
- 客户端 known-schema 字段问题:看 `bidi.decoded.jsonl` 的 `message`。例如 `user_message.message_id`、selected image、conversation state bytes 等字段是否在解码结果里。
|
||||
- 后端如何理解请求:看 `bidi.decoded.jsonl` 的 `intent`,再接 `runtime.jsonl`。
|
||||
- provider request 问题:看 `provider.jsonl`,尤其是 `llm_request`。
|
||||
- UI/流式输出问题:看 `runsse.jsonl`。
|
||||
- 请求状态问题:先看 `state.json`、`context.json`、`usage.json`,再用 debug 文件补证。
|
||||
- debug 缺失问题:看 `config.yaml`、mtime、app log、orphan debug 目录。
|
||||
|
||||
runtime model parameters,例如 thinking strength,只是 provider request 证据的一类例子:
|
||||
|
||||
- `bidi.raw.jsonl` 说明客户端原始上传了什么。
|
||||
- `bidi.decoded.jsonl.message` 说明上行包按当前 known schema 解码出了什么。
|
||||
- `bidi.decoded.jsonl.intent` 说明后端从 decoded input 里提取并准备使用了什么。
|
||||
- `runtime.jsonl` 说明后端把什么挂到了请求状态上。
|
||||
- `provider.jsonl` 说明最终为 provider 准备了什么。
|
||||
|
||||
只有普通 history 时不要过度断言。例如 `context.json` 里的 `reasoning_content` 能说明产生过 reasoning 文本,但不能单独证明是哪一个 runtime parameter value 导致的。
|
||||
|
||||
注意证据边界:
|
||||
|
||||
- `bidi.decoded.jsonl` 使用当前已知 proto schema 做解码。未知字段或原始 framing 差异不能靠 decoded 证明,必须回到 `bidi.raw.jsonl`。
|
||||
- `context.json` 仍是持久化历史事实源;debug 文件只能证明某次请求链路附近发生过什么。
|
||||
- `provider.jsonl` 的 provider body 和 `bidi.raw.jsonl` / `bidi.decoded.jsonl` 都可能很大,回复用户时只摘必要字段,不粘贴完整图片、完整 body 或 secret。
|
||||
|
||||
## Debug 文件缺失
|
||||
|
||||
如果某个 request 没有 debug 文件,要明确说明“没有直接 debug 证据”。常见原因:
|
||||
|
||||
- 请求发生时 `log: false`。
|
||||
- 正在运行的二进制版本早于 debug logging 或 hot reload 实现。
|
||||
- 事件发生时还没有解析到 conversation id,记录在 `_debug/orphan/<requestId>/`。
|
||||
- 请求在开启 `log` 前已经完成。
|
||||
- 写文件失败;如果该版本有相关记录,app log 里可能有 warning。
|
||||
|
||||
debug 证据缺失时,回退到 `state.json`、`context.json`、`usage.json`、`logs/app.log`,并把结论标成推断,而不是直接证明。
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: cursor-proxy-debugger
|
||||
description: Maintain, diagnose, extend, and validate the standalone Cursor HTTPS protocol debugger in cursor-proxy-debugger. Use when changing its command startup, MITM capture behavior, Connect streaming or protobuf decoding, SQLite persistence, local debugging API, embedded web UI, tests, documentation, or when investigating captured Cursor BidiAppend, RunSSE, Fork Chat, or model-discovery traffic.
|
||||
---
|
||||
|
||||
# Cursor Proxy Debugger
|
||||
|
||||
Treat `cursor-proxy-debugger` as an independent Go module and executable project. Keep its command entry point and all debugger-specific assets in that directory.
|
||||
|
||||
## Respect the project boundary
|
||||
|
||||
- Keep every Go file in the project root in `package main`; do not recreate a command directory in the main repository.
|
||||
- Reuse the shared CA and generated Cursor protobuf packages from `cursor-byok` rather than copying them.
|
||||
- The canonical proto sources are `cursor-byok/internal/backend/cursor/proto`; update or regenerate them in the main repository when schemas change.
|
||||
- Keep the tool observational: never modify Cursor settings, system proxy settings, or the installed client automatically.
|
||||
- Bind the debugging UI to loopback addresses only. Continue passing non-target CONNECT traffic through without MITM.
|
||||
- Preserve forwarded request and response bodies even when local capture limits truncate stored copies.
|
||||
|
||||
## Locate the responsibility
|
||||
|
||||
- `main.go`: flags, startup output, browser opening, signals, and graceful shutdown.
|
||||
- `proxy.go` and `capture.go`: listeners, target matching, MITM, streaming capture, and forwarding.
|
||||
- `decode.go`: Connect envelopes, compression, protobuf message selection, and JSON views.
|
||||
- `decode_stored.go`: persisted payload hydration and stored protobuf/text views.
|
||||
- `proxy_capture.go`: request/response body capture and frame event assembly.
|
||||
- `store.go`: hot-memory state, SQLite persistence, subscriptions, and conversation queries.
|
||||
- `store_queries.go`: persisted exchange queries, cloning, redaction helpers, and subscriptions.
|
||||
- `types.go`: configuration and API-facing capture models.
|
||||
- `web.go`: loopback API, SSE events, CA download, security headers, and embedded assets.
|
||||
- `web/app.js`: page state, rendering, Monaco editor lifecycle, and bootstrap.
|
||||
- `web/app_events.js`: UI event binding for filters, details, pause, and resizing.
|
||||
- `web/view_helpers.js`: display formatting, HTML escaping, and copy-text helpers.
|
||||
- `web/styles*.css`: split base, control, detail, and responsive stylesheets.
|
||||
- `web/`: dependency-free debugging UI and its Chinese/English text.
|
||||
|
||||
## Follow the change workflow
|
||||
|
||||
1. Inspect `git status` and the relevant staged and unstaged diffs before editing; captures and debugger files may already contain user work.
|
||||
2. Read the smallest responsible source files. This standalone temporary debugger intentionally does not carry a test suite; for backend, MITM, or routing changes in formal product modules, also follow `chinese-code-style` and its `MODULES.md` boundary rules.
|
||||
3. For a new protocol endpoint, confirm the exact URL path, request/response direction, streaming mode, compression, and generated protobuf message type. Do not infer schemas from similar endpoints.
|
||||
4. Decode incrementally across arbitrary read boundaries. Treat Connect flags and the five-byte frame header as protocol data, and keep malformed-frame errors visible without breaking upstream forwarding.
|
||||
5. Redact sensitive headers in every newly exposed API or UI path. Never log or render authorization material by default.
|
||||
6. When changing UI text, update both locale tables in `web/i18n.js`, keep `data-i18n` keys aligned, and verify the fallback language.
|
||||
7. Update `README.md` and `README.en.md` together when commands, flags, supported traffic, storage, or setup steps change.
|
||||
|
||||
## TDD boundary and proportional validation
|
||||
|
||||
- Formal product modules must follow TDD: write or update a focused failing test first, implement the smallest change that makes it pass, then refactor while keeping the test green.
|
||||
- This project is a temporary observational tool, so TDD is not mandatory and test files may be intentionally omitted. Validate it with formatting, build checks, the style checker, and targeted manual smoke checks instead.
|
||||
|
||||
- Format changed Go files with `gofmt`.
|
||||
- Run `go build -o <temporary-path>/cursor-proxy-debugger .` from the standalone project after entry-point, dependency, embed, or build-task changes. Do not require tests while this temporary project has no tests.
|
||||
- Run the Chinese style checker on changed handwritten source files.
|
||||
- For UI changes, start with `go run . -open=false` when safe, query `/api/status`, and inspect the page in a browser if layout or interaction changed.
|
||||
- For capture or decoding changes, perform focused manual checks for split reads, compressed frames, malformed input, endpoint direction, persistence, or pass-through behavior as applicable.
|
||||
|
||||
## Use the canonical commands
|
||||
|
||||
From `cursor-proxy-debugger`:
|
||||
|
||||
```bash
|
||||
go run .
|
||||
go build -o ./bin/cursor-proxy-debugger .
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Cursor Proxy Debugger"
|
||||
short_description: "维护、诊断并验证独立的 Cursor HTTPS 协议调试代理"
|
||||
default_prompt: "Use $cursor-proxy-debugger to diagnose or modify the standalone Cursor protocol debugging proxy at /Users/leokun/Documents/cursor-proxy-debugger."
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
name: i18n-requirements
|
||||
description: Use when adding or changing frontend UI text, locale support, translation JSON, the static i18n scanner, language selection, or native tray labels in this repository; keeps source messages, generated catalogs, translations, and runtime locale registration consistent.
|
||||
---
|
||||
|
||||
# I18n Requirements
|
||||
|
||||
## Source Messages
|
||||
|
||||
- Treat `zh-CN` as the only source locale.
|
||||
- Write user-visible frontend text as Chinese source literals in scanned files under `frontend/src/`.
|
||||
- Do not branch on locale or hard-code English, Japanese, Russian, or other translated UI text in components or state modules.
|
||||
- Let `frontend/plugins/static-i18n-plugin.js` replace source literals with runtime helpers. Do not hand-write generated message IDs in application code.
|
||||
- Keep internal matching tokens out of the catalog. Use a regex for Chinese protocol/error matching instead of a user-visible string literal when the text is not intended for display.
|
||||
- Do not place ordinary user-visible source messages under `frontend/src/i18n/`; the scanner excludes that directory. Native language names in `LOCALE_OPTIONS` are an intentional exception.
|
||||
|
||||
## Generated Catalogs
|
||||
|
||||
- Treat `frontend/src/i18n/generated/catalog.json` and the source-locale entries as scanner output. Do not manually edit catalog references or message IDs.
|
||||
- Run `npm run build` from `frontend/` after changing UI text. The build must run with `--scan` and update every locale file.
|
||||
- Preserve every placeholder exactly across locales, including `{0}`, `{1}`, newlines, and formula fragments such as `${1}`.
|
||||
- Provide a non-empty translation for every catalog key in every non-source locale. Do not rely on the Chinese fallback for completed locale support.
|
||||
|
||||
## Adding A Locale
|
||||
|
||||
Update all of these integration points together:
|
||||
|
||||
- `SUPPORTED_LOCALES` in `frontend/plugins/static-i18n-plugin.js`.
|
||||
- `SUPPORTED_LOCALES` and `LOCALE_OPTIONS` in `frontend/src/i18n/config.js`.
|
||||
- The locale JSON import, `localeMessages`, and primary-language mapping in `frontend/src/i18n/runtime.js`.
|
||||
- `frontend/src/i18n/locales/<locale>.json` with the complete catalog key set.
|
||||
- Native tray labels in `internal/app/runner.go`.
|
||||
|
||||
## Verification
|
||||
|
||||
After the scan build:
|
||||
|
||||
1. Confirm `npm run build` succeeds.
|
||||
2. Confirm every locale JSON has the same keys as `catalog.json`.
|
||||
3. Confirm non-source locale files contain no empty values.
|
||||
4. Confirm translated placeholders match the source entry placeholders.
|
||||
5. Run the build twice when scanner behavior changed and confirm generated files are stable.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "I18n Requirements"
|
||||
short_description: "Keep UI translations and generated catalogs in sync"
|
||||
default_prompt: "Use $i18n-requirements to update localized UI text safely."
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: prefix-cache-stability
|
||||
description: Use when changing prompt compilation, history replay, persisted conversation state, model request construction, or dynamic reminders in this repo; protects prefix-cache hit rate by keeping model-visible history append-only and dynamic attention scoped to the latest request.
|
||||
---
|
||||
|
||||
# Prefix Cache Stability
|
||||
|
||||
Use this skill before editing prompt, history replay, persisted conversation state, or provider request code.
|
||||
|
||||
## Hard Constraints
|
||||
|
||||
- Model-visible history is append-only. If a message was sent to the model and is meant to remain historical context, persist it and replay it at the same relative position.
|
||||
- Do not move previously sent model-visible messages to a new position in later requests.
|
||||
- Keep the largest stable prefix first: system prompt, imported replay, persisted user/request/tool history, then current-turn suffix context.
|
||||
- Truly dynamic attention is latest-only. Current state blocks, latest edit guards, and other volatile reminders should be appended near the end of the current request and should not become long-lived prefix content unless they are intentionally persisted as historical facts.
|
||||
- Persisted prompt context must be worded so it is safe as history. Avoid stale wording like "currently" unless the context is only latest-only.
|
||||
- Never optimize cache by dropping correctness-critical context.
|
||||
- Never remove, strip, reorder, or suppress historical `reasoning_content` replay merely to reduce repetitive thinking. Some providers need prior reasoning for valid continuation; optimize the latest tool guidance or current-turn prompt behavior instead.
|
||||
|
||||
## Implementation Pattern
|
||||
|
||||
1. Classify each prompt addition:
|
||||
- Stable system policy: belongs in the fixed system prompt.
|
||||
- Historical model-visible context: persist as replayable history.
|
||||
- Latest-only attention: append as current suffix, do not persist.
|
||||
2. For persisted context, store enough metadata to dedupe the same turn, usually `source` plus a content hash.
|
||||
3. Replay persisted context from history/projector, not by regenerating and inserting it into old positions.
|
||||
4. On provider retries or same-turn follow-up passes, do not duplicate an already persisted prompt context.
|
||||
5. Persisted conversation state must include replayable prompt context so a restarted conversation preserves the same prefix. In this repo, use `context.json.items` for replayable semantic history and `state.json` for mutable latest state.
|
||||
|
||||
## Verification
|
||||
|
||||
- Compare adjacent provider request artifacts or captured canonical request bodies and compute the longest common prefix.
|
||||
- Check final raw SSE usage fields before blaming local metrics. Some OpenAI-compatible providers do not return cached-token fields.
|
||||
- A healthy change should make old request prefixes stable while allowing only the newest suffix to vary.
|
||||
Reference in New Issue
Block a user