diff --git a/.gitignore b/.gitignore index 23c966c..08f7838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ claude-server.tar +.DS_Store dist .task bin @@ -8,8 +9,12 @@ logs.zip frontend/bindings dist node_modules +*.tsbuildinfo cursor-server.tar /cursor-server/target/ +/cursor-server/*.db +/cursor-server/*.db-shm +/cursor-server/*.db-wal server-node/cursor.tar server-go/cursor.tar server-go/log/ diff --git a/Cursor上下文与状态同步抓包分析.md b/Cursor上下文与状态同步抓包分析.md index 380ef65..f9d1775 100644 --- a/Cursor上下文与状态同步抓包分析.md +++ b/Cursor上下文与状态同步抓包分析.md @@ -47,8 +47,8 @@ BidiAppend(request_id, append_seqno, data) | ID | 作用域 | 用途 | | --- | --- | --- | | `conversation_id` | 跨 Turn | 持久会话、最新 checkpoint、子会话 | -| `request_id` | 一次 Run | 关联 RunSSE 与 BidiAppend | -| `run_id` | 一次执行 | 当前样本中常与 `request_id` 相同,但不应假设永远相同 | +| `request_id` | 一次具体执行/传输尝试 | 关联 RunSSE 与 BidiAppend;Cursor adapter 以它创建内部 RunId | +| `run_id` | Cursor 逻辑 Run 元数据 | 普通样本中常与 `request_id` 相同;队列/子代理恢复时可能跨新 request 复用,不能作为执行表主键 | | `append_seqno` | 单个 request | Bidi 上行排序与去重 | | KV `id` | 单个 request | 配对 KV request/result | | Exec `id` | 单个 request | 配对本地执行 request/result | @@ -79,6 +79,8 @@ BidiAppend(request_id, append_seqno, data) - `requested_model`、候选子 Agent 模型和模型覆盖; - 客户端能力位。 +`conversation_state` 的字段存在性不能用来判断是否已有历史。Cursor 在新对话中也会发送一个已分配但 roots 为空的 state;它表示空历史基线,首份 checkpoint 才写入 system root。只有 roots 非空的恢复历史才要求其中恰好存在一个 system prompt root。 + 主会话首轮模型为 `grok-4.6`,参数为 `effort=high`、`fast=true`。首轮引用的 rules 为 1,114 字节、skills 为 5,207 字节、MCP 为 28,089 字节;空 subagents 使用 SHA-256 空串地址: ```text @@ -812,7 +814,7 @@ LLM 本身不保存会话。Loop 引擎反复把当前完整上下文投射成 ```text Provider SSE → OpenAI Chat / Responses / Anthropic Adapter -→ Canonical ResponseEvent +→ 统一 ModelEvent → Loop State Machine → Cursor AgentServerMessage → Connect RunSSE @@ -825,13 +827,36 @@ start text_start / text_delta / text_end thinking_start / thinking_delta / thinking_end toolcall_start / toolcall_delta / toolcall_end -done(stop | length | toolUse | error | aborted) -error +done(stop | length | toolUse) +stream_error / cancellation ``` +其中 `error` 与 cancellation 是流错误和 Run 终态,不伪装成成功事件序列中的 `done(error/aborted)`;`length/incomplete` 也不是正常完成。 + 模型端点差异只留在 Adapter。Loop、Blob、checkpoint 和 Cursor transport 不依赖具体 provider。 -### 16.3 RunSSE 的线格式 +### 16.3 上游调用的最小知识边界 + +当前自然的数据流是: + +```text +selected revision +→ 纯 model/projection:canonical messages → typed history +→ PromptSpec + ModelSpec + typed history = ModelRequest +→ ModelInvocation(call_id + cancellation + ModelRequest) +→ 固定 Provider adapter 的 request projector +→ HTTP response headers → SSE decoder +→ ModelEvent +→ 严格 ModelCycle +``` + +`ModelRequest` 只保存可重放输入,不含 request id、时间、`model_call_id` 或 Cursor mode;调用 ID 和取消属于 `ModelInvocation`。每一轮都显式发送完整请求,不使用 `previous_response_id` 等服务端隐式会话作为上下文事实源。相同 PromptSpec、ModelSpec 和 selected revision 必须产生可比较的同一请求;下一 revision 只扩展旧历史前缀。 + +Provider adapter 只认识 typed text/image/assistant/call/result 和自己的 endpoint JSON,不读 SQLite,不发 Cursor protobuf,不构造 checkpoint,也不执行工具。Chat、Responses、Anthropic 分别保存并回传自己的 reasoning replay state;跨端点不解码、不伪造。usage 的累计语义由各 adapter 在内部消化,对公共 ModelCycle 只发一次端点本轮最终可信总量;第二个 Usage 是协议错误,不能静默覆盖前值。缺失时保持缺失。 + +同一个 CancellationToken 必须同时覆盖等待 HTTP 响应头和读取 SSE。若只在 SSE 建立后监听取消,新 Run 会无法及时中断仍卡在上游握手中的旧 Run。HTTP 非成功状态、裸 EOF、未闭合 content/tool block、`length/incomplete` 都是显式失败,不能 fallback 成正常 Done。 + +### 16.4 RunSSE 的线格式 `RunSSE` 虽然使用流式 HTTP,但正文不是浏览器式文本 `data: ...\n\n`。它是 Connect 流式二进制 envelope: @@ -866,30 +891,31 @@ Runtime tag 虽然是实时产生的,但首次追加后也立即成为不可 `model_call_id` 只是一次模型调用的关联 ID,不是 provider 前缀缓存条件。跨模型时正常构造新请求即可,不应为了复用 `model_call_id` 改写历史。 +PromptSpec 只在单个 Run 内冻结。新 Run 切换模型或模式时,如果 system prompt 内容发生变化,Cursor checkpoint 应以新的内容寻址 Blob 替换 system root,同时复用其余历史 message roots;不能因为旧 system root 文本不同而拒绝对话。该请求自然进入新的模型/Prompt 缓存域,不要求跨模型共享缓存。上游 ModelRequest 始终使用本 Run 的 PromptSpec,旧 system root 只用于恢复时验证历史结构,不进入 canonical messages。 + ### 17.2 Tool call/result 的完整性约束 -工具可以并行执行并乱序完成,但下一次模型调用不能看到悬空 tool call。内部状态应把每个调用和结果一对一关联: +工具可以并行执行并乱序完成,但下一次模型调用不能看到悬空 tool call。每个完成结果以一组相邻消息原子提交: ```text -ToolBatch -├─ slot 0: call0 ↔ result0 -├─ slot 1: call1 ↔ result1 -└─ slot 2: call2 ↔ result2 +assistant(call3) → tool(result3) +assistant(call1) → tool(result1) +assistant(call0) → tool(result0) ``` -只有当前模型产生的 Tool Batch 全部具有可投射结果后,才能构造下一轮 provider request。该约束保证相同 committed state 总能产生相同请求。 +这里的 pair 顺序就是真实 `completion_seq`,不为恢复原始 call 顺序而阻塞已完成工具。只有当前模型产生的 ToolRound 全部具有可投射结果后,公共 projector 才按 durable ToolRoundId 折叠成一个 assistant batch;assistant 内 calls 恢复 Provider 原始 index,随后的 result messages 保持 completion_seq。该约束保证相同 committed revision 总能产生相同请求。 抓包中的模型 transcript 采用 AI SDK/OpenAI Chat 风格: ```text assistant: [call0, call1, call2, call3] -tool: result0 tool: result1 -tool: result2 tool: result3 +tool: result2 +tool: result0 ``` -真实执行完成顺序为 `result1、result3、result2、result0`,但最终持久化顺序恢复成原始 call 顺序。这里的“一对一”是语义配对和原子投射约束,不要求所有 provider 都使用字面上的 `call0,result0,call1,result1` 排列;具体线格式由端点 Adapter 决定。 +这里的“一对一”是语义配对、相邻原子提交和整批完整性约束,不要求 result 与 call index 同序;具体线格式由端点 Adapter 决定。 ## 18. Tool 流事件与客户端占位卡片 @@ -932,7 +958,7 @@ tool name → execution delta/result mapper ``` -## 19. Checkpoint 的时机与单 Tool 回滚 +## 19. Checkpoint 的时机与 ToolRound 恢复 ### 19.1 Checkpoint 是可恢复提交,不只是 UI 快照 @@ -963,35 +989,52 @@ checkpoint pending=1 但抓包中的四工具样本没有把第一个完成结果单独写进 Turn。第一个 `tool_call_completed` 后发出的 checkpoint 仍只有 Thinking 和 Assistant 两个 Step;等四个工具全完成后,四个 Tool Step 才一起进入新 Turn。因此当前样本能恢复到“工具批次正在执行”,不能恢复到“其中某一个工具已经完成且结果已持久化”。 -### 19.3 服务端目标:单 Tool 粒度 +### 19.3 服务端目标:忠实实现 ToolRound 粒度 -为了避免崩溃后重复执行写文件、shell、MCP 等有副作用的工具,服务端实现应提高到单 Tool 粒度。每个 call 使用固定 slot,完成状态可以和其他工具交叉: +服务端不能以“减少副作用重放”为理由发明抓包中不存在的部分完成 checkpoint。正确提交点只有两个: ```text -slot[0] = call0 pending -slot[1] = call1 completed(result1) -slot[2] = call2 pending +Provider 完整结束为 tools +→ 原子保存 ToolRound assistant 与全部有序 calls +→ checkpoint(stable roots 不变,pending assistant = 1) +→ 执行工具 + +每个 Tool Result 到达 +→ 按真实完成顺序原子追加 assistant(call) → tool(result) +→ 中间结果不发布 checkpoint +→ 最后一个结果使 ToolRound settled +→ Blob SET/ACK +→ checkpoint(assistant batch + 全部 results 进入 stable roots,pending = 0) +→ 下一轮 LLM ``` -建议两个提交点: +SQLite 的 durable ToolRound 可以记录单 call 完成状态,用于进程内一致性和诊断;它不是客户端已经持有的恢复点。Cursor 自动恢复只以客户端下一次带回的 eligible checkpoint 为事实,因此 staged 状态中断后会重新执行整批工具。若未来要避免某类副作用重复执行,需要新的 wire 证据或客户端幂等键,不能把部分 ToolResult 塞进当前 checkpoint 语义。 + +### 19.4 当前版本复核:exchange 9005 与 Cursor.app + +当前 Cursor `3.16.17` 的完整 exchange `9005` 给出更精确的序列。frame `467/493/504/526/543/565/579/613/625/1643/1644/1645` 的 `(stable roots, pending)` 依次为: ```text -Tool 参数完整 -→ 持久化 Tool Intent -→ Blob 确认 -→ checkpoint(call=pending) -→ 才向客户端发 ExecServerMessage - -Tool Result 返回 -→ 服务端本地 SQLite 先记 working state -→ 生成 Result JSON Blob、Completed Tool Step Blob、新 Turn Blob -→ Blob 确认 -→ checkpoint(call=completed) +(31,0) → (31,1) → (35,0) +→ (35,1) → (39,0) +→ (39,1) → (43,0) +→ (43,1) → (48,0) +→ (48,1) → (49,0) → (49,0) ``` -Checkpoint 可以记录部分完成的 Tool Batch,但 LLM Projector 仍须等待整批 call/result 完整,不能把部分完成状态投射为下一轮模型请求。这样同时满足单 Tool 回滚与 LLM tool protocol 完整性。 +四个 ToolRound 的 settled checkpoint 都严格早于下一轮首个模型 interaction:`504 < 505`、`543 < 544`、`579 < 580`、`625 < 626`。这证明 settled checkpoint 是继续 Loop 前的 client-state barrier,但不代表存在 wire checkpoint ACK;protobuf 只有 Blob SET 的 `set_blob_result(id)`。 -## 20. Blob 确认、重试与 Checkpoint 送达 +最终新 Blob 位于 RunSSE frame `1638..1641`,分别是 thinking Step、assistant Step、更新后的 Turn wrapper 和 assistant root JSON;同一 request 的 Bidi `id=122..125` 都返回成功 SET result。RunSSE 随后是 frame `1642` 的 `turn_ended`,再是 `1643..1645` 的 staged、settled、相同 settled 重发。抓包数据库没有保存两条独立 HTTP 流中每个 frame 的统一时间戳,因此不能仅凭 frame index 声称 ACK 与 `turn_ended` 的跨流先后;能够确认的是四个 ACK 均在 RunSSE 结束前到达。实现采用更强且确定的安全屏障:这四个新增 Blob 全部 ACK 后才解除 final state barrier,并发送 `turn_ended`/checkpoint。三份终局 checkpoint 的最后一个 Turn BlobID 相同,因此终局 presentation delta 只能消费一次。 + +Cursor.app 的运行代码把 `turn_ended` 前的 checkpoint 标为 `eligible`,之后标为 `ineligible_terminal_turn`。断流恢复会带回最新 eligible state 并改用 `resume_action`;若该 state 含完整 pending assistant,服务端恢复 ToolRound 并先执行工具,不得再次调用 LLM。`pendingToolCallStartedAtMs`、未知 reasoning signature 和旧 Step 时间都必须原样保留。 + +这个标记发生在客户端消费帧时,因此 `turn_ended` 与第一份终局 checkpoint 之间存在一个很窄的断流窗口:客户端已见 `turn_ended`,但还没有见 `ineligible_terminal_turn`,此时仍可能用上一份 eligible checkpoint 重试。这不改变协议顺序,也不构成 checkpoint ACK 的理由;服务端只能以下一次 `run_request` 实际带回的 state 为准。 + +stable root JSON 的 wire `id` 也不是内部身份:同一 exchange 的四个不同 assistant 工具批次都使用字符串 `"1"`,tool result root 的 `id` 等于 `toolCallId`。内部 MessageId/ToolRoundId 必须从 BlobID、序位和 durable round 产生,不能按 wire id 合并。 + +checkpoint 的非 canonical 元数据并非全部冻结。exchange `9005` 的 `read_paths` 随成功 Read 从 11 项增加到 12 项;Todo/Plan/UpdateCurrentStep 也由 typed completion 或 canonical messages 确定性推进。`token_details.used_tokens` 是当前一次完整模型上下文的占用,不等于整个 Turn 的累计 provider input。服务端以最后一次 provider 调用返回的 `input_tokens + output_tokens` 更新它;`max_tokens` 来自 Cursor 既有 checkpoint 或请求模型的 `context` 参数。breakdown 的分类值是展示估算,不冒充 provider usage,但其 token 合计必须严格等于权威 `used_tokens`。 + +## 20. Blob 确认与 Checkpoint 送达 ### 20.1 Blob SET 的确认语义 @@ -1012,7 +1055,7 @@ message SetBlobResult { `SetBlobResult {}` 表示成功,带 `error` 表示失败。当前完整 exchange 372 中 154 个 `set_blob_args` 对应 154 个无错误 `set_blob_result`,成功样本没有发现缺失确认。 -BlobID 是内容哈希,所以重发同一个 `blob_id + blob_data` 是幂等操作。KV `id` 用于匹配一次尝试;同一 Blob 可以在超时后以新的 KV `id` 重试,迟到或重复结果按 BlobID 合并为已确认状态。 +BlobID 是内容哈希,因此相同内容天然得到相同 ID;但当前会话协议仍把每次 SET 表达为唯一 KV `id` 对应唯一 `set_blob_result`。实现不在超时后生成新 KV id 重试同一 Blob,也不合并迟到尝试。 服务端应区分: @@ -1021,7 +1064,7 @@ Working State:结果已经到达服务端,但客户端 Blob 是否持久化 Committed Checkpoint:只引用已经得到成功确认的 Blob ``` -如果某个确认暂时没有返回,不应把它立即判为写入失败,也不能发布引用该 Blob 的 checkpoint。继续保持 working state、重试内容寻址写入,并保留上一个 committed checkpoint。 +如果确认在配置的等待期限内没有返回,当前 checkpoint job 失败,并进入该 Run 的 typed Error/取消生命周期;绝不能发布引用该 Blob 的 checkpoint,也不保存跨 RunSSE working/outbox 等待以后续传。 候选 checkpoint 不必等待与它无关的所有 Blob,只需要满足: @@ -1034,7 +1077,7 @@ Checkpoint C 可以发布 抓包中 `turn_ended` 后可能先发送一个过渡 checkpoint,随后相同的稳定最终 checkpoint 连续发送两到三次,最后才发送 Connect EndStream。exchange 372 的尾部是 `pending=1` 的过渡 checkpoint,接着两次 `roots=59、pending=0` 的相同最终 checkpoint。正常未断流的 RunSSE 是有序可靠字节流:客户端如果收到了后面的 EndStream,就一定先收到了位于它之前的完整 checkpoint 帧。因此在正常完成路径上,可以断言最终 checkpoint 已经通过 RunSSE 送达客户端,不需要额外 checkpoint ACK 才结束。 -需要严格区分“传输送达”和“应用层确认”:协议没有单独的 `checkpoint_ack`。重复帧说明客户端必须幂等接受相同 checkpoint,也增强了尾部发送的稳健性,但仅凭重复本身不能证明断线之后客户端已经持久化了哪一份状态。断流时应以客户端下一次 `run_request` 实际带回的 checkpoint 为恢复事实,并从服务端 SQLite working/outbox 状态继续同步。 +需要严格区分“传输送达”和“应用层确认”:协议没有单独的 `checkpoint_ack`。重复帧说明客户端必须幂等接受相同 checkpoint,也增强了尾部发送的稳健性,但仅凭重复本身不能证明断线之后客户端已经持久化了哪一份状态。断流时只以客户端下一次 `run_request` 实际带回的 checkpoint 为恢复事实;服务端不重放旧 RunSSE 帧,也不从不存在的 outbox 猜测客户端状态。 ## 21. Usage 与 Turn 收口 @@ -1058,7 +1101,32 @@ message TurnEndedUpdate { } ``` -最小实现不需要生成 `token_delta`。权威 usage 只信任各 LLM Adapter 从 provider 最终事件读取到的值:不根据文本 delta 自己 tokenize,不推算 cache token,也不推算 reasoning token。provider 未返回的可选字段保持缺失。 +exchange `9005` 明确包含 378 个 `token_delta`,合计 5022;它们穿插在 thinking、text、tool/exec 流事件之间。该流最终的 `turn_ended` 是 `input=388564、output=5870、cache_read=346112、reasoning=2736`,因此 `token_delta` 既不是 Turn input,也不是最终 output 的逐块拆分。checkpoint 的 `used_tokens` 同时从 35302 前进到 44492,`max_tokens=256000`。三者职责必须分开: + +```text +token_delta 生成期间供 Cursor UI 增量刷新 +checkpoint.token_details 当前上下文占用/上限 +turn_ended 整个 Run 的 provider 权威累计量 +``` + +通用 provider 端点通常只在流末给出可信 token 数,无法复现 Cursor 私有服务逐 chunk 的估算。当前实现因此在每次模型调用的 terminal Usage 到达时发送一个 `token_delta(output_tokens)`,不根据文本、thinking 或工具参数自行 tokenize;随后用该次调用的 `input_tokens + output_tokens` 更新 checkpoint。这样 UI 会更新,数值仍全部来自 provider,只是刷新粒度为一次模型调用而非每个 chunk。 + +同一抓包还证明 breakdown 不是把权威总量按比例平摊。六个非对话分类在所有 checkpoint 中保持固定: + +| id | label | character_count | estimated_tokens | +| --- | --- | ---: | ---: | +| `system_prompt` | System prompt | 3372 | 920 | +| `tools` | Tool definitions | 40174 | 10965 | +| `rules` | Rules | 7684 | 2097 | +| `skills` | Skills | 6305 | 1720 | +| `mcp` | MCP & dynamic tools | 11916 | 3252 | +| `subagents` | Subagent definitions | 3413 | 931 | + +`summarized_conversation` 在该样本为零;`conversation` 随消息增长,并取得 `used_tokens` 扣除其他分类估算后的剩余值。例如最终 `used_tokens=44492`,其他分类合计 19885,故 conversation 恰为 24607。实现遵守同一结构:按实际投射内容分别统计 UTF-16 `character_count`;system prompt、静态工具、rules、skills、动态 MCP、subagent 和已有 summary 独立估算;普通 user/assistant/tool 内容进入 conversation;最后由 conversation 吸收权威总量的余数。若分类估算异常超过权威总量,则只按最大余数法压缩非 conversation 分类,保证八类非负且总和始终精确。 + +分类边界来自实际数据流而不是工具名猜测:静态 prompt 和 ToolDefinition 由当前 PromptSpec 提供,动态 MCP ToolDefinition 进入 `mcp`,只有 `origin=runtime` 的消息才解析其中明确的 ``、``、``、`` 区段;用户正文即使含相似文本也仍属于 conversation。分类估算器按 Cursor/JavaScript 的 UTF-16 字符口径统计,ASCII 使用每字符约 `0.273` token、非 ASCII 使用每 UTF-16 code unit 约 `0.55` token;这只决定分类分布,不改变 provider 权威总量。当前抓包的 `prompt_context_usage_tree` 为空,因此只生成已被证实的八类 breakdown,不编造 tree/node 或 snapshot Blob。 + +权威 usage 只信任各 LLM Adapter 从 provider 最终事件读取到的值:不推算 cache token,也不推算 reasoning token。adapter 可读取多个端点累计快照,但必须先汇总并只交付一个 terminal total;公共状态机收到重复 Usage 直接失败。provider 未返回的可选字段保持缺失。 `turn_ended` 表达整个 Cursor Run/Turn 的汇总,而不是一次 provider 调用。exchange 372 的最终值为: @@ -1072,14 +1140,15 @@ reasoning_tokens = 5004 `input_tokens` 已明显超过单次 256K 上下文,证明它是同一 Run 内多次 LLM 调用的累计值。实现时只对 provider 返回的可信调用总量求和,然后在最终 `turn_ended` 一次汇报。 -最终收口顺序建议为: +当前实现的固定收口顺序为: ```text 最终 provider done(stop) → 最终 text_delta / step_completed -→ SET 最终 assistant JSON / Step / Turn Blob +→ 构造并 SET 最终 assistant JSON / Step / Turn Blob +→ 等待这些新增 Blob 的配对 SET ACK → turn_ended(整轮可信 usage 总量) -→ 等待所引用 Blob 成功确认并发送过渡 checkpoint(如果需要) +→ 发送 pending=1 的过渡 checkpoint → final checkpoint(pending=0) → 幂等重复 final checkpoint → Connect EndStream @@ -1186,7 +1255,7 @@ request_id → 一次 Run/传输尝试 RunSSE → request_id 的下行通道 ``` -当前抓包没有 abort/error 尾部,因此异常路径只能作为实现约束:用户打断时取消旧 provider 和尚未继续的工具,提交最后安全 checkpoint,以 canceled/aborted EndStream 结束旧 Run;不能伪造正常成功的 `turn_ended`。单纯的 RunSSE 断线也不等于 Turn 已结束,恢复事实应来自客户端下一次带回的 checkpoint。 +当前抓包没有 abort/error 尾部,因此异常路径只能作为实现约束:用户打断时取消旧 provider 和尚未继续的工具,保留此前已经发布的最后安全 checkpoint,不再为取消制造新 checkpoint,并以 canceled/aborted EndStream 结束旧 Run;不能伪造正常成功的 `turn_ended`。单纯的 RunSSE 断线也不等于 Turn 已结束,恢复事实应来自客户端下一次带回的 checkpoint。 ## 23. Runtime tag:运行时产生、严格追加一次 @@ -1291,54 +1360,80 @@ CreatePlan 参数/成功结果 → 更新 current_plan 投影 ## 25. 多模式 Prompt 与 Tool 资产 -`main` 分支已有完整的静态 prompt、模式工具定义和 reminder 模板。已将其中 18 个语言无关资产原样复制到当前分支的 `prompt/`: +当前静态资产已经收敛到 `prompt/cursor/`。完整工具 schema 只有根目录一个 catalog,各模式只保存有序 manifest;共享 schema 不在不同模式间复制: ```text -prompt/ -├─ common_prefix.md -├─ agent/ prompt.md + tools.json -├─ ask/ prompt.md + tools.json -├─ plan/ prompt.md + tools.json + system_reminder.txt -├─ debug/ prompt.md + tools.json + initial/continuing reminder -├─ multitask/ prompt.md + tools.json -├─ subagent/ prompt.md + tools.json -├─ compaction/ prompt.md -└─ commit/ prompt.md +prompt/cursor/ +├─ tools.json # 完整 schema catalog + Task.subagent variant +├─ modes/ # 每个模式的有序工具 manifest +│ ├─ agent.json +│ ├─ ask.json +│ ├─ plan.json +│ ├─ debug.json +│ ├─ multitask.json +│ ├─ subagent.json +│ └─ compaction.json +├─ agent/ +│ ├─ prompt.md # 静态 system prompt +│ └─ runtime.md # 本模式的 user-role runtime 模板 +├─ ask/{prompt.md,runtime.md} +├─ plan/{prompt.md,runtime.md} +├─ debug/{prompt.md,runtime.md} +├─ multitask/{prompt.md,runtime.md} +├─ subagent/{prompt.md,runtime.md} +└─ compaction/{prompt.md,runtime.md} ``` 工具数量: | 模式 | Tools | | --- | ---: | -| Agent | 21 | -| Ask | 19 | -| Plan | 17 | -| Debug | 19 | -| Multitask | 21 | -| Subagent | 4 | +| Agent | 20 | +| Ask | 15 | +| Plan | 13 | +| Debug | 15 | +| Multitask | 17 | +| Subagent | 20 | +| Compaction | 0 | Rust 服务端应在启动时加载、解析并校验这些资产: ```rust struct ModeAssets { - system_prompt: Arc, + prompt: Arc, + runtime: Arc, tools: Arc<[ToolDefinition]>, - runtime_reminders: Arc<[RuntimeTemplate]>, } ``` -模式映射: +模式映射和消费规则: ```text -AGENT_MODE_AGENT → common prefix + agent prompt/tools -AGENT_MODE_ASK → common prefix + ask prompt/tools -AGENT_MODE_PLAN → common prefix + plan prompt/tools/reminder -AGENT_MODE_DEBUG → debug prompt/tools + initial/continuing reminder -AGENT_MODE_MULTITASK → common prefix + multitask prompt/tools -子 Agent conversation → subagent prompt +受限 tools +UserMessage.mode → 当前 Run 的 prompt.md + runtime.md + tools manifest +conversation_state.mode → 仅给没有 UserMessage.mode 的后台完成等动作提供模式 +subagent_type_name → 明确选择 subagent 资产 ``` -静态 prompt 和工具目录按 mode 选择;运行时 reminder 必须遵守第 23 节的 exactly-once append,不能因为每轮加载同一个模板而重复加入 messages。模式或工具集合切换可以形成新的 provider cache 边界,但已提交的模型 messages 仍然保持严格只追加。 +不能用恢复出来的 `conversation_state.mode` 覆盖当前 `UserMessage.mode`;否则 UI 刚切换 Ask/Plan/Debug/Multitask 时,本轮仍会用旧模式的 prompt 和 tools。也不使用目录别名或缺失资产 fallback:每个可用模式都必须显式维护自己的 `prompt.md` 和 `runtime.md`,缺失或模板占位符非法时服务启动失败。 + +`runtime.md` 是一次性渲染的 Markdown 模板。通用占位符为: + +```text +{{REQUEST_CONTEXT}} +{{OPEN_FILES}} +{{SELECTED_CONTEXT}} +{{ACTION_CONTEXT}} +{{TIMESTAMP}} +{{USER_QUERY}} +``` + +Debug 额外使用 `{{DEBUG_SERVER_ENDPOINT}}`、`{{DEBUG_LOG_PATH}}` 和 `{{DEBUG_SESSION_ID}}`。模板必须包含 `TIMESTAMP` 和 `USER_QUERY`;其他区块完全取决于当前 RunRequest:有数据就加入,没有就渲染为空,不从历史猜测,不制造空标签,不使用默认内容托底。渲染是单遍替换,用户文本中恰好出现 `{{...}}` 不会被当成第二层模板执行。 + +当前请求的 `RequestContextRulesPart`、`RequestContextSkillsPart`、`RequestContextSubagentsPart` 和 `RequestContextMcpsPart` 先按 BlobID 取回,校验 hash 和 byte length,再按明确 protobuf 类型解码;缺 Blob、长度不符或类型错误都是协议错误,不能忽略。公共请求上下文按抓包顺序编译为 `user_info → git_status → agent_transcripts → rules/skills/subagents/MCP`,后四类同样只在当前请求携带时出现。 + +每个携带用户语义的 RunRequest 最终只产生一条 `role=user, origin=runtime` 的 canonical message:模式 reminder、当前请求上下文、时间、`user_query` 和图片都在同一条 message 中。原始 `UserMessage.text` 不再另行投射,避免同一用户问题出现两次。该 message 以 `run-request:{request_id}` 作为 runtime event identity,在 Start 或 Resume 进入 provider 前与 messages 一起持久化;恢复和 provider 重试只能重放已持久化文本,不能重新取时间或重新渲染。 + +静态 prompt 和工具目录按 mode 选择;运行时 message 必须遵守第 23 节的 exactly-once append。模式或工具集合切换可以形成新的 provider cache 边界,但已提交的模型 messages 仍然保持严格只追加。 @@ -1355,7 +1450,7 @@ BidiAppend.run_request → 投射 RunSSE 流事件 → 客户端执行 Tool → BidiAppend 返回结果 -→ 单 Tool checkpoint +→ ToolRound settled checkpoint → 下一轮 LLM → turn_ended → final checkpoint @@ -1367,7 +1462,7 @@ Runtime event 恰好追加一个 runtime-origin/user-role message。 Provider 重试只重放,不能重复追加任何 message。 Tool call/result 必须一对一完整,不能向 LLM 投射悬空调用。 Tool 可以乱序完成,但下一轮 LLM 必须等待整个 Tool Batch 完整。 -每个 Tool 单独持久化和 checkpoint,避免有副作用工具被重复执行。 +每个 Tool Result 单独原子持久化,但只在整个 ToolRound staged/settled 边界发布 checkpoint。 Blob 先确认,checkpoint 后发布。 turn_ended、final checkpoint、EndStream 是三个独立边界。 Usage 只信任 provider,最终按整个 Turn 汇总。 @@ -1484,29 +1579,28 @@ is_expected = false 成功路径: ```text -业务消息 -→ Blob SET / ACK barrier -→ turn_ended + final checkpoint +最终 assistant revision 提交 +→ staged/settled 所需 Blob SET / ACK barrier +→ turn_ended +→ staged pending=1 +→ settled pending=0 +→ 幂等重发同一 settled → EndStream {} → 关闭 RunSSE 输出 ``` -真实抓包的客户端可见尾序列是 `turn_ended → 重复 final checkpoint → EndStream {}`。`main` 分支现有 Go 实现是 `Blob ACK → checkpoint → turn_ended → EndStream {}`;两者的最终语义相同,但 Rust 的协议兼容测试应固定所采用的客户端可见顺序。 +真实抓包稳定呈现 `turn_ended → staged → settled → settled 重发 → EndStream {}`。Cursor.app 将 `turn_ended` 之前的 checkpoint 视为 eligible,将其后的终局快照视为 `ineligible_terminal_turn`;这些顺序不能因“最终语义相同”而交换。 Provider 失败路径: ```text 停止 provider -→ 保存已经收到并确认的部分 assistant 输出 → 保存 provider 已汇报的 usage 与失败元数据 -→ 从当前已提交 messages 构造 checkpoint -→ Blob SET / ACK barrier -→ 发布 checkpoint → Error EndStream → 关闭 RunSSE 输出 ``` -失败路径不发送 `turn_ended`,不发送错误 `TextDelta`,也不把错误字符串追加为 assistant message。已经作为正常 provider delta 发出的部分内容可以保留;错误本身只存在于 run 元数据和 Connect error 中。若在 checkpoint Blob 同步失败时采用超时策略,可以跳过未获确认的 checkpoint,但仍必须发送 Error EndStream,不能让流永久悬挂。 +失败路径不发送 `turn_ended`,不发送错误 `TextDelta`,不把错误字符串或半截 assistant 追加进 canonical messages,也不伪造新的成功 checkpoint;失败前已经发布的 initial/settled checkpoint 仍然有效。已经发送到 UI 的 partial text/thinking 只作为诊断展示;错误本身进入 Run 元数据和 Connect error。checkpoint Blob 构造或 ACK 失败同样直接进入 Error 生命周期。 用户取消或新 Run 打断旧 Run: @@ -1518,7 +1612,7 @@ Provider 失败路径: → 关闭旧 RunSSE 输出 ``` -取消不发送 `turn_ended`,也不发布一个代表成功完成的新 checkpoint。Cursor 对 Connect `canceled` 有专门处理,不应将它显示为普通错误。已在更早的单 Tool checkpoint 中确认的副作用和消息保持有效;未完成工具不能投射进下一轮 LLM。 +取消不发送 `turn_ended`,也不发布一个代表成功完成的新 checkpoint。Cursor 对 Connect `canceled` 有专门处理,不应将它显示为普通错误。此前已经发布的 settled ToolRound checkpoint 保持有效;尚未 settled 的工具批次不能投射进下一轮 LLM。 ### 26.5 统一终结不变量 @@ -1591,13 +1685,13 @@ RunRegistry 下发 Exec 前在内存中建立 `id → call_id`,客户端结果到达时用 `message.id` O(1) 查找,不查 SQLite。数字 ID 在整个 request 内单调递增,条目在 result/exit 到达后标记为 `ResultReceived`,在随后的 `stream_close` 到达时删除。Run 取消或失败时对仍为 `Running` 的 ID 发送 abort,然后清空全部条目。 -这个映射是运行期协议状态,不是上下文事实源。SQLite 只保存已经关联成功的 `run_tool_results` 和 checkpoint,不参与每个 Shell 流片段的实时查找。 +这个映射是运行期协议状态,不是上下文事实源。SQLite 只在 durable `tool_round_calls` 中保存已经关联成功的 ToolResult,不参与每个 Shell 流片段的实时查找;checkpoint 也不是 SQLite 中的第二份会话状态。 -客户端会在 result/exit 之后紧接着发送 `stream_close`。因此 `run_tool_results` 的持久化不能使用“事务内先 SELECT completion_seq,再将 deferred transaction 升级为写事务”的方式,它会和 Bidi `append_seqno` 的并发更新产生 `SQLITE_BUSY_SNAPSHOT`。completion_seq 的计算和 ToolResult 插入必须合并为单条原子 `INSERT ... SELECT`。 +客户端会在 result/exit 之后紧接着发送 `stream_close`。ToolResult 的 completion_seq、call 状态、assistant/result message pair、ToolRound version 和新 revision 必须在同一个 immediate transaction 中推进,不能先读序号再把 deferred transaction 升级为写事务,否则会留下竞态或 `SQLITE_BUSY_SNAPSHOT`。 ### 26.8 ToolResult 向 LLM 的字符串投射 -Canonical `ToolResult.output` 允许保存任意 JSON Value,因为 Todo/Plan fold、checkpoint 和调试都需要保留工具结果的结构。但是投射到 LLM 请求时,ToolResult content 必须始终是字符串,不能把 JSON object、array、number、boolean 或 null 直接放入 message content。这是所有 provider adapter 的共同输入不变量,不应由 OpenAI Chat、Responses 或 Anthropic 各自补救。 +Canonical `ToolResult.content` 本身就是字符串。adapter 在 typed Cursor 结果进入核心之前只做一次规范化:文本原样保存,结构化结果用确定性的 JSON 序列化保存。OpenAI Chat、Responses 和 Anthropic 因而都读取同一个 String,不在各端点重复猜测 JSON 类型。 已观测的失败是 `TodoWrite` 将对象结果持久化后,projector 直接生成: @@ -1620,12 +1714,13 @@ OpenAI Chat 因此拒绝 `messages[7]`,报错 `content should be a string or a 统一规则: ```text -output 是 JSON string → 直接使用原字符串,不二次加引号 -output 是其他 JSON 类型 → serde_json::to_string(output) -最终 ProviderMessage.content → 始终 Value::String +typed terminal result +→ Cursor adapter 生成 String ToolResult.content +→ SQLite/canonical message 原样保存该 String +→ Provider adapter 按本端点的 tool-result 字段放入同一个 String ``` -字符串化只发生在 `CanonicalMessage → ProviderMessage` 边界;SQLite、Blob 和 derived state 仍保留原始结构化 JSON。这样既满足 LLM 端点约束,又不破坏幂等状态投影。 +Todo/Plan/UpdateCurrentStep 等派生状态需要结构时,从已知工具的字符串 content 严格解析自己的 JSON schema;解析失败是协议错误或表示该工具没有可派生状态,不能把 canonical 类型重新放宽成任意 Value。这样 core 与 Provider 都不需要知道 Cursor protobuf。 ### 26.9 Thinking 历史的端点投射 @@ -1640,12 +1735,13 @@ The `reasoning_content` in the thinking mode must be passed back to the API. 公共投射必须保持中性结构: ```text -ProviderMessage -├─ content = assistant text -└─ thinking = assistant thinking +ProjectedMessage::Assistant +├─ text = 可展示 assistant text +├─ thinking = 可展示 thinking summary +└─ replay_state = 端点产生的不透明续传状态 ``` -具体 provider adapter 再负责端点字段映射。OpenAI Chat 必须生成: +具体 provider adapter 再负责端点字段映射。OpenAI Chat 只有在 replay state 的 `provider_kind=openai_chat` 且其中确实包含 `reasoning_content` 时才生成: ```json { @@ -1656,10 +1752,9 @@ ProviderMessage } ``` -DeepSeek 官方文档进一步明确了这里不是“字段存在即可”的校验: +DeepSeek 的 Chat 兼容端点进一步明确了这里不是“字段存在即可”的校验: -- 未调用工具的 assistant thinking,在后续请求中可以不回传;即使回传也会被忽略。 -- 只要 assistant 调用了工具,该次模型响应的 `reasoning_content` 就必须完整、原样参与后续请求。 +- 只要 assistant 调用了工具,该次模型响应的 `reasoning_content` 就必须完整参与后续请求。 - 官方示例直接追加完整的 `response.choices[0].message`,即同一条 assistant message 同时包含 `content`、`reasoning_content` 和该次响应的全部 `tool_calls`。 - 用 `reasoning_content: ""` 给拆分出的 assistant tool-call message 补字段不是正确修复;它仍然丢失了原始思维内容。 @@ -1670,7 +1765,7 @@ DeepSeek 官方文档进一步明确了这里不是“字段存在即可”的 ```text Cursor 持久化与 checkpoint 视图 assistant(call 1) → tool result 1 → assistant(call 2) → tool result 2 - 单工具完成、单工具 checkpoint + 单结果原子提交;ToolRound 完整后 settled checkpoint LLM provider 请求视图 assistant( @@ -1685,11 +1780,12 @@ assistant( 服务端继续按已完成工具保存 1:1 pair,因此中断时不会把尚未得到结果的 tool call 投射给下一次 LLM。每条 canonical assistant tool message 额外保存: ```text -model_call_id 同一次 provider 响应的分组键 +model_call_id 同一次 provider 调用的 UI/观测关联值 tool.index provider 返回的原始 tool-call 顺序 +tool_round_id durable assistant/result 分组身份 ``` -公共 projector 在编译模型请求时,按 `model_call_id` 合并同一响应的 assistant pair,取唯一的非空 `text` 和完整 `thinking`,按 `tool.index` 恢复全部 tool calls,再按同一顺序投射 ToolResult。这样 SQLite/Blob 与 Cursor 仍保留单工具粒度,而 OpenAI Chat 端点看到的是其要求的原始 assistant 响应形状。 +公共 projector 以 durable `tool_round_id` 合并同一响应的 assistant pair,取唯一的非空 `text`、完整 `thinking` 和 provider replay state,按 `tool.index` 恢复全部 tool calls,再按真实 `completion_seq` 投射 ToolResult。`model_call_id` 只用于 UI/调用关联,不承担持久分组身份。这样 SQLite 保留真实完成顺序,而 OpenAI Chat 端点看到的是其要求的原始 assistant 响应形状。 关键不变量: @@ -1701,9 +1797,9 @@ ToolBatch 未完整时不发起下一轮 LLM 请求 完成后的 provider messages 中不存在悬空 tool_calls ``` -普通模型从未返回 thinking 时,请求形状不变。OpenAI Responses 和 Anthropic 不能直接复用 `reasoning_content` 字段,应由各自 adapter 按端点原生结构处理;公共层不将 thinking 降级为普通文本。 +普通模型从未返回 replay state 时,请求形状不变。OpenAI Responses 要回传完整 reasoning output items/encrypted content,Anthropic 要回传完整 thinking blocks/signatures;两者都不能复用 `reasoning_content` 字段。公共层不将可展示 thinking 冒充任一端点的续传状态,跨 Provider 时也不解码其他端点的 capsule。 -旧版本已写入的 assistant tool pair 没有 `model_call_id` 和 `tool.index`,无法无歧义恢复原始 provider 响应。服务端不猜测旧分组;验证本修复应新建对话。新数据不需要额外迁移。 +当前实现不兼容旧 schema 或猜测缺失分组;初始 schema 直接保存 ToolRoundId、call index、completion sequence 和 replay state。 ### 26.10 Tool 完成事件与 UI 生命周期 @@ -1736,7 +1832,7 @@ ToolCallCompletedUpdate - `ExecClientMessage.message = None` 不是完成结果,只表示尚无载荷;必须保持 Pending,随后等待 typed result、Shell exit、throw 或异常 stream close。 - `PendingExecRegistry` 在下发 Exec 时保存完整 `ToolCall`、真实 `started_at_ms` 和 Shell 流缓冲;数字 ID 只在当前 Run 内用于关联。 -- Shell、Delete、Grep、Ls、ReadMcpResource、WriteShellStdin 等可直接复用上行 typed result;Read、Write、Diagnostics、MCP、Subagent、PiEdit 按 Cursor ToolCall 所需结果类型做无损或语义等价转换。 +- Shell、Delete、Grep、ReadMcpResource 等可直接复用上行 typed result;Read、Write、Diagnostics、MCP、Subagent 与编辑工具按 Cursor ToolCall 所需结果类型做无损或语义等价转换。 - `TodoWrite` 这类服务端本地工具必须构造明确 typed success。`ExecClientThrow` 不是工具结果,直接进入统一 Error 生命周期,不伪造成某个 typed result。 - Canonical `ToolResult` 继续用于 LLM 和持久化;不能用它替代 UI 所需的 typed protobuf result。 - 成败不能通过完整 protobuf `Debug` 字符串搜索 `Error`、`Failure` 等单词判断。成功写入的文件内容可能恰好包含这些词,从而产生假失败。已知 oneof 必须按具体 success/error variant 判定。 @@ -1750,50 +1846,50 @@ ToolCallStarted(args, started_at_ms) → ExecClientMessage(id, typed result) / Shell exit ├→ take(id) 消费 PendingExec 的唯一所有权 └→ 同时生成 Canonical ToolResult 与完整 typed ToolCall -→ Blob 存储确认与单 Tool checkpoint → ToolCallCompleted(args + typed result + timestamps) -→ ToolBatch 全部完整后进入下一轮 LLM +→ 最后一个结果提交后构造 ToolRound settled Blob/Turn +→ Blob ACK + settled checkpoint +→ 下一轮 LLM ``` -客户端通常在 typed result 后立即发送 `stream_close`。终态 result 已经消费 PendingExec,因此 close 只是幂等尾包;不能再维护额外的 Finished/Closed 状态。`ToolCallCompleted` 必须在该工具的消息与 checkpoint 已经提交后发布。 +客户端通常在 typed result 后立即发送 `stream_close`。终态 result 已经消费 PendingExec,因此 close 只是幂等尾包;不能再维护额外的 Finished/Closed 状态。`ToolCallCompleted` 与 staged checkpoint 没有伪全局顺序;硬约束是 typed result 对应的 canonical 消息已经提交,且整个 ToolRound 的 settled checkpoint 先于下一轮模型 interaction。 ### 26.11 Tool completion 的模块边界 -首次实现虽然修正了协议,但将 pending registry、结果通道和 typed protobuf 转换同时塞进了 `run/tool_batch.rs`、`cursor/exec.rs` 与 `cursor/interaction.rs`,使运行期关联、Exec 解析、UI 投射互相穿插。整理后的职责为: +整理后的职责为: ```text -cursor/pending.rs -└─ id → ToolCall/timing/stream buffer;存在即 Running,take 即终态 +cursor/tools/runtime.rs +└─ 当前 Cursor Run 的 wire_id → PendingExec/PendingInteraction 与完成墓碑 -cursor/tools.rs -└─ 工具批次的 Cursor step index、唯一 transport 和本地立即完成工具 +cursor/tools/dispatch/ +└─ 完整 ToolCall → 唯一 Exec/Interaction/Local transport -cursor/exec.rs -└─ 解析 ExecClientMessage/ShellStream,产生 ToolCompletion +cursor/tools/codec/{request,response}.rs +└─ ExecServerMessage 编码与 ExecClientMessage/ShellStream 解码 -cursor/tool_result.rs -├─ ToolCompletion 与结果 channel -├─ typed Exec/Interaction result → canonical 字符串结果 + typed ToolCall -└─ 本地 TodoWrite/CommunicateUpdate 的明确终态 +cursor/tools/result/ +└─ typed terminal result → String ToolResult + typed ToolCall -cursor/interaction.rs -└─ text/thinking/tool started/completed/usage 的事件外壳与 args 渲染 +cursor/interaction/ +└─ 模型流、InteractionQuery 和 typed ToolCall UI 渲染 -run/loop_engine.rs -└─ 只决定何时持久化、checkpoint、发布 completion 和进入下一轮 +run/tool_round.rs +└─ 只提交 canonical call/result、等待整批完成与 client state barrier + +cursor/checkpoint/worker.rs +└─ 串行构造 staged/settled/final Blob 图并发布 checkpoint ``` -原 `run/tool_batch.rs` 和 `model::ToolBatch` 均已删除。Loop 已经持有有序 `ordered_calls`,只需一个 `completed call_id` 集合判断 barrier;再复制一份 calls/results 容器没有增加信息。Cursor 数字 ID、protobuf typed result 和 UI 卡片状态也不再伪装成 Loop 领域状态。 - -`ToolCompletion` 对 Loop 是一个需要延迟发布的不透明完成信封:Loop 读取其中的 canonical `ToolResult` 完成消息和 checkpoint,Cursor adapter 读取 presentation payload 生成 UI typed result。Loop 不匹配 protobuf oneof,也不决定某种工具在 Cursor UI 中如何展示。 +`ToolCompletion` 不越过 client boundary。CursorSession 读取其中的 String ToolResult 发送通用 `ClientCommand::ToolResult`,同时保留 typed presentation,等核心回送对应 `StateCommitted` 后发布 UI completion。Loop 从未持有 protobuf oneof,也不决定某种工具在 Cursor UI 中如何展示。 ### 26.12 自然状态与无 fallback 约束 本轮整理删除了几类会掩盖协议错误的级联和猜测: -- 工具不会再依次尝试 `Exec → Interaction → Local`。名称在 `cursor/tools.rs` 映射到唯一 transport,Loop 不持有 `ToolRoute`;未知工具立即报 Protocol error。动态 MCP 只有在本轮定义表中存在时才走 Exec。 +- 工具不会再依次尝试 `Exec → Interaction → Local`。名称在 `cursor/tools/dispatch/` 映射到唯一 transport,Loop 不持有 `ToolRoute`;未知工具立即报 Protocol error。动态 MCP 只有在本轮定义表中存在时才走 Exec。 - Pending 项不存在 `Running/Finished/Closed` 并行标志。存在于 map 就是 Running;terminal result、throw、提前 close 都通过 `take(id)` 消费唯一所有权。 -- `ToolCompletion` 不允许只有 canonical result、没有 UI presentation 的半成品。构造成功即同时拥有可稳定投射给 LLM 的结果与完整 typed ToolCall;结构化本地结果只在 provider 边界字符串化。 +- `ToolCompletion` 不允许只有 canonical result、没有 UI presentation 的半成品。构造成功即同时拥有 String ToolResult 与完整 typed ToolCall;结构化本地结果在 Cursor result adapter 边界只字符串化一次。 - `ToolCall.name` 与 `ExecClientMessage` oneof 必须精确匹配。Read 对上 WriteResult 等组合直接报 Protocol error,且已经消费该 terminal ID,不能继续复用。 - 不再通过 protobuf `Debug` 文本生成 tool result 或判断成败;每个支持的 oneof 都显式读取。 - LLM 返回的工具参数必须是合法 JSON;不再在解析失败时降级成普通字符串。 @@ -1806,7 +1902,8 @@ run/loop_engine.rs ```text tool name → 唯一 transport pending id → take → ToolCompletion -ToolCompletion → persist/checkpoint → publish +ToolCompletion → ClientCommand → durable commit → UI publish +ToolRound settled → checkpoint barrier → next model call ``` 默认值只保留在协议本身定义为 optional 的字段上;它不能用来掩盖缺少必需字段、未知消息类型或不匹配的生命周期。 @@ -1839,7 +1936,7 @@ Cursor tool dispatcher - Task 返回 `agent_id` 与 `background_reason` 后,父 Tool 即完成;子代理随后通过独立 conversation/RunSSE 继续。父 Loop 不等待子 Run 结束。 - `request_context` 和 `execute_hook` 虽然也使用 ExecServer/ClientMessage,但不是 LLM Tool,不能追加 assistant/tool pair。 -`AwaitShell` 不是该协议中的工具。proto 中的 `AwaitToolCall / SubagentAwaitArgs` 属于 Task/Subagent 语义,不能因为字段形状相近就把 `shell_id` 填入 `agent_id`。服务端不暴露 `AwaitShell`,也不保留该错误映射;后台 Shell 只使用实际存在的 Shell、ForceBackgroundShell 与 WriteShellStdin 消息。 +`AwaitShell` 是抓包确认的模型工具,Cursor pending contract 的 identifier 为 `AWAIT`,typed UI 使用 `AwaitToolCall`。它通过终端输出文件、等待时间和可选正则表达多阶段等待;不能把 `shell_id` 错填进 Subagent 的 `agent_id`。`ForceBackgroundShell` 与 `WriteShellStdin` 不是当前模型工具,不出现在 tool catalog,也不作为 Shell 的 fallback;后台化只由 Shell stream 的真实 `Backgrounded` 结果表达。 `CommunicateUpdateSuccess.message_index` 是当前 Turn 中该 ToolCall 对应的 `ConversationStep` 一基位置,不是本地调用次数。抓包第一组事件依次产生 thinking、assistant text、CommunicateUpdate,因此结果为 `message_index = 3`;同一 Turn 后续样本累计为 `6`。服务端按已提交步骤数、当前 thinking/text 和本批 call 位置确定该值。 @@ -1859,25 +1956,24 @@ WebFetch 的第二阶段可以由 proto 无歧义确定:approval 后将同一 Cursor adapter 识别 typed terminal → ToolCompletion(canonical result + typed UI result) → Loop 统一持久化 -→ Blob ACK barrier -→ 单 Tool checkpoint → ToolCallCompleted -→ 整批完整后下一轮 LLM +→ 整批完整后 Blob ACK + settled checkpoint barrier +→ 下一轮 LLM ``` 具体落点: -- `cursor/tools.rs` 独占工具路由和本地工具启动,并计算 Cursor step index。 -- `cursor/exec.rs` 独占 Shell 流阶段与 Exec wire event。 -- `cursor/tool_result.rs` 独占 typed result 到 `ToolCompletion` 的转换。 -- `run/loop_engine.rs` 不再包含 `ToolRoute` 或工具名称表。 +- `cursor/tools/dispatch/` 独占工具路由和本地工具启动。 +- `cursor/tools/codec/response.rs` 独占 Shell 流阶段与 Exec wire event 解码。 +- `cursor/tools/result/` 独占 typed result 到 `ToolCompletion` 的转换。 +- `run/engine.rs` 和 `run/tool_round.rs` 不包含 `ToolRoute` 或工具名称表。 - 未知工具与不匹配 oneof 立即返回 Protocol Error;不尝试 Exec → Interaction → Local fallback。 ## 27. Run 进度观测:provider_call_index 必须随 Loop 更新 -对运行中的 `4468e12f-4f90-4bd9-90ed-d57c9c2bc7a9` 复核后,最初看到的状态并不是卡在第一个 Ls:Ls 的 typed result、messages 和 checkpoint 都已经完成,Run 随后继续完成 Write、ReadLints、Shell 等调用,最终形成 8 轮 provider call 并正常结束。此前数据库始终显示 `provider_call_index = 0`,只是该字段从未被 Loop 更新,因而给出了错误的观测结果。 +对运行中的 `4468e12f-4f90-4bd9-90ed-d57c9c2bc7a9` 复核后,UI loading 不能仅凭数据库某一列判断 Loop 是否越过工具 barrier。该 Run 随后继续完成编辑、ReadLints、Shell 等调用,最终形成 8 轮 provider call 并正常结束。此前数据库始终显示 `provider_call_index = 0`,只是该字段从未被 Loop 更新,因而给出了错误的观测结果。 -`append_seqno` 只表示 BidiAppend 上行序号推进,不能回答当前正在执行第几轮 LLM。`run_tool_results` 在批次完成后会被清除,outbox ACK 也只能说明 checkpoint 已确认;它们都不能替代 provider 调用进度。 +`append_seqno` 只表示 BidiAppend 上行序号推进,不能回答当前正在执行第几轮 LLM。ToolRound 状态和 Blob SET ACK 也不能替代 provider 调用进度;协议不存在 checkpoint ACK 或持久 outbox。 固定规则为: @@ -1981,3 +2077,278 @@ ShellStream Backgrounded(shell_id + pid) - Backgrounded 终态生成成功的 canonical 字符串结果,其中包含 shell_id、pid、terminals_folder 和后台化前已收到的输出;typed ShellResult 同时保留这些字段供 Cursor UI 使用。 - Runtime environment prompt 明确追加 terminals folder,使下一轮 LLM 可以按 Shell 工具规则读取后台日志。 - Backgrounded 已是当前 ToolCall 的终态。后台输出不重新打开 ToolCall,也不引入不存在的 AwaitShell。 +- `Backgrounded` 只结束当前 ToolCall,不结束客户端持有的后台进程;成功 `TurnEnded/EndStream` 不发送 `ExecServerAbort`。失败或取消也只 abort 尚未返回终态的 Exec,不能回收已经后台化的 Shell。 +- 后台化只有一层:长驻命令本身保持前台形式,例如 `python3 -m http.server 9000`,并以 `block_until_ms=0` 交给 Cursor 管理。不能同时使用 `nohup`、`&` 或 `disown`;否则 Cursor 管理的是很快退出的外层 shell,真实子进程不再具有后台 Shell 生命周期。 + +本地异常样本 `run_id=2aacf882-3b6b-4b66-9c08-5342ee5cd6b6` 正是双重后台化:Shell 参数已经是 `block_until_ms=0`,command 又执行 `nohup python3 -m http.server 9000 ... &`。Run 正常 completed,服务端没有 abort;终端 `121702` 记录外层命令成功结束,而真正 server 子进程随后消失。因此修复位于 Shell 模型契约,codec 保持官方 ShellArgs,不对用户命令做字符串改写。 + +## 30. Write / StrReplace 的参数流、展示流与执行边界 + +provider 的 `ToolCallArgumentsDelta` 是原始 JSON 文本增量,Cursor 的 `EditToolCallDelta.stream_content_delta` 是编辑卡片消费的语义内容增量。两者不是同一种事件。 + +最新官方抓包中的两个编辑分别写入 10414 字符和修改 6 字符。两者在 Cursor UI 层都表现为 `EditToolCall`,Exec 层都实际执行 `ReadArgs → ReadResult → WriteArgs → WriteResult`,没有出现 `PiEditArgs`。第一次使用数字 id 46/47,第二次使用缺省 id 0/1;每组 Read 和 Write 都复用同一个原始 `tool_call_id`。 + +固定事件映射为: + +```text +LLM ToolCallStart +→ PartialToolCallUpdate(call_id/name, 空 Edit 占位) + +LLM ToolCallArgumentsDelta(raw JSON) +→ 增量 JSON 字符串解码 +→ Write.contents / StrReplace.new_string 的已解码字符 + 立即发布 ToolCallDelta(EditToolCallDelta.stream_content_delta) +→ path 完整后发布 PartialToolCall(EditArgs.path) + +LLM 参数完整 +→ 对完整 arguments_text 做一次严格 JSON 解析 +→ ToolCallStarted(EditArgs.path + 已累计的完整 stream_content) +→ 隐藏 ReadArgs(path, 同一个 tool_call_id) + +BidiAppend ReadResult +→ Write:得到 before;file_not_found 表示 before 为空 +→ StrReplace:在 before 上执行规范化后的精确 old_string/new_string 替换 +→ 隐藏 WriteArgs(path, 完整 after, 同一个 tool_call_id) + +BidiAppend WriteResult +→ 用 before/after 构造 diff、lines_added、lines_removed 和 EditResult +→ 持久化完整 assistant/tool pair +→ ToolCallCompleted +→ 若为本轮最后结果:Blob ACK 与 ToolRound settled checkpoint +``` + +`EditToolCallDelta` 不依赖 path,也不依赖 `ToolCallStarted`。官方抓包明确出现“完整内容 delta → path partial → started”的顺序,因此服务端不能把内容缓存到 path 到达之后。`ToolCallStarted` 是参数已经完整、即将执行的边界,不是编辑增量的前置条件。 + +Read 和 Write 是两个独立 Exec 请求,各有自己的数字 `id`,由 `PendingExecRegistry` 分别匹配 BidiAppend 返回;它们共享同一个 `tool_call_id`,因为对 UI 和 LLM 来说仍是同一个工具。客户端只执行普通 Read/Write,不知道服务端内部的两阶段状态。 + +编辑域的文本统一使用 LF:JSON 的 `\\n` 先解码为真实换行,再将 CRLF 和单独 CR 规范化为 LF。Read 内容、Write 完整内容、StrReplace 的 old/new、UI stream delta、精确匹配、diff 和 `WriteArgs.file_text` 使用同一规范文本。流式规范化必须保留 chunk 末尾未决的 CR,等下一 chunk 判断它是否与 LF 组成 CRLF,不能重复发布换行。 + +抓包中的 `tool_call_id` 含真实内部换行,例如 `call-...\nfc_..._0`。它是 Cursor wire 的不透明标识,不得拆分、重建或清理内部换行;Partial、Delta、Started、隐藏 Read、隐藏 Write、Completed 必须逐字复用。Provider 的 call id/item id 应作为独立元数据保存,不能靠反向解析这个组合值恢复。 + +实现保持 Loop 工具无关:`run/model_cycle.rs` 只消费统一 provider 事件,`cursor/tools/stream.rs` 只做实时 UI 投射,`cursor/tools/edit.rs` 负责 LF 规范化、替换计算和 diff,`cursor/tools/codec/response.rs` 负责隐藏 Read/Write 状态推进,`cursor/tools/runtime.rs` 保存当前阶段。没有 post-read、Windows path 猜测、内容不一致自动修复或旧编辑消息兼容路径。 + +messages、Blob 和 checkpoint 只保存最终完整 ToolCall 与 ToolResult。`PartialToolCall` 和 `EditToolCallDelta` 都是可丢弃的实时 UI 投影,不进入上下文事实源,也不影响下一轮 LLM 的前缀稳定性。 + +## 31. 本地 Agent 路由与 Cursor backend 转发边界 + +`--test-backend-url` 或等价 endpoint 配置会把大量 Cursor backend 请求送入本地服务,不只有 Agent loop。官方抓包中的这些请求具有统一上游 `https://api2.cursor.sh`。因此 Rust 服务不能把尚未实现的接口当作本地 404;否则模型列表、服务配置、对话 metadata、认证及其他旁路业务都会被误判为不存在。 + +固定路由顺序为: + +```text +incoming request +├─ POST /agent.v1.AgentService/RunSSE +│ └─ 本地 RunSSE handler +├─ POST /aiserver.v1.BidiService/BidiAppend +│ └─ 本地 BidiAppend handler +└─ 其他 method/path + └─ https://api2.cursor.sh + 原 path/query +``` + +转发保持 method、path/query、端到端 headers 和 body;响应保持上游 status、端到端 headers 和 body。请求和响应都使用流,不先聚合完整正文,因此 Connect/SSE 和大请求不会被代理层阻塞。目标 `Host`/authority 必须改为上游,`Connection`、`Transfer-Encoding`、`Upgrade` 等 hop-by-hop headers 不能跨连接复制。 + +本地 `RequestDecompressionLayer` 只作用于两个被接管的 protobuf 路由。代理请求不经过本地解压,避免 body 已改变而 `Content-Encoding` 仍沿用原值。只有无法建立上游连接时才由本地返回 `502 unavailable`;上游实际返回的 4xx/5xx 不改写。 + +每次代理在收到上游响应头后记录 method、path、status 和耗时;连接失败记录 error。由此客户端出现 404 时可以明确区分:它是上游真实 404,而不是 Rust Router 漏注册产生的默认 404。 + +## 32. 子代理的写入、MCP 能力与工具集合 + +主对话 `conversation_id = c7e5502c-8953-4a73-b5bb-226dd9c0b8f3` 中,`request_id = 37fca97d-4f8a-487e-a465-bf6975654ffb` 的用户指令为: + +```text +接下来发起三个子代理,测试他们的文件写入和mcp能力 +其中2个是后台的,一个是前台的 +``` + +该轮实际创建了三个独立子对话:两个 `run_in_background = true`,一个 `run_in_background = false`。子代理能够执行文件写入和 MCP 操作,因此子代理不是只读搜索器,也不是只能返回文本的缩减 Loop。 + +抓包 checkpoint 中的 `pendingToolExecutionContracts.allowedToolNames` 确认,子代理当前工具集合为: + +```text +Shell +Grep +Delete +WebSearch +WebFetch +GenerateImage +ReadLints +EditNotebook +TodoWrite +StrReplace +Write +Read +Glob +Task +AwaitShell +GetMcpTools +FetchMcpResource +SwitchMode +UpdateCurrentStep +CallMcpTool +``` + +关键结论: + +- 子代理明确包含 `Write`、`StrReplace`、`EditNotebook` 和 `Delete`,具备写文件及修改工作区的能力。 +- 子代理明确包含 `GetMcpTools`、`CallMcpTool` 和 `FetchMcpResource`,具备 MCP 发现、调用和资源读取能力。 +- `run_in_background` 只决定父 Run 是否等待子代理完成,不改变子代理的 tools、messages、Blob/checkpoint 或 LLM Loop 语义。前台和后台子代理都是完整的独立 Run。 +- 子代理工具集不含 `AskQuestion`,而是用 `UpdateCurrentStep` 向父 Task 的时间线报告进度和最终摘要。 +- `Task` 仍在子代理工具集中;是否允许再创建子代理由子 Run 末尾的 runtime/system reminder 和服务端策略约束,不应靠删除 wire tool 来猜测。 + +因此,服务端不应为“前台子代理”、“后台子代理”或“MCP 子代理”建立不同 Loop。它们共享同一个 `RunActor + ToolDispatcher`;差异只来自子 `RunRequest` 的代理类型、模型配置、runtime reminder 和父子关系字段。 + +## 33. Agent 工具资产与子代理自然派生 + +工具资产现在只有一个完整 schema 事实源:`prompt/cursor/tools.json`。不存在 `tools-full.json`,也不存在 Agent/Subagent 各自复制的完整 schema。`prompt/cursor/modes/*.json` 只按抓包保存有序名称;需要不同参数形状的 `Task.subagent` 是同一 catalog 中的显式 variant,`UpdateCurrentStep` 也在 catalog 中定义一次。 + +模型请求编译时按抓包关系形成最终工具集: + +```text +主 Agent = tools.json catalog + × modes/agent.json 的有序选择 + +子 Agent = tools.json catalog + × modes/subagent.json 的有序选择 + - AskQuestion + + Task.subagent(无 environment/cloud_base_branch) + + UpdateCurrentStep +``` + +`suppress_subagent_progress_update_tool = true` 时再移除 `UpdateCurrentStep`。这不是 fallback 或兼容分支,而是 RunRequest 中有明确 wire 字段控制的能力。子代理仍保留 `Task`,但没有 Cloud 参数;`PatchEdit` 不再存在,统一使用当前协议中的 `StrReplace`。 + +抓包中主代理和子代理的基础 system prompt 使用相同 Blob hash。子代理身份、父任务和运行期要求由追加的 user/runtime 信息表达,因此子代理编译也使用 Agent prompt,不使用另一份容易漂移的缩减 system prompt。这同时保持 messages 的只追加语义和前缀稳定性。 + +### 33.1 Task 与子代理模型 + +`Task` 的自然链路为: + +```text +LLM Task arguments +→ TaskToolCall.args(UI) +→ ExecServerMessage.subagent_args(客户端执行) +→ 独立子 RunRequest +``` + +`generalPurpose` 在 `TaskArgs.subagent_type` 中编码为 `unspecified`,但在 `SubagentArgs.subagent_type` 中发送字符串 `generalPurpose`;`cursor-guide` 使用明确的 `cursor_guide` oneof,其余具有协议 oneof 的类型同理,自定义类型保留原始名称,不能先转小写再回写。 + +`SubagentArgs.parent_conversation_id` 使用当前 conversation;`root_parent_conversation_id` 使用 `conversation_group_id`,根对话没有 group 时才等于当前 conversation。`accept_hook_additional_contexts = false`,与抓包一致。模型在父 Run 内一次解析:`subagent_model_overrides` 的显式 model 优先,inherit 解析为父模型,disabled 直接拒绝该类型;没有 override 时,`Task.model = inherit` 或缺省同样解析为父模型,显式 model 则原样使用。确定的 `model_id` 才进入 SubagentArgs,子 RunRequest 再通过 `requested_model` 把模型和参数传给独立 Run。父子模型不同不改变 messages 或前缀缓存规则。 + +### 33.2 UpdateCurrentStep 与 checkpoint + +模型工具名是 `UpdateCurrentStep`,Cursor protobuf 的表现类型仍叫 `CommunicateUpdateToolCall`。服务端必须保持这两个命名层次,不能向模型暴露旧名 `CommunicateUpdate`。 + +该工具本地立即完成,成功结果写入 canonical messages: + +```text +arguments.current_step / final_summary / completed_subtitle +→ CommunicateUpdateToolCall +→ success(current_step, message_index) +→ canonical ToolResult +``` + +子 BidiAppend 的 `X-Parent-Agent-Tool-Call-Id` 被绑定到 `RunHandle`,同一 Run 若收到冲突值直接报协议错误。checkpoint 不维护第二套可变进度状态,而是从已持久化的 assistant ToolCall 和对应 ToolResult fold 出 `CommunicateUpdateTurnState`,写入: + +```text +communicate_update_states_by_parent_tool_call_id[parent Task call_id] +``` + +其中 `history[]` 保存每次 `current_step + message_index`,最后一次带值的调用提供 `final_summary` 和 `completed_subtitle`。因此恢复、重放和 checkpoint 都由 messages 唯一决定。 + +### 33.3 GetMcpTools 使用客户端实时状态 + +旧实现直接读取初始 RunRequest 的 MCP descriptor 快照并在服务端本地完成,这是错误的:它绕过了客户端当前连接状态。抓包确认的链路为: + +```text +GetMcpTools started +→ ExecServerMessage.mcp_state_exec_args +→ BidiAppend McpStateExecResult(success.servers / error / rejected) +→ 按 server、toolName、pattern 过滤 +→ GetMcpTools completed +``` + +现在 `GetMcpTools` 与其他客户端 Exec 一样先在 `PendingExecRegistry` 以数字 id 登记,再等待该 id 的 Bidi 结果。`McpStateExecArgs.server_identifiers` 只在请求指定 server 时填写,`kick_only = false`、`accept_hook_additional_contexts = false`。成功、错误和拒绝都生成相应 typed tool result,并以字符串内容追加到下一轮 LLM messages;不再从数据库或旧 descriptor 旁路完成。 + +请求 `790aff97-8c6a-4717-b9db-ccdae211c67c` 暴露了调用阶段的第二个协议要求:`GetMcpTools` 能正常列出 `server=plugin-browser-use-browser-use, toolName=browser_exec`,但旧服务端随后把 `McpArgs.name` 也写成 `browser_exec`、把 `provider_identifier` 写成空字符串,因此 Cursor 三次都返回 `MCP tool not found: browser_exec`。 + +官方抓包的 `McpStateExecResult` 已经给出完整定义,例如: + +```text +server_identifier = plugin-browser-use-browser-use +definition.name = plugin-browser-use-browser-use-browser_exec +provider_identifier = browser-use +tool_name = browser_exec +``` + +后续官方 `McpArgs` 原样使用这四个值。因此 Run 内的 MCP 定义表必须由成功的 `McpStateExecResult` 更新,以 `(server_identifier, tool_name)` 为键;`CallMcpTool` 只从这张客户端实时表取回完整 `McpToolDefinition` 并填写 Exec。不能从 server 名称截取 provider,也不能自行拼接 definition name;当精确定义不存在时,应明确要求先执行 `GetMcpTools`,不发送字段不完整的 MCP Exec。这个定义表属于 `CursorToolRuntime`,在 Run 结束时与其他 Exec 态一起释放,不读写 SQLite。 + +官方 conversation `c62e79ea-1bb2-4190-adae-cadf584d9976`(request `b0562e27-4b0e-4373-afd9-e19c74b2838e`)还给出了完整成功闭环:RunSSE frame 45/49 分别要求 `user-context7` 和 `user-codegraph` 的 MCP state;frame 157 以 `id=8` 发送 Context7 `McpArgs`,frame 184 以 `id=9` 发送 Codegraph `McpArgs`。Bidi exchange 11084 以同一 `id=8` 返回真实文本内容,exchange 11095 以 `id=9` 返回 `No results found for "main"`,两者均为 `McpResult.success`。因此 MCP 成功结果不能被压缩成 `mcp success content=N` 这类调试摘要;必须把 text、output location 和 structured content 编译成 canonical 字符串 ToolResult,`is_error` 原样保留,再进入下一轮 LLM。 + +### 33.4 证据边界 + +当前抓包已经给出 Shell、Read/Write/Edit、Delete、Glob/Grep、WebFetch、Task、AwaitShell、MCP、SwitchMode、UpdateCurrentStep 等 wire 生命周期。`WebSearch` 抓包还证明客户端只返回 approval,搜索结果由官方服务端产生;`GenerateImage` 同样属于服务端外部执行能力。它们不能伪装成本地成功,也不能仅凭 proto 编造执行器:在接入明确的搜索/图像 provider 前,现有代码只实现其 Cursor approval wire,批准后仍必须显式报未配置的服务端能力,而不是产生虚假 ToolResult。 + +### 33.5 子代理/队列恢复中的 Run 身份 + +本地异常样本显示,`001e763b-fcd4-4945-969f-57721dd827d2` 是根 Run;它派生了四个独立子 Run:`dd0971a8…`(explore)、`5ddee013…`(generalPurpose)、`2412aab8…`(shell)和 `dea4c0f5…`(cursor-guide)。`cursor-guide` 失败回传期间,Cursor 以新的 RunSSE/Bidi `request_id=2bfd06f0…` 发起一次尝试,但 `AgentRunRequest.run_id` 复用了根值 `001e763b…`。因此该 wire 字段不能作为 `runs.run_id` 的执行唯一键。 + +Cursor adapter 现在使用每次 RunSSE/Bidi 的 `request_id` 创建通用内部 RunId;wire `run_id` 不越过 adapter 成为 Store 主键。这样队列恢复是新执行,可以按客户端带回的 revision 取得 conversation ownership 并取消旧执行,而不会撞旧行。 + +此外,Run claim 失败发生在新执行尚未拥有数据库状态之前。该失败只能向当前客户端返回 typed Error,绝不能调用 `finish_run` 修改同 ID 的既有记录。旧实现正是违反了这一点:重复 INSERT 失败后又把仍在工作的根 `001e…` 标成 failed。现在只有 claim 成功的 Run 才有资格持久化 Completed/Cancelled/Failed 终态。 + +### 33.6 后台子代理完成通知 + +Task 首次创建子代理时,客户端 `SubagentSuccess` 已返回 `agent_id`,Task 调用参数中的 `description` 是该子代理的用户可见 name。两者必须立即进入 canonical ToolResult 字符串: + +```text +Subagent name: {description} +Subagent ID: {agent_id} +``` + +这条 ToolResult 表达“Task 创建出了哪个对象”,即使后台 Task 此时没有 `final_message` 也不能返回空字符串;否则 Cursor typed UI 虽持有 `TaskSuccess.agent_id`,下一轮 LLM 却不知道刚创建的子代理身份,只能从 transcript 文件或后续 completion 猜测。若首次创建时已经有 `final_message`,它接在身份之后。`resume={已有 agent_id}` 不是创建,不重复包装身份,仍只返回本次执行结果;`resume=self` 会创建新子代理,因此使用新返回的 name 和 ID。 + +官方抓包确认,后台子代理结束后客户端会为父 conversation 发起新的 RunSSE/Bidi。该 `AgentRunRequest.action` 不是普通 `user_message_action`,而是 `background_task_completion_action`;每个 completion 明确携带 `task_id`、`subagent_id`、父 `tool_call_id`、`title`、`status`、`reason`、`detail` 和 transcript `output_path`。服务端不应自行轮询子 Run,也不应从 Task 文本猜测哪个子代理完成。 + +当 `kind = SUBAGENT` 且 `reason = TASK_FINISHED` 时,completion 的 detail 先成为本轮模型可见的完成上下文,随后以 user/runtime 身份追加官方完整版 follow-up: + +```text +Perform any necessary follow-up actions in response to the subagent completion above. If no follow-up work is needed, no further action is required. If you mention an agent or subagent in your response, link it with the `[Name](id)` Don't use generic label such as `[agent]`, `[worker]`, or `[subagent]`. For cloud subagents, when the agent has edited code, link to `[Review](bc-id#changes)`, or, if you know the exact added and deleted line counts, `[Review +A −D](bc-id#changes)`, replacing A and D with those counts. Never write A or D literally. Use `[Try Live](bc-id#desktop)` only when the agent used computer use. Don't repeat the same confirmation every time. +``` + +抓包中四个后台子代理依次完成时,客户端发起了四个 completion Run,以上完整提醒也出现四次。它不是 conversation 级一次性提示,而是每个完成事件各追加一次;幂等键为 `subagent-completed:{subagent_id}`。同一 completion 重试不会产生第二条 message,不同子代理完成则保持原始时间顺序继续追加。 + +```text +后台 Task 启动,父 Turn 结束 +→ 子代理完成 +→ 客户端发送 background_task_completion_action +→ 服务端验证 SUBAGENT + TASK_FINISHED + subagent_id +→ 持久化完成 detail 与完整 follow-up user/runtime message +→ checkpoint 确认 +→ 父 conversation 新一轮 LLM +``` + +该事件同时生成 `is_simulated_msg = true`、`simulated_msg_reason = BACKGROUND_TASK_COMPLETION` 的 Cursor UserMessage/Turn,因此 UI、Blob 图和模型上下文表达同一事实。服务端此前虽然能读取普通 UserMessage 的 `subagent_system_reminder`,却完全忽略 `background_task_completion_action`;这正是后台子代理完成后父代理不会自然汇报的原因。 + +runtime message 的 checkpoint wire ID 是稳定身份 `runtime:{event_id}`,恢复时必须原样保留。`cursor-root:{blob_id}:{ordinal}` 只用于 wire ID 会重复、仅表达投射位置的普通 Cursor message,例如 assistant 的 `id = "1"`;不能替换 runtime 身份。请求 `9c1b5252-38a9-4829-87f5-2d2dda3ea37c` 的失败正是因为恢复代码把已有 `runtime:subagent-completed:{subagent_id}` 改成了位置 ID:数据库按相同 `runtime_event_id` 找到旧事件,却发现完整 canonical message 的 `message_id` 已改变,于是正确拒绝“同一事件、不同内容”。修复应恢复稳定身份,不能放宽唯一约束、覆盖旧消息或吞掉冲突。 + +### 33.7 编辑历史消息与活动后缀截断 + +`UserMessageAction` 没有 `edited` 标志;协议提供的稳定逻辑身份是 `UserMessage.message_id`。Cursor 在用户修改历史消息后会复用这个 ID 并发送新的内容。它不能继续直接充当不可变 canonical message 的主键,否则同 ID、不同 payload 会触发 `message id or runtime event reused with different content`。 + +服务端把客户端逻辑输入身份记为 `cursor:user:{message_id}`,并在第一次看到它时绑定“该输入追加前”的 `base_revision_id`: + +```text +第一次发送 M +input anchor(M) = revision before M +→ append immutable runtime message for this Run +→ append assistant/tool suffix + +编辑并再次发送 M +→ resolve input anchor(M) +→ conversation active head 回到 revision before M +→ append a new immutable runtime message +→ 生成新的 assistant/tool suffix +``` + +因此活动上下文的实际结果就是“编辑点之前的前缀 + 修改后的用户消息 + 新后缀”。原用户消息以及它后面的 assistant/tool 消息不会进入新的 LLM 请求,也不会出现在新 checkpoint 的活动 Turn 图中。旧 revision 和不可变 Blob 不做覆盖或物理删除,仍可用于历史回滚;这里所谓删除是从当前 revision 的可达集合中删除。 + +输入 anchor 使用 `(conversation_id, input_id)` 唯一键并持久化,不能只放在 Run 内存中:编辑可能发生在进程重启后。重复请求通过同一个 anchor 得到同一 base;不同内容则形成新的不可变分支。Run claim 已具备把 conversation head 原子切到所选 base 的能力,后续 append 仍受 active Run ownership 保护。 diff --git a/README.md b/README.md index ca5b4ab..1a4e922 100644 --- a/README.md +++ b/README.md @@ -1,170 +1,87 @@ -# Cursor Rust 服务端实施与验收计划 +# cursor-byok -## 项目说明 +`cursor-byok` 是一个基于真实 Cursor Agent 流量与 protobuf 实现的自托管服务端,用于把 Cursor 客户端接入用户指定的 LLM Provider。 -`cursor-byok` 是一个兼容 Cursor Agent 客户端协议的自托管服务端项目,用于把 Cursor 客户端接入用户指定的 LLM Provider。项目根据真实客户端流量和提取出的 protobuf 协议实现,不依赖 Cursor 原服务保存对话状态。 +当前 Rust 服务 `cursor-server` 已实现: -当前 Rust 服务 `cursor-server` 实现以下完整链路: +- Cursor `RunSSE + BidiAppend` 双向协议与 Connect envelope。 +- OpenAI Chat、OpenAI Responses、Anthropic 三种无状态流式端点。 +- `LLM → 客户端工具 → 结果提交 → 下一轮 LLM` 的通用 Loop。 +- append-only canonical messages、不可变 revision 分支和同对话 Run 抢占。 +- Cursor typed tool UI、Exec/Interaction、多阶段编辑、MCP 与子代理。 +- Blob CAS、KV GET/SET ACK、两阶段 checkpoint 和 pending ToolRound 恢复。 +- 未匹配 Cursor backend 路由原样流式转发到上游。 +- React + Vite + TypeScript + Tailwind 管理台,支持 Provider 配置、模型发现和调用明细。 +- 每次 Provider 调用的时间、模型快照、状态和 authoritative usage;详细模式保存脱敏请求与原始流响应。 -- 通过 `RunSSE + BidiAppend` 组成的双向协议与 Cursor 客户端通信。 -- 将 OpenAI Chat、OpenAI Responses 和 Anthropic 的流式响应统一为内部 `ResponseEvent`。 -- 运行无状态 LLM Loop:`LLM → 客户端工具执行 → 结果追加 → 下一轮 LLM`,直到 Turn 完成或被新 Run 打断。 -- 以 append-only messages 作为上下文唯一事实源,保证相邻 LLM 请求的稳定前缀和可重复投射。 -- 支持文本、thinking、tool start、参数增量、tool result、usage、model、rules、commands、skills、MCP 和 subagent 上下文。 -- 使用 SQLite 持久化 messages、Run 状态、Blob CAS、引用边和 outbox。 -- 使用不可变 Blob 对象图表达 Conversation、Turn、UserMessage 和 Steps;BlobID 为原始内容的 `SHA-256`。 -- 在客户端确认 Blob 已存储后发布 checkpoint,并提供单工具粒度的历史回滚和未确认操作恢复。 +启动方式见 [cursor-server/README.md](./cursor-server/README.md)。协议证据见 [Cursor上下文与状态同步抓包分析.md](./Cursor上下文与状态同步抓包分析.md),当前实现约束见 [一次性重构计划计划.md](./docs/一次性重构计划计划.md)。 -运行时职责划分如下:Cursor 客户端负责真正执行本地工具并保存服务端同步的 Blob;`cursor-server` 负责 Loop 决策、上下文投射、Provider 调用、状态持久化和 checkpoint 构造。Todo/Plan 等业务状态不单独维护,而是从 messages 确定性推导。 - -协议与状态模型的抓包结论见 [Cursor上下文与状态同步抓包分析.md](./Cursor上下文与状态同步抓包分析.md)。Rust 服务的启动方式和运行配置见 [cursor-server/README.md](./cursor-server/README.md)。 - -## 目录硬约束 - -下列目录、文件名和职责是实现验收条件,不是建议。首版只允许一个 `cursor-server` crate;代码必须落在对应文件,不得用 `core.rs`、`service.rs` 等总入口替代,也不得提前创建 MCP/subagent 空模块。新增文件必须说明为何现有职责无法容纳;删除、改名或移动下列文件必须先同步修改本计划。 +## 核心数据流 ```text -cursor-byok/ -├── cursor-server/ # 新 Rust 服务 -│ ├── Cargo.toml -│ ├── build.rs # 从 cursor-proto/proto 生成 prost 类型 -│ ├── README.md # 启动方式、架构和核心不变量 -│ │ -│ ├── migrations/ -│ │ └── 0001_initial.sql # Blob、messages、runs、outbox -│ │ -│ ├── src/ -│ │ ├── main.rs # 进程入口 -│ │ ├── lib.rs # 模块出口 -│ │ ├── app.rs # 依赖组装、启动和关闭 -│ │ ├── config.rs # 地址、数据库、provider 配置 -│ │ ├── error.rs # 服务统一错误 -│ │ │ -│ │ ├── model/ # 纯领域类型,不依赖 Cursor/provider -│ │ │ ├── mod.rs -│ │ │ ├── message.rs # CanonicalMessage、Role、Origin -│ │ │ ├── runtime_tag.rs # RuntimeEvent、exactly-once 约束 -│ │ │ ├── conversation.rs # Conversation、Turn、revision -│ │ │ ├── tool.rs # ToolCall、ToolResult -│ │ │ └── usage.rs # provider usage 与 Turn usage -│ │ │ -│ │ ├── run/ # Loop 引擎和一次 request 的状态机 -│ │ │ ├── mod.rs -│ │ │ ├── registry.rs # request_id → RunHandle -│ │ │ ├── actor.rs # 每个 Run 一个 actor -│ │ │ ├── command.rs # run_request、exec/KV result、abort -│ │ │ ├── inbox.rs # append_seqno 排序、去重 -│ │ │ ├── loop_engine.rs # LLM → Tool → LLM 主循环 -│ │ │ └── lifecycle.rs # turn_ended/checkpoint/EndStream -│ │ │ -│ │ ├── cursor/ # Cursor 协议适配器 -│ │ │ ├── mod.rs -│ │ │ ├── proto.rs # include prost 生成代码 -│ │ │ ├── connect.rs # 5-byte Connect envelope -│ │ │ ├── handlers.rs # Axum 路由入口 -│ │ │ ├── bidi_append.rs # 上行 AgentClientMessage -│ │ │ ├── run_sse.rs # 下行 AgentServerMessage -│ │ │ ├── interaction.rs # 交互事件、Tool args 和 usage 投射 -│ │ │ ├── exec.rs # Exec 上行/下行解析 -│ │ │ ├── pending.rs # Exec/Interaction 的运行期 ID 关联 -│ │ │ ├── tools.rs # 唯一工具路由、本地工具和 Cursor step index -│ │ │ ├── tool_result.rs # typed result、UI completion 和结果通道 -│ │ │ ├── blob_sync.rs # KV GET/SET、ACK、重试 -│ │ │ └── checkpoint.rs # Blob 图和 checkpoint 构造 -│ │ │ -│ │ ├── provider/ # LLM 端点适配器 -│ │ │ ├── mod.rs # Provider trait -│ │ │ ├── event.rs # Canonical ResponseEvent -│ │ │ ├── openai_chat.rs # 第一条可运行链路 -│ │ │ ├── openai_responses.rs -│ │ │ └── anthropic.rs -│ │ │ -│ │ ├── prompting/ # 模型请求编译 -│ │ │ ├── mod.rs -│ │ │ ├── assets.rs # 校验并嵌入根目录 prompt/ -│ │ │ ├── compiler.rs # messages + mode + tools -│ │ │ ├── projector.rs # CanonicalMessage → provider 格式 -│ │ │ └── derived_state.rs # 从 messages fold Todo/Plan -│ │ │ -│ │ └── store/ # SQLite 持久化 -│ │ ├── mod.rs -│ │ ├── sqlite.rs # pool、事务、PRAGMA -│ │ ├── messages.rs # append-only messages -│ │ ├── blobs.rs # CAS 与引用边 -│ │ ├── conversations.rs # conversation head/revision -│ │ ├── runs.rs # 活动 Run 和恢复信息 -│ │ └── outbox.rs # KV/checkpoint 待确认操作 -│ │ -│ └── tests/ -│ ├── support/ -│ │ ├── fake_provider.rs -│ │ ├── fake_cursor.rs -│ │ └── fixtures.rs -│ ├── text_turn.rs # 纯文本完整 Turn -│ ├── tool_loop.rs # LLM → Tool → LLM -│ ├── runtime_tag_once.rs # Runtime tag 不重复追加 -│ ├── prefix_stability.rs # M(n) 是 M(n+1) 前缀 -│ ├── checkpoint_recovery.rs # 单 Tool 回滚 -│ ├── interrupt.rs # 新 Run 打断旧 Run -│ └── connect_wire.rs # Connect 二进制兼容性 -│ -├── cursor-proto/ # 现有 protobuf 提取和源文件 -├── cursor-backend/ # 现有 Go 抓包调试器 -├── prompt/ # 已复制的完整模式资产 -└── docs/ +HTTP / Connect + ↓ +Cursor adapter + ↓ ClientCommand / ClientEvent +RunEngine + ↓ +canonical messages + selected revision + ↓ +typed ModelRequest + ↓ +Provider adapter → HTTP/SSE → ModelEvent ``` -## 按文件实施顺序与通过条件 +Loop 不依赖 Cursor protobuf、Blob、checkpoint、数字 wire id 或具体 Provider JSON。Cursor adapter 和 Provider adapter 只在各自边界做协议投射。 -1. `Cargo.toml`、`build.rs`、`src/{main,lib,app,config,error}.rs`:服务能加载配置、迁移数据库、生成 Cursor protobuf 并启动/优雅关闭。 -2. `src/model/*.rs`、`src/store/*.rs`、`migrations/0001_initial.sql`:实现纯领域消息、runtime tag、tool/usage,以及 append-only messages、Blob CAS/引用边、revision、run 恢复与 outbox;`tests/runtime_tag_once.rs` 和 `tests/prefix_stability.rs` 必须通过。 -3. `src/cursor/{proto,connect,handlers,bidi_append,run_sse}.rs`:实现抓包一致的 5-byte Connect envelope、二进制 RunSSE、BidiAppend 解码和 `append_seqno` 排序去重;`tests/connect_wire.rs` 必须通过。 -4. `src/provider/{event,openai_chat,openai_responses,anthropic}.rs`、`src/prompting/*.rs`:三个端点统一为 canonical `ResponseEvent`;所有 mode 的 prompt/tool 资产可加载;messages 投射幂等且保持严格前缀。 -5. `src/run/*.rs`、`src/cursor/{tools,interaction,exec,pending,tool_result}.rs`:一个 request 一个 RunActor;Loop 只处理统一 `ToolCompletion`,工具名称到 Exec/Interaction/Local 的唯一映射只存在于 `cursor/tools.rs`;完成 toolstart 占位、参数增量、客户端执行、打断与 usage。每个完成的工具必须原子追加 `assistant(tool_call) → tool(result)`,整批完成前不得进入下一次 LLM。 -6. `src/cursor/{blob_sync,checkpoint}.rs`、`src/store/{blobs,outbox}.rs`:构造不可变 Blob 对象图,KV SET 未 ACK 前不得发布引用它的 checkpoint;checkpoint 达到单工具粒度,最终状态重复发布后再 EndStream;`tests/checkpoint_recovery.rs` 必须通过。 -7. `tests/{text_turn,tool_loop,interrupt}.rs` 与 `tests/support/*.rs`:覆盖纯文本 Turn、完整工具循环、新 Run 打断旧 Run和恢复路径。最终验收命令固定为 `cargo fmt --check`、`cargo clippy --all-targets -- -D warnings`、`cargo test --all-targets`。 +## 状态与 checkpoint +- SQLite 中的 immutable messages 与 revision 父链是对话事实源;回滚只选择旧 revision 并建立新分支。 +- ToolRound 保存完整 assistant、原始 call 顺序和真实 result 完成顺序;结果未齐时不会把悬空 tool call 投给下一轮模型。 +- BlobID 是 `SHA-256(data)`;Blob 类型来自引用字段,不编码在 ID 中。 +- checkpoint 引用的新 Blob 必须先收到对应 KV SET ACK。协议中不存在 checkpoint ACK,也不保存跨流 outbox。 +- staged checkpoint 内联完整 pending assistant;ToolRound 全部结果提交后才折叠进 stable roots。抓包没有单 ToolResult checkpoint,因此实现也不制造该状态。 +- settled checkpoint 必须先于下一轮 LLM 调用。最终文本轮严格发送 `turn_ended → staged → settled → settled 重发 → EndStream`。 +- Cursor.app 只把 `turn_ended` 前的 checkpoint 作为自动恢复候选;恢复 pending assistant 时先继续工具,不重复调用 LLM。 -模块依赖方向固定为: +## 目录边界 -HTTP/Connect - ↓ -cursor adapter - ↓ -run actor - ↓ -model + prompting - ↓ -provider / client tools - ↓ -store + checkpoint +```text +cursor-server/src/ +├── control/ # 客户端无关的 Provider、模型、调用观测 HTTP API +├── client/ # 所有客户端共用的最小 command/event port +├── model/ # canonical message、revision、ModelSpec、typed history +├── run/ # 协议无关 Loop、ModelCycle、ToolRound 和 RunRegistry +├── provider/ # 各 LLM 端点请求与流事件适配 +├── cursor/ +│ ├── request/ # RunRequest → PreparedRun/CursorRunContext +│ ├── prompting/ # Cursor prompt、工具 catalog 和 mode manifest +│ ├── projection/ # Cursor AI-SDK stable/pending JSON 编解码 +│ ├── interaction/ # UI 更新、InteractionQuery、typed ToolCall 渲染 +│ ├── tools/ # Cursor 工具 transport、runtime、dispatch 和 result +│ └── checkpoint/ # root/Turn/derived/recovery 与串行 worker +└── store/ # SQLite messages、revision、ToolRound、Run 和 Blob CAS -几个关键决定: -model/ 不引用 Cursor protobuf,也不引用具体 provider。 -cursor/ 只负责协议转换,不能包含 Loop 业务决策。 -provider/ 只把不同端点转换为统一 ResponseEvent。 -prompting/derived_state.rs 只 fold messages,不持久化 Todo/Plan。 -store/messages.rs 是上下文唯一事实源。 -store/outbox.rs 保存尚未确认的 Blob/checkpoint 操作。 -MCP 和 subagent 暂时不建空目录:MCP 先作为动态 Tool 接入;subagent 复用 RunActor,需求落地时再加入 run/subagent.rs。 - -依赖建议: -``` -tokio 异步运行时 -axum + hyper Connect HTTP 服务 -prost + prost-build protobuf -protoc-bin-vendored 避免系统 protoc 依赖 -sqlx/sqlite 持久化和事务 -reqwest provider HTTP -eventsource-stream provider SSE -serde/serde_json 模型和工具 JSON -sha2 + base64 BlobID -bytes 二进制载荷 -tokio-util CancellationToken -thiserror + tracing 错误和日志 -include_dir 编译期嵌入 prompt/ 资产 +console/ # React 管理台;只依赖 control API,不依赖 Cursor protobuf ``` -Cursor上下文与状态同步抓包分析.md 来告诉你很多信息,你需要一次性读他 +详细到文件的目标目录和验收项只在重构计划中维护,README 不复制第二份易漂移的完整文件清单。 -Users/leokun/Library/Application Support/cursor-byok/cursor-proxy-debugger.db 是cursor的原服务抓包信息,内容由/Users/leokun/Documents/cursor-byok/cursor-backend产生 +## 工程原则 + +- 不保留旧路径、兼容层或失败后的隐式 fallback。 +- 同一状态只有一个所有者;协议层不做 Loop 决策。 +- PromptSpec、ModelSpec 和 selected revision 决定可重放的 ModelRequest;request id、时间和 model call id 不进入模型输入。 +- 前缀稳定限定在相同 PromptSpec/ModelSpec/Provider route;新 Run 切换模型或模式时只替换 Cursor system root,其他历史 message roots 继续复用。 +- Provider replay state 只回传给产生它的端点;可展示 thinking 不是跨端点 reasoning 字段。 +- Provider usage 只采用端点报告的单轮最终值,不自行估算。 +- 同一个取消信号覆盖等待 HTTP 响应头和读取 SSE 两段。 + +## 验证 + +```bash +cd cursor-server +cargo fmt --check +cargo clippy --all-targets -- -D warnings +cargo test --all-targets +``` diff --git a/console/index.html b/console/index.html new file mode 100644 index 0000000..ea0724d --- /dev/null +++ b/console/index.html @@ -0,0 +1,12 @@ + + + + + + Cursor BYOK + + +
+ + + diff --git a/console/package-lock.json b/console/package-lock.json new file mode 100644 index 0000000..a364e45 --- /dev/null +++ b/console/package-lock.json @@ -0,0 +1,2533 @@ +{ + "name": "cursor-byok-console", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cursor-byok-console", + "version": "0.1.0", + "dependencies": { + "@tanstack/react-query": "^5.90.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "tailwindcss": "^4.1.0", + "typescript": "~5.9.0", + "vite": "^7.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.407", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.407.tgz", + "integrity": "sha512-4R8XgQOdfxexCd/u63lRm6wCHjECwI45MV9wxAs2ggtfWe2hwlo1ql97jKsju2IcJ+jFSTwBssyYoiWhh7mauQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/console/package.json b/console/package.json new file mode 100644 index 0000000..43efb1d --- /dev/null +++ b/console/package.json @@ -0,0 +1,26 @@ +{ + "name": "cursor-byok-console", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "typecheck": "tsc -b --pretty false" + }, + "dependencies": { + "@tanstack/react-query": "^5.90.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "tailwindcss": "^4.1.0", + "typescript": "~5.9.0", + "vite": "^7.1.0" + } +} diff --git a/console/src/api/client.ts b/console/src/api/client.ts new file mode 100644 index 0000000..20406ea --- /dev/null +++ b/console/src/api/client.ts @@ -0,0 +1,38 @@ +import type { CallDetail, LlmCall, Provider, ProviderInput, ProviderModel } from './types' + +async function request(path: string, init?: RequestInit): Promise { + const response = await fetch(path, { + ...init, + headers: { 'content-type': 'application/json', ...init?.headers }, + }) + if (!response.ok) { + const error = await response.json().catch(() => ({ message: response.statusText })) + throw new Error(error.message ?? `HTTP ${response.status}`) + } + if (response.status === 204) return undefined as T + return response.json() as Promise +} + +export const api = { + providers: () => request('/api/providers'), + createProvider: (input: ProviderInput) => + request('/api/providers', { method: 'POST', body: JSON.stringify(input) }), + updateProvider: (id: number, input: ProviderInput) => + request(`/api/providers/${id}`, { method: 'PUT', body: JSON.stringify(input) }), + deleteProvider: (id: number) => request(`/api/providers/${id}`, { method: 'DELETE' }), + discoverModels: (id: number) => + request<{ models: string[] }>(`/api/providers/${id}/models/discover`, { method: 'POST' }), + saveModels: (id: number, models: unknown[]) => + request(`/api/providers/${id}/models`, { + method: 'POST', + body: JSON.stringify({ models }), + }), + models: () => request('/api/models'), + deleteModel: (hash: string) => request(`/api/models/${hash}`, { method: 'DELETE' }), + calls: () => request('/api/llm-calls'), + call: (id: string) => request(`/api/llm-calls/${encodeURIComponent(id)}`), + observability: () => request<{ detailed: boolean }>('/api/settings/observability'), + setObservability: (detailed: boolean) => request<{ detailed: boolean }>('/api/settings/observability', { + method: 'PUT', body: JSON.stringify({ detailed }), + }), +} diff --git a/console/src/api/types.ts b/console/src/api/types.ts new file mode 100644 index 0000000..b18c828 --- /dev/null +++ b/console/src/api/types.ts @@ -0,0 +1,61 @@ +export type ProviderType = 'openai-chat' | 'openai-responses' | 'anthropic' + +export interface Provider { + provider_id: number + name: string + provider_type: ProviderType + base_url: string + has_api_key: boolean + custom_headers: Record + created_at_ms: number + updated_at_ms: number +} + +export interface ProviderInput { + name: string + provider_type: ProviderType + base_url: string + api_key?: string + custom_headers: Record +} + +export interface ProviderModel { + model_hash: string + provider_id: number + model_id: string + display_name: string + enabled: boolean + sort_order: number + context_window_tokens?: number + max_output_tokens?: number + reasoning_enabled: boolean + reasoning_effort?: string + extra_params: Record + created_at_ms: number + updated_at_ms: number +} + +export interface LlmCall { + call_id: string + run_id: string + conversation_id: string + model_hash?: string + model_id: string + display_name: string + provider_type: ProviderType + status: string + created_at_ms: number + duration_ms?: number + ttfb_ms?: number + ttft_ms?: number + input_tokens?: number + output_tokens?: number + total_tokens?: number + detailed: boolean +} + +export interface CallDetail { + call: LlmCall + request?: { headers: Record; body: unknown; byte_count: number } + response_chunks: { seq: number; received_offset_ms: number; data: string; byte_count: number }[] +} diff --git a/console/src/app/App.tsx b/console/src/app/App.tsx new file mode 100644 index 0000000..bb18f70 --- /dev/null +++ b/console/src/app/App.tsx @@ -0,0 +1,48 @@ +import { NavLink, Route, Routes } from 'react-router-dom' + +import { CallDetailPage } from '../features/calls/CallDetailPage' +import { CallsPage } from '../features/calls/CallsPage' +import { ModelsPage } from '../features/models/ModelsPage' +import { ProvidersPage } from '../features/providers/ProvidersPage' +import { ObservabilityPage } from '../features/settings/ObservabilityPage' + +const links = [ + ['/', 'Providers'], + ['/models', 'Models'], + ['/calls', 'LLM Calls'], + ['/settings', 'Settings'], +] as const + +export function App() { + return ( +
+
+
+
Cursor BYOK
+ +
+
+
+ + } /> + } /> + } /> + } /> + } /> + +
+
+ ) +} diff --git a/console/src/features/calls/CallDetailPage.tsx b/console/src/features/calls/CallDetailPage.tsx new file mode 100644 index 0000000..a4e9c5f --- /dev/null +++ b/console/src/features/calls/CallDetailPage.tsx @@ -0,0 +1,28 @@ +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router-dom' + +import { api } from '../../api/client' + +export function CallDetailPage() { + const { callId = '' } = useParams() + const detail = useQuery({ queryKey: ['call', callId], queryFn: () => api.call(callId) }) + if (!detail.data) return

加载中…

+ const { call, request, response_chunks: chunks } = detail.data + return
+

{call.display_name}

{call.call_id} · {call.status}

+
+ + +
+ + +
+} + +function Metric({ label, value, suffix = '' }: { label: string; value?: number; suffix?: string }) { + return

{label}

{value ?? '—'} {value == null ? '' : suffix}
+} + +function Payload({ title, value }: { title: string; value: unknown }) { + return

{title}

{typeof value === 'string' ? value : JSON.stringify(value, null, 2)}
+} diff --git a/console/src/features/calls/CallsPage.tsx b/console/src/features/calls/CallsPage.tsx new file mode 100644 index 0000000..73f2851 --- /dev/null +++ b/console/src/features/calls/CallsPage.tsx @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query' + +import { api } from '../../api/client' +import { Link } from 'react-router-dom' + +export function CallsPage() { + const calls = useQuery({ queryKey: ['calls'], queryFn: api.calls, refetchInterval: 3000 }) + return

LLM Calls

每一行对应一次真实 Provider 请求。

+
+ + {calls.data?.map((call) => + + + )}
时间模型状态TTFT耗时Tokens
{new Date(call.created_at_ms).toLocaleString()}{call.display_name}{call.model_id}{call.status}{call.ttft_ms == null ? '—' : `${call.ttft_ms} ms`}{call.duration_ms == null ? '—' : `${call.duration_ms} ms`}{call.total_tokens ?? '—'}
+
+
+} diff --git a/console/src/features/models/ModelsPage.tsx b/console/src/features/models/ModelsPage.tsx new file mode 100644 index 0000000..e1d8eae --- /dev/null +++ b/console/src/features/models/ModelsPage.tsx @@ -0,0 +1,46 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useState } from 'react' + +import { api } from '../../api/client' +import type { ProviderModel } from '../../api/types' + +export function ModelsPage() { + const client = useQueryClient() + const models = useQuery({ queryKey: ['models'], queryFn: api.models }) + const save = useMutation({ + mutationFn: ({ model, input }: { model: ProviderModel; input: ModelEdit }) => + api.saveModels(model.provider_id, [{ + model_id: model.model_id, display_name: input.displayName, enabled: input.enabled, sort_order: model.sort_order, + context_window_tokens: input.contextWindow || undefined, max_output_tokens: input.maxOutput || undefined, + reasoning_enabled: input.reasoning, reasoning_effort: input.effort || undefined, + extra_params: model.extra_params, + }]), + onSuccess: () => client.invalidateQueries({ queryKey: ['models'] }), + }) + return

Models

Hash 是 Cursor 和其他客户端使用的稳定公开标识。

+
+ + {models.data?.map((model) => save.mutate({ model, input })} />)}
Hash / Provider IDDisplay nameContextMax outputReasoning状态
+
+
+} + +interface ModelEdit { displayName: string; enabled: boolean; contextWindow: number; maxOutput: number; reasoning: boolean; effort: string } + +function ModelRow({ model, onSave }: { model: ProviderModel; onSave: (input: ModelEdit) => void }) { + const [displayName, setDisplayName] = useState(model.display_name) + const [contextWindow, setContextWindow] = useState(model.context_window_tokens ?? 0) + const [maxOutput, setMaxOutput] = useState(model.max_output_tokens ?? 0) + const [reasoning, setReasoning] = useState(model.reasoning_enabled) + const [effort, setEffort] = useState(model.reasoning_effort ?? '') + const value = (enabled: boolean): ModelEdit => ({ displayName, enabled, contextWindow, maxOutput, reasoning, effort }) + return + {model.model_hash}{model.model_id} + setDisplayName(event.target.value)} /> + setContextWindow(Number(event.target.value))} /> + setMaxOutput(Number(event.target.value))} /> +
setReasoning(event.target.checked)} /> setEffort(event.target.value)} />
+ {model.enabled ? 'Enabled' : 'Disabled'} +
+ +} diff --git a/console/src/features/providers/ProviderForm.tsx b/console/src/features/providers/ProviderForm.tsx new file mode 100644 index 0000000..1c3bdd9 --- /dev/null +++ b/console/src/features/providers/ProviderForm.tsx @@ -0,0 +1,38 @@ +import { useState } from 'react' + +import type { Provider, ProviderInput, ProviderType } from '../../api/types' + +export function ProviderForm({ provider, onSave, busy }: { provider?: Provider; onSave: (value: ProviderInput) => void; busy: boolean }) { + const [name, setName] = useState(provider?.name ?? '') + const [providerType, setProviderType] = useState(provider?.provider_type ?? 'openai-chat') + const [baseUrl, setBaseUrl] = useState(provider?.base_url ?? 'https://api.openai.com/v1') + const [apiKey, setApiKey] = useState('') + const [headers, setHeaders] = useState(JSON.stringify(provider?.custom_headers ?? {}, null, 2)) + + return ( +
{ + event.preventDefault() + onSave({ name, provider_type: providerType, base_url: baseUrl, api_key: apiKey || undefined, custom_headers: JSON.parse(headers) }) + }} + > + setName(e.target.value)} required /> + + + + setBaseUrl(e.target.value)} required /> + setApiKey(e.target.value)} /> +