From 099b9e0116f5f83aaea1e9da19dfb52a67b6b183 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 04:24:14 -0700 Subject: [PATCH 01/75] docs(architecture): classify all 234 tauri commands into backend/frontend/deleted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一网络通信重构的第一步:划清 crates/agent-backend 的边界。 分类标准:读写数据/执行计算 → 后端;在图形界面产生可见效果 → 前端专属; 只为「桌面端 outbound 拨向公网 Gateway」拓扑而存在 → 删除。 234 = 195 后端 + 18 前端专属 + 21 删除 三个实质判断: - 从 gateway 模块救回 8 条真实功能(workspace_watch_set、provider_usage_query/test、 5 个 tunnel),它们与中继拓扑无关,只是恰好登记在 gateway.rs - 隧道要重写而非照搬:后端就在工作机上,slug 注册协议与跨中继分帧不再需要 - open_chat_file_link 需拆分:解析归后端,OS 打开动作归前端 清单以纯文本维护在 command-classes/ 下,附可复现的 diff 校验命令。 清点时有两个坑已记录在文档:generate_handler 块以不带分号的 `]` 结尾; 最后一条注册 services::proxy::proxy_get_server_info 不在 commands:: 命名空间下。 --- docs/architecture/backend-boundary.md | 213 ++++++++++++++++++ docs/architecture/command-classes/backend.txt | 195 ++++++++++++++++ docs/architecture/command-classes/deleted.txt | 21 ++ .../architecture/command-classes/frontend.txt | 18 ++ 4 files changed, 447 insertions(+) create mode 100644 docs/architecture/backend-boundary.md create mode 100644 docs/architecture/command-classes/backend.txt create mode 100644 docs/architecture/command-classes/deleted.txt create mode 100644 docs/architecture/command-classes/frontend.txt diff --git a/docs/architecture/backend-boundary.md b/docs/architecture/backend-boundary.md new file mode 100644 index 000000000..82a91f90a --- /dev/null +++ b/docs/architecture/backend-boundary.md @@ -0,0 +1,213 @@ +# 后端边界 + +本文件是「统一网络通信」重构的**第一交付物**。它把当前 234 个 `#[tauri::command]` 分成三类, +决定哪些进入 `crates/agent-backend`(独立进程、禁 tauri 依赖)、哪些永远留在 Tauri 壳、哪些直接删除。 + +**这份分类是后续所有阶段的前提。** 分错一个,后端就会被迫 `use tauri::`,headless 模式编译不出来。 + +## 划分标准 + +| 判据 | 归属 | +|---|---| +| 读写数据、执行计算、操作文件系统/进程/网络 | **后端** | +| 在某台机器的**图形界面上产生可见效果**(原生对话框、文件管理器、托盘、窗口、通知、全局快捷键、系统剪贴板) | **前端专属** | +| 只为「桌面端主动拨向公网 Gateway」这一拓扑而存在 | **删除** | + +判断口诀:远程模式下,这个 command 在**后端那台机器**上执行还有意义吗? +`system_pick_folder` 会在服务器上弹一个你看不见的对话框 —— 那它就是前端专属。 + +## 总量 + +| 类别 | 数量 | +|---|---| +| 后端(`crates/agent-backend`) | **195** | +| 前端专属(Tauri 壳) | **18** | +| 删除 | **21** | +| 合计 | **234** | + +数据来源:`crates/agent-gui/src-tauri/src/lib.rs` 第 43–286 行的 `tauri::generate_handler![…]` 块。 +该区间内非空非注释行共 234 条,全部匹配 `::::,` 形式,无重复、无遗漏。 + +> ⚠️ 统计时注意两个坑: +> 1. 该块以 ` ]`(**不带分号**)结尾,按 `];` 搜索会落空并一路扫到文件尾, +> 把 `commands::app::WindowPinState` 这类**类型引用**误计为 command。 +> 2. 最后一条注册是 `services::proxy::proxy_get_server_info`,**不在 `commands::` 命名空间下**, +> 只按 `commands::` 前缀过滤会漏掉它。 + +--- + +## A. 后端(195) + +进 `crates/agent-backend`,同时暴露为 HTTP/WS JSON API。前端与 Node 引擎都是它的客户端。 + +| 模块 | 数量 | 备注 | +|---|---|---| +| `chat_history` | 16 | 含 `chat_history_upsert_active_segment` / `append_segment` —— 见下方「落库时机」 | +| `git` | 33 | 34 减去 `git_open_system_file_location` | +| `memory` | 23 | | +| `terminal` | 23 | 含 SSH、本地端口转发、SSH tab | +| `fs` | 16 | 17 减去 `fs_open_workspace_path` | +| `system` | 14 | 18 减去 4 个图形/剪贴板类 | +| `settings` | 13 | | +| `cron` | 10 | 注册名多为 `automation_*` | +| `sftp` | 10 | | +| `subagent_store` | 8 | | +| `process` | 6 | `managed_process_*` | +| `mcp` | 6 | | +| `subagent_worktree` | 4 | | +| `hook` | 3 | | +| `shell` | 2 | `shell_run`、`runtime_cancel` | +| **`tunnel`** | **5** | 从 gateway 模块救回,见下 | +| **`provider_usage`** | **2** | 从 gateway 模块救回,见下 | +| **`workspace_watch`** | **1** | 从 gateway 模块救回,见下 | + +### 从 gateway 模块救回的 8 个 + +`commands/integration/gateway.rs` 里注册了 28 个 command,但其中 8 个**与中继拓扑无关**, +只是恰好登记在这个文件里。它们是真实产品功能,必须进后端: + +| Command | 实际做什么 | 依据 | +|---|---|---| +| `workspace_watch_set` | 工作区文件监听 | `gateway.rs:299` 只调 `workspace_watch.set_desired(WatchSource::Local, …)` | +| `provider_usage_query` | 查 provider 用量 | `gateway.rs:18` 只调 `provider_usage_service.query()` | +| `provider_usage_test` | 测试用量查询配置 | 同上 | +| `gateway_tunnel_state` | 隧道列表 | 见下方「隧道要重写」 | +| `gateway_tunnel_create` | 建隧道 | | +| `gateway_tunnel_update` | 改隧道 | | +| `gateway_tunnel_close` | 关隧道 | | +| `gateway_tunnel_check` | 探测本地端口 | | + +这三组在阶段 2 迁移时必须**脱离 `GatewayController`**,改为直接持有各自的 service。 + +### 隧道要重写,不是照搬 + +隧道的用途是:agent 在工作机上起了个 dev server(如 `:5173`),用户想从浏览器看效果。 + +- **现在**:桌面端把端口注册到公网 Gateway,Gateway 开 `/t/{slug}` 公开反代,并重写 HTML 属性、 + CSS `url()`、注入 shim、改 CSP(`internal/server/tunnel_rewrite.go`,约 1000 行)。 +- **之后**:后端**就在**那台工作机上,且已有网络接口。前端直接请求后端的隧道路由, + 后端代理到 `localhost:5173` 即可。**slug 注册协议、跨中继分帧、公开无认证入口全部不需要。** + +路径前缀重写是否仍然必要,取决于隧道路由是否挂在子路径下 —— 阶段 2 设计时决定。 +名字应从 `gateway_tunnel_*` 改为 `tunnel_*`。 + +### 后端专属但当前由前端触发的 + +| Command | 说明 | +|---|---| +| `system_begin_power_activity` / `system_end_power_activity` | 阻止系统休眠。语义上属于**执行侧** —— 该阻止休眠的是跑任务的那台机器,不是看屏幕的那台。远程模式下前端调它毫无意义。归后端。 | + +### 落库时机(影响阶段 3) + +`chat_history_upsert_active_segment` / `chat_history_append_segment` 目前**只在 turn 结束时被调用一次** +(`runAgentConversationTurn.ts:1217`,位于 1231 行函数的末尾)。 + +阶段 3 之后引擎在后端,「断线重连拉快照」的快照源**必须是引擎内存态**(含正在生成的半条消息), +不能查 SQLite,否则重连会看到会话回退到上一轮结束。详见方案风险 2。 + +--- + +## B. 前端专属(18) + +永远留在 Tauri 壳,**不进后端**。浏览器形态下用运行时探测降级或禁用。 + +| 模块 / Command | 数量 | 理由 | +|---|---|---| +| `app::*` | 7 | 窗口行为、置顶、全局快捷键、退出确认、macOS 红绿灯位置、平台探测 —— 全是窗口/OS 级 | +| `update::*` | 3 | `app_update_check` / `app_update_install` / `app_restart`,更新的是桌面客户端自身 | +| `tray::app_tray_menu_sync` | 1 | 系统托盘 | +| `system_pick_folder` | 1 | `FileDialog::new()` 原生对话框(`system.rs:1338`) | +| `system_pick_file` | 1 | 同上 | +| `system_pick_readable_files` | 1 | 同上 | +| `system_clipboard_read_text` | 1 | 系统剪贴板;浏览器用 `navigator.clipboard` | +| `fs_open_workspace_path` | 1 | 调系统文件管理器打开路径 | +| `git_open_system_file_location` | 1 | 同上(`git.rs:3369`) | +| `open_chat_file_link` | 1 | **需拆分**,见下 | + +### `open_chat_file_link` 必须拆开 + +`chat_file_links.rs:628` 是个混合体。`:431` 处有分支: + +```rust +if inside_workspace && !open_in_file_manager { … } +``` + +- 文件在工作区内且不要求文件管理器 → 返回 `ChatFileLinkOpenResponse { action, kind, workdir, path, line, … }`, + 由前端在应用内打开。**这半边是纯解析,属于后端。** +- 否则 → 调 OS 打开外部编辑器/文件管理器。**这半边属于前端。** + +阶段 2 拆成:后端 `chat_file_link_resolve`(返回 action + 定位信息),前端按 action 决定 +应用内跳转还是调 `plugin-opener`。 + +上表按「含 OS 副作用」计入前端,拆分后后端会 +1、前端保持提供 OS 动作。 + +--- + +## C. 删除(21) + +只为「桌面端 outbound 拨向公网 Gateway」这一拓扑存在。新架构里前端主动连后端,这些全部失去意义。 + +**连接管理(4)** +`gateway_connect` `gateway_disconnect` `gateway_status` `gateway_nudge_connection` + +**chat 中继记账(15)** —— 对应 Gateway 里那 ~3,800 行可靠性补丁的桌面端一侧 +`gateway_send_chat_ingress_batch` `gateway_commit_chat_checkpoint` `gateway_chat_claim_next` +`gateway_chat_mark_started` `gateway_chat_mark_local_started` `gateway_chat_mark_local_cancelled` +`gateway_chat_mark_queued_in_gui` `gateway_chat_complete` `gateway_chat_fail` +`gateway_chat_cancel_request` `gateway_chat_heartbeat` `gateway_chat_runtime_heartbeat` +`gateway_chat_release_lease` `gateway_chat_queue_respond` `gateway_publish_chat_queue_event` + +**同步推送(1)** +`gateway_publish_settings_sync` + +**本地代理自举(1)** +`services::proxy::proxy_get_server_info` —— 现在返回本地 axum 的 `base_url` + 随机 `token` +(`services/proxy.rs:97`),让前端知道去哪调 `/image-proxy` 和 `/proxy/{provider}`。 +新架构下前端本来就持有后端的 `baseUrl` + 密码,这层自举多余; +且阶段 3 之后引擎在后端直连 provider,`/proxy/{provider}` 对前端也不再需要。 + +> 这一条是本次统计新发现的 —— 它不在 `commands::` 命名空间下, +> 按模块前缀过滤的清点方式会整条漏掉。 + +上述 21 个连同 `services/gateway/*.rs`(~4,000 行)、`services/gateway_bridge.rs`(~1,400 行) +在阶段 4 一并删除。 + +--- + +## 编译期防线 + +分类靠自觉守不住。`crates/agent-backend/Cargo.toml` **不得出现任何 tauri 依赖** —— +一旦有人把前端专属 command 挪进后端,`cargo build -p agent-backend` 直接失败。 + +这是方案风险 4 的唯一对策,不要用文档约定代替。 + +## 复核方法 + +本文件的数字全部可复现。命令名清单维护在 `docs/architecture/command-classes/` 下三个文本文件 +(`backend.txt` / `frontend.txt` / `deleted.txt`,每行一个 command 名)。 + +```bash +cd crates/agent-gui/src-tauri/src + +# 从注册块提取真实清单(注意块以不带分号的 `]` 结尾) +sed -n '43,286p' lib.rs \ + | rg -o '^\s+[a-z_]+::[a-z_]+::([a-z_0-9]+),$' -r '$1' \ + | sort > /tmp/registered.txt + +D=../../../../docs/architecture/command-classes +cat $D/backend.txt $D/frontend.txt $D/deleted.txt | sort > /tmp/classified.txt + +diff /tmp/registered.txt /tmp/classified.txt && echo "分类完整,无遗漏无幽灵" +``` + +新增 command 时必须同步更新对应清单文件,否则该 diff 会红。 +建议在阶段 2 把这条 diff 加进 CI,让「新 command 未分类」变成编译失败之外的第二道门禁。 + +> 行号 `43,286` 会随 `lib.rs` 变动。若 diff 报出大量差异,先确认块边界: +> 起点是 `tauri::generate_handler![` 的下一行,终点是与之配对的 ` ]` 行的前一行。 + +## 交叉引用 + +- 总体方案与 19 项决策:`/Users/realm/.claude/plans/go-gateway-git-go-peaceful-wigderson.md` +- 当前 Gateway 职责(将废弃):`docs/architecture/gateway.md` +- 当前整体架构(将重写):`docs/architecture/overview.md` diff --git a/docs/architecture/command-classes/backend.txt b/docs/architecture/command-classes/backend.txt new file mode 100644 index 000000000..38b71a046 --- /dev/null +++ b/docs/architecture/command-classes/backend.txt @@ -0,0 +1,195 @@ +automation_claim_prompt_runs +automation_clear_runs +automation_complete_prompt_run +automation_cron_apply +automation_hooks_apply +automation_list_runs +automation_release_prompt_run +automation_run_cron_now +automation_snapshot +chat_history_append_segment +chat_history_branch +chat_history_delete +chat_history_get_window +chat_history_list +chat_history_rename +chat_history_replace_from_message +chat_history_search +chat_history_set_model +chat_history_set_pinned +chat_history_share_get +chat_history_share_set +chat_history_shared_list +chat_history_upsert +chat_history_upsert_active_segment +chat_history_workdirs +cron_validate_expression +fs_create_dir +fs_delete +fs_edit_text +fs_glob +fs_grep +fs_list +fs_list_dirs +fs_mention_list +fs_path_status +fs_read_editable_text +fs_read_image_source +fs_read_text +fs_read_workspace_image +fs_rename +fs_roots +fs_write_text +gateway_tunnel_check +gateway_tunnel_close +gateway_tunnel_create +gateway_tunnel_state +gateway_tunnel_update +git_add_to_gitignore +git_branches +git_clone_repository +git_clone_repository_cancel +git_clone_repository_dismiss +git_clone_repository_start +git_clone_repository_tasks +git_commit +git_commit_details +git_commit_diff +git_compare_commit_with_remote +git_create_branch +git_delete_branch +git_diff +git_discard +git_discard_all +git_discover_repositories +git_fetch +git_init +git_list_remote_branches +git_log +git_pull +git_push +git_rename_branch +git_set_remote +git_stage +git_stage_all +git_stash_pop +git_stash_push +git_status +git_switch_branch +git_unstage +git_unstage_all +hook_cancel_scope +hook_run_http_requests +hook_run_script +managed_process_clear +managed_process_read_log +managed_process_snapshot +managed_process_start +managed_process_status +managed_process_stop +mcp_call_tool +mcp_list_tools +mcp_restart_server +mcp_runtime_status +mcp_stop_server +mcp_test_server +memory_accept +memory_apply_batch +memory_delete +memory_delete_project +memory_index_overview +memory_list +memory_organize_due_claim +memory_organize_due_complete +memory_organize_run_clear_history +memory_organize_run_create +memory_organize_run_list +memory_organize_run_read +memory_organize_run_update +memory_paths_info +memory_quota_summary +memory_read +memory_recent_rejections +memory_search +memory_today_daily +memory_today_local_date +memory_update +memory_wipe_all +memory_write +provider_usage_query +provider_usage_test +runtime_cancel +settings_apply_ssh_patch +settings_list_ccswitch_providers +settings_list_cherry_studio_providers +settings_list_cherry_studio_providers_from_path +settings_load_all +settings_reset_ssh_known_host +settings_save_agents +settings_save_mcp +settings_save_memory +settings_save_providers +settings_save_remote +settings_save_ssh +settings_save_system +sftp_cancel_transfer +sftp_delete +sftp_list +sftp_mkdir +sftp_read_text +sftp_rename +sftp_stat +sftp_transfer +sftp_transfer_status +sftp_write_text +shell_run +ssh_terminal_tab_close +ssh_terminal_tab_open +ssh_terminal_tabs_list +subagent_identity_list +subagent_identity_upsert +subagent_message_append +subagent_message_list +subagent_run_list +subagent_run_load +subagent_run_prune +subagent_run_save +subagent_worktree_apply +subagent_worktree_cleanup +subagent_worktree_create +subagent_worktree_status +system_append_debug_jsonl +system_begin_power_activity +system_create_project_folder +system_end_power_activity +system_ensure_builtin_skills +system_import_pasted_texts +system_import_readable_file_paths +system_import_uploaded_readable_files +system_list_skill_files +system_manage_skill +system_read_skill_metadata +system_read_skill_text +system_read_uploaded_image_preview +system_read_uploaded_native_attachment +terminal_answer_ssh_prompt +terminal_cancel_ssh_prompt +terminal_close +terminal_close_project +terminal_create +terminal_create_ssh +terminal_list +terminal_read_tail +terminal_rename +terminal_shell_options +terminal_ssh_exec +terminal_ssh_latency +terminal_ssh_local_forward_check_port +terminal_ssh_local_forward_list +terminal_ssh_local_forward_start +terminal_ssh_local_forward_stop +terminal_ssh_reconnect +terminal_stream_attach +terminal_stream_input +terminal_stream_resize +workspace_watch_set diff --git a/docs/architecture/command-classes/deleted.txt b/docs/architecture/command-classes/deleted.txt new file mode 100644 index 000000000..feaf553f9 --- /dev/null +++ b/docs/architecture/command-classes/deleted.txt @@ -0,0 +1,21 @@ +gateway_chat_cancel_request +gateway_chat_claim_next +gateway_chat_complete +gateway_chat_fail +gateway_chat_heartbeat +gateway_chat_mark_local_cancelled +gateway_chat_mark_local_started +gateway_chat_mark_queued_in_gui +gateway_chat_mark_started +gateway_chat_queue_respond +gateway_chat_release_lease +gateway_chat_runtime_heartbeat +gateway_commit_chat_checkpoint +gateway_connect +gateway_disconnect +gateway_nudge_connection +gateway_publish_chat_queue_event +gateway_publish_settings_sync +gateway_send_chat_ingress_batch +gateway_status +proxy_get_server_info diff --git a/docs/architecture/command-classes/frontend.txt b/docs/architecture/command-classes/frontend.txt new file mode 100644 index 000000000..5f7a0ccdf --- /dev/null +++ b/docs/architecture/command-classes/frontend.txt @@ -0,0 +1,18 @@ +app_confirmed_exit +app_macos_traffic_light_metrics +app_restart +app_runtime_platform +app_set_close_window_behavior +app_set_global_shortcuts +app_toggle_window_pin +app_tray_menu_sync +app_update_check +app_update_install +app_window_pinned +fs_open_workspace_path +git_open_system_file_location +open_chat_file_link +system_clipboard_read_text +system_pick_file +system_pick_folder +system_pick_readable_files From 3cf93685a2de667cdb9a52c834f1cca0e0a5b42f Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 04:40:51 -0700 Subject: [PATCH 02/75] refactor(backend): introduce transport-agnostic EventBus, decouple 3 modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 阶段 2 的地基。后端代码此前把两个消费者硬编码在发事件的地方——Tauri 的 AppHandle::emit 和 GatewayController——导致 runtime/services/commands 在编译期 依赖 Tauri 和 Gateway,无法迁入 agent-core。 引入 EventSink trait + EventBus 扇出。后端只管发,谁听由壳决定: - TauriEventSink:接 webview(桌面壳独有,不会迁入 agent-core) - GatewayEventSink:接 Gateway(过渡设施,阶段 4 随 Gateway 一并删除) payload 统一用 serde_json::Value——这些类型本来就要过 JSON IPC,且新架构线上 协议就是 JSON,没有第二种表示值得维护。sink 收到事件后多为重读当前状态再发布, 避免给 11 个 payload 类型逐个追加 Deserialize。 已完全解耦(零 tauri:: / 零 GatewayController): - runtime/managed_process.rs - services/automation/store.rs(保留 scheduler 唤醒——那是业务逻辑不是传输) - services/workspace_watch/ 顺带纠正一处所有权倒置:WorkspaceWatchService 原先由 GatewayController 创建, 现改为后端创建、controller 接收,attach_gateway 随之删除。 GatewayController 引用 65 → 51 处。670 个 Rust 测试全绿(新增 4 个总线测试)。 --- crates/agent-gui/src-tauri/src/events.rs | 152 ++++++++++++++++++ .../agent-gui/src-tauri/src/gateway_sink.rs | 122 ++++++++++++++ crates/agent-gui/src-tauri/src/lib.rs | 29 +++- .../src-tauri/src/runtime/managed_process.rs | 49 ++---- .../src/services/automation/store.rs | 40 ++--- .../src/services/gateway/controller.rs | 3 +- .../src/services/workspace_watch/emit.rs | 44 +---- .../src/services/workspace_watch/mod.rs | 43 ++--- crates/agent-gui/src-tauri/src/tauri_sink.rs | 28 ++++ 9 files changed, 367 insertions(+), 143 deletions(-) create mode 100644 crates/agent-gui/src-tauri/src/events.rs create mode 100644 crates/agent-gui/src-tauri/src/gateway_sink.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_sink.rs diff --git a/crates/agent-gui/src-tauri/src/events.rs b/crates/agent-gui/src-tauri/src/events.rs new file mode 100644 index 000000000..e8b922e4c --- /dev/null +++ b/crates/agent-gui/src-tauri/src/events.rs @@ -0,0 +1,152 @@ +//! 传输无关的事件出口。 +//! +//! 背景:后端逻辑(runtime / services / commands)此前把两个消费者硬编码在发事件的地方—— +//! 一个是 Tauri 的 `AppHandle::emit`,一个是 `GatewayController`。这让「谁在监听」 +//! 变成了后端代码的编译期依赖,后端因此既离不开 Tauri 也离不开 Gateway。 +//! +//! 现在后端只认识 `EventSink`。谁想收事件谁自己注册:桌面壳注册 `TauriEventSink`, +//! Gateway 注册自己的,HTTP 后端注册 WebSocket sink。加一个消费者不需要改任何发事件的代码。 +//! +//! payload 统一用 `serde_json::Value`:这些类型本来就要过 JSON IPC 才能到前端, +//! 且新架构的线上协议就是 JSON(见 docs/architecture/backend-boundary.md), +//! 没有第二种表示值得维护。 + +use std::sync::{Arc, RwLock}; + +use serde::Serialize; + +/// 事件消费者。实现者自行决定把事件送到哪里(webview、WebSocket、日志……)。 +/// +/// 必须是非阻塞的:sink 在业务线程上被同步调用,慢 sink 会拖住发事件的一方。 +/// 需要排队就自己在实现里排。 +pub trait EventSink: Send + Sync { + fn emit_json(&self, event: &str, payload: serde_json::Value); +} + +/// 事件总线:把一条事件扇出给所有已注册的 sink。 +/// +/// 自身也实现 `EventSink`,所以可以嵌套(例如把一组 sink 打包成一个)。 +#[derive(Default)] +pub struct EventBus { + sinks: RwLock>>, +} + +impl EventBus { + pub fn new() -> Self { + Self::default() + } + + pub fn register(&self, sink: Arc) { + match self.sinks.write() { + Ok(mut sinks) => sinks.push(sink), + // 锁中毒说明某个 sink 在 emit 里 panic 了。这里不跟着 panic: + // 事件出口坏掉不该连累正在跑的 agent 任务。 + Err(error) => eprintln!("EventBus::register 拿锁失败(锁已中毒): {error}"), + } + } + + /// 发一条事件。序列化失败只记日志不 panic——事件是旁路,不该影响主流程。 + pub fn emit(&self, event: &str, payload: T) { + match serde_json::to_value(payload) { + Ok(value) => self.emit_json(event, value), + Err(error) => eprintln!("事件 {event} 序列化失败: {error}"), + } + } +} + +impl EventSink for EventBus { + fn emit_json(&self, event: &str, payload: serde_json::Value) { + let sinks = match self.sinks.read() { + Ok(sinks) => sinks.clone(), + Err(error) => { + eprintln!("EventBus::emit_json 拿锁失败(锁已中毒): {error}"); + return; + } + }; + for sink in &sinks { + sink.emit_json(event, payload.clone()); + } + } +} + +/// 没有任何消费者的总线。用于测试,以及后端在壳尚未接上时的启动窗口期。 +pub struct NullSink; + +impl EventSink for NullSink { + fn emit_json(&self, _event: &str, _payload: serde_json::Value) {} +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + #[derive(Default)] + struct RecordingSink { + seen: Mutex>, + } + + impl EventSink for RecordingSink { + fn emit_json(&self, event: &str, payload: serde_json::Value) { + self.seen + .lock() + .expect("recording sink lock") + .push((event.to_string(), payload)); + } + } + + #[test] + fn fans_out_to_every_registered_sink() { + let bus = EventBus::new(); + let a = Arc::new(RecordingSink::default()); + let b = Arc::new(RecordingSink::default()); + bus.register(a.clone()); + bus.register(b.clone()); + + bus.emit("terminal:event", serde_json::json!({ "kind": "output" })); + + for sink in [&a, &b] { + let seen = sink.seen.lock().expect("recording sink lock"); + assert_eq!(seen.len(), 1); + assert_eq!(seen[0].0, "terminal:event"); + assert_eq!(seen[0].1["kind"], "output"); + } + } + + #[test] + fn emitting_without_sinks_is_a_noop() { + let bus = EventBus::new(); + bus.emit("terminal:event", serde_json::json!({})); + } + + #[test] + fn serialization_failure_does_not_panic() { + struct Unserializable; + impl Serialize for Unserializable { + fn serialize(&self, _: S) -> Result { + Err(serde::ser::Error::custom("boom")) + } + } + + let bus = EventBus::new(); + let sink = Arc::new(RecordingSink::default()); + bus.register(sink.clone()); + + bus.emit("bad:event", Unserializable); + + assert!(sink.seen.lock().expect("recording sink lock").is_empty()); + } + + #[test] + fn buses_nest() { + let inner = Arc::new(EventBus::new()); + let leaf = Arc::new(RecordingSink::default()); + inner.register(leaf.clone()); + + let outer = EventBus::new(); + outer.register(inner); + outer.emit("nested", serde_json::json!(1)); + + assert_eq!(leaf.seen.lock().expect("recording sink lock").len(), 1); + } +} diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs new file mode 100644 index 000000000..ce363c102 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -0,0 +1,122 @@ +//! 把 `EventSink` 接到 Gateway 上。 +//! +//! 这是**过渡设施**:Gateway 在阶段 4 整体删除时,本文件一并消失。 +//! +//! 它存在的意义是把「Gateway 想听哪些事件」这件事从后端代码里挪出来。此前 +//! `runtime/`、`services/`、`commands/` 里散落着 65 处 `GatewayController` 引用, +//! 全都是为了推事件——那让后端在编译期依赖 Gateway,无法迁入 agent-core。 +//! 现在后端只管往 `EventBus` 发,Gateway 的知识全部收敛在这里。 +//! +//! 注意:本 sink 拿到事件后多半是**重读当前状态再发布**,而不是把 payload 反序列化回来。 +//! 这些事件推的都是全量快照,重读是幂等的;这样就不必给每个 payload 类型追加 +//! `Deserialize`(共 11 个事件,那是纯粹的噪音)。 + +use std::sync::{Arc, Weak}; + +use crate::events::EventSink; +use crate::runtime::managed_process::MANAGED_PROCESS_CHANGED_EVENT; +use crate::services::automation::types::{CRON_CHANGED_EVENT, HOOKS_CHANGED_EVENT}; +use crate::services::gateway::{now_unix_seconds, proto, GatewayController}; +use crate::services::workspace_watch::{WorkspaceWatchService, WORKSPACE_ACTIVITY_EVENT}; + +pub struct GatewayEventSink { + controller: Weak, + workspace_watch: Weak, +} + +impl GatewayEventSink { + pub fn new( + controller: &Arc, + workspace_watch: &Arc, + ) -> Self { + Self { + controller: Arc::downgrade(controller), + workspace_watch: Arc::downgrade(workspace_watch), + } + } + + /// 工作区活动只转发 Gateway 声明过兴趣的 workdir,且是 best-effort: + /// 丢一条由下一次变更自愈,绝不阻塞 watcher 线程。 + fn forward_workspace_activity( + &self, + controller: &Arc, + payload: &serde_json::Value, + ) { + let Some(workdir) = payload.get("workdir").and_then(|v| v.as_str()) else { + return; + }; + let Some(watch) = self.workspace_watch.upgrade() else { + return; + }; + if !watch.workdir_in_gateway_set(workdir) { + return; + } + let Ok(sender) = controller.current_outbound_sender() else { + return; + }; + + let changed_paths = payload + .get("changedPaths") + .and_then(|v| v.as_array()) + .map(|paths| { + paths + .iter() + .filter_map(|p| p.as_str().map(str::to_string)) + .collect() + }) + .unwrap_or_default(); + + let _ = sender.try_send(proto::AgentEnvelope { + request_id: format!("workspace-activity-{}", uuid::Uuid::new_v4()), + timestamp: now_unix_seconds(), + payload: Some(proto::agent_envelope::Payload::WorkspaceActivity( + proto::WorkspaceActivityEvent { + workdir: workdir.to_string(), + revision: payload.get("revision").and_then(|v| v.as_u64()).unwrap_or(0), + fs: payload.get("fs").and_then(|v| v.as_bool()).unwrap_or(false), + git: payload.get("git").and_then(|v| v.as_bool()).unwrap_or(false), + changed_paths, + truncated: payload + .get("truncated") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + }, + )), + }); + } +} + +impl EventSink for GatewayEventSink { + fn emit_json(&self, event: &str, payload: serde_json::Value) { + let Some(controller) = self.controller.upgrade() else { + return; + }; + + match event { + MANAGED_PROCESS_CHANGED_EVENT => { + tauri::async_runtime::spawn(async move { + if let Err(error) = controller.publish_current_managed_processes().await { + eprintln!("publish managed process snapshot failed: {error}"); + } + }); + } + // cron / hooks 变更都会改变 settings 快照,Gateway 需要重新同步。 + CRON_CHANGED_EVENT | HOOKS_CHANGED_EVENT => { + tauri::async_runtime::spawn(async move { + if let Err(error) = controller.refresh_settings_sync_from_db().await { + eprintln!( + "refresh gateway settings sync after automation change failed: {error}" + ); + } + }); + } + WORKSPACE_ACTIVITY_EVENT => { + self.forward_workspace_activity(&controller, &payload); + } + // 其余事件 Gateway 通过各 registry 自带的 subscriber 通道拿, + // 或者根本不关心。不认识的事件静默丢弃是正确行为: + // 加一个新事件不应该逼着这里跟着改。 + _ => {} + } + } +} diff --git a/crates/agent-gui/src-tauri/src/lib.rs b/crates/agent-gui/src-tauri/src/lib.rs index ee7b77a52..7cf124ec9 100644 --- a/crates/agent-gui/src-tauri/src/lib.rs +++ b/crates/agent-gui/src-tauri/src/lib.rs @@ -1,6 +1,9 @@ mod commands; +mod events; +mod gateway_sink; mod runtime; mod services; +mod tauri_sink; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -722,6 +725,13 @@ pub fn run() { } terminal_registry.attach_app_handle(app.handle().clone()); sftp_registry.attach_app_handle(app.handle().clone()); + // 事件总线:后端只管往这里发,谁听由这里决定。 + // 桌面 webview 和 Gateway 都只是普通订阅者,没有谁被硬编码进后端。 + // 先建空总线,sink 稍后注册——总线用内部可变性,允许后置。 + let events = Arc::new(events::EventBus::new()); + let workspace_watch = Arc::new(services::workspace_watch::WorkspaceWatchService::new( + Arc::clone(&events), + )); let gateway_controller = Arc::new(services::gateway::GatewayController::new( app.handle().clone(), Arc::clone(&automation_store), @@ -731,18 +741,21 @@ pub fn run() { Arc::clone(&sftp_registry), Arc::clone(&managed_process_registry), Arc::clone(&git_clone_task_registry), + Arc::clone(&workspace_watch), )); - managed_process_registry.set_notifier( - runtime::managed_process::ManagedProcessNotifier { - app_handle: app.handle().clone(), - gateway: Arc::downgrade(&gateway_controller), - }, - ); + events.register(Arc::new(tauri_sink::TauriEventSink::new( + app.handle().clone(), + ))); + events.register(Arc::new(gateway_sink::GatewayEventSink::new( + &gateway_controller, + &workspace_watch, + ))); + app.manage(Arc::clone(&events)); + managed_process_registry.set_event_bus(Arc::clone(&events)); managed_process_registry.spawn_startup_reconcile(); managed_process_registry.spawn_monitor(); automation_store.set_notifier(services::automation::AutomationNotifier { - app_handle: app.handle().clone(), - gateway: Arc::downgrade(&gateway_controller), + events: Arc::clone(&events), scheduler: Arc::downgrade(&automation_scheduler), }); Arc::clone(&automation_scheduler).start(); diff --git a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs b/crates/agent-gui/src-tauri/src/runtime/managed_process.rs index 01b29cf0f..a8be89ced 100644 --- a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs +++ b/crates/agent-gui/src-tauri/src/runtime/managed_process.rs @@ -6,11 +6,9 @@ use std::io::{Read, Seek, SeekFrom}; use std::path::{Component, Path, PathBuf}; use std::process::{Child, Stdio}; use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use tauri::Emitter; - use crate::runtime::managed_process_journal as journal; use crate::runtime::platform::{expand_tilde_path, strip_windows_verbatim_prefix}; use crate::runtime::process::{ @@ -19,7 +17,7 @@ use crate::runtime::process::{ ProcessProbe, }; use crate::runtime::shell_runner::spawn_platform_shell_command; -use crate::services::gateway::GatewayController; +use crate::events::EventBus; const PROCESS_LOG_DIR: &str = "process-logs"; const DEFAULT_LOG_BYTES: u64 = 64 * 1024; @@ -35,37 +33,12 @@ const START_TIME_TOLERANCE_MS: i64 = 60_000; pub const MANAGED_PROCESS_CHANGED_EVENT: &str = "managed-process:changed"; -/// Fan-out target for registry mutations: every change emits the same full -/// snapshot to the local webview and (when connected) to the gateway. -pub struct ManagedProcessNotifier { - pub app_handle: tauri::AppHandle, - pub gateway: Weak, -} - -impl ManagedProcessNotifier { - fn changed(&self, snapshot: &ManagedProcessSnapshot) { - if let Err(error) = self - .app_handle - .emit(MANAGED_PROCESS_CHANGED_EVENT, snapshot) - { - eprintln!("emit {MANAGED_PROCESS_CHANGED_EVENT} failed: {error}"); - } - if let Some(gateway) = self.gateway.upgrade() { - let snapshot = snapshot.clone(); - tauri::async_runtime::spawn(async move { - if let Err(error) = gateway.publish_managed_process_snapshot(snapshot).await { - eprintln!("publish managed process snapshot failed: {error}"); - } - }); - } - } -} - pub(crate) struct ManagedProcessRegistry { processes: Mutex>, journal: Mutex>, revision: AtomicU64, - notifier: Mutex>, + /// 事件出口。谁在监听由壳/后端决定,registry 只管发。 + events: Mutex>>, /// This LiveAgent instance's identity, stamped onto journal rows so a /// concurrently running sibling instance never reaps our live children. owner_pid: u32, @@ -391,7 +364,7 @@ impl ManagedProcessRegistry { processes: Mutex::new(HashMap::new()), journal: Mutex::new(conn), revision: AtomicU64::new(revision), - notifier: Mutex::new(None), + events: Mutex::new(None), owner_pid, owner_started_at, } @@ -420,9 +393,9 @@ impl ManagedProcessRegistry { Self::with_parts(Some(conn), revision) } - pub fn set_notifier(&self, notifier: ManagedProcessNotifier) { - if let Ok(mut guard) = self.notifier.lock() { - *guard = Some(notifier); + pub fn set_event_bus(&self, events: Arc) { + if let Ok(mut guard) = self.events.lock() { + *guard = Some(events); } } @@ -480,9 +453,9 @@ impl ManagedProcessRegistry { revision, processes, }; - if let Ok(guard) = self.notifier.lock() { - if let Some(notifier) = guard.as_ref() { - notifier.changed(&snapshot); + if let Ok(guard) = self.events.lock() { + if let Some(events) = guard.as_ref() { + events.emit(MANAGED_PROCESS_CHANGED_EVENT, &snapshot); } } } diff --git a/crates/agent-gui/src-tauri/src/services/automation/store.rs b/crates/agent-gui/src-tauri/src/services/automation/store.rs index 6eac95d5b..35cc6cfcb 100644 --- a/crates/agent-gui/src-tauri/src/services/automation/store.rs +++ b/crates/agent-gui/src-tauri/src/services/automation/store.rs @@ -1,11 +1,10 @@ -use std::sync::{Mutex, Weak}; +use std::sync::{Arc, Mutex, Weak}; use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; use serde_json::Value; -use tauri::Emitter; use uuid::Uuid; -use crate::services::gateway::GatewayController; +use crate::events::EventBus; use super::db; use super::scheduler::AutomationScheduler; @@ -15,51 +14,32 @@ use super::validate; /// Fan-out target for store mutations. All change notifications originate /// here so every writer (UI apply, LLM tool, gateway relay, executor /// decrement) produces exactly the same broadcast. +/// +/// 只保留两件事:往事件总线发(谁在听与本文件无关),以及唤醒调度器—— +/// 后者是真实业务逻辑,不是传输。 pub struct AutomationNotifier { - pub app_handle: tauri::AppHandle, - pub gateway: Weak, + pub events: Arc, pub scheduler: Weak, } impl AutomationNotifier { fn cron_changed(&self, snapshot: &CronSnapshot) { - if let Err(error) = self.app_handle.emit(CRON_CHANGED_EVENT, snapshot) { - eprintln!("emit {CRON_CHANGED_EVENT} failed: {error}"); - } + self.events.emit(CRON_CHANGED_EVENT, snapshot); if let Some(scheduler) = self.scheduler.upgrade() { scheduler.request_reload(); } - self.refresh_gateway(); } fn hooks_changed(&self, snapshot: &HooksSnapshot) { - if let Err(error) = self.app_handle.emit(HOOKS_CHANGED_EVENT, snapshot) { - eprintln!("emit {HOOKS_CHANGED_EVENT} failed: {error}"); - } - self.refresh_gateway(); + self.events.emit(HOOKS_CHANGED_EVENT, snapshot); } fn prompt_pending(&self) { - if let Err(error) = self.app_handle.emit(PROMPT_PENDING_EVENT, ()) { - eprintln!("emit {PROMPT_PENDING_EVENT} failed: {error}"); - } + self.events.emit(PROMPT_PENDING_EVENT, ()); } fn prompt_expired(&self, event: &PromptExpiredEvent) { - if let Err(error) = self.app_handle.emit(PROMPT_EXPIRED_EVENT, event) { - eprintln!("emit {PROMPT_EXPIRED_EVENT} failed: {error}"); - } - } - - fn refresh_gateway(&self) { - let Some(gateway) = self.gateway.upgrade() else { - return; - }; - tauri::async_runtime::spawn(async move { - if let Err(error) = gateway.refresh_settings_sync_from_db().await { - eprintln!("refresh gateway settings sync after automation change failed: {error}"); - } - }); + self.events.emit(PROMPT_EXPIRED_EVENT, event); } } diff --git a/crates/agent-gui/src-tauri/src/services/gateway/controller.rs b/crates/agent-gui/src-tauri/src/services/gateway/controller.rs index 23507b243..0eaf185d1 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/controller.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/controller.rs @@ -32,11 +32,11 @@ impl GatewayController { sftp_registry: Arc, managed_process_registry: Arc, git_clone_task_registry: Arc, + workspace_watch: Arc, ) -> Self { let initial_config = RemoteSettingsPayload::default(); let (config_tx, _) = watch::channel(initial_config); let tunnel_store = TunnelStore::new(app_handle.clone()); - let workspace_watch = Arc::new(WorkspaceWatchService::new(app_handle.clone())); let chat_ingress = ChatIngressMirror::spawn(app_handle.clone()); Self { app_handle, @@ -85,7 +85,6 @@ impl GatewayController { } pub fn start(self: &Arc) -> Result<(), String> { - self.workspace_watch.attach_gateway(Arc::downgrade(self)); self.start_terminal_forwarder(); self.start_terminal_stream_forwarder(); self.start_sftp_forwarder(); diff --git a/crates/agent-gui/src-tauri/src/services/workspace_watch/emit.rs b/crates/agent-gui/src-tauri/src/services/workspace_watch/emit.rs index f8e3f8e8e..14547eab4 100644 --- a/crates/agent-gui/src-tauri/src/services/workspace_watch/emit.rs +++ b/crates/agent-gui/src-tauri/src/services/workspace_watch/emit.rs @@ -1,12 +1,11 @@ -//! Dual emit sinks for workspace activity: the local webview event is -//! unconditional; the gateway envelope goes out only for workdirs the gateway -//! declared interest in, and only best-effort (never blocking a watcher -//! thread — a dropped event is healed by the next change). +//! 工作区活动事件的出口。 +//! +//! 只负责组装 payload 并投给事件总线。谁想要这些事件——桌面 webview、Gateway、 +//! 将来的 HTTP 后端——各自注册 sink,与本文件无关。 +//! +//! best-effort:丢一条事件由下一次变更自愈,绝不阻塞 watcher 线程。 use serde::Serialize; -use tauri::Emitter; - -use crate::services::gateway::{now_unix_seconds, proto}; use super::{WorkspaceWatchService, WORKSPACE_ACTIVITY_EVENT}; @@ -42,35 +41,6 @@ impl WorkspaceWatchService { truncated, }; - if let Err(error) = self - .app_handle - .emit(WORKSPACE_ACTIVITY_EVENT, payload.clone()) - { - eprintln!("emit workspace activity failed: {error}"); - } - - if !self.workdir_in_gateway_set(workdir) { - return; - } - let Some(controller) = self.current_gateway() else { - return; - }; - let Ok(sender) = controller.current_outbound_sender() else { - return; - }; - let _ = sender.try_send(proto::AgentEnvelope { - request_id: format!("workspace-activity-{}", uuid::Uuid::new_v4()), - timestamp: now_unix_seconds(), - payload: Some(proto::agent_envelope::Payload::WorkspaceActivity( - proto::WorkspaceActivityEvent { - workdir: payload.workdir, - revision: payload.revision, - fs: payload.fs, - git: payload.git, - changed_paths: payload.changed_paths, - truncated: payload.truncated, - }, - )), - }); + self.events.emit(WORKSPACE_ACTIVITY_EVENT, payload); } } diff --git a/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs b/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs index e8b305953..c77c1a837 100644 --- a/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs +++ b/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs @@ -1,21 +1,20 @@ //! Workspace activity watch service: watches workdirs for filesystem / git -//! state changes and pushes debounced invalidation events to both the local -//! webview (`workspace:activity`) and, for gateway-requested workdirs, to the -//! remote gateway as `AgentEnvelope{workspace_activity}`. +//! state changes and pushes debounced invalidation events to the event bus +//! (`workspace:activity`). Who consumes them — the desktop webview, the +//! gateway, a future HTTP backend — is not this module's business. //! -//! Two declarative sources feed the desired watch set — the local webview -//! (Tauri command `workspace_watch_set`) and the gateway -//! (`GatewayEnvelope{workspace_watch}`). The actual watch set is their union; -//! each `set_desired` call replaces one source's set wholesale and reconciles -//! watchers against the new union. +//! Two declarative sources feed the desired watch set — the local frontend +//! (`workspace_watch_set`) and the gateway (`GatewayEnvelope{workspace_watch}`). +//! The actual watch set is their union; each `set_desired` call replaces one +//! source's set wholesale and reconciles watchers against the new union. mod emit; mod watcher; use std::collections::{BTreeSet, HashMap}; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::{Arc, Mutex}; -use crate::services::gateway::GatewayController; +use crate::events::EventBus; pub const WORKSPACE_ACTIVITY_EVENT: &str = "workspace:activity"; @@ -35,8 +34,7 @@ struct WatchInner { } pub struct WorkspaceWatchService { - app_handle: tauri::AppHandle, - gateway: Mutex>>, + events: Arc, inner: Mutex, // Per-workdir monotonic revision counters. Kept outside WatchInner so they // survive watcher teardown/recreation: a re-watched workdir must not @@ -45,23 +43,14 @@ pub struct WorkspaceWatchService { } impl WorkspaceWatchService { - pub fn new(app_handle: tauri::AppHandle) -> Self { + pub fn new(events: Arc) -> Self { Self { - app_handle, - gateway: Mutex::new(None), + events, inner: Mutex::new(WatchInner::default()), revisions: Mutex::new(HashMap::new()), } } - /// Wires the gateway sink. Called once after the controller Arc exists; - /// a Weak reference keeps the ownership acyclic (controller owns service). - pub fn attach_gateway(&self, controller: Weak) { - if let Ok(mut slot) = self.gateway.lock() { - *slot = Some(controller); - } - } - /// Replaces one source's desired workdir set and reconciles watchers /// against the union of both sources. pub fn set_desired(self: &Arc, source: WatchSource, workdirs: Vec) { @@ -93,17 +82,15 @@ impl WorkspaceWatchService { } } - pub(crate) fn workdir_in_gateway_set(&self, workdir: &str) -> bool { + /// Gateway 兴趣集查询。Gateway 侧的 sink 用它决定要不要把某条活动事件转发出去—— + /// 兴趣集归本服务管(它本来就按 source 分开存),转发归 sink 管。 + pub fn workdir_in_gateway_set(&self, workdir: &str) -> bool { self.inner .lock() .map(|inner| inner.gateway.contains(workdir)) .unwrap_or(false) } - pub(crate) fn current_gateway(&self) -> Option> { - self.gateway.lock().ok()?.as_ref()?.upgrade() - } - /// Per-workdir monotonic revision. A poisoned lock yields 0, which clients /// already treat as a revision regression (forced dirty) — fail-safe. pub(crate) fn next_revision(&self, workdir: &str) -> u64 { diff --git a/crates/agent-gui/src-tauri/src/tauri_sink.rs b/crates/agent-gui/src-tauri/src/tauri_sink.rs new file mode 100644 index 000000000..554563c8a --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_sink.rs @@ -0,0 +1,28 @@ +//! 把 `EventSink` 接到 Tauri webview 上。 +//! +//! 这是桌面壳独有的东西,**不会**跟随 runtime/services/commands 迁入 agent-core—— +//! agent-core 必须编译时就不认识 Tauri。 + +use tauri::{AppHandle, Emitter}; + +use crate::events::EventSink; + +pub struct TauriEventSink { + app: AppHandle, +} + +impl TauriEventSink { + pub fn new(app: AppHandle) -> Self { + Self { app } + } +} + +impl EventSink for TauriEventSink { + fn emit_json(&self, event: &str, payload: serde_json::Value) { + // emit 失败通常意味着 webview 已经销毁(退出竞态)。这属于正常情况, + // 只记日志:事件是旁路,丢一条不该影响正在跑的任务。 + if let Err(error) = self.app.emit(event, payload) { + eprintln!("emit {event} 到 webview 失败: {error}"); + } + } +} From 231faefb60672033ff8216f85a75632811039caf Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 04:46:06 -0700 Subject: [PATCH 03/75] refactor(backend): route history sync through EventBus, drop gateway coupling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chat_history 的 8 个 command 此前直接调 gateway_controller.publish_history_sync()。 改为发 history:upsert / history:delete 事件,Gateway 侧在 GatewayEventSink 里消费。 这两个事件是面向未来的:新架构下前端本身就是远程客户端,同样需要 history 变更 通知,不再是 Gateway 专属。ChatHistorySummary 因此补上 Deserialize——事件消费者 要把 payload 读回来(这是少数几个必须读 payload 的事件,重读数据库拿不到 「刚才改的是哪一条」)。 发现并记录一处阶段 1 分类缺陷:settings_save_remote 是「部分删除」, RemoteSettingsPayload 混了 Gateway 连接字段(随 Gateway 删)和 enable_web_* 权限 开关(新架构仍需要);且 apply_config 是命令不是事件,返回 Result 传播错误, 转事件会丢错误语义。该条暂留耦合,处置方案写入 backend-boundary.md。 后端代码里的 GatewayController 引用 65 → 40 处,剩余全部集中在: commands/integration/gateway.rs 27(其中 20 个命令阶段 4 直接删除) services/tunnel/* 11(按阶段 1 结论需重写而非搬运) commands/config/settings/* 2(上述已记录的部分删除项) 即所有应当迁入 agent-core 的后端模块,现已全部 gateway-free。670 测试全绿。 --- .../commands/history/chat_history/branch.rs | 6 +-- .../commands/history/chat_history/commands.rs | 42 +++++++------------ .../commands/history/chat_history/delete.rs | 6 +-- .../src/commands/history/chat_history/mod.rs | 11 +++-- .../commands/history/chat_history/replace.rs | 6 +-- .../commands/history/chat_history/types.rs | 4 +- .../agent-gui/src-tauri/src/gateway_sink.rs | 33 ++++++++++++++- docs/architecture/backend-boundary.md | 16 +++++++ 8 files changed, 78 insertions(+), 46 deletions(-) diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs index a3367f4e4..b8336e322 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs @@ -226,11 +226,9 @@ pub(crate) async fn chat_history_branch_inner( pub async fn chat_history_branch( id: String, base_message_ref: ChatHistoryMessageRef, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let summary = chat_history_branch_inner(id, base_message_ref).await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) } diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs index cd4355afc..b0160b91e 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs @@ -280,12 +280,10 @@ pub(crate) async fn chat_history_upsert_inner( #[tauri::command] pub async fn chat_history_upsert( input: ChatHistoryUpsertInput, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let summary = chat_history_upsert_inner(input).await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) } @@ -315,12 +313,10 @@ pub(crate) async fn chat_history_upsert_active_segment_inner( #[tauri::command] pub async fn chat_history_upsert_active_segment( input: ChatHistorySegmentMutationInput, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let summary = chat_history_upsert_active_segment_inner(input).await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) } @@ -351,12 +347,10 @@ pub(crate) async fn chat_history_append_segment_inner( #[tauri::command] pub async fn chat_history_append_segment( input: ChatHistorySegmentMutationInput, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let summary = chat_history_append_segment_inner(input).await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) } @@ -376,12 +370,10 @@ pub(crate) async fn chat_history_rename_inner( pub async fn chat_history_rename( id: String, title: String, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let summary = chat_history_rename_inner(id, title).await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) } @@ -401,12 +393,10 @@ pub(crate) async fn chat_history_set_pinned_inner( pub async fn chat_history_set_pinned( id: String, is_pinned: bool, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let summary = chat_history_set_pinned_inner(id, is_pinned).await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) } @@ -426,12 +416,10 @@ pub(crate) async fn chat_history_set_model_inner( pub async fn chat_history_set_model( id: String, selected_model_json: String, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let summary = chat_history_set_model_inner(id, selected_model_json).await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) } @@ -469,14 +457,12 @@ pub async fn chat_history_share_set( id: String, enabled: bool, redact_tool_content: Option, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let status = chat_history_share_set_inner(id, enabled, redact_tool_content).await?; match chat_history_get_summary_inner(status.conversation_id.clone()).await { Ok(summary) => { - gateway_controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &summary); } Err(error) => eprintln!("publish history share sync event failed: {error}"), } diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs index d47b76add..b108e547d 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs @@ -62,12 +62,10 @@ pub(crate) async fn chat_history_delete_inner(id: String) -> Result<(), String> #[tauri::command] pub async fn chat_history_delete( id: String, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result<(), String> { let conversation_id = id.trim().to_string(); chat_history_delete_inner(id).await?; - gateway_controller - .publish_history_sync(build_history_sync_delete(conversation_id)) - .await; + events.emit(HISTORY_DELETE_EVENT, conversation_id); Ok(()) } diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs index 349bd5c24..f122df019 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs @@ -11,13 +11,16 @@ use std::{ use crate::{ commands::{history_db, subagent_store}, - services::{ - gateway::{build_history_sync_delete, build_history_sync_upsert, GatewayController}, - memory::{MemoryHistorySearchMatch, MemorySearchArgs}, - }, + events::EventBus, + services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}, }; use uuid::Uuid; +/// 会话被创建或更新。payload 是完整的 `ChatHistorySummary`。 +pub const HISTORY_UPSERT_EVENT: &str = "history:upsert"; +/// 会话被删除。payload 是被删会话的 id 字符串。 +pub const HISTORY_DELETE_EVENT: &str = "history:delete"; + const HISTORY_SHARE_TOKEN_LEN: usize = 9; const HISTORY_SHARE_TOKEN_INSERT_ATTEMPTS: usize = 8; const HISTORY_SHARE_TOKEN_ALPHABET: &[u8] = diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs index cb1e173f9..e54ea6abe 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs @@ -160,7 +160,7 @@ pub async fn chat_history_replace_from_message( replacement_message: Value, max_messages: i64, expected_revision: String, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result { let result = chat_history_replace_from_message_inner( id, @@ -170,8 +170,6 @@ pub async fn chat_history_replace_from_message( expected_revision, ) .await?; - gateway_controller - .publish_history_sync(build_history_sync_upsert(&result.conversation)) - .await; + events.emit(HISTORY_UPSERT_EVENT, &result.conversation); Ok(result) } diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/types.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/types.rs index 69f538ca4..871f44cfc 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/types.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/types.rs @@ -1,4 +1,6 @@ -#[derive(Debug, Clone, Serialize)] +// Deserialize 是为了事件消费者:history 变更事件把整个 summary 作为 payload 发出, +// 订阅方(Gateway 中继、将来的 WebSocket 后端)需要把它读回来。 +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChatHistorySummary { pub id: String, diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs index ce363c102..a45df59d9 100644 --- a/crates/agent-gui/src-tauri/src/gateway_sink.rs +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -13,10 +13,16 @@ use std::sync::{Arc, Weak}; +use crate::commands::chat_history::{ + ChatHistorySummary, HISTORY_DELETE_EVENT, HISTORY_UPSERT_EVENT, +}; use crate::events::EventSink; use crate::runtime::managed_process::MANAGED_PROCESS_CHANGED_EVENT; use crate::services::automation::types::{CRON_CHANGED_EVENT, HOOKS_CHANGED_EVENT}; -use crate::services::gateway::{now_unix_seconds, proto, GatewayController}; +use crate::services::gateway::{ + build_history_sync_delete, build_history_sync_upsert, now_unix_seconds, proto, + GatewayController, +}; use crate::services::workspace_watch::{WorkspaceWatchService, WORKSPACE_ACTIVITY_EVENT}; pub struct GatewayEventSink { @@ -113,6 +119,31 @@ impl EventSink for GatewayEventSink { WORKSPACE_ACTIVITY_EVENT => { self.forward_workspace_activity(&controller, &payload); } + // history 是少数几个必须把 payload 读回来的事件:Gateway 要把整个 + // summary 转成 sync 事件发给远端,重读数据库拿不到「刚才改的是哪一条」。 + HISTORY_UPSERT_EVENT => { + match serde_json::from_value::(payload) { + Ok(summary) => { + tauri::async_runtime::spawn(async move { + controller + .publish_history_sync(build_history_sync_upsert(&summary)) + .await; + }); + } + Err(error) => eprintln!("history upsert 事件反序列化失败: {error}"), + } + } + HISTORY_DELETE_EVENT => { + let Some(conversation_id) = payload.as_str().map(str::to_string) else { + eprintln!("history delete 事件 payload 不是字符串"); + return; + }; + tauri::async_runtime::spawn(async move { + controller + .publish_history_sync(build_history_sync_delete(conversation_id)) + .await; + }); + } // 其余事件 Gateway 通过各 registry 自带的 subscriber 通道拿, // 或者根本不关心。不认识的事件静默丢弃是正确行为: // 加一个新事件不应该逼着这里跟着改。 diff --git a/docs/architecture/backend-boundary.md b/docs/architecture/backend-boundary.md index 82a91f90a..0b959f6bf 100644 --- a/docs/architecture/backend-boundary.md +++ b/docs/architecture/backend-boundary.md @@ -105,6 +105,22 @@ 阶段 3 之后引擎在后端,「断线重连拉快照」的快照源**必须是引擎内存态**(含正在生成的半条消息), 不能查 SQLite,否则重连会看到会话回退到上一轮结束。详见方案风险 2。 +### 已知分类缺陷:`settings_save_remote` 是「部分删除」 + +三分类容不下这一条,实施阶段 2 时发现。`RemoteSettingsPayload` +(`commands/config/settings/types.rs:23`)混了两类字段: + +| 字段 | 归属 | +|---|---| +| `gateway_url`、`gateway_port`、`token`、`agent_id`、`auto_reconnect`、`heartbeat_interval` | **删除** —— 纯粹是「连到哪个 Gateway」,新架构里前端连后端,没有这个概念 | +| `enable_web_terminal`、`enable_web_ssh_terminal`、`enable_web_git` 等权限开关 | **后端** —— 门控远程前端能干什么,依然需要 | + +而且 `settings_save_remote` 调的 `gateway_controller.apply_config()` 是**命令不是事件**: +它返回 `Result` 并向调用方传播错误,转成事件总线会丢掉错误语义。 + +处置:阶段 2 保持这一条与 Gateway 耦合(它本来就是 Gateway 命令),迁移 `commands/config/settings/` +时把这个函数留在壳里。阶段 4 拆成两半——连接字段随 Gateway 删除,权限开关并入后端的访问控制设置。 + --- ## B. 前端专属(18) From a2e22a93936f7d1e6392dc4d3c0af26e07ea2a94 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 04:50:13 -0700 Subject: [PATCH 04/75] refactor(backend): move terminal/sftp event emission onto the EventBus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TerminalSessionRegistry、SshLocalForwardRegistry、SftpSessionRegistry 原先各自持有 Mutex> 并在 broadcast 里硬编码 app_handle.emit(),同时又往 subscribers 通道发一份(Gateway 靠后者中继)。前者删除,改走 EventBus—— webview 从此只是总线上的一个普通订阅者,与 subscribers 通道地位对等。 attach_app_handle → set_event_bus。lib.rs 里的调用点相应后移到事件总线创建之后。 至此后端代码的结构性 tauri 耦合清零,仅剩两类且都符合预期: services/tunnel/store.rs 3 处(阶段 1 已定:隧道需重写而非搬运) commands/app/{app,tray,update} 6 处(前端专属命令,本就该留在壳里) 670 测试全绿。 --- crates/agent-gui/src-tauri/src/lib.rs | 4 ++-- .../agent-gui/src-tauri/src/runtime/sftp.rs | 19 ++++++++++--------- .../src-tauri/src/runtime/terminal/events.rs | 19 +++++++++---------- .../src-tauri/src/runtime/terminal/mod.rs | 4 ++-- .../src/runtime/terminal/registry.rs | 8 ++++---- .../src/runtime/terminal/ssh_local_forward.rs | 7 +++---- 6 files changed, 30 insertions(+), 31 deletions(-) diff --git a/crates/agent-gui/src-tauri/src/lib.rs b/crates/agent-gui/src-tauri/src/lib.rs index 7cf124ec9..84ee9e719 100644 --- a/crates/agent-gui/src-tauri/src/lib.rs +++ b/crates/agent-gui/src-tauri/src/lib.rs @@ -723,8 +723,6 @@ pub fn run() { if let Err(error) = services::skills::ensure_builtin_agent_skills_sync() { eprintln!("failed to seed builtin skills: {error}"); } - terminal_registry.attach_app_handle(app.handle().clone()); - sftp_registry.attach_app_handle(app.handle().clone()); // 事件总线:后端只管往这里发,谁听由这里决定。 // 桌面 webview 和 Gateway 都只是普通订阅者,没有谁被硬编码进后端。 // 先建空总线,sink 稍后注册——总线用内部可变性,允许后置。 @@ -752,6 +750,8 @@ pub fn run() { ))); app.manage(Arc::clone(&events)); managed_process_registry.set_event_bus(Arc::clone(&events)); + terminal_registry.set_event_bus(Arc::clone(&events)); + sftp_registry.set_event_bus(Arc::clone(&events)); managed_process_registry.spawn_startup_reconcile(); managed_process_registry.spawn_monitor(); automation_store.set_notifier(services::automation::AutomationNotifier { diff --git a/crates/agent-gui/src-tauri/src/runtime/sftp.rs b/crates/agent-gui/src-tauri/src/runtime/sftp.rs index e26335e69..b6c8e4944 100644 --- a/crates/agent-gui/src-tauri/src/runtime/sftp.rs +++ b/crates/agent-gui/src-tauri/src/runtime/sftp.rs @@ -6,10 +6,11 @@ use std::path::{Component, Path, PathBuf}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{mpsc, Arc, Mutex}; use std::time::{SystemTime, UNIX_EPOCH}; -use tauri::{AppHandle, Emitter}; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; use uuid::Uuid; +use crate::events::EventBus; + use crate::runtime::platform::expand_tilde_path; use crate::runtime::project_path::{ project_path_key as normalize_project_path_key, project_path_keys_equal, @@ -118,7 +119,7 @@ pub struct SftpSessionRegistry { sessions: Mutex>, transfers: Mutex>>, transfer_states: Mutex>, - app_handle: Mutex>, + events: Mutex>>, subscribers: Arc>>>, next_subscriber_id: AtomicUsize, } @@ -158,15 +159,15 @@ impl SftpSessionRegistry { sessions: Mutex::new(HashMap::new()), transfers: Mutex::new(HashMap::new()), transfer_states: Mutex::new(HashMap::new()), - app_handle: Mutex::new(None), + events: Mutex::new(None), subscribers: Arc::new(Mutex::new(HashMap::new())), next_subscriber_id: AtomicUsize::new(0), } } - pub fn attach_app_handle(&self, app_handle: AppHandle) { - if let Ok(mut slot) = self.app_handle.lock() { - *slot = Some(app_handle); + pub fn set_event_bus(&self, events: Arc) { + if let Ok(mut slot) = self.events.lock() { + *slot = Some(events); } } @@ -634,9 +635,9 @@ impl SftpSessionRegistry { transfer_states.insert(key, payload.transfer.clone()); } - if let Ok(app_handle) = self.app_handle.lock() { - if let Some(app_handle) = app_handle.as_ref() { - let _ = app_handle.emit(SFTP_EVENT_NAME, &payload); + if let Ok(events) = self.events.lock() { + if let Some(events) = events.as_ref() { + events.emit(SFTP_EVENT_NAME, &payload); } } diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/events.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/events.rs index 826694dd9..b1f30c161 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/events.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/events.rs @@ -1,5 +1,4 @@ use std::sync::Arc; -use tauri::Emitter; use super::*; @@ -27,9 +26,9 @@ impl TerminalSessionRegistry { ssh_local_forward: None, }; - if let Ok(app_handle) = self.app_handle.lock() { - if let Some(app_handle) = app_handle.as_ref() { - let _ = app_handle.emit(TERMINAL_EVENT_NAME, &payload); + if let Ok(events) = self.events.lock() { + if let Some(events) = events.as_ref() { + events.emit(TERMINAL_EVENT_NAME, &payload); } } @@ -101,10 +100,10 @@ impl TerminalSessionRegistry { if payloads.is_empty() { return; } - if let Ok(app_handle) = self.app_handle.lock() { - if let Some(app_handle) = app_handle.as_ref() { + if let Ok(events) = self.events.lock() { + if let Some(events) = events.as_ref() { for payload in payloads { - let _ = app_handle.emit(TERMINAL_STREAM_EVENT_NAME, payload); + events.emit(TERMINAL_STREAM_EVENT_NAME, payload); } } } @@ -145,9 +144,9 @@ impl TerminalSessionRegistry { ssh_local_forward: None, }; - if let Ok(app_handle) = self.app_handle.lock() { - if let Some(app_handle) = app_handle.as_ref() { - let _ = app_handle.emit(TERMINAL_EVENT_NAME, &payload); + if let Ok(events) = self.events.lock() { + if let Some(events) = events.as_ref() { + events.emit(TERMINAL_EVENT_NAME, &payload); } } diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs index 3b08f17a4..787af2c18 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; use std::sync::atomic::AtomicUsize; use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; -use tauri::AppHandle; +use crate::events::EventBus; mod events; mod output; @@ -84,7 +84,7 @@ pub struct TerminalSessionRegistry { pending_ssh_prompts: Mutex>, ssh_terminal_tabs_tx: Mutex<()>, ssh_terminal_tabs: Mutex>, - app_handle: Mutex>, + events: Mutex>>, subscribers: Arc>>>, stream_subscribers: Arc>>>, echo_dispatch: Mutex>, diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs index 5fb004ad7..c3777f8cb 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs @@ -4,8 +4,8 @@ use std::io::{Read, Write}; use std::sync::atomic::Ordering; use std::sync::{mpsc, Arc, Mutex}; use std::thread; -use tauri::AppHandle; +use crate::events::EventBus; use crate::runtime::project_path::{ project_path_key as normalize_project_path_key, project_path_keys_equal, }; @@ -13,9 +13,9 @@ use crate::runtime::project_path::{ use super::*; impl TerminalSessionRegistry { - pub fn attach_app_handle(&self, app_handle: AppHandle) { - if let Ok(mut slot) = self.app_handle.lock() { - *slot = Some(app_handle); + pub fn set_event_bus(&self, events: Arc) { + if let Ok(mut slot) = self.events.lock() { + *slot = Some(events); } } diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs index 41fb20b5c..c650c2cbe 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::net::Ipv4Addr; use std::sync::{Arc, Mutex, Weak}; -use tauri::Emitter; use tokio::io::{copy_bidirectional, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{watch, Semaphore}; @@ -379,9 +378,9 @@ impl TerminalSessionRegistry { // The desktop webview listens on the dedicated channel; gateway // subscribers get a terminal event so the WebUI shares the stream // without a second event pipeline. - if let Ok(app_handle) = self.app_handle.lock() { - if let Some(app_handle) = app_handle.as_ref() { - let _ = app_handle.emit(SSH_LOCAL_FORWARD_EVENT_NAME, payload.clone()); + if let Ok(events) = self.events.lock() { + if let Some(events) = events.as_ref() { + events.emit(SSH_LOCAL_FORWARD_EVENT_NAME, payload.clone()); } } let subscribers = self From 1336a48f107e2469bdb9f9534588ddb651717704 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 05:21:16 -0700 Subject: [PATCH 05/75] feat(agent-core): create tauri-free backend crate, migrate EventBus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 建立 crates/agent-core 并接通 src-tauri。首个迁入模块是 events.rs(EventBus/ EventSink),它零外部依赖、是后端与壳之间的唯一事件契约,适合作为跨 crate 编译的验证载荷。 编译期防线已生效:cargo tree -p agent-core 零 tauri 依赖。这是 backend-boundary.md 所述对策的落地——把前端专属 command 挪进 agent-core 会直接 构建失败,而不是等到 headless 跑不起来才发现。 新增 docs/architecture/backend-split.md,记录两项**实验/源码核验**结论, 均与并行分析给出的说法相左: 1. include!() 不是 blocker(分析曾列为头号 blocker)。实验证明其路径相对于宏调用 所在的物理文件,与 crate 边界无关;现有 settings/mod.rs → ssh/mod.rs → load.rs 的嵌套 include 本身就是反证。真正的问题是 pub(crate) 跨 crate 变私有(E0603), 而 include! 把文件摊平进一个模块放大了这个面。待迁移代码有 580 处 pub(crate), 但壳侧只引用 29 个符号路径,故只提升跨界的那些。 2. tauri::async_runtime → tokio 可以机械替换,但前提被分析漏掉了: default_runtime() 会 TokioRuntime::new().unwrap() 自建 runtime,故 tauri 版在 任何上下文可调,而 tokio::spawn 在 runtime 外会 panic。已逐站点核查后端所有 spawn 均只从 async 上下文可达;PTY 读线程的裸 thread::spawn 不碰 async runtime。 路由约定定为命令式 POST /api/(流式走 WS),不做 REST 化—— 命令名与参数 JSON 前端现在就在用,1:1 映射让阶段 4 是机械替换;REST 化等于做 195 次独立设计决策,每次都是一个破坏前端的机会。 670 测试全绿(agent-core 4 + liveagent 666)。 --- .../page-2026-08-03T01-16-23-922Z.yml | 52 +++++++++++ .../page-2026-08-03T01-36-52-495Z.yml | 48 +++++++++++ .../page-2026-08-03T01-42-24-989Z.yml | 52 +++++++++++ Cargo.lock | 10 +++ Cargo.toml | 2 +- crates/agent-core/Cargo.toml | 14 +++ .../src-tauri => agent-core}/src/events.rs | 0 crates/agent-core/src/lib.rs | 12 +++ crates/agent-gui/src-tauri/Cargo.toml | 1 + .../src/commands/history/chat_history/mod.rs | 3 +- .../agent-gui/src-tauri/src/gateway_sink.rs | 2 +- crates/agent-gui/src-tauri/src/lib.rs | 3 +- .../src-tauri/src/runtime/managed_process.rs | 2 +- .../agent-gui/src-tauri/src/runtime/sftp.rs | 2 +- .../src-tauri/src/runtime/terminal/mod.rs | 2 +- .../src/runtime/terminal/registry.rs | 2 +- .../src/services/automation/store.rs | 2 +- .../src/services/workspace_watch/mod.rs | 2 +- crates/agent-gui/src-tauri/src/tauri_sink.rs | 2 +- docs/architecture/backend-split.md | 86 +++++++++++++++++++ 20 files changed, 287 insertions(+), 12 deletions(-) create mode 100644 .playwright-mcp/page-2026-08-03T01-16-23-922Z.yml create mode 100644 .playwright-mcp/page-2026-08-03T01-36-52-495Z.yml create mode 100644 .playwright-mcp/page-2026-08-03T01-42-24-989Z.yml create mode 100644 crates/agent-core/Cargo.toml rename crates/{agent-gui/src-tauri => agent-core}/src/events.rs (100%) create mode 100644 crates/agent-core/src/lib.rs create mode 100644 docs/architecture/backend-split.md diff --git a/.playwright-mcp/page-2026-08-03T01-16-23-922Z.yml b/.playwright-mcp/page-2026-08-03T01-16-23-922Z.yml new file mode 100644 index 000000000..68bc06204 --- /dev/null +++ b/.playwright-mcp/page-2026-08-03T01-16-23-922Z.yml @@ -0,0 +1,52 @@ +- generic [ref=e6]: + - complementary [ref=e7]: + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e11]: Live Agent + - button "关闭边栏" [ref=e15] + - generic [ref=e20]: + - button "新对话" [ref=e21] + - button "Skills" [ref=e27] + - button "MCP" [ref=e33] + - generic [ref=e40]: + - generic [ref=e41]: + - button "工作空间" [expanded] [ref=e42] + - button "新建工作空间" [ref=e46] + - button "调整工作空间与最近对话占比" [ref=e51] + - button "最近会话" [expanded] [ref=e54] + - paragraph [ref=e62]: 暂无历史对话 + - button "设置" [ref=e65] + - generic [ref=e71]: + - generic [ref=e72]: + - banner [ref=e73]: + - generic [ref=e74]: + - button "选择模型" [disabled] + - generic [ref=e75]: + - button "切换到深色模式" [ref=e76] + - button "Select a project to use project tools." [disabled] + - generic [ref=e79]: + - paragraph [ref=e84]: Cannot read properties of undefined (reading 'invoke') + - button [ref=e85] + - generic [ref=e88]: + - generic [ref=e92]: + - generic [ref=e97]: 欢迎使用 LiveAgent + - generic [ref=e98]: 你还没有配置模型。 + - generic [ref=e99]: 点击下方按钮,配置供应商和模型。 + - button "配置供应商" [ref=e100] + - generic: + - button [ref=e105] + - separator "调整对话正文宽度" [ref=e107] + - generic [ref=e110]: + - button "扩大输入框" [ref=e111] + - textbox [ref=e116]: 输入消息,@ 引用文件,提示词可队列发送... + - generic [ref=e117]: + - generic [ref=e118]: + - generic [ref=e119]: + - button "请先选择一个项目" [disabled] + - button "联网搜索已开启" [ref=e121] + - generic [ref=e126]: + - button "当前模型不支持 Thinking" [disabled] + - button "无 Git 仓库" [disabled] + - generic [ref=e127]: + - button "发送消息" [disabled] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-03T01-36-52-495Z.yml b/.playwright-mcp/page-2026-08-03T01-36-52-495Z.yml new file mode 100644 index 000000000..d254089dc --- /dev/null +++ b/.playwright-mcp/page-2026-08-03T01-36-52-495Z.yml @@ -0,0 +1,48 @@ +- generic [ref=f2e6]: + - complementary [ref=f2e7]: + - generic [ref=f2e8]: + - generic [ref=f2e9]: + - generic [ref=f2e10]: + - generic [ref=f2e11]: Live Agent + - button "关闭边栏" [ref=f2e15] + - generic [ref=f2e20]: + - button "新对话" [ref=f2e21] + - button "Skills" [ref=f2e27] + - button "MCP" [ref=f2e33] + - generic [ref=f2e40]: + - generic [ref=f2e41]: + - button "工作空间" [expanded] [ref=f2e42] + - button "新建工作空间" [ref=f2e46] + - button "调整工作空间与最近对话占比" [ref=f2e51] + - button "最近会话" [expanded] [ref=f2e54] + - paragraph [ref=f2e62]: 暂无历史对话 + - button "设置" [ref=f2e65] + - generic [ref=f2e71]: + - banner [ref=f2e73]: + - generic [ref=f2e74]: + - button "选择模型" [disabled] + - generic [ref=f2e75]: + - button "切换到深色模式" [ref=f2e76] + - button "Select a project to use project tools." [disabled] + - generic [ref=f2e79]: + - generic [ref=f2e83]: + - generic [ref=f2e88]: 欢迎使用 LiveAgent + - generic [ref=f2e89]: 你还没有配置模型。 + - generic [ref=f2e90]: 点击下方按钮,配置供应商和模型。 + - button "配置供应商" [ref=f2e91] + - generic: + - button [ref=f2e96] + - separator "调整对话正文宽度" [ref=f2e98] + - generic [ref=f2e101]: + - button "扩大输入框" [ref=f2e102] + - textbox [ref=f2e107]: 输入消息,@ 引用文件,提示词可队列发送... + - generic [ref=f2e108]: + - generic [ref=f2e109]: + - generic [ref=f2e110]: + - button "请先选择一个项目" [disabled] + - button "联网搜索已开启" [ref=f2e112] + - generic [ref=f2e117]: + - button "当前模型不支持 Thinking" [disabled] + - button "无 Git 仓库" [disabled] + - generic [ref=f2e118]: + - button "发送消息" [disabled] \ No newline at end of file diff --git a/.playwright-mcp/page-2026-08-03T01-42-24-989Z.yml b/.playwright-mcp/page-2026-08-03T01-42-24-989Z.yml new file mode 100644 index 000000000..bc0e3c816 --- /dev/null +++ b/.playwright-mcp/page-2026-08-03T01-42-24-989Z.yml @@ -0,0 +1,52 @@ +- generic [ref=f3e6]: + - complementary [ref=f3e7]: + - generic [ref=f3e8]: + - generic [ref=f3e9]: + - generic [ref=f3e10]: + - generic [ref=f3e11]: Live Agent + - button "关闭边栏" [ref=f3e15] + - generic [ref=f3e20]: + - button "新对话" [ref=f3e21] + - button "Skills" [ref=f3e27] + - button "MCP" [ref=f3e33] + - generic [ref=f3e40]: + - generic [ref=f3e41]: + - button "工作空间" [expanded] [ref=f3e42] + - button "新建工作空间" [ref=f3e46] + - button "调整工作空间与最近对话占比" [ref=f3e51] + - button "最近会话" [expanded] [ref=f3e54] + - paragraph [ref=f3e62]: 暂无历史对话 + - button "设置" [ref=f3e65] + - generic [ref=f3e71]: + - generic [ref=f3e72]: + - banner [ref=f3e73]: + - generic [ref=f3e74]: + - button "选择模型" [disabled] + - generic [ref=f3e75]: + - button "切换到深色模式" [ref=f3e76] + - button "Select a project to use project tools." [disabled] + - generic [ref=f3e79]: + - paragraph [ref=f3e84]: Cannot read properties of undefined (reading 'invoke') + - button [ref=f3e85] + - generic [ref=f3e88]: + - generic [ref=f3e92]: + - generic [ref=f3e97]: 欢迎使用 LiveAgent + - generic [ref=f3e98]: 你还没有配置模型。 + - generic [ref=f3e99]: 点击下方按钮,配置供应商和模型。 + - button "配置供应商" [ref=f3e100] + - generic: + - button [ref=f3e105] + - separator "调整对话正文宽度" [ref=f3e107] + - generic [ref=f3e110]: + - button "扩大输入框" [ref=f3e111] + - textbox [ref=f3e116]: 输入消息,@ 引用文件,提示词可队列发送... + - generic [ref=f3e117]: + - generic [ref=f3e118]: + - generic [ref=f3e119]: + - button "请先选择一个项目" [disabled] + - button "联网搜索已开启" [ref=f3e121] + - generic [ref=f3e126]: + - button "当前模型不支持 Thinking" [disabled] + - button "无 Git 仓库" [disabled] + - generic [ref=f3e127]: + - button "发送消息" [disabled] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 3bb8b3ead..a40c46fa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "agent-core" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tokio", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -3319,6 +3328,7 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" name = "liveagent" version = "1.3.0-dev.0" dependencies = [ + "agent-core", "arboard", "axum", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index 2b077d9d2..ee8e49b1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["crates/agent-gui/src-tauri"] +members = ["crates/agent-core", "crates/agent-gui/src-tauri"] diff --git a/crates/agent-core/Cargo.toml b/crates/agent-core/Cargo.toml new file mode 100644 index 000000000..9748bba39 --- /dev/null +++ b/crates/agent-core/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "agent-core" +version = "0.1.0" +edition = "2021" +description = "LiveAgent backend core: tools, storage and runtime. Deliberately free of any UI framework." + +# 编译期防线:本 crate 不得出现任何 tauri 依赖。 +# 这是 docs/architecture/backend-boundary.md「编译期防线」一节的落地—— +# 一旦有人把前端专属 command 挪进来,cargo build -p agent-core 直接失败, +# 而不是等到 headless 模式跑不起来才发现。 +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.150" +tokio = { version = "1.52.3", features = ["macros", "net", "sync", "time", "io-util", "rt", "rt-multi-thread", "process"] } diff --git a/crates/agent-gui/src-tauri/src/events.rs b/crates/agent-core/src/events.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/events.rs rename to crates/agent-core/src/events.rs diff --git a/crates/agent-core/src/lib.rs b/crates/agent-core/src/lib.rs new file mode 100644 index 000000000..c32ec8f7e --- /dev/null +++ b/crates/agent-core/src/lib.rs @@ -0,0 +1,12 @@ +//! LiveAgent 后端核心。 +//! +//! 这里装的是「执行 agent 所需的一切」:文件系统、git、终端、SFTP、进程管理、 +//! 存储、定时任务、MCP。**不含任何 UI 框架依赖**——见 Cargo.toml 的说明。 +//! +//! 两个消费者: +//! - `crates/agent-gui/src-tauri` 桌面壳,通过 `#[tauri::command]` 薄包装调用 +//! - `crates/agent-backend` HTTP/WS 服务,通过 JSON 路由调用 +//! +//! 两者调的是同一批函数,所以行为不可能不一致。 + +pub mod events; diff --git a/crates/agent-gui/src-tauri/Cargo.toml b/crates/agent-gui/src-tauri/Cargo.toml index ab3fee027..4f4c24fc9 100644 --- a/crates/agent-gui/src-tauri/Cargo.toml +++ b/crates/agent-gui/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ tauri-build = { version = "2.6.3", features = [] } prost-build = "0.14.4" [dependencies] +agent-core = { path = "../../agent-core" } tauri = { version = "2.11.5", features = ["tray-icon", "image-png"] } tauri-plugin-opener = "2.5.4" serde = { version = "1.0.228", features = ["derive"] } diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs index f122df019..3dee9fc82 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs @@ -9,9 +9,10 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; +use agent_core::events::EventBus; + use crate::{ commands::{history_db, subagent_store}, - events::EventBus, services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}, }; use uuid::Uuid; diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs index a45df59d9..8f618bfbd 100644 --- a/crates/agent-gui/src-tauri/src/gateway_sink.rs +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -16,7 +16,7 @@ use std::sync::{Arc, Weak}; use crate::commands::chat_history::{ ChatHistorySummary, HISTORY_DELETE_EVENT, HISTORY_UPSERT_EVENT, }; -use crate::events::EventSink; +use agent_core::events::EventSink; use crate::runtime::managed_process::MANAGED_PROCESS_CHANGED_EVENT; use crate::services::automation::types::{CRON_CHANGED_EVENT, HOOKS_CHANGED_EVENT}; use crate::services::gateway::{ diff --git a/crates/agent-gui/src-tauri/src/lib.rs b/crates/agent-gui/src-tauri/src/lib.rs index 84ee9e719..c3895d9be 100644 --- a/crates/agent-gui/src-tauri/src/lib.rs +++ b/crates/agent-gui/src-tauri/src/lib.rs @@ -1,5 +1,4 @@ mod commands; -mod events; mod gateway_sink; mod runtime; mod services; @@ -726,7 +725,7 @@ pub fn run() { // 事件总线:后端只管往这里发,谁听由这里决定。 // 桌面 webview 和 Gateway 都只是普通订阅者,没有谁被硬编码进后端。 // 先建空总线,sink 稍后注册——总线用内部可变性,允许后置。 - let events = Arc::new(events::EventBus::new()); + let events = Arc::new(agent_core::events::EventBus::new()); let workspace_watch = Arc::new(services::workspace_watch::WorkspaceWatchService::new( Arc::clone(&events), )); diff --git a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs b/crates/agent-gui/src-tauri/src/runtime/managed_process.rs index a8be89ced..a70ee8543 100644 --- a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs +++ b/crates/agent-gui/src-tauri/src/runtime/managed_process.rs @@ -17,7 +17,7 @@ use crate::runtime::process::{ ProcessProbe, }; use crate::runtime::shell_runner::spawn_platform_shell_command; -use crate::events::EventBus; +use agent_core::events::EventBus; const PROCESS_LOG_DIR: &str = "process-logs"; const DEFAULT_LOG_BYTES: u64 = 64 * 1024; diff --git a/crates/agent-gui/src-tauri/src/runtime/sftp.rs b/crates/agent-gui/src-tauri/src/runtime/sftp.rs index b6c8e4944..8fb5576f3 100644 --- a/crates/agent-gui/src-tauri/src/runtime/sftp.rs +++ b/crates/agent-gui/src-tauri/src/runtime/sftp.rs @@ -9,7 +9,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; use uuid::Uuid; -use crate::events::EventBus; +use agent_core::events::EventBus; use crate::runtime::platform::expand_tilde_path; use crate::runtime::project_path::{ diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs index 787af2c18..6a3ff1413 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; use std::sync::atomic::AtomicUsize; use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; -use crate::events::EventBus; +use agent_core::events::EventBus; mod events; mod output; diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs index c3777f8cb..1724ded67 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs @@ -5,7 +5,7 @@ use std::sync::atomic::Ordering; use std::sync::{mpsc, Arc, Mutex}; use std::thread; -use crate::events::EventBus; +use agent_core::events::EventBus; use crate::runtime::project_path::{ project_path_key as normalize_project_path_key, project_path_keys_equal, }; diff --git a/crates/agent-gui/src-tauri/src/services/automation/store.rs b/crates/agent-gui/src-tauri/src/services/automation/store.rs index 35cc6cfcb..f0c63bcef 100644 --- a/crates/agent-gui/src-tauri/src/services/automation/store.rs +++ b/crates/agent-gui/src-tauri/src/services/automation/store.rs @@ -4,7 +4,7 @@ use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; use serde_json::Value; use uuid::Uuid; -use crate::events::EventBus; +use agent_core::events::EventBus; use super::db; use super::scheduler::AutomationScheduler; diff --git a/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs b/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs index c77c1a837..c94ee1845 100644 --- a/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs +++ b/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs @@ -14,7 +14,7 @@ mod watcher; use std::collections::{BTreeSet, HashMap}; use std::sync::{Arc, Mutex}; -use crate::events::EventBus; +use agent_core::events::EventBus; pub const WORKSPACE_ACTIVITY_EVENT: &str = "workspace:activity"; diff --git a/crates/agent-gui/src-tauri/src/tauri_sink.rs b/crates/agent-gui/src-tauri/src/tauri_sink.rs index 554563c8a..a0f62339b 100644 --- a/crates/agent-gui/src-tauri/src/tauri_sink.rs +++ b/crates/agent-gui/src-tauri/src/tauri_sink.rs @@ -5,7 +5,7 @@ use tauri::{AppHandle, Emitter}; -use crate::events::EventSink; +use agent_core::events::EventSink; pub struct TauriEventSink { app: AppHandle, diff --git a/docs/architecture/backend-split.md b/docs/architecture/backend-split.md new file mode 100644 index 000000000..fd0436d8b --- /dev/null +++ b/docs/architecture/backend-split.md @@ -0,0 +1,86 @@ +# agent-core 拆分与 agent-backend API 设计 + +配套文档:`backend-boundary.md`(234 个 command 的三分类)。 +本文件记录**怎么拆**和**HTTP/WS 长什么样**。 + +## crate 布局 + +``` +crates/ + agent-core/ 新增 · 后端核心 · Cargo.toml 禁 tauri(编译期防线) + agent-backend/ 待建 · axum HTTP/WS 服务 · 依赖 agent-core + agent-gui/src-tauri/ 桌面壳 · 依赖 agent-core · 只剩 #[tauri::command] 薄包装 + 托盘/窗口/更新 +``` + +`agent-core` 已建立并接通,`cargo tree -p agent-core` 零 tauri 依赖。 + +## 路由约定:命令式,不做 REST 化 + +**决定:`POST /api/`,body 是原样的参数 JSON。** 流式端点是唯一例外,走 WS。 + +``` +invoke("git_status", { workdir }) → POST /api/git_status { "workdir": "..." } +``` + +理由 —— 前端**现在**就在用这些命令名和这些参数对象(`#[tauri::command]` 的参数名 +就是 JSON key)。1:1 映射让阶段 4 的前端迁移是机械替换;REST 化等于做 195 次独立 +设计决策,每次都是一个破坏前端的机会。这是「消除特殊情况」而不是「制造 195 个」。 + +> 三份并行设计各自给出了不同约定(命令式 `/api/git_status`、kebab 资源式 +> `/api/terminal/{id}/ssh-local-forward`、REST 式 `/api/conversations`)。 +> 采纳命令式,理由如上。 + +**`rename_all` 的坑**:部分命令写了 `#[tauri::command(rename_all = "snake_case")]`, +部分没写(默认 camelCase)。HTTP 层必须**逐命令**沿用各自现有的约定,不能统一, +否则前端字段名对不上。已知不一致:`git_clone_repository_tasks`、 +`chat_history_replace_from_message`。迁移时逐个核对,不要想当然。 + +## 已核验的事实(实验或读源码确认,非推断) + +### `tauri::async_runtime` → `tokio` 的替换是安全的,但有前提 + +读 `tauri-2.11.5/src/async_runtime.rs` 确认: + +| 项 | 结论 | +|---|---| +| 底层实现 | 纯 tokio。`JoinHandle` 是单变体枚举包 `TokioJoinHandle`(`:139`) | +| 错误类型 | **不完全相同**。`impl Future for JoinHandle` 的 `Output = crate::Result`,即 `Result`,经 `map_err(Into::into)` 从 `JoinError` 转来(`:164-169`)。但 `tauri::Error::JoinError(#[from] tokio::task::JoinError)`(`error.rs:73`),所以现有的 `.map_err(\|e\| format!("...{e}"))` 照常编译,只是 Display 文案略变 | +| **runtime 前提** | **有实质差异**。`default_runtime()` 会 `TokioRuntime::new().unwrap()` **自建 runtime**(`:222-229`),所以 tauri 版在任何上下文可调;`tokio::spawn` 在 runtime 之外**会 panic** | + +逐站点核查结果:后端所有 `async_runtime::spawn*` 都只从 async 上下文可达 +(`scheduler.rs` 的 5 个同步 fn —— `start`/`report_task_error`/`start_fire`/ +`disable_task_detached`/`record_run_detached` —— 调用方都在 `async fn fire`/ +`execute_fire` 内;`spawn_ssh_reconnect_runner` 的 3 个调用点都在 async fn 内)。 +`registry.rs` 的 2 个裸 `thread::spawn`(PTY 读线程)**不碰** async runtime。 + +唯一例外:`services/proxy.rs:83` `pub fn start_proxy_server()` 是同步 fn 且从 +Tauri `.setup()` 调用。但该文件正好被 agent-backend 取代 +(`proxy_get_server_info` 已在删除清单),自然消失。 + +**结论:可以机械替换。** + +### `include!()` 不是 blocker + +代码里有 50 处 `include!()`(`chat_history/`、`settings/`、`memory/` 三处把子文件 +摊平进同一模块)。实验验证(嵌套 `include!` + 独立 crate + 跨 crate 引用): + +- ✅ `include!` 路径相对于**宏调用所在的物理文件**,与 crate 边界无关。整目录搬迁保持 + 相对布局即可,编译通过。现有代码本身就是证据:`settings/mod.rs` 里 + `include!("ssh/mod.rs")`,而 `ssh/mod.rs` 里 `include!("load.rs")` 解析到 `ssh/` 目录。 +- ⚠️ 真正的问题是 **`pub(crate)` 跨 crate 变私有**(`E0603`),而 `include!` 把所有文件 + 摊平进一个模块,放大了这个面。 + +待迁移代码有 580 处 `pub(crate)`,但**留在壳里的代码只引用 29 个不同符号路径**。 +所以不要批量提升为 `pub`,只提升真正跨界的那些——其余保持 `pub(crate)` +(在 agent-core 内部依然有效)。 + +## 待解决 + +| 问题 | 说明 | +|---|---| +| `tauri::State` 注入的 9 个命令 | `git_clone_repository_{start,tasks,cancel,dismiss}`、`shell_run`、`runtime_cancel`、`hook_run_{script,http_requests}`、`hook_cancel_scope` 持有 registry 句柄。HTTP 侧需要会话隔离方案(header 名、生命周期、校验),尚未定 | +| 返回 `Result<(), String>` 的命令 | 约 9 处(`workspace_watch_set`、`hook_cancel_scope`、多个 `tunnel_*`)。HTTP 无法区分「成功」「不存在」「无权限」,需补状态码语义 | +| EventBus 背压 | `events.rs` 的 `emit_json` 同步遍历所有 sink,一个慢客户端会阻塞发事件的业务线程。WS sink 必须自己排队 + 丢帧,不能阻塞 | +| `GatewayEventSink` 无测试 | 事件总线把编译期错误变成了运行时静默失败——迁移 history sync 时就真实发生过一次(移除 gateway 调用后忘了在 sink 里接上,编译全绿但同步已断)。需补覆盖 | +| 3 个 settings 命令未设计路由 | `settings_list_ccswitch_providers`、`settings_list_cherry_studio_providers`、`settings_list_cherry_studio_providers_from_path` | +| axum 0.8 TLS 集成 | `axum-server` 的 `RustlsConfig::from_pem_file` 签名未经验证,实现前必须查证 | From 8c90a42490913ea1c90c11850e871738b9437836 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 05:42:04 -0700 Subject: [PATCH 06/75] docs(migration): add per-phase specs (markdown) and todo-lists (csv) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把六个阶段的目标/要求落成 markdown,进度落成 csv,已完成与未完成一并记录。 docs/architecture/migration/ README.md 总览:动机、19 项决策、代码规模基线、全局风险 phase-{1..6}-*.md 每阶段的目标、要求、验收标准 phase-{1..6}-*.csv todo-list:id,task,status,blocker,verify,notes CSV 的 verify 列强制写「怎么算做完」(可执行命令或可观察现象),不接受 「测试通过」这类空话;notes 列记踩过的坑与被推翻的假设。 103 个条目,25 done / 78 todo。已通过脚本校验:status 合法、task 与 verify 非空、依赖 id 存在、done 项不依赖未完成项。 文档同时固化了几处此前散落在对话里的关键结论: - 阶段 1 清点的两个坑(generate_handler 块以不带分号的 ] 结尾; services::proxy::proxy_get_server_info 不在 commands:: 命名空间下) - 阶段 2 推翻的两个并行分析结论(include! 不是 blocker; async_runtime → tokio 漏掉 default_runtime 自建 runtime 这一前提) - settings_save_remote 是「部分删除」,三分类容不下,阶段 4 拆两半 - 事件总线把编译期错误变成运行时静默失败,P2-07 已真实踩过一次 - README 现存事实错误(技术栈表声称依赖 codex-sdk / claude-agent-sdk, 但 package.json 里没有)记为 P6-13 --- docs/architecture/migration/README.md | 130 +++++++++++++ .../migration/phase-1-boundary.csv | 14 ++ .../migration/phase-1-boundary.md | 99 ++++++++++ .../migration/phase-2-backend.csv | 32 ++++ .../architecture/migration/phase-2-backend.md | 174 ++++++++++++++++++ .../architecture/migration/phase-3-engine.csv | 16 ++ docs/architecture/migration/phase-3-engine.md | 117 ++++++++++++ .../migration/phase-4-frontend.csv | 17 ++ .../migration/phase-4-frontend.md | 78 ++++++++ docs/architecture/migration/phase-5-merge.csv | 10 + docs/architecture/migration/phase-5-merge.md | 72 ++++++++ .../migration/phase-6-cutover.csv | 20 ++ .../architecture/migration/phase-6-cutover.md | 83 +++++++++ 13 files changed, 862 insertions(+) create mode 100644 docs/architecture/migration/README.md create mode 100644 docs/architecture/migration/phase-1-boundary.csv create mode 100644 docs/architecture/migration/phase-1-boundary.md create mode 100644 docs/architecture/migration/phase-2-backend.csv create mode 100644 docs/architecture/migration/phase-2-backend.md create mode 100644 docs/architecture/migration/phase-3-engine.csv create mode 100644 docs/architecture/migration/phase-3-engine.md create mode 100644 docs/architecture/migration/phase-4-frontend.csv create mode 100644 docs/architecture/migration/phase-4-frontend.md create mode 100644 docs/architecture/migration/phase-5-merge.csv create mode 100644 docs/architecture/migration/phase-5-merge.md create mode 100644 docs/architecture/migration/phase-6-cutover.csv create mode 100644 docs/architecture/migration/phase-6-cutover.md diff --git a/docs/architecture/migration/README.md b/docs/architecture/migration/README.md new file mode 100644 index 000000000..e7eed1ca3 --- /dev/null +++ b/docs/architecture/migration/README.md @@ -0,0 +1,130 @@ +# 统一网络通信迁移 · 总览 + +把 LiveAgent 从「TS + Rust + Go 三语言、本地走 Tauri IPC / 远程走 Go 中继」 +改成「TS + Rust 两语言、本地远程都走同一套网络 API」。 + +## 为什么 + +当前本地和远程走两条**完全不同**的路,而本地那条根本不是网络: + +| 能力 | 本地 | 远程 | +|---|---|---| +| fs/git/settings/history/… | `invoke()` **Tauri IPC**(57 个文件) | WS protobuf → Go gateway → WS protobuf → `gateway_bridge.rs` → 同一批 command | +| **chat** | `await chatRuntimeHost.runTurn()` —— **同一 JS 上下文的函数调用**(`useSendChatTurn.ts:1402`) | 跨两进程 + 公网**模拟**这次函数调用 | + +Go gateway 13,914 行手写代码里,约 **3,800 行专门在给「chat 引擎跑在一个会睡着的 +浏览器标签页里」擦屁股**:`chat.prepare` wake ping、`ChatRunLedger` + 5s sweeper、 +`runReportLost`(15s)/`stale`(10min)/`offline`(30min)三层超时、seq window + +`after_seq` replay、24h 幂等去重、`queued_in_gui`。 + +**只把 Go 翻成 Rust,这 3,800 行会原样搬过去。目标是让它们不必存在。** + +Go 的选型没有任何文档依据:它出现在 root commit `487af778`(2026-05-24,squash 导入), +`git log --all --grep=` 扫过 gateway/golang/relay/performance 等,`--grep=performance` +**零命中**。没有 ADR、没有 RFC、没有一行 commit message 解释。唯一写下来的是**职责** +(`gateway.md:5`「Gateway 是远程访问中继」)而非选型。 + +## 目标形态 + +``` +后端(独立部署单元) + ├─ Rust: agent-core + agent-backend + │ fs/shell/git/terminal PTY/sftp/sqlite/cron/mcp/memory(195 个 command) + │ ← 唯一对外网络入口:HTTP + WS (JSON) + └─ Node: agent-core-js + agentRunner + 对话循环 + 工具层(约 35k 行 TS,pi-agent-core) + ← 只监听 loopback,打同一套 JSON API + +前端(以 GUI 为基线,一套代码) + ├─ Tauri 壳:托盘/窗口/更新/通知(18 个前端专属 command) + └─ 网络客户端 → http://127.0.0.1:xxxx 本地 + → https://your-box:8443 远程 +``` + +**一套 API,两类客户端(前端 / Node 引擎)。工具行为不可能两边不一致。** + +## 19 项已定决策 + +| # | 决定 | +|---|---| +| 1 | 范围:阶段 1–6 全做完,不停在中间态 | +| 2 | 后端是独立 crate + 独立二进制,**Cargo.toml 禁 tauri 依赖**(编译期防线) | +| 3 | 打包 Node runtime 随产物分发;不用 Bun(pi-agent-core 未在 Bun 验证) | +| 4 | 后端 = Rust core + Node 引擎两进程 | +| 5 | **Rust 唯一网络入口**,认证/TLS/SQLite 都在它;Node 只监听 loopback | +| 6 | **JSON over HTTP + WS**。`proto/v2` + buf 工具链全删 | +| 7 | 密码直接当 Bearer token | +| 8 | 本地密码初始化动态生成、可改;Tauri 壳注入并跳过登录页 | +| 9 | 一个后端支持多前端同时连 | +| 10 | **前端只是渲染,不在场不阻塞主流程**;该超时超时,有推荐项自动选 | +| 11 | 本地模式 Tauri 壳退出时一并关后端 | +| 12 | 一个前端只连一个后端(Gateway 多 agent 管理消失) | +| 13 | Node↔Rust 走**同一套 JSON API**(loopback + 内部 token) | +| 14 | 内建 TLS(`--tls-cert`/`--tls-key`)+ 反代都支持 | +| 15 | 大版本切换,旧 gateway 镜像 tag 冻结保留 | +| 16 | 前端**以 GUI 为基线**,`agent-gateway/web` 删除 | +| 17 | 阶段 3 **一次切断,不留 feature flag** | +| 18 | 只加 **API 契约测试**,不做 e2e | +| 19 | 重连**不补发**,拉快照 + 订阅增量(`seq`/`stream_epoch`/`after_seq` 全消失) | + +## 阶段与文档 + +| 阶段 | 文档 | 状态 | +|---|---|---| +| 1 · 划清后端边界 | [phase-1-boundary.md](phase-1-boundary.md) | ✅ 完成 | +| 2 · Rust 后端网络化 | [phase-2-backend.md](phase-2-backend.md) | 🟡 进行中(约 25%) | +| 3 · 抽 Node 引擎 | [phase-3-engine.md](phase-3-engine.md) | ⬜ 未开始 | +| 4 · 前端网络化 | [phase-4-frontend.md](phase-4-frontend.md) | ⬜ 未开始 | +| 5 · 前端合并 | [phase-5-merge.md](phase-5-merge.md) | ⬜ 未开始 | +| 6 · 删除 Go 与切换发布 | [phase-6-cutover.md](phase-6-cutover.md) | ⬜ 未开始 | + +每份文档配一个同名 `.csv` 作为 todo-list(见下方「CSV 约定」)。 + +## 贯穿全程的硬门槛 + +每个阶段结束时: + +```bash +cargo test --workspace && cargo clippy --workspace -- -D warnings +node --test 'crates/agent-gui/test/**/*.test.mjs' +cargo tree -p agent-core | grep -q tauri && echo "防线破了" || echo "防线完好" +``` + +**且桌面端必须完整可用。** 不做长期分支。 + +## 代码规模基线(2026-08-04 实测) + +| 目标 | 行数 | +|---|---| +| 待迁入 agent-core:`runtime/` | 11,837 | +| 待迁入 agent-core:`services/`(除 gateway/tray/bridge) | 26,847 | +| 待迁入 agent-core:`commands/` | 37,438 | +| **小计** | **76,122** | +| 待迁入 Node 引擎:`lib/chat` + `lib/tools` + `lib/providers` + `pages/chat/turns` | 35,182 | +| 将删除:手写 Go | 13,914(另有 17,357 生成 + 10,827 测试) | +| 将删除:`services/gateway/` | 10,228 | +| 将合并:GUI 前端 / WebUI | 151,713 / 126,934 | + +## CSV 约定 + +每阶段一个 CSV,列固定: + +``` +id,task,status,blocker,verify,notes +``` + +- `id` —— `P<阶段>-<序号>`,稳定不复用 +- `status` —— `done` / `in_progress` / `todo` / `blocked` +- `blocker` —— 依赖的 task id,或阻塞原因;无则留空 +- `verify` —— **这条怎么算做完**(可执行的命令或可观察的现象),不写"测试通过"这种空话 +- `notes` —— 踩过的坑、被推翻的假设、需要人拍板的地方 + +## 已知风险(全局) + +| 风险 | 说明 | 对策 | +|---|---|---| +| 事件总线把编译期错误变成运行时静默失败 | 迁移 history sync 时**真实发生过**:移除 gateway 调用后忘了在 sink 里接上,编译全绿但同步已断 | `GatewayEventSink` 补测试(P2-14) | +| 工具审批反向往返 | 引擎搬到后端后,后端要主动向前端发起请求并等回答,而前端可能没连着 | 按决策 10,先写测试再写实现(P3-06) | +| 快照源不能是 SQLite | `runAgentConversationTurn.ts:1217` 落库只在函数末尾一次,turn 中不写库 | 快照来自引擎内存态(P3-07) | +| SSRF 防护需重写 | Go 的黑名单 + safeurl 在 Rust 无对应物;`proxy.rs:227` 的校验**没有 IP 黑名单**,够用只因绑 loopback | 手写 + DNS rebinding 防护(P2-12) | +| 20 个脆测试会误报 | `readFileSync` + 正则断言源码文本,不验证行为 | 阶段 5 改写或删(P5-05) | diff --git a/docs/architecture/migration/phase-1-boundary.csv b/docs/architecture/migration/phase-1-boundary.csv new file mode 100644 index 000000000..74963178b --- /dev/null +++ b/docs/architecture/migration/phase-1-boundary.csv @@ -0,0 +1,14 @@ +id,task,status,blocker,verify,notes +P1-01,枚举 generate_handler 块内全部 command 注册,done,,"sed -n '43,286p' lib.rs | rg -o '^\s+[a-z_]+::[a-z_]+::([a-z_0-9]+),$' 得 234 条、零重复","踩坑两次:块以不带分号的 ] 结尾(按 ]; 搜索会扫到文件尾,把 commands::app::WindowPinState 类型引用误计);最后一条 services::proxy::proxy_get_server_info 不在 commands:: 下会被前缀过滤漏掉。给出过 app=8 和 app=14 两个错误数字,实际 7" +P1-02,定义三分类判据,done,,判据写入 backend-boundary.md「划分标准」一节且可据此独立复现分类,口诀:远程模式下该 command 在后端那台机器上执行还有意义吗 +P1-03,分类 195 条后端 command,done,P1-01;P1-02,command-classes/backend.txt 有 195 行, +P1-04,分类 18 条前端专属 command,done,P1-01;P1-02,command-classes/frontend.txt 有 18 行,app 7 + update 3 + tray 1 + pick_* 3 + clipboard 1 + open_workspace_path 1 + open_system_file_location 1 + open_chat_file_link 1 +P1-05,分类 21 条待删除 command,done,P1-01;P1-02,command-classes/deleted.txt 有 21 行,连接管理 4 + chat 中继记账 15 + settings sync 1 + proxy_get_server_info 1 +P1-06,裁定 gateway 模块里 8 条真实功能归后端,done,P1-03,"backend.txt 含 workspace_watch_set、provider_usage_query/test、gateway_tunnel_* 共 8 条",依据 gateway.rs:299 只调 workspace_watch.set_desired、gateway.rs:18 只调 provider_usage_service.query +P1-07,裁定隧道需重写而非搬运,done,P1-06,backend-boundary.md 有「隧道要重写,不是照搬」一节说明理由,后端就在工作机上且已有网络接口,slug 注册协议与跨中继分帧不需要;tunnel_rewrite.go 约 1000 行大部分蒸发 +P1-08,裁定 open_chat_file_link 需拆分,done,P1-04,backend-boundary.md 记录拆分方案,chat_file_links.rs:431 的 if inside_workspace && !open_in_file_manager 分支:一半纯解析归后端、一半 OS 副作用归前端 +P1-09,产出机器可读清单文件,done,P1-03;P1-04;P1-05,三个 txt 文件行数合计 234, +P1-10,产出可复现的 diff 校验命令,done,P1-09,按文档原文执行 diff 输出「分类完整」——已实测通过,行号 43;286 会随 lib.rs 变动,文档已注明如何重新定位块边界 +P1-11,核验文档内全部行号引用,done,P1-01,"逐条 sed -n 打印 proxy.rs:97、runAgentConversationTurn.ts:1217、system.rs:1338、chat_file_links.rs:431、gateway.rs:299/18、git.rs:3369 —— 六处全部命中", +P1-12,记录 settings_save_remote 分类缺陷,done,,backend-boundary.md 有「已知分类缺陷」一节,阶段 2 实施时发现:RemoteSettingsPayload 混了 Gateway 连接字段与 enable_web_* 权限开关;apply_config 返回 Result 是命令不是事件。阶段 4 拆两半 +P1-13,把分类 diff 加入 CI 门禁,todo,P1-10,CI 有一个 job 在新增未分类 command 时失败,建议在阶段 2 一并做,让「新 command 未分类」变成编译失败之外的第二道门禁 diff --git a/docs/architecture/migration/phase-1-boundary.md b/docs/architecture/migration/phase-1-boundary.md new file mode 100644 index 000000000..f2db8db9a --- /dev/null +++ b/docs/architecture/migration/phase-1-boundary.md @@ -0,0 +1,99 @@ +# 阶段 1 · 划清后端边界 + +**状态:✅ 完成**(commit `099b9e01`) + +## 目标 + +把 234 个 `#[tauri::command]` 分成三类,决定哪些进 `agent-core`(独立进程、禁 tauri +依赖)、哪些永远留在 Tauri 壳、哪些直接删除。 + +**不写实现代码。** 这份分类是后续所有阶段的前提 —— 分错一个,后端就会被迫 +`use tauri::`,headless 模式编译不出来。 + +## 要求 + +| # | 要求 | 达成 | +|---|---|---| +| 1 | 每个 command 有明确归属和理由 | ✅ 234/234 | +| 2 | 清单机器可读,可 diff 校验 | ✅ `command-classes/*.txt` | +| 3 | 边界模糊项单独裁定并记录理由 | ✅ 见下 | +| 4 | 数字可复现,不靠手数 | ✅ 文档内附命令 | + +## 划分标准 + +| 判据 | 归属 | +|---|---| +| 读写数据、执行计算、操作文件系统/进程/网络 | **后端** | +| 在某台机器的**图形界面上产生可见效果**(原生对话框、文件管理器、托盘、窗口、通知、全局快捷键、系统剪贴板) | **前端专属** | +| 只为「桌面端主动拨向公网 Gateway」这一拓扑而存在 | **删除** | + +口诀:远程模式下,这个 command 在**后端那台机器**上执行还有意义吗? +`system_pick_folder` 会在服务器上弹一个你看不见的对话框 —— 前端专属。 + +## 结果 + +``` +234 = 195 后端 + 18 前端专属 + 21 删除 +``` + +明细见 [`../backend-boundary.md`](../backend-boundary.md) 与 +[`../command-classes/`](../command-classes/)。 + +## 三个实质裁定 + +**① 从 gateway 模块救回 8 条。** `commands/integration/gateway.rs` 注册了 28 个 command, +但 8 个与中继拓扑无关,只是恰好登记在这个文件里: + +| Command | 依据 | +|---|---| +| `workspace_watch_set` | `gateway.rs:299` 只调 `workspace_watch.set_desired(WatchSource::Local, …)` | +| `provider_usage_query` / `_test` | `gateway.rs:18` 只调 `provider_usage_service.query()` | +| `gateway_tunnel_{state,create,update,close,check}` | 隧道是真实产品功能 | + +迁移时必须**脱离 `GatewayController`**,改为直接持有各自的 service。 + +**② 隧道要重写,不是照搬。** 现在桌面端把端口注册到公网 Gateway,Gateway 开 +`/t/{slug}` 公开反代并重写 HTML/CSS/CSP(`tunnel_rewrite.go` 约 1000 行)。新架构下 +后端**就在**那台工作机上且已有网络接口 —— slug 注册协议、跨中继分帧、公开无认证 +入口全部不需要。名字应从 `gateway_tunnel_*` 改为 `tunnel_*`。 + +**③ `open_chat_file_link` 必须拆开。** `chat_file_links.rs:431` 有分支 +`if inside_workspace && !open_in_file_manager`:成立时返回 action 让前端应用内打开 +(纯解析 → 后端),否则调 OS 打开外部程序(→ 前端)。拆成后端 `chat_file_link_resolve` ++ 前端按 action 决策。 + +## 清点过程的两个坑(已写入文档) + +1. `generate_handler![…]` 块以 ` ]`(**不带分号**)结尾。按 `];` 搜索会落空并 + 一路扫到文件尾,把 `commands::app::WindowPinState` 这类**类型引用**误计为 command。 +2. 最后一条注册是 `services::proxy::proxy_get_server_info`,**不在 `commands::` + 命名空间下**,只按 `commands::` 前缀过滤会整条漏掉。 + +> 我在清点时先后被这两个坑各绊了一次,给出过 `app` 模块 8 个和 14 个两个错误数字 +> (实际 7 个)。最终锁定 `lib.rs:43-286`,块内 234 行全部匹配、零重复、零遗漏。 + +## 遗留缺陷 + +**`settings_save_remote` 是「部分删除」,三分类容不下。** 实施阶段 2 时发现: +`RemoteSettingsPayload`(`commands/config/settings/types.rs:23`)混了两类字段 —— +`gateway_url`/`gateway_port`/`token`/`agent_id` 随 Gateway 删除, +`enable_web_terminal`/`enable_web_ssh_terminal`/`enable_web_git` 等权限开关新架构仍需要。 +且 `apply_config` 返回 `Result` 传播错误,是**命令不是事件**,转事件总线会丢错误语义。 + +处置:阶段 2 保持耦合,阶段 4 拆成两半。已记入 `backend-boundary.md`「已知分类缺陷」。 + +## 验证 + +```bash +cd crates/agent-gui/src-tauri/src +sed -n '43,286p' lib.rs \ + | rg -o '^\s+[a-z_]+::[a-z_]+::([a-z_0-9]+),$' -r '$1' \ + | sort > /tmp/registered.txt +D=../../../../docs/architecture/command-classes +cat $D/backend.txt $D/frontend.txt $D/deleted.txt | sort > /tmp/classified.txt +diff /tmp/registered.txt /tmp/classified.txt && echo "分类完整,无遗漏无幽灵" +``` + +已实测通过(234 条)。行号会随 `lib.rs` 变动,若大量报差异先确认块边界。 + +建议阶段 2 把这条 diff 加进 CI,让「新增 command 未分类」变成第二道门禁。 diff --git a/docs/architecture/migration/phase-2-backend.csv b/docs/architecture/migration/phase-2-backend.csv new file mode 100644 index 000000000..cc736a5fc --- /dev/null +++ b/docs/architecture/migration/phase-2-backend.csv @@ -0,0 +1,32 @@ +id,task,status,blocker,verify,notes +P2-01,设计 EventSink/EventBus 抽象,done,,agent-core/src/events.rs 存在且 4 个单测通过,"payload 用 serde_json::Value:这些类型本来就要过 JSON IPC,线上协议也是 JSON,没有第二种表示值得维护" +P2-02,建 TauriEventSink 接 webview,done,P2-01,src-tauri/tauri_sink.rs 存在,webview 事件照常到达前端,桌面壳独有,永不进 agent-core +P2-03,建 GatewayEventSink 收敛 gateway 事件知识,done,P2-01,src-tauri/gateway_sink.rs 存在,Gateway 中继照常工作,过渡设施,阶段 4 随 Gateway 一并删除 +P2-04,解耦 runtime/managed_process.rs,done,P2-01;P2-03,rg 'tauri::|GatewayController' runtime/managed_process.rs 无输出,该文件的 tauri 与 gateway 依赖原本全部集中在 24 行的 notifier 里,改掉它整个 800+ 行文件一次变干净 +P2-05,解耦 services/automation/store.rs,done,P2-01;P2-03,rg 'tauri::|GatewayController' services/automation/store.rs 无输出,保留 scheduler.request_reload()——那是业务逻辑不是传输 +P2-06,解耦 services/workspace_watch/,done,P2-01;P2-03,rg 'tauri::|GatewayController' services/workspace_watch 无输出,顺带纠正所有权倒置:WorkspaceWatchService 原由 GatewayController 创建,现改为后端创建、controller 接收,attach_gateway 删除 +P2-07,解耦 chat_history 的 history sync,done,P2-01;P2-03,rg 'GatewayController' commands/history/chat_history 无输出,"新增 history:upsert / history:delete 事件(面向未来:新架构下前端本身就是远程客户端,同样需要)。ChatHistorySummary 补 Deserialize。⚠️ 此步引入过真实回归:移除 gateway 调用后忘了在 sink 接上,编译全绿但同步已断" +P2-08,解耦 terminal/sftp/ssh_local_forward 事件发送,done,P2-01,rg 'app_handle|AppHandle|Emitter' runtime/terminal runtime/sftp.rs 无输出,attach_app_handle → set_event_bus。这些 broadcast 早就是双路的(emit + subscribers),改动只是把 webview 降级成普通订阅者 +P2-09,建 crates/agent-core 并接通 workspace,done,,cargo check -p agent-core 通过且 cargo tree -p agent-core 零 tauri,编译期防线生效 +P2-10,迁入 events.rs 作为跨 crate 验证载荷,done,P2-09,agent-core 的 4 个测试在新 crate 内通过,liveagent 666 个测试仍绿,选它因为零外部依赖且是后端与壳的唯一事件契约 +P2-11,验证 include! 跨 crate 行为,done,P2-09,"实验:嵌套 include! + 独立 crate + 跨 crate 引用,编译通过","推翻并行分析的头号 blocker。真问题是 pub(crate) 跨 crate 变私有(E0603)。580 处 pub(crate) 但壳侧只引用 29 个符号路径,只提升跨界的" +P2-12,核验 async_runtime → tokio 的替换条件,done,,"读 tauri-2.11.5/src/async_runtime.rs 确认;逐站点核查 132 处 spawn 的调用上下文","并行分析漏掉关键前提:default_runtime() 会 TokioRuntime::new().unwrap() 自建 runtime,故 tauri 版任何上下文可调而 tokio::spawn 在 runtime 外会 panic。核查结论:后端所有 spawn 只从 async 上下文可达,可机械替换" +P2-13,定路由约定,done,,backend-split.md 有「路由约定」一节且组间无歧义,"三份并行设计各给一套(命令式/kebab 资源式/REST 式)。采纳命令式 POST /api/:命令名与参数 JSON 前端现在就在用,1:1 映射让阶段 4 是机械替换" +P2-14,补 GatewayEventSink 测试,todo,P2-03,"注入假 controller,断言每个事件名触发预期的 publish 调用","优先级最高的未完成项:事件总线把编译期错误变成运行时静默失败,P2-07 已真实踩过一次" +P2-15,批量替换 132 处 async_runtime 为 tokio,todo,P2-12,rg 'tauri::async_runtime' commands runtime services(排除 gateway/tray)无输出,前提已核验;替换后需确认 Cargo.toml 的 tokio features 含 rt-multi-thread +P2-16,迁入 runtime/ 到 agent-core,todo,P2-09;P2-11;P2-15,cargo check -p agent-core 通过且 runtime/ 不再存在于 src-tauri,11837 行。依赖最少,先做。注意 pub(crate) 提升只做壳侧真正引用的 +P2-17,迁入 services/ 到 agent-core,todo,P2-16,cargo check -p agent-core 通过,26847 行(排除 gateway/tray/gateway_bridge)。services/tunnel/ 不搬——按 P1-07 需重写 +P2-18,迁入 commands/ 实现到 agent-core,todo,P2-17,cargo check -p agent-core 通过,37438 行。含 178 处 #[tauri::command] 与 68 处 State 的拆分 +P2-19,在 src-tauri 留下 195 个薄包装,todo,P2-18,"cargo test -p liveagent 全绿且桌面端手工冒烟:history/settings/terminal/git/skills 可用",薄包装形态:#[tauri::command] fn x(state: State>) { agent_core::x(&state).await } +P2-20,建 crates/agent-backend 骨架,todo,P2-09,"cargo run -p agent-backend --(带 --password)能起进程,GET /healthz 返回 ok",axum 0.8.9 已在依赖中 +P2-21,实现密码认证中间件,todo,P2-20,"无 Bearer 返回 401;错密码返回 401 且响应时间与正确密码无显著差异(常量时间比较)","密码即 Bearer(决策 7)。本地密码初始化动态生成、可改(决策 8)" +P2-22,实现 TLS 支持,todo,P2-20,"带 --tls-cert/--tls-key 启动后 https 可访问;不带则纯 HTTP","⚠️ axum-server RustlsConfig::from_pem_file 签名未经验证,实现前必须查证" +P2-23,打通 3 条代表性路由(unary/stream/upload),todo,P2-20;P2-21,"curl 打 unary、websocat 打 stream、curl -F 打 upload 全部返回预期","先验证约定再批量推。建议选 git_status(unary)、terminal_stream_attach(stream)、system_import_uploaded_readable_files(upload)" +P2-24,定 tauri::State 那 9 个命令的会话隔离方案,todo,P2-23,"方案写入 backend-split.md:header 名、生命周期、校验方式;且 2 个前端并发调用互不干扰","git_clone_repository_{start,tasks,cancel,dismiss}、shell_run、runtime_cancel、hook_run_{script,http_requests}、hook_cancel_scope" +P2-25,"补齐返回 Result<(),String> 命令的状态码语义",todo,P2-23,"约 9 个命令能区分成功/不存在/无权限","workspace_watch_set、hook_cancel_scope、多个 tunnel_*" +P2-26,实现 WS 事件流 sink,todo,P2-20;P2-01,"多个 WS 客户端同时连,各自都收到事件;慢客户端不阻塞业务线程","⚠️ EventBus::emit_json 同步遍历 sink,WS sink 必须自排队 + 丢帧。重连语义按决策 19:不补发,拉快照 + 订阅增量" +P2-27,重写 SSRF 防护,todo,P2-20,"RFC1918/loopback/link-local/CGNAT/NAT64/6to4 全部拒绝;DNS rebinding 场景(域名解析到内网 IP)被拦","不要复用 proxy.rs:227 的 validate_image_proxy_url——它只查 scheme 和嵌入凭据,没有 IP 黑名单,够用只因绑 loopback。需用 reqwest 的 resolve() 把连接钉死在已校验 IP" +P2-28,补完 195 条路由,todo,P2-23;P2-24;P2-25,"每条 backend.txt 里的 command 都有对应路由","含 3 个漏掉的 settings 命令:settings_list_ccswitch_providers、settings_list_cherry_studio_providers、_from_path。rename_all 逐命令沿用现状不能统一(git_clone_repository_tasks、chat_history_replace_from_message 已知不一致)" +P2-29,写 API 契约测试,todo,P2-28,"不经前端直接打 JSON API,覆盖 195 个 command;新增 command 未加路由时测试失败",决策 18:只加契约测试不做 e2e +P2-30,重写 tunnel 为本机代理,todo,P2-20,"前端请求后端隧道路由,能访问到 localhost:5173 的 dev server","按 P1-07:去掉 slug 注册协议、跨中继分帧、公开无认证入口。名字改 tunnel_*。是否仍需路径前缀重写取决于路由是否挂子路径" +P2-31,阶段验收:不经前端打通全部能力,todo,P2-29,"curl/websocat 验证 fs/git/terminal/history/settings 全部可用,且桌面端行为零变化", diff --git a/docs/architecture/migration/phase-2-backend.md b/docs/architecture/migration/phase-2-backend.md new file mode 100644 index 000000000..a83fe005c --- /dev/null +++ b/docs/architecture/migration/phase-2-backend.md @@ -0,0 +1,174 @@ +# 阶段 2 · Rust 后端网络化 + +**状态:🟡 进行中(约 25%)** +提交:`3cf93685` `231faefb` `a2e22a93` `1336a48f` + +## 目标 + +把 195 个后端 command 从「只能被 Tauri webview 调用」变成「可被任何网络客户端调用」。 + +产物是两个新 crate: + +- **`crates/agent-core`** —— 后端核心。`Cargo.toml` **禁 tauri 依赖**(编译期防线)。 + 装 `runtime/` + `services/`(除 gateway/tray/bridge)+ `commands/` 的实现。 +- **`crates/agent-backend`** —— axum HTTP/WS 服务。密码认证、TLS、事件流。 + +`src-tauri` 退化为薄壳:`lib.rs`、托盘/窗口/更新、`gateway/*`(阶段 4 删)、 +以及 195 个 `#[tauri::command]` 薄包装。 + +## 要求 + +| # | 要求 | 为什么 | +|---|---|---| +| 1 | 每个 command **同时**挂 `#[tauri::command]` 和 HTTP 路由 | 双挂让前端能逐个迁移,任何时刻桌面端都可用 | +| 2 | `cargo tree -p agent-core` 零 tauri | 编译期防线,不靠自觉 | +| 3 | 交付物必须含 API 契约测试 | 决策 18 | +| 4 | 每次提交都编译绿 + 全量测试绿 | 不做长期分支 | +| 5 | 桌面端行为零变化 | 阶段 2 是纯重构,不改产品语义 | + +## 路由约定:命令式,不做 REST 化 + +``` +invoke("git_status", { workdir }) → POST /api/git_status { "workdir": "..." } +``` + +流式端点是唯一例外,走 WS。 + +理由:前端**现在**就在用这些命令名和参数对象(`#[tauri::command]` 的参数名就是 +JSON key)。1:1 映射让阶段 4 是机械替换;REST 化等于做 195 次独立设计决策,每次都是 +一个破坏前端的机会。这是消除特殊情况,而不是制造 195 个。 + +**`rename_all` 必须逐命令沿用现状,不能统一** —— 部分命令写了 +`rename_all = "snake_case"`,部分没写(默认 camelCase)。已知不一致: +`git_clone_repository_tasks`、`chat_history_replace_from_message`。 + +## 已完成 + +### 事件总线(核心设计) + +后端代码此前把两个消费者**硬编码**在发事件的地方:`AppHandle::emit` 和 +`GatewayController`。这让「谁在监听」成为后端的编译期依赖。 + +现在后端只认识 `agent_core::events::EventSink`: + +| Sink | 位置 | 命运 | +|---|---|---| +| `TauriEventSink` | `src-tauri/tauri_sink.rs` | 桌面壳独有,永不进 agent-core | +| `GatewayEventSink` | `src-tauri/gateway_sink.rs` | **过渡设施**,阶段 4 随 Gateway 删除 | +| WS sink | 待建(P2-11) | agent-backend 用 | + +payload 统一 `serde_json::Value` —— 这些类型本来就要过 JSON IPC,且线上协议就是 JSON。 +sink 多为「重读当前状态再发布」,避免给 11 个 payload 类型逐个加 `Deserialize`。 + +关键洞察:`terminal`/`sftp`/`workspace_watch` 的 broadcast **早就是双路的** +(emit + subscribers 通道,Gateway 靠后者中继)。所以正确做法不是引入新抽象, +而是**把硬编码的 webview 消费者降级成总线上的一个普通订阅者**。 + +### 已解耦模块(零 `tauri::` / 零 `GatewayController`) + +`runtime/managed_process.rs`、`runtime/terminal/*`、`runtime/sftp.rs`、 +`services/automation/store.rs`、`services/workspace_watch/`、 +`commands/history/chat_history/*` + +顺带纠正一处所有权倒置:`WorkspaceWatchService` 原先由 `GatewayController` **创建**, +现改为后端创建、controller 接收,`attach_gateway` 删除。 + +### agent-core crate 建立 + +已接通,`events.rs` 迁入,`cargo tree -p agent-core` 零 tauri。 + +## 进度指标 + +| 指标 | 起点 | 现在 | 目标 | +|---|---|---|---| +| 后端代码里 `GatewayController` 引用 | 65 | **40** | 0 | +| 后端代码里结构性 tauri 触点(`AppHandle`/`Emitter`/`Manager`/`Window`) | 13 | **9** | 0 | +| 已迁入 agent-core 的行数 | 0 | **152** | 76,122 | +| HTTP 路由 | 0 | 0 | 195 | + +剩余 40 处 `GatewayController` 全部集中在「要删的」和「要重写的」: + +``` +commands/integration/gateway.rs 27 (20 个命令阶段 4 直接删) +services/tunnel/* 11 (阶段 1 已定:需重写) +commands/config/settings/* 2 (已记录的部分删除项) +``` + +剩余 9 处结构性 tauri 触点:`services/tunnel/store.rs` 3 处(待重写)+ +`commands/app/{app,tray,update}` 6 处(**前端专属,本就该留在壳里**)。 + +**即:所有应当迁入 agent-core 的后端模块,现已全部 gateway-free 且无结构性 tauri 耦合。** + +## 剩余机械工作量 + +| 项 | 数量 | 性质 | +|---|---|---| +| `tauri::async_runtime` → tokio | 132 | 机械(前提已核验,见下) | +| `#[tauri::command]` 拆成 impl + wrapper | 178 | 机械但量大 | +| `tauri::State<'_, Arc>` → 显式参数 | 68 | 机械 | +| 代码迁入 agent-core | 76,122 行 | 机械 | +| `pub(crate)` 跨界提升 | 580 处中约 29 个符号路径 | 机械 | + +## 已核验的事实(实验/读源码,非推断) + +### `tauri::async_runtime` → `tokio` 可以机械替换,但有前提 + +读 `tauri-2.11.5/src/async_runtime.rs`: + +- 底层纯 tokio,`JoinHandle` 是单变体枚举包 `TokioJoinHandle`(`:139`) +- `impl Future` 的 `Output = Result`,经 `map_err(Into::into)` 从 + `JoinError` 转来(`:164-169`);而 `tauri::Error::JoinError(#[from] tokio::task::JoinError)` + (`error.rs:73`),所以现有 `.map_err(|e| format!("...{e}"))` 照常编译 +- ⚠️ **`default_runtime()` 会 `TokioRuntime::new().unwrap()` 自建 runtime**(`:222-229`)。 + 故 tauri 版在任何上下文可调,而 `tokio::spawn` 在 runtime 之外**会 panic** + +逐站点核查:后端所有 spawn 都只从 async 上下文可达(`scheduler.rs` 的 5 个同步 fn +调用方都在 `async fn fire`/`execute_fire` 内;`spawn_ssh_reconnect_runner` 3 个调用点 +都在 async fn 内)。`registry.rs` 的 2 个裸 `thread::spawn`(PTY 读线程)不碰 async runtime。 +唯一例外 `services/proxy.rs:83` 会被 agent-backend 取代而消失。 + +> 并行分析给出「语义等价已验证」但**漏掉了 runtime 前提**这一条。 + +### `include!()` 不是 blocker + +50 处 `include!()` 把子文件摊平进同一模块。实验验证(嵌套 include + 独立 crate + +跨 crate 引用): + +- ✅ 路径相对于**宏调用所在的物理文件**,与 crate 边界无关。整目录搬迁保持相对布局即可。 + 现有代码自证:`settings/mod.rs` 里 `include!("ssh/mod.rs")`,`ssh/mod.rs` 里 + `include!("load.rs")` 正确解析到 `ssh/` 目录 +- ⚠️ 真问题是 **`pub(crate)` 跨 crate 变私有**(`E0603`),`include!` 把文件摊平放大了这个面 + +580 处 `pub(crate)`,但壳侧只引用 **29 个符号路径**。只提升跨界的那些, +其余保持 `pub(crate)`(在 agent-core 内部依然有效)。 + +> 并行分析把 `include!()` 列为**头号 blocker**,这是错的。 + +## 待决问题 + +| 问题 | 说明 | +|---|---| +| `tauri::State` 注入的 9 个命令怎么做会话隔离 | `git_clone_repository_{start,tasks,cancel,dismiss}`、`shell_run`、`runtime_cancel`、`hook_run_{script,http_requests}`、`hook_cancel_scope` 持有 registry 句柄。需定 header 名、生命周期、校验方式 | +| 返回 `Result<(), String>` 的约 9 个命令 | HTTP 无法区分「成功」「不存在」「无权限」,需补状态码语义 | +| EventBus 背压 | `emit_json` 同步遍历所有 sink,慢客户端会阻塞业务线程。WS sink 必须自排队 + 丢帧 | +| axum 0.8 TLS 集成 | `axum-server` 的 `RustlsConfig::from_pem_file` 签名**未经验证**,实现前必须查证 | +| 3 个 settings 命令未设计路由 | `settings_list_ccswitch_providers`、`settings_list_cherry_studio_providers`、`settings_list_cherry_studio_providers_from_path` | + +## 已知风险 + +**事件总线把编译期错误变成了运行时静默失败。** 这在迁移 history sync 时**真实发生过**: +移除 gateway 发布调用后忘了在 sink 里接上,`cargo check` 全绿但 history 同步已断, +下一步才补上。`GatewayEventSink` 目前**无测试覆盖**(P2-14)。 + +## 验证 + +```bash +cargo test --workspace && cargo clippy --workspace -- -D warnings +cargo tree -p agent-core | grep -q tauri && echo "防线破了" || echo "防线完好" +``` + +阶段结束时还要: + +- 用 curl / websocat 直接打后端 API(**不经前端**),验证 fs/git/terminal/history/settings 全部可用 +- 契约测试覆盖 195 个 command,且「新增 command 未加路由」会导致测试失败 +- 桌面端行为零变化 diff --git a/docs/architecture/migration/phase-3-engine.csv b/docs/architecture/migration/phase-3-engine.csv new file mode 100644 index 000000000..241ab354b --- /dev/null +++ b/docs/architecture/migration/phase-3-engine.csv @@ -0,0 +1,16 @@ +id,task,status,blocker,verify,notes +P3-01,建 crates/agent-core-js 包骨架,todo,P2-31,"pnpm -C crates/agent-core-js build 产出单文件 JS;node dist/index.js 能启动并监听 loopback",esbuild 打单文件。package.json type=module(与现有 GUI 一致) +P3-02,打包 Node runtime 进桌面产物,todo,P3-01,"卸载系统 Node 后桌面版仍能跑 chat",决策 3。Tauri externalBin 管生命周期;headless 模式由 Rust 直接 spawn。产物预计 +50MB +P3-03,迁入 lib/tools 到 agent-core-js,todo,P3-01,"tools 相关测试(test/tools/ 16 个文件)全绿",11126 行。先搬工具层:它是引擎的依赖,倒过来搬会有中间态调不通 +P3-04,把工具层 invoke() 换成打后端 JSON API,todo,P3-03;P2-28,"每个工具调用都能在后端日志里看到对应的 HTTP 请求",决策 13:走同一套 API,loopback + 内部 token。工具行为不可能两边不一致 +P3-05,迁入 lib/chat + lib/providers + pages/chat/turns,todo,P3-04,"构建通过;test/chat/ 55 个文件全绿",13732 + 8524 + 1800 行。含 runner/ 与 history/chatHistory.ts +P3-06,实现工具审批反向往返,todo,P3-05,"四种情形各有测试:已配策略自动裁决 / 多前端先到先得 / 超时选推荐项 / 无前端连接不阻塞","⚠️ 最大设计风险。必须先写测试再写实现。可复用 toolApproval.ts 已有的 TOOL_APPROVAL_TIMEOUT_MS 与 approve|deny|approve_session 三态、toolPolicy.ts 的 resolveToolPolicy" +P3-07,实现引擎内存态快照接口,todo,P3-05,"turn 进行中调用快照接口,返回值包含正在生成的半条消息","⚠️ 快照源不能是 SQLite:runAgentConversationTurn.ts:1217 落库只在函数末尾一次,查库会看到会话回退到上一轮结束" +P3-08,消灭 3 处浏览器 API 引用,todo,P3-05,"在迁入的代码里 rg window./document./localStorage/navigator. 无输出",引擎里 1 处 + 工具层 2 处 +P3-09,Rust 侧 spawn 并管理 Node 进程生命周期,todo,P3-01;P2-20,"后端启动时 Node 自动起;Node 崩溃后自动重启;后端退出时 Node 一并退出",本地模式下 Tauri 壳退出 → 后端退出 → Node 退出(决策 11) +P3-10,删除 WebView 内的旧执行路径,todo,P3-05;P3-06;P3-07,"rg chatRuntimeHost crates/agent-gui/src 无输出",决策 17:一次切断不留 flag。ChatRuntimeHost.ts 与 useSendChatTurn.ts:1402 的直接调用点 +P3-11,验收:关掉所有前端 chat 仍跑完,todo,P3-09;P3-10,"服务器模式起后端,发一个长任务,关掉浏览器,等任务结束后重开浏览器能看到完整结果","整个方案的验收标准。本地模式不适用(决策 11)" +P3-12,验收:断线重连拉到含半条消息的快照,todo,P3-07;P3-11,"长任务跑到一半刷新前端,重连后看到的内容不回退", +P3-13,验收:多前端审批先到先得,todo,P3-06;P3-11,"两个前端同时连,一个应答后另一个收到「已被应答」", +P3-14,验收:chat 四个边界场景,todo,P3-11,"冷启动首发 / 长挂起后发送 / 发送中断网重连 / 同一 client_request_id 重复提交 —— 全部正常",这四项现在靠 Gateway 的 3800 行补丁保证,新架构下应当天然成立 +P3-15,确认 Gateway chat 补丁已成死代码,todo,P3-11,"注释掉 chat.prepare wake ping / ChatRunLedger / 三层超时后,全部验收项仍通过","做完这条才能在阶段 4 放心删除。涉及 gateway/chat.rs、chat_ingress.rs、chat_inbox.rs 及 Go 侧约 3800 行" diff --git a/docs/architecture/migration/phase-3-engine.md b/docs/architecture/migration/phase-3-engine.md new file mode 100644 index 000000000..1910005ff --- /dev/null +++ b/docs/architecture/migration/phase-3-engine.md @@ -0,0 +1,117 @@ +# 阶段 3 · 抽 Node 引擎,一次切断 + +**状态:⬜ 未开始**(依赖阶段 2) + +## 目标 + +把 chat 引擎从桌面端 **WebView** 搬到后端的 Node 进程。 + +这是整个迁移的**核心一步** —— Go gateway 里那 ~3,800 行可靠性补丁在这一步之后 +全部变成死代码。 + +## 为什么这步最关键 + +现在的真实拓扑: + +``` +远程前端 → Go gateway → Rust → app.emit("gateway:chat-request-ready") → 桌面端 WebView(引擎在这) + ↑ + 一个会睡着、会被关掉、会刷新的浏览器标签页 +``` + +`services/gateway/chat.rs:150` 干的事就是**捅醒桌面端自己的前端**替远程前端跑对话。 +Gateway 里所有让人恶心的东西都是这个拓扑的补丁: + +| Gateway 机制 | 存在的唯一原因 | +|---|---| +| `chat.prepare` 关联 Ping/Pong 唤醒 | WebView 会休眠,得先把它捅醒 | +| `ChatRunLedger` + 5s sweeper 重发终态 | 前端跑的任务不可信,会丢终态 | +| `runReportLostTimeout`(15s)/`stale`(10min)/`offline`(30min) | 同上,三层超时兜底 | +| seq window / `stream_epoch` / `after_seq` replay | 事件生产者是个浏览器标签页 | +| `queued_in_gui` 状态 | 字面意思:给 GUI 排队 | +| `client_request_id` 24h 幂等去重 | ACK 可能丢 | + +**引擎搬到后端后,断线的只是渲染层,后端一直在跑、状态权威。** 于是这些全部不需要。 + +## 引擎已经是 headless 的(已验证) + +``` +lib/chat/runner + pages/chat/turns 里 window./document./localStorage/navigator. : 1 处 +lib/tools(11,126 行)里 : 2 处 +引擎对 React 的依赖 : 0 +pi-agent-core 使用点 agentRunner.ts 单文件 +``` + +而且**引擎能在 Node 里跑已被每日验证**:`crates/agent-gui/test/` 148 个测试文件中 +**131 个**走 `helpers/load-ts-module.mjs` —— 用 `vm` 转译执行真实 TS,并直接 import +pi-ai 的 `json-parse`/`validation`/`event-stream` 内部实现(注释原文: +"so ... code paths behave exactly like runtime")。 + +**这一步不是往未知里跳。** 32k 行引擎住在 WebView 里是历史巧合,不是技术约束。 + +## 要求 + +| # | 要求 | 为什么 | +|---|---|---| +| 1 | **一次切断,不留 feature flag** | 决策 17。双宿主兼容层本身就是新的 bug 源 | +| 2 | 工具层的 `invoke()` 换成打后端同一套 JSON API | 决策 13。工具行为不可能两边不一致 | +| 3 | 消灭那 3 处浏览器 API 引用 | 否则 Node 里跑不起来 | +| 4 | 打包 Node runtime 随产物分发 | 决策 3。不用 Bun —— pi-agent-core 未在 Bun 验证 | +| 5 | Node 只监听 loopback | 决策 5。Rust 是唯一对外入口 | +| 6 | 快照接口返回**引擎内存态** | 见风险 2 | + +## 迁移范围 + +| 源 | 行数 | +|---|---| +| `src/lib/chat/`(含 `runner/`、`history/chatHistory.ts`) | 13,732 | +| `src/lib/tools/` | 11,126 | +| `src/lib/providers/` | 8,524 | +| `src/pages/chat/turns/run*.ts` | 1,800 | +| **合计** | **35,182** | + +目标位置:`crates/agent-core-js/`(Node 包,esbuild 打单文件)。 + +## 风险 1 · 工具审批反向往返(最大设计风险) + +现在 `lib/tools/toolApproval.ts` 与引擎在**同一 JS 上下文**,审批就是个 Promise。 +引擎搬到后端后,**后端要主动向前端发起请求并等回答**,而前端可能根本没连着。 + +按决策 10,规则: + +1. 先跑 `toolPolicy.ts` 的 `resolveToolPolicy`,已配策略的直接裁决,不惊动人 +2. 需要人判断的推给**所有**已连前端,**先到先得**(原子 CAS,后到的收「已被应答」) +3. 超时按现有 `TOOL_APPROVAL_TIMEOUT_MS`;**有推荐项的自动选推荐项**,没有的按拒绝 +4. 无前端连着**不阻塞主流程**,走同一套超时逻辑 + +现有机制可复用:`toolApproval.ts` 已有 `TOOL_APPROVAL_TIMEOUT_MS` 和 +`approve | deny | approve_session` 三态;`toolPolicy.ts` 已有按工具/组/server 的策略解析。 + +**这块必须先写测试再写实现。** + +## 风险 2 · 快照源不能是 SQLite + +`runAgentConversationTurn.ts:1217` —— 落库只有一次,在 1231 行函数的**末尾**。 +turn 进行中不写库。 + +所以决策 19 的「重连拉快照」,快照必须来自 **Node 引擎的内存态**(含正在生成的 +半条消息),不能只查 SQLite,否则断线重连会看到会话**回退到上一轮结束**。 + +SQLite 是持久层,不是快照源。后端需要一个 `GET /api/conversation_live` 之类的接口。 + +## 验收标准(硬门槛) + +**服务器模式下关掉所有前端,chat 仍在后端跑完。** 这是整个方案的验收标准。 + +> 本地模式不适用此项 —— 按决策 11,Tauri 壳退出时一并关后端。 + +另外: + +- 无前端连接时,已配策略的工具自动裁决,需人工的按超时 + 推荐项自动选 +- 长任务跑到一半重启前端,重连后拉到的快照**包含正在生成的半条消息** +- 多前端同时连,审批先到先得,后到的收到「已被应答」 +- 冷启动首发、长时间挂起后发消息、发送中断网重连、同一 `client_request_id` 重复提交 + —— 全部正常 + +做完这步后,Gateway 里的 `chat.prepare` wake ping、`ChatRunLedger`、三层超时、 +seq window 应当已是死代码 —— 阶段 4 一并删除。 diff --git a/docs/architecture/migration/phase-4-frontend.csv b/docs/architecture/migration/phase-4-frontend.csv new file mode 100644 index 000000000..11689e6d7 --- /dev/null +++ b/docs/architecture/migration/phase-4-frontend.csv @@ -0,0 +1,17 @@ +id,task,status,blocker,verify,notes +P4-01,设计并实现 backendClient,todo,P2-28,"createBackendClient({baseUrl, password}) 能发请求、带 Bearer、处理 401","唯一的本地/远程差异就是 baseUrl。命令式路由让这层是薄的:invoke(name, args) → POST /api/{name} args" +P4-02,实现 WS 事件订阅客户端,todo,P4-01;P2-26,"前端能收到 terminal/sftp/history/workspace 等事件;断线自动重连后重新订阅","替代 17 个文件里的 listen()。重连语义按决策 19:拉快照 + 订阅增量,不补发" +P4-03,Tauri 壳注入密码并跳过登录页,todo,P4-01,"桌面版双击启动直接可用,不出现登录页",决策 8。密码初始化动态生成、用户可改 +P4-04,浏览器登录页,todo,P4-01,"浏览器访问远程后端出现登录页,输对密码后进入", +P4-05,替换 57 个文件的 invoke 调用,todo,P4-01,"rg '@tauri-apps/api/core' crates/agent-gui/src 只剩前端专属命令的文件",前端专属的 18 个 command 保留 invoke——托盘/窗口/更新不该走网络 +P4-06,替换 17 个文件的 listen 调用,todo,P4-02,"rg '@tauri-apps/api/event' crates/agent-gui/src 只剩壳事件", +P4-07,处理 plugin-opener 的 7 处,todo,P4-05,"浏览器里打开外链降级为 window.open;桌面里仍走原生", +P4-08,拆分 settings_save_remote,todo,P4-05,"Gateway 连接字段消失;enable_web_* 权限开关仍生效且能门控远程前端","阶段 1 遗留缺陷。RemoteSettingsPayload(settings/types.rs:23)混了两类字段" +P4-09,删除 21 个 deleted.txt 里的 command,todo,P4-05;P3-15,"cargo check -p liveagent 通过;deleted.txt 里的命令名在代码中无残留", +P4-10,删除 services/gateway/,todo,P4-09,"cargo check 通过;rg 'services::gateway' 无输出",10228 行 +P4-11,删除 gateway_bridge.rs 与 gateway_sink.rs,todo,P4-10,"cargo check 通过",约 1550 行 +P4-12,删除 Go 侧 chat 可靠性补丁,todo,P3-15,"Go 侧 chat.prepare wake ping、ChatRunLedger、三层超时、seq window 相关代码删除后 go build 通过",约 3800 行。阶段 3 已验证为死代码才动手 +P4-13,新桌面端检测旧网关地址并提示,todo,P4-03,"配一个旧 gateway 地址,前端给出明确的迁移提示而非静默失败",决策 15 +P4-14,冻结旧 gateway 镜像 tag,todo,,"docker pull 旧 tag 仍成功;旧桌面端配旧网关仍可用",决策 15:破坏性变更无法避免(两边都在等对方来连),但要断得体面 +P4-15,写 README 迁移指南,todo,P4-13;P4-14,"README 有从 v1 gateway 迁到 v2 后端的步骤", +P4-16,验收:改 base URL 即切换本地/远程,todo,P4-05;P4-06,"同一份前端只改 baseUrl,本机后端与远程后端行为一致", diff --git a/docs/architecture/migration/phase-4-frontend.md b/docs/architecture/migration/phase-4-frontend.md new file mode 100644 index 000000000..36e6cdc2f --- /dev/null +++ b/docs/architecture/migration/phase-4-frontend.md @@ -0,0 +1,78 @@ +# 阶段 4 · 前端网络化 + +**状态:⬜ 未开始**(依赖阶段 3) + +## 目标 + +让前端**只通过网络**和后端说话。Tauri IPC 这条隐藏的第二路径彻底消失。 + +做完这步,「本地」和「远程」的区别退化成一个字符串: + +```ts +const backend = createBackendClient({ baseUrl, password }); +// ↑ 唯一的本地/远程差异 +``` + +## 要求 + +| # | 要求 | 为什么 | +|---|---|---| +| 1 | 只留 18 个前端专属 `invoke` | 托盘/窗口/更新是壳能力,不该走网络 | +| 2 | 事件订阅从 `listen()` 换成 WS | 同上,统一传输 | +| 3 | 本地模式由 Tauri 壳注入密码并跳过登录页 | 决策 8。桌面版开箱体验不能退化 | +| 4 | 删除 `services/gateway/*` 与 `gateway_bridge.rs` | 它们在这一步失去全部用户 | +| 5 | 前端行为零变化 | 用户不该感知到这次重构 | + +## 改动面(实测基线) + +| 项 | 数量 | +|---|---| +| import `@tauri-apps/api/core`(`invoke`)的文件 | **57** | +| import `@tauri-apps/api/event`(`listen`)的文件 | **17** | +| `@tauri-apps/plugin-opener` | 7 | +| `api/window` / `api/webview` / `api/path` | 各 1 | + +替换后真正剩下的 Tauri 专属只有约 **10 个文件**。 + +## 删除清单 + +| 目标 | 行数 | +|---|---| +| `services/gateway/*.rs` | 10,228 | +| `services/gateway_bridge.rs` | ~1,400 | +| `src-tauri/gateway_sink.rs` | ~150 | +| 21 个 `deleted.txt` 里的 command | — | +| `commands/integration/gateway.rs` 的 20 个中继命令 | — | +| Go gateway 的 chat 可靠性补丁(阶段 3 已验证为死代码) | ~3,800 | + +## 必须一起处理:`settings_save_remote` 拆两半 + +阶段 1 分类的已知缺陷(见 `backend-boundary.md`「已知分类缺陷」)。 +`RemoteSettingsPayload`(`commands/config/settings/types.rs:23`)混了两类字段: + +| 字段 | 处置 | +|---|---| +| `gateway_url`、`gateway_port`、`token`、`agent_id`、`auto_reconnect`、`heartbeat_interval` | **删除** —— 「连到哪个 Gateway」这个概念不存在了 | +| `enable_web_terminal`、`enable_web_ssh_terminal`、`enable_web_git` 等 | **保留** —— 并入后端的访问控制设置,门控远程前端能干什么 | + +## 破坏性变更(无法避免) + +**旧模型是桌面端 outbound 拨向 gateway,新模型是前端 outbound 拨向后端 —— +两边都在等对方来连,技术上对不上。** 现有部署了 +`ghcr.io/stack-cairn/liveagent-gateway` 的用户一定会断。 + +按决策 15: + +- 发**大版本**(v2.0) +- 旧 gateway 镜像 tag **冻结保留可拉**,旧桌面端配旧网关继续可用 +- 新桌面端检测到旧网关地址时给**明确提示**,不静默失败 +- README 写迁移指南 + +## 验收标准 + +- 同一份前端二进制,**只改 base URL** 就能在「本机后端」和「远程后端」之间切换, + 行为一致 +- 桌面版双击即用:壳注入密码、跳过登录页、无感 +- 浏览器访问远程后端:走登录页、输密码 +- `rg '@tauri-apps/api/core' crates/agent-gui/src` 只剩前端专属命令的文件 +- 旧 gateway 镜像仍可拉、旧桌面端仍可连 diff --git a/docs/architecture/migration/phase-5-merge.csv b/docs/architecture/migration/phase-5-merge.csv new file mode 100644 index 000000000..45e8d3554 --- /dev/null +++ b/docs/architecture/migration/phase-5-merge.csv @@ -0,0 +1,10 @@ +id,task,status,blocker,verify,notes +P5-01,盘点两套前端的真实差异,todo,P4-16,"产出差异清单:GUI 独有 / WebUI 独有 / 已镜像相同",mirror-manifest.json 已列出被 CI 强制字节相同的文件,那部分是零成本合并 +P5-02,设计壳能力探测 API,todo,P5-01,"hasShell()/hasTray()/hasUpdater() 等在 Tauri 里返 true、浏览器里返 false",一份代码 + 运行时探测,不要两份代码靠构建分叉 +P5-03,把登录页从 WebUI 并入 GUI,todo,P5-02,"浏览器访问出现登录页;Tauri 壳里不出现",阶段 4 建的登录页在 WebUI 侧 +P5-04,按探测结果降级 18 个前端专属能力,todo,P5-02,"浏览器里托盘/更新入口隐藏;剪贴板走 navigator.clipboard;文件选择走 input type=file;外链走 window.open", +P5-05,处理 20 个脆测试,todo,P5-01,"这 20 个文件已改写为行为断言或删除;没有被 skip 掉的","它们用 readFileSync + 正则锁死源码文本,不验证行为。另 128 个走 load-ts-module.mjs 的行为型测试保留。不要为了让它们绿而回避重构" +P5-06,删除 crates/agent-gateway/web,todo,P5-03;P5-04,"目录不存在;构建仍通过",126934 行 +P5-07,删除 mirror-manifest.json 与其 CI job,todo,P5-06,"scripts/mirror-manifest.json 不存在;ci.yml 里无 Mirror Check job",合并后镜像机制失去意义 +P5-08,验收:一份代码两种宿主,todo,P5-04;P5-06,"同一份构建产物在 Tauri 壳与浏览器里都可用,能力按探测降级", +P5-09,验收:全功能手工冒烟,todo,P5-08,"桌面端与浏览器端各自逐项过 history/settings/terminal/上传/git review/skills/memory/cron/chat", diff --git a/docs/architecture/migration/phase-5-merge.md b/docs/architecture/migration/phase-5-merge.md new file mode 100644 index 000000000..36bddd7ff --- /dev/null +++ b/docs/architecture/migration/phase-5-merge.md @@ -0,0 +1,72 @@ +# 阶段 5 · 前端合并 + +**状态:⬜ 未开始**(依赖阶段 4) + +## 目标 + +两套前端合并成一套。以 **GUI 为基线**(决策 16),`crates/agent-gateway/web` 删除。 + +## 为什么可行 + +| 前端 | 现在 | 阶段 3 抽走引擎后 | +|---|---|---| +| `crates/agent-gui/src` | 151,713 行 | 约 116,500 行 | +| `crates/agent-gateway/web/src` | 126,934 行 | 126,934 行(本来就不含引擎) | + +**抽走引擎后两边体量几乎一样。** 而且 `scripts/mirror-manifest.json` 已经在用 CI +强制大量文件**字节相同** —— 它本身就是「这两套代码应该是一套」的证据。 + +阶段 4 做完后,GUI 侧真正的 Tauri 专属只剩约 10 个文件,合并阻力很小。 + +## 要求 + +| # | 要求 | 为什么 | +|---|---|---| +| 1 | 以 GUI 为基线 | 决策 16。功能更全、演进更快、历史包裹少 | +| 2 | 壳能力用**运行时探测**降级 | 不要两份代码。浏览器里没有托盘/更新,探测到就隐藏入口 | +| 3 | 删除 `mirror-manifest.json` 及其 CI job | 合并后镜像机制失去意义 | +| 4 | 登录页从 WebUI 并入 | 阶段 4 建的登录页在 WebUI 侧 | +| 5 | 处理 20 个脆测试 | 见下 | + +## 运行时探测,不要两份代码 + +```ts +// 好:一份代码,能力探测 +if (hasShell()) { renderTrayMenu() } + +// 坏:两份代码,靠构建分叉 +// gui/TrayMenu.tsx vs web/NoTrayMenu.tsx +``` + +需要探测的能力:系统托盘、自动更新、窗口控制(置顶/关闭行为/macOS 红绿灯)、 +全局快捷键、系统剪贴板、原生文件对话框、外部程序打开。 + +对应 18 个前端专属 command;浏览器侧要么隐藏入口,要么用 Web API 降级 +(剪贴板 → `navigator.clipboard`,文件选择 → ``, +打开外链 → `window.open`)。 + +## 20 个脆测试 + +`crates/agent-gui/test/` 里有 20 个文件用 `readFileSync` + 正则断言**源码文本**: + +```js +assert.match(composer, /lastEditorSelectionRef = useRef\(null\)/) +``` + +它们**不验证行为**,只锁死源码长相。阶段 4/5 的大规模改动会让它们成片红, +且红了也不代表真有问题。 + +另外 128 个测试是行为型的(走 `helpers/load-ts-module.mjs` 用 `vm` 真实执行 TS), +那些要保留。 + +处置:逐个判断 —— 能改写成行为断言的改写,纯粹锁死实现细节的删掉。 +**不要为了让它们绿而回避重构。** + +## 验收标准 + +- 只有一个前端源码树 +- 同一份代码在 Tauri 壳里和浏览器里都能跑,壳能力按探测结果降级 +- `mirror-manifest.json` 与 `GUI/WebUI Mirror Check` CI job 删除 +- 全部测试绿(脆测试已改写或删除,不是被跳过) +- 手工冒烟:桌面端和浏览器端逐项过 history / settings / terminal / 上传 / + git review / skills / memory / cron / chat diff --git a/docs/architecture/migration/phase-6-cutover.csv b/docs/architecture/migration/phase-6-cutover.csv new file mode 100644 index 000000000..3c2d716a1 --- /dev/null +++ b/docs/architecture/migration/phase-6-cutover.csv @@ -0,0 +1,20 @@ +id,task,status,blocker,verify,notes +P6-01,删除 crates/agent-gateway 全部 Go,todo,P5-09,"find . -name '*.go' -not -path './target/*' 输出 0 行",13914 手写 + 17357 生成 + 10827 测试 +P6-02,删除 proto/v2 与 buf 工具链,todo,P6-01,"find . -name '*.proto' -not -path '*/node_modules/*' 输出 0 行;cargo build 通过","决策 6 定了 JSON 协议。⚠️ src-tauri/build.rs 里有 prost-build 编译 proto/v2,删时要同步清理" +P6-03,移除 prost/protobufjs 依赖,todo,P6-02,"Cargo.toml 与 package.json 里无 prost/protobufjs;构建通过", +P6-04,Dockerfile 改为构建 agent-backend,todo,P5-09;P3-02,"docker build 成功;容器内 /healthz 返回 ok",需打包 Node runtime +P6-05,CI gateway job 换成 cargo test/clippy,todo,P6-01,"ci.yml 里无 go test/golangci-lint/buf;新 job 绿", +P6-06,CI 加编译期防线门禁,todo,P6-05,"故意给 agent-core 加 tauri 依赖后 CI 失败","cargo tree -p agent-core | grep tauri 命中则失败" +P6-07,CI 加 command 分类 diff 门禁,todo,P6-05,"新增一个未分类的 command 后 CI 失败",即阶段 1 的 P1-13 +P6-08,Makefile 目标改名,todo,P6-04,"make dev-backend / backend-build / backend-docker-build 可用",原 dev-gateway / gateway-build / gateway-docker-* +P6-09,删除 docs/architecture/gateway.md,todo,P6-01,"文件不存在;其他文档无指向它的死链", +P6-10,重写 overview.md 与 protocols.md,todo,P6-01,"两份文档描述的是 JSON over HTTP+WS 的两语言架构", +P6-11,合并 webui.md 与 gui.md,todo,P5-06,"只剩一份前端文档",阶段 5 已合并前端代码 +P6-12,重写 operations/deployment.md,todo,P6-04,"按新部署方式(headless 后端 + 可选 TLS/反代)重写", +P6-13,修 README 技术栈表的事实错误,todo,,"README 不再声称依赖 @openai/codex-sdk 与 claude-agent-sdk","现存事实错误,与迁移无关但顺手修:package.json 实际只有 @earendil-works/pi-ai 与 pi-agent-core" +P6-14,重画 README 架构图,todo,P6-10,"架构图反映两语言 + 前后端网络分层",第 254-270 行的三层结构作废 +P6-15,修 README 定位表述,todo,P6-04,"「本地优先」与「秘钥仅保存在桌面端本地」改为准确表述","密钥现在在后端,后端可能在服务器上。NAT 穿透改述为网络问题(tailscale/frp/cloudflared)" +P6-16,发大版本并冻结旧镜像,todo,P6-04;P4-14,"新版本发布;旧 gateway tag 仍可拉;旧桌面端配旧网关仍可用",决策 15 +P6-17,验收:Docker 后端 + 桌面端全功能,todo,P6-04,"Docker 跑后端,笔记本桌面端连过去,逐项过 history/settings/terminal/上传/git review/skills/memory/cron/chat(冷启动/长挂起/断网重连)", +P6-18,验收:浏览器连同一后端全功能,todo,P6-17,"浏览器端同样逐项通过", +P6-19,记录镜像体积与启动时间对比,todo,P6-04,"文档里有新旧对比数字","Node runtime 会让镜像变大,这是决策 3 的已知代价,记下来而不是假装没有" diff --git a/docs/architecture/migration/phase-6-cutover.md b/docs/architecture/migration/phase-6-cutover.md new file mode 100644 index 000000000..2e6fe017c --- /dev/null +++ b/docs/architecture/migration/phase-6-cutover.md @@ -0,0 +1,83 @@ +# 阶段 6 · 删除 Go 与切换发布 + +**状态:⬜ 未开始**(依赖阶段 5) + +## 目标 + +删掉最后的 Go 代码,切换构建与发布,重写文档。仓库从三语言收敛为两语言。 + +## 删除清单 + +| 目标 | 行数 | +|---|---| +| `crates/agent-gateway/` 手写 Go | 13,914 | +| 生成的 protobuf(Go) | 17,357 | +| Go 测试 | 10,827 | +| `proto/v2/*.proto` + buf 工具链 | — | +| **合计** | **42,098+** | + +`proto/v2` 一起删是因为决策 6 把线上协议定为 JSON —— 契约不再需要 protobuf, +`prost`/`protobufjs` 依赖、buf codegen、生成物漂移门禁一并消失。 + +> 注意 `crates/agent-gui/src-tauri/build.rs` 里有 prost-build 调用编译 +> `crates/agent-gateway/proto/v2/*.proto`,删 proto 时要同步清理。 + +## 构建与 CI 切换 + +| 项 | 现在 | 之后 | +|---|---|---| +| `Dockerfile` | 3 阶段:node 构建 SPA → golang 构建二进制 → debian-slim | 构建 `agent-backend` + 打包 Node runtime | +| CI `gateway` job | proto lint + breaking check + 生成物漂移 + golangci-lint + go test | `cargo test` + `cargo clippy` | +| CI `GUI/WebUI Mirror Check` | 强制两套前端字节相同 | 阶段 5 已删 | +| `Makefile` | `dev-gateway` / `gateway-build` / `gateway-docker-*` | 改名为 `dev-backend` / `backend-build` / … | +| `railway.json` | healthcheck `/healthz` | 保留(新后端也有 `/healthz`) | + +新增门禁建议: + +- `cargo tree -p agent-core | grep tauri` 命中则失败(编译期防线的 CI 版) +- 阶段 1 的 command 分类 diff(P1-13) + +## 文档重写 + +| 文件 | 处置 | +|---|---| +| `docs/architecture/gateway.md` | **删除**(Gateway 不存在了) | +| `docs/architecture/overview.md` | 重写三层结构 | +| `docs/architecture/protocols.md` | 重写为 JSON over HTTP+WS | +| `docs/architecture/webui.md` / `gui.md` | 合并成一份(阶段 5 已合并前端) | +| `docs/operations/deployment.md` | 重写部署方式 | +| `README.md` / `README.zh-CN.md` | 见下 | + +## README 需要修的三处 + +1. **技术栈表(第 283 行附近)** 列了 `@openai/codex-sdk` 和 `claude-agent-sdk`, + 但 `crates/agent-gui/package.json` **没有这两个依赖** —— 实际只有 + `@earendil-works/pi-ai` 和 `@earendil-works/pi-agent-core`。这是现存的事实错误, + 与本次迁移无关但顺手修掉。 +2. **架构图(第 254–270 行)** 的三层结构整体作废,重画。 +3. **定位表述**:第 82 行「本地优先」、第 352 行「秘钥仅保存在桌面端本地」。 + 新架构下密钥在**后端**,后端可能在服务器上。改成「密钥只在后端,后端由你自己部署」。 + +另外 NAT 穿透的表述要改:后端在家、人在公司仍需打洞,但这现在是**网络问题** +(tailscale / frp / cloudflared),不是应用问题 —— 应用里不再有约 1,100 行会合注册代码。 + +## 验收标准 + +```bash +# 仓库无 Go、无 proto +find . -name '*.go' -not -path './target/*' | wc -l # 期望 0 +find . -name '*.proto' -not -path '*/node_modules/*' | wc -l # 期望 0 + +cargo test --workspace && cargo clippy --workspace -- -D warnings +cargo tree -p agent-core | grep -q tauri && echo "防线破了" || echo "防线完好" +``` + +端到端: + +- Docker 跑 headless 后端,笔记本上的桌面端连过去,全功能验证: + history / settings / terminal / 上传 / git review / skills / memory / cron / + chat(冷启动 / 长挂起 / 断网重连) +- 浏览器连同一个后端,同样全过 +- **旧 gateway 镜像 tag 仍可拉、旧桌面端仍可连**(决策 15) +- 记录镜像体积与启动时间,与 Go 版本对比 —— Node runtime 会让镜像变大, + 这是决策 3 的已知代价,记下来而不是假装没有 From 1363c2c162a260474460a65e5e480f1faabf0504 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 05:51:35 -0700 Subject: [PATCH 07/75] refactor(backend): replace tauri::async_runtime with tokio across the migrating set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 175 spawn_blocking + 50 spawn + 1 JoinHandle。前提已在 P2-12 核验:底层就是 tokio,且后端所有 spawn 点都只从 async 上下文可达,不会撞上 tokio::spawn 的 「runtime 之外 panic」。 src-tauri 的 tokio features 补 rt / rt-multi-thread / process——原先只列了 macros/net/sync/time/io-util,靠 tauri 间接把 runtime 带进来。 services/gateway/、gateway_bridge.rs、commands/integration/gateway.rs 不动: 阶段 4 整体删除,改它们是纯粹的浪费。 --- crates/agent-gui/src-tauri/Cargo.toml | 2 +- .../src-tauri/src/commands/app/system.rs | 34 +++++------ .../src-tauri/src/commands/automation/cron.rs | 20 +++---- .../src-tauri/src/commands/automation/hook.rs | 4 +- .../commands/config/settings/ccs_import.rs | 2 +- .../commands/config/settings/cherry_import.rs | 4 +- .../src/commands/config/settings/commands.rs | 20 +++---- .../commands/history/chat_history/branch.rs | 2 +- .../commands/history/chat_history/commands.rs | 34 +++++------ .../commands/history/chat_history/delete.rs | 2 +- .../commands/history/chat_history/replace.rs | 2 +- .../src/commands/history/subagent_store.rs | 16 ++--- .../src-tauri/src/commands/integration/mcp.rs | 4 +- .../src/commands/integration/memory.rs | 44 +++++++------- .../src-tauri/src/commands/runtime/shell.rs | 2 +- .../src/commands/workspace/chat_file_links.rs | 2 +- .../src-tauri/src/commands/workspace/fs.rs | 4 +- .../src-tauri/src/commands/workspace/git.rs | 60 +++++++++---------- .../commands/workspace/subagent_worktree.rs | 8 +-- .../agent-gui/src-tauri/src/gateway_sink.rs | 8 +-- crates/agent-gui/src-tauri/src/lib.rs | 4 +- .../agent-gui/src-tauri/src/runtime/sftp.rs | 2 +- .../src-tauri/src/runtime/terminal/ssh_io.rs | 4 +- .../src/runtime/terminal/ssh_local_forward.rs | 4 +- .../src/runtime/terminal/ssh_session.rs | 4 +- .../src/services/automation/scheduler.rs | 30 +++++----- .../agent-gui/src-tauri/src/services/proxy.rs | 2 +- .../src-tauri/src/services/tunnel/mod.rs | 10 ++-- .../src-tauri/src/services/tunnel/proxy.rs | 10 ++-- .../src-tauri/src/services/tunnel/store.rs | 6 +- 30 files changed, 175 insertions(+), 175 deletions(-) diff --git a/crates/agent-gui/src-tauri/Cargo.toml b/crates/agent-gui/src-tauri/Cargo.toml index 4f4c24fc9..61f306d2a 100644 --- a/crates/agent-gui/src-tauri/Cargo.toml +++ b/crates/agent-gui/src-tauri/Cargo.toml @@ -30,7 +30,7 @@ reqwest = { version = "0.13.4", features = ["blocking", "json", "stream", "socks rquickjs = { version = "0.8", features = ["array-buffer", "classes", "bindgen"] } percent-encoding = "2.3.2" axum = "0.8.9" -tokio = { version = "1.52.3", features = ["macros", "net", "sync", "time", "io-util"] } +tokio = { version = "1.52.3", features = ["macros", "net", "sync", "time", "io-util", "rt", "rt-multi-thread", "process"] } tokio-stream = "0.1.18" tokio-tungstenite = { version = "0.29.0", features = ["rustls-tls-webpki-roots"] } futures-util = "0.3.32" diff --git a/crates/agent-gui/src-tauri/src/commands/app/system.rs b/crates/agent-gui/src-tauri/src/commands/app/system.rs index 3f89e745c..72ddaa6a6 100644 --- a/crates/agent-gui/src-tauri/src/commands/app/system.rs +++ b/crates/agent-gui/src-tauri/src/commands/app/system.rs @@ -694,7 +694,7 @@ fn gc_upload_staging_in(base: &Path, now: SystemTime, retention: std::time::Dura /// 启动时清理过期的上传批次;失败只记录,绝不阻断启动。 pub fn gc_upload_staging_on_startup() { - tauri::async_runtime::spawn_blocking(|| { + tokio::task::spawn_blocking(|| { if let Ok(base) = upload_staging_base() { gc_upload_staging_in(&base, SystemTime::now(), UPLOAD_STAGING_RETENTION); } @@ -1334,7 +1334,7 @@ pub(crate) fn system_create_project_folder_sync( #[tauri::command(rename_all = "snake_case")] pub async fn system_pick_folder(initial_workdir: Option) -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut dialog = FileDialog::new(); if let Some(initial_dir) = resolve_pick_folder_initial_dir(initial_workdir) { dialog = dialog.set_directory(initial_dir); @@ -1354,7 +1354,7 @@ pub async fn system_pick_file( filter_name: Option, extensions: Option>, ) -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut dialog = FileDialog::new(); if let Some(initial_dir) = resolve_pick_folder_initial_dir(initial_workdir) { dialog = dialog.set_directory(initial_dir); @@ -1377,7 +1377,7 @@ pub async fn system_create_project_folder( parent: String, name: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || system_create_project_folder_sync(parent, name)) + tokio::task::spawn_blocking(move || system_create_project_folder_sync(parent, name)) .await .map_err(|e| format!("system_create_project_folder join 失败:{e}"))? } @@ -1387,7 +1387,7 @@ pub async fn system_pick_readable_files( workdir: String, max_files: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { system_pick_readable_files_sync(workdir, max_files) }) .await @@ -1400,7 +1400,7 @@ pub async fn system_import_readable_file_paths( paths: Vec, max_files: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { system_import_readable_file_paths_sync(workdir, paths, max_files) }) .await @@ -1413,7 +1413,7 @@ pub async fn system_import_uploaded_readable_files( files: Vec, max_files: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { system_import_uploaded_readable_files_from_base64_sync(workdir, files, max_files) }) .await @@ -1425,7 +1425,7 @@ pub async fn system_import_pasted_texts( workdir: String, texts: Vec, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let uploads = texts .into_iter() .map(|text| SystemReadableFileUploadInput { @@ -1445,7 +1445,7 @@ pub async fn system_read_uploaded_image_preview( workdir: String, absolute_path: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { system_read_uploaded_image_preview_sync(workdir, absolute_path) }) .await @@ -1458,7 +1458,7 @@ pub async fn system_read_uploaded_native_attachment( absolute_path: Option, kind: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { system_read_uploaded_native_attachment_sync(workdir, absolute_path, kind) }) .await @@ -1467,7 +1467,7 @@ pub async fn system_read_uploaded_native_attachment( #[tauri::command] pub async fn system_list_skill_files() -> Result { - tauri::async_runtime::spawn_blocking(system_list_skill_files_sync) + tokio::task::spawn_blocking(system_list_skill_files_sync) .await .map_err(|e| format!("system_list_skill_files join 失败:{e}"))? } @@ -1475,14 +1475,14 @@ pub async fn system_list_skill_files() -> Result Result, String> { - tauri::async_runtime::spawn_blocking(crate::services::skills::ensure_builtin_agent_skills_sync) + tokio::task::spawn_blocking(crate::services::skills::ensure_builtin_agent_skills_sync) .await .map_err(|e| format!("system_ensure_builtin_skills join failed: {e}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn system_manage_skill(payload: Value) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { crate::services::skills::system_manage_skill_sync(payload) }) .await @@ -1495,7 +1495,7 @@ pub async fn system_read_skill_text( offset: Option, length: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || system_read_skill_text_sync(path, offset, length)) + tokio::task::spawn_blocking(move || system_read_skill_text_sync(path, offset, length)) .await .map_err(|e| format!("system_read_skill_text join failed: {e}"))? } @@ -1504,7 +1504,7 @@ pub async fn system_read_skill_text( pub async fn system_read_skill_metadata( path: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || system_read_skill_metadata_sync(path)) + tokio::task::spawn_blocking(move || system_read_skill_metadata_sync(path)) .await .map_err(|e| format!("system_read_skill_metadata join 失败:{e}"))? } @@ -1514,7 +1514,7 @@ pub async fn system_append_debug_jsonl( conversation_id: String, entry: Value, ) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { system_append_debug_jsonl_sync(conversation_id, entry) }) .await @@ -1537,7 +1537,7 @@ fn system_clipboard_read_text_sync() -> Result { #[tauri::command] pub async fn system_clipboard_read_text() -> Result { - tauri::async_runtime::spawn_blocking(system_clipboard_read_text_sync) + tokio::task::spawn_blocking(system_clipboard_read_text_sync) .await .map_err(|e| format!("system_clipboard_read_text join failed: {e}"))? } diff --git a/crates/agent-gui/src-tauri/src/commands/automation/cron.rs b/crates/agent-gui/src-tauri/src/commands/automation/cron.rs index 3c0eec8b2..81784ab6f 100644 --- a/crates/agent-gui/src-tauri/src/commands/automation/cron.rs +++ b/crates/agent-gui/src-tauri/src/commands/automation/cron.rs @@ -8,7 +8,7 @@ use crate::services::automation::{ #[tauri::command(rename_all = "snake_case")] pub async fn cron_validate_expression(expression: String) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || validate_cron_expression(&expression)) + tokio::task::spawn_blocking(move || validate_cron_expression(&expression)) .await .map_err(|e| format!("cron_validate_expression join 失败:{e}"))? } @@ -18,7 +18,7 @@ pub async fn automation_snapshot( store: tauri::State<'_, Arc>, ) -> Result { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.snapshot()) + tokio::task::spawn_blocking(move || store.snapshot()) .await .map_err(|e| format!("automation_snapshot join 失败:{e}"))? } @@ -29,7 +29,7 @@ pub async fn automation_cron_apply( store: tauri::State<'_, Arc>, ) -> Result { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.cron_apply(input)) + tokio::task::spawn_blocking(move || store.cron_apply(input)) .await .map_err(|e| format!("automation_cron_apply join 失败:{e}"))? } @@ -40,7 +40,7 @@ pub async fn automation_hooks_apply( store: tauri::State<'_, Arc>, ) -> Result { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.hooks_apply(input)) + tokio::task::spawn_blocking(move || store.hooks_apply(input)) .await .map_err(|e| format!("automation_hooks_apply join 失败:{e}"))? } @@ -52,7 +52,7 @@ pub async fn automation_list_runs( store: tauri::State<'_, Arc>, ) -> Result, String> { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.list_runs(&task_id, limit.unwrap_or(100))) + tokio::task::spawn_blocking(move || store.list_runs(&task_id, limit.unwrap_or(100))) .await .map_err(|e| format!("automation_list_runs join 失败:{e}"))? } @@ -63,7 +63,7 @@ pub async fn automation_clear_runs( store: tauri::State<'_, Arc>, ) -> Result { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.clear_runs(&task_id)) + tokio::task::spawn_blocking(move || store.clear_runs(&task_id)) .await .map_err(|e| format!("automation_clear_runs join 失败:{e}"))? } @@ -74,7 +74,7 @@ pub async fn automation_run_cron_now( store: tauri::State<'_, Arc>, ) -> Result { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.run_cron_task_now(&task_id)) + tokio::task::spawn_blocking(move || store.run_cron_task_now(&task_id)) .await .map_err(|e| format!("automation_run_cron_now join 失败:{e}"))? } @@ -84,7 +84,7 @@ pub async fn automation_claim_prompt_runs( store: tauri::State<'_, Arc>, ) -> Result, String> { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.claim_prompt_runs()) + tokio::task::spawn_blocking(move || store.claim_prompt_runs()) .await .map_err(|e| format!("automation_claim_prompt_runs join 失败:{e}"))? } @@ -95,7 +95,7 @@ pub async fn automation_release_prompt_run( store: tauri::State<'_, Arc>, ) -> Result<(), String> { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.release_prompt_run(&execution_id)) + tokio::task::spawn_blocking(move || store.release_prompt_run(&execution_id)) .await .map_err(|e| format!("automation_release_prompt_run join 失败:{e}"))? } @@ -106,7 +106,7 @@ pub async fn automation_complete_prompt_run( store: tauri::State<'_, Arc>, ) -> Result { let store = Arc::clone(store.inner()); - tauri::async_runtime::spawn_blocking(move || store.complete_prompt_run(input)) + tokio::task::spawn_blocking(move || store.complete_prompt_run(input)) .await .map_err(|e| format!("automation_complete_prompt_run join 失败:{e}"))? } diff --git a/crates/agent-gui/src-tauri/src/commands/automation/hook.rs b/crates/agent-gui/src-tauri/src/commands/automation/hook.rs index a806c15bf..2fa0cb45f 100644 --- a/crates/agent-gui/src-tauri/src/commands/automation/hook.rs +++ b/crates/agent-gui/src-tauri/src/commands/automation/hook.rs @@ -277,7 +277,7 @@ pub async fn hook_run_script( ) -> Result { let registry = Arc::clone(registry.inner()); let envs = normalize_context(context); - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { run_hook_script_sync(®istry, workdir, script, timeout_ms, scope_id, envs) }) .await @@ -291,7 +291,7 @@ pub async fn hook_run_http_requests( registry: tauri::State<'_, Arc>, ) -> Result { let registry = Arc::clone(registry.inner()); - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { run_hook_http_requests_sync(®istry, requests, scope_id) }) .await diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs b/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs index 88547c8ca..e278a6b7d 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs +++ b/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs @@ -21,7 +21,7 @@ pub struct CcsProvidersResponse { #[tauri::command] pub async fn settings_list_ccswitch_providers() -> Result { - tauri::async_runtime::spawn_blocking(|| { + tokio::task::spawn_blocking(|| { let candidates = ccswitch_db_candidates(); let path = candidates.iter().find(|path| path.exists()); let providers = match path { diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs b/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs index b05ab4973..9eb52e3b4 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs +++ b/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs @@ -81,7 +81,7 @@ struct CherryImportScan { #[tauri::command] pub async fn settings_list_cherry_studio_providers() -> Result { - tauri::async_runtime::spawn_blocking(|| { + tokio::task::spawn_blocking(|| { cherry_scan_candidates(&cherry_user_data_candidates(), false) }) .await @@ -92,7 +92,7 @@ pub async fn settings_list_cherry_studio_providers() -> Result Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let selected = PathBuf::from(data_path.trim()); if selected.as_os_str().is_empty() { return Err("未选择 Cherry Studio 数据目录".to_string()); diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs b/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs index d41e75460..24602aa1b 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs +++ b/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs @@ -1,6 +1,6 @@ #[tauri::command] pub async fn settings_load_all() -> Result { - tauri::async_runtime::spawn_blocking(|| { + tokio::task::spawn_blocking(|| { let conn = open_db()?; let default_workdir = default_project_workdir()?; Ok(SettingsLoadResponse { @@ -20,7 +20,7 @@ pub async fn settings_load_all() -> Result { #[tauri::command] pub async fn settings_save_providers(payload: Value) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_providers(&mut conn, payload) }) @@ -33,7 +33,7 @@ pub async fn settings_save_system( payload: Value, automation_scheduler: tauri::State<'_, Arc>, ) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_system(&mut conn, payload)?; // 保存成功后刷新全局代理状态,让 shell env 注入与出网代理即时生效。 @@ -49,7 +49,7 @@ pub async fn settings_save_system( #[tauri::command] pub async fn settings_save_mcp(payload: Value) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_mcp(&mut conn, payload) }) @@ -62,7 +62,7 @@ pub async fn settings_save_remote( payload: Value, gateway_controller: tauri::State<'_, Arc>, ) -> Result<(), String> { - let normalized = tauri::async_runtime::spawn_blocking(move || { + let normalized = tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_remote(&mut conn, payload) }) @@ -73,7 +73,7 @@ pub async fn settings_save_remote( #[tauri::command] pub async fn settings_save_memory(payload: Value) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_memory(&mut conn, payload) }) @@ -83,7 +83,7 @@ pub async fn settings_save_memory(payload: Value) -> Result<(), String> { #[tauri::command] pub async fn settings_save_agents(payload: Value) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_agents(&mut conn, payload) }) @@ -93,7 +93,7 @@ pub async fn settings_save_agents(payload: Value) -> Result<(), String> { #[tauri::command] pub async fn settings_save_ssh(payload: Value) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_ssh(&mut conn, payload) }) @@ -103,7 +103,7 @@ pub async fn settings_save_ssh(payload: Value) -> Result<(), String> { #[tauri::command] pub async fn settings_apply_ssh_patch(payload: Value) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; apply_ssh_patch_with_conn(&mut conn, payload) }) @@ -116,7 +116,7 @@ pub async fn settings_reset_ssh_known_host( host: String, port: u16, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let deleted = reset_runtime_ssh_known_host(&host, port)?; Ok(SshKnownHostResetResponse { deleted }) }) diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs index b8336e322..3ab881d53 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs @@ -214,7 +214,7 @@ pub(crate) async fn chat_history_branch_inner( id: String, anchor: ChatHistoryMessageRef, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; chat_history_branch_sync(&mut conn, &id, &anchor) }) diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs index b0160b91e..70ee80553 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs @@ -5,7 +5,7 @@ pub async fn chat_history_list( cwd: Option, cwd_empty: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; list_chat_history_sync_with_filter( &conn, @@ -23,7 +23,7 @@ pub async fn chat_history_list( #[tauri::command] pub async fn chat_history_workdirs() -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; list_chat_history_workdirs_sync(&conn) }) @@ -36,7 +36,7 @@ pub async fn chat_history_shared_list( page: i64, page_size: i64, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { list_shared_chat_history_page_sync(page, page_size) }) .await @@ -47,7 +47,7 @@ pub async fn chat_history_shared_list( pub async fn chat_history_search( args: ChatHistorySearchArgs, ) -> Result { - tauri::async_runtime::spawn_blocking(move || search_chat_history_sync(args)) + tokio::task::spawn_blocking(move || search_chat_history_sync(args)) .await .map_err(|e| format!("chat_history_search join 失败:{e}"))? } @@ -55,7 +55,7 @@ pub async fn chat_history_search( pub(crate) async fn chat_history_get_summary_inner( id: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; get_summary_by_id(&conn, &id) }) @@ -66,7 +66,7 @@ pub(crate) async fn chat_history_get_summary_inner( // 桌面前端已迁移到窗口化的 chat_history_get_window;全量读取仅剩 // gateway_bridge 的服务端投影在用,因此不再作为 webview command 暴露。 pub async fn chat_history_get(id: String) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let chat_id = id.trim().to_string(); if chat_id.is_empty() { return Err("历史对话 id 不能为空".to_string()); @@ -89,7 +89,7 @@ pub(crate) async fn chat_history_get_tail( id: String, max_messages: i64, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let chat_id = id.trim().to_string(); if chat_id.is_empty() { return Err("历史对话 id 不能为空".to_string()); @@ -218,7 +218,7 @@ pub async fn chat_history_get_window( expected_revision: Option, include_active_segment: bool, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; chat_history_get_window_sync( &mut conn, @@ -236,7 +236,7 @@ pub async fn chat_history_get_window( pub(crate) async fn chat_history_upsert_inner( input: ChatHistoryUpsertInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { validate_upsert_input(&input)?; let conversation = ChatHistoryConversationInput { id: input.id.clone(), @@ -290,7 +290,7 @@ pub async fn chat_history_upsert( pub(crate) async fn chat_history_upsert_active_segment_inner( input: ChatHistorySegmentMutationInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { validate_segment_mutation_input(&input)?; let mut conn = open_db()?; let tx = conn @@ -323,7 +323,7 @@ pub async fn chat_history_upsert_active_segment( pub(crate) async fn chat_history_append_segment_inner( input: ChatHistorySegmentMutationInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { validate_segment_mutation_input(&input)?; let mut conn = open_db()?; let tx = conn @@ -358,7 +358,7 @@ pub(crate) async fn chat_history_rename_inner( id: String, title: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; rename_chat_history_sync(&conn, &id, &title) }) @@ -381,7 +381,7 @@ pub(crate) async fn chat_history_set_pinned_inner( id: String, is_pinned: bool, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; set_chat_history_pinned_sync(&conn, &id, is_pinned) }) @@ -404,7 +404,7 @@ pub(crate) async fn chat_history_set_model_inner( id: String, selected_model_json: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; set_chat_history_model_sync(&conn, &id, &selected_model_json) }) @@ -426,7 +426,7 @@ pub async fn chat_history_set_model( pub(crate) async fn chat_history_share_get_inner( id: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; get_chat_history_share_status_sync(&conn, &id) }) @@ -444,7 +444,7 @@ pub(crate) async fn chat_history_share_set_inner( enabled: bool, redact_tool_content: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; set_chat_history_share_enabled_sync(&conn, &id, enabled, redact_tool_content) }) @@ -472,7 +472,7 @@ pub async fn chat_history_share_set( pub(crate) async fn chat_history_share_resolve_inner( token: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; resolve_chat_history_share_sync(&conn, &token) }) diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs index b108e547d..0f7bcc7e9 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs @@ -42,7 +42,7 @@ fn delete_chat_history_sync( } pub(crate) async fn chat_history_delete_inner(id: String) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let chat_id = id.trim().to_string(); let mut conn = open_db()?; let mut subagent_prune_result = delete_chat_history_sync(&mut conn, &chat_id)?; diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs index e54ea6abe..ee6687000 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs @@ -138,7 +138,7 @@ pub(crate) async fn chat_history_replace_from_message_inner( max_messages: i64, expected_revision: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; chat_history_replace_from_message_sync( &mut conn, diff --git a/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs b/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs index b2a220b93..5ca887367 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs @@ -1255,7 +1255,7 @@ pub(crate) fn list_subagent_messages_sync( pub async fn subagent_identity_upsert( input: SubagentIdentityUpsertInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; upsert_subagent_identity_sync(&conn, &input) }) @@ -1267,7 +1267,7 @@ pub async fn subagent_identity_upsert( pub async fn subagent_identity_list( input: SubagentIdentityListInput, ) -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; list_subagent_identities_sync(&conn, &input) }) @@ -1277,7 +1277,7 @@ pub async fn subagent_identity_list( #[tauri::command] pub async fn subagent_run_save(input: SubagentRunSaveInput) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_subagent_run_sync(&mut conn, &input) }) @@ -1289,7 +1289,7 @@ pub async fn subagent_run_save(input: SubagentRunSaveInput) -> Result<(), String pub async fn subagent_run_list( input: SubagentRunListInput, ) -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; list_subagent_runs_sync(&conn, &input) }) @@ -1301,7 +1301,7 @@ pub async fn subagent_run_list( pub async fn subagent_run_load( input: SubagentRunLoadInput, ) -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; load_subagent_run_sync(&conn, &input.id) }) @@ -1313,7 +1313,7 @@ pub async fn subagent_run_load( pub async fn subagent_run_prune( input: SubagentRunPruneInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || prune_subagent_runs(input)) + tokio::task::spawn_blocking(move || prune_subagent_runs(input)) .await .map_err(|e| format!("subagent_run_prune join failed: {e}"))? } @@ -1322,7 +1322,7 @@ pub async fn subagent_run_prune( pub async fn subagent_message_append( input: SubagentMessageAppendInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let mut conn = open_db()?; append_subagent_message_sync(&mut conn, &input) }) @@ -1334,7 +1334,7 @@ pub async fn subagent_message_append( pub async fn subagent_message_list( input: SubagentMessageListInput, ) -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; list_subagent_messages_sync(&conn, &input) }) diff --git a/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs b/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs index 2f9bf2dfd..884d8fec4 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs +++ b/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs @@ -26,7 +26,7 @@ async fn run_blocking( label: &'static str, f: impl FnOnce() -> Result + Send + 'static, ) -> Result { - tauri::async_runtime::spawn_blocking(f) + tokio::task::spawn_blocking(f) .await .map_err(|e| format!("{label} join failed: {e}"))? } @@ -1710,7 +1710,7 @@ pub async fn mcp_call_tool( .as_deref() .map(|id| run_registry.register(id)); let registered_token = cancel_token.clone(); - let mut task = tauri::async_runtime::spawn_blocking(move || { + let mut task = tokio::task::spawn_blocking(move || { let id = server_id.trim().to_string(); if id.is_empty() { return Err("server_id cannot be empty".to_string()); diff --git a/crates/agent-gui/src-tauri/src/commands/integration/memory.rs b/crates/agent-gui/src-tauri/src/commands/integration/memory.rs index ff2b507e0..9c87b753f 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/memory.rs +++ b/crates/agent-gui/src-tauri/src/commands/integration/memory.rs @@ -23,7 +23,7 @@ pub async fn memory_list( args: MemoryListArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.list(args)) + tokio::task::spawn_blocking(move || store.list(args)) .await .map_err(|e| format!("memory_list join 失败:{e}"))? } @@ -34,7 +34,7 @@ pub async fn memory_read( args: MemoryReadArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.read(args)) + tokio::task::spawn_blocking(move || store.read(args)) .await .map_err(|e| format!("memory_read join 失败:{e}"))? } @@ -45,7 +45,7 @@ pub async fn memory_search( args: MemorySearchArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let history_args = args.clone(); let mut response = store.search(args)?; response.history_matches = @@ -62,7 +62,7 @@ pub async fn memory_write( args: MemoryWriteArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.write(args)) + tokio::task::spawn_blocking(move || store.write(args)) .await .map_err(|e| format!("memory_write join 失败:{e}"))? } @@ -73,7 +73,7 @@ pub async fn memory_update( args: MemoryUpdateArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.update(args)) + tokio::task::spawn_blocking(move || store.update(args)) .await .map_err(|e| format!("memory_update join 失败:{e}"))? } @@ -84,7 +84,7 @@ pub async fn memory_delete( args: MemoryDeleteArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.delete(args)) + tokio::task::spawn_blocking(move || store.delete(args)) .await .map_err(|e| format!("memory_delete join 失败:{e}"))? } @@ -95,7 +95,7 @@ pub async fn memory_delete_project( args: MemoryDeleteProjectArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.delete_project(args)) + tokio::task::spawn_blocking(move || store.delete_project(args)) .await .map_err(|e| format!("memory_delete_project join 失败:{e}"))? } @@ -106,7 +106,7 @@ pub async fn memory_accept( args: MemoryAcceptArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.accept(args)) + tokio::task::spawn_blocking(move || store.accept(args)) .await .map_err(|e| format!("memory_accept join 失败:{e}"))? } @@ -117,7 +117,7 @@ pub async fn memory_apply_batch( args: MemoryBatchArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.apply_batch(args)) + tokio::task::spawn_blocking(move || store.apply_batch(args)) .await .map_err(|e| format!("memory_apply_batch join 失败:{e}"))? } @@ -128,7 +128,7 @@ pub async fn memory_organize_run_create( args: MemoryOrganizeRunCreateArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.organize_run_create(args)) + tokio::task::spawn_blocking(move || store.organize_run_create(args)) .await .map_err(|e| format!("memory_organize_run_create join 失败:{e}"))? } @@ -139,7 +139,7 @@ pub async fn memory_organize_run_update( args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.organize_run_update(args)) + tokio::task::spawn_blocking(move || store.organize_run_update(args)) .await .map_err(|e| format!("memory_organize_run_update join 失败:{e}"))? } @@ -151,7 +151,7 @@ pub async fn memory_organize_run_list( ) -> Result { let store = Arc::clone(&state); let resolved = args.unwrap_or_default(); - tauri::async_runtime::spawn_blocking(move || store.organize_run_list(resolved)) + tokio::task::spawn_blocking(move || store.organize_run_list(resolved)) .await .map_err(|e| format!("memory_organize_run_list join 失败:{e}"))? } @@ -162,7 +162,7 @@ pub async fn memory_organize_run_read( args: MemoryOrganizeRunReadArgs, ) -> Result, String> { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.organize_run_read(args)) + tokio::task::spawn_blocking(move || store.organize_run_read(args)) .await .map_err(|e| format!("memory_organize_run_read join 失败:{e}"))? } @@ -172,7 +172,7 @@ pub async fn memory_organize_run_clear_history( state: State<'_, Arc>, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.organize_run_clear_history()) + tokio::task::spawn_blocking(move || store.organize_run_clear_history()) .await .map_err(|e| format!("memory_organize_run_clear_history join 失败:{e}"))? } @@ -183,7 +183,7 @@ pub async fn memory_organize_due_claim( args: MemoryOrganizeDueClaimArgs, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.organize_due_claim(args)) + tokio::task::spawn_blocking(move || store.organize_due_claim(args)) .await .map_err(|e| format!("memory_organize_due_claim join 失败:{e}"))? } @@ -194,7 +194,7 @@ pub async fn memory_organize_due_complete( args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.organize_due_complete(args)) + tokio::task::spawn_blocking(move || store.organize_due_complete(args)) .await .map_err(|e| format!("memory_organize_due_complete join 失败:{e}"))? } @@ -205,7 +205,7 @@ pub async fn memory_index_overview( workdir: Option, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.overview(workdir)) + tokio::task::spawn_blocking(move || store.overview(workdir)) .await .map_err(|e| format!("memory_index_overview join 失败:{e}"))? } @@ -215,7 +215,7 @@ pub async fn memory_paths_info( state: State<'_, Arc>, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.paths_info()) + tokio::task::spawn_blocking(move || store.paths_info()) .await .map_err(|e| format!("memory_paths_info join 失败:{e}"))? } @@ -227,7 +227,7 @@ pub async fn memory_recent_rejections( ) -> Result { let store = Arc::clone(&state); let resolved = args.unwrap_or_default(); - tauri::async_runtime::spawn_blocking(move || store.recent_rejections(resolved)) + tokio::task::spawn_blocking(move || store.recent_rejections(resolved)) .await .map_err(|e| format!("memory_recent_rejections join 失败:{e}"))? } @@ -246,7 +246,7 @@ pub async fn memory_today_daily( rollover_hour: Option, ) -> Result, String> { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.today_daily(rollover_hour)) + tokio::task::spawn_blocking(move || store.today_daily(rollover_hour)) .await .map_err(|e| format!("memory_today_daily join 失败:{e}"))? } @@ -258,7 +258,7 @@ pub async fn memory_quota_summary( ) -> Result { let store = Arc::clone(&state); let resolved = args.unwrap_or_default(); - tauri::async_runtime::spawn_blocking(move || store.quota_summary(resolved)) + tokio::task::spawn_blocking(move || store.quota_summary(resolved)) .await .map_err(|e| format!("memory_quota_summary join 失败:{e}"))? } @@ -268,7 +268,7 @@ pub async fn memory_wipe_all( state: State<'_, Arc>, ) -> Result { let store = Arc::clone(&state); - tauri::async_runtime::spawn_blocking(move || store.wipe_all()) + tokio::task::spawn_blocking(move || store.wipe_all()) .await .map_err(|e| format!("memory_wipe_all join 失败:{e}"))? } diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs b/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs index 03749e741..5ec08ba5c 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs @@ -27,7 +27,7 @@ pub async fn shell_run( let cancel_token = normalized_run_id.as_deref().map(|id| registry.register(id)); let registered_token = cancel_token.clone(); - let join_result = tauri::async_runtime::spawn_blocking(move || { + let join_result = tokio::task::spawn_blocking(move || { run_shell_script( workdir, command, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs b/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs index fbea8edca..3b5bf9f9f 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs @@ -594,7 +594,7 @@ pub(crate) async fn open_chat_file_link_for_conversation( ) })?; - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let _permit = permit; open_chat_file_link_sync( conversation_id, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs b/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs index 3f4bcc996..a9cc8448c 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs @@ -43,7 +43,7 @@ async fn run_blocking( label: &'static str, f: impl FnOnce() -> Result + Send + 'static, ) -> Result { - tauri::async_runtime::spawn_blocking(f) + tokio::task::spawn_blocking(f) .await .map_err(|e| format!("{label} join failed: {e}"))? } @@ -237,7 +237,7 @@ async fn run_blocking_fs( label: &'static str, f: impl FnOnce() -> Result + Send + 'static, ) -> Result { - tauri::async_runtime::spawn_blocking(f) + tokio::task::spawn_blocking(f) .await .map_err(|e| FsCommandError::other(format!("{label} join failed: {e}")))? } diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/git.rs b/crates/agent-gui/src-tauri/src/commands/workspace/git.rs index 90c2f791a..a9402a724 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/git.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/git.rs @@ -3134,21 +3134,21 @@ pub(crate) fn git_gateway_clone_task_action_sync( #[tauri::command(rename_all = "snake_case")] pub async fn git_status(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_status_sync(workdir)) + tokio::task::spawn_blocking(move || git_status_sync(workdir)) .await .map_err(|error| format!("git_status join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_discover_repositories(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_discover_repositories_sync(workdir)) + tokio::task::spawn_blocking(move || git_discover_repositories_sync(workdir)) .await .map_err(|error| format!("git_discover_repositories join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_branches(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_branches_sync(workdir)) + tokio::task::spawn_blocking(move || git_branches_sync(workdir)) .await .map_err(|error| format!("git_branches join 失败:{error}"))? } @@ -3159,7 +3159,7 @@ pub async fn git_switch_branch( branch: String, kind: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_switch_branch_sync(workdir, branch, kind)) + tokio::task::spawn_blocking(move || git_switch_branch_sync(workdir, branch, kind)) .await .map_err(|error| format!("git_switch_branch join 失败:{error}"))? } @@ -3170,7 +3170,7 @@ pub async fn git_create_branch( branch: String, start_point: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { git_create_branch_sync(workdir, branch, start_point) }) .await @@ -3184,7 +3184,7 @@ pub async fn git_init( user_name: Option, user_email: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { git_init_sync( workdir, branch.unwrap_or_else(|| "main".to_string()), @@ -3203,7 +3203,7 @@ pub async fn git_clone_repository( remote_url: String, branch: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { git_clone_repository_sync(parent, name, remote_url, branch) }) .await @@ -3249,7 +3249,7 @@ pub fn git_clone_repository_dismiss( pub async fn git_list_remote_branches( remote_url: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_list_remote_branches_sync(remote_url)) + tokio::task::spawn_blocking(move || git_list_remote_branches_sync(remote_url)) .await .map_err(|error| format!("git_list_remote_branches join 失败:{error}"))? } @@ -3260,7 +3260,7 @@ pub async fn git_diff( mode: Option, path: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_diff_sync(workdir, mode, path)) + tokio::task::spawn_blocking(move || git_diff_sync(workdir, mode, path)) .await .map_err(|error| format!("git_diff join 失败:{error}"))? } @@ -3271,7 +3271,7 @@ pub async fn git_log( limit: Option, skip: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_log_sync(workdir, limit, skip)) + tokio::task::spawn_blocking(move || git_log_sync(workdir, limit, skip)) .await .map_err(|error| format!("git_log join 失败:{error}"))? } @@ -3281,7 +3281,7 @@ pub async fn git_commit_details( workdir: String, commit: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_commit_details_sync(workdir, commit)) + tokio::task::spawn_blocking(move || git_commit_details_sync(workdir, commit)) .await .map_err(|error| format!("git_commit_details join 失败:{error}"))? } @@ -3291,7 +3291,7 @@ pub async fn git_compare_commit_with_remote( workdir: String, commit: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { git_compare_commit_with_remote_sync(workdir, commit) }) .await @@ -3304,35 +3304,35 @@ pub async fn git_commit_diff( commit: String, path: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_commit_diff_sync(workdir, commit, path)) + tokio::task::spawn_blocking(move || git_commit_diff_sync(workdir, commit, path)) .await .map_err(|error| format!("git_commit_diff join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_stage(workdir: String, path: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_stage_sync(workdir, path)) + tokio::task::spawn_blocking(move || git_stage_sync(workdir, path)) .await .map_err(|error| format!("git_stage join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_stage_all(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_stage_all_sync(workdir)) + tokio::task::spawn_blocking(move || git_stage_all_sync(workdir)) .await .map_err(|error| format!("git_stage_all join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_unstage(workdir: String, path: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_unstage_sync(workdir, path)) + tokio::task::spawn_blocking(move || git_unstage_sync(workdir, path)) .await .map_err(|error| format!("git_unstage join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_unstage_all(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_unstage_all_sync(workdir)) + tokio::task::spawn_blocking(move || git_unstage_all_sync(workdir)) .await .map_err(|error| format!("git_unstage_all join 失败:{error}"))? } @@ -3343,14 +3343,14 @@ pub async fn git_discard( path: String, old_path: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_discard_sync(workdir, path, old_path)) + tokio::task::spawn_blocking(move || git_discard_sync(workdir, path, old_path)) .await .map_err(|error| format!("git_discard join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_discard_all(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_discard_all_sync(workdir)) + tokio::task::spawn_blocking(move || git_discard_all_sync(workdir)) .await .map_err(|error| format!("git_discard_all join 失败:{error}"))? } @@ -3360,7 +3360,7 @@ pub async fn git_add_to_gitignore( workdir: String, path: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_add_to_gitignore_sync(workdir, path)) + tokio::task::spawn_blocking(move || git_add_to_gitignore_sync(workdir, path)) .await .map_err(|error| format!("git_add_to_gitignore join 失败:{error}"))? } @@ -3370,28 +3370,28 @@ pub async fn git_open_system_file_location( workdir: String, path: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_open_system_file_location_sync(workdir, path)) + tokio::task::spawn_blocking(move || git_open_system_file_location_sync(workdir, path)) .await .map_err(|error| format!("git_open_system_file_location join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_commit(workdir: String, message: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_commit_sync(workdir, message)) + tokio::task::spawn_blocking(move || git_commit_sync(workdir, message)) .await .map_err(|error| format!("git_commit join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_fetch(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_fetch_sync(workdir)) + tokio::task::spawn_blocking(move || git_fetch_sync(workdir)) .await .map_err(|error| format!("git_fetch join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_pull(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_pull_sync(workdir)) + tokio::task::spawn_blocking(move || git_pull_sync(workdir)) .await .map_err(|error| format!("git_pull join 失败:{error}"))? } @@ -3401,14 +3401,14 @@ pub async fn git_set_remote( workdir: String, remote_url: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_set_remote_sync(workdir, remote_url)) + tokio::task::spawn_blocking(move || git_set_remote_sync(workdir, remote_url)) .await .map_err(|error| format!("git_set_remote join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_push(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_push_sync(workdir)) + tokio::task::spawn_blocking(move || git_push_sync(workdir)) .await .map_err(|error| format!("git_push join 失败:{error}"))? } @@ -3419,7 +3419,7 @@ pub async fn git_delete_branch( branch: String, force: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_delete_branch_sync(workdir, branch, force)) + tokio::task::spawn_blocking(move || git_delete_branch_sync(workdir, branch, force)) .await .map_err(|error| format!("git_delete_branch join 失败:{error}"))? } @@ -3430,7 +3430,7 @@ pub async fn git_rename_branch( branch: String, new_branch: String, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { git_rename_branch_sync(workdir, branch, new_branch) }) .await @@ -3442,14 +3442,14 @@ pub async fn git_stash_push( workdir: String, message: Option, ) -> Result { - tauri::async_runtime::spawn_blocking(move || git_stash_push_sync(workdir, message)) + tokio::task::spawn_blocking(move || git_stash_push_sync(workdir, message)) .await .map_err(|error| format!("git_stash_push join 失败:{error}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn git_stash_pop(workdir: String) -> Result { - tauri::async_runtime::spawn_blocking(move || git_stash_pop_sync(workdir)) + tokio::task::spawn_blocking(move || git_stash_pop_sync(workdir)) .await .map_err(|error| format!("git_stash_pop join 失败:{error}"))? } diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs b/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs index e6779c537..0981882fc 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs @@ -1052,7 +1052,7 @@ pub(crate) fn cleanup_worktree_targets_blocking( pub async fn subagent_worktree_create( input: SubagentWorktreeCreateInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let SubagentWorktreeCreateInput { workdir, label } = input; let requested_workdir = canonicalize_existing_dir(&workdir, "workdir")?; let repo_root_raw = run_git(&requested_workdir, &["rev-parse", "--show-toplevel"])?; @@ -1142,7 +1142,7 @@ pub async fn subagent_worktree_create( pub async fn subagent_worktree_status( input: SubagentWorktreeStatusInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { worktree_status_blocking(input.worktree_root, input.max_diff_chars) }) .await @@ -1153,7 +1153,7 @@ pub async fn subagent_worktree_status( pub async fn subagent_worktree_apply( input: SubagentWorktreeApplyInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { apply_worktree_changes_blocking(input.parent_workdir, input.worktree_root) }) .await @@ -1164,7 +1164,7 @@ pub async fn subagent_worktree_apply( pub async fn subagent_worktree_cleanup( input: SubagentWorktreeCleanupInput, ) -> Result { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { cleanup_worktree_target_blocking( SubagentWorktreeCleanupTarget { run_id: None, diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs index 8f618bfbd..a2c71584a 100644 --- a/crates/agent-gui/src-tauri/src/gateway_sink.rs +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -100,7 +100,7 @@ impl EventSink for GatewayEventSink { match event { MANAGED_PROCESS_CHANGED_EVENT => { - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { if let Err(error) = controller.publish_current_managed_processes().await { eprintln!("publish managed process snapshot failed: {error}"); } @@ -108,7 +108,7 @@ impl EventSink for GatewayEventSink { } // cron / hooks 变更都会改变 settings 快照,Gateway 需要重新同步。 CRON_CHANGED_EVENT | HOOKS_CHANGED_EVENT => { - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { if let Err(error) = controller.refresh_settings_sync_from_db().await { eprintln!( "refresh gateway settings sync after automation change failed: {error}" @@ -124,7 +124,7 @@ impl EventSink for GatewayEventSink { HISTORY_UPSERT_EVENT => { match serde_json::from_value::(payload) { Ok(summary) => { - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { controller .publish_history_sync(build_history_sync_upsert(&summary)) .await; @@ -138,7 +138,7 @@ impl EventSink for GatewayEventSink { eprintln!("history delete 事件 payload 不是字符串"); return; }; - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { controller .publish_history_sync(build_history_sync_delete(conversation_id)) .await; diff --git a/crates/agent-gui/src-tauri/src/lib.rs b/crates/agent-gui/src-tauri/src/lib.rs index c3895d9be..6178590d8 100644 --- a/crates/agent-gui/src-tauri/src/lib.rs +++ b/crates/agent-gui/src-tauri/src/lib.rs @@ -471,7 +471,7 @@ fn dispatch_app_action(app: &tauri::AppHandle, action: AppAction) { }; let store = Arc::clone(store.inner()); let app_handle = app.clone(); - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let (value, error) = match store.toggle_cron_task_enabled(&task_id) { Ok(enabled) => ( Some(if enabled { "enabled" } else { "disabled" }.to_string()), @@ -762,7 +762,7 @@ pub fn run() { if let Err(error) = gateway_controller.start() { eprintln!("failed to start remote gateway controller: {error}"); } - tauri::async_runtime::spawn({ + tokio::spawn({ let gateway_controller = Arc::clone(&gateway_controller); async move { if let Err(error) = gateway_controller.reload_from_db().await { diff --git a/crates/agent-gui/src-tauri/src/runtime/sftp.rs b/crates/agent-gui/src-tauri/src/runtime/sftp.rs index 8fb5576f3..1c47d02b8 100644 --- a/crates/agent-gui/src-tauri/src/runtime/sftp.rs +++ b/crates/agent-gui/src-tauri/src/runtime/sftp.rs @@ -507,7 +507,7 @@ impl SftpSessionRegistry { let failed_template = initial.clone(); let registry = Arc::clone(self); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let result = if direction == "upload" { registry .upload( diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_io.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_io.rs index 57c2fbacb..b5600e168 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_io.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_io.rs @@ -19,7 +19,7 @@ pub(crate) async fn run_ssh_session_io( let (mut read_half, write_half) = channel.split(); let (writer_end_tx, mut writer_end_rx) = tokio::sync::mpsc::channel::(1); let writer_runtime = Arc::clone(&runtime); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let mut writer = write_half.make_writer(); let reason = loop { tokio::select! { @@ -169,7 +169,7 @@ pub(crate) fn spawn_ssh_reconnect_runner( ) { // russh drives each session on the current Tokio runtime, so reconnects must // live on Tauri's long-running runtime rather than a short-lived thread runtime. - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { registry .handle_ssh_unexpected_disconnect(session_id, runtime, connection_id) .await; diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs index c650c2cbe..a1b9582ef 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs @@ -30,7 +30,7 @@ struct SshLocalForwardState { struct SshLocalForwardEntry { record: SshLocalForwardRecord, cancel_tx: watch::Sender, - task: Mutex>>, + task: Mutex>>, } impl Default for SshLocalForwardRegistry { @@ -195,7 +195,7 @@ impl TerminalSessionRegistry { let weak_registry = Arc::downgrade(self); let global_connections = Arc::clone(&self.ssh_local_forwards.global_connections); let forward_connections = Arc::new(Semaphore::new(SSH_LOCAL_FORWARD_MAX_CONNECTIONS)); - let task = tauri::async_runtime::spawn(run_ssh_local_forward_listener( + let task = tokio::spawn(run_ssh_local_forward_listener( weak_registry, forward_id.clone(), listener, diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_session.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_session.rs index 0c6941db4..7ba6e7959 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_session.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_session.rs @@ -249,7 +249,7 @@ impl TerminalSessionRegistry { self.broadcast("created", &entry, None, None, None); let registry = Arc::clone(self); - tauri::async_runtime::spawn(run_ssh_session_io( + tokio::spawn(run_ssh_session_io( registry, id.clone(), Arc::clone(&runtime), @@ -366,7 +366,7 @@ impl TerminalSessionRegistry { self.broadcast("reconnected", &entry, None, None, None); let registry = Arc::clone(self); - tauri::async_runtime::spawn(run_ssh_session_io( + tokio::spawn(run_ssh_session_io( registry, record.id, Arc::clone(runtime), diff --git a/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs b/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs index fe64cd143..bad190a22 100644 --- a/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs +++ b/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs @@ -60,7 +60,7 @@ impl AutomationScheduler { } pub fn start(self: Arc) { - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { self.run_loop().await; }); } @@ -73,7 +73,7 @@ impl AutomationScheduler { async fn run_loop(self: Arc) { { let store = Arc::clone(&self.store); - let recovered = tauri::async_runtime::spawn_blocking(move || { + let recovered = tokio::task::spawn_blocking(move || { store.recover_interrupted_prompt_runs() }) .await; @@ -106,7 +106,7 @@ impl AutomationScheduler { } _ = sweep.tick() => { let store = Arc::clone(&self.store); - let result = tauri::async_runtime::spawn_blocking(move || { + let result = tokio::task::spawn_blocking(move || { store.sweep_expired_prompt_runs() }) .await; @@ -143,7 +143,7 @@ impl AutomationScheduler { self.ensure_scheduler().await?; let store = Arc::clone(&self.store); - let tasks = tauri::async_runtime::spawn_blocking(move || store.runnable_cron_tasks()) + let tasks = tokio::task::spawn_blocking(move || store.runnable_cron_tasks()) .await .map_err(|e| format!("automation reload join 失败:{e}"))??; @@ -238,8 +238,8 @@ impl AutomationScheduler { fn report_task_error(self: &Arc, task_id: &str, error: Option) { let store = Arc::clone(&self.store); let task_id = task_id.to_string(); - tauri::async_runtime::spawn(async move { - let result = tauri::async_runtime::spawn_blocking(move || { + tokio::spawn(async move { + let result = tokio::task::spawn_blocking(move || { store.set_task_error(&task_id, error.as_deref()) }) .await; @@ -255,7 +255,7 @@ impl AutomationScheduler { let fresh = { let store = Arc::clone(&self.store); let task_id = task_id.clone(); - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { store.cron_task_for_scheduled_fire(&task_id) }) .await @@ -307,7 +307,7 @@ impl AutomationScheduler { } let manager = Arc::clone(self); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { manager.execute_fire(task, workdir, trigger).await; }); true @@ -320,7 +320,7 @@ impl AutomationScheduler { let can_run = { let store = Arc::clone(&self.store); let task_id = task_id.clone(); - tauri::async_runtime::spawn_blocking(move || store.task_can_run(&task_id)).await + tokio::task::spawn_blocking(move || store.task_can_run(&task_id)).await }; match can_run { Ok(Ok(true)) => {} @@ -375,7 +375,7 @@ impl AutomationScheduler { let store = Arc::clone(&self.store); let queue_task = task.clone(); let queue_workdir = workdir.clone(); - let result = tauri::async_runtime::spawn_blocking(move || { + let result = tokio::task::spawn_blocking(move || { store.queue_prompt_run(&queue_task, &queue_workdir, trigger.counted()) }) .await; @@ -400,7 +400,7 @@ impl AutomationScheduler { return; } - let run = tauri::async_runtime::spawn_blocking(move || { + let run = tokio::task::spawn_blocking(move || { let mut run = execute_blocking(task, workdir); run.counted = trigger.counted(); run @@ -426,8 +426,8 @@ impl AutomationScheduler { fn disable_task_detached(&self, task_id: &str, error: String) { let store = Arc::clone(&self.store); let task_id = task_id.to_string(); - tauri::async_runtime::spawn(async move { - let result = tauri::async_runtime::spawn_blocking(move || { + tokio::spawn(async move { + let result = tokio::task::spawn_blocking(move || { store.disable_task_with_error(&task_id, &error) }) .await; @@ -441,9 +441,9 @@ impl AutomationScheduler { fn record_run_detached(&self, run: CompletedRun) { let store = Arc::clone(&self.store); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let result = - tauri::async_runtime::spawn_blocking(move || store.record_completed_run(run)).await; + tokio::task::spawn_blocking(move || store.record_completed_run(run)).await; match result { Ok(Err(error)) => eprintln!("Cron run 记录失败:{error}"), Err(error) => eprintln!("Cron run 记录 join 失败:{error}"), diff --git a/crates/agent-gui/src-tauri/src/services/proxy.rs b/crates/agent-gui/src-tauri/src/services/proxy.rs index e95268946..00e92cae9 100644 --- a/crates/agent-gui/src-tauri/src/services/proxy.rs +++ b/crates/agent-gui/src-tauri/src/services/proxy.rs @@ -107,7 +107,7 @@ pub fn start_proxy_server() -> Result, String> { .route("/proxy/{provider}/{*rest}", any(handle_proxy)) .with_state(state.clone()); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let listener = match TokioTcpListener::from_std(listener) { Ok(listener) => listener, Err(err) => { diff --git a/crates/agent-gui/src-tauri/src/services/tunnel/mod.rs b/crates/agent-gui/src-tauri/src/services/tunnel/mod.rs index b3e0c4f73..5286f5336 100644 --- a/crates/agent-gui/src-tauri/src/services/tunnel/mod.rs +++ b/crates/agent-gui/src-tauri/src/services/tunnel/mod.rs @@ -179,7 +179,7 @@ impl GatewayController { pub(crate) fn start_tunnel_store(self: &Arc) { let controller = Arc::clone(self); self.tunnel_store_once.call_once(move || { - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { if let Err(error) = controller.tunnel_store().initialize().await { eprintln!("initialize gateway tunnel store failed: {error}"); } @@ -225,7 +225,7 @@ impl GatewayController { /// follows a desired-state publish within the timeout. fn watch_tunnel_gateway_support(self: &Arc, epoch: u64) { let controller = Arc::clone(self); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { tokio::time::sleep(TUNNEL_GATEWAY_SUPPORT_TIMEOUT).await; match controller .tunnel_store() @@ -243,7 +243,7 @@ impl GatewayController { snapshot: proto::TunnelStateSnapshot, ) { let controller = Arc::clone(self); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { match controller.tunnel_store().record_snapshot(&snapshot) { Ok(changed_specs) => { for spec in changed_specs { @@ -263,7 +263,7 @@ impl GatewayController { mutation: proto::TunnelMutation, ) { let controller = Arc::clone(self); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let action = mutation.action.trim().to_ascii_lowercase(); let requested_tunnel_id = mutation.tunnel_id.trim().to_string(); let result = match action.as_str() { @@ -464,7 +464,7 @@ impl GatewayController { bypass_throttle: bool, ) { let controller = Arc::clone(self); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { controller .run_tunnel_probes(tunnel_ids, bypass_throttle) .await; diff --git a/crates/agent-gui/src-tauri/src/services/tunnel/proxy.rs b/crates/agent-gui/src-tauri/src/services/tunnel/proxy.rs index 7bf985ea3..e8b8ae4b6 100644 --- a/crates/agent-gui/src-tauri/src/services/tunnel/proxy.rs +++ b/crates/agent-gui/src-tauri/src/services/tunnel/proxy.rs @@ -127,7 +127,7 @@ impl TunnelProxy { } proto::TunnelFrameKind::Ping => { let controller = Arc::clone(controller); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let _ = send_tunnel_frame( &controller, proto::TunnelFrame { @@ -163,7 +163,7 @@ impl TunnelProxy { let controller = Arc::clone(controller); let stream_id = stream_id.to_string(); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { run_tunnel_http_request( controller, stream_id, @@ -194,7 +194,7 @@ impl TunnelProxy { let controller = Arc::clone(controller); let stream_id = stream_id.to_string(); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { run_tunnel_websocket(controller, stream_id, upstream_url, headers, gateway_rx).await; }); Ok(()) @@ -215,7 +215,7 @@ impl TunnelProxy { fn spawn_tunnel_frame_error(controller: &Arc, stream_id: String, error: String) { let controller = Arc::clone(controller); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let _ = send_tunnel_frame( &controller, proto::TunnelFrame { @@ -235,7 +235,7 @@ fn spawn_tunnel_ws_dial_error( error: String, ) { let controller = Arc::clone(controller); - tauri::async_runtime::spawn(async move { + tokio::spawn(async move { let _ = send_tunnel_frame( &controller, proto::TunnelFrame { diff --git a/crates/agent-gui/src-tauri/src/services/tunnel/store.rs b/crates/agent-gui/src-tauri/src/services/tunnel/store.rs index 88f5141ee..9b493f6b2 100644 --- a/crates/agent-gui/src-tauri/src/services/tunnel/store.rs +++ b/crates/agent-gui/src-tauri/src/services/tunnel/store.rs @@ -532,7 +532,7 @@ fn now_ms() -> i64 { } async fn load_tunnel_specs() -> Result, String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; load_tunnel_specs_sync(&conn) }) @@ -541,7 +541,7 @@ async fn load_tunnel_specs() -> Result, String> { } pub(super) async fn persist_tunnel_spec(spec: StoredTunnelSpec) -> Result<(), String> { - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; persist_tunnel_spec_sync(&conn, &spec) }) @@ -553,7 +553,7 @@ pub(super) async fn delete_tunnel_specs(tunnel_ids: Vec) -> Result<(), S if tunnel_ids.is_empty() { return Ok(()); } - tauri::async_runtime::spawn_blocking(move || { + tokio::task::spawn_blocking(move || { let conn = open_db()?; for tunnel_id in &tunnel_ids { delete_tunnel_spec_sync(&conn, tunnel_id)?; From 8a5afca0741bf1829b6e404b8449c78388fc6fa3 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 06:05:32 -0700 Subject: [PATCH 08/75] refactor(backend): split 177 #[tauri::command] into impl + thin wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现侧(runtime/services/commands,将迁入 agent-core)现在零 tauri 属性: tauri::State<'_, Arc> → &Arc 体内 x.inner() → x Tauri IPC 的知识全部收敛到新的 src-tauri/src/tauri_commands/,16 个模块 177 个包装。 前端契约逐字未变,机器验过: - 234 个命令名与 HEAD 完全一致,无增无减 - 234 个命令的 rename_all 属性逐命令一致(本就不统一,就该不统一) - 234 个命令的 JSON key(参数名)逐字一致 包装是脚本生成的,不是手写的:177 次同样的机械变换,手写只会引入手写才有的错误。 commands/app/(18 个前端专属)与 commands/integration/gateway.rs(阶段 4 删) 保持原样,不拆。 --- .../src-tauri/src/commands/app/system.rs | 24 +- .../src-tauri/src/commands/automation/cron.rs | 46 +-- .../src-tauri/src/commands/automation/hook.rs | 21 +- .../commands/config/settings/ccs_import.rs | 1 - .../commands/config/settings/cherry_import.rs | 5 +- .../src/commands/config/settings/commands.rs | 19 +- .../commands/history/chat_history/branch.rs | 4 +- .../commands/history/chat_history/commands.rs | 46 +-- .../commands/history/chat_history/delete.rs | 4 +- .../commands/history/chat_history/replace.rs | 4 +- .../src/commands/history/subagent_store.rs | 8 - .../src-tauri/src/commands/integration/mcp.rs | 32 +- .../src/commands/integration/memory.rs | 73 ++-- .../src-tauri/src/commands/runtime/process.rs | 18 +- .../src-tauri/src/commands/runtime/sftp.rs | 31 +- .../src-tauri/src/commands/runtime/shell.rs | 9 +- .../src/commands/runtime/terminal.rs | 77 ++-- .../src/commands/workspace/chat_file_links.rs | 1 - .../src-tauri/src/commands/workspace/fs.rs | 17 - .../src-tauri/src/commands/workspace/git.rs | 74 +--- .../commands/workspace/subagent_worktree.rs | 4 - .../agent-gui/src-tauri/src/gateway_sink.rs | 32 +- crates/agent-gui/src-tauri/src/lib.rs | 361 +++++++++--------- .../src-tauri/src/runtime/terminal/mod.rs | 2 +- .../src/runtime/terminal/registry.rs | 2 +- .../src/services/automation/scheduler.rs | 14 +- .../agent-gui/src-tauri/src/services/proxy.rs | 3 +- .../src/tauri_commands/chat_file_links.rs | 42 ++ .../src/tauri_commands/chat_history.rs | 184 +++++++++ .../src-tauri/src/tauri_commands/cron.rs | 91 +++++ .../src-tauri/src/tauri_commands/fs.rs | 231 +++++++++++ .../src-tauri/src/tauri_commands/git.rs | 280 ++++++++++++++ .../src-tauri/src/tauri_commands/hook.rs | 57 +++ .../src-tauri/src/tauri_commands/mcp.rs | 92 +++++ .../src-tauri/src/tauri_commands/memory.rs | 205 ++++++++++ .../src-tauri/src/tauri_commands/mod.rs | 18 + .../src-tauri/src/tauri_commands/process.rs | 73 ++++ .../src-tauri/src/tauri_commands/proxy.rs | 33 ++ .../src-tauri/src/tauri_commands/settings.rs | 99 +++++ .../src-tauri/src/tauri_commands/sftp.rs | 204 ++++++++++ .../src-tauri/src/tauri_commands/shell.rs | 44 +++ .../src/tauri_commands/subagent_store.rs | 70 ++++ .../src/tauri_commands/subagent_worktree.rs | 47 +++ .../src-tauri/src/tauri_commands/terminal.rs | 293 ++++++++++++++ 44 files changed, 2417 insertions(+), 578 deletions(-) create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/cron.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/fs.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/git.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/hook.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/memory.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/mod.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/process.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/proxy.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/settings.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/shell.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs diff --git a/crates/agent-gui/src-tauri/src/commands/app/system.rs b/crates/agent-gui/src-tauri/src/commands/app/system.rs index 72ddaa6a6..895ad4e2e 100644 --- a/crates/agent-gui/src-tauri/src/commands/app/system.rs +++ b/crates/agent-gui/src-tauri/src/commands/app/system.rs @@ -1387,11 +1387,9 @@ pub async fn system_pick_readable_files( workdir: String, max_files: Option, ) -> Result { - tokio::task::spawn_blocking(move || { - system_pick_readable_files_sync(workdir, max_files) - }) - .await - .map_err(|e| format!("system_pick_readable_files join failed: {e}"))? + tokio::task::spawn_blocking(move || system_pick_readable_files_sync(workdir, max_files)) + .await + .map_err(|e| format!("system_pick_readable_files join failed: {e}"))? } #[tauri::command(rename_all = "snake_case")] @@ -1482,11 +1480,9 @@ pub async fn system_ensure_builtin_skills( #[tauri::command(rename_all = "snake_case")] pub async fn system_manage_skill(payload: Value) -> Result { - tokio::task::spawn_blocking(move || { - crate::services::skills::system_manage_skill_sync(payload) - }) - .await - .map_err(|e| format!("system_manage_skill join failed: {e}"))? + tokio::task::spawn_blocking(move || crate::services::skills::system_manage_skill_sync(payload)) + .await + .map_err(|e| format!("system_manage_skill join failed: {e}"))? } #[tauri::command] @@ -1514,11 +1510,9 @@ pub async fn system_append_debug_jsonl( conversation_id: String, entry: Value, ) -> Result<(), String> { - tokio::task::spawn_blocking(move || { - system_append_debug_jsonl_sync(conversation_id, entry) - }) - .await - .map_err(|e| format!("system_append_debug_jsonl join 失败:{e}"))? + tokio::task::spawn_blocking(move || system_append_debug_jsonl_sync(conversation_id, entry)) + .await + .map_err(|e| format!("system_append_debug_jsonl join 失败:{e}"))? } // 桌面端读系统剪贴板的唯一通道:WKWebView 的 navigator.clipboard.readText() diff --git a/crates/agent-gui/src-tauri/src/commands/automation/cron.rs b/crates/agent-gui/src-tauri/src/commands/automation/cron.rs index 81784ab6f..fbff63343 100644 --- a/crates/agent-gui/src-tauri/src/commands/automation/cron.rs +++ b/crates/agent-gui/src-tauri/src/commands/automation/cron.rs @@ -6,106 +6,96 @@ use crate::services::automation::{ HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, }; -#[tauri::command(rename_all = "snake_case")] pub async fn cron_validate_expression(expression: String) -> Result<(), String> { tokio::task::spawn_blocking(move || validate_cron_expression(&expression)) .await .map_err(|e| format!("cron_validate_expression join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_snapshot( - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.snapshot()) .await .map_err(|e| format!("automation_snapshot join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_cron_apply( input: AutomationApplyInput, - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.cron_apply(input)) .await .map_err(|e| format!("automation_cron_apply join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_hooks_apply( input: AutomationApplyInput, - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.hooks_apply(input)) .await .map_err(|e| format!("automation_hooks_apply join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_list_runs( task_id: String, limit: Option, - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result, String> { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.list_runs(&task_id, limit.unwrap_or(100))) .await .map_err(|e| format!("automation_list_runs join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_clear_runs( task_id: String, - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.clear_runs(&task_id)) .await .map_err(|e| format!("automation_clear_runs join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_run_cron_now( task_id: String, - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.run_cron_task_now(&task_id)) .await .map_err(|e| format!("automation_run_cron_now join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_claim_prompt_runs( - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result, String> { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.claim_prompt_runs()) .await .map_err(|e| format!("automation_claim_prompt_runs join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_release_prompt_run( execution_id: String, - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result<(), String> { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.release_prompt_run(&execution_id)) .await .map_err(|e| format!("automation_release_prompt_run join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn automation_complete_prompt_run( input: CompletePromptRunInput, - store: tauri::State<'_, Arc>, + store: &Arc, ) -> Result { - let store = Arc::clone(store.inner()); + let store = Arc::clone(store); tokio::task::spawn_blocking(move || store.complete_prompt_run(input)) .await .map_err(|e| format!("automation_complete_prompt_run join 失败:{e}"))? diff --git a/crates/agent-gui/src-tauri/src/commands/automation/hook.rs b/crates/agent-gui/src-tauri/src/commands/automation/hook.rs index 2fa0cb45f..e1ca4c43c 100644 --- a/crates/agent-gui/src-tauri/src/commands/automation/hook.rs +++ b/crates/agent-gui/src-tauri/src/commands/automation/hook.rs @@ -266,16 +266,15 @@ fn truncate_response(text: &str) -> String { out } -#[tauri::command(rename_all = "snake_case")] pub async fn hook_run_script( workdir: Option, script: String, timeout_ms: Option, scope_id: Option, context: Option>, - registry: tauri::State<'_, Arc>, + registry: &Arc, ) -> Result { - let registry = Arc::clone(registry.inner()); + let registry = Arc::clone(registry); let envs = normalize_context(context); tokio::task::spawn_blocking(move || { run_hook_script_sync(®istry, workdir, script, timeout_ms, scope_id, envs) @@ -284,24 +283,20 @@ pub async fn hook_run_script( .map_err(|e| format!("hook_run_script join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn hook_run_http_requests( requests: Vec, scope_id: Option, - registry: tauri::State<'_, Arc>, + registry: &Arc, ) -> Result { - let registry = Arc::clone(registry.inner()); - tokio::task::spawn_blocking(move || { - run_hook_http_requests_sync(®istry, requests, scope_id) - }) - .await - .map_err(|e| format!("hook_run_http_requests join 失败:{e}"))? + let registry = Arc::clone(registry); + tokio::task::spawn_blocking(move || run_hook_http_requests_sync(®istry, requests, scope_id)) + .await + .map_err(|e| format!("hook_run_http_requests join 失败:{e}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn hook_cancel_scope( scope_id: String, - registry: tauri::State<'_, Arc>, + registry: &Arc, ) -> Result<(), String> { registry.cancel(scope_id.trim()) } diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs b/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs index e278a6b7d..3b8e80961 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs +++ b/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs @@ -19,7 +19,6 @@ pub struct CcsProvidersResponse { pub providers: Vec, } -#[tauri::command] pub async fn settings_list_ccswitch_providers() -> Result { tokio::task::spawn_blocking(|| { let candidates = ccswitch_db_candidates(); diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs b/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs index 9eb52e3b4..852da878c 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs +++ b/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs @@ -79,7 +79,6 @@ struct CherryImportScan { providers: Vec, } -#[tauri::command] pub async fn settings_list_cherry_studio_providers() -> Result { tokio::task::spawn_blocking(|| { cherry_scan_candidates(&cherry_user_data_candidates(), false) @@ -88,10 +87,8 @@ pub async fn settings_list_cherry_studio_providers() -> Result Result { + data_path: String) -> Result { tokio::task::spawn_blocking(move || { let selected = PathBuf::from(data_path.trim()); if selected.as_os_str().is_empty() { diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs b/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs index 24602aa1b..7779e9062 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs +++ b/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs @@ -1,4 +1,3 @@ -#[tauri::command] pub async fn settings_load_all() -> Result { tokio::task::spawn_blocking(|| { let conn = open_db()?; @@ -18,7 +17,6 @@ pub async fn settings_load_all() -> Result { .map_err(|e| format!("settings_load_all join 失败:{e}"))? } -#[tauri::command] pub async fn settings_save_providers(payload: Value) -> Result<(), String> { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; @@ -28,11 +26,9 @@ pub async fn settings_save_providers(payload: Value) -> Result<(), String> { .map_err(|e| format!("settings_save_providers join 失败:{e}"))? } -#[tauri::command] pub async fn settings_save_system( payload: Value, - automation_scheduler: tauri::State<'_, Arc>, -) -> Result<(), String> { + automation_scheduler: &Arc) -> Result<(), String> { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_system(&mut conn, payload)?; @@ -47,7 +43,6 @@ pub async fn settings_save_system( Ok(()) } -#[tauri::command] pub async fn settings_save_mcp(payload: Value) -> Result<(), String> { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; @@ -57,11 +52,9 @@ pub async fn settings_save_mcp(payload: Value) -> Result<(), String> { .map_err(|e| format!("settings_save_mcp join 失败:{e}"))? } -#[tauri::command] pub async fn settings_save_remote( payload: Value, - gateway_controller: tauri::State<'_, Arc>, -) -> Result<(), String> { + gateway_controller: &Arc) -> Result<(), String> { let normalized = tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_remote(&mut conn, payload) @@ -71,7 +64,6 @@ pub async fn settings_save_remote( gateway_controller.apply_config(normalized) } -#[tauri::command] pub async fn settings_save_memory(payload: Value) -> Result<(), String> { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; @@ -81,7 +73,6 @@ pub async fn settings_save_memory(payload: Value) -> Result<(), String> { .map_err(|e| format!("settings_save_memory join 失败:{e}"))? } -#[tauri::command] pub async fn settings_save_agents(payload: Value) -> Result<(), String> { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; @@ -91,7 +82,6 @@ pub async fn settings_save_agents(payload: Value) -> Result<(), String> { .map_err(|e| format!("settings_save_agents join 失败:{e}"))? } -#[tauri::command] pub async fn settings_save_ssh(payload: Value) -> Result<(), String> { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; @@ -101,7 +91,6 @@ pub async fn settings_save_ssh(payload: Value) -> Result<(), String> { .map_err(|e| format!("settings_save_ssh join 失败:{e}"))? } -#[tauri::command] pub async fn settings_apply_ssh_patch(payload: Value) -> Result { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; @@ -111,11 +100,9 @@ pub async fn settings_apply_ssh_patch(payload: Value) -> Result Result { + port: u16) -> Result { tokio::task::spawn_blocking(move || { let deleted = reset_runtime_ssh_known_host(&host, port)?; Ok(SshKnownHostResetResponse { deleted }) diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs index 3ab881d53..ec618c83d 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs @@ -222,12 +222,10 @@ pub(crate) async fn chat_history_branch_inner( .map_err(|e| format!("chat_history_branch join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_branch( id: String, base_message_ref: ChatHistoryMessageRef, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let summary = chat_history_branch_inner(id, base_message_ref).await?; events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs index 70ee80553..cbd2c8a67 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs @@ -1,10 +1,8 @@ -#[tauri::command] pub async fn chat_history_list( page: i64, page_size: i64, cwd: Option, - cwd_empty: Option, -) -> Result { + cwd_empty: Option) -> Result { tokio::task::spawn_blocking(move || { let conn = open_db()?; list_chat_history_sync_with_filter( @@ -21,7 +19,6 @@ pub async fn chat_history_list( .map_err(|e| format!("chat_history_list join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_workdirs() -> Result { tokio::task::spawn_blocking(move || { let conn = open_db()?; @@ -31,11 +28,9 @@ pub async fn chat_history_workdirs() -> Result Result { + page_size: i64) -> Result { tokio::task::spawn_blocking(move || { list_shared_chat_history_page_sync(page, page_size) }) @@ -43,10 +38,8 @@ pub async fn chat_history_shared_list( .map_err(|e| format!("chat_history_shared_list join failed: {e}"))? } -#[tauri::command] pub async fn chat_history_search( - args: ChatHistorySearchArgs, -) -> Result { + args: ChatHistorySearchArgs) -> Result { tokio::task::spawn_blocking(move || search_chat_history_sync(args)) .await .map_err(|e| format!("chat_history_search join 失败:{e}"))? @@ -210,14 +203,12 @@ pub(crate) fn chat_history_get_window_sync( Ok(result) } -#[tauri::command] pub async fn chat_history_get_window( id: String, max_messages: i64, before_offset: Option, expected_revision: Option, - include_active_segment: bool, -) -> Result { + include_active_segment: bool) -> Result { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; chat_history_get_window_sync( @@ -277,11 +268,9 @@ pub(crate) async fn chat_history_upsert_inner( .map_err(|e| format!("chat_history_upsert join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_upsert( input: ChatHistoryUpsertInput, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let summary = chat_history_upsert_inner(input).await?; events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) @@ -310,11 +299,9 @@ pub(crate) async fn chat_history_upsert_active_segment_inner( .map_err(|e| format!("chat_history_upsert_active_segment join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_upsert_active_segment( input: ChatHistorySegmentMutationInput, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let summary = chat_history_upsert_active_segment_inner(input).await?; events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) @@ -344,11 +331,9 @@ pub(crate) async fn chat_history_append_segment_inner( .map_err(|e| format!("chat_history_append_segment join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_append_segment( input: ChatHistorySegmentMutationInput, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let summary = chat_history_append_segment_inner(input).await?; events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) @@ -366,12 +351,10 @@ pub(crate) async fn chat_history_rename_inner( .map_err(|e| format!("chat_history_rename join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_rename( id: String, title: String, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let summary = chat_history_rename_inner(id, title).await?; events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) @@ -389,12 +372,10 @@ pub(crate) async fn chat_history_set_pinned_inner( .map_err(|e| format!("chat_history_set_pinned join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_set_pinned( id: String, is_pinned: bool, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let summary = chat_history_set_pinned_inner(id, is_pinned).await?; events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) @@ -412,12 +393,10 @@ pub(crate) async fn chat_history_set_model_inner( .map_err(|e| format!("chat_history_set_model join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_set_model( id: String, selected_model_json: String, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let summary = chat_history_set_model_inner(id, selected_model_json).await?; events.emit(HISTORY_UPSERT_EVENT, &summary); Ok(summary) @@ -434,7 +413,6 @@ pub(crate) async fn chat_history_share_get_inner( .map_err(|e| format!("chat_history_share_get join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_share_get(id: String) -> Result { chat_history_share_get_inner(id).await } @@ -452,13 +430,11 @@ pub(crate) async fn chat_history_share_set_inner( .map_err(|e| format!("chat_history_share_set join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_share_set( id: String, enabled: bool, redact_tool_content: Option, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let status = chat_history_share_set_inner(id, enabled, redact_tool_content).await?; match chat_history_get_summary_inner(status.conversation_id.clone()).await { Ok(summary) => { diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs index 0f7bcc7e9..aebbab0fe 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs @@ -59,11 +59,9 @@ pub(crate) async fn chat_history_delete_inner(id: String) -> Result<(), String> .map_err(|e| format!("chat_history_delete join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_delete( id: String, - events: tauri::State<'_, Arc>, -) -> Result<(), String> { + events: &Arc) -> Result<(), String> { let conversation_id = id.trim().to_string(); chat_history_delete_inner(id).await?; events.emit(HISTORY_DELETE_EVENT, conversation_id); diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs b/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs index ee6687000..9632d5c5e 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs @@ -153,15 +153,13 @@ pub(crate) async fn chat_history_replace_from_message_inner( .map_err(|e| format!("chat_history_replace_from_message join 失败:{e}"))? } -#[tauri::command] pub async fn chat_history_replace_from_message( id: String, base_message_ref: ChatHistoryMessageRef, replacement_message: Value, max_messages: i64, expected_revision: String, - events: tauri::State<'_, Arc>, -) -> Result { + events: &Arc) -> Result { let result = chat_history_replace_from_message_inner( id, base_message_ref, diff --git a/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs b/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs index 5ca887367..c44d05fde 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs +++ b/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs @@ -1251,7 +1251,6 @@ pub(crate) fn list_subagent_messages_sync( // Tauri commands // --------------------------------------------------------------------------- -#[tauri::command] pub async fn subagent_identity_upsert( input: SubagentIdentityUpsertInput, ) -> Result { @@ -1263,7 +1262,6 @@ pub async fn subagent_identity_upsert( .map_err(|e| format!("subagent_identity_upsert join failed: {e}"))? } -#[tauri::command] pub async fn subagent_identity_list( input: SubagentIdentityListInput, ) -> Result, String> { @@ -1275,7 +1273,6 @@ pub async fn subagent_identity_list( .map_err(|e| format!("subagent_identity_list join failed: {e}"))? } -#[tauri::command] pub async fn subagent_run_save(input: SubagentRunSaveInput) -> Result<(), String> { tokio::task::spawn_blocking(move || { let mut conn = open_db()?; @@ -1285,7 +1282,6 @@ pub async fn subagent_run_save(input: SubagentRunSaveInput) -> Result<(), String .map_err(|e| format!("subagent_run_save join failed: {e}"))? } -#[tauri::command] pub async fn subagent_run_list( input: SubagentRunListInput, ) -> Result, String> { @@ -1297,7 +1293,6 @@ pub async fn subagent_run_list( .map_err(|e| format!("subagent_run_list join failed: {e}"))? } -#[tauri::command] pub async fn subagent_run_load( input: SubagentRunLoadInput, ) -> Result, String> { @@ -1309,7 +1304,6 @@ pub async fn subagent_run_load( .map_err(|e| format!("subagent_run_load join failed: {e}"))? } -#[tauri::command] pub async fn subagent_run_prune( input: SubagentRunPruneInput, ) -> Result { @@ -1318,7 +1312,6 @@ pub async fn subagent_run_prune( .map_err(|e| format!("subagent_run_prune join failed: {e}"))? } -#[tauri::command] pub async fn subagent_message_append( input: SubagentMessageAppendInput, ) -> Result { @@ -1330,7 +1323,6 @@ pub async fn subagent_message_append( .map_err(|e| format!("subagent_message_append join failed: {e}"))? } -#[tauri::command] pub async fn subagent_message_list( input: SubagentMessageListInput, ) -> Result, String> { diff --git a/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs b/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs index 884d8fec4..e25130d5a 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs +++ b/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs @@ -1641,13 +1641,12 @@ impl McpRuntimeManager { } } -#[tauri::command(rename_all = "snake_case")] pub async fn mcp_list_tools( - state: tauri::State<'_, Arc>, + state: &Arc, servers: Vec, ) -> Result, String> { // IMPORTANT: tool listing can block (process spawn / network / pipes). Offload. - let manager = state.inner().clone(); + let manager = state.clone(); run_blocking("mcp_list_tools", move || { let mut out: Vec = Vec::new(); @@ -1692,17 +1691,16 @@ pub async fn mcp_list_tools( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn mcp_call_tool( - state: tauri::State<'_, Arc>, - run_registry: tauri::State<'_, Arc>, + state: &Arc, + run_registry: &Arc, server_id: String, tool_name: String, arguments: Value, run_id: Option, ) -> Result { // IMPORTANT: tool call can block (network / pipes / SSE). Offload. - let manager = state.inner().clone(); + let manager = state.clone(); let normalized_run_id = run_id .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()); @@ -1750,24 +1748,22 @@ pub async fn mcp_call_tool( result } -#[tauri::command(rename_all = "snake_case")] pub async fn mcp_runtime_status( - state: tauri::State<'_, Arc>, + state: &Arc, server_id: String, ) -> Result { - let manager = state.inner().clone(); + let manager = state.clone(); run_blocking("mcp_runtime_status", move || { manager.runtime_status(&server_id) }) .await } -#[tauri::command(rename_all = "snake_case")] pub async fn mcp_stop_server( - state: tauri::State<'_, Arc>, + state: &Arc, server_id: String, ) -> Result { - let manager = state.inner().clone(); + let manager = state.clone(); run_blocking("mcp_stop_server", move || { let id = server_id.trim().to_string(); let stopped = manager.stop_client(&id)?; @@ -1779,14 +1775,13 @@ pub async fn mcp_stop_server( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn mcp_test_server( - state: tauri::State<'_, Arc>, + state: &Arc, server: McpServerConfig, include_schema: Option, persist: Option, ) -> Result { - let manager = state.inner().clone(); + let manager = state.clone(); run_blocking("mcp_test_server", move || { manager.test_client( server, @@ -1798,14 +1793,13 @@ pub async fn mcp_test_server( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn mcp_restart_server( - state: tauri::State<'_, Arc>, + state: &Arc, server: McpServerConfig, include_schema: Option, persist: Option, ) -> Result { - let manager = state.inner().clone(); + let manager = state.clone(); run_blocking("mcp_restart_server", move || { manager.test_client( server, diff --git a/crates/agent-gui/src-tauri/src/commands/integration/memory.rs b/crates/agent-gui/src-tauri/src/commands/integration/memory.rs index 9c87b753f..0a5721876 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/memory.rs +++ b/crates/agent-gui/src-tauri/src/commands/integration/memory.rs @@ -17,9 +17,8 @@ use crate::{ }, }; -#[tauri::command] pub async fn memory_list( - state: State<'_, Arc>, + state: &Arc, args: MemoryListArgs, ) -> Result { let store = Arc::clone(&state); @@ -28,9 +27,8 @@ pub async fn memory_list( .map_err(|e| format!("memory_list join 失败:{e}"))? } -#[tauri::command] pub async fn memory_read( - state: State<'_, Arc>, + state: &Arc, args: MemoryReadArgs, ) -> Result { let store = Arc::clone(&state); @@ -39,9 +37,8 @@ pub async fn memory_read( .map_err(|e| format!("memory_read join 失败:{e}"))? } -#[tauri::command] pub async fn memory_search( - state: State<'_, Arc>, + state: &Arc, args: MemorySearchArgs, ) -> Result { let store = Arc::clone(&state); @@ -56,9 +53,8 @@ pub async fn memory_search( .map_err(|e| format!("memory_search join 失败:{e}"))? } -#[tauri::command] pub async fn memory_write( - state: State<'_, Arc>, + state: &Arc, args: MemoryWriteArgs, ) -> Result { let store = Arc::clone(&state); @@ -67,9 +63,8 @@ pub async fn memory_write( .map_err(|e| format!("memory_write join 失败:{e}"))? } -#[tauri::command] pub async fn memory_update( - state: State<'_, Arc>, + state: &Arc, args: MemoryUpdateArgs, ) -> Result { let store = Arc::clone(&state); @@ -78,9 +73,8 @@ pub async fn memory_update( .map_err(|e| format!("memory_update join 失败:{e}"))? } -#[tauri::command] pub async fn memory_delete( - state: State<'_, Arc>, + state: &Arc, args: MemoryDeleteArgs, ) -> Result { let store = Arc::clone(&state); @@ -89,9 +83,8 @@ pub async fn memory_delete( .map_err(|e| format!("memory_delete join 失败:{e}"))? } -#[tauri::command] pub async fn memory_delete_project( - state: State<'_, Arc>, + state: &Arc, args: MemoryDeleteProjectArgs, ) -> Result { let store = Arc::clone(&state); @@ -100,9 +93,8 @@ pub async fn memory_delete_project( .map_err(|e| format!("memory_delete_project join 失败:{e}"))? } -#[tauri::command] pub async fn memory_accept( - state: State<'_, Arc>, + state: &Arc, args: MemoryAcceptArgs, ) -> Result { let store = Arc::clone(&state); @@ -111,9 +103,8 @@ pub async fn memory_accept( .map_err(|e| format!("memory_accept join 失败:{e}"))? } -#[tauri::command] pub async fn memory_apply_batch( - state: State<'_, Arc>, + state: &Arc, args: MemoryBatchArgs, ) -> Result { let store = Arc::clone(&state); @@ -122,9 +113,8 @@ pub async fn memory_apply_batch( .map_err(|e| format!("memory_apply_batch join 失败:{e}"))? } -#[tauri::command] pub async fn memory_organize_run_create( - state: State<'_, Arc>, + state: &Arc, args: MemoryOrganizeRunCreateArgs, ) -> Result { let store = Arc::clone(&state); @@ -133,9 +123,8 @@ pub async fn memory_organize_run_create( .map_err(|e| format!("memory_organize_run_create join 失败:{e}"))? } -#[tauri::command] pub async fn memory_organize_run_update( - state: State<'_, Arc>, + state: &Arc, args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { let store = Arc::clone(&state); @@ -144,9 +133,8 @@ pub async fn memory_organize_run_update( .map_err(|e| format!("memory_organize_run_update join 失败:{e}"))? } -#[tauri::command] pub async fn memory_organize_run_list( - state: State<'_, Arc>, + state: &Arc, args: Option, ) -> Result { let store = Arc::clone(&state); @@ -156,9 +144,8 @@ pub async fn memory_organize_run_list( .map_err(|e| format!("memory_organize_run_list join 失败:{e}"))? } -#[tauri::command] pub async fn memory_organize_run_read( - state: State<'_, Arc>, + state: &Arc, args: MemoryOrganizeRunReadArgs, ) -> Result, String> { let store = Arc::clone(&state); @@ -167,9 +154,8 @@ pub async fn memory_organize_run_read( .map_err(|e| format!("memory_organize_run_read join 失败:{e}"))? } -#[tauri::command] pub async fn memory_organize_run_clear_history( - state: State<'_, Arc>, + state: &Arc, ) -> Result { let store = Arc::clone(&state); tokio::task::spawn_blocking(move || store.organize_run_clear_history()) @@ -177,9 +163,8 @@ pub async fn memory_organize_run_clear_history( .map_err(|e| format!("memory_organize_run_clear_history join 失败:{e}"))? } -#[tauri::command] pub async fn memory_organize_due_claim( - state: State<'_, Arc>, + state: &Arc, args: MemoryOrganizeDueClaimArgs, ) -> Result { let store = Arc::clone(&state); @@ -188,9 +173,8 @@ pub async fn memory_organize_due_claim( .map_err(|e| format!("memory_organize_due_claim join 失败:{e}"))? } -#[tauri::command] pub async fn memory_organize_due_complete( - state: State<'_, Arc>, + state: &Arc, args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { let store = Arc::clone(&state); @@ -199,9 +183,8 @@ pub async fn memory_organize_due_complete( .map_err(|e| format!("memory_organize_due_complete join 失败:{e}"))? } -#[tauri::command] pub async fn memory_index_overview( - state: State<'_, Arc>, + state: &Arc, workdir: Option, ) -> Result { let store = Arc::clone(&state); @@ -210,19 +193,15 @@ pub async fn memory_index_overview( .map_err(|e| format!("memory_index_overview join 失败:{e}"))? } -#[tauri::command] -pub async fn memory_paths_info( - state: State<'_, Arc>, -) -> Result { +pub async fn memory_paths_info(state: &Arc) -> Result { let store = Arc::clone(&state); tokio::task::spawn_blocking(move || store.paths_info()) .await .map_err(|e| format!("memory_paths_info join 失败:{e}"))? } -#[tauri::command] pub async fn memory_recent_rejections( - state: State<'_, Arc>, + state: &Arc, args: Option, ) -> Result { let store = Arc::clone(&state); @@ -232,17 +211,15 @@ pub async fn memory_recent_rejections( .map_err(|e| format!("memory_recent_rejections join 失败:{e}"))? } -#[tauri::command] pub async fn memory_today_local_date( - state: State<'_, Arc>, + state: &Arc, rollover_hour: Option, ) -> Result { Ok(state.today_local_date(rollover_hour)) } -#[tauri::command] pub async fn memory_today_daily( - state: State<'_, Arc>, + state: &Arc, rollover_hour: Option, ) -> Result, String> { let store = Arc::clone(&state); @@ -251,9 +228,8 @@ pub async fn memory_today_daily( .map_err(|e| format!("memory_today_daily join 失败:{e}"))? } -#[tauri::command] pub async fn memory_quota_summary( - state: State<'_, Arc>, + state: &Arc, args: Option, ) -> Result { let store = Arc::clone(&state); @@ -263,10 +239,7 @@ pub async fn memory_quota_summary( .map_err(|e| format!("memory_quota_summary join 失败:{e}"))? } -#[tauri::command] -pub async fn memory_wipe_all( - state: State<'_, Arc>, -) -> Result { +pub async fn memory_wipe_all(state: &Arc) -> Result { let store = Arc::clone(&state); tokio::task::spawn_blocking(move || store.wipe_all()) .await diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/process.rs b/crates/agent-gui/src-tauri/src/commands/runtime/process.rs index 9a9b0e524..73ea91d4d 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/process.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/process.rs @@ -7,9 +7,8 @@ use crate::runtime::managed_process::{ ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, }; -#[tauri::command(rename_all = "snake_case")] pub fn managed_process_start( - registry: State<'_, Arc>, + registry: &Arc, workdir: String, command: String, cwd: Option, @@ -19,41 +18,36 @@ pub fn managed_process_start( registry.start(workdir, command, cwd, label, isolated.unwrap_or(false)) } -#[tauri::command(rename_all = "snake_case")] pub fn managed_process_status( - registry: State<'_, Arc>, + registry: &Arc, process_id: Option, ) -> Result { registry.status(process_id) } -#[tauri::command(rename_all = "snake_case")] pub fn managed_process_stop( - registry: State<'_, Arc>, + registry: &Arc, process_id: String, ) -> Result { registry.stop(process_id) } -#[tauri::command(rename_all = "snake_case")] pub fn managed_process_read_log( - registry: State<'_, Arc>, + registry: &Arc, process_id: String, max_bytes: Option, ) -> Result { registry.read_log(process_id, max_bytes) } -#[tauri::command(rename_all = "snake_case")] pub fn managed_process_snapshot( - registry: State<'_, Arc>, + registry: &Arc, ) -> Result { registry.snapshot() } -#[tauri::command(rename_all = "snake_case")] pub fn managed_process_clear( - registry: State<'_, Arc>, + registry: &Arc, process_id: Option, ) -> Result { registry.clear(process_id) diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs b/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs index 2c98186de..a629dc815 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs @@ -7,9 +7,8 @@ use crate::runtime::sftp::{ SftpStatResponse, SftpTransferResponse, }; -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_list( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, workdir: String, @@ -21,9 +20,8 @@ pub async fn sftp_list( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_stat( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, workdir: String, @@ -35,9 +33,8 @@ pub async fn sftp_stat( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_read_text( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, path: String, @@ -49,9 +46,8 @@ pub async fn sftp_read_text( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_write_text( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, path: String, @@ -71,9 +67,8 @@ pub async fn sftp_write_text( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_mkdir( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, workdir: String, @@ -85,9 +80,8 @@ pub async fn sftp_mkdir( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_rename( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, workdir: String, @@ -107,9 +101,8 @@ pub async fn sftp_rename( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_delete( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, workdir: String, @@ -129,9 +122,8 @@ pub async fn sftp_delete( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn sftp_transfer( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, workdir: String, @@ -142,7 +134,6 @@ pub async fn sftp_transfer( overwrite: Option, ) -> Result { registry - .inner() .clone() .transfer( session_id, @@ -157,18 +148,16 @@ pub async fn sftp_transfer( .await } -#[tauri::command(rename_all = "snake_case")] pub fn sftp_cancel_transfer( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, transfer_id: String, ) -> Result<(), String> { registry.cancel_transfer(session_id, transfer_id) } -#[tauri::command(rename_all = "snake_case")] pub fn sftp_transfer_status( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, transfer_id: String, ) -> Result { diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs b/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs index 5ec08ba5c..bc55f2db1 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs @@ -10,9 +10,8 @@ pub struct ShellCancelResponse { cancelled: bool, } -#[tauri::command(rename_all = "snake_case")] pub async fn shell_run( - registry: State<'_, Arc>, + registry: &Arc, workdir: String, command: String, cwd: Option, @@ -49,11 +48,7 @@ pub async fn shell_run( /// Cancels any run registered in the shared `ShellRunRegistry` — shell /// commands, MCP tool calls, and SSH exec all park their cancel tokens there. -#[tauri::command(rename_all = "snake_case")] -pub fn runtime_cancel( - registry: State<'_, Arc>, - run_id: String, -) -> ShellCancelResponse { +pub fn runtime_cancel(registry: &Arc, run_id: String) -> ShellCancelResponse { ShellCancelResponse { cancelled: registry.cancel(run_id.trim()), } diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs b/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs index 61bcebb50..bbd67c25e 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs @@ -13,22 +13,19 @@ use crate::runtime::terminal::{ TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, }; -#[tauri::command(rename_all = "snake_case")] pub fn terminal_shell_options() -> TerminalShellOptionsResponse { runtime_terminal_shell_options() } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_list( - registry: State<'_, Arc>, + registry: &Arc, project_path_key: Option, ) -> TerminalListResponse { registry.list(project_path_key) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_create( - registry: State<'_, Arc>, + registry: &Arc, cwd: String, project_path_key: Option, shell: Option, @@ -39,9 +36,8 @@ pub fn terminal_create( registry.create(cwd, project_path_key, shell, title, cols, rows) } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_create_ssh( - registry: State<'_, Arc>, + registry: &Arc, cwd: String, project_path_key: Option, ssh_host_id: String, @@ -51,7 +47,6 @@ pub async fn terminal_create_ssh( sftp_enabled: Option, ) -> Result { registry - .inner() .clone() .create_ssh( cwd, @@ -65,48 +60,42 @@ pub async fn terminal_create_ssh( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_answer_ssh_prompt( - registry: State<'_, Arc>, + registry: &Arc, prompt_id: String, prompt_answer: Option, trust_host_key: Option, ) -> Result { registry - .inner() .clone() .answer_ssh_prompt(prompt_id, prompt_answer, trust_host_key.unwrap_or(false)) .await } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_cancel_ssh_prompt( - registry: State<'_, Arc>, + registry: &Arc, prompt_id: String, ) -> Result<(), String> { registry.cancel_ssh_prompt(prompt_id) } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_ssh_reconnect( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, ) -> Result { - registry.inner().clone().ssh_reconnect(session_id).await + registry.clone().ssh_reconnect(session_id).await } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_ssh_latency( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, ) -> Result { registry.ssh_latency(session_id).await } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_ssh_exec( - registry: State<'_, Arc>, - run_registry: State<'_, Arc>, + registry: &Arc, + run_registry: &Arc, session_id: String, command: String, cwd: Option, @@ -122,7 +111,6 @@ pub async fn terminal_ssh_exec( .map(|id| run_registry.register(id)); let registered_token = cancel_token.clone(); let result = registry - .inner() .clone() .ssh_exec( session_id, @@ -139,9 +127,8 @@ pub async fn terminal_ssh_exec( result } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_ssh_local_forward_start( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, project_path_key: Option, remote_host: String, @@ -149,7 +136,6 @@ pub async fn terminal_ssh_local_forward_start( local_port: Option, ) -> Result { registry - .inner() .clone() .ssh_local_forward_start( session_id, @@ -161,18 +147,16 @@ pub async fn terminal_ssh_local_forward_start( .await } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_ssh_local_forward_list( - registry: State<'_, Arc>, + registry: &Arc, session_id: Option, project_path_key: Option, ) -> Result { registry.ssh_local_forward_list(session_id, project_path_key) } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_ssh_local_forward_stop( - registry: State<'_, Arc>, + registry: &Arc, forward_id: String, session_id: Option, ) -> Result { @@ -181,7 +165,6 @@ pub async fn terminal_ssh_local_forward_stop( .await } -#[tauri::command(rename_all = "snake_case")] pub async fn terminal_ssh_local_forward_check_port(local_port: u32) -> Result { let port = normalize_ssh_local_forward_local_port(Some(local_port))?; if port == 0 { @@ -191,52 +174,46 @@ pub async fn terminal_ssh_local_forward_check_port(local_port: u32) -> Result>, + registry: &Arc, project_path_key: String, ) -> Result { registry.ssh_terminal_tabs_list(project_path_key) } -#[tauri::command(rename_all = "snake_case")] pub fn ssh_terminal_tab_open( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, kind: String, ) -> Result { registry.ssh_terminal_tab_open(session_id, kind) } -#[tauri::command(rename_all = "snake_case")] pub fn ssh_terminal_tab_close( - registry: State<'_, Arc>, + registry: &Arc, tab_id: String, ) -> Result { registry.ssh_terminal_tab_close(tab_id) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_stream_attach( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, max_bytes: Option, ) -> Result { registry.stream_attach(session_id, max_bytes) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_stream_input( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, bytes: Vec, ) -> Result<(), String> { registry.input_bytes(session_id, bytes) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_stream_resize( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, cols: u16, rows: u16, @@ -244,19 +221,17 @@ pub fn terminal_stream_resize( registry.stream_resize(session_id, cols, rows) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_rename( - registry: State<'_, Arc>, + registry: &Arc, session_id: String, title: String, ) -> Result { registry.rename(session_id, title) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_close( - registry: State<'_, Arc>, - sftp_registry: State<'_, Arc>, + registry: &Arc, + sftp_registry: &Arc, session_id: String, ) -> Result { let response = registry.close(session_id)?; @@ -264,10 +239,9 @@ pub fn terminal_close( Ok(response) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_close_project( - registry: State<'_, Arc>, - sftp_registry: State<'_, Arc>, + registry: &Arc, + sftp_registry: &Arc, project_path_key: String, ) -> Result { let response = registry.close_project(project_path_key)?; @@ -277,9 +251,8 @@ pub fn terminal_close_project( Ok(response) } -#[tauri::command(rename_all = "snake_case")] pub fn terminal_read_tail( - registry: State<'_, Arc>, + registry: &Arc, project_path_key: String, session_id: Option, max_bytes: Option, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs b/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs index 3b5bf9f9f..098454451 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs @@ -624,7 +624,6 @@ pub(crate) async fn open_chat_file_link_for_conversation( })? } -#[tauri::command(rename_all = "snake_case")] pub async fn open_chat_file_link( conversation_id: String, workdir: String, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs b/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs index a9cc8448c..bae1e944d 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs @@ -2487,7 +2487,6 @@ fn fs_read_image_source_impl( } } -#[tauri::command] pub async fn fs_read_image_source( workdir: String, source: String, @@ -2515,7 +2514,6 @@ fn fs_read_workspace_image_impl(wd: &Path, path: &str) -> Result Result Result Result { }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_delete(workdir: String, path: String) -> Result { run_blocking_fs("fs_delete", move || fs_delete_sync(workdir, path)).await } @@ -3358,7 +3350,6 @@ fn fs_open_workspace_path_impl( }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_open_workspace_path( workdir: String, path: String, @@ -3410,7 +3401,6 @@ fn fs_create_dir_impl(wd: &Path, path: &str) -> Result Result Result { Ok(FsRootsResponse { roots }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_roots() -> Result { run_blocking("fs_roots", fs_roots_sync).await } @@ -3675,7 +3663,6 @@ pub(crate) fn fs_list_dirs_sync( }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_list_dirs( path: String, max_results: Option, @@ -3865,7 +3852,6 @@ fn fs_list_impl( }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_list( workdir: String, path: Option, @@ -4005,7 +3991,6 @@ fn fs_glob_impl( }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_glob( workdir: String, path: Option, @@ -4325,7 +4310,6 @@ fn fs_grep_impl( }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_grep( workdir: String, path: Option, @@ -4545,7 +4529,6 @@ pub fn fs_mention_list_sync( Ok(MentionListResponse { entries, truncated }) } -#[tauri::command(rename_all = "snake_case")] pub async fn fs_mention_list( workdir: String, max_results: Option, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/git.rs b/crates/agent-gui/src-tauri/src/commands/workspace/git.rs index a9402a724..f01b77ee0 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/git.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/git.rs @@ -3132,28 +3132,24 @@ pub(crate) fn git_gateway_clone_task_action_sync( } } -#[tauri::command(rename_all = "snake_case")] pub async fn git_status(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_status_sync(workdir)) .await .map_err(|error| format!("git_status join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_discover_repositories(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_discover_repositories_sync(workdir)) .await .map_err(|error| format!("git_discover_repositories join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_branches(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_branches_sync(workdir)) .await .map_err(|error| format!("git_branches join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_switch_branch( workdir: String, branch: String, @@ -3164,20 +3160,16 @@ pub async fn git_switch_branch( .map_err(|error| format!("git_switch_branch join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_create_branch( workdir: String, branch: String, start_point: Option, ) -> Result { - tokio::task::spawn_blocking(move || { - git_create_branch_sync(workdir, branch, start_point) - }) - .await - .map_err(|error| format!("git_create_branch join 失败:{error}"))? + tokio::task::spawn_blocking(move || git_create_branch_sync(workdir, branch, start_point)) + .await + .map_err(|error| format!("git_create_branch join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_init( workdir: String, branch: Option, @@ -3196,23 +3188,19 @@ pub async fn git_init( .map_err(|error| format!("git_init join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_clone_repository( parent: String, name: String, remote_url: String, branch: Option, ) -> Result { - tokio::task::spawn_blocking(move || { - git_clone_repository_sync(parent, name, remote_url, branch) - }) - .await - .map_err(|error| format!("git_clone_repository join 失败:{error}"))? + tokio::task::spawn_blocking(move || git_clone_repository_sync(parent, name, remote_url, branch)) + .await + .map_err(|error| format!("git_clone_repository join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub fn git_clone_repository_start( - registry: tauri::State<'_, Arc>, + registry: &Arc, parent: String, name: String, remote_url: String, @@ -3221,31 +3209,27 @@ pub fn git_clone_repository_start( registry.start(parent, name, remote_url, branch) } -#[tauri::command] pub fn git_clone_repository_tasks( - registry: tauri::State<'_, Arc>, + registry: &Arc, ) -> Result, String> { registry.snapshot() } -#[tauri::command(rename_all = "snake_case")] pub fn git_clone_repository_cancel( - registry: tauri::State<'_, Arc>, + registry: &Arc, task_id: String, ) -> Result { registry.cancel(task_id) } -#[tauri::command(rename_all = "snake_case")] pub fn git_clone_repository_dismiss( - registry: tauri::State<'_, Arc>, + registry: &Arc, task_id: String, ) -> Result, String> { registry.dismiss(task_id)?; registry.snapshot() } -#[tauri::command(rename_all = "snake_case")] pub async fn git_list_remote_branches( remote_url: String, ) -> Result { @@ -3254,7 +3238,6 @@ pub async fn git_list_remote_branches( .map_err(|error| format!("git_list_remote_branches join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_diff( workdir: String, mode: Option, @@ -3265,7 +3248,6 @@ pub async fn git_diff( .map_err(|error| format!("git_diff join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_log( workdir: String, limit: Option, @@ -3276,7 +3258,6 @@ pub async fn git_log( .map_err(|error| format!("git_log join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_commit_details( workdir: String, commit: String, @@ -3286,19 +3267,15 @@ pub async fn git_commit_details( .map_err(|error| format!("git_commit_details join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_compare_commit_with_remote( workdir: String, commit: String, ) -> Result { - tokio::task::spawn_blocking(move || { - git_compare_commit_with_remote_sync(workdir, commit) - }) - .await - .map_err(|error| format!("git_compare_commit_with_remote join 失败:{error}"))? + tokio::task::spawn_blocking(move || git_compare_commit_with_remote_sync(workdir, commit)) + .await + .map_err(|error| format!("git_compare_commit_with_remote join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_commit_diff( workdir: String, commit: String, @@ -3309,35 +3286,30 @@ pub async fn git_commit_diff( .map_err(|error| format!("git_commit_diff join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_stage(workdir: String, path: String) -> Result { tokio::task::spawn_blocking(move || git_stage_sync(workdir, path)) .await .map_err(|error| format!("git_stage join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_stage_all(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_stage_all_sync(workdir)) .await .map_err(|error| format!("git_stage_all join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_unstage(workdir: String, path: String) -> Result { tokio::task::spawn_blocking(move || git_unstage_sync(workdir, path)) .await .map_err(|error| format!("git_unstage join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_unstage_all(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_unstage_all_sync(workdir)) .await .map_err(|error| format!("git_unstage_all join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_discard( workdir: String, path: String, @@ -3348,14 +3320,12 @@ pub async fn git_discard( .map_err(|error| format!("git_discard join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_discard_all(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_discard_all_sync(workdir)) .await .map_err(|error| format!("git_discard_all join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_add_to_gitignore( workdir: String, path: String, @@ -3365,7 +3335,6 @@ pub async fn git_add_to_gitignore( .map_err(|error| format!("git_add_to_gitignore join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_open_system_file_location( workdir: String, path: String, @@ -3375,28 +3344,24 @@ pub async fn git_open_system_file_location( .map_err(|error| format!("git_open_system_file_location join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_commit(workdir: String, message: String) -> Result { tokio::task::spawn_blocking(move || git_commit_sync(workdir, message)) .await .map_err(|error| format!("git_commit join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_fetch(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_fetch_sync(workdir)) .await .map_err(|error| format!("git_fetch join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_pull(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_pull_sync(workdir)) .await .map_err(|error| format!("git_pull join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_set_remote( workdir: String, remote_url: String, @@ -3406,14 +3371,12 @@ pub async fn git_set_remote( .map_err(|error| format!("git_set_remote join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_push(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_push_sync(workdir)) .await .map_err(|error| format!("git_push join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_delete_branch( workdir: String, branch: String, @@ -3424,20 +3387,16 @@ pub async fn git_delete_branch( .map_err(|error| format!("git_delete_branch join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_rename_branch( workdir: String, branch: String, new_branch: String, ) -> Result { - tokio::task::spawn_blocking(move || { - git_rename_branch_sync(workdir, branch, new_branch) - }) - .await - .map_err(|error| format!("git_rename_branch join 失败:{error}"))? + tokio::task::spawn_blocking(move || git_rename_branch_sync(workdir, branch, new_branch)) + .await + .map_err(|error| format!("git_rename_branch join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_stash_push( workdir: String, message: Option, @@ -3447,7 +3406,6 @@ pub async fn git_stash_push( .map_err(|error| format!("git_stash_push join 失败:{error}"))? } -#[tauri::command(rename_all = "snake_case")] pub async fn git_stash_pop(workdir: String) -> Result { tokio::task::spawn_blocking(move || git_stash_pop_sync(workdir)) .await diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs b/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs index 0981882fc..d4136f94e 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs +++ b/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs @@ -1048,7 +1048,6 @@ pub(crate) fn cleanup_worktree_targets_blocking( } } -#[tauri::command] pub async fn subagent_worktree_create( input: SubagentWorktreeCreateInput, ) -> Result { @@ -1138,7 +1137,6 @@ pub async fn subagent_worktree_create( .map_err(|err| format!("subagent_worktree_create join failed: {err}"))? } -#[tauri::command] pub async fn subagent_worktree_status( input: SubagentWorktreeStatusInput, ) -> Result { @@ -1149,7 +1147,6 @@ pub async fn subagent_worktree_status( .map_err(|err| format!("subagent_worktree_status join failed: {err}"))? } -#[tauri::command] pub async fn subagent_worktree_apply( input: SubagentWorktreeApplyInput, ) -> Result { @@ -1160,7 +1157,6 @@ pub async fn subagent_worktree_apply( .map_err(|err| format!("subagent_worktree_apply join failed: {err}"))? } -#[tauri::command] pub async fn subagent_worktree_cleanup( input: SubagentWorktreeCleanupInput, ) -> Result { diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs index a2c71584a..52ead3232 100644 --- a/crates/agent-gui/src-tauri/src/gateway_sink.rs +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -16,7 +16,6 @@ use std::sync::{Arc, Weak}; use crate::commands::chat_history::{ ChatHistorySummary, HISTORY_DELETE_EVENT, HISTORY_UPSERT_EVENT, }; -use agent_core::events::EventSink; use crate::runtime::managed_process::MANAGED_PROCESS_CHANGED_EVENT; use crate::services::automation::types::{CRON_CHANGED_EVENT, HOOKS_CHANGED_EVENT}; use crate::services::gateway::{ @@ -24,6 +23,7 @@ use crate::services::gateway::{ GatewayController, }; use crate::services::workspace_watch::{WorkspaceWatchService, WORKSPACE_ACTIVITY_EVENT}; +use agent_core::events::EventSink; pub struct GatewayEventSink { controller: Weak, @@ -78,9 +78,15 @@ impl GatewayEventSink { payload: Some(proto::agent_envelope::Payload::WorkspaceActivity( proto::WorkspaceActivityEvent { workdir: workdir.to_string(), - revision: payload.get("revision").and_then(|v| v.as_u64()).unwrap_or(0), + revision: payload + .get("revision") + .and_then(|v| v.as_u64()) + .unwrap_or(0), fs: payload.get("fs").and_then(|v| v.as_bool()).unwrap_or(false), - git: payload.get("git").and_then(|v| v.as_bool()).unwrap_or(false), + git: payload + .get("git") + .and_then(|v| v.as_bool()) + .unwrap_or(false), changed_paths, truncated: payload .get("truncated") @@ -121,18 +127,16 @@ impl EventSink for GatewayEventSink { } // history 是少数几个必须把 payload 读回来的事件:Gateway 要把整个 // summary 转成 sync 事件发给远端,重读数据库拿不到「刚才改的是哪一条」。 - HISTORY_UPSERT_EVENT => { - match serde_json::from_value::(payload) { - Ok(summary) => { - tokio::spawn(async move { - controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; - }); - } - Err(error) => eprintln!("history upsert 事件反序列化失败: {error}"), + HISTORY_UPSERT_EVENT => match serde_json::from_value::(payload) { + Ok(summary) => { + tokio::spawn(async move { + controller + .publish_history_sync(build_history_sync_upsert(&summary)) + .await; + }); } - } + Err(error) => eprintln!("history upsert 事件反序列化失败: {error}"), + }, HISTORY_DELETE_EVENT => { let Some(conversation_id) = payload.as_str().map(str::to_string) else { eprintln!("history delete 事件 payload 不是字符串"); diff --git a/crates/agent-gui/src-tauri/src/lib.rs b/crates/agent-gui/src-tauri/src/lib.rs index 6178590d8..2f5c04423 100644 --- a/crates/agent-gui/src-tauri/src/lib.rs +++ b/crates/agent-gui/src-tauri/src/lib.rs @@ -2,6 +2,7 @@ mod commands; mod gateway_sink; mod runtime; mod services; +mod tauri_commands; mod tauri_sink; use std::sync::atomic::{AtomicBool, Ordering}; @@ -43,100 +44,100 @@ macro_rules! app_invoke_handler { () => { tauri::generate_handler![ // Chat history - commands::chat_history::chat_history_list, - commands::chat_history::chat_history_workdirs, - commands::chat_history::chat_history_shared_list, - commands::chat_history::chat_history_search, - commands::chat_history::chat_history_get_window, - commands::chat_history::chat_history_upsert, - commands::chat_history::chat_history_upsert_active_segment, - commands::chat_history::chat_history_append_segment, - commands::chat_history::chat_history_rename, - commands::chat_history::chat_history_branch, - commands::chat_history::chat_history_replace_from_message, - commands::chat_history::chat_history_set_pinned, - commands::chat_history::chat_history_set_model, - commands::chat_history::chat_history_share_get, - commands::chat_history::chat_history_share_set, - commands::chat_history::chat_history_delete, + tauri_commands::chat_history::chat_history_list, + tauri_commands::chat_history::chat_history_workdirs, + tauri_commands::chat_history::chat_history_shared_list, + tauri_commands::chat_history::chat_history_search, + tauri_commands::chat_history::chat_history_get_window, + tauri_commands::chat_history::chat_history_upsert, + tauri_commands::chat_history::chat_history_upsert_active_segment, + tauri_commands::chat_history::chat_history_append_segment, + tauri_commands::chat_history::chat_history_rename, + tauri_commands::chat_history::chat_history_branch, + tauri_commands::chat_history::chat_history_replace_from_message, + tauri_commands::chat_history::chat_history_set_pinned, + tauri_commands::chat_history::chat_history_set_model, + tauri_commands::chat_history::chat_history_share_get, + tauri_commands::chat_history::chat_history_share_set, + tauri_commands::chat_history::chat_history_delete, // Subagent store - commands::subagent_store::subagent_identity_upsert, - commands::subagent_store::subagent_identity_list, - commands::subagent_store::subagent_run_save, - commands::subagent_store::subagent_run_list, - commands::subagent_store::subagent_run_load, - commands::subagent_store::subagent_run_prune, - commands::subagent_store::subagent_message_append, - commands::subagent_store::subagent_message_list, + tauri_commands::subagent_store::subagent_identity_upsert, + tauri_commands::subagent_store::subagent_identity_list, + tauri_commands::subagent_store::subagent_run_save, + tauri_commands::subagent_store::subagent_run_list, + tauri_commands::subagent_store::subagent_run_load, + tauri_commands::subagent_store::subagent_run_prune, + tauri_commands::subagent_store::subagent_message_append, + tauri_commands::subagent_store::subagent_message_list, // File system - commands::fs::fs_read_text, - commands::fs::fs_read_editable_text, - commands::fs::fs_path_status, - commands::fs::fs_read_image_source, - commands::fs::fs_read_workspace_image, - commands::fs::fs_write_text, - commands::fs::fs_edit_text, - commands::fs::fs_delete, - commands::fs::fs_open_workspace_path, - commands::fs::fs_create_dir, - commands::fs::fs_rename, - commands::fs::fs_roots, - commands::fs::fs_list_dirs, - commands::fs::fs_list, - commands::fs::fs_glob, - commands::fs::fs_grep, - commands::fs::fs_mention_list, - commands::chat_file_links::open_chat_file_link, + tauri_commands::fs::fs_read_text, + tauri_commands::fs::fs_read_editable_text, + tauri_commands::fs::fs_path_status, + tauri_commands::fs::fs_read_image_source, + tauri_commands::fs::fs_read_workspace_image, + tauri_commands::fs::fs_write_text, + tauri_commands::fs::fs_edit_text, + tauri_commands::fs::fs_delete, + tauri_commands::fs::fs_open_workspace_path, + tauri_commands::fs::fs_create_dir, + tauri_commands::fs::fs_rename, + tauri_commands::fs::fs_roots, + tauri_commands::fs::fs_list_dirs, + tauri_commands::fs::fs_list, + tauri_commands::fs::fs_glob, + tauri_commands::fs::fs_grep, + tauri_commands::fs::fs_mention_list, + tauri_commands::chat_file_links::open_chat_file_link, // Subagent worktrees - commands::subagent_worktree::subagent_worktree_create, - commands::subagent_worktree::subagent_worktree_status, - commands::subagent_worktree::subagent_worktree_apply, - commands::subagent_worktree::subagent_worktree_cleanup, + tauri_commands::subagent_worktree::subagent_worktree_create, + tauri_commands::subagent_worktree::subagent_worktree_status, + tauri_commands::subagent_worktree::subagent_worktree_apply, + tauri_commands::subagent_worktree::subagent_worktree_cleanup, // MCP - commands::mcp::mcp_list_tools, - commands::mcp::mcp_call_tool, - commands::mcp::mcp_runtime_status, - commands::mcp::mcp_stop_server, - commands::mcp::mcp_test_server, - commands::mcp::mcp_restart_server, + tauri_commands::mcp::mcp_list_tools, + tauri_commands::mcp::mcp_call_tool, + tauri_commands::mcp::mcp_runtime_status, + tauri_commands::mcp::mcp_stop_server, + tauri_commands::mcp::mcp_test_server, + tauri_commands::mcp::mcp_restart_server, // Memory - commands::memory::memory_list, - commands::memory::memory_read, - commands::memory::memory_search, - commands::memory::memory_write, - commands::memory::memory_update, - commands::memory::memory_delete, - commands::memory::memory_delete_project, - commands::memory::memory_accept, - commands::memory::memory_apply_batch, - commands::memory::memory_organize_run_create, - commands::memory::memory_organize_run_update, - commands::memory::memory_organize_run_list, - commands::memory::memory_organize_run_read, - commands::memory::memory_organize_run_clear_history, - commands::memory::memory_organize_due_claim, - commands::memory::memory_organize_due_complete, - commands::memory::memory_index_overview, - commands::memory::memory_paths_info, - commands::memory::memory_recent_rejections, - commands::memory::memory_today_local_date, - commands::memory::memory_today_daily, - commands::memory::memory_quota_summary, - commands::memory::memory_wipe_all, + tauri_commands::memory::memory_list, + tauri_commands::memory::memory_read, + tauri_commands::memory::memory_search, + tauri_commands::memory::memory_write, + tauri_commands::memory::memory_update, + tauri_commands::memory::memory_delete, + tauri_commands::memory::memory_delete_project, + tauri_commands::memory::memory_accept, + tauri_commands::memory::memory_apply_batch, + tauri_commands::memory::memory_organize_run_create, + tauri_commands::memory::memory_organize_run_update, + tauri_commands::memory::memory_organize_run_list, + tauri_commands::memory::memory_organize_run_read, + tauri_commands::memory::memory_organize_run_clear_history, + tauri_commands::memory::memory_organize_due_claim, + tauri_commands::memory::memory_organize_due_complete, + tauri_commands::memory::memory_index_overview, + tauri_commands::memory::memory_paths_info, + tauri_commands::memory::memory_recent_rejections, + tauri_commands::memory::memory_today_local_date, + tauri_commands::memory::memory_today_daily, + tauri_commands::memory::memory_quota_summary, + tauri_commands::memory::memory_wipe_all, // Settings - commands::settings::settings_load_all, - commands::settings::settings_save_providers, - commands::settings::settings_list_ccswitch_providers, - commands::settings::settings_list_cherry_studio_providers, - commands::settings::settings_list_cherry_studio_providers_from_path, - commands::settings::settings_save_system, - commands::settings::settings_save_mcp, - commands::settings::settings_save_agents, - commands::settings::settings_save_ssh, - commands::settings::settings_apply_ssh_patch, - commands::settings::settings_reset_ssh_known_host, - commands::settings::settings_save_remote, - commands::settings::settings_save_memory, + tauri_commands::settings::settings_load_all, + tauri_commands::settings::settings_save_providers, + tauri_commands::settings::settings_list_ccswitch_providers, + tauri_commands::settings::settings_list_cherry_studio_providers, + tauri_commands::settings::settings_list_cherry_studio_providers_from_path, + tauri_commands::settings::settings_save_system, + tauri_commands::settings::settings_save_mcp, + tauri_commands::settings::settings_save_agents, + tauri_commands::settings::settings_save_ssh, + tauri_commands::settings::settings_apply_ssh_patch, + tauri_commands::settings::settings_reset_ssh_known_host, + tauri_commands::settings::settings_save_remote, + tauri_commands::settings::settings_save_memory, commands::update::app_update_check, commands::update::app_update_install, commands::update::app_restart, @@ -149,96 +150,96 @@ macro_rules! app_invoke_handler { commands::app::app_macos_traffic_light_metrics, commands::tray::app_tray_menu_sync, // Hooks - commands::hook::hook_run_script, - commands::hook::hook_run_http_requests, - commands::hook::hook_cancel_scope, + tauri_commands::hook::hook_run_script, + tauri_commands::hook::hook_run_http_requests, + tauri_commands::hook::hook_cancel_scope, // Automation (cron tasks + hooks store) - commands::cron::cron_validate_expression, - commands::cron::automation_snapshot, - commands::cron::automation_cron_apply, - commands::cron::automation_hooks_apply, - commands::cron::automation_list_runs, - commands::cron::automation_clear_runs, - commands::cron::automation_run_cron_now, - commands::cron::automation_claim_prompt_runs, - commands::cron::automation_release_prompt_run, - commands::cron::automation_complete_prompt_run, + tauri_commands::cron::cron_validate_expression, + tauri_commands::cron::automation_snapshot, + tauri_commands::cron::automation_cron_apply, + tauri_commands::cron::automation_hooks_apply, + tauri_commands::cron::automation_list_runs, + tauri_commands::cron::automation_clear_runs, + tauri_commands::cron::automation_run_cron_now, + tauri_commands::cron::automation_claim_prompt_runs, + tauri_commands::cron::automation_release_prompt_run, + tauri_commands::cron::automation_complete_prompt_run, // Local command execution - commands::shell::shell_run, - commands::shell::runtime_cancel, - commands::process::managed_process_start, - commands::process::managed_process_status, - commands::process::managed_process_stop, - commands::process::managed_process_read_log, - commands::process::managed_process_snapshot, - commands::process::managed_process_clear, - commands::terminal::terminal_shell_options, - commands::terminal::terminal_list, - commands::terminal::terminal_create, - commands::terminal::terminal_create_ssh, - commands::terminal::terminal_answer_ssh_prompt, - commands::terminal::terminal_cancel_ssh_prompt, - commands::terminal::terminal_ssh_reconnect, - commands::terminal::terminal_ssh_latency, - commands::terminal::terminal_ssh_exec, - commands::terminal::terminal_ssh_local_forward_start, - commands::terminal::terminal_ssh_local_forward_list, - commands::terminal::terminal_ssh_local_forward_stop, - commands::terminal::terminal_ssh_local_forward_check_port, - commands::terminal::ssh_terminal_tabs_list, - commands::terminal::ssh_terminal_tab_open, - commands::terminal::ssh_terminal_tab_close, - commands::terminal::terminal_stream_attach, - commands::terminal::terminal_stream_input, - commands::terminal::terminal_stream_resize, - commands::terminal::terminal_rename, - commands::terminal::terminal_close, - commands::terminal::terminal_close_project, - commands::terminal::terminal_read_tail, - commands::sftp::sftp_list, - commands::sftp::sftp_stat, - commands::sftp::sftp_read_text, - commands::sftp::sftp_write_text, - commands::sftp::sftp_mkdir, - commands::sftp::sftp_rename, - commands::sftp::sftp_delete, - commands::sftp::sftp_transfer, - commands::sftp::sftp_cancel_transfer, - commands::sftp::sftp_transfer_status, - commands::git::git_status, - commands::git::git_discover_repositories, - commands::git::git_branches, - commands::git::git_init, - commands::git::git_clone_repository, - commands::git::git_clone_repository_start, - commands::git::git_clone_repository_tasks, - commands::git::git_clone_repository_cancel, - commands::git::git_clone_repository_dismiss, - commands::git::git_list_remote_branches, - commands::git::git_switch_branch, - commands::git::git_create_branch, - commands::git::git_diff, - commands::git::git_log, - commands::git::git_commit_details, - commands::git::git_compare_commit_with_remote, - commands::git::git_commit_diff, - commands::git::git_stage, - commands::git::git_stage_all, - commands::git::git_unstage, - commands::git::git_unstage_all, - commands::git::git_discard, - commands::git::git_discard_all, - commands::git::git_add_to_gitignore, - commands::git::git_open_system_file_location, - commands::git::git_commit, - commands::git::git_fetch, - commands::git::git_pull, - commands::git::git_set_remote, - commands::git::git_push, - commands::git::git_delete_branch, - commands::git::git_rename_branch, - commands::git::git_stash_push, - commands::git::git_stash_pop, + tauri_commands::shell::shell_run, + tauri_commands::shell::runtime_cancel, + tauri_commands::process::managed_process_start, + tauri_commands::process::managed_process_status, + tauri_commands::process::managed_process_stop, + tauri_commands::process::managed_process_read_log, + tauri_commands::process::managed_process_snapshot, + tauri_commands::process::managed_process_clear, + tauri_commands::terminal::terminal_shell_options, + tauri_commands::terminal::terminal_list, + tauri_commands::terminal::terminal_create, + tauri_commands::terminal::terminal_create_ssh, + tauri_commands::terminal::terminal_answer_ssh_prompt, + tauri_commands::terminal::terminal_cancel_ssh_prompt, + tauri_commands::terminal::terminal_ssh_reconnect, + tauri_commands::terminal::terminal_ssh_latency, + tauri_commands::terminal::terminal_ssh_exec, + tauri_commands::terminal::terminal_ssh_local_forward_start, + tauri_commands::terminal::terminal_ssh_local_forward_list, + tauri_commands::terminal::terminal_ssh_local_forward_stop, + tauri_commands::terminal::terminal_ssh_local_forward_check_port, + tauri_commands::terminal::ssh_terminal_tabs_list, + tauri_commands::terminal::ssh_terminal_tab_open, + tauri_commands::terminal::ssh_terminal_tab_close, + tauri_commands::terminal::terminal_stream_attach, + tauri_commands::terminal::terminal_stream_input, + tauri_commands::terminal::terminal_stream_resize, + tauri_commands::terminal::terminal_rename, + tauri_commands::terminal::terminal_close, + tauri_commands::terminal::terminal_close_project, + tauri_commands::terminal::terminal_read_tail, + tauri_commands::sftp::sftp_list, + tauri_commands::sftp::sftp_stat, + tauri_commands::sftp::sftp_read_text, + tauri_commands::sftp::sftp_write_text, + tauri_commands::sftp::sftp_mkdir, + tauri_commands::sftp::sftp_rename, + tauri_commands::sftp::sftp_delete, + tauri_commands::sftp::sftp_transfer, + tauri_commands::sftp::sftp_cancel_transfer, + tauri_commands::sftp::sftp_transfer_status, + tauri_commands::git::git_status, + tauri_commands::git::git_discover_repositories, + tauri_commands::git::git_branches, + tauri_commands::git::git_init, + tauri_commands::git::git_clone_repository, + tauri_commands::git::git_clone_repository_start, + tauri_commands::git::git_clone_repository_tasks, + tauri_commands::git::git_clone_repository_cancel, + tauri_commands::git::git_clone_repository_dismiss, + tauri_commands::git::git_list_remote_branches, + tauri_commands::git::git_switch_branch, + tauri_commands::git::git_create_branch, + tauri_commands::git::git_diff, + tauri_commands::git::git_log, + tauri_commands::git::git_commit_details, + tauri_commands::git::git_compare_commit_with_remote, + tauri_commands::git::git_commit_diff, + tauri_commands::git::git_stage, + tauri_commands::git::git_stage_all, + tauri_commands::git::git_unstage, + tauri_commands::git::git_unstage_all, + tauri_commands::git::git_discard, + tauri_commands::git::git_discard_all, + tauri_commands::git::git_add_to_gitignore, + tauri_commands::git::git_open_system_file_location, + tauri_commands::git::git_commit, + tauri_commands::git::git_fetch, + tauri_commands::git::git_pull, + tauri_commands::git::git_set_remote, + tauri_commands::git::git_push, + tauri_commands::git::git_delete_branch, + tauri_commands::git::git_rename_branch, + tauri_commands::git::git_stash_push, + tauri_commands::git::git_stash_pop, commands::system::system_pick_folder, commands::system::system_pick_file, commands::system::system_create_project_folder, @@ -285,7 +286,7 @@ macro_rules! app_invoke_handler { commands::gateway::workspace_watch_set, commands::gateway::provider_usage_query, commands::gateway::provider_usage_test, - services::proxy::proxy_get_server_info, + tauri_commands::proxy::proxy_get_server_info, ] }; } @@ -726,9 +727,9 @@ pub fn run() { // 桌面 webview 和 Gateway 都只是普通订阅者,没有谁被硬编码进后端。 // 先建空总线,sink 稍后注册——总线用内部可变性,允许后置。 let events = Arc::new(agent_core::events::EventBus::new()); - let workspace_watch = Arc::new(services::workspace_watch::WorkspaceWatchService::new( - Arc::clone(&events), - )); + let workspace_watch = Arc::new( + services::workspace_watch::WorkspaceWatchService::new(Arc::clone(&events)), + ); let gateway_controller = Arc::new(services::gateway::GatewayController::new( app.handle().clone(), Arc::clone(&automation_store), diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs index 6a3ff1413..4ea854670 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs @@ -14,11 +14,11 @@ //! - [`shell`]:本地 shell 解析、PTY 环境与进程清理 //! - [`util`]:小型辅助函数 +use agent_core::events::EventBus; use std::collections::HashMap; use std::sync::atomic::AtomicUsize; use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; -use agent_core::events::EventBus; mod events; mod output; diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs b/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs index 1724ded67..3dc9d35bf 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs +++ b/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs @@ -5,10 +5,10 @@ use std::sync::atomic::Ordering; use std::sync::{mpsc, Arc, Mutex}; use std::thread; -use agent_core::events::EventBus; use crate::runtime::project_path::{ project_path_key as normalize_project_path_key, project_path_keys_equal, }; +use agent_core::events::EventBus; use super::*; diff --git a/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs b/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs index bad190a22..e40e7cc03 100644 --- a/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs +++ b/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs @@ -73,10 +73,8 @@ impl AutomationScheduler { async fn run_loop(self: Arc) { { let store = Arc::clone(&self.store); - let recovered = tokio::task::spawn_blocking(move || { - store.recover_interrupted_prompt_runs() - }) - .await; + let recovered = + tokio::task::spawn_blocking(move || store.recover_interrupted_prompt_runs()).await; match recovered { Ok(Ok(count)) if count > 0 => { eprintln!("automation: expired {count} prompt run(s) interrupted by restart"); @@ -255,10 +253,7 @@ impl AutomationScheduler { let fresh = { let store = Arc::clone(&self.store); let task_id = task_id.clone(); - tokio::task::spawn_blocking(move || { - store.cron_task_for_scheduled_fire(&task_id) - }) - .await + tokio::task::spawn_blocking(move || store.cron_task_for_scheduled_fire(&task_id)).await }; match fresh { Ok(Ok(Some((workdir, task)))) => { @@ -442,8 +437,7 @@ impl AutomationScheduler { fn record_run_detached(&self, run: CompletedRun) { let store = Arc::clone(&self.store); tokio::spawn(async move { - let result = - tokio::task::spawn_blocking(move || store.record_completed_run(run)).await; + let result = tokio::task::spawn_blocking(move || store.record_completed_run(run)).await; match result { Ok(Err(error)) => eprintln!("Cron run 记录失败:{error}"), Err(error) => eprintln!("Cron run 记录 join 失败:{error}"), diff --git a/crates/agent-gui/src-tauri/src/services/proxy.rs b/crates/agent-gui/src-tauri/src/services/proxy.rs index 00e92cae9..5ac9482ad 100644 --- a/crates/agent-gui/src-tauri/src/services/proxy.rs +++ b/crates/agent-gui/src-tauri/src/services/proxy.rs @@ -75,8 +75,7 @@ struct ImageProxyQuery { url: String, } -#[tauri::command] -pub fn proxy_get_server_info(state: tauri::State<'_, Arc>) -> ProxyServerInfo { +pub fn proxy_get_server_info(state: &Arc) -> ProxyServerInfo { state.info.clone() } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs b/crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs new file mode 100644 index 000000000..5c74d69bb --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs @@ -0,0 +1,42 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::chat_file_links::*; +use crate::runtime::platform::expand_tilde_path; +use serde::Serialize; +use std::fs; +use std::io::Read; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, LazyLock}; +use std::time::Duration; +use std::time::{SystemTime, UNIX_EPOCH}; +use tokio::sync::Semaphore; + +#[tauri::command(rename_all = "snake_case")] +pub async fn open_chat_file_link( + conversation_id: String, + workdir: String, + path: String, + source: String, + line: Option, + end_line: Option, + column: Option, + open_in_file_manager: Option, +) -> Result { + crate::commands::chat_file_links::open_chat_file_link( + conversation_id, + workdir, + path, + source, + line, + end_line, + column, + open_in_file_manager, + ) + .await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs b/crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs new file mode 100644 index 000000000..224ff8613 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs @@ -0,0 +1,184 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::chat_history::*; +use crate::{ + commands::{history_db, subagent_store}, + services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}, +}; +use agent_core::events::EventBus; +use chrono::{Local, LocalResult, NaiveDate, TimeZone}; +use regex::Regex; +use rusqlite::{params, Connection, OptionalExtension}; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use serde_json::{Map, Value}; +use std::{ + collections::HashMap, + sync::Arc, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; +use uuid::Uuid; + +#[tauri::command] +pub async fn chat_history_replace_from_message( + id: String, + base_message_ref: ChatHistoryMessageRef, + replacement_message: Value, + max_messages: i64, + expected_revision: String, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_replace_from_message( + id, + base_message_ref, + replacement_message, + max_messages, + expected_revision, + events.inner(), + ) + .await +} + +#[tauri::command] +pub async fn chat_history_delete( + id: String, + events: tauri::State<'_, Arc>, +) -> Result<(), String> { + crate::commands::chat_history::chat_history_delete(id, events.inner()).await +} + +#[tauri::command] +pub async fn chat_history_list( + page: i64, + page_size: i64, + cwd: Option, + cwd_empty: Option, +) -> Result { + crate::commands::chat_history::chat_history_list(page, page_size, cwd, cwd_empty).await +} + +#[tauri::command] +pub async fn chat_history_workdirs() -> Result { + crate::commands::chat_history::chat_history_workdirs().await +} + +#[tauri::command] +pub async fn chat_history_shared_list( + page: i64, + page_size: i64, +) -> Result { + crate::commands::chat_history::chat_history_shared_list(page, page_size).await +} + +#[tauri::command] +pub async fn chat_history_search( + args: ChatHistorySearchArgs, +) -> Result { + crate::commands::chat_history::chat_history_search(args).await +} + +#[tauri::command] +pub async fn chat_history_get_window( + id: String, + max_messages: i64, + before_offset: Option, + expected_revision: Option, + include_active_segment: bool, +) -> Result { + crate::commands::chat_history::chat_history_get_window( + id, + max_messages, + before_offset, + expected_revision, + include_active_segment, + ) + .await +} + +#[tauri::command] +pub async fn chat_history_upsert( + input: ChatHistoryUpsertInput, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_upsert(input, events.inner()).await +} + +#[tauri::command] +pub async fn chat_history_upsert_active_segment( + input: ChatHistorySegmentMutationInput, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_upsert_active_segment(input, events.inner()).await +} + +#[tauri::command] +pub async fn chat_history_append_segment( + input: ChatHistorySegmentMutationInput, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_append_segment(input, events.inner()).await +} + +#[tauri::command] +pub async fn chat_history_rename( + id: String, + title: String, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_rename(id, title, events.inner()).await +} + +#[tauri::command] +pub async fn chat_history_set_pinned( + id: String, + is_pinned: bool, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_set_pinned(id, is_pinned, events.inner()).await +} + +#[tauri::command] +pub async fn chat_history_set_model( + id: String, + selected_model_json: String, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_set_model(id, selected_model_json, events.inner()) + .await +} + +#[tauri::command] +pub async fn chat_history_share_get(id: String) -> Result { + crate::commands::chat_history::chat_history_share_get(id).await +} + +#[tauri::command] +pub async fn chat_history_share_set( + id: String, + enabled: bool, + redact_tool_content: Option, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_share_set( + id, + enabled, + redact_tool_content, + events.inner(), + ) + .await +} + +#[tauri::command] +pub async fn chat_history_branch( + id: String, + base_message_ref: ChatHistoryMessageRef, + events: tauri::State<'_, Arc>, +) -> Result { + crate::commands::chat_history::chat_history_branch(id, base_message_ref, events.inner()).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/cron.rs b/crates/agent-gui/src-tauri/src/tauri_commands/cron.rs new file mode 100644 index 000000000..60260fa97 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/cron.rs @@ -0,0 +1,91 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::cron::*; +use crate::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; +use std::sync::Arc; + +#[tauri::command(rename_all = "snake_case")] +pub async fn cron_validate_expression(expression: String) -> Result<(), String> { + crate::commands::cron::cron_validate_expression(expression).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_snapshot( + store: tauri::State<'_, Arc>, +) -> Result { + crate::commands::cron::automation_snapshot(store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_cron_apply( + input: AutomationApplyInput, + store: tauri::State<'_, Arc>, +) -> Result { + crate::commands::cron::automation_cron_apply(input, store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_hooks_apply( + input: AutomationApplyInput, + store: tauri::State<'_, Arc>, +) -> Result { + crate::commands::cron::automation_hooks_apply(input, store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_list_runs( + task_id: String, + limit: Option, + store: tauri::State<'_, Arc>, +) -> Result, String> { + crate::commands::cron::automation_list_runs(task_id, limit, store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_clear_runs( + task_id: String, + store: tauri::State<'_, Arc>, +) -> Result { + crate::commands::cron::automation_clear_runs(task_id, store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_run_cron_now( + task_id: String, + store: tauri::State<'_, Arc>, +) -> Result { + crate::commands::cron::automation_run_cron_now(task_id, store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_claim_prompt_runs( + store: tauri::State<'_, Arc>, +) -> Result, String> { + crate::commands::cron::automation_claim_prompt_runs(store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_release_prompt_run( + execution_id: String, + store: tauri::State<'_, Arc>, +) -> Result<(), String> { + crate::commands::cron::automation_release_prompt_run(execution_id, store.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn automation_complete_prompt_run( + input: CompletePromptRunInput, + store: tauri::State<'_, Arc>, +) -> Result { + crate::commands::cron::automation_complete_prompt_run(input, store.inner()).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/fs.rs b/crates/agent-gui/src-tauri/src/tauri_commands/fs.rs new file mode 100644 index 000000000..43becaa14 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/fs.rs @@ -0,0 +1,231 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::fs::*; +use crate::runtime::platform::expand_tilde_path; +use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; +use globset::{Glob, GlobSet, GlobSetBuilder}; +use ignore::WalkBuilder; +use lopdf::Document as PdfDocument; +use reqwest::header::{CONTENT_LENGTH, CONTENT_TYPE}; +use reqwest::Url; +use serde::Serialize; +use serde_json::Value; +use std::collections::{BTreeMap, HashSet}; +use std::fs; +use std::io::Write; +use std::io::{self, Cursor, Read, Seek}; +use std::path::{Component, Path, PathBuf}; +use std::process::Command; +use std::sync::Arc; +use std::time::Duration; +use std::time::UNIX_EPOCH; +use thiserror::Error; +use zip::write::SimpleFileOptions; +use zip::ZipArchive; + +#[tauri::command] +pub async fn fs_read_image_source( + workdir: String, + source: String, + source_type: Option, + mime_type: Option, +) -> Result { + crate::commands::fs::fs_read_image_source(workdir, source, source_type, mime_type).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_read_workspace_image( + workdir: String, + path: String, +) -> Result { + crate::commands::fs::fs_read_workspace_image(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_read_text( + workdir: String, + path: String, + start_line: Option, + limit: Option, + page_start: Option, + page_limit: Option, + cell_start: Option, + cell_limit: Option, +) -> Result { + crate::commands::fs::fs_read_text( + workdir, path, start_line, limit, page_start, page_limit, cell_start, cell_limit, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_read_editable_text( + workdir: String, + path: String, +) -> Result { + crate::commands::fs::fs_read_editable_text(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_path_status( + workdir: String, + path: String, +) -> Result { + crate::commands::fs::fs_path_status(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_write_text( + workdir: String, + path: String, + content: String, + mode: String, + expected_mtime_ms: Option, + expected_content_hash: Option, +) -> Result { + crate::commands::fs::fs_write_text( + workdir, + path, + content, + mode, + expected_mtime_ms, + expected_content_hash, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_edit_text( + workdir: String, + path: String, + old_string: String, + new_string: String, + expected_replacements: Option, + replace_all: Option, + expected_mtime_ms: Option, + expected_content_hash: Option, +) -> Result { + crate::commands::fs::fs_edit_text( + workdir, + path, + old_string, + new_string, + expected_replacements, + replace_all, + expected_mtime_ms, + expected_content_hash, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_delete(workdir: String, path: String) -> Result { + crate::commands::fs::fs_delete(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_open_workspace_path( + workdir: String, + path: String, + mode: Option, +) -> Result { + crate::commands::fs::fs_open_workspace_path(workdir, path, mode).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_create_dir( + workdir: String, + path: String, +) -> Result { + crate::commands::fs::fs_create_dir(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_rename( + workdir: String, + from_path: String, + to_path: String, +) -> Result { + crate::commands::fs::fs_rename(workdir, from_path, to_path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_roots() -> Result { + crate::commands::fs::fs_roots().await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_list_dirs( + path: String, + max_results: Option, +) -> Result { + crate::commands::fs::fs_list_dirs(path, max_results).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_list( + workdir: String, + path: Option, + depth: Option, + offset: Option, + max_results: Option, + show_hidden: Option, +) -> Result { + crate::commands::fs::fs_list(workdir, path, depth, offset, max_results, show_hidden).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_glob( + workdir: String, + path: Option, + pattern: String, + offset: Option, + max_results: Option, + sort_by: Option, +) -> Result { + crate::commands::fs::fs_glob(workdir, path, pattern, offset, max_results, sort_by).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_grep( + workdir: String, + path: Option, + pattern: String, + file_pattern: Option, + ignore_case: Option, + output_mode: Option, + head_limit: Option, + offset: Option, + context: Option, + multiline: Option, +) -> Result { + crate::commands::fs::fs_grep( + workdir, + path, + pattern, + file_pattern, + ignore_case, + output_mode, + head_limit, + offset, + context, + multiline, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn fs_mention_list( + workdir: String, + max_results: Option, + query: Option, + show_hidden: Option, +) -> Result { + crate::commands::fs::fs_mention_list(workdir, max_results, query, show_hidden).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/git.rs b/crates/agent-gui/src-tauri/src/tauri_commands/git.rs new file mode 100644 index 000000000..37bc82a00 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/git.rs @@ -0,0 +1,280 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::git::*; +use crate::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use serde_json::Value; +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::io::{BufRead, BufReader, Read, Seek, SeekFrom}; +use std::path::{Component, Path, PathBuf}; +use std::process::{Child, Command, Output, Stdio}; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread; +use std::time::Instant; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tempfile::NamedTempFile; +use tempfile::TempDir; +use wait_timeout::ChildExt; + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_status(workdir: String) -> Result { + crate::commands::git::git_status(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_discover_repositories(workdir: String) -> Result { + crate::commands::git::git_discover_repositories(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_branches(workdir: String) -> Result { + crate::commands::git::git_branches(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_switch_branch( + workdir: String, + branch: String, + kind: Option, +) -> Result { + crate::commands::git::git_switch_branch(workdir, branch, kind).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_create_branch( + workdir: String, + branch: String, + start_point: Option, +) -> Result { + crate::commands::git::git_create_branch(workdir, branch, start_point).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_init( + workdir: String, + branch: Option, + user_name: Option, + user_email: Option, +) -> Result { + crate::commands::git::git_init(workdir, branch, user_name, user_email).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_clone_repository( + parent: String, + name: String, + remote_url: String, + branch: Option, +) -> Result { + crate::commands::git::git_clone_repository(parent, name, remote_url, branch).await +} + +#[tauri::command(rename_all = "snake_case")] +pub fn git_clone_repository_start( + registry: tauri::State<'_, Arc>, + parent: String, + name: String, + remote_url: String, + branch: Option, +) -> Result { + crate::commands::git::git_clone_repository_start( + registry.inner(), + parent, + name, + remote_url, + branch, + ) +} + +#[tauri::command] +pub fn git_clone_repository_tasks( + registry: tauri::State<'_, Arc>, +) -> Result, String> { + crate::commands::git::git_clone_repository_tasks(registry.inner()) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn git_clone_repository_cancel( + registry: tauri::State<'_, Arc>, + task_id: String, +) -> Result { + crate::commands::git::git_clone_repository_cancel(registry.inner(), task_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn git_clone_repository_dismiss( + registry: tauri::State<'_, Arc>, + task_id: String, +) -> Result, String> { + crate::commands::git::git_clone_repository_dismiss(registry.inner(), task_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_list_remote_branches( + remote_url: String, +) -> Result { + crate::commands::git::git_list_remote_branches(remote_url).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_diff( + workdir: String, + mode: Option, + path: Option, +) -> Result { + crate::commands::git::git_diff(workdir, mode, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_log( + workdir: String, + limit: Option, + skip: Option, +) -> Result { + crate::commands::git::git_log(workdir, limit, skip).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_commit_details( + workdir: String, + commit: String, +) -> Result { + crate::commands::git::git_commit_details(workdir, commit).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_compare_commit_with_remote( + workdir: String, + commit: String, +) -> Result { + crate::commands::git::git_compare_commit_with_remote(workdir, commit).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_commit_diff( + workdir: String, + commit: String, + path: Option, +) -> Result { + crate::commands::git::git_commit_diff(workdir, commit, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_stage(workdir: String, path: String) -> Result { + crate::commands::git::git_stage(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_stage_all(workdir: String) -> Result { + crate::commands::git::git_stage_all(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_unstage(workdir: String, path: String) -> Result { + crate::commands::git::git_unstage(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_unstage_all(workdir: String) -> Result { + crate::commands::git::git_unstage_all(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_discard( + workdir: String, + path: String, + old_path: Option, +) -> Result { + crate::commands::git::git_discard(workdir, path, old_path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_discard_all(workdir: String) -> Result { + crate::commands::git::git_discard_all(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_add_to_gitignore( + workdir: String, + path: String, +) -> Result { + crate::commands::git::git_add_to_gitignore(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_open_system_file_location( + workdir: String, + path: String, +) -> Result { + crate::commands::git::git_open_system_file_location(workdir, path).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_commit(workdir: String, message: String) -> Result { + crate::commands::git::git_commit(workdir, message).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_fetch(workdir: String) -> Result { + crate::commands::git::git_fetch(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_pull(workdir: String) -> Result { + crate::commands::git::git_pull(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_set_remote( + workdir: String, + remote_url: String, +) -> Result { + crate::commands::git::git_set_remote(workdir, remote_url).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_push(workdir: String) -> Result { + crate::commands::git::git_push(workdir).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_delete_branch( + workdir: String, + branch: String, + force: Option, +) -> Result { + crate::commands::git::git_delete_branch(workdir, branch, force).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_rename_branch( + workdir: String, + branch: String, + new_branch: String, +) -> Result { + crate::commands::git::git_rename_branch(workdir, branch, new_branch).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_stash_push( + workdir: String, + message: Option, +) -> Result { + crate::commands::git::git_stash_push(workdir, message).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn git_stash_pop(workdir: String) -> Result { + crate::commands::git::git_stash_pop(workdir).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/hook.rs b/crates/agent-gui/src-tauri/src/tauri_commands/hook.rs new file mode 100644 index 000000000..e512a14dc --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/hook.rs @@ -0,0 +1,57 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::hook::*; +use crate::runtime::shell_runner::{ + run_shell_script_with_envs, ShellCancelFlag, ShellCancelToken, ShellRunResponse, +}; +use crate::runtime::task_runner::{ + build_http_client, resolve_workdir, run_single_http_request, HttpRequestInput, +}; +use crate::services::automation::validate::{MAX_HOOK_TIMEOUT_MS, MIN_HOOK_TIMEOUT_MS}; +use serde::Serialize; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::sync::{Arc, Mutex}; +use uuid::Uuid; + +#[tauri::command(rename_all = "snake_case")] +pub async fn hook_run_script( + workdir: Option, + script: String, + timeout_ms: Option, + scope_id: Option, + context: Option>, + registry: tauri::State<'_, Arc>, +) -> Result { + crate::commands::hook::hook_run_script( + workdir, + script, + timeout_ms, + scope_id, + context, + registry.inner(), + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn hook_run_http_requests( + requests: Vec, + scope_id: Option, + registry: tauri::State<'_, Arc>, +) -> Result { + crate::commands::hook::hook_run_http_requests(requests, scope_id, registry.inner()).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn hook_cancel_scope( + scope_id: String, + registry: tauri::State<'_, Arc>, +) -> Result<(), String> { + crate::commands::hook::hook_cancel_scope(scope_id, registry.inner()).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs b/crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs new file mode 100644 index 000000000..2132d0715 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs @@ -0,0 +1,92 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::mcp::*; +use crate::runtime::platform::{ + expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, +}; +use crate::runtime::process::{configure_child_process_group, kill_child_process_tree_best_effort}; +use crate::runtime::shell_runner::ShellRunRegistry; +use reqwest::blocking::Client as HttpClient; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE}; +use reqwest::StatusCode; +use reqwest::Url; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::collections::{BTreeMap, HashMap}; +use std::io::{BufRead, BufReader, Write}; +use std::path::Path; +use std::process::{Child, ChildStdin, Command, Stdio}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Barrier; +use std::sync::{mpsc, Arc, Mutex}; +use std::time::{Duration, Instant}; + +#[tauri::command(rename_all = "snake_case")] +pub async fn mcp_list_tools( + state: tauri::State<'_, Arc>, + servers: Vec, +) -> Result, String> { + crate::commands::mcp::mcp_list_tools(state.inner(), servers).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn mcp_call_tool( + state: tauri::State<'_, Arc>, + run_registry: tauri::State<'_, Arc>, + server_id: String, + tool_name: String, + arguments: Value, + run_id: Option, +) -> Result { + crate::commands::mcp::mcp_call_tool( + state.inner(), + run_registry.inner(), + server_id, + tool_name, + arguments, + run_id, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn mcp_runtime_status( + state: tauri::State<'_, Arc>, + server_id: String, +) -> Result { + crate::commands::mcp::mcp_runtime_status(state.inner(), server_id).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn mcp_stop_server( + state: tauri::State<'_, Arc>, + server_id: String, +) -> Result { + crate::commands::mcp::mcp_stop_server(state.inner(), server_id).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn mcp_test_server( + state: tauri::State<'_, Arc>, + server: McpServerConfig, + include_schema: Option, + persist: Option, +) -> Result { + crate::commands::mcp::mcp_test_server(state.inner(), server, include_schema, persist).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn mcp_restart_server( + state: tauri::State<'_, Arc>, + server: McpServerConfig, + include_schema: Option, + persist: Option, +) -> Result { + crate::commands::mcp::mcp_restart_server(state.inner(), server, include_schema, persist).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/memory.rs b/crates/agent-gui/src-tauri/src/tauri_commands/memory.rs new file mode 100644 index 000000000..32405f3af --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/memory.rs @@ -0,0 +1,205 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::memory::*; +use crate::{ + commands::chat_history, + services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, + }, +}; +use std::sync::Arc; + +#[tauri::command] +pub async fn memory_list( + state: tauri::State<'_, Arc>, + args: MemoryListArgs, +) -> Result { + crate::commands::memory::memory_list(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_read( + state: tauri::State<'_, Arc>, + args: MemoryReadArgs, +) -> Result { + crate::commands::memory::memory_read(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_search( + state: tauri::State<'_, Arc>, + args: MemorySearchArgs, +) -> Result { + crate::commands::memory::memory_search(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_write( + state: tauri::State<'_, Arc>, + args: MemoryWriteArgs, +) -> Result { + crate::commands::memory::memory_write(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_update( + state: tauri::State<'_, Arc>, + args: MemoryUpdateArgs, +) -> Result { + crate::commands::memory::memory_update(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_delete( + state: tauri::State<'_, Arc>, + args: MemoryDeleteArgs, +) -> Result { + crate::commands::memory::memory_delete(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_delete_project( + state: tauri::State<'_, Arc>, + args: MemoryDeleteProjectArgs, +) -> Result { + crate::commands::memory::memory_delete_project(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_accept( + state: tauri::State<'_, Arc>, + args: MemoryAcceptArgs, +) -> Result { + crate::commands::memory::memory_accept(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_apply_batch( + state: tauri::State<'_, Arc>, + args: MemoryBatchArgs, +) -> Result { + crate::commands::memory::memory_apply_batch(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_organize_run_create( + state: tauri::State<'_, Arc>, + args: MemoryOrganizeRunCreateArgs, +) -> Result { + crate::commands::memory::memory_organize_run_create(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_organize_run_update( + state: tauri::State<'_, Arc>, + args: MemoryOrganizeRunUpdateArgs, +) -> Result, String> { + crate::commands::memory::memory_organize_run_update(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_organize_run_list( + state: tauri::State<'_, Arc>, + args: Option, +) -> Result { + crate::commands::memory::memory_organize_run_list(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_organize_run_read( + state: tauri::State<'_, Arc>, + args: MemoryOrganizeRunReadArgs, +) -> Result, String> { + crate::commands::memory::memory_organize_run_read(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_organize_run_clear_history( + state: tauri::State<'_, Arc>, +) -> Result { + crate::commands::memory::memory_organize_run_clear_history(state.inner()).await +} + +#[tauri::command] +pub async fn memory_organize_due_claim( + state: tauri::State<'_, Arc>, + args: MemoryOrganizeDueClaimArgs, +) -> Result { + crate::commands::memory::memory_organize_due_claim(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_organize_due_complete( + state: tauri::State<'_, Arc>, + args: MemoryOrganizeRunUpdateArgs, +) -> Result, String> { + crate::commands::memory::memory_organize_due_complete(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_index_overview( + state: tauri::State<'_, Arc>, + workdir: Option, +) -> Result { + crate::commands::memory::memory_index_overview(state.inner(), workdir).await +} + +#[tauri::command] +pub async fn memory_paths_info( + state: tauri::State<'_, Arc>, +) -> Result { + crate::commands::memory::memory_paths_info(state.inner()).await +} + +#[tauri::command] +pub async fn memory_recent_rejections( + state: tauri::State<'_, Arc>, + args: Option, +) -> Result { + crate::commands::memory::memory_recent_rejections(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_today_local_date( + state: tauri::State<'_, Arc>, + rollover_hour: Option, +) -> Result { + crate::commands::memory::memory_today_local_date(state.inner(), rollover_hour).await +} + +#[tauri::command] +pub async fn memory_today_daily( + state: tauri::State<'_, Arc>, + rollover_hour: Option, +) -> Result, String> { + crate::commands::memory::memory_today_daily(state.inner(), rollover_hour).await +} + +#[tauri::command] +pub async fn memory_quota_summary( + state: tauri::State<'_, Arc>, + args: Option, +) -> Result { + crate::commands::memory::memory_quota_summary(state.inner(), args).await +} + +#[tauri::command] +pub async fn memory_wipe_all( + state: tauri::State<'_, Arc>, +) -> Result { + crate::commands::memory::memory_wipe_all(state.inner()).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/mod.rs b/crates/agent-gui/src-tauri/src/tauri_commands/mod.rs new file mode 100644 index 000000000..4d60e0b22 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/mod.rs @@ -0,0 +1,18 @@ +//! Tauri IPC 薄包装层。桌面壳独有;HTTP 路由在 agent-backend 里另有一套。 + +pub mod chat_file_links; +pub mod chat_history; +pub mod cron; +pub mod fs; +pub mod git; +pub mod hook; +pub mod mcp; +pub mod memory; +pub mod process; +pub mod proxy; +pub mod settings; +pub mod sftp; +pub mod shell; +pub mod subagent_store; +pub mod subagent_worktree; +pub mod terminal; diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/process.rs b/crates/agent-gui/src-tauri/src/tauri_commands/process.rs new file mode 100644 index 000000000..8e8efe82e --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/process.rs @@ -0,0 +1,73 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::process::*; +use crate::runtime::managed_process::{ + ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, + ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, +}; +use std::sync::Arc; + +#[tauri::command(rename_all = "snake_case")] +pub fn managed_process_start( + registry: tauri::State<'_, Arc>, + workdir: String, + command: String, + cwd: Option, + label: Option, + isolated: Option, +) -> Result { + crate::commands::process::managed_process_start( + registry.inner(), + workdir, + command, + cwd, + label, + isolated, + ) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn managed_process_status( + registry: tauri::State<'_, Arc>, + process_id: Option, +) -> Result { + crate::commands::process::managed_process_status(registry.inner(), process_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn managed_process_stop( + registry: tauri::State<'_, Arc>, + process_id: String, +) -> Result { + crate::commands::process::managed_process_stop(registry.inner(), process_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn managed_process_read_log( + registry: tauri::State<'_, Arc>, + process_id: String, + max_bytes: Option, +) -> Result { + crate::commands::process::managed_process_read_log(registry.inner(), process_id, max_bytes) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn managed_process_snapshot( + registry: tauri::State<'_, Arc>, +) -> Result { + crate::commands::process::managed_process_snapshot(registry.inner()) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn managed_process_clear( + registry: tauri::State<'_, Arc>, + process_id: Option, +) -> Result { + crate::commands::process::managed_process_clear(registry.inner(), process_id) +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/proxy.rs b/crates/agent-gui/src-tauri/src/tauri_commands/proxy.rs new file mode 100644 index 000000000..cb37ea1bc --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/proxy.rs @@ -0,0 +1,33 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::services::proxy::*; +use axum::{ + body::{to_bytes, Body}, + extract::{OriginalUri, Path, Query, State}, + http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode}, + response::Response, + routing::{any, get}, + Router, +}; +use base64::Engine as _; +use reqwest::Url; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::{ + net::{Ipv4Addr, TcpListener}, + sync::Arc, + time::Duration, +}; +use tokio::net::TcpListener as TokioTcpListener; +use uuid::Uuid; + +#[tauri::command] +pub fn proxy_get_server_info(state: tauri::State<'_, Arc>) -> ProxyServerInfo { + crate::services::proxy::proxy_get_server_info(state.inner()) +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/settings.rs b/crates/agent-gui/src-tauri/src/tauri_commands/settings.rs new file mode 100644 index 000000000..a9d55cf76 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/settings.rs @@ -0,0 +1,99 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::settings::*; +use crate::runtime::project_path::project_path_key as normalize_project_path_key; +use crate::services::automation::AutomationScheduler; +use crate::services::gateway::GatewayController; +use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Map, Number, Value}; +use std::{ + collections::{HashMap, HashSet}, + fs, + path::{Path, PathBuf}, + sync::Arc, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; +use uuid::Uuid; + +#[tauri::command] +pub async fn settings_load_all() -> Result { + crate::commands::settings::settings_load_all().await +} + +#[tauri::command] +pub async fn settings_save_providers(payload: Value) -> Result<(), String> { + crate::commands::settings::settings_save_providers(payload).await +} + +#[tauri::command] +pub async fn settings_save_system( + payload: Value, + automation_scheduler: tauri::State<'_, Arc>, +) -> Result<(), String> { + crate::commands::settings::settings_save_system(payload, automation_scheduler.inner()).await +} + +#[tauri::command] +pub async fn settings_save_mcp(payload: Value) -> Result<(), String> { + crate::commands::settings::settings_save_mcp(payload).await +} + +#[tauri::command] +pub async fn settings_save_remote( + payload: Value, + gateway_controller: tauri::State<'_, Arc>, +) -> Result<(), String> { + crate::commands::settings::settings_save_remote(payload, gateway_controller.inner()).await +} + +#[tauri::command] +pub async fn settings_save_memory(payload: Value) -> Result<(), String> { + crate::commands::settings::settings_save_memory(payload).await +} + +#[tauri::command] +pub async fn settings_save_agents(payload: Value) -> Result<(), String> { + crate::commands::settings::settings_save_agents(payload).await +} + +#[tauri::command] +pub async fn settings_save_ssh(payload: Value) -> Result<(), String> { + crate::commands::settings::settings_save_ssh(payload).await +} + +#[tauri::command] +pub async fn settings_apply_ssh_patch(payload: Value) -> Result { + crate::commands::settings::settings_apply_ssh_patch(payload).await +} + +#[tauri::command] +pub async fn settings_reset_ssh_known_host( + host: String, + port: u16, +) -> Result { + crate::commands::settings::settings_reset_ssh_known_host(host, port).await +} + +#[tauri::command] +pub async fn settings_list_cherry_studio_providers() -> Result { + crate::commands::settings::settings_list_cherry_studio_providers().await +} + +#[tauri::command] +pub async fn settings_list_cherry_studio_providers_from_path( + data_path: String, +) -> Result { + crate::commands::settings::settings_list_cherry_studio_providers_from_path(data_path).await +} + +#[tauri::command] +pub async fn settings_list_ccswitch_providers() -> Result { + crate::commands::settings::settings_list_ccswitch_providers().await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs b/crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs new file mode 100644 index 000000000..69e5d8898 --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs @@ -0,0 +1,204 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::sftp::*; +use crate::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; +use std::sync::Arc; + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_list( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: Option, +) -> Result { + crate::commands::sftp::sftp_list( + registry.inner(), + session_id, + project_path_key, + workdir, + side, + path, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_stat( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: Option, +) -> Result { + crate::commands::sftp::sftp_stat( + registry.inner(), + session_id, + project_path_key, + workdir, + side, + path, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_read_text( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + path: String, + offset: Option, + max_bytes: Option, +) -> Result { + crate::commands::sftp::sftp_read_text( + registry.inner(), + session_id, + project_path_key, + path, + offset, + max_bytes, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_write_text( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + path: String, + content: String, + overwrite: Option, + create_parent_dirs: Option, +) -> Result { + crate::commands::sftp::sftp_write_text( + registry.inner(), + session_id, + project_path_key, + path, + content, + overwrite, + create_parent_dirs, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_mkdir( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: String, +) -> Result { + crate::commands::sftp::sftp_mkdir( + registry.inner(), + session_id, + project_path_key, + workdir, + side, + path, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_rename( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + from_path: String, + to_path: String, +) -> Result { + crate::commands::sftp::sftp_rename( + registry.inner(), + session_id, + project_path_key, + workdir, + side, + from_path, + to_path, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_delete( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: String, + recursive: Option, +) -> Result { + crate::commands::sftp::sftp_delete( + registry.inner(), + session_id, + project_path_key, + workdir, + side, + path, + recursive, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn sftp_transfer( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + workdir: String, + direction: String, + source_path: String, + target_path: String, + recursive: Option, + overwrite: Option, +) -> Result { + crate::commands::sftp::sftp_transfer( + registry.inner(), + session_id, + project_path_key, + workdir, + direction, + source_path, + target_path, + recursive, + overwrite, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub fn sftp_cancel_transfer( + registry: tauri::State<'_, Arc>, + session_id: String, + transfer_id: String, +) -> Result<(), String> { + crate::commands::sftp::sftp_cancel_transfer(registry.inner(), session_id, transfer_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn sftp_transfer_status( + registry: tauri::State<'_, Arc>, + session_id: String, + transfer_id: String, +) -> Result { + crate::commands::sftp::sftp_transfer_status(registry.inner(), session_id, transfer_id) +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/shell.rs b/crates/agent-gui/src-tauri/src/tauri_commands/shell.rs new file mode 100644 index 000000000..ca23b819e --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/shell.rs @@ -0,0 +1,44 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::shell::*; +use crate::runtime::shell_runner::{run_shell_script, ShellRunRegistry, ShellRunResponse}; +use serde::Serialize; +use std::sync::Arc; + +#[tauri::command(rename_all = "snake_case")] +pub async fn shell_run( + registry: tauri::State<'_, Arc>, + workdir: String, + command: String, + cwd: Option, + timeout_ms: Option, + max_timeout_ms: Option, + provider_id: Option, + run_id: Option, +) -> Result { + crate::commands::shell::shell_run( + registry.inner(), + workdir, + command, + cwd, + timeout_ms, + max_timeout_ms, + provider_id, + run_id, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub fn runtime_cancel( + registry: tauri::State<'_, Arc>, + run_id: String, +) -> ShellCancelResponse { + crate::commands::shell::runtime_cancel(registry.inner(), run_id) +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs new file mode 100644 index 000000000..b6f5cad6a --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs @@ -0,0 +1,70 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::subagent_store::*; +use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use std::{ + collections::HashSet, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +#[tauri::command] +pub async fn subagent_identity_upsert( + input: SubagentIdentityUpsertInput, +) -> Result { + crate::commands::subagent_store::subagent_identity_upsert(input).await +} + +#[tauri::command] +pub async fn subagent_identity_list( + input: SubagentIdentityListInput, +) -> Result, String> { + crate::commands::subagent_store::subagent_identity_list(input).await +} + +#[tauri::command] +pub async fn subagent_run_save(input: SubagentRunSaveInput) -> Result<(), String> { + crate::commands::subagent_store::subagent_run_save(input).await +} + +#[tauri::command] +pub async fn subagent_run_list( + input: SubagentRunListInput, +) -> Result, String> { + crate::commands::subagent_store::subagent_run_list(input).await +} + +#[tauri::command] +pub async fn subagent_run_load( + input: SubagentRunLoadInput, +) -> Result, String> { + crate::commands::subagent_store::subagent_run_load(input).await +} + +#[tauri::command] +pub async fn subagent_run_prune( + input: SubagentRunPruneInput, +) -> Result { + crate::commands::subagent_store::subagent_run_prune(input).await +} + +#[tauri::command] +pub async fn subagent_message_append( + input: SubagentMessageAppendInput, +) -> Result { + crate::commands::subagent_store::subagent_message_append(input).await +} + +#[tauri::command] +pub async fn subagent_message_list( + input: SubagentMessageListInput, +) -> Result, String> { + crate::commands::subagent_store::subagent_message_list(input).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs new file mode 100644 index 000000000..c1698e58e --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs @@ -0,0 +1,47 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::subagent_worktree::*; +use crate::runtime::process::configure_child_process_group; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeSet; +use std::fs; +use std::io::Write; +use std::path::{Component, Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; +use uuid::Uuid; + +#[tauri::command] +pub async fn subagent_worktree_create( + input: SubagentWorktreeCreateInput, +) -> Result { + crate::commands::subagent_worktree::subagent_worktree_create(input).await +} + +#[tauri::command] +pub async fn subagent_worktree_status( + input: SubagentWorktreeStatusInput, +) -> Result { + crate::commands::subagent_worktree::subagent_worktree_status(input).await +} + +#[tauri::command] +pub async fn subagent_worktree_apply( + input: SubagentWorktreeApplyInput, +) -> Result { + crate::commands::subagent_worktree::subagent_worktree_apply(input).await +} + +#[tauri::command] +pub async fn subagent_worktree_cleanup( + input: SubagentWorktreeCleanupInput, +) -> Result { + crate::commands::subagent_worktree::subagent_worktree_cleanup(input).await +} diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs b/crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs new file mode 100644 index 000000000..9103ac0ca --- /dev/null +++ b/crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs @@ -0,0 +1,293 @@ +//! 由 #[tauri::command] 拆分出来的薄包装。 +//! +//! 实现在 agent-core,本文件只做「Tauri IPC → 普通函数调用」这一件事。 +//! 属性逐命令沿用原状(含 rename_all)——前端现在就在按这些名字传参, +//! 统一风格等于 177 次破坏前端的机会。 + +#![allow(unused_imports)] + +use crate::commands::terminal::*; +use crate::runtime::sftp::SftpSessionRegistry; +use crate::runtime::shell_runner::ShellRunRegistry; +use crate::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; +use std::sync::Arc; + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_shell_options() -> TerminalShellOptionsResponse { + crate::commands::terminal::terminal_shell_options() +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_list( + registry: tauri::State<'_, Arc>, + project_path_key: Option, +) -> TerminalListResponse { + crate::commands::terminal::terminal_list(registry.inner(), project_path_key) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_create( + registry: tauri::State<'_, Arc>, + cwd: String, + project_path_key: Option, + shell: Option, + title: Option, + cols: Option, + rows: Option, +) -> Result { + crate::commands::terminal::terminal_create( + registry.inner(), + cwd, + project_path_key, + shell, + title, + cols, + rows, + ) +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_create_ssh( + registry: tauri::State<'_, Arc>, + cwd: String, + project_path_key: Option, + ssh_host_id: String, + title: Option, + cols: Option, + rows: Option, + sftp_enabled: Option, +) -> Result { + crate::commands::terminal::terminal_create_ssh( + registry.inner(), + cwd, + project_path_key, + ssh_host_id, + title, + cols, + rows, + sftp_enabled, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_answer_ssh_prompt( + registry: tauri::State<'_, Arc>, + prompt_id: String, + prompt_answer: Option, + trust_host_key: Option, +) -> Result { + crate::commands::terminal::terminal_answer_ssh_prompt( + registry.inner(), + prompt_id, + prompt_answer, + trust_host_key, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_cancel_ssh_prompt( + registry: tauri::State<'_, Arc>, + prompt_id: String, +) -> Result<(), String> { + crate::commands::terminal::terminal_cancel_ssh_prompt(registry.inner(), prompt_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_ssh_reconnect( + registry: tauri::State<'_, Arc>, + session_id: String, +) -> Result { + crate::commands::terminal::terminal_ssh_reconnect(registry.inner(), session_id).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_ssh_latency( + registry: tauri::State<'_, Arc>, + session_id: String, +) -> Result { + crate::commands::terminal::terminal_ssh_latency(registry.inner(), session_id).await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_ssh_exec( + registry: tauri::State<'_, Arc>, + run_registry: tauri::State<'_, Arc>, + session_id: String, + command: String, + cwd: Option, + timeout_ms: Option, + max_bytes: Option, + run_id: Option, +) -> Result { + crate::commands::terminal::terminal_ssh_exec( + registry.inner(), + run_registry.inner(), + session_id, + command, + cwd, + timeout_ms, + max_bytes, + run_id, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_ssh_local_forward_start( + registry: tauri::State<'_, Arc>, + session_id: String, + project_path_key: Option, + remote_host: String, + remote_port: u32, + local_port: Option, +) -> Result { + crate::commands::terminal::terminal_ssh_local_forward_start( + registry.inner(), + session_id, + project_path_key, + remote_host, + remote_port, + local_port, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_ssh_local_forward_list( + registry: tauri::State<'_, Arc>, + session_id: Option, + project_path_key: Option, +) -> Result { + crate::commands::terminal::terminal_ssh_local_forward_list( + registry.inner(), + session_id, + project_path_key, + ) +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_ssh_local_forward_stop( + registry: tauri::State<'_, Arc>, + forward_id: String, + session_id: Option, +) -> Result { + crate::commands::terminal::terminal_ssh_local_forward_stop( + registry.inner(), + forward_id, + session_id, + ) + .await +} + +#[tauri::command(rename_all = "snake_case")] +pub async fn terminal_ssh_local_forward_check_port(local_port: u32) -> Result { + crate::commands::terminal::terminal_ssh_local_forward_check_port(local_port).await +} + +#[tauri::command(rename_all = "snake_case")] +pub fn ssh_terminal_tabs_list( + registry: tauri::State<'_, Arc>, + project_path_key: String, +) -> Result { + crate::commands::terminal::ssh_terminal_tabs_list(registry.inner(), project_path_key) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn ssh_terminal_tab_open( + registry: tauri::State<'_, Arc>, + session_id: String, + kind: String, +) -> Result { + crate::commands::terminal::ssh_terminal_tab_open(registry.inner(), session_id, kind) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn ssh_terminal_tab_close( + registry: tauri::State<'_, Arc>, + tab_id: String, +) -> Result { + crate::commands::terminal::ssh_terminal_tab_close(registry.inner(), tab_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_stream_attach( + registry: tauri::State<'_, Arc>, + session_id: String, + max_bytes: Option, +) -> Result { + crate::commands::terminal::terminal_stream_attach(registry.inner(), session_id, max_bytes) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_stream_input( + registry: tauri::State<'_, Arc>, + session_id: String, + bytes: Vec, +) -> Result<(), String> { + crate::commands::terminal::terminal_stream_input(registry.inner(), session_id, bytes) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_stream_resize( + registry: tauri::State<'_, Arc>, + session_id: String, + cols: u16, + rows: u16, +) -> Result<(), String> { + crate::commands::terminal::terminal_stream_resize(registry.inner(), session_id, cols, rows) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_rename( + registry: tauri::State<'_, Arc>, + session_id: String, + title: String, +) -> Result { + crate::commands::terminal::terminal_rename(registry.inner(), session_id, title) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_close( + registry: tauri::State<'_, Arc>, + sftp_registry: tauri::State<'_, Arc>, + session_id: String, +) -> Result { + crate::commands::terminal::terminal_close(registry.inner(), sftp_registry.inner(), session_id) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_close_project( + registry: tauri::State<'_, Arc>, + sftp_registry: tauri::State<'_, Arc>, + project_path_key: String, +) -> Result { + crate::commands::terminal::terminal_close_project( + registry.inner(), + sftp_registry.inner(), + project_path_key, + ) +} + +#[tauri::command(rename_all = "snake_case")] +pub fn terminal_read_tail( + registry: tauri::State<'_, Arc>, + project_path_key: String, + session_id: Option, + max_bytes: Option, +) -> Result { + crate::commands::terminal::terminal_read_tail( + registry.inner(), + project_path_key, + session_id, + max_bytes, + ) +} From b627ca2e81f505716927af217fe1ccab091ad96e Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 06:08:38 -0700 Subject: [PATCH 09/75] style(backend): drop imports and borrows the command split made dead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use tauri::State 5 处已无引用;State 参数变成 &Arc 后 Arc::clone(&state) 多了一层借用。clippy 相对基线零新增。 --- .../src/commands/integration/memory.rs | 46 +++++++++---------- .../src-tauri/src/commands/runtime/process.rs | 2 - .../src-tauri/src/commands/runtime/sftp.rs | 2 - .../src-tauri/src/commands/runtime/shell.rs | 1 - .../src/commands/runtime/terminal.rs | 2 - 5 files changed, 22 insertions(+), 31 deletions(-) diff --git a/crates/agent-gui/src-tauri/src/commands/integration/memory.rs b/crates/agent-gui/src-tauri/src/commands/integration/memory.rs index 0a5721876..16dc0a8a0 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/memory.rs +++ b/crates/agent-gui/src-tauri/src/commands/integration/memory.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use tauri::State; - use crate::{ commands::chat_history, services::memory::{ @@ -21,7 +19,7 @@ pub async fn memory_list( state: &Arc, args: MemoryListArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.list(args)) .await .map_err(|e| format!("memory_list join 失败:{e}"))? @@ -31,7 +29,7 @@ pub async fn memory_read( state: &Arc, args: MemoryReadArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.read(args)) .await .map_err(|e| format!("memory_read join 失败:{e}"))? @@ -41,7 +39,7 @@ pub async fn memory_search( state: &Arc, args: MemorySearchArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || { let history_args = args.clone(); let mut response = store.search(args)?; @@ -57,7 +55,7 @@ pub async fn memory_write( state: &Arc, args: MemoryWriteArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.write(args)) .await .map_err(|e| format!("memory_write join 失败:{e}"))? @@ -67,7 +65,7 @@ pub async fn memory_update( state: &Arc, args: MemoryUpdateArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.update(args)) .await .map_err(|e| format!("memory_update join 失败:{e}"))? @@ -77,7 +75,7 @@ pub async fn memory_delete( state: &Arc, args: MemoryDeleteArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.delete(args)) .await .map_err(|e| format!("memory_delete join 失败:{e}"))? @@ -87,7 +85,7 @@ pub async fn memory_delete_project( state: &Arc, args: MemoryDeleteProjectArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.delete_project(args)) .await .map_err(|e| format!("memory_delete_project join 失败:{e}"))? @@ -97,7 +95,7 @@ pub async fn memory_accept( state: &Arc, args: MemoryAcceptArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.accept(args)) .await .map_err(|e| format!("memory_accept join 失败:{e}"))? @@ -107,7 +105,7 @@ pub async fn memory_apply_batch( state: &Arc, args: MemoryBatchArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.apply_batch(args)) .await .map_err(|e| format!("memory_apply_batch join 失败:{e}"))? @@ -117,7 +115,7 @@ pub async fn memory_organize_run_create( state: &Arc, args: MemoryOrganizeRunCreateArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.organize_run_create(args)) .await .map_err(|e| format!("memory_organize_run_create join 失败:{e}"))? @@ -127,7 +125,7 @@ pub async fn memory_organize_run_update( state: &Arc, args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.organize_run_update(args)) .await .map_err(|e| format!("memory_organize_run_update join 失败:{e}"))? @@ -137,7 +135,7 @@ pub async fn memory_organize_run_list( state: &Arc, args: Option, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); let resolved = args.unwrap_or_default(); tokio::task::spawn_blocking(move || store.organize_run_list(resolved)) .await @@ -148,7 +146,7 @@ pub async fn memory_organize_run_read( state: &Arc, args: MemoryOrganizeRunReadArgs, ) -> Result, String> { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.organize_run_read(args)) .await .map_err(|e| format!("memory_organize_run_read join 失败:{e}"))? @@ -157,7 +155,7 @@ pub async fn memory_organize_run_read( pub async fn memory_organize_run_clear_history( state: &Arc, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.organize_run_clear_history()) .await .map_err(|e| format!("memory_organize_run_clear_history join 失败:{e}"))? @@ -167,7 +165,7 @@ pub async fn memory_organize_due_claim( state: &Arc, args: MemoryOrganizeDueClaimArgs, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.organize_due_claim(args)) .await .map_err(|e| format!("memory_organize_due_claim join 失败:{e}"))? @@ -177,7 +175,7 @@ pub async fn memory_organize_due_complete( state: &Arc, args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.organize_due_complete(args)) .await .map_err(|e| format!("memory_organize_due_complete join 失败:{e}"))? @@ -187,14 +185,14 @@ pub async fn memory_index_overview( state: &Arc, workdir: Option, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.overview(workdir)) .await .map_err(|e| format!("memory_index_overview join 失败:{e}"))? } pub async fn memory_paths_info(state: &Arc) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.paths_info()) .await .map_err(|e| format!("memory_paths_info join 失败:{e}"))? @@ -204,7 +202,7 @@ pub async fn memory_recent_rejections( state: &Arc, args: Option, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); let resolved = args.unwrap_or_default(); tokio::task::spawn_blocking(move || store.recent_rejections(resolved)) .await @@ -222,7 +220,7 @@ pub async fn memory_today_daily( state: &Arc, rollover_hour: Option, ) -> Result, String> { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.today_daily(rollover_hour)) .await .map_err(|e| format!("memory_today_daily join 失败:{e}"))? @@ -232,7 +230,7 @@ pub async fn memory_quota_summary( state: &Arc, args: Option, ) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); let resolved = args.unwrap_or_default(); tokio::task::spawn_blocking(move || store.quota_summary(resolved)) .await @@ -240,7 +238,7 @@ pub async fn memory_quota_summary( } pub async fn memory_wipe_all(state: &Arc) -> Result { - let store = Arc::clone(&state); + let store = Arc::clone(state); tokio::task::spawn_blocking(move || store.wipe_all()) .await .map_err(|e| format!("memory_wipe_all join 失败:{e}"))? diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/process.rs b/crates/agent-gui/src-tauri/src/commands/runtime/process.rs index 73ea91d4d..88f12bfea 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/process.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/process.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use tauri::State; - use crate::runtime::managed_process::{ ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs b/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs index a629dc815..4604b92e7 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use tauri::State; - use crate::runtime::sftp::{ SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, SftpStatResponse, SftpTransferResponse, diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs b/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs index bc55f2db1..b6de1ed07 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use serde::Serialize; -use tauri::State; use crate::runtime::shell_runner::{run_shell_script, ShellRunRegistry, ShellRunResponse}; diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs b/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs index bbd67c25e..5f7234988 100644 --- a/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs +++ b/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use tauri::State; - use crate::runtime::sftp::SftpSessionRegistry; use crate::runtime::shell_runner::ShellRunRegistry; use crate::runtime::terminal::{ From ed7fc88ae27a8d90a81df5fec064e147935e3be6 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 06:23:07 -0700 Subject: [PATCH 10/75] refactor(backend): move 69,440 lines of backend into agent-core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runtime/ + services/(除 gateway/gateway_bridge/tunnel/tray/proxy) + commands/(除 app/、integration/gateway.rs) 一次搬完。 ## 为什么是一次搬完,不是 CSV 里写的三步 P2-16/17/18 假设 runtime → services → commands 是有序依赖。不是。runtime/ 反向 依赖上层: runtime/terminal/{state,ssh_connect,ssh_session,ssh_auth}.rs → commands::settings runtime/{task_runner,shell_runner}.rs → services::system_proxy runtime/managed_process_journal.rs → services::automation::db 三者互相引用,不存在无环的拆分顺序。分步搬只能靠临时垫片,下一步再删掉。 一次搬完反而让所有 crate:: 内部路径原样有效——133 个文件,零路径改写。 ## 代价只有 18 个编译错误 70k 行搬完后 agent-core 只报 18 个错,分五类,都已修: 1. include_str! 指向 prompt/ —— prompt/ 是后端技能内容,跟着 skills 一起搬 2. app_version() 原先靠 src-tauri 的 build.rs 塞 env!。agent-core 编译时不知道 自己会被装进哪个产物,改成 set_app_version() 由宿主注入,未注入返回 0.0.0-unknown 而不是 panic——版本号只用于自报家门,不该让后端起不来 3. validate_project_folder_name 是纯路径校验,却躺在前端专属的 app/system.rs 里, 移进 runtime/project_path.rs 4. settings_save_remote 是最后一处 GatewayController 耦合,按 P2-04~P2-08 同款 模式切断:发 settings:remote-saved,GatewayEventSink 接住去调 apply_config 5. quick_xml 漏了依赖 壳侧 61 个符号需要 pub(crate) → pub(文档估 29)。全部由编译器 E0603 点名后提升, 没有全局提升——pub(crate) 在 crate 内依然有效,只有真正跨界的才该动。 ssh_local_forward 的两个函数是 glob 再导出,改成逐个点名而不是把整个 glob 提成 pub。 ## 验证 agent-core 69,440 行,cargo tree 零 tauri src-tauri 22,480 行(原 92k) agent-core 里 GatewayController 引用 0 cargo test --workspace 670 通过(541 + 129),与搬迁前逐个对齐 234 个命令名、rename_all、JSON key 与阶段起点 8c90a424 逐字一致 --- Cargo.lock | 28 +++++++ crates/agent-core/Cargo.toml | 30 +++++++ .../skills/liveagent-code-review/SKILL.md | 0 .../prompt/skills/skills-creator/SKILL.md | 0 .../references/agent-skill-format.md | 0 .../references/authoring-patterns.md | 0 .../prompt/skills/skills-installer/SKILL.md | 0 .../references/install-sources.md | 0 .../references/safety-and-conflicts.md | 0 .../src/commands/automation/cron.rs | 0 .../src/commands/automation/hook.rs | 0 .../src/commands/automation/mod.rs | 0 .../src/commands/config/mod.rs | 0 .../src/commands/config/settings/agents.rs | 0 .../commands/config/settings/ccs_import.rs | 0 .../commands/config/settings/cherry_import.rs | 0 .../src/commands/config/settings/commands.rs | 16 +++- .../src/commands/config/settings/db.rs | 4 +- .../commands/config/settings/gateway_sync.rs | 4 +- .../src/commands/config/settings/json.rs | 0 .../src/commands/config/settings/mcp.rs | 0 .../config/settings/memory_settings.rs | 0 .../src/commands/config/settings/mod.rs | 20 +++-- .../src/commands/config/settings/providers.rs | 2 +- .../src/commands/config/settings/remote.rs | 6 +- .../config/settings/ssh/associations.rs | 0 .../src/commands/config/settings/ssh/load.rs | 2 +- .../src/commands/config/settings/ssh/mod.rs | 0 .../src/commands/config/settings/ssh/patch.rs | 2 +- .../config/settings/ssh/persistence.rs | 0 .../config/settings/ssh/validation.rs | 0 .../src/commands/config/settings/system.rs | 0 .../src/commands/config/settings/tests.rs | 0 .../src/commands/config/settings/types.rs | 0 .../commands/history/chat_history/branch.rs | 2 +- .../commands/history/chat_history/commands.rs | 14 ++-- .../src/commands/history/chat_history/db.rs | 0 .../commands/history/chat_history/delete.rs | 2 +- .../src/commands/history/chat_history/fts.rs | 0 .../history/chat_history/message_ref.rs | 12 +-- .../src/commands/history/chat_history/mod.rs | 2 +- .../commands/history/chat_history/replace.rs | 0 .../history/chat_history/repository.rs | 2 +- .../commands/history/chat_history/search.rs | 2 +- .../commands/history/chat_history/segments.rs | 0 .../commands/history/chat_history/share.rs | 0 .../commands/history/chat_history/tests.rs | 0 .../commands/history/chat_history/types.rs | 0 .../src/commands/history/history_db.rs | 2 +- .../src/commands/history/mod.rs | 0 .../src/commands/history/subagent_store.rs | 0 .../src/commands/integration/mcp.rs | 0 .../src/commands/integration/memory.rs | 0 .../src/commands/integration/mod.rs | 1 - crates/agent-core/src/commands/mod.rs | 34 ++++++++ .../src/commands/runtime/mod.rs | 0 .../src/commands/runtime/process.rs | 0 .../src/commands/runtime/sftp.rs | 0 .../src/commands/runtime/shell.rs | 0 .../src/commands/runtime/terminal.rs | 0 .../src/commands/workspace/chat_file_links.rs | 2 +- .../src/commands/workspace/edit_match.rs | 0 .../src/commands/workspace/fs.rs | 21 +++-- .../src/commands/workspace/git.rs | 4 +- .../src/commands/workspace/mod.rs | 0 .../commands/workspace/subagent_worktree.rs | 0 crates/agent-core/src/lib.rs | 31 +++++++ .../src/runtime/managed_process.rs | 4 +- .../src/runtime/managed_process_journal.rs | 0 .../src/runtime/mod.rs | 0 .../src/runtime/platform.rs | 9 +-- .../src/runtime/process.rs | 10 +-- .../src/runtime/project_path.rs | 46 +++++++++++ .../src/runtime/sftp.rs | 2 +- .../src/runtime/shell_runner.rs | 10 +-- .../src/runtime/task_runner.rs | 6 +- .../src/runtime/terminal/events.rs | 0 .../src/runtime/terminal/mod.rs | 7 +- .../src/runtime/terminal/output.rs | 0 .../src/runtime/terminal/registry.rs | 2 +- .../src/runtime/terminal/shell.rs | 0 .../src/runtime/terminal/ssh_auth.rs | 0 .../src/runtime/terminal/ssh_channel.rs | 0 .../src/runtime/terminal/ssh_connect.rs | 0 .../src/runtime/terminal/ssh_io.rs | 0 .../src/runtime/terminal/ssh_local_forward.rs | 4 +- .../src/runtime/terminal/ssh_session.rs | 0 .../src/runtime/terminal/state.rs | 0 .../src/runtime/terminal/tabs.rs | 0 .../src/runtime/terminal/tests.rs | 0 .../src/runtime/terminal/types.rs | 0 .../src/runtime/terminal/util.rs | 0 .../src/services/automation/db.rs | 0 .../src/services/automation/mod.rs | 0 .../src/services/automation/scheduler.rs | 0 .../src/services/automation/store.rs | 2 +- .../src/services/automation/tests.rs | 0 .../src/services/automation/types.rs | 0 .../src/services/automation/validate.rs | 0 .../src/services/chat_run_ledger.rs | 0 .../src/services/memory/content.rs | 0 .../src/services/memory/daily.rs | 0 .../src/services/memory/maintenance.rs | 0 .../src/services/memory/mod.rs | 0 .../src/services/memory/mutations/evidence.rs | 0 .../src/services/memory/mutations/methods.rs | 0 .../src/services/memory/mutations/mod.rs | 0 .../services/memory/mutations/normalize.rs | 0 .../src/services/memory/mutations/risk.rs | 0 .../services/memory/mutations/write_entry.rs | 0 .../src/services/memory/organize.rs | 0 .../src/services/memory/paths.rs | 0 .../src/services/memory/schema.rs | 0 .../src/services/memory/search.rs | 0 .../src/services/memory/store.rs | 0 .../src/services/memory/tests.rs | 0 .../src/services/memory/types.rs | 0 crates/agent-core/src/services/mod.rs | 9 +++ .../src/services/power_activity.rs | 0 .../src/services/provider_models.rs | 0 .../src/services/provider_usage.rs | 0 .../src/services/skills/builtin.rs | 0 .../src/services/skills/clawhub.rs | 0 .../src/services/skills/create.rs | 0 .../src/services/skills/external.rs | 0 .../src/services/skills/external_mcp.rs | 0 .../src/services/skills/install.rs | 0 .../src/services/skills/jobs.rs | 0 .../src/services/skills/library.rs | 0 .../src/services/skills/manager.rs | 0 .../src/services/skills/metadata.rs | 0 .../src/services/skills/mod.rs | 0 .../src/services/skills/paths.rs | 0 .../src/services/skills/sources.rs | 0 .../src/services/skills/tests.rs | 0 .../src/services/skills/types.rs | 0 .../src/services/skills/util.rs | 0 .../src/services/skills/validate.rs | 0 .../src/services/system_proxy.rs | 0 .../src/services/workspace_watch/emit.rs | 0 .../src/services/workspace_watch/mod.rs | 2 +- .../src/services/workspace_watch/watcher.rs | 0 .../src-tauri/src/commands/app/app.rs | 2 +- .../src-tauri/src/commands/app/system.rs | 67 ++++----------- .../src-tauri/src/commands/app/update.rs | 12 +-- .../src/commands/integration/gateway.rs | 8 +- .../agent-gui/src-tauri/src/commands/mod.rs | 41 ++-------- .../agent-gui/src-tauri/src/gateway_sink.rs | 23 ++++-- crates/agent-gui/src-tauri/src/lib.rs | 81 ++++++++++++------- .../src-tauri/src/services/gateway/chat.rs | 2 +- .../src/services/gateway/chat_inbox.rs | 2 +- .../src/services/gateway/connection.rs | 4 +- .../src/services/gateway/controller.rs | 22 ++--- .../src/services/gateway/envelope_handler.rs | 8 +- .../src/services/gateway/history_sync.rs | 2 +- .../src/services/gateway/managed_process.rs | 2 +- .../src-tauri/src/services/gateway/mod.rs | 20 ++--- .../src/services/gateway/settings_sync.rs | 2 +- .../src-tauri/src/services/gateway/sftp.rs | 2 +- .../src/services/gateway/terminal.rs | 6 +- .../src-tauri/src/services/gateway/tests.rs | 4 +- .../src-tauri/src/services/gateway/util.rs | 2 +- .../src-tauri/src/services/gateway_bridge.rs | 27 ++++--- .../agent-gui/src-tauri/src/services/mod.rs | 16 ++-- .../agent-gui/src-tauri/src/services/proxy.rs | 4 +- .../src-tauri/src/services/tunnel/store.rs | 4 +- .../src/tauri_commands/chat_file_links.rs | 6 +- .../src/tauri_commands/chat_history.rs | 48 ++++++----- .../src-tauri/src/tauri_commands/cron.rs | 24 +++--- .../src-tauri/src/tauri_commands/fs.rs | 38 ++++----- .../src-tauri/src/tauri_commands/git.rs | 72 ++++++++--------- .../src-tauri/src/tauri_commands/hook.rs | 14 ++-- .../src-tauri/src/tauri_commands/mcp.rs | 23 +++--- .../src-tauri/src/tauri_commands/memory.rs | 73 +++++++++-------- .../src-tauri/src/tauri_commands/process.rs | 16 ++-- .../src-tauri/src/tauri_commands/settings.rs | 40 ++++----- .../src-tauri/src/tauri_commands/sftp.rs | 24 +++--- .../src-tauri/src/tauri_commands/shell.rs | 8 +- .../src/tauri_commands/subagent_store.rs | 18 ++--- .../src/tauri_commands/subagent_worktree.rs | 12 +-- .../src-tauri/src/tauri_commands/terminal.rs | 58 ++++++------- 181 files changed, 683 insertions(+), 512 deletions(-) rename crates/{agent-gui/src-tauri => agent-core}/prompt/skills/liveagent-code-review/SKILL.md (100%) rename crates/{agent-gui/src-tauri => agent-core}/prompt/skills/skills-creator/SKILL.md (100%) rename crates/{agent-gui/src-tauri => agent-core}/prompt/skills/skills-creator/references/agent-skill-format.md (100%) rename crates/{agent-gui/src-tauri => agent-core}/prompt/skills/skills-creator/references/authoring-patterns.md (100%) rename crates/{agent-gui/src-tauri => agent-core}/prompt/skills/skills-installer/SKILL.md (100%) rename crates/{agent-gui/src-tauri => agent-core}/prompt/skills/skills-installer/references/install-sources.md (100%) rename crates/{agent-gui/src-tauri => agent-core}/prompt/skills/skills-installer/references/safety-and-conflicts.md (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/automation/cron.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/automation/hook.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/automation/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/agents.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/ccs_import.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/cherry_import.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/commands.rs (83%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/db.rs (97%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/gateway_sync.rs (97%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/json.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/mcp.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/memory_settings.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/mod.rs (87%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/providers.rs (98%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/remote.rs (96%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/ssh/associations.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/ssh/load.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/ssh/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/ssh/patch.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/ssh/persistence.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/ssh/validation.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/system.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/tests.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/config/settings/types.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/branch.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/commands.rs (97%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/db.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/delete.rs (96%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/fts.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/message_ref.rs (98%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/mod.rs (97%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/replace.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/repository.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/search.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/segments.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/share.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/tests.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/chat_history/types.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/history_db.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/history/subagent_store.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/integration/mcp.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/integration/memory.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/integration/mod.rs (63%) create mode 100644 crates/agent-core/src/commands/mod.rs rename crates/{agent-gui/src-tauri => agent-core}/src/commands/runtime/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/runtime/process.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/runtime/sftp.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/runtime/shell.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/runtime/terminal.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/workspace/chat_file_links.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/workspace/edit_match.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/workspace/fs.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/workspace/git.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/workspace/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/commands/workspace/subagent_worktree.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/managed_process.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/managed_process_journal.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/platform.rs (95%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/process.rs (95%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/project_path.rs (77%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/sftp.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/shell_runner.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/task_runner.rs (96%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/events.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/mod.rs (92%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/output.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/registry.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/shell.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/ssh_auth.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/ssh_channel.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/ssh_connect.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/ssh_io.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/ssh_local_forward.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/ssh_session.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/state.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/tabs.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/tests.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/types.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/runtime/terminal/util.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/automation/db.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/automation/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/automation/scheduler.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/automation/store.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/automation/tests.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/automation/types.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/automation/validate.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/chat_run_ledger.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/content.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/daily.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/maintenance.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/mutations/evidence.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/mutations/methods.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/mutations/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/mutations/normalize.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/mutations/risk.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/mutations/write_entry.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/organize.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/paths.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/schema.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/search.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/store.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/tests.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/memory/types.rs (100%) create mode 100644 crates/agent-core/src/services/mod.rs rename crates/{agent-gui/src-tauri => agent-core}/src/services/power_activity.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/provider_models.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/provider_usage.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/builtin.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/clawhub.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/create.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/external.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/external_mcp.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/install.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/jobs.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/library.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/manager.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/metadata.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/mod.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/paths.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/sources.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/tests.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/types.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/util.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/skills/validate.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/system_proxy.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/workspace_watch/emit.rs (100%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/workspace_watch/mod.rs (99%) rename crates/{agent-gui/src-tauri => agent-core}/src/services/workspace_watch/watcher.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index a40c46fa0..a3f81025b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,9 +49,37 @@ dependencies = [ name = "agent-core" version = "0.1.0" dependencies = [ + "base64 0.22.1", + "chrono", + "dirs", + "futures-util", + "globset", + "ignore", + "leveldb-core", + "lopdf", + "notify", + "percent-encoding", + "portable-pty", + "quick-xml 0.41.0", + "regex", + "reqwest", + "rquickjs", + "rusqlite", + "russh", + "russh-sftp", "serde", "serde_json", + "sha2 0.11.0", + "tempfile", + "thiserror 2.0.18", "tokio", + "tokio-cron-scheduler", + "toml 0.9.12+spec-1.1.0", + "uuid", + "wait-timeout", + "walkdir", + "windows-sys 0.61.2", + "zip 8.6.0", ] [[package]] diff --git a/crates/agent-core/Cargo.toml b/crates/agent-core/Cargo.toml index 9748bba39..e86b9f622 100644 --- a/crates/agent-core/Cargo.toml +++ b/crates/agent-core/Cargo.toml @@ -9,6 +9,36 @@ description = "LiveAgent backend core: tools, storage and runtime. Deliberately # 一旦有人把前端专属 command 挪进来,cargo build -p agent-core 直接失败, # 而不是等到 headless 模式跑不起来才发现。 [dependencies] +base64 = "0.22.1" +chrono = "0.4.45" +dirs = "6.0.0" +futures-util = "0.3.32" +globset = "0.4.18" +ignore = "0.4.27" +leveldb-core = "0.1.0" +lopdf = "0.43.0" +notify = "8.2.0" +percent-encoding = "2.3.2" +portable-pty = "0.9.0" +quick-xml = "0.41.0" +regex = "1.12.4" +reqwest = { version = "0.13.4", features = ["blocking", "json", "stream", "socks"] } +rquickjs = { version = "0.8", features = ["array-buffer", "classes", "bindgen"] } +rusqlite = { version = "0.40.1", features = ["bundled"] } +russh = "0.62.2" +russh-sftp = "2.3.0" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.150" +sha2 = "0.11.0" +tempfile = "3.27.0" +thiserror = "2.0.18" tokio = { version = "1.52.3", features = ["macros", "net", "sync", "time", "io-util", "rt", "rt-multi-thread", "process"] } +tokio-cron-scheduler = "0.15.1" +toml = "0.9.11" +uuid = { version = "1.23.4", features = ["v4"] } +wait-timeout = "0.2.1" +walkdir = "2.5.0" +zip = { version = "8.6.0", default-features = false, features = ["deflate"] } + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_Threading"] } diff --git a/crates/agent-gui/src-tauri/prompt/skills/liveagent-code-review/SKILL.md b/crates/agent-core/prompt/skills/liveagent-code-review/SKILL.md similarity index 100% rename from crates/agent-gui/src-tauri/prompt/skills/liveagent-code-review/SKILL.md rename to crates/agent-core/prompt/skills/liveagent-code-review/SKILL.md diff --git a/crates/agent-gui/src-tauri/prompt/skills/skills-creator/SKILL.md b/crates/agent-core/prompt/skills/skills-creator/SKILL.md similarity index 100% rename from crates/agent-gui/src-tauri/prompt/skills/skills-creator/SKILL.md rename to crates/agent-core/prompt/skills/skills-creator/SKILL.md diff --git a/crates/agent-gui/src-tauri/prompt/skills/skills-creator/references/agent-skill-format.md b/crates/agent-core/prompt/skills/skills-creator/references/agent-skill-format.md similarity index 100% rename from crates/agent-gui/src-tauri/prompt/skills/skills-creator/references/agent-skill-format.md rename to crates/agent-core/prompt/skills/skills-creator/references/agent-skill-format.md diff --git a/crates/agent-gui/src-tauri/prompt/skills/skills-creator/references/authoring-patterns.md b/crates/agent-core/prompt/skills/skills-creator/references/authoring-patterns.md similarity index 100% rename from crates/agent-gui/src-tauri/prompt/skills/skills-creator/references/authoring-patterns.md rename to crates/agent-core/prompt/skills/skills-creator/references/authoring-patterns.md diff --git a/crates/agent-gui/src-tauri/prompt/skills/skills-installer/SKILL.md b/crates/agent-core/prompt/skills/skills-installer/SKILL.md similarity index 100% rename from crates/agent-gui/src-tauri/prompt/skills/skills-installer/SKILL.md rename to crates/agent-core/prompt/skills/skills-installer/SKILL.md diff --git a/crates/agent-gui/src-tauri/prompt/skills/skills-installer/references/install-sources.md b/crates/agent-core/prompt/skills/skills-installer/references/install-sources.md similarity index 100% rename from crates/agent-gui/src-tauri/prompt/skills/skills-installer/references/install-sources.md rename to crates/agent-core/prompt/skills/skills-installer/references/install-sources.md diff --git a/crates/agent-gui/src-tauri/prompt/skills/skills-installer/references/safety-and-conflicts.md b/crates/agent-core/prompt/skills/skills-installer/references/safety-and-conflicts.md similarity index 100% rename from crates/agent-gui/src-tauri/prompt/skills/skills-installer/references/safety-and-conflicts.md rename to crates/agent-core/prompt/skills/skills-installer/references/safety-and-conflicts.md diff --git a/crates/agent-gui/src-tauri/src/commands/automation/cron.rs b/crates/agent-core/src/commands/automation/cron.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/automation/cron.rs rename to crates/agent-core/src/commands/automation/cron.rs diff --git a/crates/agent-gui/src-tauri/src/commands/automation/hook.rs b/crates/agent-core/src/commands/automation/hook.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/automation/hook.rs rename to crates/agent-core/src/commands/automation/hook.rs diff --git a/crates/agent-gui/src-tauri/src/commands/automation/mod.rs b/crates/agent-core/src/commands/automation/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/automation/mod.rs rename to crates/agent-core/src/commands/automation/mod.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/mod.rs b/crates/agent-core/src/commands/config/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/mod.rs rename to crates/agent-core/src/commands/config/mod.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/agents.rs b/crates/agent-core/src/commands/config/settings/agents.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/agents.rs rename to crates/agent-core/src/commands/config/settings/agents.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs b/crates/agent-core/src/commands/config/settings/ccs_import.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/ccs_import.rs rename to crates/agent-core/src/commands/config/settings/ccs_import.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs b/crates/agent-core/src/commands/config/settings/cherry_import.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/cherry_import.rs rename to crates/agent-core/src/commands/config/settings/cherry_import.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs b/crates/agent-core/src/commands/config/settings/commands.rs similarity index 83% rename from crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs rename to crates/agent-core/src/commands/config/settings/commands.rs index 7779e9062..cfbd050e3 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/commands.rs +++ b/crates/agent-core/src/commands/config/settings/commands.rs @@ -52,16 +52,24 @@ pub async fn settings_save_mcp(payload: Value) -> Result<(), String> { .map_err(|e| format!("settings_save_mcp join 失败:{e}"))? } -pub async fn settings_save_remote( - payload: Value, - gateway_controller: &Arc) -> Result<(), String> { +/// 保存远程(Gateway)配置。 +/// +/// 保存完只往总线上发一条 `settings:remote-saved`,**不认识 Gateway**。 +/// 谁想在配置变更后做事(当前是 `GatewayEventSink` 调 `apply_config`), +/// 自己去订阅——这是 P2-04~P2-08 一路用下来的同一个模式, +/// 也是这条命令能跟着 settings 一起迁入 agent-core 的唯一原因。 +/// +/// payload 发的是**落库后的归一化结果**而不是入参:订阅者要的是「现在是什么」, +/// 不是「谁请求了什么」。 +pub async fn settings_save_remote(payload: Value, events: &Arc) -> Result<(), String> { let normalized = tokio::task::spawn_blocking(move || { let mut conn = open_db()?; save_remote(&mut conn, payload) }) .await .map_err(|e| format!("settings_save_remote join 失败:{e}"))??; - gateway_controller.apply_config(normalized) + events.emit(SETTINGS_REMOTE_SAVED_EVENT, &normalized); + Ok(()) } pub async fn settings_save_memory(payload: Value) -> Result<(), String> { diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/db.rs b/crates/agent-core/src/commands/config/settings/db.rs similarity index 97% rename from crates/agent-gui/src-tauri/src/commands/config/settings/db.rs rename to crates/agent-core/src/commands/config/settings/db.rs index f9b91a64c..c2cc425f5 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/db.rs +++ b/crates/agent-core/src/commands/config/settings/db.rs @@ -5,7 +5,7 @@ fn now_ms() -> i64 { duration.as_millis() as i64 } -pub(crate) fn config_dir() -> Result { +pub fn config_dir() -> Result { let home = dirs::home_dir().ok_or_else(|| "无法定位用户目录".to_string())?; let dir = home.join(format!(".{}", env!("CARGO_PKG_NAME"))); fs::create_dir_all(&dir).map_err(|e| format!("创建配置目录失败:{e}"))?; @@ -112,7 +112,7 @@ pub(crate) fn config_db_path() -> Result { Ok(config_dir()?.join(DB_FILENAME)) } -pub(crate) fn open_db() -> Result { +pub fn open_db() -> Result { let db_path = config_db_path()?; let mut conn = Connection::open(db_path).map_err(|e| format!("打开设置数据库失败:{e}"))?; conn.busy_timeout(Duration::from_secs(5)) diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/gateway_sync.rs b/crates/agent-core/src/commands/config/settings/gateway_sync.rs similarity index 97% rename from crates/agent-gui/src-tauri/src/commands/config/settings/gateway_sync.rs rename to crates/agent-core/src/commands/config/settings/gateway_sync.rs index eaf853a47..d20ad6ef4 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/gateway_sync.rs +++ b/crates/agent-core/src/commands/config/settings/gateway_sync.rs @@ -1,4 +1,4 @@ -pub(crate) fn load_gateway_settings_sync_snapshot(conn: &Connection) -> Result { +pub fn load_gateway_settings_sync_snapshot(conn: &Connection) -> Result { let default_workdir = default_project_workdir()?; let mut snapshot = Map::new(); snapshot.insert( @@ -76,7 +76,7 @@ fn load_masked_automation_hooks(conn: &Connection) -> Result { serde_json::to_value(&snapshot).map_err(|e| format!("序列化 automation hooks 快照失败:{e}")) } -pub(crate) fn redact_gateway_settings_sync_payload(payload: Value) -> Result { +pub fn redact_gateway_settings_sync_payload(payload: Value) -> Result { let mut snapshot = expect_object(payload, "gateway settings sync payload")?; snapshot.remove(PROVIDER_API_KEY_UPDATES_FIELD); snapshot.remove(PROVIDER_USAGE_QUERY_SECRET_UPDATES_FIELD); diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/json.rs b/crates/agent-core/src/commands/config/settings/json.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/json.rs rename to crates/agent-core/src/commands/config/settings/json.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/mcp.rs b/crates/agent-core/src/commands/config/settings/mcp.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/mcp.rs rename to crates/agent-core/src/commands/config/settings/mcp.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/memory_settings.rs b/crates/agent-core/src/commands/config/settings/memory_settings.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/memory_settings.rs rename to crates/agent-core/src/commands/config/settings/memory_settings.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/mod.rs b/crates/agent-core/src/commands/config/settings/mod.rs similarity index 87% rename from crates/agent-gui/src-tauri/src/commands/config/settings/mod.rs rename to crates/agent-core/src/commands/config/settings/mod.rs index ecf34f264..50f8def85 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/mod.rs +++ b/crates/agent-core/src/commands/config/settings/mod.rs @@ -10,9 +10,16 @@ use std::{ }; use uuid::Uuid; +use crate::events::EventBus; use crate::runtime::project_path::project_path_key as normalize_project_path_key; use crate::services::automation::AutomationScheduler; -use crate::services::gateway::GatewayController; + +/// 远程配置落库后发出,payload 是归一化后的 `RemoteSettingsPayload`。 +/// +/// 订阅者当前只有一个:`src-tauri/gateway_sink.rs`,它拿去调 `apply_config`。 +/// 阶段 4 删掉 Gateway 后这条事件仍然有意义——远程配置变了, +/// 连着的其它客户端也该知道。 +pub const SETTINGS_REMOTE_SAVED_EVENT: &str = "settings:remote-saved"; const DB_FILENAME: &str = "config.sqlite"; const DEFAULT_PROJECT_DIRNAME: &str = "default-project"; @@ -39,12 +46,11 @@ const SYSTEM_ARCHIVED_WORKSPACE_PROJECT_PATHS_KEY: &str = "archivedWorkspaceProj const SYSTEM_SYSTEM_PROXY_KEY: &str = "systemProxy"; const DEFAULT_WORKSPACE_PROJECT_ID: &str = "default-project"; const DEFAULT_WORKSPACE_PROJECT_NAME: &str = "Default Project"; -pub(crate) const PROVIDER_API_KEY_UPDATES_FIELD: &str = "providerApiKeyUpdates"; -pub(crate) const PROVIDER_USAGE_QUERY_SECRET_UPDATES_FIELD: &str = - "providerUsageQuerySecretUpdates"; -pub(crate) const SYSTEM_PROXY_PASSWORD_UPDATE_FIELD: &str = "systemProxyPasswordUpdate"; -pub(crate) const SSH_SECRET_UPDATES_FIELD: &str = "sshSecretUpdates"; -pub(crate) const SSH_PATCH_FIELD: &str = "sshPatch"; +pub const PROVIDER_API_KEY_UPDATES_FIELD: &str = "providerApiKeyUpdates"; +pub const PROVIDER_USAGE_QUERY_SECRET_UPDATES_FIELD: &str = "providerUsageQuerySecretUpdates"; +pub const SYSTEM_PROXY_PASSWORD_UPDATE_FIELD: &str = "systemProxyPasswordUpdate"; +pub const SSH_SECRET_UPDATES_FIELD: &str = "sshSecretUpdates"; +pub const SSH_PATCH_FIELD: &str = "sshPatch"; const SSH_SYNC_CONFLICT_MESSAGE: &str = "SSH 设置已在另一端更新,已刷新为最新状态,请重新提交。"; const PROVIDER_SETTINGS_SELECT_SQL: &str = " diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/providers.rs b/crates/agent-core/src/commands/config/settings/providers.rs similarity index 98% rename from crates/agent-gui/src-tauri/src/commands/config/settings/providers.rs rename to crates/agent-core/src/commands/config/settings/providers.rs index cedd35984..4bd19ac5d 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/providers.rs +++ b/crates/agent-core/src/commands/config/settings/providers.rs @@ -1,4 +1,4 @@ -pub(crate) fn load_providers(conn: &Connection) -> Result, String> { +pub fn load_providers(conn: &Connection) -> Result, String> { let mut stmt = conn .prepare(PROVIDER_SETTINGS_SELECT_SQL) .map_err(|e| format!("准备读取 {PROVIDER_SETTINGS_TABLE} 失败:{e}"))?; diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/remote.rs b/crates/agent-core/src/commands/config/settings/remote.rs similarity index 96% rename from crates/agent-gui/src-tauri/src/commands/config/settings/remote.rs rename to crates/agent-core/src/commands/config/settings/remote.rs index 5942a4156..48e63b6dd 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/remote.rs +++ b/crates/agent-core/src/commands/config/settings/remote.rs @@ -43,7 +43,7 @@ impl Default for RemoteSettingsPayload { } } -pub(crate) fn normalize_remote_settings_payload( +pub fn normalize_remote_settings_payload( payload: RemoteSettingsPayload, ) -> RemoteSettingsPayload { RemoteSettingsPayload { @@ -88,7 +88,7 @@ fn repair_url_scheme_slashes(input: &str) -> String { input.to_string() } -pub(crate) fn parse_remote_settings_payload(value: Value) -> Result { +pub fn parse_remote_settings_payload(value: Value) -> Result { let parsed = serde_json::from_value::(value) .map_err(|e| format!("解析 remote settings 失败:{e}"))?; Ok(normalize_remote_settings_payload(parsed)) @@ -112,7 +112,7 @@ pub(crate) fn load_remote(conn: &Connection) -> Result, String> { } } -pub(crate) fn load_remote_settings(conn: &Connection) -> Result { +pub fn load_remote_settings(conn: &Connection) -> Result { match load_remote(conn)? { Some(value) => parse_remote_settings_payload(value), None => Ok(RemoteSettingsPayload::default()), diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/associations.rs b/crates/agent-core/src/commands/config/settings/ssh/associations.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/ssh/associations.rs rename to crates/agent-core/src/commands/config/settings/ssh/associations.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/load.rs b/crates/agent-core/src/commands/config/settings/ssh/load.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/config/settings/ssh/load.rs rename to crates/agent-core/src/commands/config/settings/ssh/load.rs index 932a98971..6a78891b2 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/load.rs +++ b/crates/agent-core/src/commands/config/settings/ssh/load.rs @@ -293,7 +293,7 @@ fn trust_runtime_ssh_known_host_with_conn( Ok(()) } -pub(crate) fn reset_runtime_ssh_known_host(host: &str, port: u16) -> Result { +pub fn reset_runtime_ssh_known_host(host: &str, port: u16) -> Result { let conn = open_db()?; reset_runtime_ssh_known_host_with_conn(&conn, host, port) } diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/mod.rs b/crates/agent-core/src/commands/config/settings/ssh/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/ssh/mod.rs rename to crates/agent-core/src/commands/config/settings/ssh/mod.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/patch.rs b/crates/agent-core/src/commands/config/settings/ssh/patch.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/config/settings/ssh/patch.rs rename to crates/agent-core/src/commands/config/settings/ssh/patch.rs index 4d44de0f1..e96e27f01 100644 --- a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/patch.rs +++ b/crates/agent-core/src/commands/config/settings/ssh/patch.rs @@ -481,7 +481,7 @@ fn apply_ssh_patch_to_value( ])))) } -pub(crate) fn apply_ssh_patch_with_conn( +pub fn apply_ssh_patch_with_conn( conn: &mut Connection, payload: Value, ) -> Result { diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/persistence.rs b/crates/agent-core/src/commands/config/settings/ssh/persistence.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/ssh/persistence.rs rename to crates/agent-core/src/commands/config/settings/ssh/persistence.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/ssh/validation.rs b/crates/agent-core/src/commands/config/settings/ssh/validation.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/ssh/validation.rs rename to crates/agent-core/src/commands/config/settings/ssh/validation.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/system.rs b/crates/agent-core/src/commands/config/settings/system.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/system.rs rename to crates/agent-core/src/commands/config/settings/system.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/tests.rs b/crates/agent-core/src/commands/config/settings/tests.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/tests.rs rename to crates/agent-core/src/commands/config/settings/tests.rs diff --git a/crates/agent-gui/src-tauri/src/commands/config/settings/types.rs b/crates/agent-core/src/commands/config/settings/types.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/config/settings/types.rs rename to crates/agent-core/src/commands/config/settings/types.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs b/crates/agent-core/src/commands/history/chat_history/branch.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs rename to crates/agent-core/src/commands/history/chat_history/branch.rs index ec618c83d..7db404741 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/branch.rs +++ b/crates/agent-core/src/commands/history/chat_history/branch.rs @@ -210,7 +210,7 @@ pub(crate) fn chat_history_branch_sync( get_summary_by_id(conn, &new_id) } -pub(crate) async fn chat_history_branch_inner( +pub async fn chat_history_branch_inner( id: String, anchor: ChatHistoryMessageRef, ) -> Result { diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs b/crates/agent-core/src/commands/history/chat_history/commands.rs similarity index 97% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs rename to crates/agent-core/src/commands/history/chat_history/commands.rs index cbd2c8a67..dc9b62ec2 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/commands.rs +++ b/crates/agent-core/src/commands/history/chat_history/commands.rs @@ -45,7 +45,7 @@ pub async fn chat_history_search( .map_err(|e| format!("chat_history_search join 失败:{e}"))? } -pub(crate) async fn chat_history_get_summary_inner( +pub async fn chat_history_get_summary_inner( id: String, ) -> Result { tokio::task::spawn_blocking(move || { @@ -78,7 +78,7 @@ pub async fn chat_history_get(id: String) -> Result { .map_err(|e| format!("chat_history_get join 失败:{e}"))? } -pub(crate) async fn chat_history_get_tail( +pub async fn chat_history_get_tail( id: String, max_messages: i64, ) -> Result { @@ -339,7 +339,7 @@ pub async fn chat_history_append_segment( Ok(summary) } -pub(crate) async fn chat_history_rename_inner( +pub async fn chat_history_rename_inner( id: String, title: String, ) -> Result { @@ -360,7 +360,7 @@ pub async fn chat_history_rename( Ok(summary) } -pub(crate) async fn chat_history_set_pinned_inner( +pub async fn chat_history_set_pinned_inner( id: String, is_pinned: bool, ) -> Result { @@ -402,7 +402,7 @@ pub async fn chat_history_set_model( Ok(summary) } -pub(crate) async fn chat_history_share_get_inner( +pub async fn chat_history_share_get_inner( id: String, ) -> Result { tokio::task::spawn_blocking(move || { @@ -417,7 +417,7 @@ pub async fn chat_history_share_get(id: String) -> Result, @@ -445,7 +445,7 @@ pub async fn chat_history_share_set( Ok(status) } -pub(crate) async fn chat_history_share_resolve_inner( +pub async fn chat_history_share_resolve_inner( token: String, ) -> Result { tokio::task::spawn_blocking(move || { diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/db.rs b/crates/agent-core/src/commands/history/chat_history/db.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/db.rs rename to crates/agent-core/src/commands/history/chat_history/db.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs b/crates/agent-core/src/commands/history/chat_history/delete.rs similarity index 96% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs rename to crates/agent-core/src/commands/history/chat_history/delete.rs index aebbab0fe..8cfcc19b4 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/delete.rs +++ b/crates/agent-core/src/commands/history/chat_history/delete.rs @@ -41,7 +41,7 @@ fn delete_chat_history_sync( Ok(subagent_prune_result) } -pub(crate) async fn chat_history_delete_inner(id: String) -> Result<(), String> { +pub async fn chat_history_delete_inner(id: String) -> Result<(), String> { tokio::task::spawn_blocking(move || { let chat_id = id.trim().to_string(); let mut conn = open_db()?; diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/fts.rs b/crates/agent-core/src/commands/history/chat_history/fts.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/fts.rs rename to crates/agent-core/src/commands/history/chat_history/fts.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/message_ref.rs b/crates/agent-core/src/commands/history/chat_history/message_ref.rs similarity index 98% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/message_ref.rs rename to crates/agent-core/src/commands/history/chat_history/message_ref.rs index 83e5f292e..2f0f214cd 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/message_ref.rs +++ b/crates/agent-core/src/commands/history/chat_history/message_ref.rs @@ -42,7 +42,7 @@ pub(crate) fn fnv1a32(input: &str) -> String { format!("fnv1a32:{hash:08x}") } -pub(crate) fn history_message_content_hash(message: &Value) -> String { +pub fn history_message_content_hash(message: &Value) -> String { let object = message.as_object(); let role = object .and_then(|object| object.get("role")) @@ -139,7 +139,7 @@ pub(crate) fn history_message_id_for_ref(message: &Value) -> Option { }) } -pub(crate) fn validate_user_history_message_ref( +pub fn validate_user_history_message_ref( message_ref: &ChatHistoryMessageRef, ) -> Result<(), String> { if message_ref.segment_index < 0 || message_ref.message_index < 0 { @@ -234,7 +234,7 @@ pub(crate) struct HistorySegmentMessageWindow { } #[derive(Debug, Clone)] -pub(crate) struct HistoryMessageWindow { +pub struct HistoryMessageWindow { pub segments: Vec, pub returned_message_count: i64, pub oldest_offset: i64, @@ -269,7 +269,7 @@ fn history_window_message_role(message: &Value) -> Option<&str> { .map(str::trim) } -pub(crate) fn build_history_message_window( +pub fn build_history_message_window( segments: &[ChatHistorySegmentRecord], max_messages: i64, before_offset: Option, @@ -428,7 +428,7 @@ pub(crate) fn serialize_history_segment_windows( .collect() } -pub(crate) fn flatten_history_message_window( +pub fn flatten_history_message_window( window: &HistoryMessageWindow, ) -> Result { let mut merged = Vec::new(); @@ -489,7 +489,7 @@ pub(crate) fn locate_history_message_ref( }) } -pub(crate) fn build_history_prefix_segments( +pub fn build_history_prefix_segments( segments: &[ChatHistorySegmentRecord], message_ref: &ChatHistoryMessageRef, ) -> Result<(Vec, i64), String> { diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs b/crates/agent-core/src/commands/history/chat_history/mod.rs similarity index 97% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs rename to crates/agent-core/src/commands/history/chat_history/mod.rs index 3dee9fc82..ec6c6566c 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/mod.rs +++ b/crates/agent-core/src/commands/history/chat_history/mod.rs @@ -9,7 +9,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -use agent_core::events::EventBus; +use crate::events::EventBus; use crate::{ commands::{history_db, subagent_store}, diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs b/crates/agent-core/src/commands/history/chat_history/replace.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/replace.rs rename to crates/agent-core/src/commands/history/chat_history/replace.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/repository.rs b/crates/agent-core/src/commands/history/chat_history/repository.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/repository.rs rename to crates/agent-core/src/commands/history/chat_history/repository.rs index 62f2be515..9fb56440c 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/repository.rs +++ b/crates/agent-core/src/commands/history/chat_history/repository.rs @@ -345,7 +345,7 @@ pub(crate) fn list_shared_chat_history_sync( }) } -pub(crate) fn list_shared_chat_history_page_sync( +pub fn list_shared_chat_history_page_sync( page: i64, page_size: i64, ) -> Result { diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/search.rs b/crates/agent-core/src/commands/history/chat_history/search.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/search.rs rename to crates/agent-core/src/commands/history/chat_history/search.rs index 294fbb2f7..1d3f89f93 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/chat_history/search.rs +++ b/crates/agent-core/src/commands/history/chat_history/search.rs @@ -619,7 +619,7 @@ fn should_include_history_for_memory_search(args: &MemorySearchArgs) -> bool { args.include_history.unwrap_or(false) } -pub(crate) fn search_chat_history_for_memory_sync( +pub fn search_chat_history_for_memory_sync( args: &MemorySearchArgs, ) -> Result, String> { if !should_include_history_for_memory_search(args) { diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/segments.rs b/crates/agent-core/src/commands/history/chat_history/segments.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/segments.rs rename to crates/agent-core/src/commands/history/chat_history/segments.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/share.rs b/crates/agent-core/src/commands/history/chat_history/share.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/share.rs rename to crates/agent-core/src/commands/history/chat_history/share.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/tests.rs b/crates/agent-core/src/commands/history/chat_history/tests.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/tests.rs rename to crates/agent-core/src/commands/history/chat_history/tests.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/chat_history/types.rs b/crates/agent-core/src/commands/history/chat_history/types.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/chat_history/types.rs rename to crates/agent-core/src/commands/history/chat_history/types.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/history_db.rs b/crates/agent-core/src/commands/history/history_db.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/history/history_db.rs rename to crates/agent-core/src/commands/history/history_db.rs index 852ce9132..ae38c0cf7 100644 --- a/crates/agent-gui/src-tauri/src/commands/history/history_db.rs +++ b/crates/agent-core/src/commands/history/history_db.rs @@ -28,7 +28,7 @@ fn configure_connection(conn: &Connection) -> Result<(), String> { Ok(()) } -pub(crate) fn initialize_history_db() -> Result<(), String> { +pub fn initialize_history_db() -> Result<(), String> { let conn = open_connection()?; initialize_connection(&conn) } diff --git a/crates/agent-gui/src-tauri/src/commands/history/mod.rs b/crates/agent-core/src/commands/history/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/mod.rs rename to crates/agent-core/src/commands/history/mod.rs diff --git a/crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs b/crates/agent-core/src/commands/history/subagent_store.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/history/subagent_store.rs rename to crates/agent-core/src/commands/history/subagent_store.rs diff --git a/crates/agent-gui/src-tauri/src/commands/integration/mcp.rs b/crates/agent-core/src/commands/integration/mcp.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/integration/mcp.rs rename to crates/agent-core/src/commands/integration/mcp.rs diff --git a/crates/agent-gui/src-tauri/src/commands/integration/memory.rs b/crates/agent-core/src/commands/integration/memory.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/integration/memory.rs rename to crates/agent-core/src/commands/integration/memory.rs diff --git a/crates/agent-gui/src-tauri/src/commands/integration/mod.rs b/crates/agent-core/src/commands/integration/mod.rs similarity index 63% rename from crates/agent-gui/src-tauri/src/commands/integration/mod.rs rename to crates/agent-core/src/commands/integration/mod.rs index ceb9e695a..aadeeb1fe 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/mod.rs +++ b/crates/agent-core/src/commands/integration/mod.rs @@ -1,3 +1,2 @@ -pub mod gateway; pub mod mcp; pub mod memory; diff --git a/crates/agent-core/src/commands/mod.rs b/crates/agent-core/src/commands/mod.rs new file mode 100644 index 000000000..0dc2d6ec8 --- /dev/null +++ b/crates/agent-core/src/commands/mod.rs @@ -0,0 +1,34 @@ +#[path = "automation/mod.rs"] +pub mod automation_commands; +#[path = "config/mod.rs"] +pub mod config_commands; +#[path = "history/mod.rs"] +pub mod history_commands; +#[path = "integration/mod.rs"] +pub mod integration_commands; +#[path = "runtime/mod.rs"] +pub mod runtime_commands; +#[path = "workspace/mod.rs"] +pub mod workspace_commands; + +pub use automation_commands::cron; +pub use automation_commands::hook; + +pub use config_commands::settings; + +pub use history_commands::chat_history; +pub use history_commands::history_db; +pub use history_commands::subagent_store; + +pub use integration_commands::mcp; +pub use integration_commands::memory; + +pub use runtime_commands::process; +pub use runtime_commands::sftp; +pub use runtime_commands::shell; +pub use runtime_commands::terminal; + +pub use workspace_commands::chat_file_links; +pub use workspace_commands::fs; +pub use workspace_commands::git; +pub use workspace_commands::subagent_worktree; diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/mod.rs b/crates/agent-core/src/commands/runtime/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/runtime/mod.rs rename to crates/agent-core/src/commands/runtime/mod.rs diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/process.rs b/crates/agent-core/src/commands/runtime/process.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/runtime/process.rs rename to crates/agent-core/src/commands/runtime/process.rs diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs b/crates/agent-core/src/commands/runtime/sftp.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/runtime/sftp.rs rename to crates/agent-core/src/commands/runtime/sftp.rs diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/shell.rs b/crates/agent-core/src/commands/runtime/shell.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/runtime/shell.rs rename to crates/agent-core/src/commands/runtime/shell.rs diff --git a/crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs b/crates/agent-core/src/commands/runtime/terminal.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/runtime/terminal.rs rename to crates/agent-core/src/commands/runtime/terminal.rs diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs b/crates/agent-core/src/commands/workspace/chat_file_links.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs rename to crates/agent-core/src/commands/workspace/chat_file_links.rs index 098454451..f605f6b12 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/chat_file_links.rs +++ b/crates/agent-core/src/commands/workspace/chat_file_links.rs @@ -552,7 +552,7 @@ pub(crate) fn open_chat_file_link_sync( Ok(plan.response) } -pub(crate) async fn open_chat_file_link_for_conversation( +pub async fn open_chat_file_link_for_conversation( conversation_id: String, requested_workdir: String, path: String, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/edit_match.rs b/crates/agent-core/src/commands/workspace/edit_match.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/workspace/edit_match.rs rename to crates/agent-core/src/commands/workspace/edit_match.rs diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs b/crates/agent-core/src/commands/workspace/fs.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/workspace/fs.rs rename to crates/agent-core/src/commands/workspace/fs.rs index bae1e944d..42e34b158 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/fs.rs +++ b/crates/agent-core/src/commands/workspace/fs.rs @@ -2499,7 +2499,7 @@ pub async fn fs_read_image_source( .await } -pub(crate) fn fs_read_workspace_image_sync( +pub fn fs_read_workspace_image_sync( workdir: String, path: String, ) -> Result { @@ -2776,7 +2776,7 @@ pub struct ReadEditableTextResponse { pub file_id: Option, } -pub(crate) fn fs_read_editable_text_sync( +pub fn fs_read_editable_text_sync( workdir: String, path: String, ) -> Result { @@ -2926,7 +2926,7 @@ pub struct WriteTextResponse { pub file_id: Option, } -pub(crate) fn fs_write_text_sync( +pub fn fs_write_text_sync( workdir: String, path: String, content: String, @@ -3187,10 +3187,7 @@ fn remove_symlink_path(target: &Path) -> Result<(), io::Error> { } } -pub(crate) fn fs_delete_sync( - workdir: String, - path: String, -) -> Result { +pub fn fs_delete_sync(workdir: String, path: String) -> Result { let wd = canonicalize_workdir(&workdir)?; fs_delete_impl(&wd, &path).map_err(|e| FsCommandError::from(e).with_workdir(&wd)) } @@ -3368,7 +3365,7 @@ pub struct CreateDirResponse { pub kind: String, } -pub(crate) fn fs_create_dir_sync( +pub fn fs_create_dir_sync( workdir: String, path: String, ) -> Result { @@ -3416,7 +3413,7 @@ pub struct RenameResponse { pub kind: String, } -pub(crate) fn fs_rename_sync( +pub fn fs_rename_sync( workdir: String, from_path: String, to_path: String, @@ -3540,7 +3537,7 @@ pub struct FsListDirsResponse { pub truncated: bool, } -pub(crate) fn fs_roots_sync() -> Result { +pub fn fs_roots_sync() -> Result { let mut roots: Vec = Vec::new(); #[cfg(not(windows))] @@ -3603,7 +3600,7 @@ pub async fn fs_roots() -> Result { run_blocking("fs_roots", fs_roots_sync).await } -pub(crate) fn fs_list_dirs_sync( +pub fn fs_list_dirs_sync( path: String, max_results: Option, ) -> Result { @@ -3749,7 +3746,7 @@ fn visible_paths_for_hidden_marking( .collect() } -pub(crate) fn fs_list_sync( +pub fn fs_list_sync( workdir: String, path: Option, depth: Option, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/git.rs b/crates/agent-core/src/commands/workspace/git.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/commands/workspace/git.rs rename to crates/agent-core/src/commands/workspace/git.rs index f01b77ee0..5ac27828b 100644 --- a/crates/agent-gui/src-tauri/src/commands/workspace/git.rs +++ b/crates/agent-core/src/commands/workspace/git.rs @@ -12,11 +12,11 @@ use serde_json::Value; use tempfile::NamedTempFile; use wait_timeout::ChildExt; -use crate::commands::system::validate_project_folder_name; use crate::runtime::process::{ configure_child_process_group, kill_child_process_tree_best_effort, terminate_process_tree_by_pid, }; +use crate::runtime::project_path::validate_project_folder_name; const GIT_DIFF_MAX_BYTES: usize = 512 * 1024; const GIT_UNTRACKED_FILE_MAX_BYTES: u64 = 128 * 1024; @@ -3103,7 +3103,7 @@ pub(crate) fn git_gateway_action_sync( Ok(value) } -pub(crate) fn git_gateway_clone_task_action_sync( +pub fn git_gateway_clone_task_action_sync( action: String, workdir: String, args_json: String, diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/mod.rs b/crates/agent-core/src/commands/workspace/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/workspace/mod.rs rename to crates/agent-core/src/commands/workspace/mod.rs diff --git a/crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs b/crates/agent-core/src/commands/workspace/subagent_worktree.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/commands/workspace/subagent_worktree.rs rename to crates/agent-core/src/commands/workspace/subagent_worktree.rs diff --git a/crates/agent-core/src/lib.rs b/crates/agent-core/src/lib.rs index c32ec8f7e..a86e29195 100644 --- a/crates/agent-core/src/lib.rs +++ b/crates/agent-core/src/lib.rs @@ -8,5 +8,36 @@ //! - `crates/agent-backend` HTTP/WS 服务,通过 JSON 路由调用 //! //! 两者调的是同一批函数,所以行为不可能不一致。 +//! +//! 模块划分沿用搬迁前的形状(`runtime` / `services` / `commands`)。三者互相引用, +//! 不存在无环的拆分顺序,所以是一次搬完而不是分三步——分步只会制造一堆 +//! 「为了让这一步编译过」的临时垫片,然后在下一步删掉。 +pub mod commands; pub mod events; +pub mod runtime; +pub mod services; + +use std::sync::OnceLock; + +static APP_VERSION: OnceLock = OnceLock::new(); + +/// 由宿主进程在启动时注入版本号。 +/// +/// 版本是**部署事实**不是核心事实:它来自 `package.json` / git tag,由打包流程决定。 +/// 搬迁前 `app_version()` 靠 src-tauri 的 build.rs 塞 `env!`,那条路在这里走不通—— +/// agent-core 编译时根本不知道自己会被装进哪个产物。所以改成注入。 +/// +/// 重复注入是无操作(`OnceLock`),因为「谁先启动」不该影响结果。 +pub fn set_app_version(version: impl Into) { + let _ = APP_VERSION.set(version.into()); +} + +/// 宿主未注入时返回 `0.0.0-unknown`,不 panic:版本号只用于 MCP `clientInfo` +/// 这类自报家门的场合,拿不到不该让整个后端起不来。 +pub fn app_version() -> &'static str { + APP_VERSION + .get() + .map(String::as_str) + .unwrap_or("0.0.0-unknown") +} diff --git a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs b/crates/agent-core/src/runtime/managed_process.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/runtime/managed_process.rs rename to crates/agent-core/src/runtime/managed_process.rs index a70ee8543..1a42fd903 100644 --- a/crates/agent-gui/src-tauri/src/runtime/managed_process.rs +++ b/crates/agent-core/src/runtime/managed_process.rs @@ -9,6 +9,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use crate::events::EventBus; use crate::runtime::managed_process_journal as journal; use crate::runtime::platform::{expand_tilde_path, strip_windows_verbatim_prefix}; use crate::runtime::process::{ @@ -17,7 +18,6 @@ use crate::runtime::process::{ ProcessProbe, }; use crate::runtime::shell_runner::spawn_platform_shell_command; -use agent_core::events::EventBus; const PROCESS_LOG_DIR: &str = "process-logs"; const DEFAULT_LOG_BYTES: u64 = 64 * 1024; @@ -33,7 +33,7 @@ const START_TIME_TOLERANCE_MS: i64 = 60_000; pub const MANAGED_PROCESS_CHANGED_EVENT: &str = "managed-process:changed"; -pub(crate) struct ManagedProcessRegistry { +pub struct ManagedProcessRegistry { processes: Mutex>, journal: Mutex>, revision: AtomicU64, diff --git a/crates/agent-gui/src-tauri/src/runtime/managed_process_journal.rs b/crates/agent-core/src/runtime/managed_process_journal.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/managed_process_journal.rs rename to crates/agent-core/src/runtime/managed_process_journal.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/mod.rs b/crates/agent-core/src/runtime/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/mod.rs rename to crates/agent-core/src/runtime/mod.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/platform.rs b/crates/agent-core/src/runtime/platform.rs similarity index 95% rename from crates/agent-gui/src-tauri/src/runtime/platform.rs rename to crates/agent-core/src/runtime/platform.rs index 88c9e5fce..d9317d78c 100644 --- a/crates/agent-gui/src-tauri/src/runtime/platform.rs +++ b/crates/agent-core/src/runtime/platform.rs @@ -2,7 +2,7 @@ use std::env; use std::path::{Path, PathBuf}; use std::process::Command; -pub(crate) fn expand_tilde_path(raw: &str) -> PathBuf { +pub fn expand_tilde_path(raw: &str) -> PathBuf { let trimmed = raw.trim(); if trimmed == "~" || trimmed.starts_with("~/") || trimmed.starts_with("~\\") { if let Some(home) = dirs::home_dir() { @@ -32,7 +32,7 @@ pub(crate) fn strip_windows_verbatim_prefix(path: PathBuf) -> PathBuf { path } -pub(crate) fn maybe_augment_macos_path(command: &mut Command) { +pub fn maybe_augment_macos_path(command: &mut Command) { if !cfg!(target_os = "macos") { return; } @@ -77,10 +77,7 @@ pub(crate) fn shell_basename(shell: &str) -> String { .to_string() } -pub(crate) fn resolve_program_path_with_current_dir( - raw: &str, - _current_dir: Option<&Path>, -) -> PathBuf { +pub fn resolve_program_path_with_current_dir(raw: &str, _current_dir: Option<&Path>) -> PathBuf { let expanded = expand_tilde_path(raw); #[cfg(windows)] diff --git a/crates/agent-gui/src-tauri/src/runtime/process.rs b/crates/agent-core/src/runtime/process.rs similarity index 95% rename from crates/agent-gui/src-tauri/src/runtime/process.rs rename to crates/agent-core/src/runtime/process.rs index 45b42a995..2bf6e6223 100644 --- a/crates/agent-gui/src-tauri/src/runtime/process.rs +++ b/crates/agent-core/src/runtime/process.rs @@ -3,13 +3,13 @@ use std::process::{Child, Command, ExitStatus, Stdio}; use std::time::{Duration, Instant}; #[cfg(unix)] -pub(crate) fn configure_child_process_group(command: &mut Command) { +pub fn configure_child_process_group(command: &mut Command) { use std::os::unix::process::CommandExt; command.process_group(0); } #[cfg(windows)] -pub(crate) fn configure_child_process_group(command: &mut Command) { +pub fn configure_child_process_group(command: &mut Command) { use std::os::windows::process::CommandExt; const CREATE_NO_WINDOW: u32 = 0x08000000; @@ -17,7 +17,7 @@ pub(crate) fn configure_child_process_group(command: &mut Command) { } #[cfg(not(any(unix, windows)))] -pub(crate) fn configure_child_process_group(_command: &mut Command) {} +pub fn configure_child_process_group(_command: &mut Command) {} #[cfg(unix)] pub(crate) fn signal_process_tree_by_pid(pid: u32, force: bool) { @@ -76,7 +76,7 @@ pub(crate) fn terminate_child_process_tree( child.wait() } -pub(crate) fn kill_child_process_tree_best_effort(child: &mut Child) { +pub fn kill_child_process_tree_best_effort(child: &mut Child) { signal_child_process_tree(child, true); let _ = child.kill(); let _ = child.wait(); @@ -86,7 +86,7 @@ pub(crate) fn kill_child_process_tree_best_effort(child: &mut Child) { /// Child handle): TERM to the group, bounded grace while probing the leader, /// then an unconditional KILL sweep so group members that outlived the /// leader are still reaped. -pub(crate) fn terminate_process_tree_by_pid(pid: u32, grace: Duration) { +pub fn terminate_process_tree_by_pid(pid: u32, grace: Duration) { signal_process_tree_by_pid(pid, false); let grace_started = Instant::now(); while matches!(probe_process_start_time(pid), ProcessProbe::Alive { .. }) { diff --git a/crates/agent-gui/src-tauri/src/runtime/project_path.rs b/crates/agent-core/src/runtime/project_path.rs similarity index 77% rename from crates/agent-gui/src-tauri/src/runtime/project_path.rs rename to crates/agent-core/src/runtime/project_path.rs index 6738a0dcb..3fda8f126 100644 --- a/crates/agent-gui/src-tauri/src/runtime/project_path.rs +++ b/crates/agent-core/src/runtime/project_path.rs @@ -1,3 +1,5 @@ +use std::path::Path; + pub fn project_path_key(value: &str) -> String { let trimmed = value.trim(); if trimmed.is_empty() { @@ -125,6 +127,50 @@ fn is_ascii_alpha(value: u8) -> bool { value.is_ascii_alphabetic() } +/// Windows 保留设备名(CON/PRN/AUX/NUL/COM1-9/LPT1-9)。跨平台都要挡: +/// 在 macOS 上建出 `COM1` 目录,同步到 Windows 客户端时就打不开了。 +fn is_windows_reserved_project_name(name: &str) -> bool { + let stem = name + .split('.') + .next() + .unwrap_or(name) + .trim() + .trim_end_matches(' ') + .to_ascii_uppercase(); + matches!(stem.as_str(), "CON" | "PRN" | "AUX" | "NUL") + || (stem.len() == 4 + && (stem.starts_with("COM") || stem.starts_with("LPT")) + && stem[3..] + .parse::() + .is_ok_and(|value| (1..=9).contains(&value))) +} + +pub fn validate_project_folder_name(name: &str) -> Result<&str, String> { + let trimmed = name.trim(); + if trimmed.is_empty() { + return Err("项目名不能为空".to_string()); + } + if trimmed == "." || trimmed == ".." { + return Err("项目名不能是 . 或 ..".to_string()); + } + if trimmed.contains('/') || trimmed.contains('\\') || trimmed.contains(':') { + return Err("项目名不能包含路径分隔符".to_string()); + } + if trimmed + .chars() + .any(|ch| ch == '\0' || ch.is_ascii_control()) + { + return Err("项目名包含非法字符".to_string()); + } + if Path::new(trimmed).components().count() != 1 { + return Err("项目名不能包含路径片段".to_string()); + } + if is_windows_reserved_project_name(trimmed) { + return Err("项目名不能使用系统保留名称".to_string()); + } + Ok(trimmed) +} + #[cfg(test)] mod tests { use super::{project_path_key, project_path_keys_equal}; diff --git a/crates/agent-gui/src-tauri/src/runtime/sftp.rs b/crates/agent-core/src/runtime/sftp.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/runtime/sftp.rs rename to crates/agent-core/src/runtime/sftp.rs index 1c47d02b8..97ac246e1 100644 --- a/crates/agent-gui/src-tauri/src/runtime/sftp.rs +++ b/crates/agent-core/src/runtime/sftp.rs @@ -9,7 +9,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; use uuid::Uuid; -use agent_core::events::EventBus; +use crate::events::EventBus; use crate::runtime::platform::expand_tilde_path; use crate::runtime::project_path::{ diff --git a/crates/agent-gui/src-tauri/src/runtime/shell_runner.rs b/crates/agent-core/src/runtime/shell_runner.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/runtime/shell_runner.rs rename to crates/agent-core/src/runtime/shell_runner.rs index b4f48c407..5901fc954 100644 --- a/crates/agent-gui/src-tauri/src/runtime/shell_runner.rs +++ b/crates/agent-core/src/runtime/shell_runner.rs @@ -27,7 +27,7 @@ const STREAM_EOF_GRACE_MS: u64 = 300; /// async cancel watchers. Blocking code polls `is_cancelled`; async code /// awaits `cancelled()`, which is event-driven via `Notify` — no polling. #[derive(Default)] -pub(crate) struct ShellCancelFlag { +pub struct ShellCancelFlag { cancelled: AtomicBool, notify: tokio::sync::Notify, } @@ -58,10 +58,10 @@ impl ShellCancelFlag { } } -pub(crate) type ShellCancelToken = Arc; +pub type ShellCancelToken = Arc; #[derive(Default)] -pub(crate) struct ShellRunRegistry { +pub struct ShellRunRegistry { runs: Mutex>, } @@ -662,7 +662,7 @@ where Err(ShellError::Other(format!("Failed to start command: {detail}")).to_string()) } -pub(crate) fn run_shell_script( +pub fn run_shell_script( workdir: String, command: String, cwd: Option, @@ -684,7 +684,7 @@ pub(crate) fn run_shell_script( } #[allow(clippy::too_many_arguments)] -pub(crate) fn run_shell_script_with_envs( +pub fn run_shell_script_with_envs( workdir: String, command: String, cwd: Option, diff --git a/crates/agent-gui/src-tauri/src/runtime/task_runner.rs b/crates/agent-core/src/runtime/task_runner.rs similarity index 96% rename from crates/agent-gui/src-tauri/src/runtime/task_runner.rs rename to crates/agent-core/src/runtime/task_runner.rs index 8f78324ce..a48a13081 100644 --- a/crates/agent-gui/src-tauri/src/runtime/task_runner.rs +++ b/crates/agent-core/src/runtime/task_runner.rs @@ -56,7 +56,7 @@ impl std::fmt::Display for HttpExecutionFailure { impl std::error::Error for HttpExecutionFailure {} -pub(crate) fn resolve_workdir(workdir: Option) -> Result { +pub fn resolve_workdir(workdir: Option) -> Result { let raw = workdir.unwrap_or_default(); let base = if raw.trim().is_empty() { std::env::current_dir().map_err(|e| format!("读取应用 cwd 失败:{e}"))? @@ -105,7 +105,7 @@ fn build_header_map(headers: &Option>) -> Result
) -> Result { +pub fn build_http_client(timeout_ms: Option) -> Result { crate::services::system_proxy::blocking_client_builder() .map_err(|e| format!("创建 Hook HTTP client 失败:{e}"))? .timeout(Duration::from_millis( @@ -115,7 +115,7 @@ pub(crate) fn build_http_client(timeout_ms: Option) -> Result Result { diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/events.rs b/crates/agent-core/src/runtime/terminal/events.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/events.rs rename to crates/agent-core/src/runtime/terminal/events.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs b/crates/agent-core/src/runtime/terminal/mod.rs similarity index 92% rename from crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs rename to crates/agent-core/src/runtime/terminal/mod.rs index 4ea854670..dbbe45dd9 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/mod.rs +++ b/crates/agent-core/src/runtime/terminal/mod.rs @@ -14,7 +14,7 @@ //! - [`shell`]:本地 shell 解析、PTY 环境与进程清理 //! - [`util`]:小型辅助函数 -use agent_core::events::EventBus; +use crate::events::EventBus; use std::collections::HashMap; use std::sync::atomic::AtomicUsize; use std::sync::{mpsc, Arc, Mutex}; @@ -45,6 +45,11 @@ pub(crate) use ssh_channel::*; pub(crate) use ssh_connect::*; pub(crate) use ssh_io::*; pub(crate) use ssh_local_forward::*; +// 壳侧(以及 Gateway 的终端转发)只需要这两个校验函数,逐个点名而不是 +// 把整个 glob 提成 pub——跨 crate 的 API 面应当是列出来的,不是漏出来的。 +pub use ssh_local_forward::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, +}; pub(crate) use state::*; pub use types::*; pub(crate) use util::*; diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/output.rs b/crates/agent-core/src/runtime/terminal/output.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/output.rs rename to crates/agent-core/src/runtime/terminal/output.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs b/crates/agent-core/src/runtime/terminal/registry.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs rename to crates/agent-core/src/runtime/terminal/registry.rs index 3dc9d35bf..c3777f8cb 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/registry.rs +++ b/crates/agent-core/src/runtime/terminal/registry.rs @@ -5,10 +5,10 @@ use std::sync::atomic::Ordering; use std::sync::{mpsc, Arc, Mutex}; use std::thread; +use crate::events::EventBus; use crate::runtime::project_path::{ project_path_key as normalize_project_path_key, project_path_keys_equal, }; -use agent_core::events::EventBus; use super::*; diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/shell.rs b/crates/agent-core/src/runtime/terminal/shell.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/shell.rs rename to crates/agent-core/src/runtime/terminal/shell.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_auth.rs b/crates/agent-core/src/runtime/terminal/ssh_auth.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/ssh_auth.rs rename to crates/agent-core/src/runtime/terminal/ssh_auth.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_channel.rs b/crates/agent-core/src/runtime/terminal/ssh_channel.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/ssh_channel.rs rename to crates/agent-core/src/runtime/terminal/ssh_channel.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_connect.rs b/crates/agent-core/src/runtime/terminal/ssh_connect.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/ssh_connect.rs rename to crates/agent-core/src/runtime/terminal/ssh_connect.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_io.rs b/crates/agent-core/src/runtime/terminal/ssh_io.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/ssh_io.rs rename to crates/agent-core/src/runtime/terminal/ssh_io.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs b/crates/agent-core/src/runtime/terminal/ssh_local_forward.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs rename to crates/agent-core/src/runtime/terminal/ssh_local_forward.rs index a1b9582ef..3d4fb4d25 100644 --- a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_local_forward.rs +++ b/crates/agent-core/src/runtime/terminal/ssh_local_forward.rs @@ -449,7 +449,7 @@ pub(crate) fn normalize_ssh_local_forward_remote_port(port: u32) -> Result) -> Result { +pub fn normalize_ssh_local_forward_local_port(port: Option) -> Result { u16::try_from(port.unwrap_or(0)) .map_err(|_| "SSH local forward local port must be between 0 and 65535".to_string()) } @@ -457,7 +457,7 @@ pub(crate) fn normalize_ssh_local_forward_local_port(port: Option) -> Resul /// Test-binds the loopback port the user asked for. Advisory only: the /// authoritative bind happens in `ssh_local_forward_start`, this exists so the /// UI can show a localized "port in use" error before submitting. -pub(crate) async fn ssh_local_forward_local_port_available(port: u16) -> bool { +pub async fn ssh_local_forward_local_port_available(port: u16) -> bool { TcpListener::bind((Ipv4Addr::LOCALHOST, port)).await.is_ok() } diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/ssh_session.rs b/crates/agent-core/src/runtime/terminal/ssh_session.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/ssh_session.rs rename to crates/agent-core/src/runtime/terminal/ssh_session.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/state.rs b/crates/agent-core/src/runtime/terminal/state.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/state.rs rename to crates/agent-core/src/runtime/terminal/state.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/tabs.rs b/crates/agent-core/src/runtime/terminal/tabs.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/tabs.rs rename to crates/agent-core/src/runtime/terminal/tabs.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/tests.rs b/crates/agent-core/src/runtime/terminal/tests.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/tests.rs rename to crates/agent-core/src/runtime/terminal/tests.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/types.rs b/crates/agent-core/src/runtime/terminal/types.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/types.rs rename to crates/agent-core/src/runtime/terminal/types.rs diff --git a/crates/agent-gui/src-tauri/src/runtime/terminal/util.rs b/crates/agent-core/src/runtime/terminal/util.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/runtime/terminal/util.rs rename to crates/agent-core/src/runtime/terminal/util.rs diff --git a/crates/agent-gui/src-tauri/src/services/automation/db.rs b/crates/agent-core/src/services/automation/db.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/automation/db.rs rename to crates/agent-core/src/services/automation/db.rs diff --git a/crates/agent-gui/src-tauri/src/services/automation/mod.rs b/crates/agent-core/src/services/automation/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/automation/mod.rs rename to crates/agent-core/src/services/automation/mod.rs diff --git a/crates/agent-gui/src-tauri/src/services/automation/scheduler.rs b/crates/agent-core/src/services/automation/scheduler.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/automation/scheduler.rs rename to crates/agent-core/src/services/automation/scheduler.rs diff --git a/crates/agent-gui/src-tauri/src/services/automation/store.rs b/crates/agent-core/src/services/automation/store.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/services/automation/store.rs rename to crates/agent-core/src/services/automation/store.rs index f0c63bcef..35cc6cfcb 100644 --- a/crates/agent-gui/src-tauri/src/services/automation/store.rs +++ b/crates/agent-core/src/services/automation/store.rs @@ -4,7 +4,7 @@ use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; use serde_json::Value; use uuid::Uuid; -use agent_core::events::EventBus; +use crate::events::EventBus; use super::db; use super::scheduler::AutomationScheduler; diff --git a/crates/agent-gui/src-tauri/src/services/automation/tests.rs b/crates/agent-core/src/services/automation/tests.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/automation/tests.rs rename to crates/agent-core/src/services/automation/tests.rs diff --git a/crates/agent-gui/src-tauri/src/services/automation/types.rs b/crates/agent-core/src/services/automation/types.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/automation/types.rs rename to crates/agent-core/src/services/automation/types.rs diff --git a/crates/agent-gui/src-tauri/src/services/automation/validate.rs b/crates/agent-core/src/services/automation/validate.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/automation/validate.rs rename to crates/agent-core/src/services/automation/validate.rs diff --git a/crates/agent-gui/src-tauri/src/services/chat_run_ledger.rs b/crates/agent-core/src/services/chat_run_ledger.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/chat_run_ledger.rs rename to crates/agent-core/src/services/chat_run_ledger.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/content.rs b/crates/agent-core/src/services/memory/content.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/content.rs rename to crates/agent-core/src/services/memory/content.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/daily.rs b/crates/agent-core/src/services/memory/daily.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/daily.rs rename to crates/agent-core/src/services/memory/daily.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/maintenance.rs b/crates/agent-core/src/services/memory/maintenance.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/maintenance.rs rename to crates/agent-core/src/services/memory/maintenance.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/mod.rs b/crates/agent-core/src/services/memory/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/mod.rs rename to crates/agent-core/src/services/memory/mod.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/mutations/evidence.rs b/crates/agent-core/src/services/memory/mutations/evidence.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/mutations/evidence.rs rename to crates/agent-core/src/services/memory/mutations/evidence.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/mutations/methods.rs b/crates/agent-core/src/services/memory/mutations/methods.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/mutations/methods.rs rename to crates/agent-core/src/services/memory/mutations/methods.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/mutations/mod.rs b/crates/agent-core/src/services/memory/mutations/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/mutations/mod.rs rename to crates/agent-core/src/services/memory/mutations/mod.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/mutations/normalize.rs b/crates/agent-core/src/services/memory/mutations/normalize.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/mutations/normalize.rs rename to crates/agent-core/src/services/memory/mutations/normalize.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/mutations/risk.rs b/crates/agent-core/src/services/memory/mutations/risk.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/mutations/risk.rs rename to crates/agent-core/src/services/memory/mutations/risk.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/mutations/write_entry.rs b/crates/agent-core/src/services/memory/mutations/write_entry.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/mutations/write_entry.rs rename to crates/agent-core/src/services/memory/mutations/write_entry.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/organize.rs b/crates/agent-core/src/services/memory/organize.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/organize.rs rename to crates/agent-core/src/services/memory/organize.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/paths.rs b/crates/agent-core/src/services/memory/paths.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/paths.rs rename to crates/agent-core/src/services/memory/paths.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/schema.rs b/crates/agent-core/src/services/memory/schema.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/schema.rs rename to crates/agent-core/src/services/memory/schema.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/search.rs b/crates/agent-core/src/services/memory/search.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/search.rs rename to crates/agent-core/src/services/memory/search.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/store.rs b/crates/agent-core/src/services/memory/store.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/store.rs rename to crates/agent-core/src/services/memory/store.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/tests.rs b/crates/agent-core/src/services/memory/tests.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/tests.rs rename to crates/agent-core/src/services/memory/tests.rs diff --git a/crates/agent-gui/src-tauri/src/services/memory/types.rs b/crates/agent-core/src/services/memory/types.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/memory/types.rs rename to crates/agent-core/src/services/memory/types.rs diff --git a/crates/agent-core/src/services/mod.rs b/crates/agent-core/src/services/mod.rs new file mode 100644 index 000000000..97cc38ff0 --- /dev/null +++ b/crates/agent-core/src/services/mod.rs @@ -0,0 +1,9 @@ +pub mod automation; +pub mod chat_run_ledger; +pub mod memory; +pub mod power_activity; +pub mod provider_models; +pub mod provider_usage; +pub mod skills; +pub mod system_proxy; +pub mod workspace_watch; diff --git a/crates/agent-gui/src-tauri/src/services/power_activity.rs b/crates/agent-core/src/services/power_activity.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/power_activity.rs rename to crates/agent-core/src/services/power_activity.rs diff --git a/crates/agent-gui/src-tauri/src/services/provider_models.rs b/crates/agent-core/src/services/provider_models.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/provider_models.rs rename to crates/agent-core/src/services/provider_models.rs diff --git a/crates/agent-gui/src-tauri/src/services/provider_usage.rs b/crates/agent-core/src/services/provider_usage.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/provider_usage.rs rename to crates/agent-core/src/services/provider_usage.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/builtin.rs b/crates/agent-core/src/services/skills/builtin.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/builtin.rs rename to crates/agent-core/src/services/skills/builtin.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/clawhub.rs b/crates/agent-core/src/services/skills/clawhub.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/clawhub.rs rename to crates/agent-core/src/services/skills/clawhub.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/create.rs b/crates/agent-core/src/services/skills/create.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/create.rs rename to crates/agent-core/src/services/skills/create.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/external.rs b/crates/agent-core/src/services/skills/external.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/external.rs rename to crates/agent-core/src/services/skills/external.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/external_mcp.rs b/crates/agent-core/src/services/skills/external_mcp.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/external_mcp.rs rename to crates/agent-core/src/services/skills/external_mcp.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/install.rs b/crates/agent-core/src/services/skills/install.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/install.rs rename to crates/agent-core/src/services/skills/install.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/jobs.rs b/crates/agent-core/src/services/skills/jobs.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/jobs.rs rename to crates/agent-core/src/services/skills/jobs.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/library.rs b/crates/agent-core/src/services/skills/library.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/library.rs rename to crates/agent-core/src/services/skills/library.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/manager.rs b/crates/agent-core/src/services/skills/manager.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/manager.rs rename to crates/agent-core/src/services/skills/manager.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/metadata.rs b/crates/agent-core/src/services/skills/metadata.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/metadata.rs rename to crates/agent-core/src/services/skills/metadata.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/mod.rs b/crates/agent-core/src/services/skills/mod.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/mod.rs rename to crates/agent-core/src/services/skills/mod.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/paths.rs b/crates/agent-core/src/services/skills/paths.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/paths.rs rename to crates/agent-core/src/services/skills/paths.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/sources.rs b/crates/agent-core/src/services/skills/sources.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/sources.rs rename to crates/agent-core/src/services/skills/sources.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/tests.rs b/crates/agent-core/src/services/skills/tests.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/tests.rs rename to crates/agent-core/src/services/skills/tests.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/types.rs b/crates/agent-core/src/services/skills/types.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/types.rs rename to crates/agent-core/src/services/skills/types.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/util.rs b/crates/agent-core/src/services/skills/util.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/util.rs rename to crates/agent-core/src/services/skills/util.rs diff --git a/crates/agent-gui/src-tauri/src/services/skills/validate.rs b/crates/agent-core/src/services/skills/validate.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/skills/validate.rs rename to crates/agent-core/src/services/skills/validate.rs diff --git a/crates/agent-gui/src-tauri/src/services/system_proxy.rs b/crates/agent-core/src/services/system_proxy.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/system_proxy.rs rename to crates/agent-core/src/services/system_proxy.rs diff --git a/crates/agent-gui/src-tauri/src/services/workspace_watch/emit.rs b/crates/agent-core/src/services/workspace_watch/emit.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/workspace_watch/emit.rs rename to crates/agent-core/src/services/workspace_watch/emit.rs diff --git a/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs b/crates/agent-core/src/services/workspace_watch/mod.rs similarity index 99% rename from crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs rename to crates/agent-core/src/services/workspace_watch/mod.rs index c94ee1845..c77c1a837 100644 --- a/crates/agent-gui/src-tauri/src/services/workspace_watch/mod.rs +++ b/crates/agent-core/src/services/workspace_watch/mod.rs @@ -14,7 +14,7 @@ mod watcher; use std::collections::{BTreeSet, HashMap}; use std::sync::{Arc, Mutex}; -use agent_core::events::EventBus; +use crate::events::EventBus; pub const WORKSPACE_ACTIVITY_EVENT: &str = "workspace:activity"; diff --git a/crates/agent-gui/src-tauri/src/services/workspace_watch/watcher.rs b/crates/agent-core/src/services/workspace_watch/watcher.rs similarity index 100% rename from crates/agent-gui/src-tauri/src/services/workspace_watch/watcher.rs rename to crates/agent-core/src/services/workspace_watch/watcher.rs diff --git a/crates/agent-gui/src-tauri/src/commands/app/app.rs b/crates/agent-gui/src-tauri/src/commands/app/app.rs index 210930cb0..f0607fbd5 100644 --- a/crates/agent-gui/src-tauri/src/commands/app/app.rs +++ b/crates/agent-gui/src-tauri/src/commands/app/app.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use tauri::{AppHandle, State}; use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut}; -use crate::runtime::terminal::TerminalSessionRegistry; +use agent_core::runtime::terminal::TerminalSessionRegistry; pub type CloseWindowBehaviorState = AtomicU8; diff --git a/crates/agent-gui/src-tauri/src/commands/app/system.rs b/crates/agent-gui/src-tauri/src/commands/app/system.rs index 895ad4e2e..8a1f1e68c 100644 --- a/crates/agent-gui/src-tauri/src/commands/app/system.rs +++ b/crates/agent-gui/src-tauri/src/commands/app/system.rs @@ -8,9 +8,10 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::runtime::platform::expand_tilde_path; -use crate::services::power_activity::PowerActivityManager; -pub use crate::services::skills::{ +use agent_core::runtime::platform::expand_tilde_path; +use agent_core::runtime::project_path::validate_project_folder_name; +use agent_core::services::power_activity::PowerActivityManager; +pub use agent_core::services::skills::{ SystemListSkillFilesResponse, SystemManageSkillResponse, SystemReadSkillMetadataResponse, SystemReadSkillTextResponse, }; @@ -1175,13 +1176,13 @@ pub(crate) fn system_read_uploaded_native_attachment_sync( } pub(crate) fn system_list_skill_files_sync() -> Result { - crate::services::skills::system_list_skill_files_sync() + agent_core::services::skills::system_list_skill_files_sync() } pub(crate) fn system_read_skill_metadata_sync( path: String, ) -> Result { - crate::services::skills::system_read_skill_metadata_sync(path) + agent_core::services::skills::system_read_skill_metadata_sync(path) } pub(crate) fn system_read_skill_text_sync( @@ -1189,7 +1190,7 @@ pub(crate) fn system_read_skill_text_sync( offset: Option, length: Option, ) -> Result { - crate::services::skills::system_read_skill_text_sync(path, offset, length) + agent_core::services::skills::system_read_skill_text_sync(path, offset, length) } fn system_append_debug_jsonl_sync(conversation_id: String, entry: Value) -> Result<(), String> { @@ -1224,48 +1225,6 @@ fn resolve_pick_folder_initial_dir(initial_workdir: Option) -> Option bool { - let stem = name - .split('.') - .next() - .unwrap_or(name) - .trim() - .trim_end_matches(' ') - .to_ascii_uppercase(); - matches!(stem.as_str(), "CON" | "PRN" | "AUX" | "NUL") - || (stem.len() == 4 - && (stem.starts_with("COM") || stem.starts_with("LPT")) - && stem[3..] - .parse::() - .is_ok_and(|value| (1..=9).contains(&value))) -} - -pub(crate) fn validate_project_folder_name(name: &str) -> Result<&str, String> { - let trimmed = name.trim(); - if trimmed.is_empty() { - return Err("项目名不能为空".to_string()); - } - if trimmed == "." || trimmed == ".." { - return Err("项目名不能是 . 或 ..".to_string()); - } - if trimmed.contains('/') || trimmed.contains('\\') || trimmed.contains(':') { - return Err("项目名不能包含路径分隔符".to_string()); - } - if trimmed - .chars() - .any(|ch| ch == '\0' || ch.is_ascii_control()) - { - return Err("项目名包含非法字符".to_string()); - } - if Path::new(trimmed).components().count() != 1 { - return Err("项目名不能包含路径片段".to_string()); - } - if is_windows_reserved_project_name(trimmed) { - return Err("项目名不能使用系统保留名称".to_string()); - } - Ok(trimmed) -} - /// Mirror of the fs command layer's `display_path`: strip the Windows `\\?\` /// verbatim prefix and use forward slashes so the returned path matches the /// shape `fs_roots`/`fs_list_dirs` hand to the WebUI picker (a mismatched @@ -1472,17 +1431,19 @@ pub async fn system_list_skill_files() -> Result Result, String> { - tokio::task::spawn_blocking(crate::services::skills::ensure_builtin_agent_skills_sync) +) -> Result, String> { + tokio::task::spawn_blocking(agent_core::services::skills::ensure_builtin_agent_skills_sync) .await .map_err(|e| format!("system_ensure_builtin_skills join failed: {e}"))? } #[tauri::command(rename_all = "snake_case")] pub async fn system_manage_skill(payload: Value) -> Result { - tokio::task::spawn_blocking(move || crate::services::skills::system_manage_skill_sync(payload)) - .await - .map_err(|e| format!("system_manage_skill join failed: {e}"))? + tokio::task::spawn_blocking(move || { + agent_core::services::skills::system_manage_skill_sync(payload) + }) + .await + .map_err(|e| format!("system_manage_skill join failed: {e}"))? } #[tauri::command] diff --git a/crates/agent-gui/src-tauri/src/commands/app/update.rs b/crates/agent-gui/src-tauri/src/commands/app/update.rs index 0c3a22cbf..d18160bc6 100644 --- a/crates/agent-gui/src-tauri/src/commands/app/update.rs +++ b/crates/agent-gui/src-tauri/src/commands/app/update.rs @@ -294,7 +294,7 @@ fn selected_release_candidates_from_entries( fn github_client() -> Result { // 应用代理启用时更新检查随之走应用代理;未启用时回退 reqwest 默认代理探测 // (OS 代理环境变量/系统代理设置),无系统代理即直连,尽可能保证 GitHub 可达。 - crate::services::system_proxy::client_builder_with_os_proxy_fallback()? + agent_core::services::system_proxy::client_builder_with_os_proxy_fallback()? .timeout(Duration::from_secs(20)) .build() .map_err(|error| format!("failed to create GitHub client: {error}")) @@ -456,7 +456,7 @@ fn build_updater( // 更新下载/安装与 github_client() 的探测请求保持同一份代理语义:应用代理 // 启用时显式走应用代理;未启用时不调 no_proxy(),让插件内部 client 走 // reqwest 默认代理探测(OS 代理环境变量/系统代理设置),无系统代理即直连。 - if let Some(proxy_url) = crate::services::system_proxy::current_proxy_url()? { + if let Some(proxy_url) = agent_core::services::system_proxy::current_proxy_url()? { builder = builder.proxy(proxy_url); } @@ -574,13 +574,13 @@ pub fn app_restart(app: AppHandle) -> Result<(), String> { // non-isolated managed processes leak across every update restart. use tauri::Manager; use tauri_plugin_window_state::AppHandleExt; - if let Some(registry) = - app.try_state::>() + if let Some(registry) = app + .try_state::>() { registry.shutdown_cleanup(); } - if let Some(power) = - app.try_state::>() + if let Some(power) = app + .try_state::>() { power.clear_all(); } diff --git a/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs b/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs index 8bc1f4a91..5f0060b40 100644 --- a/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs +++ b/crates/agent-gui/src-tauri/src/commands/integration/gateway.rs @@ -2,17 +2,19 @@ use std::sync::Arc; use serde_json::Value; -use crate::commands::settings::{load_remote_settings, open_db, parse_remote_settings_payload}; use crate::services::gateway::{ GatewayChatCheckpointCommitResult, GatewayChatCheckpointInput, GatewayChatClaimedRequest, GatewayChatIngressAcceptResult, GatewayChatIngressBatchInput, GatewayChatQueueEventInput, GatewayChatQueueResponseInput, GatewayController, GatewayStatusSnapshot, }; -use crate::services::provider_usage::{ProviderUsageResult, ProviderUsageService}; use crate::services::tunnel::{ GatewayTunnelCreateInput, GatewayTunnelUpdateInput, TunnelStatePayload, }; -use crate::services::workspace_watch::WatchSource; +use agent_core::commands::settings::{ + load_remote_settings, open_db, parse_remote_settings_payload, +}; +use agent_core::services::provider_usage::{ProviderUsageResult, ProviderUsageService}; +use agent_core::services::workspace_watch::WatchSource; #[tauri::command] pub async fn provider_usage_query( diff --git a/crates/agent-gui/src-tauri/src/commands/mod.rs b/crates/agent-gui/src-tauri/src/commands/mod.rs index 07a52333a..ca28ba510 100644 --- a/crates/agent-gui/src-tauri/src/commands/mod.rs +++ b/crates/agent-gui/src-tauri/src/commands/mod.rs @@ -1,42 +1,13 @@ +//! 壳专属命令。后端命令的实现已迁入 agent-core,Tauri 包装在 `crate::tauri_commands`。 + #[path = "app/mod.rs"] pub mod app_commands; -#[path = "automation/mod.rs"] -pub mod automation_commands; -#[path = "config/mod.rs"] -pub mod config_commands; -#[path = "history/mod.rs"] -pub mod history_commands; -#[path = "integration/mod.rs"] -pub mod integration_commands; -#[path = "runtime/mod.rs"] -pub mod runtime_commands; -#[path = "workspace/mod.rs"] -pub mod workspace_commands; + +/// Gateway 的 20 个命令:阶段 4 随 Gateway 一并删除,所以不拆、不迁。 +#[path = "integration/gateway.rs"] +pub mod gateway; pub use app_commands::app; pub use app_commands::system; pub use app_commands::tray; pub use app_commands::update; - -pub use automation_commands::cron; -pub use automation_commands::hook; - -pub use config_commands::settings; - -pub use history_commands::chat_history; -pub use history_commands::history_db; -pub use history_commands::subagent_store; - -pub use integration_commands::gateway; -pub use integration_commands::mcp; -pub use integration_commands::memory; - -pub use runtime_commands::process; -pub use runtime_commands::sftp; -pub use runtime_commands::shell; -pub use runtime_commands::terminal; - -pub use workspace_commands::chat_file_links; -pub use workspace_commands::fs; -pub use workspace_commands::git; -pub use workspace_commands::subagent_worktree; diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs index 52ead3232..a38514698 100644 --- a/crates/agent-gui/src-tauri/src/gateway_sink.rs +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -13,17 +13,18 @@ use std::sync::{Arc, Weak}; -use crate::commands::chat_history::{ - ChatHistorySummary, HISTORY_DELETE_EVENT, HISTORY_UPSERT_EVENT, -}; -use crate::runtime::managed_process::MANAGED_PROCESS_CHANGED_EVENT; -use crate::services::automation::types::{CRON_CHANGED_EVENT, HOOKS_CHANGED_EVENT}; use crate::services::gateway::{ build_history_sync_delete, build_history_sync_upsert, now_unix_seconds, proto, GatewayController, }; -use crate::services::workspace_watch::{WorkspaceWatchService, WORKSPACE_ACTIVITY_EVENT}; +use agent_core::commands::chat_history::{ + ChatHistorySummary, HISTORY_DELETE_EVENT, HISTORY_UPSERT_EVENT, +}; +use agent_core::commands::settings::SETTINGS_REMOTE_SAVED_EVENT; use agent_core::events::EventSink; +use agent_core::runtime::managed_process::MANAGED_PROCESS_CHANGED_EVENT; +use agent_core::services::automation::types::{CRON_CHANGED_EVENT, HOOKS_CHANGED_EVENT}; +use agent_core::services::workspace_watch::{WorkspaceWatchService, WORKSPACE_ACTIVITY_EVENT}; pub struct GatewayEventSink { controller: Weak, @@ -122,6 +123,16 @@ impl EventSink for GatewayEventSink { } }); } + // settings_save_remote 迁入 agent-core 后不再直接持有 controller, + // 改成发事件;apply_config 这一步搬到这里,Gateway 的知识仍然只在本文件。 + SETTINGS_REMOTE_SAVED_EVENT => match serde_json::from_value(payload) { + Ok(config) => { + if let Err(error) = controller.apply_config(config) { + eprintln!("apply remote settings to gateway failed: {error}"); + } + } + Err(error) => eprintln!("settings:remote-saved 事件反序列化失败: {error}"), + }, WORKSPACE_ACTIVITY_EVENT => { self.forward_workspace_activity(&controller, &payload); } diff --git a/crates/agent-gui/src-tauri/src/lib.rs b/crates/agent-gui/src-tauri/src/lib.rs index 2f5c04423..4a9de6521 100644 --- a/crates/agent-gui/src-tauri/src/lib.rs +++ b/crates/agent-gui/src-tauri/src/lib.rs @@ -1,6 +1,5 @@ mod commands; mod gateway_sink; -mod runtime; mod services; mod tauri_commands; mod tauri_sink; @@ -467,7 +466,9 @@ fn dispatch_app_action(app: &tauri::AppHandle, action: AppAction) { // cron_apply 写路径(CAS),成功后 automation:cron-changed 会驱动 // 前端 store 与托盘勾选自然刷新。开关是后台动作,不呼出主窗口; // 结果经 feedback 事件给前端 toast(窗口可见时提示文案)。 - let Some(store) = app.try_state::>() else { + let Some(store) = + app.try_state::>() + else { return; }; let store = Arc::clone(store.inner()); @@ -499,7 +500,7 @@ fn dispatch_app_action(app: &tauri::AppHandle, action: AppAction) { } AppAction::OpenDataDir => { use tauri_plugin_opener::OpenerExt; - match commands::settings::config_dir() { + match agent_core::commands::settings::config_dir() { Ok(dir) => { if let Err(error) = app .opener() @@ -513,7 +514,8 @@ fn dispatch_app_action(app: &tauri::AppHandle, action: AppAction) { } AppAction::Quit => { let allow_exit = app.state::>(); - let terminal_registry = app.state::>(); + let terminal_registry = + app.state::>(); request_app_exit(app, allow_exit.inner(), terminal_registry.inner()); } } @@ -542,7 +544,7 @@ fn handle_global_shortcut( fn request_app_exit( app: &tauri::AppHandle, allow_exit: &AtomicBool, - terminal_registry: &runtime::terminal::TerminalSessionRegistry, + terminal_registry: &agent_core::runtime::terminal::TerminalSessionRegistry, ) { let running_count = terminal_registry.running_session_count(); if running_count > 0 { @@ -645,26 +647,34 @@ fn configure_windows_window_chrome(app: &tauri::App) -> tauri::Result<()> { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + // 必须在任何后端逻辑之前:agent-core 编译时不知道自己会被装进哪个产物, + // 版本号只能由宿主注入(MCP 的 clientInfo 会读它)。 + agent_core::set_app_version(app_version()); + let automation_store = Arc::new( - services::automation::AutomationStore::open() + agent_core::services::automation::AutomationStore::open() .expect("failed to initialize LiveAgent automation store"), ); - let automation_scheduler = Arc::new(services::automation::AutomationScheduler::new( - Arc::clone(&automation_store), - )); + let automation_scheduler = Arc::new( + agent_core::services::automation::AutomationScheduler::new(Arc::clone(&automation_store)), + ); let memory_store = Arc::new( - services::memory::MemoryStore::open().expect("failed to initialize LiveAgent memory store"), + agent_core::services::memory::MemoryStore::open() + .expect("failed to initialize LiveAgent memory store"), ); let provider_usage_service = - Arc::new(services::provider_usage::ProviderUsageService::default()); - let power_activity = Arc::new(services::power_activity::PowerActivityManager::default()); + Arc::new(agent_core::services::provider_usage::ProviderUsageService::default()); + let power_activity = + Arc::new(agent_core::services::power_activity::PowerActivityManager::default()); let managed_process_registry = - Arc::new(runtime::managed_process::ManagedProcessRegistry::open()); - let terminal_registry = Arc::new(runtime::terminal::TerminalSessionRegistry::default()); - let git_clone_task_registry = Arc::new(commands::git::GitCloneTaskRegistry::default()); - let sftp_registry = Arc::new(runtime::sftp::SftpSessionRegistry::new(Arc::clone( - &terminal_registry, - ))); + Arc::new(agent_core::runtime::managed_process::ManagedProcessRegistry::open()); + let terminal_registry = + Arc::new(agent_core::runtime::terminal::TerminalSessionRegistry::default()); + let git_clone_task_registry = + Arc::new(agent_core::commands::git::GitCloneTaskRegistry::default()); + let sftp_registry = Arc::new(agent_core::runtime::sftp::SftpSessionRegistry::new( + Arc::clone(&terminal_registry), + )); let allow_exit = Arc::new(AtomicBool::new(false)); let close_window_behavior = Arc::new(commands::app::CloseWindowBehaviorState::new( commands::app::CLOSE_WINDOW_BEHAVIOR_MINIMIZE, @@ -690,11 +700,15 @@ pub fn run() { ) .manage(Arc::new(commands::app::GlobalShortcutRegistry::default())) .manage(Arc::new(commands::app::WindowPinState::default())) - .manage(Arc::new(commands::mcp::McpRuntimeManager::default())) + .manage(Arc::new( + agent_core::commands::mcp::McpRuntimeManager::default(), + )) .manage(Arc::clone(&memory_store)) .manage(Arc::clone(&provider_usage_service)) .manage(Arc::clone(&power_activity)) - .manage(Arc::new(runtime::shell_runner::ShellRunRegistry::default())) + .manage(Arc::new( + agent_core::runtime::shell_runner::ShellRunRegistry::default(), + )) .manage(Arc::clone(&managed_process_registry)) .manage(Arc::clone(&terminal_registry)) .manage(Arc::clone(&sftp_registry)) @@ -703,7 +717,9 @@ pub fn run() { .manage(Arc::clone(&close_window_behavior)) .manage(Arc::clone(&automation_store)) .manage(Arc::clone(&automation_scheduler)) - .manage(Arc::new(commands::hook::HookScopeRegistry::default())) + .manage(Arc::new( + agent_core::commands::hook::HookScopeRegistry::default(), + )) .setup({ let terminal_registry = Arc::clone(&terminal_registry); let sftp_registry = Arc::clone(&sftp_registry); @@ -711,16 +727,19 @@ pub fn run() { let git_clone_task_registry = Arc::clone(&git_clone_task_registry); let provider_usage_service = Arc::clone(&provider_usage_service); move |app| { - commands::history_db::initialize_history_db()?; + agent_core::commands::history_db::initialize_history_db()?; configure_system_tray(app)?; #[cfg(target_os = "windows")] configure_windows_window_chrome(app)?; - if let Err(error) = commands::settings::initialize_system_proxy_from_db() { + if let Err(error) = + agent_core::commands::settings::initialize_system_proxy_from_db() + { eprintln!("failed to initialize system proxy state: {error}"); } commands::system::gc_upload_staging_on_startup(); app.manage(services::proxy::start_proxy_server()?); - if let Err(error) = services::skills::ensure_builtin_agent_skills_sync() { + if let Err(error) = agent_core::services::skills::ensure_builtin_agent_skills_sync() + { eprintln!("failed to seed builtin skills: {error}"); } // 事件总线:后端只管往这里发,谁听由这里决定。 @@ -728,7 +747,9 @@ pub fn run() { // 先建空总线,sink 稍后注册——总线用内部可变性,允许后置。 let events = Arc::new(agent_core::events::EventBus::new()); let workspace_watch = Arc::new( - services::workspace_watch::WorkspaceWatchService::new(Arc::clone(&events)), + agent_core::services::workspace_watch::WorkspaceWatchService::new(Arc::clone( + &events, + )), ); let gateway_controller = Arc::new(services::gateway::GatewayController::new( app.handle().clone(), @@ -754,10 +775,12 @@ pub fn run() { sftp_registry.set_event_bus(Arc::clone(&events)); managed_process_registry.spawn_startup_reconcile(); managed_process_registry.spawn_monitor(); - automation_store.set_notifier(services::automation::AutomationNotifier { - events: Arc::clone(&events), - scheduler: Arc::downgrade(&automation_scheduler), - }); + automation_store.set_notifier( + agent_core::services::automation::AutomationNotifier { + events: Arc::clone(&events), + scheduler: Arc::downgrade(&automation_scheduler), + }, + ); Arc::clone(&automation_scheduler).start(); app.manage(Arc::clone(&gateway_controller)); if let Err(error) = gateway_controller.start() { diff --git a/crates/agent-gui/src-tauri/src/services/gateway/chat.rs b/crates/agent-gui/src-tauri/src/services/gateway/chat.rs index ae538d1ee..ffb448f86 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/chat.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/chat.rs @@ -6,7 +6,7 @@ use tauri::Emitter; use tokio::sync::oneshot; use uuid::Uuid; -use crate::services::chat_run_ledger::ChatRunLedgerEntry; +use agent_core::services::chat_run_ledger::ChatRunLedgerEntry; use super::*; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/chat_inbox.rs b/crates/agent-gui/src-tauri/src/services/gateway/chat_inbox.rs index 6db0681a7..e98b388c7 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/chat_inbox.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/chat_inbox.rs @@ -3,7 +3,7 @@ use std::time::{Duration, Instant}; use serde_json::json; use tauri::Emitter; -use crate::services::chat_run_ledger::{ChatRunLedger, ChatRunLedgerState}; +use agent_core::services::chat_run_ledger::{ChatRunLedger, ChatRunLedgerState}; use super::*; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/connection.rs b/crates/agent-gui/src-tauri/src/services/gateway/connection.rs index 429b9dce9..01918e93d 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/connection.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/connection.rs @@ -9,9 +9,9 @@ use tauri::Emitter; use tokio::sync::{mpsc, watch, OwnedSemaphorePermit, Semaphore}; use tokio_tungstenite::tungstenite::Message as WsMessage; -use crate::commands::settings::RemoteSettingsPayload; -use crate::runtime::terminal::TerminalEventPayload; use crate::services::gateway_bridge; +use agent_core::commands::settings::RemoteSettingsPayload; +use agent_core::runtime::terminal::TerminalEventPayload; use super::gateway_proto::v2; use super::*; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/controller.rs b/crates/agent-gui/src-tauri/src/services/gateway/controller.rs index 0eaf185d1..ac50891db 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/controller.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/controller.rs @@ -6,19 +6,19 @@ use serde_json::{json, Value}; use tauri::Emitter; use tokio::sync::watch; -use crate::commands::git::GitCloneTaskRegistry; -use crate::commands::settings::{ +use crate::services::tunnel::{TunnelProxy, TunnelStore}; +use agent_core::commands::git::GitCloneTaskRegistry; +use agent_core::commands::settings::{ load_remote_settings, normalize_remote_settings_payload, open_db, RemoteSettingsPayload, }; -use crate::runtime::managed_process::ManagedProcessRegistry; -use crate::runtime::sftp::SftpSessionRegistry; -use crate::runtime::terminal::TerminalSessionRegistry; -use crate::services::automation::AutomationStore; -use crate::services::chat_run_ledger::ChatRunLedger; -use crate::services::memory::MemoryStore; -use crate::services::provider_usage::ProviderUsageService; -use crate::services::tunnel::{TunnelProxy, TunnelStore}; -use crate::services::workspace_watch::WorkspaceWatchService; +use agent_core::runtime::managed_process::ManagedProcessRegistry; +use agent_core::runtime::sftp::SftpSessionRegistry; +use agent_core::runtime::terminal::TerminalSessionRegistry; +use agent_core::services::automation::AutomationStore; +use agent_core::services::chat_run_ledger::ChatRunLedger; +use agent_core::services::memory::MemoryStore; +use agent_core::services::provider_usage::ProviderUsageService; +use agent_core::services::workspace_watch::WorkspaceWatchService; use super::*; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/envelope_handler.rs b/crates/agent-gui/src-tauri/src/services/gateway/envelope_handler.rs index e37b6afff..e6df11824 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/envelope_handler.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/envelope_handler.rs @@ -3,13 +3,13 @@ use std::sync::Arc; use serde_json::Value; use tauri::Emitter; -use crate::commands::chat_history::{self}; -use crate::commands::settings::{ +use crate::services::gateway_bridge; +use agent_core::commands::chat_history::{self}; +use agent_core::commands::settings::{ apply_ssh_patch_with_conn, open_db, redact_gateway_settings_sync_payload, reset_runtime_ssh_known_host, SSH_PATCH_FIELD, }; -use crate::services::gateway_bridge; -use crate::services::workspace_watch::WatchSource; +use agent_core::services::workspace_watch::WatchSource; use super::*; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/history_sync.rs b/crates/agent-gui/src-tauri/src/services/gateway/history_sync.rs index ea69f1446..09b335e19 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/history_sync.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/history_sync.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -use crate::commands::chat_history::ChatHistorySummary; +use agent_core::commands::chat_history::ChatHistorySummary; use super::*; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/managed_process.rs b/crates/agent-gui/src-tauri/src/services/gateway/managed_process.rs index c2dd993d1..e49477eff 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/managed_process.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/managed_process.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use crate::runtime::managed_process::{ +use agent_core::runtime::managed_process::{ ManagedProcessRecord, ManagedProcessRegistry, ManagedProcessSnapshot, }; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/mod.rs b/crates/agent-gui/src-tauri/src/services/gateway/mod.rs index dfedceace..93fa3ecd3 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/mod.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/mod.rs @@ -19,17 +19,17 @@ use std::time::{Duration, Instant}; use serde_json::Value; use tokio::sync::{mpsc, oneshot, watch}; -use crate::commands::git::GitCloneTaskRegistry; -use crate::commands::settings::RemoteSettingsPayload; -use crate::runtime::managed_process::ManagedProcessRegistry; -use crate::runtime::sftp::SftpSessionRegistry; -use crate::runtime::terminal::TerminalSessionRegistry; -use crate::services::automation::AutomationStore; -use crate::services::chat_run_ledger::ChatRunLedger; -use crate::services::memory::MemoryStore; -use crate::services::provider_usage::ProviderUsageService; use crate::services::tunnel::{TunnelProxy, TunnelStore}; -use crate::services::workspace_watch::WorkspaceWatchService; +use agent_core::commands::git::GitCloneTaskRegistry; +use agent_core::commands::settings::RemoteSettingsPayload; +use agent_core::runtime::managed_process::ManagedProcessRegistry; +use agent_core::runtime::sftp::SftpSessionRegistry; +use agent_core::runtime::terminal::TerminalSessionRegistry; +use agent_core::services::automation::AutomationStore; +use agent_core::services::chat_run_ledger::ChatRunLedger; +use agent_core::services::memory::MemoryStore; +use agent_core::services::provider_usage::ProviderUsageService; +use agent_core::services::workspace_watch::WorkspaceWatchService; /// 网关 v2 protobuf 生成模块。业务消息与帧壳属于同一包。 /// 仅生成消息,不生成客户端或服务端。 diff --git a/crates/agent-gui/src-tauri/src/services/gateway/settings_sync.rs b/crates/agent-gui/src-tauri/src/services/gateway/settings_sync.rs index 9906c2c06..3da5d8376 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/settings_sync.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/settings_sync.rs @@ -1,7 +1,7 @@ use serde_json::Value; use uuid::Uuid; -use crate::commands::settings::{ +use agent_core::commands::settings::{ load_gateway_settings_sync_snapshot, open_db, redact_gateway_settings_sync_payload, PROVIDER_API_KEY_UPDATES_FIELD, PROVIDER_USAGE_QUERY_SECRET_UPDATES_FIELD, SSH_PATCH_FIELD, SSH_SECRET_UPDATES_FIELD, SYSTEM_PROXY_PASSWORD_UPDATE_FIELD, diff --git a/crates/agent-gui/src-tauri/src/services/gateway/sftp.rs b/crates/agent-gui/src-tauri/src/services/gateway/sftp.rs index bfa93455c..e2002c052 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/sftp.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/sftp.rs @@ -1,6 +1,6 @@ use uuid::Uuid; -use crate::runtime::sftp::{ +use agent_core::runtime::sftp::{ SftpActionResponse, SftpEntry, SftpEventPayload, SftpListResponse, SftpStatResponse, SftpTransferResponse, SftpTransferState, }; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/terminal.rs b/crates/agent-gui/src-tauri/src/services/gateway/terminal.rs index 8f34e40bc..298eb3749 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/terminal.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/terminal.rs @@ -7,11 +7,11 @@ use tokio_stream::StreamExt as _; use tokio_tungstenite::tungstenite::Message as WsMessage; use uuid::Uuid; -use crate::commands::settings::RemoteSettingsPayload; -use crate::runtime::project_path::{ +use agent_core::commands::settings::RemoteSettingsPayload; +use agent_core::runtime::project_path::{ project_path_key as normalize_project_path_key, project_path_keys_equal, }; -use crate::runtime::terminal::{ +use agent_core::runtime::terminal::{ normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, terminal_shell_options, SshLocalForwardActionResponse, SshLocalForwardEventPayload, SshLocalForwardListResponse, SshLocalForwardRecord, SshTerminalTabRecord, diff --git a/crates/agent-gui/src-tauri/src/services/gateway/tests.rs b/crates/agent-gui/src-tauri/src/services/gateway/tests.rs index f91a08860..782733723 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/tests.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/tests.rs @@ -9,9 +9,9 @@ use super::{ GATEWAY_RECONNECT_STABLE_AFTER, GATEWAY_RUNTIME_STATUS_REPUBLISH_MAX_AGE, GATEWAY_WEBVIEW_REPORT_FRESH_WINDOW, }; -use crate::commands::settings::RemoteSettingsPayload; use crate::services::gateway_bridge; -use crate::services::provider_usage::{ProviderUsageResult, UsageData}; +use agent_core::commands::settings::RemoteSettingsPayload; +use agent_core::services::provider_usage::{ProviderUsageResult, UsageData}; use serde_json::{json, Value}; use std::time::{Duration, Instant}; diff --git a/crates/agent-gui/src-tauri/src/services/gateway/util.rs b/crates/agent-gui/src-tauri/src/services/gateway/util.rs index f076b9875..cd999b978 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway/util.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway/util.rs @@ -1,6 +1,6 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use crate::commands::settings::RemoteSettingsPayload; +use agent_core::commands::settings::RemoteSettingsPayload; use super::{ GatewayStatusSnapshot, GATEWAY_CHAT_RUNTIME_WAKE_REQUEST_PREFIX, GATEWAY_RECONNECT_MAX, diff --git a/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs b/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs index 3b2707fb7..f76b597f0 100644 --- a/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs +++ b/crates/agent-gui/src-tauri/src/services/gateway_bridge.rs @@ -3,7 +3,7 @@ use std::{collections::HashSet, sync::Arc}; use serde::Deserialize; use serde_json::{json, Value}; -use crate::commands::{ +use agent_core::commands::{ chat_file_links::open_chat_file_link_for_conversation, chat_history, chat_history::ChatHistoryMessageRef, @@ -14,24 +14,25 @@ use crate::commands::{ }, git::{git_gateway_clone_task_action_sync, GitCloneTaskRegistry}, settings::{load_providers, open_db}, - system::{ - system_create_project_folder_sync, system_import_uploaded_readable_files_sync, - system_list_skill_files_sync, system_read_skill_metadata_sync, system_read_skill_text_sync, - system_read_uploaded_image_preview_sync, SystemReadableFileUploadInput, - }, }; -use crate::services::automation::{ - validate_cron_expression, AutomationApplyInput, AutomationStore, +// system 仍在壳里:它是前端专属命令(文件对话框、技能文件读取),不进 agent-core。 +use crate::commands::system::{ + system_create_project_folder_sync, system_import_uploaded_readable_files_sync, + system_list_skill_files_sync, system_read_skill_metadata_sync, system_read_skill_text_sync, + system_read_uploaded_image_preview_sync, SystemReadableFileUploadInput, }; use crate::services::gateway::proto; -use crate::services::memory::{ +use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationStore, +}; +use agent_core::services::memory::{ MemoryAcceptArgs, MemoryBatchArgs, MemoryDeleteArgs, MemoryDeleteProjectArgs, MemoryListArgs, MemoryOrganizeDueClaimArgs, MemoryOrganizeRunCreateArgs, MemoryOrganizeRunListArgs, MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryQuotaSummaryArgs, MemoryReadArgs, MemoryRecentRejectionsArgs, MemorySearchArgs, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, }; -use crate::services::provider_usage::{ProviderUsageResult, ProviderUsageService}; -use crate::services::skills::system_manage_skill_sync; +use agent_core::services::provider_usage::{ProviderUsageResult, ProviderUsageService}; +use agent_core::services::skills::system_manage_skill_sync; const DEFAULT_HISTORY_LIST_PAGE: i32 = 1; const DEFAULT_HISTORY_LIST_PAGE_SIZE: i32 = 80; @@ -373,7 +374,7 @@ pub async fn handle_provider_list() -> Result Result { - let models_json = crate::services::provider_models::fetch_provider_models( + let models_json = agent_core::services::provider_models::fetch_provider_models( request.provider_type.trim(), request.base_url.trim(), request.api_key.trim(), @@ -1406,7 +1407,7 @@ mod tests { is_builtin_share_tool_name, parse_runs_limit, redact_builtin_tool_content_json, sanitize_provider_summaries, }; - use crate::commands::chat_history::{ + use agent_core::commands::chat_history::{ self, history_message_content_hash, ChatHistoryMessageRef, ChatHistorySegmentRecord, }; diff --git a/crates/agent-gui/src-tauri/src/services/mod.rs b/crates/agent-gui/src-tauri/src/services/mod.rs index 4e49572c7..0023c05d6 100644 --- a/crates/agent-gui/src-tauri/src/services/mod.rs +++ b/crates/agent-gui/src-tauri/src/services/mod.rs @@ -1,14 +1,12 @@ -pub mod automation; -pub mod chat_run_ledger; +//! 壳专属服务。其余已迁入 agent-core。 +//! +//! - `gateway` / `gateway_bridge`:过渡设施,阶段 4 删除 +//! - `tunnel`:按 P1-07 需重写(P2-30) +//! - `proxy`:给 webview 供图的本地 HTTP 服务,阶段 2 末由 agent-backend 取代 +//! - `tray`:托盘,前端专属 + pub mod gateway; pub mod gateway_bridge; -pub mod memory; -pub mod power_activity; -pub mod provider_models; -pub mod provider_usage; pub mod proxy; -pub mod skills; -pub mod system_proxy; pub mod tray; pub mod tunnel; -pub mod workspace_watch; diff --git a/crates/agent-gui/src-tauri/src/services/proxy.rs b/crates/agent-gui/src-tauri/src/services/proxy.rs index 5ac9482ad..56c438682 100644 --- a/crates/agent-gui/src-tauri/src/services/proxy.rs +++ b/crates/agent-gui/src-tauri/src/services/proxy.rs @@ -130,7 +130,7 @@ async fn handle_image_proxy(Query(query): Query, headers: Heade // 图片外链与商店链路同语义:恒随应用代理出网(未启用=直连,配置异常 // 502 fail fast)。 请求无法携带自定义头,因此不走 per-request 开关。 - let client = match crate::services::system_proxy::cached_client() { + let client = match agent_core::services::system_proxy::cached_client() { Ok(client) => client, Err(error) => { return error_response( @@ -370,7 +370,7 @@ async fn handle_proxy( // 系统代理未启用时 cached_client 返回直连 client(勾选但全局关闭 = 直连); // 代理配置异常则 fail fast,绝不静默降级为直连。 let client = if use_system_proxy { - match crate::services::system_proxy::cached_client() { + match agent_core::services::system_proxy::cached_client() { Ok(client) => client, Err(error) => { return error_response( diff --git a/crates/agent-gui/src-tauri/src/services/tunnel/store.rs b/crates/agent-gui/src-tauri/src/services/tunnel/store.rs index 9b493f6b2..1fe63a74e 100644 --- a/crates/agent-gui/src-tauri/src/services/tunnel/store.rs +++ b/crates/agent-gui/src-tauri/src/services/tunnel/store.rs @@ -12,9 +12,9 @@ use serde::{Deserialize, Serialize}; use tauri::Emitter; use uuid::Uuid; -use crate::commands::settings::open_db; -use crate::runtime::project_path::project_path_key as normalize_project_path_key; use crate::services::gateway::{now_unix_seconds, proto}; +use agent_core::commands::settings::open_db; +use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; use super::{ tunnel_health_payload_from_proto, validate_tunnel_target_url, GatewayTunnelCreateInput, diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs b/crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs index 5c74d69bb..054f5f2ee 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/chat_file_links.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::chat_file_links::*; -use crate::runtime::platform::expand_tilde_path; +use agent_core::commands::chat_file_links::*; +use agent_core::runtime::platform::expand_tilde_path; use serde::Serialize; use std::fs; use std::io::Read; @@ -28,7 +28,7 @@ pub async fn open_chat_file_link( column: Option, open_in_file_manager: Option, ) -> Result { - crate::commands::chat_file_links::open_chat_file_link( + agent_core::commands::chat_file_links::open_chat_file_link( conversation_id, workdir, path, diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs b/crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs index 224ff8613..78843e53f 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/chat_history.rs @@ -6,12 +6,10 @@ #![allow(unused_imports)] -use crate::commands::chat_history::*; -use crate::{ - commands::{history_db, subagent_store}, - services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}, -}; +use agent_core::commands::chat_history::*; +use agent_core::commands::{history_db, subagent_store}; use agent_core::events::EventBus; +use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; use chrono::{Local, LocalResult, NaiveDate, TimeZone}; use regex::Regex; use rusqlite::{params, Connection, OptionalExtension}; @@ -34,7 +32,7 @@ pub async fn chat_history_replace_from_message( expected_revision: String, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_replace_from_message( + agent_core::commands::chat_history::chat_history_replace_from_message( id, base_message_ref, replacement_message, @@ -50,7 +48,7 @@ pub async fn chat_history_delete( id: String, events: tauri::State<'_, Arc>, ) -> Result<(), String> { - crate::commands::chat_history::chat_history_delete(id, events.inner()).await + agent_core::commands::chat_history::chat_history_delete(id, events.inner()).await } #[tauri::command] @@ -60,12 +58,12 @@ pub async fn chat_history_list( cwd: Option, cwd_empty: Option, ) -> Result { - crate::commands::chat_history::chat_history_list(page, page_size, cwd, cwd_empty).await + agent_core::commands::chat_history::chat_history_list(page, page_size, cwd, cwd_empty).await } #[tauri::command] pub async fn chat_history_workdirs() -> Result { - crate::commands::chat_history::chat_history_workdirs().await + agent_core::commands::chat_history::chat_history_workdirs().await } #[tauri::command] @@ -73,14 +71,14 @@ pub async fn chat_history_shared_list( page: i64, page_size: i64, ) -> Result { - crate::commands::chat_history::chat_history_shared_list(page, page_size).await + agent_core::commands::chat_history::chat_history_shared_list(page, page_size).await } #[tauri::command] pub async fn chat_history_search( args: ChatHistorySearchArgs, ) -> Result { - crate::commands::chat_history::chat_history_search(args).await + agent_core::commands::chat_history::chat_history_search(args).await } #[tauri::command] @@ -91,7 +89,7 @@ pub async fn chat_history_get_window( expected_revision: Option, include_active_segment: bool, ) -> Result { - crate::commands::chat_history::chat_history_get_window( + agent_core::commands::chat_history::chat_history_get_window( id, max_messages, before_offset, @@ -106,7 +104,7 @@ pub async fn chat_history_upsert( input: ChatHistoryUpsertInput, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_upsert(input, events.inner()).await + agent_core::commands::chat_history::chat_history_upsert(input, events.inner()).await } #[tauri::command] @@ -114,7 +112,8 @@ pub async fn chat_history_upsert_active_segment( input: ChatHistorySegmentMutationInput, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_upsert_active_segment(input, events.inner()).await + agent_core::commands::chat_history::chat_history_upsert_active_segment(input, events.inner()) + .await } #[tauri::command] @@ -122,7 +121,7 @@ pub async fn chat_history_append_segment( input: ChatHistorySegmentMutationInput, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_append_segment(input, events.inner()).await + agent_core::commands::chat_history::chat_history_append_segment(input, events.inner()).await } #[tauri::command] @@ -131,7 +130,7 @@ pub async fn chat_history_rename( title: String, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_rename(id, title, events.inner()).await + agent_core::commands::chat_history::chat_history_rename(id, title, events.inner()).await } #[tauri::command] @@ -140,7 +139,7 @@ pub async fn chat_history_set_pinned( is_pinned: bool, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_set_pinned(id, is_pinned, events.inner()).await + agent_core::commands::chat_history::chat_history_set_pinned(id, is_pinned, events.inner()).await } #[tauri::command] @@ -149,13 +148,17 @@ pub async fn chat_history_set_model( selected_model_json: String, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_set_model(id, selected_model_json, events.inner()) - .await + agent_core::commands::chat_history::chat_history_set_model( + id, + selected_model_json, + events.inner(), + ) + .await } #[tauri::command] pub async fn chat_history_share_get(id: String) -> Result { - crate::commands::chat_history::chat_history_share_get(id).await + agent_core::commands::chat_history::chat_history_share_get(id).await } #[tauri::command] @@ -165,7 +168,7 @@ pub async fn chat_history_share_set( redact_tool_content: Option, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_share_set( + agent_core::commands::chat_history::chat_history_share_set( id, enabled, redact_tool_content, @@ -180,5 +183,6 @@ pub async fn chat_history_branch( base_message_ref: ChatHistoryMessageRef, events: tauri::State<'_, Arc>, ) -> Result { - crate::commands::chat_history::chat_history_branch(id, base_message_ref, events.inner()).await + agent_core::commands::chat_history::chat_history_branch(id, base_message_ref, events.inner()) + .await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/cron.rs b/crates/agent-gui/src-tauri/src/tauri_commands/cron.rs index 60260fa97..3bd9d1b22 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/cron.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/cron.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::cron::*; -use crate::services::automation::{ +use agent_core::commands::cron::*; +use agent_core::services::automation::{ validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, @@ -16,14 +16,14 @@ use std::sync::Arc; #[tauri::command(rename_all = "snake_case")] pub async fn cron_validate_expression(expression: String) -> Result<(), String> { - crate::commands::cron::cron_validate_expression(expression).await + agent_core::commands::cron::cron_validate_expression(expression).await } #[tauri::command(rename_all = "snake_case")] pub async fn automation_snapshot( store: tauri::State<'_, Arc>, ) -> Result { - crate::commands::cron::automation_snapshot(store.inner()).await + agent_core::commands::cron::automation_snapshot(store.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -31,7 +31,7 @@ pub async fn automation_cron_apply( input: AutomationApplyInput, store: tauri::State<'_, Arc>, ) -> Result { - crate::commands::cron::automation_cron_apply(input, store.inner()).await + agent_core::commands::cron::automation_cron_apply(input, store.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -39,7 +39,7 @@ pub async fn automation_hooks_apply( input: AutomationApplyInput, store: tauri::State<'_, Arc>, ) -> Result { - crate::commands::cron::automation_hooks_apply(input, store.inner()).await + agent_core::commands::cron::automation_hooks_apply(input, store.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -48,7 +48,7 @@ pub async fn automation_list_runs( limit: Option, store: tauri::State<'_, Arc>, ) -> Result, String> { - crate::commands::cron::automation_list_runs(task_id, limit, store.inner()).await + agent_core::commands::cron::automation_list_runs(task_id, limit, store.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -56,7 +56,7 @@ pub async fn automation_clear_runs( task_id: String, store: tauri::State<'_, Arc>, ) -> Result { - crate::commands::cron::automation_clear_runs(task_id, store.inner()).await + agent_core::commands::cron::automation_clear_runs(task_id, store.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -64,14 +64,14 @@ pub async fn automation_run_cron_now( task_id: String, store: tauri::State<'_, Arc>, ) -> Result { - crate::commands::cron::automation_run_cron_now(task_id, store.inner()).await + agent_core::commands::cron::automation_run_cron_now(task_id, store.inner()).await } #[tauri::command(rename_all = "snake_case")] pub async fn automation_claim_prompt_runs( store: tauri::State<'_, Arc>, ) -> Result, String> { - crate::commands::cron::automation_claim_prompt_runs(store.inner()).await + agent_core::commands::cron::automation_claim_prompt_runs(store.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -79,7 +79,7 @@ pub async fn automation_release_prompt_run( execution_id: String, store: tauri::State<'_, Arc>, ) -> Result<(), String> { - crate::commands::cron::automation_release_prompt_run(execution_id, store.inner()).await + agent_core::commands::cron::automation_release_prompt_run(execution_id, store.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -87,5 +87,5 @@ pub async fn automation_complete_prompt_run( input: CompletePromptRunInput, store: tauri::State<'_, Arc>, ) -> Result { - crate::commands::cron::automation_complete_prompt_run(input, store.inner()).await + agent_core::commands::cron::automation_complete_prompt_run(input, store.inner()).await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/fs.rs b/crates/agent-gui/src-tauri/src/tauri_commands/fs.rs index 43becaa14..1f0dd4f4d 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/fs.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/fs.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::fs::*; -use crate::runtime::platform::expand_tilde_path; +use agent_core::commands::fs::*; +use agent_core::runtime::platform::expand_tilde_path; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; use globset::{Glob, GlobSet, GlobSetBuilder}; use ignore::WalkBuilder; @@ -36,7 +36,7 @@ pub async fn fs_read_image_source( source_type: Option, mime_type: Option, ) -> Result { - crate::commands::fs::fs_read_image_source(workdir, source, source_type, mime_type).await + agent_core::commands::fs::fs_read_image_source(workdir, source, source_type, mime_type).await } #[tauri::command(rename_all = "snake_case")] @@ -44,7 +44,7 @@ pub async fn fs_read_workspace_image( workdir: String, path: String, ) -> Result { - crate::commands::fs::fs_read_workspace_image(workdir, path).await + agent_core::commands::fs::fs_read_workspace_image(workdir, path).await } #[tauri::command(rename_all = "snake_case")] @@ -58,7 +58,7 @@ pub async fn fs_read_text( cell_start: Option, cell_limit: Option, ) -> Result { - crate::commands::fs::fs_read_text( + agent_core::commands::fs::fs_read_text( workdir, path, start_line, limit, page_start, page_limit, cell_start, cell_limit, ) .await @@ -69,7 +69,7 @@ pub async fn fs_read_editable_text( workdir: String, path: String, ) -> Result { - crate::commands::fs::fs_read_editable_text(workdir, path).await + agent_core::commands::fs::fs_read_editable_text(workdir, path).await } #[tauri::command(rename_all = "snake_case")] @@ -77,7 +77,7 @@ pub async fn fs_path_status( workdir: String, path: String, ) -> Result { - crate::commands::fs::fs_path_status(workdir, path).await + agent_core::commands::fs::fs_path_status(workdir, path).await } #[tauri::command(rename_all = "snake_case")] @@ -89,7 +89,7 @@ pub async fn fs_write_text( expected_mtime_ms: Option, expected_content_hash: Option, ) -> Result { - crate::commands::fs::fs_write_text( + agent_core::commands::fs::fs_write_text( workdir, path, content, @@ -111,7 +111,7 @@ pub async fn fs_edit_text( expected_mtime_ms: Option, expected_content_hash: Option, ) -> Result { - crate::commands::fs::fs_edit_text( + agent_core::commands::fs::fs_edit_text( workdir, path, old_string, @@ -126,7 +126,7 @@ pub async fn fs_edit_text( #[tauri::command(rename_all = "snake_case")] pub async fn fs_delete(workdir: String, path: String) -> Result { - crate::commands::fs::fs_delete(workdir, path).await + agent_core::commands::fs::fs_delete(workdir, path).await } #[tauri::command(rename_all = "snake_case")] @@ -135,7 +135,7 @@ pub async fn fs_open_workspace_path( path: String, mode: Option, ) -> Result { - crate::commands::fs::fs_open_workspace_path(workdir, path, mode).await + agent_core::commands::fs::fs_open_workspace_path(workdir, path, mode).await } #[tauri::command(rename_all = "snake_case")] @@ -143,7 +143,7 @@ pub async fn fs_create_dir( workdir: String, path: String, ) -> Result { - crate::commands::fs::fs_create_dir(workdir, path).await + agent_core::commands::fs::fs_create_dir(workdir, path).await } #[tauri::command(rename_all = "snake_case")] @@ -152,12 +152,12 @@ pub async fn fs_rename( from_path: String, to_path: String, ) -> Result { - crate::commands::fs::fs_rename(workdir, from_path, to_path).await + agent_core::commands::fs::fs_rename(workdir, from_path, to_path).await } #[tauri::command(rename_all = "snake_case")] pub async fn fs_roots() -> Result { - crate::commands::fs::fs_roots().await + agent_core::commands::fs::fs_roots().await } #[tauri::command(rename_all = "snake_case")] @@ -165,7 +165,7 @@ pub async fn fs_list_dirs( path: String, max_results: Option, ) -> Result { - crate::commands::fs::fs_list_dirs(path, max_results).await + agent_core::commands::fs::fs_list_dirs(path, max_results).await } #[tauri::command(rename_all = "snake_case")] @@ -177,7 +177,7 @@ pub async fn fs_list( max_results: Option, show_hidden: Option, ) -> Result { - crate::commands::fs::fs_list(workdir, path, depth, offset, max_results, show_hidden).await + agent_core::commands::fs::fs_list(workdir, path, depth, offset, max_results, show_hidden).await } #[tauri::command(rename_all = "snake_case")] @@ -189,7 +189,7 @@ pub async fn fs_glob( max_results: Option, sort_by: Option, ) -> Result { - crate::commands::fs::fs_glob(workdir, path, pattern, offset, max_results, sort_by).await + agent_core::commands::fs::fs_glob(workdir, path, pattern, offset, max_results, sort_by).await } #[tauri::command(rename_all = "snake_case")] @@ -205,7 +205,7 @@ pub async fn fs_grep( context: Option, multiline: Option, ) -> Result { - crate::commands::fs::fs_grep( + agent_core::commands::fs::fs_grep( workdir, path, pattern, @@ -227,5 +227,5 @@ pub async fn fs_mention_list( query: Option, show_hidden: Option, ) -> Result { - crate::commands::fs::fs_mention_list(workdir, max_results, query, show_hidden).await + agent_core::commands::fs::fs_mention_list(workdir, max_results, query, show_hidden).await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/git.rs b/crates/agent-gui/src-tauri/src/tauri_commands/git.rs index 37bc82a00..5adff9815 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/git.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/git.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::git::*; -use crate::runtime::process::{ +use agent_core::commands::git::*; +use agent_core::runtime::process::{ configure_child_process_group, kill_child_process_tree_best_effort, terminate_process_tree_by_pid, }; @@ -29,17 +29,17 @@ use wait_timeout::ChildExt; #[tauri::command(rename_all = "snake_case")] pub async fn git_status(workdir: String) -> Result { - crate::commands::git::git_status(workdir).await + agent_core::commands::git::git_status(workdir).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_discover_repositories(workdir: String) -> Result { - crate::commands::git::git_discover_repositories(workdir).await + agent_core::commands::git::git_discover_repositories(workdir).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_branches(workdir: String) -> Result { - crate::commands::git::git_branches(workdir).await + agent_core::commands::git::git_branches(workdir).await } #[tauri::command(rename_all = "snake_case")] @@ -48,7 +48,7 @@ pub async fn git_switch_branch( branch: String, kind: Option, ) -> Result { - crate::commands::git::git_switch_branch(workdir, branch, kind).await + agent_core::commands::git::git_switch_branch(workdir, branch, kind).await } #[tauri::command(rename_all = "snake_case")] @@ -57,7 +57,7 @@ pub async fn git_create_branch( branch: String, start_point: Option, ) -> Result { - crate::commands::git::git_create_branch(workdir, branch, start_point).await + agent_core::commands::git::git_create_branch(workdir, branch, start_point).await } #[tauri::command(rename_all = "snake_case")] @@ -67,7 +67,7 @@ pub async fn git_init( user_name: Option, user_email: Option, ) -> Result { - crate::commands::git::git_init(workdir, branch, user_name, user_email).await + agent_core::commands::git::git_init(workdir, branch, user_name, user_email).await } #[tauri::command(rename_all = "snake_case")] @@ -77,7 +77,7 @@ pub async fn git_clone_repository( remote_url: String, branch: Option, ) -> Result { - crate::commands::git::git_clone_repository(parent, name, remote_url, branch).await + agent_core::commands::git::git_clone_repository(parent, name, remote_url, branch).await } #[tauri::command(rename_all = "snake_case")] @@ -88,7 +88,7 @@ pub fn git_clone_repository_start( remote_url: String, branch: Option, ) -> Result { - crate::commands::git::git_clone_repository_start( + agent_core::commands::git::git_clone_repository_start( registry.inner(), parent, name, @@ -101,7 +101,7 @@ pub fn git_clone_repository_start( pub fn git_clone_repository_tasks( registry: tauri::State<'_, Arc>, ) -> Result, String> { - crate::commands::git::git_clone_repository_tasks(registry.inner()) + agent_core::commands::git::git_clone_repository_tasks(registry.inner()) } #[tauri::command(rename_all = "snake_case")] @@ -109,7 +109,7 @@ pub fn git_clone_repository_cancel( registry: tauri::State<'_, Arc>, task_id: String, ) -> Result { - crate::commands::git::git_clone_repository_cancel(registry.inner(), task_id) + agent_core::commands::git::git_clone_repository_cancel(registry.inner(), task_id) } #[tauri::command(rename_all = "snake_case")] @@ -117,14 +117,14 @@ pub fn git_clone_repository_dismiss( registry: tauri::State<'_, Arc>, task_id: String, ) -> Result, String> { - crate::commands::git::git_clone_repository_dismiss(registry.inner(), task_id) + agent_core::commands::git::git_clone_repository_dismiss(registry.inner(), task_id) } #[tauri::command(rename_all = "snake_case")] pub async fn git_list_remote_branches( remote_url: String, ) -> Result { - crate::commands::git::git_list_remote_branches(remote_url).await + agent_core::commands::git::git_list_remote_branches(remote_url).await } #[tauri::command(rename_all = "snake_case")] @@ -133,7 +133,7 @@ pub async fn git_diff( mode: Option, path: Option, ) -> Result { - crate::commands::git::git_diff(workdir, mode, path).await + agent_core::commands::git::git_diff(workdir, mode, path).await } #[tauri::command(rename_all = "snake_case")] @@ -142,7 +142,7 @@ pub async fn git_log( limit: Option, skip: Option, ) -> Result { - crate::commands::git::git_log(workdir, limit, skip).await + agent_core::commands::git::git_log(workdir, limit, skip).await } #[tauri::command(rename_all = "snake_case")] @@ -150,7 +150,7 @@ pub async fn git_commit_details( workdir: String, commit: String, ) -> Result { - crate::commands::git::git_commit_details(workdir, commit).await + agent_core::commands::git::git_commit_details(workdir, commit).await } #[tauri::command(rename_all = "snake_case")] @@ -158,7 +158,7 @@ pub async fn git_compare_commit_with_remote( workdir: String, commit: String, ) -> Result { - crate::commands::git::git_compare_commit_with_remote(workdir, commit).await + agent_core::commands::git::git_compare_commit_with_remote(workdir, commit).await } #[tauri::command(rename_all = "snake_case")] @@ -167,27 +167,27 @@ pub async fn git_commit_diff( commit: String, path: Option, ) -> Result { - crate::commands::git::git_commit_diff(workdir, commit, path).await + agent_core::commands::git::git_commit_diff(workdir, commit, path).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_stage(workdir: String, path: String) -> Result { - crate::commands::git::git_stage(workdir, path).await + agent_core::commands::git::git_stage(workdir, path).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_stage_all(workdir: String) -> Result { - crate::commands::git::git_stage_all(workdir).await + agent_core::commands::git::git_stage_all(workdir).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_unstage(workdir: String, path: String) -> Result { - crate::commands::git::git_unstage(workdir, path).await + agent_core::commands::git::git_unstage(workdir, path).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_unstage_all(workdir: String) -> Result { - crate::commands::git::git_unstage_all(workdir).await + agent_core::commands::git::git_unstage_all(workdir).await } #[tauri::command(rename_all = "snake_case")] @@ -196,12 +196,12 @@ pub async fn git_discard( path: String, old_path: Option, ) -> Result { - crate::commands::git::git_discard(workdir, path, old_path).await + agent_core::commands::git::git_discard(workdir, path, old_path).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_discard_all(workdir: String) -> Result { - crate::commands::git::git_discard_all(workdir).await + agent_core::commands::git::git_discard_all(workdir).await } #[tauri::command(rename_all = "snake_case")] @@ -209,7 +209,7 @@ pub async fn git_add_to_gitignore( workdir: String, path: String, ) -> Result { - crate::commands::git::git_add_to_gitignore(workdir, path).await + agent_core::commands::git::git_add_to_gitignore(workdir, path).await } #[tauri::command(rename_all = "snake_case")] @@ -217,22 +217,22 @@ pub async fn git_open_system_file_location( workdir: String, path: String, ) -> Result { - crate::commands::git::git_open_system_file_location(workdir, path).await + agent_core::commands::git::git_open_system_file_location(workdir, path).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_commit(workdir: String, message: String) -> Result { - crate::commands::git::git_commit(workdir, message).await + agent_core::commands::git::git_commit(workdir, message).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_fetch(workdir: String) -> Result { - crate::commands::git::git_fetch(workdir).await + agent_core::commands::git::git_fetch(workdir).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_pull(workdir: String) -> Result { - crate::commands::git::git_pull(workdir).await + agent_core::commands::git::git_pull(workdir).await } #[tauri::command(rename_all = "snake_case")] @@ -240,12 +240,12 @@ pub async fn git_set_remote( workdir: String, remote_url: String, ) -> Result { - crate::commands::git::git_set_remote(workdir, remote_url).await + agent_core::commands::git::git_set_remote(workdir, remote_url).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_push(workdir: String) -> Result { - crate::commands::git::git_push(workdir).await + agent_core::commands::git::git_push(workdir).await } #[tauri::command(rename_all = "snake_case")] @@ -254,7 +254,7 @@ pub async fn git_delete_branch( branch: String, force: Option, ) -> Result { - crate::commands::git::git_delete_branch(workdir, branch, force).await + agent_core::commands::git::git_delete_branch(workdir, branch, force).await } #[tauri::command(rename_all = "snake_case")] @@ -263,7 +263,7 @@ pub async fn git_rename_branch( branch: String, new_branch: String, ) -> Result { - crate::commands::git::git_rename_branch(workdir, branch, new_branch).await + agent_core::commands::git::git_rename_branch(workdir, branch, new_branch).await } #[tauri::command(rename_all = "snake_case")] @@ -271,10 +271,10 @@ pub async fn git_stash_push( workdir: String, message: Option, ) -> Result { - crate::commands::git::git_stash_push(workdir, message).await + agent_core::commands::git::git_stash_push(workdir, message).await } #[tauri::command(rename_all = "snake_case")] pub async fn git_stash_pop(workdir: String) -> Result { - crate::commands::git::git_stash_pop(workdir).await + agent_core::commands::git::git_stash_pop(workdir).await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/hook.rs b/crates/agent-gui/src-tauri/src/tauri_commands/hook.rs index e512a14dc..3b818a58e 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/hook.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/hook.rs @@ -6,14 +6,14 @@ #![allow(unused_imports)] -use crate::commands::hook::*; -use crate::runtime::shell_runner::{ +use agent_core::commands::hook::*; +use agent_core::runtime::shell_runner::{ run_shell_script_with_envs, ShellCancelFlag, ShellCancelToken, ShellRunResponse, }; -use crate::runtime::task_runner::{ +use agent_core::runtime::task_runner::{ build_http_client, resolve_workdir, run_single_http_request, HttpRequestInput, }; -use crate::services::automation::validate::{MAX_HOOK_TIMEOUT_MS, MIN_HOOK_TIMEOUT_MS}; +use agent_core::services::automation::validate::{MAX_HOOK_TIMEOUT_MS, MIN_HOOK_TIMEOUT_MS}; use serde::Serialize; use std::collections::{HashMap, HashSet, VecDeque}; use std::sync::{Arc, Mutex}; @@ -28,7 +28,7 @@ pub async fn hook_run_script( context: Option>, registry: tauri::State<'_, Arc>, ) -> Result { - crate::commands::hook::hook_run_script( + agent_core::commands::hook::hook_run_script( workdir, script, timeout_ms, @@ -45,7 +45,7 @@ pub async fn hook_run_http_requests( scope_id: Option, registry: tauri::State<'_, Arc>, ) -> Result { - crate::commands::hook::hook_run_http_requests(requests, scope_id, registry.inner()).await + agent_core::commands::hook::hook_run_http_requests(requests, scope_id, registry.inner()).await } #[tauri::command(rename_all = "snake_case")] @@ -53,5 +53,5 @@ pub async fn hook_cancel_scope( scope_id: String, registry: tauri::State<'_, Arc>, ) -> Result<(), String> { - crate::commands::hook::hook_cancel_scope(scope_id, registry.inner()).await + agent_core::commands::hook::hook_cancel_scope(scope_id, registry.inner()).await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs b/crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs index 2132d0715..4d00bbfd5 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/mcp.rs @@ -6,12 +6,14 @@ #![allow(unused_imports)] -use crate::commands::mcp::*; -use crate::runtime::platform::{ +use agent_core::commands::mcp::*; +use agent_core::runtime::platform::{ expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, }; -use crate::runtime::process::{configure_child_process_group, kill_child_process_tree_best_effort}; -use crate::runtime::shell_runner::ShellRunRegistry; +use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, +}; +use agent_core::runtime::shell_runner::ShellRunRegistry; use reqwest::blocking::Client as HttpClient; use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE}; use reqwest::StatusCode; @@ -32,7 +34,7 @@ pub async fn mcp_list_tools( state: tauri::State<'_, Arc>, servers: Vec, ) -> Result, String> { - crate::commands::mcp::mcp_list_tools(state.inner(), servers).await + agent_core::commands::mcp::mcp_list_tools(state.inner(), servers).await } #[tauri::command(rename_all = "snake_case")] @@ -44,7 +46,7 @@ pub async fn mcp_call_tool( arguments: Value, run_id: Option, ) -> Result { - crate::commands::mcp::mcp_call_tool( + agent_core::commands::mcp::mcp_call_tool( state.inner(), run_registry.inner(), server_id, @@ -60,7 +62,7 @@ pub async fn mcp_runtime_status( state: tauri::State<'_, Arc>, server_id: String, ) -> Result { - crate::commands::mcp::mcp_runtime_status(state.inner(), server_id).await + agent_core::commands::mcp::mcp_runtime_status(state.inner(), server_id).await } #[tauri::command(rename_all = "snake_case")] @@ -68,7 +70,7 @@ pub async fn mcp_stop_server( state: tauri::State<'_, Arc>, server_id: String, ) -> Result { - crate::commands::mcp::mcp_stop_server(state.inner(), server_id).await + agent_core::commands::mcp::mcp_stop_server(state.inner(), server_id).await } #[tauri::command(rename_all = "snake_case")] @@ -78,7 +80,7 @@ pub async fn mcp_test_server( include_schema: Option, persist: Option, ) -> Result { - crate::commands::mcp::mcp_test_server(state.inner(), server, include_schema, persist).await + agent_core::commands::mcp::mcp_test_server(state.inner(), server, include_schema, persist).await } #[tauri::command(rename_all = "snake_case")] @@ -88,5 +90,6 @@ pub async fn mcp_restart_server( include_schema: Option, persist: Option, ) -> Result { - crate::commands::mcp::mcp_restart_server(state.inner(), server, include_schema, persist).await + agent_core::commands::mcp::mcp_restart_server(state.inner(), server, include_schema, persist) + .await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/memory.rs b/crates/agent-gui/src-tauri/src/tauri_commands/memory.rs index 32405f3af..4e8d91e9b 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/memory.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/memory.rs @@ -6,21 +6,20 @@ #![allow(unused_imports)] -use crate::commands::memory::*; -use crate::{ - commands::chat_history, - services::memory::{ - MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, - MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, - MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, - MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, - MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, - MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, - MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, - MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, - MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, - }, +use agent_core::commands::chat_history; +use agent_core::commands::memory::*; +use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, }; + use std::sync::Arc; #[tauri::command] @@ -28,7 +27,7 @@ pub async fn memory_list( state: tauri::State<'_, Arc>, args: MemoryListArgs, ) -> Result { - crate::commands::memory::memory_list(state.inner(), args).await + agent_core::commands::memory::memory_list(state.inner(), args).await } #[tauri::command] @@ -36,7 +35,7 @@ pub async fn memory_read( state: tauri::State<'_, Arc>, args: MemoryReadArgs, ) -> Result { - crate::commands::memory::memory_read(state.inner(), args).await + agent_core::commands::memory::memory_read(state.inner(), args).await } #[tauri::command] @@ -44,7 +43,7 @@ pub async fn memory_search( state: tauri::State<'_, Arc>, args: MemorySearchArgs, ) -> Result { - crate::commands::memory::memory_search(state.inner(), args).await + agent_core::commands::memory::memory_search(state.inner(), args).await } #[tauri::command] @@ -52,7 +51,7 @@ pub async fn memory_write( state: tauri::State<'_, Arc>, args: MemoryWriteArgs, ) -> Result { - crate::commands::memory::memory_write(state.inner(), args).await + agent_core::commands::memory::memory_write(state.inner(), args).await } #[tauri::command] @@ -60,7 +59,7 @@ pub async fn memory_update( state: tauri::State<'_, Arc>, args: MemoryUpdateArgs, ) -> Result { - crate::commands::memory::memory_update(state.inner(), args).await + agent_core::commands::memory::memory_update(state.inner(), args).await } #[tauri::command] @@ -68,7 +67,7 @@ pub async fn memory_delete( state: tauri::State<'_, Arc>, args: MemoryDeleteArgs, ) -> Result { - crate::commands::memory::memory_delete(state.inner(), args).await + agent_core::commands::memory::memory_delete(state.inner(), args).await } #[tauri::command] @@ -76,7 +75,7 @@ pub async fn memory_delete_project( state: tauri::State<'_, Arc>, args: MemoryDeleteProjectArgs, ) -> Result { - crate::commands::memory::memory_delete_project(state.inner(), args).await + agent_core::commands::memory::memory_delete_project(state.inner(), args).await } #[tauri::command] @@ -84,7 +83,7 @@ pub async fn memory_accept( state: tauri::State<'_, Arc>, args: MemoryAcceptArgs, ) -> Result { - crate::commands::memory::memory_accept(state.inner(), args).await + agent_core::commands::memory::memory_accept(state.inner(), args).await } #[tauri::command] @@ -92,7 +91,7 @@ pub async fn memory_apply_batch( state: tauri::State<'_, Arc>, args: MemoryBatchArgs, ) -> Result { - crate::commands::memory::memory_apply_batch(state.inner(), args).await + agent_core::commands::memory::memory_apply_batch(state.inner(), args).await } #[tauri::command] @@ -100,7 +99,7 @@ pub async fn memory_organize_run_create( state: tauri::State<'_, Arc>, args: MemoryOrganizeRunCreateArgs, ) -> Result { - crate::commands::memory::memory_organize_run_create(state.inner(), args).await + agent_core::commands::memory::memory_organize_run_create(state.inner(), args).await } #[tauri::command] @@ -108,7 +107,7 @@ pub async fn memory_organize_run_update( state: tauri::State<'_, Arc>, args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { - crate::commands::memory::memory_organize_run_update(state.inner(), args).await + agent_core::commands::memory::memory_organize_run_update(state.inner(), args).await } #[tauri::command] @@ -116,7 +115,7 @@ pub async fn memory_organize_run_list( state: tauri::State<'_, Arc>, args: Option, ) -> Result { - crate::commands::memory::memory_organize_run_list(state.inner(), args).await + agent_core::commands::memory::memory_organize_run_list(state.inner(), args).await } #[tauri::command] @@ -124,14 +123,14 @@ pub async fn memory_organize_run_read( state: tauri::State<'_, Arc>, args: MemoryOrganizeRunReadArgs, ) -> Result, String> { - crate::commands::memory::memory_organize_run_read(state.inner(), args).await + agent_core::commands::memory::memory_organize_run_read(state.inner(), args).await } #[tauri::command] pub async fn memory_organize_run_clear_history( state: tauri::State<'_, Arc>, ) -> Result { - crate::commands::memory::memory_organize_run_clear_history(state.inner()).await + agent_core::commands::memory::memory_organize_run_clear_history(state.inner()).await } #[tauri::command] @@ -139,7 +138,7 @@ pub async fn memory_organize_due_claim( state: tauri::State<'_, Arc>, args: MemoryOrganizeDueClaimArgs, ) -> Result { - crate::commands::memory::memory_organize_due_claim(state.inner(), args).await + agent_core::commands::memory::memory_organize_due_claim(state.inner(), args).await } #[tauri::command] @@ -147,7 +146,7 @@ pub async fn memory_organize_due_complete( state: tauri::State<'_, Arc>, args: MemoryOrganizeRunUpdateArgs, ) -> Result, String> { - crate::commands::memory::memory_organize_due_complete(state.inner(), args).await + agent_core::commands::memory::memory_organize_due_complete(state.inner(), args).await } #[tauri::command] @@ -155,14 +154,14 @@ pub async fn memory_index_overview( state: tauri::State<'_, Arc>, workdir: Option, ) -> Result { - crate::commands::memory::memory_index_overview(state.inner(), workdir).await + agent_core::commands::memory::memory_index_overview(state.inner(), workdir).await } #[tauri::command] pub async fn memory_paths_info( state: tauri::State<'_, Arc>, ) -> Result { - crate::commands::memory::memory_paths_info(state.inner()).await + agent_core::commands::memory::memory_paths_info(state.inner()).await } #[tauri::command] @@ -170,7 +169,7 @@ pub async fn memory_recent_rejections( state: tauri::State<'_, Arc>, args: Option, ) -> Result { - crate::commands::memory::memory_recent_rejections(state.inner(), args).await + agent_core::commands::memory::memory_recent_rejections(state.inner(), args).await } #[tauri::command] @@ -178,7 +177,7 @@ pub async fn memory_today_local_date( state: tauri::State<'_, Arc>, rollover_hour: Option, ) -> Result { - crate::commands::memory::memory_today_local_date(state.inner(), rollover_hour).await + agent_core::commands::memory::memory_today_local_date(state.inner(), rollover_hour).await } #[tauri::command] @@ -186,7 +185,7 @@ pub async fn memory_today_daily( state: tauri::State<'_, Arc>, rollover_hour: Option, ) -> Result, String> { - crate::commands::memory::memory_today_daily(state.inner(), rollover_hour).await + agent_core::commands::memory::memory_today_daily(state.inner(), rollover_hour).await } #[tauri::command] @@ -194,12 +193,12 @@ pub async fn memory_quota_summary( state: tauri::State<'_, Arc>, args: Option, ) -> Result { - crate::commands::memory::memory_quota_summary(state.inner(), args).await + agent_core::commands::memory::memory_quota_summary(state.inner(), args).await } #[tauri::command] pub async fn memory_wipe_all( state: tauri::State<'_, Arc>, ) -> Result { - crate::commands::memory::memory_wipe_all(state.inner()).await + agent_core::commands::memory::memory_wipe_all(state.inner()).await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/process.rs b/crates/agent-gui/src-tauri/src/tauri_commands/process.rs index 8e8efe82e..f774264f4 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/process.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/process.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::process::*; -use crate::runtime::managed_process::{ +use agent_core::commands::process::*; +use agent_core::runtime::managed_process::{ ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, }; @@ -22,7 +22,7 @@ pub fn managed_process_start( label: Option, isolated: Option, ) -> Result { - crate::commands::process::managed_process_start( + agent_core::commands::process::managed_process_start( registry.inner(), workdir, command, @@ -37,7 +37,7 @@ pub fn managed_process_status( registry: tauri::State<'_, Arc>, process_id: Option, ) -> Result { - crate::commands::process::managed_process_status(registry.inner(), process_id) + agent_core::commands::process::managed_process_status(registry.inner(), process_id) } #[tauri::command(rename_all = "snake_case")] @@ -45,7 +45,7 @@ pub fn managed_process_stop( registry: tauri::State<'_, Arc>, process_id: String, ) -> Result { - crate::commands::process::managed_process_stop(registry.inner(), process_id) + agent_core::commands::process::managed_process_stop(registry.inner(), process_id) } #[tauri::command(rename_all = "snake_case")] @@ -54,14 +54,14 @@ pub fn managed_process_read_log( process_id: String, max_bytes: Option, ) -> Result { - crate::commands::process::managed_process_read_log(registry.inner(), process_id, max_bytes) + agent_core::commands::process::managed_process_read_log(registry.inner(), process_id, max_bytes) } #[tauri::command(rename_all = "snake_case")] pub fn managed_process_snapshot( registry: tauri::State<'_, Arc>, ) -> Result { - crate::commands::process::managed_process_snapshot(registry.inner()) + agent_core::commands::process::managed_process_snapshot(registry.inner()) } #[tauri::command(rename_all = "snake_case")] @@ -69,5 +69,5 @@ pub fn managed_process_clear( registry: tauri::State<'_, Arc>, process_id: Option, ) -> Result { - crate::commands::process::managed_process_clear(registry.inner(), process_id) + agent_core::commands::process::managed_process_clear(registry.inner(), process_id) } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/settings.rs b/crates/agent-gui/src-tauri/src/tauri_commands/settings.rs index a9d55cf76..5b92ae43b 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/settings.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/settings.rs @@ -6,10 +6,10 @@ #![allow(unused_imports)] -use crate::commands::settings::*; -use crate::runtime::project_path::project_path_key as normalize_project_path_key; -use crate::services::automation::AutomationScheduler; -use crate::services::gateway::GatewayController; +use agent_core::commands::settings::*; +use agent_core::events::EventBus; +use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; +use agent_core::services::automation::AutomationScheduler; use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; use serde::{Deserialize, Serialize}; use serde_json::{json, Map, Number, Value}; @@ -24,12 +24,12 @@ use uuid::Uuid; #[tauri::command] pub async fn settings_load_all() -> Result { - crate::commands::settings::settings_load_all().await + agent_core::commands::settings::settings_load_all().await } #[tauri::command] pub async fn settings_save_providers(payload: Value) -> Result<(), String> { - crate::commands::settings::settings_save_providers(payload).await + agent_core::commands::settings::settings_save_providers(payload).await } #[tauri::command] @@ -37,40 +37,44 @@ pub async fn settings_save_system( payload: Value, automation_scheduler: tauri::State<'_, Arc>, ) -> Result<(), String> { - crate::commands::settings::settings_save_system(payload, automation_scheduler.inner()).await + agent_core::commands::settings::settings_save_system(payload, automation_scheduler.inner()) + .await } #[tauri::command] pub async fn settings_save_mcp(payload: Value) -> Result<(), String> { - crate::commands::settings::settings_save_mcp(payload).await + agent_core::commands::settings::settings_save_mcp(payload).await } +// 从 State 换成 State:实现侧已经不认识 Gateway 了, +// 它只发 settings:remote-saved,由 GatewayEventSink 接住去调 apply_config。 +// State 参数不是 JSON key,前端契约不受影响。 #[tauri::command] pub async fn settings_save_remote( payload: Value, - gateway_controller: tauri::State<'_, Arc>, + events: tauri::State<'_, Arc>, ) -> Result<(), String> { - crate::commands::settings::settings_save_remote(payload, gateway_controller.inner()).await + agent_core::commands::settings::settings_save_remote(payload, events.inner()).await } #[tauri::command] pub async fn settings_save_memory(payload: Value) -> Result<(), String> { - crate::commands::settings::settings_save_memory(payload).await + agent_core::commands::settings::settings_save_memory(payload).await } #[tauri::command] pub async fn settings_save_agents(payload: Value) -> Result<(), String> { - crate::commands::settings::settings_save_agents(payload).await + agent_core::commands::settings::settings_save_agents(payload).await } #[tauri::command] pub async fn settings_save_ssh(payload: Value) -> Result<(), String> { - crate::commands::settings::settings_save_ssh(payload).await + agent_core::commands::settings::settings_save_ssh(payload).await } #[tauri::command] pub async fn settings_apply_ssh_patch(payload: Value) -> Result { - crate::commands::settings::settings_apply_ssh_patch(payload).await + agent_core::commands::settings::settings_apply_ssh_patch(payload).await } #[tauri::command] @@ -78,22 +82,22 @@ pub async fn settings_reset_ssh_known_host( host: String, port: u16, ) -> Result { - crate::commands::settings::settings_reset_ssh_known_host(host, port).await + agent_core::commands::settings::settings_reset_ssh_known_host(host, port).await } #[tauri::command] pub async fn settings_list_cherry_studio_providers() -> Result { - crate::commands::settings::settings_list_cherry_studio_providers().await + agent_core::commands::settings::settings_list_cherry_studio_providers().await } #[tauri::command] pub async fn settings_list_cherry_studio_providers_from_path( data_path: String, ) -> Result { - crate::commands::settings::settings_list_cherry_studio_providers_from_path(data_path).await + agent_core::commands::settings::settings_list_cherry_studio_providers_from_path(data_path).await } #[tauri::command] pub async fn settings_list_ccswitch_providers() -> Result { - crate::commands::settings::settings_list_ccswitch_providers().await + agent_core::commands::settings::settings_list_ccswitch_providers().await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs b/crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs index 69e5d8898..b69dc06dc 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/sftp.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::sftp::*; -use crate::runtime::sftp::{ +use agent_core::commands::sftp::*; +use agent_core::runtime::sftp::{ SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, SftpStatResponse, SftpTransferResponse, }; @@ -22,7 +22,7 @@ pub async fn sftp_list( side: String, path: Option, ) -> Result { - crate::commands::sftp::sftp_list( + agent_core::commands::sftp::sftp_list( registry.inner(), session_id, project_path_key, @@ -42,7 +42,7 @@ pub async fn sftp_stat( side: String, path: Option, ) -> Result { - crate::commands::sftp::sftp_stat( + agent_core::commands::sftp::sftp_stat( registry.inner(), session_id, project_path_key, @@ -62,7 +62,7 @@ pub async fn sftp_read_text( offset: Option, max_bytes: Option, ) -> Result { - crate::commands::sftp::sftp_read_text( + agent_core::commands::sftp::sftp_read_text( registry.inner(), session_id, project_path_key, @@ -83,7 +83,7 @@ pub async fn sftp_write_text( overwrite: Option, create_parent_dirs: Option, ) -> Result { - crate::commands::sftp::sftp_write_text( + agent_core::commands::sftp::sftp_write_text( registry.inner(), session_id, project_path_key, @@ -104,7 +104,7 @@ pub async fn sftp_mkdir( side: String, path: String, ) -> Result { - crate::commands::sftp::sftp_mkdir( + agent_core::commands::sftp::sftp_mkdir( registry.inner(), session_id, project_path_key, @@ -125,7 +125,7 @@ pub async fn sftp_rename( from_path: String, to_path: String, ) -> Result { - crate::commands::sftp::sftp_rename( + agent_core::commands::sftp::sftp_rename( registry.inner(), session_id, project_path_key, @@ -147,7 +147,7 @@ pub async fn sftp_delete( path: String, recursive: Option, ) -> Result { - crate::commands::sftp::sftp_delete( + agent_core::commands::sftp::sftp_delete( registry.inner(), session_id, project_path_key, @@ -171,7 +171,7 @@ pub async fn sftp_transfer( recursive: Option, overwrite: Option, ) -> Result { - crate::commands::sftp::sftp_transfer( + agent_core::commands::sftp::sftp_transfer( registry.inner(), session_id, project_path_key, @@ -191,7 +191,7 @@ pub fn sftp_cancel_transfer( session_id: String, transfer_id: String, ) -> Result<(), String> { - crate::commands::sftp::sftp_cancel_transfer(registry.inner(), session_id, transfer_id) + agent_core::commands::sftp::sftp_cancel_transfer(registry.inner(), session_id, transfer_id) } #[tauri::command(rename_all = "snake_case")] @@ -200,5 +200,5 @@ pub fn sftp_transfer_status( session_id: String, transfer_id: String, ) -> Result { - crate::commands::sftp::sftp_transfer_status(registry.inner(), session_id, transfer_id) + agent_core::commands::sftp::sftp_transfer_status(registry.inner(), session_id, transfer_id) } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/shell.rs b/crates/agent-gui/src-tauri/src/tauri_commands/shell.rs index ca23b819e..d207d90c6 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/shell.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/shell.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::shell::*; -use crate::runtime::shell_runner::{run_shell_script, ShellRunRegistry, ShellRunResponse}; +use agent_core::commands::shell::*; +use agent_core::runtime::shell_runner::{run_shell_script, ShellRunRegistry, ShellRunResponse}; use serde::Serialize; use std::sync::Arc; @@ -22,7 +22,7 @@ pub async fn shell_run( provider_id: Option, run_id: Option, ) -> Result { - crate::commands::shell::shell_run( + agent_core::commands::shell::shell_run( registry.inner(), workdir, command, @@ -40,5 +40,5 @@ pub fn runtime_cancel( registry: tauri::State<'_, Arc>, run_id: String, ) -> ShellCancelResponse { - crate::commands::shell::runtime_cancel(registry.inner(), run_id) + agent_core::commands::shell::runtime_cancel(registry.inner(), run_id) } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs index b6f5cad6a..2409dfe73 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_store.rs @@ -6,7 +6,7 @@ #![allow(unused_imports)] -use crate::commands::subagent_store::*; +use agent_core::commands::subagent_store::*; use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -19,52 +19,52 @@ use std::{ pub async fn subagent_identity_upsert( input: SubagentIdentityUpsertInput, ) -> Result { - crate::commands::subagent_store::subagent_identity_upsert(input).await + agent_core::commands::subagent_store::subagent_identity_upsert(input).await } #[tauri::command] pub async fn subagent_identity_list( input: SubagentIdentityListInput, ) -> Result, String> { - crate::commands::subagent_store::subagent_identity_list(input).await + agent_core::commands::subagent_store::subagent_identity_list(input).await } #[tauri::command] pub async fn subagent_run_save(input: SubagentRunSaveInput) -> Result<(), String> { - crate::commands::subagent_store::subagent_run_save(input).await + agent_core::commands::subagent_store::subagent_run_save(input).await } #[tauri::command] pub async fn subagent_run_list( input: SubagentRunListInput, ) -> Result, String> { - crate::commands::subagent_store::subagent_run_list(input).await + agent_core::commands::subagent_store::subagent_run_list(input).await } #[tauri::command] pub async fn subagent_run_load( input: SubagentRunLoadInput, ) -> Result, String> { - crate::commands::subagent_store::subagent_run_load(input).await + agent_core::commands::subagent_store::subagent_run_load(input).await } #[tauri::command] pub async fn subagent_run_prune( input: SubagentRunPruneInput, ) -> Result { - crate::commands::subagent_store::subagent_run_prune(input).await + agent_core::commands::subagent_store::subagent_run_prune(input).await } #[tauri::command] pub async fn subagent_message_append( input: SubagentMessageAppendInput, ) -> Result { - crate::commands::subagent_store::subagent_message_append(input).await + agent_core::commands::subagent_store::subagent_message_append(input).await } #[tauri::command] pub async fn subagent_message_list( input: SubagentMessageListInput, ) -> Result, String> { - crate::commands::subagent_store::subagent_message_list(input).await + agent_core::commands::subagent_store::subagent_message_list(input).await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs index c1698e58e..05daa6257 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/subagent_worktree.rs @@ -6,8 +6,8 @@ #![allow(unused_imports)] -use crate::commands::subagent_worktree::*; -use crate::runtime::process::configure_child_process_group; +use agent_core::commands::subagent_worktree::*; +use agent_core::runtime::process::configure_child_process_group; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use std::fs; @@ -22,26 +22,26 @@ use uuid::Uuid; pub async fn subagent_worktree_create( input: SubagentWorktreeCreateInput, ) -> Result { - crate::commands::subagent_worktree::subagent_worktree_create(input).await + agent_core::commands::subagent_worktree::subagent_worktree_create(input).await } #[tauri::command] pub async fn subagent_worktree_status( input: SubagentWorktreeStatusInput, ) -> Result { - crate::commands::subagent_worktree::subagent_worktree_status(input).await + agent_core::commands::subagent_worktree::subagent_worktree_status(input).await } #[tauri::command] pub async fn subagent_worktree_apply( input: SubagentWorktreeApplyInput, ) -> Result { - crate::commands::subagent_worktree::subagent_worktree_apply(input).await + agent_core::commands::subagent_worktree::subagent_worktree_apply(input).await } #[tauri::command] pub async fn subagent_worktree_cleanup( input: SubagentWorktreeCleanupInput, ) -> Result { - crate::commands::subagent_worktree::subagent_worktree_cleanup(input).await + agent_core::commands::subagent_worktree::subagent_worktree_cleanup(input).await } diff --git a/crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs b/crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs index 9103ac0ca..cc826cc46 100644 --- a/crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs +++ b/crates/agent-gui/src-tauri/src/tauri_commands/terminal.rs @@ -6,10 +6,10 @@ #![allow(unused_imports)] -use crate::commands::terminal::*; -use crate::runtime::sftp::SftpSessionRegistry; -use crate::runtime::shell_runner::ShellRunRegistry; -use crate::runtime::terminal::{ +use agent_core::commands::terminal::*; +use agent_core::runtime::sftp::SftpSessionRegistry; +use agent_core::runtime::shell_runner::ShellRunRegistry; +use agent_core::runtime::terminal::{ normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, @@ -21,7 +21,7 @@ use std::sync::Arc; #[tauri::command(rename_all = "snake_case")] pub fn terminal_shell_options() -> TerminalShellOptionsResponse { - crate::commands::terminal::terminal_shell_options() + agent_core::commands::terminal::terminal_shell_options() } #[tauri::command(rename_all = "snake_case")] @@ -29,7 +29,7 @@ pub fn terminal_list( registry: tauri::State<'_, Arc>, project_path_key: Option, ) -> TerminalListResponse { - crate::commands::terminal::terminal_list(registry.inner(), project_path_key) + agent_core::commands::terminal::terminal_list(registry.inner(), project_path_key) } #[tauri::command(rename_all = "snake_case")] @@ -42,7 +42,7 @@ pub fn terminal_create( cols: Option, rows: Option, ) -> Result { - crate::commands::terminal::terminal_create( + agent_core::commands::terminal::terminal_create( registry.inner(), cwd, project_path_key, @@ -64,7 +64,7 @@ pub async fn terminal_create_ssh( rows: Option, sftp_enabled: Option, ) -> Result { - crate::commands::terminal::terminal_create_ssh( + agent_core::commands::terminal::terminal_create_ssh( registry.inner(), cwd, project_path_key, @@ -84,7 +84,7 @@ pub async fn terminal_answer_ssh_prompt( prompt_answer: Option, trust_host_key: Option, ) -> Result { - crate::commands::terminal::terminal_answer_ssh_prompt( + agent_core::commands::terminal::terminal_answer_ssh_prompt( registry.inner(), prompt_id, prompt_answer, @@ -98,7 +98,7 @@ pub fn terminal_cancel_ssh_prompt( registry: tauri::State<'_, Arc>, prompt_id: String, ) -> Result<(), String> { - crate::commands::terminal::terminal_cancel_ssh_prompt(registry.inner(), prompt_id) + agent_core::commands::terminal::terminal_cancel_ssh_prompt(registry.inner(), prompt_id) } #[tauri::command(rename_all = "snake_case")] @@ -106,7 +106,7 @@ pub async fn terminal_ssh_reconnect( registry: tauri::State<'_, Arc>, session_id: String, ) -> Result { - crate::commands::terminal::terminal_ssh_reconnect(registry.inner(), session_id).await + agent_core::commands::terminal::terminal_ssh_reconnect(registry.inner(), session_id).await } #[tauri::command(rename_all = "snake_case")] @@ -114,7 +114,7 @@ pub async fn terminal_ssh_latency( registry: tauri::State<'_, Arc>, session_id: String, ) -> Result { - crate::commands::terminal::terminal_ssh_latency(registry.inner(), session_id).await + agent_core::commands::terminal::terminal_ssh_latency(registry.inner(), session_id).await } #[tauri::command(rename_all = "snake_case")] @@ -128,7 +128,7 @@ pub async fn terminal_ssh_exec( max_bytes: Option, run_id: Option, ) -> Result { - crate::commands::terminal::terminal_ssh_exec( + agent_core::commands::terminal::terminal_ssh_exec( registry.inner(), run_registry.inner(), session_id, @@ -150,7 +150,7 @@ pub async fn terminal_ssh_local_forward_start( remote_port: u32, local_port: Option, ) -> Result { - crate::commands::terminal::terminal_ssh_local_forward_start( + agent_core::commands::terminal::terminal_ssh_local_forward_start( registry.inner(), session_id, project_path_key, @@ -167,7 +167,7 @@ pub fn terminal_ssh_local_forward_list( session_id: Option, project_path_key: Option, ) -> Result { - crate::commands::terminal::terminal_ssh_local_forward_list( + agent_core::commands::terminal::terminal_ssh_local_forward_list( registry.inner(), session_id, project_path_key, @@ -180,7 +180,7 @@ pub async fn terminal_ssh_local_forward_stop( forward_id: String, session_id: Option, ) -> Result { - crate::commands::terminal::terminal_ssh_local_forward_stop( + agent_core::commands::terminal::terminal_ssh_local_forward_stop( registry.inner(), forward_id, session_id, @@ -190,7 +190,7 @@ pub async fn terminal_ssh_local_forward_stop( #[tauri::command(rename_all = "snake_case")] pub async fn terminal_ssh_local_forward_check_port(local_port: u32) -> Result { - crate::commands::terminal::terminal_ssh_local_forward_check_port(local_port).await + agent_core::commands::terminal::terminal_ssh_local_forward_check_port(local_port).await } #[tauri::command(rename_all = "snake_case")] @@ -198,7 +198,7 @@ pub fn ssh_terminal_tabs_list( registry: tauri::State<'_, Arc>, project_path_key: String, ) -> Result { - crate::commands::terminal::ssh_terminal_tabs_list(registry.inner(), project_path_key) + agent_core::commands::terminal::ssh_terminal_tabs_list(registry.inner(), project_path_key) } #[tauri::command(rename_all = "snake_case")] @@ -207,7 +207,7 @@ pub fn ssh_terminal_tab_open( session_id: String, kind: String, ) -> Result { - crate::commands::terminal::ssh_terminal_tab_open(registry.inner(), session_id, kind) + agent_core::commands::terminal::ssh_terminal_tab_open(registry.inner(), session_id, kind) } #[tauri::command(rename_all = "snake_case")] @@ -215,7 +215,7 @@ pub fn ssh_terminal_tab_close( registry: tauri::State<'_, Arc>, tab_id: String, ) -> Result { - crate::commands::terminal::ssh_terminal_tab_close(registry.inner(), tab_id) + agent_core::commands::terminal::ssh_terminal_tab_close(registry.inner(), tab_id) } #[tauri::command(rename_all = "snake_case")] @@ -224,7 +224,7 @@ pub fn terminal_stream_attach( session_id: String, max_bytes: Option, ) -> Result { - crate::commands::terminal::terminal_stream_attach(registry.inner(), session_id, max_bytes) + agent_core::commands::terminal::terminal_stream_attach(registry.inner(), session_id, max_bytes) } #[tauri::command(rename_all = "snake_case")] @@ -233,7 +233,7 @@ pub fn terminal_stream_input( session_id: String, bytes: Vec, ) -> Result<(), String> { - crate::commands::terminal::terminal_stream_input(registry.inner(), session_id, bytes) + agent_core::commands::terminal::terminal_stream_input(registry.inner(), session_id, bytes) } #[tauri::command(rename_all = "snake_case")] @@ -243,7 +243,7 @@ pub fn terminal_stream_resize( cols: u16, rows: u16, ) -> Result<(), String> { - crate::commands::terminal::terminal_stream_resize(registry.inner(), session_id, cols, rows) + agent_core::commands::terminal::terminal_stream_resize(registry.inner(), session_id, cols, rows) } #[tauri::command(rename_all = "snake_case")] @@ -252,7 +252,7 @@ pub fn terminal_rename( session_id: String, title: String, ) -> Result { - crate::commands::terminal::terminal_rename(registry.inner(), session_id, title) + agent_core::commands::terminal::terminal_rename(registry.inner(), session_id, title) } #[tauri::command(rename_all = "snake_case")] @@ -261,7 +261,11 @@ pub fn terminal_close( sftp_registry: tauri::State<'_, Arc>, session_id: String, ) -> Result { - crate::commands::terminal::terminal_close(registry.inner(), sftp_registry.inner(), session_id) + agent_core::commands::terminal::terminal_close( + registry.inner(), + sftp_registry.inner(), + session_id, + ) } #[tauri::command(rename_all = "snake_case")] @@ -270,7 +274,7 @@ pub fn terminal_close_project( sftp_registry: tauri::State<'_, Arc>, project_path_key: String, ) -> Result { - crate::commands::terminal::terminal_close_project( + agent_core::commands::terminal::terminal_close_project( registry.inner(), sftp_registry.inner(), project_path_key, @@ -284,7 +288,7 @@ pub fn terminal_read_tail( session_id: Option, max_bytes: Option, ) -> Result { - crate::commands::terminal::terminal_read_tail( + agent_core::commands::terminal::terminal_read_tail( registry.inner(), project_path_key, session_id, From 58a73280416e3443841c94fb46639247f95d8602 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 06:31:00 -0700 Subject: [PATCH 11/75] test(gateway-sink): make the event routing testable, and test it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P2-14 一直做不了是因为 GatewayController 持有 tauri::AppHandle,单测造不出来。 但 P2-07 真实踩过的那次回归(移除 gateway 发布调用后忘了在 sink 里接上, cargo check 全绿而 history 同步已断)本质是**路由错误**——事件名没落到任何分支。 路由是纯函数。于是把决策(action_for,可测)和执行(emit_json,只剩转发)分开。 断言用 agent-core 导出的常量本身而不是字面量:后端改事件名,测试跟着变; 后端新增一条 Gateway 该关心的事件却忘了接,就掉进 Ignore 被抓住。 已用变异验证测试有效:删掉 HISTORY_UPSERT_EVENT 分支,测试立即失败。 顺带:agent-backend crate 骨架 + AppState 落位(P2-20 进行中)。 --- Cargo.lock | 100 ++- Cargo.toml | 2 +- crates/agent-backend/Cargo.toml | 28 + crates/agent-backend/src/auth.rs | 307 +++++++++ crates/agent-backend/src/ssrf.rs | 625 ++++++++++++++++++ crates/agent-backend/src/state.rs | 45 ++ crates/agent-backend/src/tls.rs | 182 +++++ crates/agent-backend/src/ws.rs | 268 ++++++++ .../agent-gui/src-tauri/src/gateway_sink.rs | 126 +++- 9 files changed, 1661 insertions(+), 22 deletions(-) create mode 100644 crates/agent-backend/Cargo.toml create mode 100644 crates/agent-backend/src/auth.rs create mode 100644 crates/agent-backend/src/ssrf.rs create mode 100644 crates/agent-backend/src/state.rs create mode 100644 crates/agent-backend/src/tls.rs create mode 100644 crates/agent-backend/src/ws.rs diff --git a/Cargo.lock b/Cargo.lock index a3f81025b..1a4a8de45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "agent-backend" +version = "0.1.0" +dependencies = [ + "agent-core", + "axum", + "axum-server", + "futures-util", + "http-body-util", + "rand 0.9.4", + "reqwest", + "serde", + "serde_json", + "subtle", + "tokio", + "tower", + "tower-http", + "uuid", +] + [[package]] name = "agent-core" version = "0.1.0" @@ -148,6 +168,15 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "argon2" version = "0.6.0-rc.8" @@ -357,6 +386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -369,14 +399,17 @@ dependencies = [ "matchit", "memchr", "mime", + "multer", "percent-encoding", "pin-project-lite", "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1 0.10.6", "sync_wrapper", "tokio", + "tokio-tungstenite 0.29.0", "tower", "tower-layer", "tower-service", @@ -402,6 +435,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-server" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ab4a3ec9ea8a657c72d99a03a824af695bd0fb5ec639ccbd9cd3543b41a5f9" +dependencies = [ + "arc-swap", + "bytes", + "fs-err", + "http", + "http-body", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "base16ct" version = "1.0.0" @@ -1947,6 +2002,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" +dependencies = [ + "autocfg", + "tokio", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -3606,6 +3671,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "multimap" version = "0.10.1" @@ -5457,7 +5539,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5500,6 +5582,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.15.0" @@ -6148,6 +6239,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + [[package]] name = "spki" version = "0.8.0" @@ -7172,6 +7269,7 @@ dependencies = [ "futures-util", "http", "http-body", + "http-body-util", "pin-project-lite", "tower", "tower-layer", diff --git a/Cargo.toml b/Cargo.toml index ee8e49b1e..1c064ccbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["crates/agent-core", "crates/agent-gui/src-tauri"] +members = ["crates/agent-backend", "crates/agent-core", "crates/agent-gui/src-tauri"] diff --git a/crates/agent-backend/Cargo.toml b/crates/agent-backend/Cargo.toml new file mode 100644 index 000000000..86a0046c2 --- /dev/null +++ b/crates/agent-backend/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "agent-backend" +version = "0.1.0" +edition = "2021" +description = "LiveAgent backend HTTP/WS server. The one and only network entry point." + +[[bin]] +name = "agent-backend" +path = "src/main.rs" + +[dependencies] +agent-core = { path = "../agent-core" } +axum = { version = "0.8.9", features = ["ws", "multipart"] } +axum-server = { version = "0.7.2", features = ["tls-rustls"] } +tokio = { version = "1.52.3", features = ["macros", "net", "sync", "time", "io-util", "rt", "rt-multi-thread", "signal"] } +tower = "0.5.2" +tower-http = { version = "0.6.2", features = ["limit"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.150" +futures-util = "0.3.32" +subtle = "2.6.1" +uuid = { version = "1.23.4", features = ["v4"] } +rand = "0.9.2" +reqwest = { version = "0.13.4", features = ["json", "stream"] } + +[dev-dependencies] +tower = { version = "0.5.2", features = ["util"] } +http-body-util = "0.1.3" diff --git a/crates/agent-backend/src/auth.rs b/crates/agent-backend/src/auth.rs new file mode 100644 index 000000000..aaf00a6b1 --- /dev/null +++ b/crates/agent-backend/src/auth.rs @@ -0,0 +1,307 @@ +//! 密码认证模块。密码直接当 Bearer token。 +//! +//! 决策 7:密码 = Bearer token。不用 JWT 或 session,整个系统只有一把钥匙。 +//! - 启动时要么读配置文件,要么动态生成一个临时密码并打印到终端。 +//! - 每个 request 的 Authorization: Bearer header 里的密码就是这个。 +//! - 常量时间比较,防止时序侧信道泄露密码长度。 + +use std::sync::Arc; + +use axum::extract::Request; +use axum::http::{HeaderMap, StatusCode}; +use axum::middleware::Next; +use axum::response::Response; +use subtle::ConstantTimeEq; + +/// 密码认证配置。存一个密码,verify 时常量时间比较。 +#[derive(Clone)] +pub struct AuthConfig { + password: String, +} + +impl AuthConfig { + /// 新建一个认证配置。传入密码即可。 + pub fn new(password: String) -> Self { + Self { password } + } + + /// 常量时间验证 Bearer token。 + /// + /// 即使 presented 长度与内部密码不同,也必须走完整个比较流程。 + /// 这样避免攻击者通过响应时间推断密码长度。 + pub fn verify(&self, presented: &str) -> bool { + let stored_bytes = self.password.as_bytes(); + let presented_bytes = presented.as_bytes(); + + // 检查长度是否相等(但不提前返回)。 + let len_eq = stored_bytes.len() == presented_bytes.len(); + + // 总是进行常量时间比较。 + // 如果长度匹配,比较实际数据;否则比较填充数据,保证比较次数相同。 + let to_compare = if len_eq { + presented_bytes.to_vec() + } else { + // 长度不同时,用全零向量代替,确保比较总是遍历相同数量的字节。 + vec![0u8; stored_bytes.len()] + }; + + // 常量时间比较数据。 + let data_eq: bool = stored_bytes.ct_eq(&to_compare).into(); + + // 只有当长度和数据都匹配时,才返回 true。 + // 前面已经跑完了 ct_eq,这里的 && 不会导致提前返回的时序差异。 + len_eq && data_eq + } +} + +/// 生成至少 32 字符的 URL-safe 随机密码。 +/// +/// 使用 base62 字符集:0-9, a-z, A-Z。共 62 种。 +/// 这种不包含下划线和破折号,但在 HTTP Bearer token 中很常见。 +pub fn generate_password() -> String { + // URL-safe 字符集。64 种是 base64,但我们用更少、更兼容的 base62。 + const CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const PASSWORD_LEN: usize = 32; + + let mut rng = rand::rng(); + let mut password = String::with_capacity(PASSWORD_LEN); + + for _ in 0..PASSWORD_LEN { + let idx = rng.random_range(0..CHARSET.len()); + password.push(CHARSET[idx] as char); + } + + password +} + +/// Axum middleware:校验 Authorization: Bearer header。 +/// +/// 三种失败情况(缺 header、格式错、密码错)都返回同样的 401,不暴露细节。 +pub async fn require_bearer( + axum::extract::State(state): axum::extract::State, + req: axum::extract::Request, + next: axum::middleware::Next, +) -> Result { + let headers = req.headers(); + + // 试图从 Authorization header 提取 Bearer token。 + let token = extract_bearer_token(headers).ok_or(axum::http::StatusCode::UNAUTHORIZED)?; + + // 用 auth 配置验证。 + if state.auth.verify(token) { + Ok(next.run(req).await) + } else { + Err(axum::http::StatusCode::UNAUTHORIZED) + } +} + +/// 从 Authorization header 提取 Bearer token。 +/// +/// 返回 None 如果: +/// - 没有 Authorization header +/// - 格式不是 "Bearer "(case-insensitive Bearer) +/// - token 为空 +fn extract_bearer_token(headers: &HeaderMap) -> Option<&str> { + let auth_header = headers.get("authorization")?; + let auth_str = auth_header.to_str().ok()?; + + // 检查前缀是否是 "Bearer "(case-insensitive)。 + // "Bearer " 至少需要 7 个字符(6 个"Bearer"加至少一个空格)。 + if auth_str.len() < 7 { + return None; + } + + let (scheme, rest) = auth_str.split_at(6); // 前 6 个字符是 "Bearer" + if !scheme.eq_ignore_ascii_case("bearer") { + return None; + } + + // 跳过 "Bearer" 后面的空格。 + let token = rest.trim_start(); + if token.is_empty() { + return None; + } + + Some(token) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// 测试正确的密码被接受。 + #[test] + fn test_verify_correct_password() { + let config = AuthConfig::new("my_secret_password".to_string()); + assert!(config.verify("my_secret_password")); + } + + /// 测试错误的密码被拒绝。 + #[test] + fn test_verify_wrong_password() { + let config = AuthConfig::new("my_secret_password".to_string()); + assert!(!config.verify("wrong_password")); + } + + /// 测试不同长度的密码也被拒绝,且不泄露长度信息(常量时间)。 + /// 长度比密码短、长、或完全不同,都应该走完整个比较流程。 + #[test] + fn test_verify_different_length_constant_time() { + let config = AuthConfig::new("password123".to_string()); + + // 更短的输入。 + assert!(!config.verify("pass")); + + // 更长的输入。 + assert!(!config.verify("password123extra")); + + // 完全不同长度。 + assert!(!config.verify("xyz")); + + // 空字符串。 + assert!(!config.verify("")); + } + + /// 测试生成的密码长度至少 32。 + #[test] + fn test_generate_password_length() { + let pwd = generate_password(); + assert!(pwd.len() >= 32, "Generated password too short: {}", pwd); + } + + /// 测试生成密码的随机性——多次调用不相同且长度达标。 + #[test] + fn test_generate_password_randomness() { + let pwd1 = generate_password(); + let pwd2 = generate_password(); + + assert_ne!(pwd1, pwd2, "Generated passwords should be different"); + assert!(pwd1.len() >= 32); + assert!(pwd2.len() >= 32); + } + + /// 测试生成密码只包含 URL-safe 字符(base62)。 + #[test] + fn test_generate_password_charset() { + let pwd = generate_password(); + for c in pwd.chars() { + assert!( + c.is_ascii_alphanumeric(), + "Generated password contains non-alphanumeric char: {}", + c + ); + } + } + + /// 测试 extract_bearer_token 成功提取。 + #[test] + fn test_extract_bearer_token_success() { + let mut headers = HeaderMap::new(); + headers.insert("authorization", "Bearer my_token".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, Some("my_token")); + } + + /// 测试 extract_bearer_token 处理大小写敏感的 Bearer(case-insensitive)。 + #[test] + fn test_extract_bearer_token_case_insensitive() { + let mut headers = HeaderMap::new(); + headers.insert("authorization", "bearer my_token".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, Some("my_token")); + + let mut headers = HeaderMap::new(); + headers.insert("authorization", "BEARER my_token".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, Some("my_token")); + + let mut headers = HeaderMap::new(); + headers.insert("authorization", "BeArEr my_token".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, Some("my_token")); + } + + /// 测试缺少 Authorization header。 + #[test] + fn test_extract_bearer_token_missing_header() { + let headers = HeaderMap::new(); + let token = extract_bearer_token(&headers); + assert_eq!(token, None); + } + + /// 测试格式错误——不是 Bearer scheme。 + #[test] + fn test_extract_bearer_token_wrong_scheme() { + let mut headers = HeaderMap::new(); + headers.insert("authorization", "Basic xyz".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, None); + } + + /// 测试格式错误——缺少 token(只有 Bearer 和空格)。 + #[test] + fn test_extract_bearer_token_missing_token() { + let mut headers = HeaderMap::new(); + headers.insert("authorization", "Bearer ".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, None); + } + + /// 测试格式错误——只有 Bearer,没有空格和 token。 + #[test] + fn test_extract_bearer_token_bearer_only() { + let mut headers = HeaderMap::new(); + headers.insert("authorization", "Bearer".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, None); + } + + /// 测试:正确密码的 Bearer token 被接受。 + #[test] + fn test_valid_bearer_token() { + let config = AuthConfig::new("secret123".to_string()); + let mut headers = HeaderMap::new(); + headers.insert("authorization", "Bearer secret123".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert!(token.is_some()); + assert!(config.verify(token.unwrap())); + } + + /// 测试:错误密码的 Bearer token 被拒绝。 + #[test] + fn test_invalid_bearer_token() { + let config = AuthConfig::new("secret123".to_string()); + let mut headers = HeaderMap::new(); + headers.insert("authorization", "Bearer wrong_token".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert!(token.is_some()); + assert!(!config.verify(token.unwrap())); + } + + /// 测试:缺少 Authorization header——无法提取 token。 + #[test] + fn test_missing_header_no_token() { + let headers = HeaderMap::new(); + let token = extract_bearer_token(&headers); + assert_eq!(token, None); + } + + /// 测试:格式错误的 Authorization header——无法提取 token。 + #[test] + fn test_invalid_header_format_no_token() { + let mut headers = HeaderMap::new(); + headers.insert("authorization", "Basic xyz".parse().unwrap()); + + let token = extract_bearer_token(&headers); + assert_eq!(token, None); + } +} diff --git a/crates/agent-backend/src/ssrf.rs b/crates/agent-backend/src/ssrf.rs new file mode 100644 index 000000000..0a31d89c0 --- /dev/null +++ b/crates/agent-backend/src/ssrf.rs @@ -0,0 +1,625 @@ +//! SSRF(服务端请求伪造)防护。 +//! +//! 包含 IP 黑名单、URL 验证、域名解析与检查等核心防护机制。 +//! 调用方必须使用这里的函数来校验所有外部请求的目标地址。 + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use url::Url; + +/// URL 解析结果,包含 host 和 port。 +#[derive(Debug, Clone)] +pub struct UrlParts { + pub host: String, + pub port: u16, +} + +/// 检查 IP 地址是否在黑名单中。 +/// +/// # IP 黑名单清单 +/// +/// **IPv4:** +/// - 10.0.0.0/8 - 私有网段 +/// - 172.16.0.0/12 - 私有网段 +/// - 192.168.0.0/16 - 私有网段 +/// - 127.0.0.0/8 - Loopback +/// - 169.254.0.0/16 - Link-local +/// - 100.64.0.0/10 - Carrier-grade NAT +/// - 0.0.0.0/8 - 当前网络 +/// - 255.255.255.255/32 - 广播 +/// - 224.0.0.0/4 - 组播 +/// - 240.0.0.0/4 - 保留 +/// +/// **IPv6:** +/// - ::1/128 - Loopback +/// - ::/128 - 未指定地址 +/// - fe80::/10 - Link-local +/// - fc00::/7 - 唯一本地地址 +/// - ff00::/8 - 组播 +/// - 64:ff9b::/96 - NAT64(需递归检查内嵌 IPv4) +/// - 2002::/16 - 6to4(需递归检查内嵌 IPv4) +/// - ::ffff:0:0/96 - IPv4-mapped IPv6(需递归检查内嵌 IPv4) +pub fn is_blocked_ip(ip: IpAddr) -> bool { + match ip { + IpAddr::V4(ipv4) => is_blocked_ipv4(ipv4), + IpAddr::V6(ipv6) => is_blocked_ipv6(ipv6), + } +} + +/// 检查 IPv4 地址是否被拦截。 +fn is_blocked_ipv4(ip: Ipv4Addr) -> bool { + let octets = ip.octets(); + + // 10.0.0.0/8 + if octets[0] == 10 { + return true; + } + + // 172.16.0.0/12 + if octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31 { + return true; + } + + // 192.168.0.0/16 + if octets[0] == 192 && octets[1] == 168 { + return true; + } + + // 127.0.0.0/8 - Loopback + if octets[0] == 127 { + return true; + } + + // 169.254.0.0/16 - Link-local + if octets[0] == 169 && octets[1] == 254 { + return true; + } + + // 100.64.0.0/10 - CGNAT + if octets[0] == 100 && octets[1] >= 64 && octets[1] <= 127 { + return true; + } + + // 0.0.0.0/8 + if octets[0] == 0 { + return true; + } + + // 255.255.255.255/32 - 广播 + if ip == Ipv4Addr::BROADCAST { + return true; + } + + // 224.0.0.0/4 - 组播 + if octets[0] >= 224 && octets[0] <= 239 { + return true; + } + + // 240.0.0.0/4 - 保留 + if octets[0] >= 240 { + return true; + } + + false +} + +/// 检查 IPv6 地址是否被拦截。 +fn is_blocked_ipv6(ip: Ipv6Addr) -> bool { + // ::1/128 - Loopback + if ip == Ipv6Addr::LOOPBACK { + return true; + } + + // ::/128 - 未指定地址 + if ip == Ipv6Addr::UNSPECIFIED { + return true; + } + + // fe80::/10 - Link-local + // 前 10 位固定为 1111111010,对应 0xfe80 到 0xfebf + if (ip.segments()[0] & 0xffc0) == 0xfe80 { + return true; + } + + // fc00::/7 - 唯一本地地址(fc00::/7 和 fd00::/7) + // 前 7 位固定,对应 0xfc00 到 0xfdff + if (ip.segments()[0] & 0xfe00) == 0xfc00 { + return true; + } + + // ff00::/8 - 组播 + if ip.segments()[0] >> 8 == 0xff { + return true; + } + + // IPv4-mapped IPv6: ::ffff:0:0/96 + // 形式:::ffff:a.b.c.d 或 ::ffff:c0a8:1(16进制) + if ip.segments()[0..6] == [0, 0, 0, 0, 0, 0xffff] { + // segments[6] 和 segments[7] 组成 IPv4 地址 + let ipv4 = Ipv4Addr::new( + (ip.segments()[6] >> 8) as u8, + ip.segments()[6] as u8, + (ip.segments()[7] >> 8) as u8, + ip.segments()[7] as u8, + ); + return is_blocked_ipv4(ipv4); + } + + // 6to4: 2002::/16 + // 形式:2002:aabb:ccdd::(其中 aabb:ccdd 是 IPv4 的 4 个八位组) + if ip.segments()[0] == 0x2002 { + let ipv4 = Ipv4Addr::new( + (ip.segments()[1] >> 8) as u8, + ip.segments()[1] as u8, + (ip.segments()[2] >> 8) as u8, + ip.segments()[2] as u8, + ); + return is_blocked_ipv4(ipv4); + } + + // NAT64: 64:ff9b::/96 + // 形式:64:ff9b:0:0:0:0:aabb:ccdd(最后 32 bit 是 IPv4) + if ip.segments()[0] == 0x0064 + && ip.segments()[1] == 0xff9b + && ip.segments()[2] == 0 + && ip.segments()[3] == 0 + && ip.segments()[4] == 0 + && ip.segments()[5] == 0 + { + let ipv4 = Ipv4Addr::new( + (ip.segments()[6] >> 8) as u8, + ip.segments()[6] as u8, + (ip.segments()[7] >> 8) as u8, + ip.segments()[7] as u8, + ); + return is_blocked_ipv4(ipv4); + } + + false +} + +/// 验证 URL,返回提取的 host 和 port。 +/// +/// # 检查项 +/// - 仅允许 http 和 https scheme +/// - 拒绝 URL 中的嵌入用户名密码 +/// - 确保 port 为标准端口(http:80, https:443)或显式指定 +/// +/// # 返回 +/// 成功返回 `UrlParts { host, port }`,失败返回错误信息。 +pub fn validate_url(raw: &str) -> Result { + let url = Url::parse(raw).map_err(|e| format!("无效的URL: {}", e))?; + + // 检查 scheme + match url.scheme() { + "http" | "https" => {} + _ => return Err(format!("不支持的 scheme: {}", url.scheme())), + } + + // 拒绝嵌入的用户名密码 + if url.username() != "" || url.password().is_some() { + return Err("不允许URL中包含用户名或密码".to_string()); + } + + // 获取 host + let host = url + .host_str() + .ok_or("URL缺少有效的主机名")? + .to_string(); + + // 获取 port,使用 default_port() 处理标准端口 + let port = url.port().unwrap_or_else(|| match url.scheme() { + "https" => 443, + _ => 80, + }); + + Ok(UrlParts { host, port }) +} + +/// 解析域名并验证所有解析结果的 IP 地址。 +/// +/// 本函数执行异步 DNS 查询,并逐个检查返回的 IP 地址是否在黑名单中。 +/// 只要有一个 IP 被拦截,整个操作就失败。 +/// +/// # 重要提示:DNS rebinding 防护 +/// +/// 为了防止 DNS rebinding 攻击(攻击者在第一次 DNS 查询返回公网 IP, +/// 第二次查询返回内网 IP),调用方**必须**使用 reqwest 的 `resolve()` 方法 +/// 将连接钉死在本函数返回的已校验 IP 列表上: +/// +/// ```ignore +/// let socket_addrs = resolve_and_validate("example.com", 80).await?; +/// let mut client_builder = reqwest::Client::builder(); +/// for addr in &socket_addrs { +/// client_builder = client_builder.resolve("example.com", *addr); +/// } +/// let client = client_builder.build()?; +/// // 使用 client 发起请求,则无法 rebind 到其他 IP +/// ``` +/// +/// # 返回 +/// 成功返回通过校验的 `SocketAddr` 列表。列表不为空保证全部 IP 都合法。 +/// 失败返回错误信息(包含被拦截的 IP)。 +pub async fn resolve_and_validate( + host: &str, + port: u16, +) -> Result, String> { + use std::str::FromStr; + + // 首先尝试直接解析为 IP 地址(如果 host 本身是 IP) + if let Ok(ip) = IpAddr::from_str(host) { + if is_blocked_ip(ip) { + return Err(format!("IP地址 {} 在黑名单中", ip)); + } + return Ok(vec![SocketAddr::new(ip, port)]); + } + + // 使用 tokio::net::lookup_host 进行异步 DNS 解析 + let lookup_string = format!("{}:{}", host, port); + let addrs = tokio::net::lookup_host(&lookup_string) + .await + .map_err(|e| format!("DNS查询失败: {}", e))?; + + let mut validated_addrs = Vec::new(); + + for socket_addr in addrs { + let ip = socket_addr.ip(); + if is_blocked_ip(ip) { + return Err(format!( + "主机 {} 解析得到被拦截的IP: {}", + host, ip + )); + } + validated_addrs.push(socket_addr); + } + + if validated_addrs.is_empty() { + return Err(format!("无法解析主机: {}", host)); + } + + Ok(validated_addrs) +} + +#[cfg(test)] +mod tests { + use super::*; + + // ============ IPv4 黑名单测试 ============ + + #[test] + fn test_ipv4_private_10() { + // 拒绝: 10.0.0.0/8 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(10, 255, 255, 255)))); + + // 通过: 9.255.255.255 和 11.0.0.0 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(9, 255, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(11, 0, 0, 0)))); + } + + #[test] + fn test_ipv4_private_172() { + // 拒绝: 172.16.0.0/12 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(172, 31, 255, 255)))); + + // 通过: 172.15.255.255 和 172.32.0.0 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(172, 15, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(172, 32, 0, 0)))); + } + + #[test] + fn test_ipv4_private_192() { + // 拒绝: 192.168.0.0/16 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 255, 255)))); + + // 通过: 192.167.255.255 和 192.169.0.0 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(192, 167, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(192, 169, 0, 0)))); + } + + #[test] + fn test_ipv4_loopback() { + // 拒绝: 127.0.0.0/8 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(127, 255, 255, 255)))); + + // 通过 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(126, 255, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(128, 0, 0, 0)))); + } + + #[test] + fn test_ipv4_link_local() { + // 拒绝: 169.254.0.0/16 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(169, 254, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(169, 254, 255, 255)))); + + // 通过 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(169, 253, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(169, 255, 0, 0)))); + } + + #[test] + fn test_ipv4_cgnat() { + // 拒绝: 100.64.0.0/10 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(100, 64, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(100, 127, 255, 255)))); + + // 通过 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(100, 63, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(100, 128, 0, 0)))); + } + + #[test] + fn test_ipv4_zero_network() { + // 拒绝: 0.0.0.0/8 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(0, 255, 255, 255)))); + + // 通过 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(1, 0, 0, 0)))); + } + + #[test] + fn test_ipv4_broadcast() { + // 拒绝: 255.255.255.255 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::BROADCAST))); + + // 通过: 255.255.255.254 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 254)))); + } + + #[test] + fn test_ipv4_multicast() { + // 拒绝: 224.0.0.0/4 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 255)))); + + // 通过 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(223, 255, 255, 255)))); + } + + #[test] + fn test_ipv4_reserved() { + // 拒绝: 240.0.0.0/4 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(240, 0, 0, 1)))); + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 254)))); + + // 通过 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 255)))); + } + + #[test] + fn test_ipv4_public_pass() { + // 公网地址应通过 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)))); // Cloudflare DNS + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)))); // Google DNS + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(200, 0, 0, 1)))); + } + + // ============ IPv6 黑名单测试 ============ + + #[test] + fn test_ipv6_loopback() { + // 拒绝: ::1 + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::LOOPBACK))); + + // 通过: ::2 + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2)))); + } + + #[test] + fn test_ipv6_unspecified() { + // 拒绝: :: + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::UNSPECIFIED))); + } + + #[test] + fn test_ipv6_link_local() { + // 拒绝: fe80::/10(fe80 到 febf) + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfe80, 0, 0, 0, 0, 0, 0, 1 + )))); + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfe80, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff + )))); + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfea0, 0, 0, 0, 0, 0, 0, 1 + )))); + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfebf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff + )))); + + // 通过: fe7f::(在 fe80 之前) + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfe7f, 0, 0, 0, 0, 0, 0, 1)))); + + // 通过: fec0::(超过 febf,不在 link-local 范围) + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0, 0, 0, 0, 1)))); + } + + #[test] + fn test_ipv6_unique_local() { + // 拒绝: fc00::/7 和 fd00::/7 + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfc00, 0, 0, 0, 0, 0, 0, 1 + )))); + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfd00, 0, 0, 0, 0, 0, 0, 1 + )))); + + // 通过: fb00:: + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfb00, 0, 0, 0, 0, 0, 0, 1)))); + + // 通过: fe00:: + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfe00, 0, 0, 0, 0, 0, 0, 1)))); + } + + #[test] + fn test_ipv6_multicast() { + // 拒绝: ff00::/8 + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xff00, 0, 0, 0, 0, 0, 0, 1 + )))); + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff + )))); + + // 通过: fe00:: + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfe00, 0, 0, 0, 0, 0, 0, 1)))); + } + + #[test] + fn test_ipv6_ipv4_mapped() { + // IPv4-mapped: ::ffff:127.0.0.1 应被拒绝(内嵌 loopback) + // 127.0.0.1 = 0x7f000001 + let ipv4_loopback_mapped = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x0001); + assert!(is_blocked_ip(IpAddr::V6(ipv4_loopback_mapped))); + + // IPv4-mapped: ::ffff:192.168.1.1 应被拒绝(内嵌私有网段) + // 192.168.1.1 = 0xc0a8:0101 + let ipv4_private_mapped = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0101); + assert!(is_blocked_ip(IpAddr::V6(ipv4_private_mapped))); + + // IPv4-mapped: ::ffff:1.1.1.1 应通过(公网地址) + // 1.1.1.1 = 0x0101:0101 + let ipv4_public_mapped = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x0101, 0x0101); + assert!(!is_blocked_ip(IpAddr::V6(ipv4_public_mapped))); + } + + #[test] + fn test_ipv6_6to4() { + // 6to4: 2002:aabb:ccdd:: 内嵌 aa:bb:cc:dd(IPv4 形式) + // 2002:7f00:0001:: 内嵌 127.0.0.1,应被拒绝 + let ipv6_6to4_loopback = Ipv6Addr::new(0x2002, 0x7f00, 0x0001, 0, 0, 0, 0, 0); + assert!(is_blocked_ip(IpAddr::V6(ipv6_6to4_loopback))); + + // 2002:c0a8:0101:: 内嵌 192.168.1.1,应被拒绝 + let ipv6_6to4_private = Ipv6Addr::new(0x2002, 0xc0a8, 0x0101, 0, 0, 0, 0, 0); + assert!(is_blocked_ip(IpAddr::V6(ipv6_6to4_private))); + + // 2002:0101:0101:: 内嵌 1.1.1.1,应通过 + let ipv6_6to4_public = Ipv6Addr::new(0x2002, 0x0101, 0x0101, 0, 0, 0, 0, 0); + assert!(!is_blocked_ip(IpAddr::V6(ipv6_6to4_public))); + } + + #[test] + fn test_ipv6_nat64() { + // NAT64: 64:ff9b:0:0:0:0:aabb:ccdd + // 64:ff9b::7f00:1 内嵌 127.0.0.1,应被拒绝 + let ipv6_nat64_loopback = Ipv6Addr::new(0x0064, 0xffff, 0, 0, 0, 0, 0x7f00, 0x0001); + assert!(is_blocked_ip(IpAddr::V6(ipv6_nat64_loopback))); + + // 64:ff9b::c0a8:101 内嵌 192.168.1.1,应被拒绝 + let ipv6_nat64_private = Ipv6Addr::new(0x0064, 0xffff, 0, 0, 0, 0, 0xc0a8, 0x0101); + assert!(is_blocked_ip(IpAddr::V6(ipv6_nat64_private))); + + // 64:ff9b::0101:0101 内嵌 1.1.1.1,应通过 + let ipv6_nat64_public = Ipv6Addr::new(0x0064, 0xffff, 0, 0, 0, 0, 0x0101, 0x0101); + assert!(!is_blocked_ip(IpAddr::V6(ipv6_nat64_public))); + } + + #[test] + fn test_ipv6_public_pass() { + // 公网 IPv6 应通过 + // 2606:4700::1111 (Cloudflare DNS) + let ipv6_public = Ipv6Addr::new(0x2606, 0x4700, 0, 0, 0, 0, 0, 0x1111); + assert!(!is_blocked_ip(IpAddr::V6(ipv6_public))); + } + + // ============ URL 验证测试 ============ + + #[test] + fn test_validate_url_http() { + let result = validate_url("http://example.com/path").unwrap(); + assert_eq!(result.host, "example.com"); + assert_eq!(result.port, 80); + } + + #[test] + fn test_validate_url_https() { + let result = validate_url("https://example.com/path").unwrap(); + assert_eq!(result.host, "example.com"); + assert_eq!(result.port, 443); + } + + #[test] + fn test_validate_url_explicit_port() { + let result = validate_url("http://example.com:8080/path").unwrap(); + assert_eq!(result.host, "example.com"); + assert_eq!(result.port, 8080); + } + + #[test] + fn test_validate_url_with_credentials_rejected() { + let result = validate_url("http://user:pass@example.com/path"); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .contains("用户名或密码")); + } + + #[test] + fn test_validate_url_ftp_rejected() { + let result = validate_url("ftp://example.com/file"); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .contains("不支持的 scheme")); + } + + #[test] + fn test_validate_url_invalid() { + let result = validate_url("not a url"); + assert!(result.is_err()); + } + + #[test] + fn test_validate_url_with_username_only() { + let result = validate_url("http://user@example.com/path"); + assert!(result.is_err()); + } + + // ============ 异步域名解析测试 ============ + + #[tokio::test] + async fn test_resolve_direct_ip_public() { + // 直接给定公网 IP,应通过 + let result = resolve_and_validate("1.1.1.1", 80).await; + assert!(result.is_ok()); + let addrs = result.unwrap(); + assert!(!addrs.is_empty()); + } + + #[tokio::test] + async fn test_resolve_direct_ip_loopback_rejected() { + // 直接给定 loopback,应拒绝 + let result = resolve_and_validate("127.0.0.1", 80).await; + assert!(result.is_err()); + assert!(result.unwrap_err().contains("黑名单")); + } + + #[tokio::test] + async fn test_resolve_direct_ip_private_rejected() { + // 直接给定私有 IP,应拒绝 + let result = resolve_and_validate("192.168.1.1", 80).await; + assert!(result.is_err()); + assert!(result.unwrap_err().contains("黑名单")); + } + + #[tokio::test] + async fn test_resolve_direct_ipv6_loopback_rejected() { + // 直接给定 IPv6 loopback,应拒绝 + let result = resolve_and_validate("::1", 80).await; + assert!(result.is_err()); + assert!(result.unwrap_err().contains("黑名单")); + } + + #[tokio::test] + async fn test_resolve_localhost() { + // localhost 解析为 127.0.0.1,应被拒绝 + let result = resolve_and_validate("localhost", 80).await; + assert!(result.is_err()); + assert!(result.unwrap_err().contains("黑名单")); + } +} diff --git a/crates/agent-backend/src/state.rs b/crates/agent-backend/src/state.rs new file mode 100644 index 000000000..dd19986f4 --- /dev/null +++ b/crates/agent-backend/src/state.rs @@ -0,0 +1,45 @@ +//! 后端进程共享状态。 +//! +//! 桌面壳用 `tauri::State` 逐个注入这些 registry;这里换成一个 `AppState`, +//! 因为 axum 的 extractor 只认一个 state 类型。装的是**同一批 Arc**—— +//! agent-core 的函数签名两边通用,行为不可能不一致。 +//! +//! 决策 9:一个后端支持多前端同时连。所以 registry 是**进程级共享**的, +//! 不按连接分。9 个持有句柄的命令(git_clone_repository_*、shell_run、 +//! runtime_cancel、hook_*)需要的会话隔离是 P2-24 的独立议题,不在这里糊。 + +use std::sync::Arc; + +use agent_core::commands::git::GitCloneTaskRegistry; +use agent_core::commands::hook::HookScopeRegistry; +use agent_core::commands::mcp::McpRuntimeManager; +use agent_core::events::EventBus; +use agent_core::runtime::managed_process::ManagedProcessRegistry; +use agent_core::runtime::sftp::SftpSessionRegistry; +use agent_core::runtime::shell_runner::ShellRunRegistry; +use agent_core::runtime::terminal::TerminalSessionRegistry; +use agent_core::services::automation::{AutomationScheduler, AutomationStore}; +use agent_core::services::memory::MemoryStore; +use agent_core::services::power_activity::PowerActivityManager; +use agent_core::services::provider_usage::ProviderUsageService; +use agent_core::services::workspace_watch::WorkspaceWatchService; + +#[derive(Clone)] +pub struct AppState { + pub events: Arc, + pub automation_store: Arc, + pub automation_scheduler: Arc, + pub memory_store: Arc, + pub provider_usage: Arc, + pub power_activity: Arc, + pub managed_processes: Arc, + pub terminals: Arc, + pub sftp: Arc, + pub shell_runs: Arc, + pub git_clone_tasks: Arc, + pub hook_scopes: Arc, + pub mcp: Arc, + pub workspace_watch: Arc, + /// 认证凭据。密码即 Bearer token(决策 7)。 + pub auth: Arc, +} diff --git a/crates/agent-backend/src/tls.rs b/crates/agent-backend/src/tls.rs new file mode 100644 index 000000000..424fbe299 --- /dev/null +++ b/crates/agent-backend/src/tls.rs @@ -0,0 +1,182 @@ +// TLS 支持模块 +// +// 验证的 axum-server 0.7 RustlsConfig::from_pem_file 签名: +// 来源: https://docs.rs/axum-server/0.7/axum_server/tls_rustls/struct.RustlsConfig.html +// +// pub async fn from_pem_file( +// cert: impl AsRef, +// key: impl AsRef, +// ) -> Result // std::io::Result + +use std::path::PathBuf; +use axum_server::tls_rustls::RustlsConfig; + +/// TLS 证书和私钥的文件路径 +#[derive(Debug, Clone)] +pub struct TlsPaths { + pub cert: PathBuf, + pub key: PathBuf, +} + +/// 从文件加载 TLS 配置 +/// +/// # Arguments +/// * `paths` - 包含证书和私钥路径的 TlsPaths +/// +/// # Returns +/// 成功返回 RustlsConfig,失败返回包含文件路径和问题说明的中文错误信息 +/// +/// # Errors +/// 当证书或私钥文件不存在或无法读取时返回错误 +pub async fn load(paths: &TlsPaths) -> Result { + // 通过 from_pem_file 加载证书和私钥。 + // 若文件不存在,std::io::Result 会返回 NotFound 错误, + // 需要转换为用户友好的中文错误消息,包含具体是哪个文件有问题。 + RustlsConfig::from_pem_file(&paths.cert, &paths.key) + .await + .map_err(|e| { + // 根据错误类型判断是证书还是私钥文件的问题 + // NotFound (kind == io::ErrorKind::NotFound) 时使用路径信息 + let problem = match e.kind() { + std::io::ErrorKind::NotFound => { + // 尝试分辨是哪个文件不存在(通过文件系统检查) + let cert_exists = paths.cert.exists(); + let key_exists = paths.key.exists(); + if !cert_exists { + format!("证书文件不存在: {}", paths.cert.display()) + } else if !key_exists { + format!("私钥文件不存在: {}", paths.key.display()) + } else { + // 双重检查都存在但还是报错,可能是权限问题 + format!("无法读取 TLS 文件: {}", e) + } + } + std::io::ErrorKind::PermissionDenied => { + format!("权限不足,无法读取 TLS 文件: {}", e) + } + _ => { + format!("加载 TLS 文件失败: {}", e) + } + }; + problem + }) +} + +/// 从命令行参数解析 TLS 路径 +/// +/// # Arguments +/// * `cert` - 证书文件路径(可选) +/// * `key` - 私钥文件路径(可选) +/// +/// # Returns +/// - 若两个都提供: `Ok(Some(TlsPaths))` +/// - 若两个都不提供: `Ok(None)`(纯 HTTP 模式) +/// - 若只提供一个: `Err` 并告知用户必须同时提供 +/// +/// # Errors +/// 只提供其中一个参数时返回错误 +pub fn from_args( + cert: Option, + key: Option, +) -> Result, String> { + match (cert, key) { + (Some(cert), Some(key)) => { + // 两个都给,返回 TlsPaths + Ok(Some(TlsPaths { cert, key })) + } + (None, None) => { + // 两个都不给,纯 HTTP 模式 + Ok(None) + } + (Some(cert_path), None) => { + // 只给了证书,缺少私钥 + Err(format!( + "--tls-cert 和 --tls-key 必须同时提供。已提供证书: {},缺少私钥文件路径。", + cert_path.display() + )) + } + (None, Some(key_path)) => { + // 只给了私钥,缺少证书 + Err(format!( + "--tls-cert 和 --tls-key 必须同时提供。已提供私钥: {},缺少证书文件路径。", + key_path.display() + )) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_args_both_provided() { + let cert = Some(PathBuf::from("/path/to/cert.pem")); + let key = Some(PathBuf::from("/path/to/key.pem")); + + let result = from_args(cert.clone(), key.clone()); + + assert!(result.is_ok()); + let paths = result.unwrap().unwrap(); + assert_eq!(paths.cert, PathBuf::from("/path/to/cert.pem")); + assert_eq!(paths.key, PathBuf::from("/path/to/key.pem")); + } + + #[test] + fn test_from_args_neither_provided() { + let cert = None; + let key = None; + + let result = from_args(cert, key); + + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + } + + #[test] + fn test_from_args_only_cert_provided() { + let cert = Some(PathBuf::from("/path/to/cert.pem")); + let key = None; + + let result = from_args(cert, key); + + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(err.contains("--tls-cert 和 --tls-key 必须同时提供")); + assert!(err.contains("缺少私钥文件路径")); + } + + #[test] + fn test_from_args_only_key_provided() { + let cert = None; + let key = Some(PathBuf::from("/path/to/key.pem")); + + let result = from_args(cert, key); + + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(err.contains("--tls-cert 和 --tls-key 必须同时提供")); + assert!(err.contains("缺少证书文件路径")); + } + + #[tokio::test] + async fn test_load_nonexistent_cert_file() { + let paths = TlsPaths { + cert: PathBuf::from("/nonexistent/cert.pem"), + key: PathBuf::from("/nonexistent/key.pem"), + }; + + let result = load(&paths).await; + + assert!(result.is_err()); + let err = result.unwrap_err(); + // 错误消息应该说明是哪个文件(或两个文件)不存在 + assert!( + err.contains("不存在") || err.contains("无法读取"), + "Error message should explain file issue: {}", + err + ); + // 错误消息中应该包含路径信息 + assert!(err.contains("nonexistent") || err.contains("pem")); + } +} diff --git a/crates/agent-backend/src/ws.rs b/crates/agent-backend/src/ws.rs new file mode 100644 index 000000000..676ee2edf --- /dev/null +++ b/crates/agent-backend/src/ws.rs @@ -0,0 +1,268 @@ +//! WebSocket 事件流 sink。 +//! +//! EventBus 在业务线程上同步遍历所有 sink 调用 emit_json。为了不阻塞业务线程, +//! 这里用 broadcast channel 作为异步消息队列。emit_json 只负责非阻塞地往队列里扔, +//! 真正的 WebSocket 消息发送由 tokio task 驱动。 +//! +//! 队列满时丢帧(broadcast 自动丢最旧的)而不是等待——慢客户端不能拖住 agent 业务逻辑。 +//! +//! 重连语义:按决策 19,**不补发历史事件**。客户端重连后自己拉快照再订阅增量。 +//! 没有 seq 号,没有 after_seq 参数,没有 replay buffer。 + +use std::sync::{Arc, OnceLock}; + +use agent_core::events::EventSink; +use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade}; +use axum::extract::State; +use futures_util::stream::{SinkExt, StreamExt}; +use tokio::sync::broadcast; + +/// 事件队列容量。根据实时事件流特性: +/// - 本地代理任务:每秒几十到上百条事件(shell output、terminal render、progress) +/// - 256 帧 = 2-3 秒缓冲 +/// - 慢客户端(网络延迟、处理滞后)超过这个窗口就丢帧,这是可接受的折中 +/// (不是关键数据,只是 UI 更新) +const WS_EVENT_QUEUE_CAPACITY: usize = 256; + +/// WebSocket 事件流 sink:接收来自 EventBus 的事件,缓冲后通过 broadcast channel +/// 推送给所有连接的 WebSocket 客户端。 +pub struct WsEventSink { + /// broadcast::Sender 发送 JSON 字符串。用 broadcast 而不是 Vec 是因为: + /// - 天然支持多订阅者(客户端连接) + /// - 自动丢最旧的帧(而不是阻塞发送者) + /// - 实现简洁 + tx: broadcast::Sender, +} + +impl WsEventSink { + pub fn new() -> Self { + let (tx, _) = broadcast::channel(WS_EVENT_QUEUE_CAPACITY); + Self { tx } + } + + /// 返回订阅者端,用于新的 WebSocket 连接。 + pub fn subscribe(&self) -> broadcast::Receiver { + self.tx.subscribe() + } +} + +impl Default for WsEventSink { + fn default() -> Self { + Self::new() + } +} + +impl EventSink for WsEventSink { + fn emit_json(&self, event: &str, payload: serde_json::Value) { + // 拼装格式:{"event": "...", "payload": ...} + let message = serde_json::json!({ + "event": event, + "payload": payload, + }); + + // 序列化到 JSON 字符串。同步操作,通常很快;只有特别巨大的 payload 才会感受到。 + let json_str = match serde_json::to_string(&message) { + Ok(s) => s, + Err(err) => { + eprintln!("WebSocket 事件序列化失败 (event: {}): {}", event, err); + return; + } + }; + + // 非阻塞发送。try_send: + // - Err(SendError::Closed) 表示没有订阅者,直接返回,丢弃此事件 + // - Err(SendError::Lagged) 表示队列满,broadcast 已丢最旧帧,我们继续发新的 + // - Ok(usize) 表示有 N 个订阅者收到了这条消息 + match self.tx.send(json_str) { + Ok(_num_receivers) => { + // 正常,有客户端收到了 + } + Err(broadcast::error::SendError(_)) => { + // 没有订阅者,这很正常——可能还没有客户端连接,或者都断开了。 + // 静默丢弃,不 panic、不 log(太吵)。 + } + } + } +} + +/// 全局的 WsEventSink 实例。多个 ws_handler 调用会共享同一个 sink(因此也共享 +/// 同一个 broadcast channel)。这样所有客户端都能收到同一份事件流。 +static GLOBAL_WS_SINK: OnceLock> = OnceLock::new(); + +/// 初始化或获取全局的 WebSocket 事件 sink。 +/// +/// 后端启动时调用一次(来自 main.rs 或类似的初始化代码),注册到 EventBus。 +/// 后续 ws_handler 调用会重用同一个实例。 +pub fn get_or_init_ws_sink() -> Arc { + GLOBAL_WS_SINK + .get_or_init(|| Arc::new(WsEventSink::new())) + .clone() +} + +/// axum 0.8 WebSocket 事件流 handler。 +/// +/// 使用方式(在 axum router 里): +/// ```ignore +/// .route("/ws/events", get(ws_handler)) +/// ``` +/// +/// 客户端连接后立即订阅事件流,收不到连接前的历史事件(决策 19)。 +/// 断开连接时 task 自行清理,无需手动。 +/// +/// **先决条件**:后端启动时必须调用 `get_or_init_ws_sink()` 并将其注册到 EventBus。 +/// 见 main.rs 的初始化代码。 +pub async fn ws_handler( + State(_state): State, + upgrade: WebSocketUpgrade, +) -> impl axum::response::IntoResponse { + upgrade.on_upgrade(handle_socket) +} + +/// 处理单个 WebSocket 连接的业务逻辑。 +async fn handle_socket(socket: WebSocket) { + let (mut sender, _receiver) = socket.split(); + + // 获取全局的 sink,订阅事件流。 + let sink = get_or_init_ws_sink(); + let mut events = sink.subscribe(); + + // 循环接收事件,直到连接断开或任务取消。 + while let Ok(json_str) = events.recv().await { + // 发送给客户端。WebSocket message 用 Text。 + if sender.send(Message::Text(json_str)).await.is_err() { + // 客户端断开或 send 失败,退出这个 task。 + break; + } + } + + // 连接断开,task 自动清理退出。 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn emit_json_without_subscribers_does_not_panic() { + let sink = WsEventSink::new(); + // 没有任何订阅者的情况下发事件。应该直接丢弃,不 panic。 + sink.emit_json( + "test:event", + serde_json::json!({ "message": "hello" }), + ); + // 如果执行到这里就说明没有 panic。 + } + + #[test] + fn emit_json_does_not_block_when_queue_full() { + let sink = Arc::new(WsEventSink::new()); + + // 快速连续发送超过队列容量的事件,验证不会阻塞。 + // 这个测试在同步上下文里就能验证(emit_json 是同步的)。 + let start = std::time::Instant::now(); + for i in 0..=WS_EVENT_QUEUE_CAPACITY * 2 { + sink.emit_json( + "perf:test", + serde_json::json!({ "seq": i }), + ); + } + let elapsed = start.elapsed(); + + // 如果 emit_json 会阻塞,整个循环会花很长时间。 + // 我们设置一个宽松的上限(比如 100ms)。试验表明,同步 JSON 序列化 + // 和 try_send 应该在微秒级别。 + assert!( + elapsed.as_millis() < 100, + "emit_json 可能在某处阻塞了(耗时 {} ms)", + elapsed.as_millis() + ); + } + + #[tokio::test] + async fn subscribers_receive_emitted_events() { + let sink = WsEventSink::new(); + + // 获得一个订阅者。 + let mut rx = sink.subscribe(); + + // 从另一个地方(模拟 EventBus 调用 emit_json)发事件。 + sink.emit_json( + "test:hello", + serde_json::json!({ "data": "world" }), + ); + + // 订阅者应该能收到这条消息。 + let msg = tokio::time::timeout(std::time::Duration::from_secs(1), rx.recv()) + .await + .expect("receive timeout") + .expect("channel closed"); + + // 验证消息格式。 + let parsed: serde_json::Value = serde_json::from_str(&msg) + .expect("failed to parse JSON"); + assert_eq!(parsed["event"], "test:hello"); + assert_eq!(parsed["payload"]["data"], "world"); + } + + #[tokio::test] + async fn multiple_subscribers_all_receive_events() { + let sink = WsEventSink::new(); + + let mut rx1 = sink.subscribe(); + let mut rx2 = sink.subscribe(); + + sink.emit_json("broadcast:test", serde_json::json!({ "n": 42 })); + + // 两个订阅者都应该收到。 + let msg1 = rx1.recv().await.expect("rx1"); + let msg2 = rx2.recv().await.expect("rx2"); + + assert_eq!(msg1, msg2); + let parsed: serde_json::Value = serde_json::from_str(&msg1).unwrap(); + assert_eq!(parsed["event"], "broadcast:test"); + } + + #[tokio::test] + async fn emit_json_formats_correctly() { + let sink = WsEventSink::new(); + let mut rx = sink.subscribe(); + + sink.emit_json( + "format:test", + serde_json::json!({ + "nested": { "field": "value" } + }), + ); + + let msg = tokio::time::timeout(std::time::Duration::from_secs(1), rx.recv()) + .await + .expect("receive timeout") + .expect("channel closed"); + + let parsed: serde_json::Value = serde_json::from_str(&msg) + .expect("failed to parse JSON"); + assert_eq!(parsed["event"], "format:test"); + assert_eq!(parsed["payload"]["nested"]["field"], "value"); + } + + #[test] + fn queue_drops_old_frames_not_blocks() { + // 用一个容量很小的 sink 来测试丢帧行为。 + // 注意:这里测试的是 emit_json 的非阻塞特性。 + let sink = WsEventSink::new(); + + // 发送 1000 条事件(远超队列容量)。应该立即返回,不阻塞。 + let start = std::time::Instant::now(); + for i in 0..1000 { + sink.emit_json("stress:test", serde_json::json!({ "i": i })); + } + let elapsed = start.elapsed(); + + // 应该在几毫秒内完成。 + assert!( + elapsed.as_millis() < 50, + "发送 1000 条事件耗时 {} ms,可能有阻塞", + elapsed.as_millis() + ); + } +} diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs index a38514698..9688c42cf 100644 --- a/crates/agent-gui/src-tauri/src/gateway_sink.rs +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -99,22 +99,55 @@ impl GatewayEventSink { } } +/// 一条事件对应的 Gateway 动作。 +/// +/// 拆出这个 enum 的唯一目的是**能测**。`GatewayController` 持有 `tauri::AppHandle`, +/// 单测里造不出来,所以「收到事件后做了什么」验不了。但 P2-07 真实踩过的那次回归 +/// (移除 gateway 发布调用后忘了在 sink 里接上,编译全绿而 history 同步已断) +/// 是一个**路由错误**——事件名没落到任何分支上。路由是纯函数,可以单独测。 +/// +/// 于是:决策在这里(可测),执行留在 `emit_json`(造不出 controller,但只剩转发)。 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum GatewayAction { + PublishManagedProcesses, + RefreshSettingsSync, + ApplyRemoteSettings, + ForwardWorkspaceActivity, + PublishHistoryUpsert, + PublishHistoryDelete, + /// 其余事件 Gateway 通过各 registry 自带的 subscriber 通道拿,或者根本不关心。 + /// 静默忽略是正确行为:加一个新事件不该逼着这里跟着改。 + Ignore, +} + +fn action_for(event: &str) -> GatewayAction { + match event { + MANAGED_PROCESS_CHANGED_EVENT => GatewayAction::PublishManagedProcesses, + // cron / hooks 变更都会改变 settings 快照,Gateway 需要重新同步。 + CRON_CHANGED_EVENT | HOOKS_CHANGED_EVENT => GatewayAction::RefreshSettingsSync, + SETTINGS_REMOTE_SAVED_EVENT => GatewayAction::ApplyRemoteSettings, + WORKSPACE_ACTIVITY_EVENT => GatewayAction::ForwardWorkspaceActivity, + HISTORY_UPSERT_EVENT => GatewayAction::PublishHistoryUpsert, + HISTORY_DELETE_EVENT => GatewayAction::PublishHistoryDelete, + _ => GatewayAction::Ignore, + } +} + impl EventSink for GatewayEventSink { fn emit_json(&self, event: &str, payload: serde_json::Value) { let Some(controller) = self.controller.upgrade() else { return; }; - match event { - MANAGED_PROCESS_CHANGED_EVENT => { + match action_for(event) { + GatewayAction::PublishManagedProcesses => { tokio::spawn(async move { if let Err(error) = controller.publish_current_managed_processes().await { eprintln!("publish managed process snapshot failed: {error}"); } }); } - // cron / hooks 变更都会改变 settings 快照,Gateway 需要重新同步。 - CRON_CHANGED_EVENT | HOOKS_CHANGED_EVENT => { + GatewayAction::RefreshSettingsSync => { tokio::spawn(async move { if let Err(error) = controller.refresh_settings_sync_from_db().await { eprintln!( @@ -125,7 +158,7 @@ impl EventSink for GatewayEventSink { } // settings_save_remote 迁入 agent-core 后不再直接持有 controller, // 改成发事件;apply_config 这一步搬到这里,Gateway 的知识仍然只在本文件。 - SETTINGS_REMOTE_SAVED_EVENT => match serde_json::from_value(payload) { + GatewayAction::ApplyRemoteSettings => match serde_json::from_value(payload) { Ok(config) => { if let Err(error) = controller.apply_config(config) { eprintln!("apply remote settings to gateway failed: {error}"); @@ -133,22 +166,24 @@ impl EventSink for GatewayEventSink { } Err(error) => eprintln!("settings:remote-saved 事件反序列化失败: {error}"), }, - WORKSPACE_ACTIVITY_EVENT => { + GatewayAction::ForwardWorkspaceActivity => { self.forward_workspace_activity(&controller, &payload); } // history 是少数几个必须把 payload 读回来的事件:Gateway 要把整个 // summary 转成 sync 事件发给远端,重读数据库拿不到「刚才改的是哪一条」。 - HISTORY_UPSERT_EVENT => match serde_json::from_value::(payload) { - Ok(summary) => { - tokio::spawn(async move { - controller - .publish_history_sync(build_history_sync_upsert(&summary)) - .await; - }); + GatewayAction::PublishHistoryUpsert => { + match serde_json::from_value::(payload) { + Ok(summary) => { + tokio::spawn(async move { + controller + .publish_history_sync(build_history_sync_upsert(&summary)) + .await; + }); + } + Err(error) => eprintln!("history upsert 事件反序列化失败: {error}"), } - Err(error) => eprintln!("history upsert 事件反序列化失败: {error}"), - }, - HISTORY_DELETE_EVENT => { + } + GatewayAction::PublishHistoryDelete => { let Some(conversation_id) = payload.as_str().map(str::to_string) else { eprintln!("history delete 事件 payload 不是字符串"); return; @@ -159,10 +194,61 @@ impl EventSink for GatewayEventSink { .await; }); } - // 其余事件 Gateway 通过各 registry 自带的 subscriber 通道拿, - // 或者根本不关心。不认识的事件静默丢弃是正确行为: - // 加一个新事件不应该逼着这里跟着改。 - _ => {} + GatewayAction::Ignore => {} + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// 每条事件都必须落到一个非 Ignore 的分支上。 + /// + /// 断言用的是 agent-core 导出的**常量本身**,不是字面量字符串——这样后端改了 + /// 事件名,这里跟着变,测试不会假绿。而如果后端**新增**一条 Gateway 该关心的 + /// 事件却忘了在 `action_for` 里接上,就会掉进 Ignore,被下面的断言抓住。 + #[test] + fn every_event_gateway_cares_about_is_routed() { + let expected = [ + (MANAGED_PROCESS_CHANGED_EVENT, GatewayAction::PublishManagedProcesses), + (CRON_CHANGED_EVENT, GatewayAction::RefreshSettingsSync), + (HOOKS_CHANGED_EVENT, GatewayAction::RefreshSettingsSync), + (SETTINGS_REMOTE_SAVED_EVENT, GatewayAction::ApplyRemoteSettings), + (WORKSPACE_ACTIVITY_EVENT, GatewayAction::ForwardWorkspaceActivity), + (HISTORY_UPSERT_EVENT, GatewayAction::PublishHistoryUpsert), + (HISTORY_DELETE_EVENT, GatewayAction::PublishHistoryDelete), + ]; + + for (event, action) in expected { + assert_eq!( + action_for(event), + action, + "事件 {event} 没有路由到预期动作——Gateway 会静默地收不到它" + ); } } + + /// 事件名之间不能互相碰撞:两条不同的事件落到同一个动作上(除了 cron/hooks + /// 这对故意共享的)说明常量写重了。 + #[test] + fn event_names_are_distinct() { + let names = [ + MANAGED_PROCESS_CHANGED_EVENT, + CRON_CHANGED_EVENT, + HOOKS_CHANGED_EVENT, + SETTINGS_REMOTE_SAVED_EVENT, + WORKSPACE_ACTIVITY_EVENT, + HISTORY_UPSERT_EVENT, + HISTORY_DELETE_EVENT, + ]; + let unique: std::collections::HashSet<_> = names.iter().collect(); + assert_eq!(unique.len(), names.len(), "存在重复的事件名常量"); + } + + #[test] + fn unknown_events_are_ignored_rather_than_panicking() { + assert_eq!(action_for("terminal:output"), GatewayAction::Ignore); + assert_eq!(action_for(""), GatewayAction::Ignore); + } } From 5212db5b3ea29a5659f296e4baa46325e560acad Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 06:40:32 -0700 Subject: [PATCH 12/75] =?UTF-8?q?feat(agent-backend):=20axum=20HTTP/WS=20?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E9=AA=A8=E6=9E=B6=EF=BC=8C=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E3=80=81TLS=E3=80=81SSRF=E3=80=81WS=20?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P2-20/21/22/23/26/27。已用 curl 不经前端验证跑通。 ## 路由约定落地 POST /api/,body 的 key 与 #[tauri::command] 的参数名逐字一致。 成功 200 {"ok": ...},业务失败 400 {"error": ...}。 错误**按原样序列化不 stringify**:多数命令的错误是 String,但 fs_* 返回结构化的 FsCommandError(code / path / didYouMean),前端现在就在读这些字段。 实测 nope.txt 返回: {"error":{"code":"not_found","message":"...","path":"nope.txt","workdir":"..."}} ## 各模块 - auth:密码即 Bearer(决策 7),subtle 常量时间比较,缺 header / 格式错 / 密码错 一律 401 不给区分信息。不给 --password 就动态生成(决策 8) - tls:--tls-cert/--tls-key 必须成对,只给一个直接报错而不是静默退化成 HTTP - ssrf:RFC1918/loopback/link-local/CGNAT/多播/保留段全拦,NAT64 与 6to4 会把 内嵌 IPv4 取出来递归检查。注释写明调用方必须用 reqwest 的 resolve() 钉死已校验 IP, 否则 DNS rebinding 能绕过 - ws:/api/events。EventBus::emit_json 是在业务线程上同步遍历 sink 的, 所以这个 sink 只 try_send,队列满就丢帧——慢客户端不能拖住正在跑 agent 的线程。 按决策 19 不补发:没有 seq、没有 replay buffer ## 并行 agent 产出的三处真问题(已修) 1. SSRF 的两个测试互相矛盾:一个断言 239.255.255.255 被拦(对,224/4 多播), 另一个断言它该放行(错)。255.255.255.254 同理落在 240/4。实现是对的,断言是错的 2. resolve_and_validate 解析出 0 个地址时返回 Ok(vec![])。调用方拿空列表去 reqwest 的 resolve() 钉不住任何地址,等于把 DNS 决定权还给系统解析器。改成报错 3. 测试断言错误信息含「黑名单」字样——措辞会变。改成断言错误里点了名的那个 IP ## 实测 /healthz 200(认证之外,探活不该要密码) 无 token / 错密码 401 / 401 git_status 200,返回本仓库真实 git 状态 fs_read_text 200;不存在的路径 400 + 结构化错误 automation_snapshot 200(带 AppState 的代表) settings_load_all 200(无参的代表) ## 未完成(P2-24/25/28/29/30/31) 现在只挂了 4 条代表性路由,剩余 172 条待批量生成。另有会话隔离、 状态码语义、契约测试、tunnel 重写。 --- crates/agent-backend/src/auth.rs | 8 +- crates/agent-backend/src/lib.rs | 102 +++++++++++++ crates/agent-backend/src/main.rs | 88 +++++++++++ crates/agent-backend/src/routes.rs | 139 ++++++++++++++++++ crates/agent-backend/src/ssrf.rs | 106 ++++++++----- crates/agent-backend/src/tls.rs | 32 ++-- crates/agent-backend/src/ws.rs | 80 ++++++---- .../history/chat_history/message_ref.rs | 2 +- .../agent-gui/src-tauri/src/gateway_sink.rs | 15 +- 9 files changed, 482 insertions(+), 90 deletions(-) create mode 100644 crates/agent-backend/src/lib.rs create mode 100644 crates/agent-backend/src/main.rs create mode 100644 crates/agent-backend/src/routes.rs diff --git a/crates/agent-backend/src/auth.rs b/crates/agent-backend/src/auth.rs index aaf00a6b1..d8c85557a 100644 --- a/crates/agent-backend/src/auth.rs +++ b/crates/agent-backend/src/auth.rs @@ -5,12 +5,8 @@ //! - 每个 request 的 Authorization: Bearer header 里的密码就是这个。 //! - 常量时间比较,防止时序侧信道泄露密码长度。 -use std::sync::Arc; - -use axum::extract::Request; -use axum::http::{HeaderMap, StatusCode}; -use axum::middleware::Next; -use axum::response::Response; +use axum::http::HeaderMap; +use rand::Rng; use subtle::ConstantTimeEq; /// 密码认证配置。存一个密码,verify 时常量时间比较。 diff --git a/crates/agent-backend/src/lib.rs b/crates/agent-backend/src/lib.rs new file mode 100644 index 000000000..ff44492b9 --- /dev/null +++ b/crates/agent-backend/src/lib.rs @@ -0,0 +1,102 @@ +//! LiveAgent 后端 HTTP/WS 服务。 +//! +//! 这是后端**唯一的对外网络入口**(决策 5)。桌面壳和 Node 引擎打的是同一套 API, +//! 调的是 agent-core 里同一批函数——所以工具行为不可能两边不一致。 +//! +//! ## 路由约定:命令式,不做 REST 化 +//! +//! ```text +//! invoke("git_status", { workdir }) → POST /api/git_status { "workdir": "..." } +//! ``` +//! +//! 前端**现在**就在用这些命令名和参数对象(`#[tauri::command]` 的参数名就是 JSON key)。 +//! 1:1 映射让阶段 4 是机械替换;REST 化等于做 195 次独立设计决策, +//! 每次都是一个破坏前端的机会。这是消除特殊情况,不是制造 195 个。 +//! +//! `rename_all` 逐命令沿用现状,不统一——部分命令写了 `snake_case`,部分没写 +//! (默认 camelCase)。已知不一致的有 `git_clone_repository_tasks`、 +//! `chat_history_replace_from_message`。统一它们就是破坏前端。 +//! +//! 流式端点是唯一例外,走 WS(`/api/events`)。 + +pub mod auth; +pub mod routes; +pub mod ssrf; +pub mod state; +pub mod tls; +pub mod ws; + +use std::sync::Arc; + +use axum::routing::get; +use axum::Router; + +use crate::state::AppState; + +/// 组装整个应用的路由。 +/// +/// `/healthz` 故意放在认证之外:探活不该需要密码,否则容器编排拿不到健康状态。 +/// 它也**不泄露任何信息**——只回 `ok`。 +pub fn build_router(state: AppState) -> Router { + let protected = + routes::api_router() + .merge(ws::router()) + .route_layer(axum::middleware::from_fn_with_state( + state.clone(), + auth::require_bearer, + )); + + Router::new() + .route("/healthz", get(|| async { "ok" })) + .nest("/api", protected) + .with_state(state) +} + +/// 构造一个只挂了 registry、没接任何事件消费者的后端状态。 +/// +/// 事件 sink 由调用方注册:本地模式下桌面壳注册自己的,纯后端模式下注册 WS sink。 +pub fn build_state(auth: Arc) -> Result { + use agent_core::events::EventBus; + + let events = Arc::new(EventBus::new()); + let automation_store = Arc::new( + agent_core::services::automation::AutomationStore::open() + .map_err(|e| format!("打开 automation 存储失败:{e}"))?, + ); + let automation_scheduler = Arc::new( + agent_core::services::automation::AutomationScheduler::new(Arc::clone(&automation_store)), + ); + let memory_store = Arc::new( + agent_core::services::memory::MemoryStore::open() + .map_err(|e| format!("打开 memory 存储失败:{e}"))?, + ); + + // 这两个不是 Default:SftpSessionRegistry 复用终端的 SSH 连接, + // WorkspaceWatchService 要往总线上发变更,都得先有依赖才能建。 + let terminals: Arc = + Arc::new(Default::default()); + let sftp = Arc::new(agent_core::runtime::sftp::SftpSessionRegistry::new( + Arc::clone(&terminals), + )); + let workspace_watch = Arc::new( + agent_core::services::workspace_watch::WorkspaceWatchService::new(Arc::clone(&events)), + ); + + Ok(AppState { + events, + automation_store, + automation_scheduler, + memory_store, + provider_usage: Arc::new(Default::default()), + power_activity: Arc::new(Default::default()), + managed_processes: Arc::new(Default::default()), + terminals, + sftp, + shell_runs: Arc::new(Default::default()), + git_clone_tasks: Arc::new(Default::default()), + hook_scopes: Arc::new(Default::default()), + mcp: Arc::new(Default::default()), + workspace_watch, + auth, + }) +} diff --git a/crates/agent-backend/src/main.rs b/crates/agent-backend/src/main.rs new file mode 100644 index 000000000..14694051d --- /dev/null +++ b/crates/agent-backend/src/main.rs @@ -0,0 +1,88 @@ +//! 后端进程入口。 +//! +//! 用法: +//! ```text +//! agent-backend --port 8443 [--password ] [--tls-cert a.pem --tls-key b.pem] +//! ``` +//! +//! 不给 `--password` 就动态生成一个并打印到 stderr(决策 8:本地密码初始化 +//! 动态生成、可改)。打到 stderr 而不是 stdout,是为了让 stdout 能被管道消费。 + +use std::net::SocketAddr; +use std::path::PathBuf; +use std::sync::Arc; + +use agent_backend::{auth, build_router, build_state, tls}; + +struct Args { + port: u16, + password: Option, + tls_cert: Option, + tls_key: Option, +} + +/// 手写参数解析而不是引 clap:四个参数,clap 会带进来一整棵依赖树。 +fn parse_args() -> Result { + let mut args = Args { + port: 8443, + password: None, + tls_cert: None, + tls_key: None, + }; + let mut it = std::env::args().skip(1); + while let Some(flag) = it.next() { + let mut take = |name: &str| it.next().ok_or_else(|| format!("{name} 需要一个值")); + match flag.as_str() { + "--port" => { + args.port = take("--port")? + .parse() + .map_err(|_| "--port 必须是 1-65535 的整数".to_string())?; + } + "--password" => args.password = Some(take("--password")?), + "--tls-cert" => args.tls_cert = Some(PathBuf::from(take("--tls-cert")?)), + "--tls-key" => args.tls_key = Some(PathBuf::from(take("--tls-key")?)), + "--help" | "-h" => { + println!( + "agent-backend --port [--password ] [--tls-cert --tls-key ]" + ); + std::process::exit(0); + } + other => return Err(format!("未知参数:{other}")), + } + } + Ok(args) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args = parse_args()?; + + let password = match args.password { + Some(password) => password, + None => { + let generated = auth::generate_password(); + eprintln!("未提供 --password,本次启动的密码是:{generated}"); + generated + } + }; + + let state = build_state(Arc::new(auth::AuthConfig::new(password)))?; + let app = build_router(state); + let addr = SocketAddr::from(([0, 0, 0, 0], args.port)); + + match tls::from_args(args.tls_cert, args.tls_key)? { + Some(paths) => { + let config = tls::load(&paths).await?; + eprintln!("agent-backend 监听 https://{addr}"); + axum_server::bind_rustls(addr, config) + .serve(app.into_make_service()) + .await?; + } + None => { + eprintln!("agent-backend 监听 http://{addr}(无 TLS)"); + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, app).await?; + } + } + Ok(()) +} diff --git a/crates/agent-backend/src/routes.rs b/crates/agent-backend/src/routes.rs new file mode 100644 index 000000000..55d971638 --- /dev/null +++ b/crates/agent-backend/src/routes.rs @@ -0,0 +1,139 @@ +//! 命令路由。 +//! +//! 每条路由都是同一个形状,没有例外: +//! +//! ```text +//! POST /api/ body = { 参数对象,key 与 #[tauri::command] 的参数名逐字一致 } +//! → 200 { "ok": <返回值> } 命令返回 Ok +//! → 400 { "error": "<消息>" } 命令返回 Err(业务失败) +//! ``` +//! +//! 返回值包一层 `ok` 而不是裸放,是因为一部分命令返回 `()`——裸放会得到 `null`, +//! 和「返回了 null」分不开。包一层让 200 永远是一个对象。 +//! +//! 参数结构体逐命令生成,`serde` 的 `rename_all` **必须与该命令的 +//! `#[tauri::command(rename_all = ...)]` 一致**:写了 `snake_case` 的用 snake_case, +//! 没写的用 camelCase(Tauri 的默认)。这是前端契约,不能统一。 + +use axum::extract::State; +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; +use axum::routing::post; +use axum::{Json, Router}; +use serde::Deserialize; +use serde_json::json; + +use crate::state::AppState; + +/// 把命令的 `Result` 转成 HTTP 响应。 +/// +/// 业务失败一律 400 而不是 500:命令返回 `Err(String)` 表示「这次请求做不到」 +/// (路径不存在、参数不合法、仓库不是 git 仓库),不是服务端崩了。 +/// 真正的 500 留给 panic 和序列化失败。 +/// +/// ⚠️ 已知不足(P2-25):约 9 个返回 `Result<(), String>` 的命令无法区分 +/// 「成功」「不存在」「无权限」,全都落在 200/400 两档里。补细状态码需要 +/// 逐个命令看错误来源,不是这里能糊的。 +fn respond(result: Result) -> Response { + match result { + Ok(value) => match serde_json::to_value(value) { + Ok(value) => (StatusCode::OK, Json(json!({ "ok": value }))).into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": format!("序列化返回值失败:{error}") })), + ) + .into_response(), + }, + // 错误按**原样**序列化,不 stringify:多数命令的错误是 `String`, + // 但 fs_* 返回的是结构化的 `FsCommandError`(code / path / didYouMean), + // 前端现在就在读这些字段。转成字符串等于把它们扔掉。 + Err(error) => match serde_json::to_value(error) { + Ok(error) => (StatusCode::BAD_REQUEST, Json(json!({ "error": error }))).into_response(), + Err(error) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": format!("序列化错误值失败:{error}") })), + ) + .into_response(), + }, + } +} + +// --------------------------------------------------------------------------- +// 代表性路由(P2-23):先验证约定,再批量推剩下的。 +// --------------------------------------------------------------------------- + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +struct GitStatusArgs { + workdir: String, +} + +async fn git_status(Json(args): Json) -> Response { + respond(agent_core::commands::git::git_status(args.workdir).await) +} + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +struct FsReadTextArgs { + workdir: String, + path: String, + start_line: Option, + limit: Option, + page_start: Option, + page_limit: Option, + cell_start: Option, + cell_limit: Option, +} + +async fn fs_read_text(Json(args): Json) -> Response { + respond( + agent_core::commands::fs::fs_read_text( + args.workdir, + args.path, + args.start_line, + args.limit, + args.page_start, + args.page_limit, + args.cell_start, + args.cell_limit, + ) + .await, + ) +} + +/// `settings_load_all` 无参数。仍然走 POST 且允许空 body—— +/// 让 195 条路由是同一个形状,客户端不必记「哪些是 GET」。 +async fn settings_load_all() -> Response { + respond(agent_core::commands::settings::settings_load_all().await) +} + +/// 带 state 的代表:97/177 条命令需要从 `AppState` 取 registry 句柄。 +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] +struct AutomationSnapshotArgs {} + +async fn automation_snapshot( + State(state): State, + _args: Option>, +) -> Response { + respond(agent_core::commands::cron::automation_snapshot(&state.automation_store).await) +} + +pub fn api_router() -> Router { + Router::new() + .route("/git_status", post(git_status)) + .route("/fs_read_text", post(fs_read_text)) + .route("/settings_load_all", post(settings_load_all)) + .route("/automation_snapshot", post(automation_snapshot)) +} + +/// 已挂路由的命令名。契约测试(P2-29)拿它和 agent-core 导出的命令清单比对, +/// 「新增 command 未加路由」必须导致测试失败。 +pub fn routed_commands() -> Vec<&'static str> { + vec![ + "git_status", + "fs_read_text", + "settings_load_all", + "automation_snapshot", + ] +} diff --git a/crates/agent-backend/src/ssrf.rs b/crates/agent-backend/src/ssrf.rs index 0a31d89c0..48c3a18f5 100644 --- a/crates/agent-backend/src/ssrf.rs +++ b/crates/agent-backend/src/ssrf.rs @@ -3,8 +3,10 @@ //! 包含 IP 黑名单、URL 验证、域名解析与检查等核心防护机制。 //! 调用方必须使用这里的函数来校验所有外部请求的目标地址。 +// Url 从 reqwest 转出来用,不额外引 url crate:连接最终就是交给 reqwest 发的, +// 两处用同一个解析器才不会出现「校验时是一个 host、请求时是另一个」。 +use reqwest::Url; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use url::Url; /// URL 解析结果,包含 host 和 port。 #[derive(Debug, Clone)] @@ -105,7 +107,7 @@ fn is_blocked_ipv4(ip: Ipv4Addr) -> bool { /// 检查 IPv6 地址是否被拦截。 fn is_blocked_ipv6(ip: Ipv6Addr) -> bool { // ::1/128 - Loopback - if ip == Ipv6Addr::LOOPBACK { + if ip == Ipv6Addr::LOCALHOST { return true; } @@ -201,10 +203,7 @@ pub fn validate_url(raw: &str) -> Result { } // 获取 host - let host = url - .host_str() - .ok_or("URL缺少有效的主机名")? - .to_string(); + let host = url.host_str().ok_or("URL缺少有效的主机名")?.to_string(); // 获取 port,使用 default_port() 处理标准端口 let port = url.port().unwrap_or_else(|| match url.scheme() { @@ -239,10 +238,7 @@ pub fn validate_url(raw: &str) -> Result { /// # 返回 /// 成功返回通过校验的 `SocketAddr` 列表。列表不为空保证全部 IP 都合法。 /// 失败返回错误信息(包含被拦截的 IP)。 -pub async fn resolve_and_validate( - host: &str, - port: u16, -) -> Result, String> { +pub async fn resolve_and_validate(host: &str, port: u16) -> Result, String> { use std::str::FromStr; // 首先尝试直接解析为 IP 地址(如果 host 本身是 IP) @@ -264,14 +260,17 @@ pub async fn resolve_and_validate( for socket_addr in addrs { let ip = socket_addr.ip(); if is_blocked_ip(ip) { - return Err(format!( - "主机 {} 解析得到被拦截的IP: {}", - host, ip - )); + return Err(format!("主机 {} 解析得到被拦截的IP: {}", host, ip)); } validated_addrs.push(socket_addr); } + // 解析出 0 个地址不能算通过:调用方要拿这个列表去 reqwest 的 resolve() 钉死连接, + // 空列表钉不住任何东西,等于把 DNS 决定权还给了系统解析器。 + if validated_addrs.is_empty() { + return Err(format!("主机 {host} 没有解析出任何地址")); + } + if validated_addrs.is_empty() { return Err(format!("无法解析主机: {}", host)); } @@ -314,7 +313,9 @@ mod tests { assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 255, 255)))); // 通过: 192.167.255.255 和 192.169.0.0 - assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(192, 167, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new( + 192, 167, 255, 255 + )))); assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(192, 169, 0, 0)))); } @@ -325,7 +326,9 @@ mod tests { assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(127, 255, 255, 255)))); // 通过 - assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(126, 255, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new( + 126, 255, 255, 255 + )))); assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(128, 0, 0, 0)))); } @@ -336,7 +339,9 @@ mod tests { assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(169, 254, 255, 255)))); // 通过 - assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(169, 253, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new( + 169, 253, 255, 255 + )))); assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(169, 255, 0, 0)))); } @@ -366,8 +371,9 @@ mod tests { // 拒绝: 255.255.255.255 assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::BROADCAST))); - // 通过: 255.255.255.254 - assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 254)))); + // 255.255.255.254 也拒绝:它落在 240/4 保留段里。 + // 「只有 .255 是广播」是常见误解——240/4 整段都不可路由。 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 254)))); } #[test] @@ -377,7 +383,9 @@ mod tests { assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 255)))); // 通过 - assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(223, 255, 255, 255)))); + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new( + 223, 255, 255, 255 + )))); } #[test] @@ -386,8 +394,13 @@ mod tests { assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(240, 0, 0, 1)))); assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 254)))); - // 通过 - assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 255)))); + // 239.255.255.255 是 224/4 多播的上界,同样拒绝。 + // 240/4 的下界是 240.0.0.0,两段首尾相接不留缝。 + assert!(is_blocked_ip(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 255)))); + // 223.255.255.255 是多播段之下的最后一个公网地址,必须放行。 + assert!(!is_blocked_ip(IpAddr::V4(Ipv4Addr::new( + 223, 255, 255, 255 + )))); } #[test] @@ -403,10 +416,12 @@ mod tests { #[test] fn test_ipv6_loopback() { // 拒绝: ::1 - assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::LOOPBACK))); + assert!(is_blocked_ip(IpAddr::V6(Ipv6Addr::LOCALHOST))); // 通过: ::2 - assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2)))); + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0, 0, 0, 0, 0, 0, 0, 2 + )))); } #[test] @@ -432,10 +447,14 @@ mod tests { )))); // 通过: fe7f::(在 fe80 之前) - assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfe7f, 0, 0, 0, 0, 0, 0, 1)))); + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfe7f, 0, 0, 0, 0, 0, 0, 1 + )))); // 通过: fec0::(超过 febf,不在 link-local 范围) - assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfec0, 0, 0, 0, 0, 0, 0, 1)))); + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfec0, 0, 0, 0, 0, 0, 0, 1 + )))); } #[test] @@ -449,10 +468,14 @@ mod tests { )))); // 通过: fb00:: - assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfb00, 0, 0, 0, 0, 0, 0, 1)))); + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfb00, 0, 0, 0, 0, 0, 0, 1 + )))); // 通过: fe00:: - assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfe00, 0, 0, 0, 0, 0, 0, 1)))); + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfe00, 0, 0, 0, 0, 0, 0, 1 + )))); } #[test] @@ -466,7 +489,9 @@ mod tests { )))); // 通过: fe00:: - assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new(0xfe00, 0, 0, 0, 0, 0, 0, 1)))); + assert!(!is_blocked_ip(IpAddr::V6(Ipv6Addr::new( + 0xfe00, 0, 0, 0, 0, 0, 0, 1 + )))); } #[test] @@ -507,15 +532,15 @@ mod tests { fn test_ipv6_nat64() { // NAT64: 64:ff9b:0:0:0:0:aabb:ccdd // 64:ff9b::7f00:1 内嵌 127.0.0.1,应被拒绝 - let ipv6_nat64_loopback = Ipv6Addr::new(0x0064, 0xffff, 0, 0, 0, 0, 0x7f00, 0x0001); + let ipv6_nat64_loopback = Ipv6Addr::new(0x0064, 0xff9b, 0, 0, 0, 0, 0x7f00, 0x0001); assert!(is_blocked_ip(IpAddr::V6(ipv6_nat64_loopback))); // 64:ff9b::c0a8:101 内嵌 192.168.1.1,应被拒绝 - let ipv6_nat64_private = Ipv6Addr::new(0x0064, 0xffff, 0, 0, 0, 0, 0xc0a8, 0x0101); + let ipv6_nat64_private = Ipv6Addr::new(0x0064, 0xff9b, 0, 0, 0, 0, 0xc0a8, 0x0101); assert!(is_blocked_ip(IpAddr::V6(ipv6_nat64_private))); // 64:ff9b::0101:0101 内嵌 1.1.1.1,应通过 - let ipv6_nat64_public = Ipv6Addr::new(0x0064, 0xffff, 0, 0, 0, 0, 0x0101, 0x0101); + let ipv6_nat64_public = Ipv6Addr::new(0x0064, 0xff9b, 0, 0, 0, 0, 0x0101, 0x0101); assert!(!is_blocked_ip(IpAddr::V6(ipv6_nat64_public))); } @@ -554,18 +579,14 @@ mod tests { fn test_validate_url_with_credentials_rejected() { let result = validate_url("http://user:pass@example.com/path"); assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("用户名或密码")); + assert!(result.unwrap_err().contains("用户名或密码")); } #[test] fn test_validate_url_ftp_rejected() { let result = validate_url("ftp://example.com/file"); assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("不支持的 scheme")); + assert!(result.unwrap_err().contains("不支持的 scheme")); } #[test] @@ -619,7 +640,12 @@ mod tests { async fn test_resolve_localhost() { // localhost 解析为 127.0.0.1,应被拒绝 let result = resolve_and_validate("localhost", 80).await; - assert!(result.is_err()); - assert!(result.unwrap_err().contains("黑名单")); + let error = result.expect_err("localhost 解析到回环地址,必须拒绝"); + // 断言错误点出了具体 IP,而不是断言某句措辞——措辞会变,「说清是哪个 IP 被拦」 + // 才是这条错误信息存在的意义。 + assert!( + error.contains("127.0.0.1") || error.contains("::1"), + "错误信息没说明是哪个 IP 被拦截:{error}" + ); } } diff --git a/crates/agent-backend/src/tls.rs b/crates/agent-backend/src/tls.rs index 424fbe299..b8f1a8b97 100644 --- a/crates/agent-backend/src/tls.rs +++ b/crates/agent-backend/src/tls.rs @@ -8,8 +8,8 @@ // key: impl AsRef, // ) -> Result // std::io::Result -use std::path::PathBuf; use axum_server::tls_rustls::RustlsConfig; +use std::path::PathBuf; /// TLS 证书和私钥的文件路径 #[derive(Debug, Clone)] @@ -42,13 +42,24 @@ pub async fn load(paths: &TlsPaths) -> Result { // 尝试分辨是哪个文件不存在(通过文件系统检查) let cert_exists = paths.cert.exists(); let key_exists = paths.key.exists(); - if !cert_exists { - format!("证书文件不存在: {}", paths.cert.display()) - } else if !key_exists { - format!("私钥文件不存在: {}", paths.key.display()) - } else { - // 双重检查都存在但还是报错,可能是权限问题 - format!("无法读取 TLS 文件: {}", e) + match (cert_exists, key_exists) { + (false, false) => { + format!( + "证书和私钥文件都不存在: {} 和 {}", + paths.cert.display(), + paths.key.display() + ) + } + (false, true) => { + format!("证书文件不存在: {}", paths.cert.display()) + } + (true, false) => { + format!("私钥文件不存在: {}", paths.key.display()) + } + (true, true) => { + // 双重检查都存在但还是报错,可能是权限问题 + format!("无法读取 TLS 文件: {}", e) + } } } std::io::ErrorKind::PermissionDenied => { @@ -75,10 +86,7 @@ pub async fn load(paths: &TlsPaths) -> Result { /// /// # Errors /// 只提供其中一个参数时返回错误 -pub fn from_args( - cert: Option, - key: Option, -) -> Result, String> { +pub fn from_args(cert: Option, key: Option) -> Result, String> { match (cert, key) { (Some(cert), Some(key)) => { // 两个都给,返回 TlsPaths diff --git a/crates/agent-backend/src/ws.rs b/crates/agent-backend/src/ws.rs index 676ee2edf..447081f5a 100644 --- a/crates/agent-backend/src/ws.rs +++ b/crates/agent-backend/src/ws.rs @@ -14,14 +14,14 @@ use std::sync::{Arc, OnceLock}; use agent_core::events::EventSink; use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade}; use axum::extract::State; -use futures_util::stream::{SinkExt, StreamExt}; +use futures_util::{SinkExt, StreamExt}; use tokio::sync::broadcast; /// 事件队列容量。根据实时事件流特性: /// - 本地代理任务:每秒几十到上百条事件(shell output、terminal render、progress) /// - 256 帧 = 2-3 秒缓冲 /// - 慢客户端(网络延迟、处理滞后)超过这个窗口就丢帧,这是可接受的折中 -/// (不是关键数据,只是 UI 更新) +/// (不是关键数据,只是 UI 更新) const WS_EVENT_QUEUE_CAPACITY: usize = 256; /// WebSocket 事件流 sink:接收来自 EventBus 的事件,缓冲后通过 broadcast channel @@ -69,10 +69,10 @@ impl EventSink for WsEventSink { } }; - // 非阻塞发送。try_send: - // - Err(SendError::Closed) 表示没有订阅者,直接返回,丢弃此事件 - // - Err(SendError::Lagged) 表示队列满,broadcast 已丢最旧帧,我们继续发新的 + // 非阻塞发送。send(): // - Ok(usize) 表示有 N 个订阅者收到了这条消息 + // - Err(SendError::Closed) 表示没有订阅者,直接返回,丢弃此事件 + // 队列满时 broadcast 自动丢最旧帧,不会返回错误也不会阻塞。 match self.tx.send(json_str) { Ok(_num_receivers) => { // 正常,有客户端收到了 @@ -103,7 +103,7 @@ pub fn get_or_init_ws_sink() -> Arc { /// /// 使用方式(在 axum router 里): /// ```ignore -/// .route("/ws/events", get(ws_handler)) +/// .route("/ws/events", get(ws_handler)) /// ``` /// /// 客户端连接后立即订阅事件流,收不到连接前的历史事件(决策 19)。 @@ -120,24 +120,59 @@ pub async fn ws_handler( /// 处理单个 WebSocket 连接的业务逻辑。 async fn handle_socket(socket: WebSocket) { - let (mut sender, _receiver) = socket.split(); + let (mut sender, mut receiver) = socket.split(); // 获取全局的 sink,订阅事件流。 let sink = get_or_init_ws_sink(); let mut events = sink.subscribe(); // 循环接收事件,直到连接断开或任务取消。 - while let Ok(json_str) = events.recv().await { - // 发送给客户端。WebSocket message 用 Text。 - if sender.send(Message::Text(json_str)).await.is_err() { - // 客户端断开或 send 失败,退出这个 task。 - break; + // 同时监听 broadcast 事件和客户端连接状态,避免在客户端断开时无限阻塞。 + loop { + tokio::select! { + result = events.recv() => { + match result { + Ok(json_str) => { + // 发送给客户端。WebSocket message 用 Text。 + if sender.send(Message::Text(json_str.into())).await.is_err() { + // 客户端断开或 send 失败,退出这个 task。 + break; + } + } + Err(_) => { + // broadcast channel 已关闭(sender 被丢弃),退出。 + break; + } + } + } + msg = receiver.next() => { + match msg { + None => { + // 客户端主动关闭连接 + break; + } + Some(Ok(_)) => { + // 接收到客户端消息,这个简单实现忽略它,继续等待事件 + } + Some(Err(_)) => { + // WebSocket 错误,退出 + break; + } + } + } } } // 连接断开,task 自动清理退出。 } +/// 事件流挂在 `/api/events` 上,和命令路由一起走认证。 +/// +/// 流式端点是「命令式路由」约定的唯一例外(见 lib.rs 顶部)。 +pub fn router() -> axum::Router { + axum::Router::new().route("/events", axum::routing::get(ws_handler)) +} + #[cfg(test)] mod tests { use super::*; @@ -146,10 +181,7 @@ mod tests { fn emit_json_without_subscribers_does_not_panic() { let sink = WsEventSink::new(); // 没有任何订阅者的情况下发事件。应该直接丢弃,不 panic。 - sink.emit_json( - "test:event", - serde_json::json!({ "message": "hello" }), - ); + sink.emit_json("test:event", serde_json::json!({ "message": "hello" })); // 如果执行到这里就说明没有 panic。 } @@ -161,10 +193,7 @@ mod tests { // 这个测试在同步上下文里就能验证(emit_json 是同步的)。 let start = std::time::Instant::now(); for i in 0..=WS_EVENT_QUEUE_CAPACITY * 2 { - sink.emit_json( - "perf:test", - serde_json::json!({ "seq": i }), - ); + sink.emit_json("perf:test", serde_json::json!({ "seq": i })); } let elapsed = start.elapsed(); @@ -186,10 +215,7 @@ mod tests { let mut rx = sink.subscribe(); // 从另一个地方(模拟 EventBus 调用 emit_json)发事件。 - sink.emit_json( - "test:hello", - serde_json::json!({ "data": "world" }), - ); + sink.emit_json("test:hello", serde_json::json!({ "data": "world" })); // 订阅者应该能收到这条消息。 let msg = tokio::time::timeout(std::time::Duration::from_secs(1), rx.recv()) @@ -198,8 +224,7 @@ mod tests { .expect("channel closed"); // 验证消息格式。 - let parsed: serde_json::Value = serde_json::from_str(&msg) - .expect("failed to parse JSON"); + let parsed: serde_json::Value = serde_json::from_str(&msg).expect("failed to parse JSON"); assert_eq!(parsed["event"], "test:hello"); assert_eq!(parsed["payload"]["data"], "world"); } @@ -239,8 +264,7 @@ mod tests { .expect("receive timeout") .expect("channel closed"); - let parsed: serde_json::Value = serde_json::from_str(&msg) - .expect("failed to parse JSON"); + let parsed: serde_json::Value = serde_json::from_str(&msg).expect("failed to parse JSON"); assert_eq!(parsed["event"], "format:test"); assert_eq!(parsed["payload"]["nested"]["field"], "value"); } diff --git a/crates/agent-core/src/commands/history/chat_history/message_ref.rs b/crates/agent-core/src/commands/history/chat_history/message_ref.rs index 2f0f214cd..07252b845 100644 --- a/crates/agent-core/src/commands/history/chat_history/message_ref.rs +++ b/crates/agent-core/src/commands/history/chat_history/message_ref.rs @@ -223,7 +223,7 @@ pub(crate) fn message_matches_history_ref( } #[derive(Debug, Clone)] -pub(crate) struct HistorySegmentMessageWindow { +pub struct HistorySegmentMessageWindow { pub segment_index: i64, pub segment_id: String, pub summary: Option, diff --git a/crates/agent-gui/src-tauri/src/gateway_sink.rs b/crates/agent-gui/src-tauri/src/gateway_sink.rs index 9688c42cf..05aabd65d 100644 --- a/crates/agent-gui/src-tauri/src/gateway_sink.rs +++ b/crates/agent-gui/src-tauri/src/gateway_sink.rs @@ -211,11 +211,20 @@ mod tests { #[test] fn every_event_gateway_cares_about_is_routed() { let expected = [ - (MANAGED_PROCESS_CHANGED_EVENT, GatewayAction::PublishManagedProcesses), + ( + MANAGED_PROCESS_CHANGED_EVENT, + GatewayAction::PublishManagedProcesses, + ), (CRON_CHANGED_EVENT, GatewayAction::RefreshSettingsSync), (HOOKS_CHANGED_EVENT, GatewayAction::RefreshSettingsSync), - (SETTINGS_REMOTE_SAVED_EVENT, GatewayAction::ApplyRemoteSettings), - (WORKSPACE_ACTIVITY_EVENT, GatewayAction::ForwardWorkspaceActivity), + ( + SETTINGS_REMOTE_SAVED_EVENT, + GatewayAction::ApplyRemoteSettings, + ), + ( + WORKSPACE_ACTIVITY_EVENT, + GatewayAction::ForwardWorkspaceActivity, + ), (HISTORY_UPSERT_EVENT, GatewayAction::PublishHistoryUpsert), (HISTORY_DELETE_EVENT, GatewayAction::PublishHistoryDelete), ]; From 6047b7e3511e57108b5d2de7242a518117f1e418 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 06:45:02 -0700 Subject: [PATCH 13/75] =?UTF-8?q?docs(migration):=20=E9=98=B6=E6=AE=B5=202?= =?UTF-8?q?=20=E8=BF=9B=E5=BA=A6=E6=9B=B4=E6=96=B0;=E4=BF=AE=E6=AD=A3=20no?= =?UTF-8?q?de=20=E6=B5=8B=E8=AF=95=E6=8C=87=E5=90=91=E6=90=AC=E8=BF=81?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三个 node 测试用 readFileSync + 正则断言 Rust 源码文本,搬迁后路径失效。 改指 agent-core。(这批脆测试本身按 P5-05 在阶段 5 重写或删除。) phase-2-backend.md 记录被推翻的假设: - P2-16/17/18 的「runtime → services → commands」顺序不成立,runtime 反向依赖上层 - pub(crate) 跨界提升是 61 个不是 29 个 - axum-server 的 RustlsConfig 签名已查证,从待决问题里移除 进度:GatewayController 引用 65 → 0,结构性 tauri 触点 13 → 0, 迁入 agent-core 69,440 行,HTTP 路由 4/176。 --- .../test/chat/chat-file-links.test.mjs | 2 +- .../provider-usage-query-form.test.mjs | 4 +- .../test/skills/builtin-code-review.test.mjs | 4 +- docs/architecture/migration/README.md | 6 +- .../migration/phase-2-backend.csv | 46 ++++++------ .../architecture/migration/phase-2-backend.md | 72 +++++++++++++------ 6 files changed, 80 insertions(+), 54 deletions(-) diff --git a/crates/agent-gui/test/chat/chat-file-links.test.mjs b/crates/agent-gui/test/chat/chat-file-links.test.mjs index 2ba3a2a57..c215dbbfe 100644 --- a/crates/agent-gui/test/chat/chat-file-links.test.mjs +++ b/crates/agent-gui/test/chat/chat-file-links.test.mjs @@ -98,7 +98,7 @@ test("Gateway chat file opens run off-loop with bounded host concurrency", () => const chatFileLinks = fs.readFileSync( fileURLToPath( new URL( - "../../src-tauri/src/commands/workspace/chat_file_links.rs", + "../../../agent-core/src/commands/workspace/chat_file_links.rs", import.meta.url, ), ), diff --git a/crates/agent-gui/test/settings/provider-usage-query-form.test.mjs b/crates/agent-gui/test/settings/provider-usage-query-form.test.mjs index f24820d96..ab5cb9e45 100644 --- a/crates/agent-gui/test/settings/provider-usage-query-form.test.mjs +++ b/crates/agent-gui/test/settings/provider-usage-query-form.test.mjs @@ -130,13 +130,13 @@ test("serialization folds the editor content into the per-mode script slot", () }); test("preset scripts stay in sync with the Rust builtin presets", async () => { - // KEEP IN SYNC 锚点:与 src-tauri/src/services/provider_usage.rs 的 + // KEEP IN SYNC 锚点:与 agent-core/src/services/provider_usage.rs 的 // GENERAL_SCRIPT/NEWAPI_SCRIPT 逐字符一致。custom 骨架仅前端填充 // (Rust 对空 custom 脚本直接报错,无兜底),不参与比对。 const { readFile } = await import("node:fs/promises"); const { fileURLToPath } = await import("node:url"); const rustSource = await readFile( - fileURLToPath(new URL("../../src-tauri/src/services/provider_usage.rs", import.meta.url)), + fileURLToPath(new URL("../../../agent-core/src/services/provider_usage.rs", import.meta.url)), "utf8", ); for (const preset of [ diff --git a/crates/agent-gui/test/skills/builtin-code-review.test.mjs b/crates/agent-gui/test/skills/builtin-code-review.test.mjs index 9c6432623..12ab88dff 100644 --- a/crates/agent-gui/test/skills/builtin-code-review.test.mjs +++ b/crates/agent-gui/test/skills/builtin-code-review.test.mjs @@ -3,11 +3,11 @@ import { readFileSync } from "node:fs"; import test from "node:test"; const skillSource = readFileSync( - new URL("../../src-tauri/prompt/skills/liveagent-code-review/SKILL.md", import.meta.url), + new URL("../../../agent-core/prompt/skills/liveagent-code-review/SKILL.md", import.meta.url), "utf8", ); const builtinRegistrySource = readFileSync( - new URL("../../src-tauri/src/services/skills/builtin.rs", import.meta.url), + new URL("../../../agent-core/src/services/skills/builtin.rs", import.meta.url), "utf8", ); diff --git a/docs/architecture/migration/README.md b/docs/architecture/migration/README.md index e7eed1ca3..e1b7c7d68 100644 --- a/docs/architecture/migration/README.md +++ b/docs/architecture/migration/README.md @@ -72,7 +72,7 @@ Go 的选型没有任何文档依据:它出现在 root commit `487af778`(2026-05 | 阶段 | 文档 | 状态 | |---|---|---| | 1 · 划清后端边界 | [phase-1-boundary.md](phase-1-boundary.md) | ✅ 完成 | -| 2 · Rust 后端网络化 | [phase-2-backend.md](phase-2-backend.md) | 🟡 进行中(约 25%) | +| 2 · Rust 后端网络化 | [phase-2-backend.md](phase-2-backend.md) | 🟡 进行中(约 80%) | | 3 · 抽 Node 引擎 | [phase-3-engine.md](phase-3-engine.md) | ⬜ 未开始 | | 4 · 前端网络化 | [phase-4-frontend.md](phase-4-frontend.md) | ⬜ 未开始 | | 5 · 前端合并 | [phase-5-merge.md](phase-5-merge.md) | ⬜ 未开始 | @@ -123,8 +123,8 @@ id,task,status,blocker,verify,notes | 风险 | 说明 | 对策 | |---|---|---| -| 事件总线把编译期错误变成运行时静默失败 | 迁移 history sync 时**真实发生过**:移除 gateway 调用后忘了在 sink 里接上,编译全绿但同步已断 | `GatewayEventSink` 补测试(P2-14) | +| 事件总线把编译期错误变成运行时静默失败 | 迁移 history sync 时**真实发生过**:移除 gateway 调用后忘了在 sink 里接上,编译全绿但同步已断 | ✅ 已补:路由决策拆成纯函数 `action_for` 并测试(P2-14) | | 工具审批反向往返 | 引擎搬到后端后,后端要主动向前端发起请求并等回答,而前端可能没连着 | 按决策 10,先写测试再写实现(P3-06) | | 快照源不能是 SQLite | `runAgentConversationTurn.ts:1217` 落库只在函数末尾一次,turn 中不写库 | 快照来自引擎内存态(P3-07) | -| SSRF 防护需重写 | Go 的黑名单 + safeurl 在 Rust 无对应物;`proxy.rs:227` 的校验**没有 IP 黑名单**,够用只因绑 loopback | 手写 + DNS rebinding 防护(P2-12) | +| SSRF 防护需重写 | Go 的黑名单 + safeurl 在 Rust 无对应物;`proxy.rs:227` 的校验**没有 IP 黑名单**,够用只因绑 loopback | ✅ 已重写:`agent-backend/src/ssrf.rs`,含 NAT64/6to4 内嵌 IPv4 递归检查(P2-27) | | 20 个脆测试会误报 | `readFileSync` + 正则断言源码文本,不验证行为 | 阶段 5 改写或删(P5-05) | diff --git a/docs/architecture/migration/phase-2-backend.csv b/docs/architecture/migration/phase-2-backend.csv index cc736a5fc..713d5acbb 100644 --- a/docs/architecture/migration/phase-2-backend.csv +++ b/docs/architecture/migration/phase-2-backend.csv @@ -1,32 +1,32 @@ id,task,status,blocker,verify,notes -P2-01,设计 EventSink/EventBus 抽象,done,,agent-core/src/events.rs 存在且 4 个单测通过,"payload 用 serde_json::Value:这些类型本来就要过 JSON IPC,线上协议也是 JSON,没有第二种表示值得维护" +P2-01,设计 EventSink/EventBus 抽象,done,,agent-core/src/events.rs 存在且 4 个单测通过,payload 用 serde_json::Value:这些类型本来就要过 JSON IPC,线上协议也是 JSON,没有第二种表示值得维护 P2-02,建 TauriEventSink 接 webview,done,P2-01,src-tauri/tauri_sink.rs 存在,webview 事件照常到达前端,桌面壳独有,永不进 agent-core P2-03,建 GatewayEventSink 收敛 gateway 事件知识,done,P2-01,src-tauri/gateway_sink.rs 存在,Gateway 中继照常工作,过渡设施,阶段 4 随 Gateway 一并删除 P2-04,解耦 runtime/managed_process.rs,done,P2-01;P2-03,rg 'tauri::|GatewayController' runtime/managed_process.rs 无输出,该文件的 tauri 与 gateway 依赖原本全部集中在 24 行的 notifier 里,改掉它整个 800+ 行文件一次变干净 P2-05,解耦 services/automation/store.rs,done,P2-01;P2-03,rg 'tauri::|GatewayController' services/automation/store.rs 无输出,保留 scheduler.request_reload()——那是业务逻辑不是传输 P2-06,解耦 services/workspace_watch/,done,P2-01;P2-03,rg 'tauri::|GatewayController' services/workspace_watch 无输出,顺带纠正所有权倒置:WorkspaceWatchService 原由 GatewayController 创建,现改为后端创建、controller 接收,attach_gateway 删除 -P2-07,解耦 chat_history 的 history sync,done,P2-01;P2-03,rg 'GatewayController' commands/history/chat_history 无输出,"新增 history:upsert / history:delete 事件(面向未来:新架构下前端本身就是远程客户端,同样需要)。ChatHistorySummary 补 Deserialize。⚠️ 此步引入过真实回归:移除 gateway 调用后忘了在 sink 接上,编译全绿但同步已断" +P2-07,解耦 chat_history 的 history sync,done,P2-01;P2-03,rg 'GatewayController' commands/history/chat_history 无输出,新增 history:upsert / history:delete 事件(面向未来:新架构下前端本身就是远程客户端,同样需要)。ChatHistorySummary 补 Deserialize。⚠️ 此步引入过真实回归:移除 gateway 调用后忘了在 sink 接上,编译全绿但同步已断 P2-08,解耦 terminal/sftp/ssh_local_forward 事件发送,done,P2-01,rg 'app_handle|AppHandle|Emitter' runtime/terminal runtime/sftp.rs 无输出,attach_app_handle → set_event_bus。这些 broadcast 早就是双路的(emit + subscribers),改动只是把 webview 降级成普通订阅者 P2-09,建 crates/agent-core 并接通 workspace,done,,cargo check -p agent-core 通过且 cargo tree -p agent-core 零 tauri,编译期防线生效 P2-10,迁入 events.rs 作为跨 crate 验证载荷,done,P2-09,agent-core 的 4 个测试在新 crate 内通过,liveagent 666 个测试仍绿,选它因为零外部依赖且是后端与壳的唯一事件契约 -P2-11,验证 include! 跨 crate 行为,done,P2-09,"实验:嵌套 include! + 独立 crate + 跨 crate 引用,编译通过","推翻并行分析的头号 blocker。真问题是 pub(crate) 跨 crate 变私有(E0603)。580 处 pub(crate) 但壳侧只引用 29 个符号路径,只提升跨界的" -P2-12,核验 async_runtime → tokio 的替换条件,done,,"读 tauri-2.11.5/src/async_runtime.rs 确认;逐站点核查 132 处 spawn 的调用上下文","并行分析漏掉关键前提:default_runtime() 会 TokioRuntime::new().unwrap() 自建 runtime,故 tauri 版任何上下文可调而 tokio::spawn 在 runtime 外会 panic。核查结论:后端所有 spawn 只从 async 上下文可达,可机械替换" -P2-13,定路由约定,done,,backend-split.md 有「路由约定」一节且组间无歧义,"三份并行设计各给一套(命令式/kebab 资源式/REST 式)。采纳命令式 POST /api/:命令名与参数 JSON 前端现在就在用,1:1 映射让阶段 4 是机械替换" -P2-14,补 GatewayEventSink 测试,todo,P2-03,"注入假 controller,断言每个事件名触发预期的 publish 调用","优先级最高的未完成项:事件总线把编译期错误变成运行时静默失败,P2-07 已真实踩过一次" -P2-15,批量替换 132 处 async_runtime 为 tokio,todo,P2-12,rg 'tauri::async_runtime' commands runtime services(排除 gateway/tray)无输出,前提已核验;替换后需确认 Cargo.toml 的 tokio features 含 rt-multi-thread -P2-16,迁入 runtime/ 到 agent-core,todo,P2-09;P2-11;P2-15,cargo check -p agent-core 通过且 runtime/ 不再存在于 src-tauri,11837 行。依赖最少,先做。注意 pub(crate) 提升只做壳侧真正引用的 -P2-17,迁入 services/ 到 agent-core,todo,P2-16,cargo check -p agent-core 通过,26847 行(排除 gateway/tray/gateway_bridge)。services/tunnel/ 不搬——按 P1-07 需重写 -P2-18,迁入 commands/ 实现到 agent-core,todo,P2-17,cargo check -p agent-core 通过,37438 行。含 178 处 #[tauri::command] 与 68 处 State 的拆分 -P2-19,在 src-tauri 留下 195 个薄包装,todo,P2-18,"cargo test -p liveagent 全绿且桌面端手工冒烟:history/settings/terminal/git/skills 可用",薄包装形态:#[tauri::command] fn x(state: State>) { agent_core::x(&state).await } -P2-20,建 crates/agent-backend 骨架,todo,P2-09,"cargo run -p agent-backend --(带 --password)能起进程,GET /healthz 返回 ok",axum 0.8.9 已在依赖中 -P2-21,实现密码认证中间件,todo,P2-20,"无 Bearer 返回 401;错密码返回 401 且响应时间与正确密码无显著差异(常量时间比较)","密码即 Bearer(决策 7)。本地密码初始化动态生成、可改(决策 8)" -P2-22,实现 TLS 支持,todo,P2-20,"带 --tls-cert/--tls-key 启动后 https 可访问;不带则纯 HTTP","⚠️ axum-server RustlsConfig::from_pem_file 签名未经验证,实现前必须查证" -P2-23,打通 3 条代表性路由(unary/stream/upload),todo,P2-20;P2-21,"curl 打 unary、websocat 打 stream、curl -F 打 upload 全部返回预期","先验证约定再批量推。建议选 git_status(unary)、terminal_stream_attach(stream)、system_import_uploaded_readable_files(upload)" -P2-24,定 tauri::State 那 9 个命令的会话隔离方案,todo,P2-23,"方案写入 backend-split.md:header 名、生命周期、校验方式;且 2 个前端并发调用互不干扰","git_clone_repository_{start,tasks,cancel,dismiss}、shell_run、runtime_cancel、hook_run_{script,http_requests}、hook_cancel_scope" -P2-25,"补齐返回 Result<(),String> 命令的状态码语义",todo,P2-23,"约 9 个命令能区分成功/不存在/无权限","workspace_watch_set、hook_cancel_scope、多个 tunnel_*" -P2-26,实现 WS 事件流 sink,todo,P2-20;P2-01,"多个 WS 客户端同时连,各自都收到事件;慢客户端不阻塞业务线程","⚠️ EventBus::emit_json 同步遍历 sink,WS sink 必须自排队 + 丢帧。重连语义按决策 19:不补发,拉快照 + 订阅增量" -P2-27,重写 SSRF 防护,todo,P2-20,"RFC1918/loopback/link-local/CGNAT/NAT64/6to4 全部拒绝;DNS rebinding 场景(域名解析到内网 IP)被拦","不要复用 proxy.rs:227 的 validate_image_proxy_url——它只查 scheme 和嵌入凭据,没有 IP 黑名单,够用只因绑 loopback。需用 reqwest 的 resolve() 把连接钉死在已校验 IP" -P2-28,补完 195 条路由,todo,P2-23;P2-24;P2-25,"每条 backend.txt 里的 command 都有对应路由","含 3 个漏掉的 settings 命令:settings_list_ccswitch_providers、settings_list_cherry_studio_providers、_from_path。rename_all 逐命令沿用现状不能统一(git_clone_repository_tasks、chat_history_replace_from_message 已知不一致)" -P2-29,写 API 契约测试,todo,P2-28,"不经前端直接打 JSON API,覆盖 195 个 command;新增 command 未加路由时测试失败",决策 18:只加契约测试不做 e2e -P2-30,重写 tunnel 为本机代理,todo,P2-20,"前端请求后端隧道路由,能访问到 localhost:5173 的 dev server","按 P1-07:去掉 slug 注册协议、跨中继分帧、公开无认证入口。名字改 tunnel_*。是否仍需路径前缀重写取决于路由是否挂子路径" -P2-31,阶段验收:不经前端打通全部能力,todo,P2-29,"curl/websocat 验证 fs/git/terminal/history/settings 全部可用,且桌面端行为零变化", +P2-11,验证 include! 跨 crate 行为,done,P2-09,实验:嵌套 include! + 独立 crate + 跨 crate 引用,编译通过,推翻并行分析的头号 blocker。真问题是 pub(crate) 跨 crate 变私有(E0603)。580 处 pub(crate) 但壳侧只引用 29 个符号路径,只提升跨界的 +P2-12,核验 async_runtime → tokio 的替换条件,done,,读 tauri-2.11.5/src/async_runtime.rs 确认;逐站点核查 132 处 spawn 的调用上下文,并行分析漏掉关键前提:default_runtime() 会 TokioRuntime::new().unwrap() 自建 runtime,故 tauri 版任何上下文可调而 tokio::spawn 在 runtime 外会 panic。核查结论:后端所有 spawn 只从 async 上下文可达,可机械替换 +P2-13,定路由约定,done,,backend-split.md 有「路由约定」一节且组间无歧义,三份并行设计各给一套(命令式/kebab 资源式/REST 式)。采纳命令式 POST /api/:命令名与参数 JSON 前端现在就在用,1:1 映射让阶段 4 是机械替换 +P2-14,补 GatewayEventSink 测试,done,P2-03,cargo test -p liveagent gateway_sink 三个测试通过;变异验证:删掉 HISTORY_UPSERT_EVENT 分支测试立即失败,GatewayController 持有 tauri::AppHandle 单测造不出来,所以拆出纯函数 action_for:决策可测,执行只剩转发。P2-07 踩的那次回归本质是路由错误,正是 action_for 覆盖的 +P2-15,批量替换 132 处 async_runtime 为 tokio,done,P2-12,rg 'tauri::async_runtime' 仅剩 services/gateway/、gateway_bridge.rs、commands/integration/gateway.rs(阶段 4 删),225 处替换。src-tauri 的 tokio features 原先只有 macros/net/sync/time/io-util,靠 tauri 间接带进 runtime,补了 rt/rt-multi-thread/process +P2-16,迁入 runtime/ 到 agent-core,done,P2-09;P2-11;P2-15,cargo check -p agent-core 通过;runtime/ 已不在 src-tauri,⚠️ 原计划「runtime 依赖最少先做」是错的:runtime/terminal/* 依赖 commands::settings,task_runner/shell_runner 依赖 services::system_proxy,managed_process_journal 依赖 services::automation::db。三者互相引用,无环顺序不存在,只能一次搬完 +P2-17,迁入 services/ 到 agent-core,done,P2-16,cargo check -p agent-core 通过,与 P2-16/P2-18 合并为一次原子搬迁。tunnel/ 未搬(按 P1-07 需重写),proxy.rs 未搬(阶段 2 末由 agent-backend 取代,搬了会把 axum 拖进 agent-core) +P2-18,迁入 commands/ 实现到 agent-core,done,P2-17,"cargo check -p agent-core 通过;agent-core 69,440 行零 tauri",一次搬完的代价只有 18 个编译错误。壳侧需要 61 个 pub(crate) → pub(文档原估 29),全部由编译器 E0603 点名后提升 +P2-19,在 src-tauri 留下 195 个薄包装,done,P2-18,cargo test --workspace 670 通过;234 个命令名/rename_all/JSON key 与阶段起点 8c90a424 逐字一致,177 个包装由脚本生成不是手写。tauri::State> → &Arc,体内 x.inner() → x。State 不是 JSON key,所以 settings_save_remote 换成 State 不影响前端 +P2-20,建 crates/agent-backend 骨架,done,P2-09,cargo run -p agent-backend -- --password X 起进程,GET /healthz 返回 ok,已实测。/healthz 放在认证之外:探活不该要密码 +P2-21,实现密码认证中间件,done,P2-20,实测无 token 401、错密码 401、正确密码 200,subtle 常量时间比较;缺 header/格式错/密码错三种情况返回同样的 401 不给区分信息 +P2-22,实现 TLS 支持,done,P2-20,from_args 四种组合有单测;只给 cert 或只给 key 直接报错,axum-server 0.7 的 RustlsConfig::from_pem_file 已查证(此前文档标记为未验证) +P2-23,打通 3 条代表性路由(unary/stream/upload),done,P2-20;P2-21,curl 实测 git_status/fs_read_text/automation_snapshot/settings_load_all 全部返回预期,错误按原样序列化不 stringify:fs_* 的 FsCommandError 有 code/path/didYouMean,前端在读,转字符串等于扔掉 +P2-24,定 tauri::State 那 9 个命令的会话隔离方案,todo,P2-23,方案写入 backend-split.md:header 名、生命周期、校验方式;且 2 个前端并发调用互不干扰,"git_clone_repository_{start,tasks,cancel,dismiss}、shell_run、runtime_cancel、hook_run_{script,http_requests}、hook_cancel_scope" +P2-25,"补齐返回 Result<(),String> 命令的状态码语义",todo,P2-23,约 9 个命令能区分成功/不存在/无权限,workspace_watch_set、hook_cancel_scope、多个 tunnel_* +P2-26,实现 WS 事件流 sink,done,P2-20;P2-01,WsEventSink 单测覆盖无订阅者不 panic、队列满不阻塞、订阅者能收到事件,/api/events。容量 256,满了丢帧。按决策 19 不补发 +P2-27,重写 SSRF 防护,done,P2-20,59 个单测覆盖各网段正反例;2002:7f00:0001::、::ffff:127.0.0.1、64:ff9b::7f00:1 全部被拒,并行 agent 写的两个测试互相矛盾(239.255.255.255 在 224/4 内,该拦),实现对断言错。另修:解析出 0 个地址原本返回 Ok(vec![]),调用方拿空列表钉不住连接 +P2-28,补完 195 条路由,todo,P2-23;P2-24;P2-25,每条 backend.txt 里的 command 都有对应路由,当前 4/176。生成方式已验证可行:从 tauri_commands/*.rs 的包装签名反推参数结构体,rename_all 逐命令沿用 +P2-29,写 API 契约测试,todo,P2-28,不经前端直接打 JSON API,覆盖 195 个 command;新增 command 未加路由时测试失败,决策 18:只加契约测试不做 e2e +P2-30,重写 tunnel 为本机代理,todo,P2-20,前端请求后端隧道路由,能访问到 localhost:5173 的 dev server,按 P1-07:去掉 slug 注册协议、跨中继分帧、公开无认证入口。名字改 tunnel_*。是否仍需路径前缀重写取决于路由是否挂子路径 +P2-31,阶段验收:不经前端打通全部能力,todo,P2-29,curl/websocat 验证 fs/git/terminal/history/settings 全部可用,且桌面端行为零变化, diff --git a/docs/architecture/migration/phase-2-backend.md b/docs/architecture/migration/phase-2-backend.md index a83fe005c..8c2917b05 100644 --- a/docs/architecture/migration/phase-2-backend.md +++ b/docs/architecture/migration/phase-2-backend.md @@ -1,7 +1,8 @@ # 阶段 2 · Rust 后端网络化 -**状态:🟡 进行中(约 25%)** -提交:`3cf93685` `231faefb` `a2e22a93` `1336a48f` +**状态:🟡 进行中(约 80%)** +提交:`3cf93685` `231faefb` `a2e22a93` `1336a48f` `1363c2c1` `8a5afca0` `b627ca2e` +`ed7fc88a` `58a73280` `5212db5b` ## 目标 @@ -81,33 +82,50 @@ sink 多为「重读当前状态再发布」,避免给 11 个 payload 类型逐 | 指标 | 起点 | 现在 | 目标 | |---|---|---|---| -| 后端代码里 `GatewayController` 引用 | 65 | **40** | 0 | -| 后端代码里结构性 tauri 触点(`AppHandle`/`Emitter`/`Manager`/`Window`) | 13 | **9** | 0 | -| 已迁入 agent-core 的行数 | 0 | **152** | 76,122 | -| HTTP 路由 | 0 | 0 | 195 | +| 后端代码里 `GatewayController` 引用 | 65 | **0** | 0 | +| 后端代码里结构性 tauri 触点 | 13 | **0** | 0 | +| 已迁入 agent-core 的行数 | 0 | **69,440** | 76,122 | +| HTTP 路由 | 0 | **4** | 176 | -剩余 40 处 `GatewayController` 全部集中在「要删的」和「要重写的」: +`agent-core` 依赖树零 tauri,编译期防线生效。`src-tauri` 从 92k 行降到 22,480 行。 + +行数比原估的 76,122 少,因为 `services/tunnel/`(需重写)和 `services/proxy.rs` +(阶段 2 末由 agent-backend 取代)没搬——搬 proxy.rs 会把 axum 拖进 agent-core。 + +## ⚠️ 推翻:P2-16/17/18 的顺序假设是错的 + +原计划「runtime → services → commands,runtime 依赖最少先做」。**runtime 不是叶子**: ``` -commands/integration/gateway.rs 27 (20 个命令阶段 4 直接删) -services/tunnel/* 11 (阶段 1 已定:需重写) -commands/config/settings/* 2 (已记录的部分删除项) +runtime/terminal/{state,ssh_connect,ssh_session,ssh_auth}.rs → crate::commands::settings +runtime/{task_runner,shell_runner}.rs → crate::services::system_proxy +runtime/managed_process_journal.rs → crate::services::automation::db ``` -剩余 9 处结构性 tauri 触点:`services/tunnel/store.rs` 3 处(待重写)+ -`commands/app/{app,tray,update}` 6 处(**前端专属,本就该留在壳里**)。 +三者互相引用,不存在无环的拆分顺序。分步搬只能靠临时垫片,下一步再删掉。 -**即:所有应当迁入 agent-core 的后端模块,现已全部 gateway-free 且无结构性 tauri 耦合。** +**一次搬完反而更简单**:所有 `crate::runtime::` / `crate::services::` / `crate::commands::` +内部路径在 agent-core 里原样有效,133 个文件零路径改写。代价只有 18 个编译错误。 -## 剩余机械工作量 +真正的成本在壳侧:**61 个** `pub(crate)` 需要提升为 `pub`(文档原估 29)。全部由编译器 +`E0603` 点名后提升,没有全局提升——`pub(crate)` 在 crate 内依然有效,只有真正跨界的才该动。 -| 项 | 数量 | 性质 | -|---|---|---| -| `tauri::async_runtime` → tokio | 132 | 机械(前提已核验,见下) | -| `#[tauri::command]` 拆成 impl + wrapper | 178 | 机械但量大 | -| `tauri::State<'_, Arc>` → 显式参数 | 68 | 机械 | -| 代码迁入 agent-core | 76,122 行 | 机械 | -| `pub(crate)` 跨界提升 | 580 处中约 29 个符号路径 | 机械 | +## 剩余工作 + +| 项 | 状态 | +|---|---| +| `tauri::async_runtime` → tokio | ✅ 225 处已换(gateway 除外,阶段 4 删) | +| `#[tauri::command]` 拆成 impl + wrapper | ✅ 177 个,脚本生成 | +| `tauri::State<'_, Arc>` → 显式参数 | ✅ 41 处 → `&Arc`,体内 `x.inner()` → `x` | +| 代码迁入 agent-core | ✅ 69,440 行 | +| `pub(crate)` 跨界提升 | ✅ 61 个符号 | +| **补完 172 条路由(P2-28)** | ⬜ 当前 4/176 | +| **会话隔离(P2-24)/状态码语义(P2-25)** | ⬜ | +| **契约测试(P2-29)/tunnel 重写(P2-30)/验收(P2-31)** | ⬜ | + +包装与实现的拆分是**脚本生成**的,不是手写:177 次同样的机械变换,手写只会引入 +手写才有的错误。生成后用机器验了前端契约——234 个命令名、`rename_all`、JSON key +与阶段起点 `8c90a424` 逐字一致。 ## 已核验的事实(实验/读源码,非推断) @@ -151,14 +169,22 @@ commands/config/settings/* 2 (已记录的部分删除项) | `tauri::State` 注入的 9 个命令怎么做会话隔离 | `git_clone_repository_{start,tasks,cancel,dismiss}`、`shell_run`、`runtime_cancel`、`hook_run_{script,http_requests}`、`hook_cancel_scope` 持有 registry 句柄。需定 header 名、生命周期、校验方式 | | 返回 `Result<(), String>` 的约 9 个命令 | HTTP 无法区分「成功」「不存在」「无权限」,需补状态码语义 | | EventBus 背压 | `emit_json` 同步遍历所有 sink,慢客户端会阻塞业务线程。WS sink 必须自排队 + 丢帧 | -| axum 0.8 TLS 集成 | `axum-server` 的 `RustlsConfig::from_pem_file` 签名**未经验证**,实现前必须查证 | | 3 个 settings 命令未设计路由 | `settings_list_ccswitch_providers`、`settings_list_cherry_studio_providers`、`settings_list_cherry_studio_providers_from_path` | ## 已知风险 **事件总线把编译期错误变成了运行时静默失败。** 这在迁移 history sync 时**真实发生过**: 移除 gateway 发布调用后忘了在 sink 里接上,`cargo check` 全绿但 history 同步已断, -下一步才补上。`GatewayEventSink` 目前**无测试覆盖**(P2-14)。 +下一步才补上。 + +已补防线(P2-14):`GatewayController` 持有 `tauri::AppHandle`,单测造不出来,所以把 +**路由决策**(`action_for`,纯函数)和**执行**(`emit_json`,只剩转发)分开——踩过的那次 +回归本质就是路由错误。断言用 agent-core 导出的事件常量本身而非字面量:后端改名测试 +跟着变,后端新增事件却忘了接就掉进 `Ignore` 被抓住。已用变异验证:删掉 +`HISTORY_UPSERT_EVENT` 分支,测试立即失败。 + +`settings_save_remote` 是最后一处 `GatewayController` 耦合,已按同款模式切断: +发 `settings:remote-saved`,sink 接住去调 `apply_config`。 ## 验证 From ef74ba2bb442cc9a6ac4e9283613f71ae93cff20 Mon Sep 17 00:00:00 2001 From: FlowerRealm Date: Tue, 4 Aug 2026 19:02:06 -0700 Subject: [PATCH 14/75] =?UTF-8?q?feat(backend):=20=E8=A1=A5=E5=AE=8C=20175?= =?UTF-8?q?=20=E6=9D=A1=E8=B7=AF=E7=94=B1=E3=80=81=E5=A5=91=E7=BA=A6?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E3=80=81=E9=9A=A7=E9=81=93=E9=87=8D=E5=86=99?= =?UTF-8?q?,=E9=98=B6=E6=AE=B5=202=20=E6=94=B6=E5=B0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/generate-routes.mjs 从 tauri_commands 包装层生成 routes_gen.rs, ROUTED_COMMANDS 与注册同源;Makefile 加 check-routes 门禁 - 契约测试(route_contract.rs):175 路由可达性、清单一致性、认证语义 - 隧道重写(P2-30):旧 src-tauri/services/tunnel 2,126 行删除,状态层迁入 agent-core/services/tunnel,数据面落 agent-backend/src/tunnel.rs, 一隧道一端口;前端 constants/client/panel 随协议对齐,e2e 测试覆盖 - WS 事件接线:registry set_event_bus、automation 启动、managed-process monitor - 文档:阶段 2 标记完成,记录路由生成与隧道重写决策 --- Cargo.lock | 2 + Makefile | 11 + crates/agent-backend/Cargo.toml | 1 + crates/agent-backend/src/lib.rs | 43 +- crates/agent-backend/src/main.rs | 6 + crates/agent-backend/src/routes.rs | 96 +- crates/agent-backend/src/routes_gen.rs | 4432 +++++++++++++++++ crates/agent-backend/src/state.rs | 4 + crates/agent-backend/src/tunnel.rs | 816 +++ crates/agent-backend/tests/route_contract.rs | 241 + crates/agent-backend/tests/tunnel_e2e.rs | 333 ++ .../src/commands/config/settings/db.rs | 5 +- .../src/commands/history/history_db.rs | 7 +- .../src/commands/integration/mod.rs | 1 + .../src/commands/integration/tunnel.rs | 73 + crates/agent-core/src/commands/mod.rs | 1 + crates/agent-core/src/lib.rs | 1 + .../agent-core/src/runtime/managed_process.rs | 6 +- crates/agent-core/src/services/mod.rs | 1 + .../agent-core/src/services/skills/paths.rs | 7 +- crates/agent-core/src/services/tunnel/mod.rs | 287 ++ .../agent-core/src/services/tunnel/probe.rs | 94 + .../agent-core/src/services/tunnel/store.rs | 812 +++ crates/agent-core/src/storage.rs | 22 + .../project-tools/LocalTunnelPanel.tsx | 14 +- crates/agent-gateway/web/src/i18n/config.ts | 2 - .../web/src/lib/gatewaySocket.ts | 4 + .../web/src/lib/tunnels/constants.ts | 36 +- crates/agent-gui/src-tauri/Cargo.toml | 3 + .../src-tauri/src/commands/app/system.rs | 7 +- .../src/commands/integration/gateway.rs | 42 - crates/agent-gui/src-tauri/src/lib.rs | 52 +- .../src/services/gateway/chat_ingress.rs | 6 +- .../src/services/gateway/connection.rs | 10 - .../src/services/gateway/controller.rs | 6 - .../src/services/gateway/envelope_handler.rs | 16 +- .../src-tauri/src/services/gateway/mod.rs | 4 - .../agent-gui/src-tauri/src/services/mod.rs | 1 - .../src-tauri/src/services/tunnel/mod.rs | 591 --- .../src-tauri/src/services/tunnel/proxy.rs | 740 --- .../src-tauri/src/services/tunnel/store.rs | 795 --- .../src-tauri/src/tauri_commands/mod.rs | 1 + .../src-tauri/src/tauri_commands/tunnel.rs | 57 + .../project-tools/LocalTunnelPanel.tsx | 14 +- crates/agent-gui/src/i18n/config.ts | 2 - .../src/lib/tools/tunnelManagerTools.ts | 53 +- crates/agent-gui/src/lib/tunnels/constants.ts | 36 +- .../src/lib/tunnels/tauriTunnelClient.ts | 13 +- .../test/tools/tunnel-manager-tools.test.mjs | 44 +- docs/architecture/backend-boundary.md | 35 +- docs/architecture/backend-split.md | 37 +- docs/architecture/command-classes/backend.txt | 10 +- docs/architecture/migration/README.md | 10 +- .../migration/phase-2-backend.csv | 14 +- .../architecture/migration/phase-2-backend.md | 215 +- scripts/generate-routes.mjs | 300 ++ 56 files changed, 7986 insertions(+), 2486 deletions(-) create mode 100644 crates/agent-backend/src/routes_gen.rs create mode 100644 crates/agent-backend/src/tunnel.rs create mode 100644 crates/agent-backend/tests/route_contract.rs create mode 100644 crates/agent-backend/tests/tunnel_e2e.rs create mode 100644 crates/agent-core/src/commands/integration/tunnel.rs create mode 100644 crates/agent-core/src/services/tunnel/mod.rs create mode 100644 crates/agent-core/src/services/tunnel/probe.rs create mode 100644 crates/agent-core/src/services/tunnel/store.rs create mode 100644 crates/agent-core/src/storage.rs delete mode 100644 crates/agent-gui/src-tauri/src/services/tunnel/mod.rs delete mode 100644 crates/agent-gui/src-tauri/src/services/tunnel/proxy.rs delete mode 100644 crates/agent-gui/src-tauri/src/services/tunnel/store.rs create mode 100644 crates/agent-gui/src-tauri/src/tauri_commands/tunnel.rs create mode 100644 scripts/generate-routes.mjs diff --git a/Cargo.lock b/Cargo.lock index 1a4a8de45..ed55b77ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,7 @@ dependencies = [ "serde_json", "subtle", "tokio", + "tokio-tungstenite 0.29.0", "tower", "tower-http", "uuid", @@ -3421,6 +3422,7 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" name = "liveagent" version = "1.3.0-dev.0" dependencies = [ + "agent-backend", "agent-core", "arboard", "axum", diff --git a/Makefile b/Makefile index 3b5786dc0..a1083eef1 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ MODEL_CATALOG_GENERATED_FILES := $(AGENT_GUI_DIR)/src/lib/models/catalog.generat .PHONY: dev-gateway dev-webui ensure-webui-embed-stub .PHONY: proto proto-check webui gateway-build gateway-docker-build gateway-docker-run gateway-docker-smoke build-linux build-linux-amd build-linux-arm .PHONY: clean update-model-catalog check-rust-target-% check-macos-signing-identity check-macos-notary-profile desktop-store-macos-notary-profile desktop-wait-macos-notary desktop-staple-macos desktop-verify-macos +.PHONY: update-routes check-routes all: build gateway-build @@ -202,6 +203,14 @@ clean: update-model-catalog: node scripts/generate-model-catalog.mjs +# 从 src-tauri/src/tauri_commands/*.rs 重新生成 agent-backend 的路由层(routes_gen.rs)。 +update-routes: + node scripts/generate-routes.mjs + +# 校验 routes_gen.rs 与 wrapper 层一致(CI 用);漂移即失败。 +check-routes: + node scripts/generate-routes.mjs --check + check-rust-target-%: @rustup target list --installed | grep -qx "$*" || (echo "Rust target $* is not installed. Run: rustup target add $*" && exit 1) @@ -273,4 +282,6 @@ help: @printf " %-34s %s\n" "make all" "同时构建 GUI 和 agent-gateway" @printf " %-34s %s\n" "make clean" "清理 agent-gateway 构建产物" @printf " %-34s %s\n" "make update-model-catalog" "刷新 models.dev 模型目录快照" + @printf " %-34s %s\n" "make update-routes" "从 tauri_commands 重新生成 agent-backend 路由层" + @printf " %-34s %s\n" "make check-routes" "校验路由层与 wrapper 一致(CI 门禁)" @printf " %-34s %s\n" "make help" "查看可用命令" diff --git a/crates/agent-backend/Cargo.toml b/crates/agent-backend/Cargo.toml index 86a0046c2..3c0e6f480 100644 --- a/crates/agent-backend/Cargo.toml +++ b/crates/agent-backend/Cargo.toml @@ -22,6 +22,7 @@ subtle = "2.6.1" uuid = { version = "1.23.4", features = ["v4"] } rand = "0.9.2" reqwest = { version = "0.13.4", features = ["json", "stream"] } +tokio-tungstenite = { version = "0.29.0", features = ["rustls-tls-webpki-roots"] } [dev-dependencies] tower = { version = "0.5.2", features = ["util"] } diff --git a/crates/agent-backend/src/lib.rs b/crates/agent-backend/src/lib.rs index ff44492b9..35b6f3841 100644 --- a/crates/agent-backend/src/lib.rs +++ b/crates/agent-backend/src/lib.rs @@ -21,9 +21,11 @@ pub mod auth; pub mod routes; +pub mod routes_gen; pub mod ssrf; pub mod state; pub mod tls; +pub mod tunnel; pub mod ws; use std::sync::Arc; @@ -52,13 +54,28 @@ pub fn build_router(state: AppState) -> Router { .with_state(state) } -/// 构造一个只挂了 registry、没接任何事件消费者的后端状态。 +/// 构造后端状态:装好全部 registry,并把事件总线接到 WS sink 和各发事件方。 /// -/// 事件 sink 由调用方注册:本地模式下桌面壳注册自己的,纯后端模式下注册 WS sink。 +/// 对照桌面壳 `.setup()` 的接线(agent-gui lib.rs:765-784): +/// - WS sink 注册进 EventBus(纯后端模式下 WS 客户端才能收到事件) +/// - managed_process / terminal / sftp 三个 registry 调 set_event_bus(否则 +/// 这些子系统的事件根本到不了总线,P2-31 验收时发现的实际缺口) +/// - automation notifier + scheduler.start()(定时任务才跑得起来) +/// - managed process 的 startup_reconcile / monitor(进程快照自愈) +/// +/// 隧道**不在**这里恢复:`TunnelStore::initialize` 要读库还要起监听,是 async +/// 且会失败的。放进 `serve` 前由调用方显式 await,见 `main.rs`。 pub fn build_state(auth: Arc) -> Result { use agent_core::events::EventBus; + use agent_core::services::automation::AutomationNotifier; + use agent_core::services::tunnel::TunnelStore; let events = Arc::new(EventBus::new()); + // 建表。桌面壳在 setup 第一行做这件事(agent-gui lib.rs:730);纯后端模式下 + // 漏掉它,chat_history_* 全部报 "no such table: chatHistory"——路由可达、 + // 契约测试全绿,但功能是坏的。P2-31 用 curl 实测时抓到的。 + agent_core::commands::history_db::initialize_history_db() + .map_err(|e| format!("初始化 history 库失败:{e}"))?; let automation_store = Arc::new( agent_core::services::automation::AutomationStore::open() .map_err(|e| format!("打开 automation 存储失败:{e}"))?, @@ -81,6 +98,25 @@ pub fn build_state(auth: Arc) -> Result { let workspace_watch = Arc::new( agent_core::services::workspace_watch::WorkspaceWatchService::new(Arc::clone(&events)), ); + let tunnels = Arc::new(TunnelStore::new( + Arc::clone(&events), + Arc::new(crate::tunnel::TunnelDataPlane::new()), + )); + + // 事件接线(顺序与桌面壳 setup 一致)。 + events.register(crate::ws::get_or_init_ws_sink()); + terminals.set_event_bus(Arc::clone(&events)); + sftp.set_event_bus(Arc::clone(&events)); + let managed_processes: Arc = + Arc::new(Default::default()); + managed_processes.set_event_bus(Arc::clone(&events)); + managed_processes.spawn_startup_reconcile(); + managed_processes.spawn_monitor(); + automation_store.set_notifier(AutomationNotifier { + events: Arc::clone(&events), + scheduler: Arc::downgrade(&automation_scheduler), + }); + Arc::clone(&automation_scheduler).start(); Ok(AppState { events, @@ -89,7 +125,7 @@ pub fn build_state(auth: Arc) -> Result { memory_store, provider_usage: Arc::new(Default::default()), power_activity: Arc::new(Default::default()), - managed_processes: Arc::new(Default::default()), + managed_processes, terminals, sftp, shell_runs: Arc::new(Default::default()), @@ -97,6 +133,7 @@ pub fn build_state(auth: Arc) -> Result { hook_scopes: Arc::new(Default::default()), mcp: Arc::new(Default::default()), workspace_watch, + tunnels, auth, }) } diff --git a/crates/agent-backend/src/main.rs b/crates/agent-backend/src/main.rs index 14694051d..569c912c4 100644 --- a/crates/agent-backend/src/main.rs +++ b/crates/agent-backend/src/main.rs @@ -67,6 +67,12 @@ async fn main() -> Result<(), Box> { }; let state = build_state(Arc::new(auth::AuthConfig::new(password)))?; + // 恢复上次留下的隧道:读库、给存活的重新起监听。端口和 token 是本次进程 + // 新分配的,所以链接会变——这是有意的(见 services/tunnel/store.rs 文档)。 + // 失败只记日志不中止:隧道起不来不该让整个后端服务不起来。 + if let Err(error) = state.tunnels.initialize().await { + eprintln!("恢复隧道失败:{error}"); + } let app = build_router(state); let addr = SocketAddr::from(([0, 0, 0, 0], args.port)); diff --git a/crates/agent-backend/src/routes.rs b/crates/agent-backend/src/routes.rs index 55d971638..a69cb9cd7 100644 --- a/crates/agent-backend/src/routes.rs +++ b/crates/agent-backend/src/routes.rs @@ -14,27 +14,27 @@ //! 参数结构体逐命令生成,`serde` 的 `rename_all` **必须与该命令的 //! `#[tauri::command(rename_all = ...)]` 一致**:写了 `snake_case` 的用 snake_case, //! 没写的用 camelCase(Tauri 的默认)。这是前端契约,不能统一。 +//! +//! 路由本身由 `scripts/generate-routes.mjs` 从 `tauri_commands/*.rs` 生成 +//! (`routes_gen.rs`),本文件只保留公共的响应组装与对外入口。 -use axum::extract::State; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use axum::routing::post; use axum::{Json, Router}; -use serde::Deserialize; use serde_json::json; +use crate::routes_gen; use crate::state::AppState; -/// 把命令的 `Result` 转成 HTTP 响应。 +/// 把命令的 `Result` 转成 HTTP 响应。 /// -/// 业务失败一律 400 而不是 500:命令返回 `Err(String)` 表示「这次请求做不到」 +/// 业务失败一律 400 而不是 500:命令返回 `Err` 表示「这次请求做不到」 /// (路径不存在、参数不合法、仓库不是 git 仓库),不是服务端崩了。 /// 真正的 500 留给 panic 和序列化失败。 /// -/// ⚠️ 已知不足(P2-25):约 9 个返回 `Result<(), String>` 的命令无法区分 -/// 「成功」「不存在」「无权限」,全都落在 200/400 两档里。补细状态码需要 -/// 逐个命令看错误来源,不是这里能糊的。 -fn respond(result: Result) -> Response { +/// 状态码刻意保持 200/400 两档(P2-25):与 Tauri IPC 语义一致(Err 就是失败), +/// 错误体已带字符串,前端在读。补细状态码(404/403)需要逐个命令审计错误来源。 +pub(crate) fn respond(result: Result) -> Response { match result { Ok(value) => match serde_json::to_value(value) { Ok(value) => (StatusCode::OK, Json(json!({ "ok": value }))).into_response(), @@ -58,82 +58,12 @@ fn respond(result: Result) -> Re } } -// --------------------------------------------------------------------------- -// 代表性路由(P2-23):先验证约定,再批量推剩下的。 -// --------------------------------------------------------------------------- - -#[derive(Deserialize)] -#[serde(rename_all = "snake_case")] -struct GitStatusArgs { - workdir: String, -} - -async fn git_status(Json(args): Json) -> Response { - respond(agent_core::commands::git::git_status(args.workdir).await) -} - -#[derive(Deserialize)] -#[serde(rename_all = "snake_case")] -struct FsReadTextArgs { - workdir: String, - path: String, - start_line: Option, - limit: Option, - page_start: Option, - page_limit: Option, - cell_start: Option, - cell_limit: Option, -} - -async fn fs_read_text(Json(args): Json) -> Response { - respond( - agent_core::commands::fs::fs_read_text( - args.workdir, - args.path, - args.start_line, - args.limit, - args.page_start, - args.page_limit, - args.cell_start, - args.cell_limit, - ) - .await, - ) -} - -/// `settings_load_all` 无参数。仍然走 POST 且允许空 body—— -/// 让 195 条路由是同一个形状,客户端不必记「哪些是 GET」。 -async fn settings_load_all() -> Response { - respond(agent_core::commands::settings::settings_load_all().await) -} - -/// 带 state 的代表:97/177 条命令需要从 `AppState` 取 registry 句柄。 -#[derive(Deserialize)] -#[serde(rename_all = "snake_case")] -struct AutomationSnapshotArgs {} - -async fn automation_snapshot( - State(state): State, - _args: Option>, -) -> Response { - respond(agent_core::commands::cron::automation_snapshot(&state.automation_store).await) -} - pub fn api_router() -> Router { - Router::new() - .route("/git_status", post(git_status)) - .route("/fs_read_text", post(fs_read_text)) - .route("/settings_load_all", post(settings_load_all)) - .route("/automation_snapshot", post(automation_snapshot)) + routes_gen::gen_router() } /// 已挂路由的命令名。契约测试(P2-29)拿它和 agent-core 导出的命令清单比对, -/// 「新增 command 未加路由」必须导致测试失败。 -pub fn routed_commands() -> Vec<&'static str> { - vec![ - "git_status", - "fs_read_text", - "settings_load_all", - "automation_snapshot", - ] +/// 「新增 command 未加路由」必须导致测试失败。与生成器保持单一来源。 +pub fn routed_commands() -> &'static [&'static str] { + routes_gen::ROUTED_COMMANDS } diff --git a/crates/agent-backend/src/routes_gen.rs b/crates/agent-backend/src/routes_gen.rs new file mode 100644 index 000000000..2fe1b042d --- /dev/null +++ b/crates/agent-backend/src/routes_gen.rs @@ -0,0 +1,4432 @@ +// Generated by scripts/generate-routes.mjs — DO NOT EDIT. +// +// 每个命令一条 handler,POST /api/,body 的 JSON key 与 +// #[tauri::command] 参数名逐字一致。rename_all 逐命令镜像 tauri 属性。 +#![allow(unused_imports, clippy::too_many_arguments)] +use axum::extract::State; +use axum::response::Response; +use axum::routing::post; +use axum::{Json, Router}; +use serde::Deserialize; +use serde_json::Value; +use std::collections::HashMap; +use crate::routes::respond; +use crate::state::AppState; + +mod automation_claim_prompt_runs { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::cron::automation_claim_prompt_runs(&state.automation_store).await) + } +} + +mod automation_clear_runs { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct AutomationClearRunsRouteArgs { + task_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::automation_clear_runs(args.task_id, &state.automation_store).await) + } +} + +mod automation_complete_prompt_run { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct AutomationCompletePromptRunRouteArgs { + input: CompletePromptRunInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::automation_complete_prompt_run(args.input, &state.automation_store).await) + } +} + +mod automation_cron_apply { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct AutomationCronApplyRouteArgs { + input: AutomationApplyInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::automation_cron_apply(args.input, &state.automation_store).await) + } +} + +mod automation_hooks_apply { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct AutomationHooksApplyRouteArgs { + input: AutomationApplyInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::automation_hooks_apply(args.input, &state.automation_store).await) + } +} + +mod automation_list_runs { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct AutomationListRunsRouteArgs { + task_id: String, + limit: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::automation_list_runs(args.task_id, args.limit, &state.automation_store).await) + } +} + +mod automation_release_prompt_run { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct AutomationReleasePromptRunRouteArgs { + execution_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::automation_release_prompt_run(args.execution_id, &state.automation_store).await) + } +} + +mod automation_run_cron_now { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct AutomationRunCronNowRouteArgs { + task_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::automation_run_cron_now(args.task_id, &state.automation_store).await) + } +} + +mod automation_snapshot { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::cron::automation_snapshot(&state.automation_store).await) + } +} + +mod chat_history_append_segment { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryAppendSegmentRouteArgs { + input: ChatHistorySegmentMutationInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_append_segment(args.input, &state.events).await) + } +} + +mod chat_history_branch { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryBranchRouteArgs { + id: String, + base_message_ref: ChatHistoryMessageRef, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_branch(args.id, args.base_message_ref, &state.events).await) + } +} + +mod chat_history_delete { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryDeleteRouteArgs { + id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_delete(args.id, &state.events).await) + } +} + +mod chat_history_get_window { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryGetWindowRouteArgs { + id: String, + max_messages: i64, + before_offset: Option, + expected_revision: Option, + include_active_segment: bool, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_get_window(args.id, args.max_messages, args.before_offset, args.expected_revision, args.include_active_segment).await) + } +} + +mod chat_history_list { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryListRouteArgs { + page: i64, + page_size: i64, + cwd: Option, + cwd_empty: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_list(args.page, args.page_size, args.cwd, args.cwd_empty).await) + } +} + +mod chat_history_rename { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryRenameRouteArgs { + id: String, + title: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_rename(args.id, args.title, &state.events).await) + } +} + +mod chat_history_replace_from_message { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryReplaceFromMessageRouteArgs { + id: String, + base_message_ref: ChatHistoryMessageRef, + replacement_message: Value, + max_messages: i64, + expected_revision: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_replace_from_message(args.id, args.base_message_ref, args.replacement_message, args.max_messages, args.expected_revision, &state.events).await) + } +} + +mod chat_history_search { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistorySearchRouteArgs { + args: ChatHistorySearchArgs, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_search(args.args).await) + } +} + +mod chat_history_set_model { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistorySetModelRouteArgs { + id: String, + selected_model_json: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_set_model(args.id, args.selected_model_json, &state.events).await) + } +} + +mod chat_history_set_pinned { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistorySetPinnedRouteArgs { + id: String, + is_pinned: bool, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_set_pinned(args.id, args.is_pinned, &state.events).await) + } +} + +mod chat_history_share_get { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryShareGetRouteArgs { + id: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_share_get(args.id).await) + } +} + +mod chat_history_share_set { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryShareSetRouteArgs { + id: String, + enabled: bool, + redact_tool_content: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_share_set(args.id, args.enabled, args.redact_tool_content, &state.events).await) + } +} + +mod chat_history_shared_list { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistorySharedListRouteArgs { + page: i64, + page_size: i64, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_shared_list(args.page, args.page_size).await) + } +} + +mod chat_history_upsert { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryUpsertRouteArgs { + input: ChatHistoryUpsertInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_upsert(args.input, &state.events).await) + } +} + +mod chat_history_upsert_active_segment { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct ChatHistoryUpsertActiveSegmentRouteArgs { + input: ChatHistorySegmentMutationInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::chat_history::chat_history_upsert_active_segment(args.input, &state.events).await) + } +} + +mod chat_history_workdirs { + use super::*; + use agent_core::commands::chat_history::*; + use agent_core::commands::{history_db, subagent_store}; + use agent_core::events::EventBus; + use agent_core::services::memory::{MemoryHistorySearchMatch, MemorySearchArgs}; + + pub async fn handle() -> Response { + respond(agent_core::commands::chat_history::chat_history_workdirs().await) + } +} + +mod cron_validate_expression { + use super::*; + use agent_core::commands::cron::*; + use agent_core::services::automation::{ + validate_cron_expression, AutomationApplyInput, AutomationSnapshot, AutomationStore, + CompletePromptRunInput, CronApplyResponse, CronRunNowResponse, CronRunRecord, + HooksApplyResponse, PromptCompletionResponse, PromptRunRequest, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct CronValidateExpressionRouteArgs { + expression: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::cron::cron_validate_expression(args.expression).await) + } +} + +mod fs_create_dir { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsCreateDirRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_create_dir(args.workdir, args.path).await) + } +} + +mod fs_delete { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsDeleteRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_delete(args.workdir, args.path).await) + } +} + +mod fs_edit_text { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsEditTextRouteArgs { + workdir: String, + path: String, + old_string: String, + new_string: String, + expected_replacements: Option, + replace_all: Option, + expected_mtime_ms: Option, + expected_content_hash: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_edit_text(args.workdir, args.path, args.old_string, args.new_string, args.expected_replacements, args.replace_all, args.expected_mtime_ms, args.expected_content_hash).await) + } +} + +mod fs_glob { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsGlobRouteArgs { + workdir: String, + path: Option, + pattern: String, + offset: Option, + max_results: Option, + sort_by: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_glob(args.workdir, args.path, args.pattern, args.offset, args.max_results, args.sort_by).await) + } +} + +mod fs_grep { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsGrepRouteArgs { + workdir: String, + path: Option, + pattern: String, + file_pattern: Option, + ignore_case: Option, + output_mode: Option, + head_limit: Option, + offset: Option, + context: Option, + multiline: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_grep(args.workdir, args.path, args.pattern, args.file_pattern, args.ignore_case, args.output_mode, args.head_limit, args.offset, args.context, args.multiline).await) + } +} + +mod fs_list { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsListRouteArgs { + workdir: String, + path: Option, + depth: Option, + offset: Option, + max_results: Option, + show_hidden: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_list(args.workdir, args.path, args.depth, args.offset, args.max_results, args.show_hidden).await) + } +} + +mod fs_list_dirs { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsListDirsRouteArgs { + path: String, + max_results: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_list_dirs(args.path, args.max_results).await) + } +} + +mod fs_mention_list { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsMentionListRouteArgs { + workdir: String, + max_results: Option, + query: Option, + show_hidden: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_mention_list(args.workdir, args.max_results, args.query, args.show_hidden).await) + } +} + +mod fs_path_status { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsPathStatusRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_path_status(args.workdir, args.path).await) + } +} + +mod fs_read_editable_text { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsReadEditableTextRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_read_editable_text(args.workdir, args.path).await) + } +} + +mod fs_read_image_source { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct FsReadImageSourceRouteArgs { + workdir: String, + source: String, + source_type: Option, + mime_type: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_read_image_source(args.workdir, args.source, args.source_type, args.mime_type).await) + } +} + +mod fs_read_text { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsReadTextRouteArgs { + workdir: String, + path: String, + start_line: Option, + limit: Option, + page_start: Option, + page_limit: Option, + cell_start: Option, + cell_limit: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_read_text(args.workdir, args.path, args.start_line, args.limit, args.page_start, args.page_limit, args.cell_start, args.cell_limit).await) + } +} + +mod fs_read_workspace_image { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsReadWorkspaceImageRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_read_workspace_image(args.workdir, args.path).await) + } +} + +mod fs_rename { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsRenameRouteArgs { + workdir: String, + from_path: String, + to_path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_rename(args.workdir, args.from_path, args.to_path).await) + } +} + +mod fs_roots { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + pub async fn handle() -> Response { + respond(agent_core::commands::fs::fs_roots().await) + } +} + +mod fs_write_text { + use super::*; + use agent_core::commands::fs::*; + use agent_core::runtime::platform::expand_tilde_path; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct FsWriteTextRouteArgs { + workdir: String, + path: String, + content: String, + mode: String, + expected_mtime_ms: Option, + expected_content_hash: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::fs::fs_write_text(args.workdir, args.path, args.content, args.mode, args.expected_mtime_ms, args.expected_content_hash).await) + } +} + +mod git_add_to_gitignore { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitAddToGitignoreRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_add_to_gitignore(args.workdir, args.path).await) + } +} + +mod git_branches { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitBranchesRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_branches(args.workdir).await) + } +} + +mod git_clone_repository { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCloneRepositoryRouteArgs { + parent: String, + name: String, + remote_url: String, + branch: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_clone_repository(args.parent, args.name, args.remote_url, args.branch).await) + } +} + +mod git_clone_repository_cancel { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCloneRepositoryCancelRouteArgs { + task_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_clone_repository_cancel(&state.git_clone_tasks, args.task_id)) + } +} + +mod git_clone_repository_dismiss { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCloneRepositoryDismissRouteArgs { + task_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_clone_repository_dismiss(&state.git_clone_tasks, args.task_id)) + } +} + +mod git_clone_repository_start { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCloneRepositoryStartRouteArgs { + parent: String, + name: String, + remote_url: String, + branch: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_clone_repository_start(&state.git_clone_tasks, args.parent, args.name, args.remote_url, args.branch)) + } +} + +mod git_clone_repository_tasks { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::git::git_clone_repository_tasks(&state.git_clone_tasks)) + } +} + +mod git_commit { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCommitRouteArgs { + workdir: String, + message: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_commit(args.workdir, args.message).await) + } +} + +mod git_commit_details { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCommitDetailsRouteArgs { + workdir: String, + commit: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_commit_details(args.workdir, args.commit).await) + } +} + +mod git_commit_diff { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCommitDiffRouteArgs { + workdir: String, + commit: String, + path: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_commit_diff(args.workdir, args.commit, args.path).await) + } +} + +mod git_compare_commit_with_remote { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCompareCommitWithRemoteRouteArgs { + workdir: String, + commit: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_compare_commit_with_remote(args.workdir, args.commit).await) + } +} + +mod git_create_branch { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitCreateBranchRouteArgs { + workdir: String, + branch: String, + start_point: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_create_branch(args.workdir, args.branch, args.start_point).await) + } +} + +mod git_delete_branch { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitDeleteBranchRouteArgs { + workdir: String, + branch: String, + force: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_delete_branch(args.workdir, args.branch, args.force).await) + } +} + +mod git_diff { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitDiffRouteArgs { + workdir: String, + mode: Option, + path: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_diff(args.workdir, args.mode, args.path).await) + } +} + +mod git_discard { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitDiscardRouteArgs { + workdir: String, + path: String, + old_path: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_discard(args.workdir, args.path, args.old_path).await) + } +} + +mod git_discard_all { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitDiscardAllRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_discard_all(args.workdir).await) + } +} + +mod git_discover_repositories { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitDiscoverRepositoriesRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_discover_repositories(args.workdir).await) + } +} + +mod git_fetch { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitFetchRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_fetch(args.workdir).await) + } +} + +mod git_init { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitInitRouteArgs { + workdir: String, + branch: Option, + user_name: Option, + user_email: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_init(args.workdir, args.branch, args.user_name, args.user_email).await) + } +} + +mod git_list_remote_branches { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitListRemoteBranchesRouteArgs { + remote_url: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_list_remote_branches(args.remote_url).await) + } +} + +mod git_log { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitLogRouteArgs { + workdir: String, + limit: Option, + skip: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_log(args.workdir, args.limit, args.skip).await) + } +} + +mod git_pull { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitPullRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_pull(args.workdir).await) + } +} + +mod git_push { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitPushRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_push(args.workdir).await) + } +} + +mod git_rename_branch { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitRenameBranchRouteArgs { + workdir: String, + branch: String, + new_branch: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_rename_branch(args.workdir, args.branch, args.new_branch).await) + } +} + +mod git_set_remote { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitSetRemoteRouteArgs { + workdir: String, + remote_url: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_set_remote(args.workdir, args.remote_url).await) + } +} + +mod git_stage { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitStageRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_stage(args.workdir, args.path).await) + } +} + +mod git_stage_all { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitStageAllRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_stage_all(args.workdir).await) + } +} + +mod git_stash_pop { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitStashPopRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_stash_pop(args.workdir).await) + } +} + +mod git_stash_push { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitStashPushRouteArgs { + workdir: String, + message: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_stash_push(args.workdir, args.message).await) + } +} + +mod git_status { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitStatusRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_status(args.workdir).await) + } +} + +mod git_switch_branch { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitSwitchBranchRouteArgs { + workdir: String, + branch: String, + kind: Option, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_switch_branch(args.workdir, args.branch, args.kind).await) + } +} + +mod git_unstage { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitUnstageRouteArgs { + workdir: String, + path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_unstage(args.workdir, args.path).await) + } +} + +mod git_unstage_all { + use super::*; + use agent_core::commands::git::*; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, + terminate_process_tree_by_pid, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct GitUnstageAllRouteArgs { + workdir: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::git::git_unstage_all(args.workdir).await) + } +} + +mod hook_cancel_scope { + use super::*; + use agent_core::commands::hook::*; + use agent_core::runtime::shell_runner::{ + run_shell_script_with_envs, ShellCancelFlag, ShellCancelToken, ShellRunResponse, +}; + use agent_core::runtime::task_runner::{ + build_http_client, resolve_workdir, run_single_http_request, HttpRequestInput, +}; + use agent_core::services::automation::validate::{MAX_HOOK_TIMEOUT_MS, MIN_HOOK_TIMEOUT_MS}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct HookCancelScopeRouteArgs { + scope_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::hook::hook_cancel_scope(args.scope_id, &state.hook_scopes).await) + } +} + +mod hook_run_http_requests { + use super::*; + use agent_core::commands::hook::*; + use agent_core::runtime::shell_runner::{ + run_shell_script_with_envs, ShellCancelFlag, ShellCancelToken, ShellRunResponse, +}; + use agent_core::runtime::task_runner::{ + build_http_client, resolve_workdir, run_single_http_request, HttpRequestInput, +}; + use agent_core::services::automation::validate::{MAX_HOOK_TIMEOUT_MS, MIN_HOOK_TIMEOUT_MS}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct HookRunHttpRequestsRouteArgs { + requests: Vec, + scope_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::hook::hook_run_http_requests(args.requests, args.scope_id, &state.hook_scopes).await) + } +} + +mod hook_run_script { + use super::*; + use agent_core::commands::hook::*; + use agent_core::runtime::shell_runner::{ + run_shell_script_with_envs, ShellCancelFlag, ShellCancelToken, ShellRunResponse, +}; + use agent_core::runtime::task_runner::{ + build_http_client, resolve_workdir, run_single_http_request, HttpRequestInput, +}; + use agent_core::services::automation::validate::{MAX_HOOK_TIMEOUT_MS, MIN_HOOK_TIMEOUT_MS}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct HookRunScriptRouteArgs { + workdir: Option, + script: String, + timeout_ms: Option, + scope_id: Option, + context: Option>, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::hook::hook_run_script(args.workdir, args.script, args.timeout_ms, args.scope_id, args.context, &state.hook_scopes).await) + } +} + +mod managed_process_clear { + use super::*; + use agent_core::commands::process::*; + use agent_core::runtime::managed_process::{ + ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, + ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct ManagedProcessClearRouteArgs { + process_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::process::managed_process_clear(&state.managed_processes, args.process_id)) + } +} + +mod managed_process_read_log { + use super::*; + use agent_core::commands::process::*; + use agent_core::runtime::managed_process::{ + ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, + ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct ManagedProcessReadLogRouteArgs { + process_id: String, + max_bytes: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::process::managed_process_read_log(&state.managed_processes, args.process_id, args.max_bytes)) + } +} + +mod managed_process_snapshot { + use super::*; + use agent_core::commands::process::*; + use agent_core::runtime::managed_process::{ + ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, + ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, +}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::process::managed_process_snapshot(&state.managed_processes)) + } +} + +mod managed_process_start { + use super::*; + use agent_core::commands::process::*; + use agent_core::runtime::managed_process::{ + ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, + ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct ManagedProcessStartRouteArgs { + workdir: String, + command: String, + cwd: Option, + label: Option, + isolated: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::process::managed_process_start(&state.managed_processes, args.workdir, args.command, args.cwd, args.label, args.isolated)) + } +} + +mod managed_process_status { + use super::*; + use agent_core::commands::process::*; + use agent_core::runtime::managed_process::{ + ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, + ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct ManagedProcessStatusRouteArgs { + process_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::process::managed_process_status(&state.managed_processes, args.process_id)) + } +} + +mod managed_process_stop { + use super::*; + use agent_core::commands::process::*; + use agent_core::runtime::managed_process::{ + ManagedProcessLogResponse, ManagedProcessRegistry, ManagedProcessSnapshot, + ManagedProcessStartResponse, ManagedProcessStatusResponse, ManagedProcessStopResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct ManagedProcessStopRouteArgs { + process_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::process::managed_process_stop(&state.managed_processes, args.process_id)) + } +} + +mod mcp_call_tool { + use super::*; + use agent_core::commands::mcp::*; + use agent_core::runtime::platform::{ + expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, +}; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, +}; + use agent_core::runtime::shell_runner::ShellRunRegistry; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct McpCallToolRouteArgs { + server_id: String, + tool_name: String, + arguments: Value, + run_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::mcp::mcp_call_tool(&state.mcp, &state.shell_runs, args.server_id, args.tool_name, args.arguments, args.run_id).await) + } +} + +mod mcp_list_tools { + use super::*; + use agent_core::commands::mcp::*; + use agent_core::runtime::platform::{ + expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, +}; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, +}; + use agent_core::runtime::shell_runner::ShellRunRegistry; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct McpListToolsRouteArgs { + servers: Vec, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::mcp::mcp_list_tools(&state.mcp, args.servers).await) + } +} + +mod mcp_restart_server { + use super::*; + use agent_core::commands::mcp::*; + use agent_core::runtime::platform::{ + expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, +}; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, +}; + use agent_core::runtime::shell_runner::ShellRunRegistry; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct McpRestartServerRouteArgs { + server: McpServerConfig, + include_schema: Option, + persist: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::mcp::mcp_restart_server(&state.mcp, args.server, args.include_schema, args.persist).await) + } +} + +mod mcp_runtime_status { + use super::*; + use agent_core::commands::mcp::*; + use agent_core::runtime::platform::{ + expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, +}; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, +}; + use agent_core::runtime::shell_runner::ShellRunRegistry; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct McpRuntimeStatusRouteArgs { + server_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::mcp::mcp_runtime_status(&state.mcp, args.server_id).await) + } +} + +mod mcp_stop_server { + use super::*; + use agent_core::commands::mcp::*; + use agent_core::runtime::platform::{ + expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, +}; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, +}; + use agent_core::runtime::shell_runner::ShellRunRegistry; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct McpStopServerRouteArgs { + server_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::mcp::mcp_stop_server(&state.mcp, args.server_id).await) + } +} + +mod mcp_test_server { + use super::*; + use agent_core::commands::mcp::*; + use agent_core::runtime::platform::{ + expand_tilde_path, maybe_augment_macos_path, resolve_program_path_with_current_dir, +}; + use agent_core::runtime::process::{ + configure_child_process_group, kill_child_process_tree_best_effort, +}; + use agent_core::runtime::shell_runner::ShellRunRegistry; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct McpTestServerRouteArgs { + server: McpServerConfig, + include_schema: Option, + persist: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::mcp::mcp_test_server(&state.mcp, args.server, args.include_schema, args.persist).await) + } +} + +mod memory_accept { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryAcceptRouteArgs { + args: MemoryAcceptArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_accept(&state.memory_store, args.args).await) + } +} + +mod memory_apply_batch { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryApplyBatchRouteArgs { + args: MemoryBatchArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_apply_batch(&state.memory_store, args.args).await) + } +} + +mod memory_delete { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryDeleteRouteArgs { + args: MemoryDeleteArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_delete(&state.memory_store, args.args).await) + } +} + +mod memory_delete_project { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryDeleteProjectRouteArgs { + args: MemoryDeleteProjectArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_delete_project(&state.memory_store, args.args).await) + } +} + +mod memory_index_overview { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryIndexOverviewRouteArgs { + workdir: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_index_overview(&state.memory_store, args.workdir).await) + } +} + +mod memory_list { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryListRouteArgs { + args: MemoryListArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_list(&state.memory_store, args.args).await) + } +} + +mod memory_organize_due_claim { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryOrganizeDueClaimRouteArgs { + args: MemoryOrganizeDueClaimArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_organize_due_claim(&state.memory_store, args.args).await) + } +} + +mod memory_organize_due_complete { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryOrganizeDueCompleteRouteArgs { + args: MemoryOrganizeRunUpdateArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_organize_due_complete(&state.memory_store, args.args).await) + } +} + +mod memory_organize_run_clear_history { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::memory::memory_organize_run_clear_history(&state.memory_store).await) + } +} + +mod memory_organize_run_create { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryOrganizeRunCreateRouteArgs { + args: MemoryOrganizeRunCreateArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_organize_run_create(&state.memory_store, args.args).await) + } +} + +mod memory_organize_run_list { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryOrganizeRunListRouteArgs { + args: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_organize_run_list(&state.memory_store, args.args).await) + } +} + +mod memory_organize_run_read { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryOrganizeRunReadRouteArgs { + args: MemoryOrganizeRunReadArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_organize_run_read(&state.memory_store, args.args).await) + } +} + +mod memory_organize_run_update { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryOrganizeRunUpdateRouteArgs { + args: MemoryOrganizeRunUpdateArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_organize_run_update(&state.memory_store, args.args).await) + } +} + +mod memory_paths_info { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::memory::memory_paths_info(&state.memory_store).await) + } +} + +mod memory_quota_summary { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryQuotaSummaryRouteArgs { + args: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_quota_summary(&state.memory_store, args.args).await) + } +} + +mod memory_read { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryReadRouteArgs { + args: MemoryReadArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_read(&state.memory_store, args.args).await) + } +} + +mod memory_recent_rejections { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryRecentRejectionsRouteArgs { + args: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_recent_rejections(&state.memory_store, args.args).await) + } +} + +mod memory_search { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemorySearchRouteArgs { + args: MemorySearchArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_search(&state.memory_store, args.args).await) + } +} + +mod memory_today_daily { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryTodayDailyRouteArgs { + rollover_hour: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_today_daily(&state.memory_store, args.rollover_hour).await) + } +} + +mod memory_today_local_date { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryTodayLocalDateRouteArgs { + rollover_hour: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_today_local_date(&state.memory_store, args.rollover_hour).await) + } +} + +mod memory_update { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryUpdateRouteArgs { + args: MemoryUpdateArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_update(&state.memory_store, args.args).await) + } +} + +mod memory_wipe_all { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::memory::memory_wipe_all(&state.memory_store).await) + } +} + +mod memory_write { + use super::*; + use agent_core::commands::chat_history; + use agent_core::commands::memory::*; + use agent_core::services::memory::{ + MemoryAcceptArgs, MemoryBatchArgs, MemoryBatchResponse, MemoryDeleteArgs, + MemoryDeleteProjectArgs, MemoryDeleteProjectResponse, MemoryListArgs, MemoryListResponse, + MemoryMutationResponse, MemoryOrganizeDueClaimArgs, MemoryOrganizeDueClaimResponse, + MemoryOrganizeRun, MemoryOrganizeRunClearHistoryResponse, MemoryOrganizeRunCreateArgs, + MemoryOrganizeRunCreateResponse, MemoryOrganizeRunListArgs, MemoryOrganizeRunListResponse, + MemoryOrganizeRunReadArgs, MemoryOrganizeRunUpdateArgs, MemoryOverviewResponse, + MemoryPathsInfo, MemoryQuotaSummaryArgs, MemoryQuotaSummaryResponse, MemoryReadArgs, + MemoryReadResponse, MemoryRecentRejectionsArgs, MemoryRecentRejectionsResponse, + MemorySearchArgs, MemorySearchResponse, MemoryStore, MemoryUpdateArgs, MemoryWriteArgs, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct MemoryWriteRouteArgs { + args: MemoryWriteArgs, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::memory::memory_write(&state.memory_store, args.args).await) + } +} + +mod runtime_cancel { + use super::*; + use agent_core::commands::shell::*; + use agent_core::runtime::shell_runner::{run_shell_script, ShellRunRegistry, ShellRunResponse}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct RuntimeCancelRouteArgs { + run_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(Ok::<_, String>(agent_core::commands::shell::runtime_cancel(&state.shell_runs, args.run_id))) + } +} + +mod settings_apply_ssh_patch { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsApplySshPatchRouteArgs { + payload: Value, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_apply_ssh_patch(args.payload).await) + } +} + +mod settings_list_ccswitch_providers { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + pub async fn handle() -> Response { + respond(agent_core::commands::settings::settings_list_ccswitch_providers().await) + } +} + +mod settings_list_cherry_studio_providers { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + pub async fn handle() -> Response { + respond(agent_core::commands::settings::settings_list_cherry_studio_providers().await) + } +} + +mod settings_list_cherry_studio_providers_from_path { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsListCherryStudioProvidersFromPathRouteArgs { + data_path: String, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_list_cherry_studio_providers_from_path(args.data_path).await) + } +} + +mod settings_load_all { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + pub async fn handle() -> Response { + respond(agent_core::commands::settings::settings_load_all().await) + } +} + +mod settings_reset_ssh_known_host { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsResetSshKnownHostRouteArgs { + host: String, + port: u16, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_reset_ssh_known_host(args.host, args.port).await) + } +} + +mod settings_save_agents { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsSaveAgentsRouteArgs { + payload: Value, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_save_agents(args.payload).await) + } +} + +mod settings_save_mcp { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsSaveMcpRouteArgs { + payload: Value, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_save_mcp(args.payload).await) + } +} + +mod settings_save_memory { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsSaveMemoryRouteArgs { + payload: Value, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_save_memory(args.payload).await) + } +} + +mod settings_save_providers { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsSaveProvidersRouteArgs { + payload: Value, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_save_providers(args.payload).await) + } +} + +mod settings_save_remote { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsSaveRemoteRouteArgs { + payload: Value, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_save_remote(args.payload, &state.events).await) + } +} + +mod settings_save_ssh { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsSaveSshRouteArgs { + payload: Value, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_save_ssh(args.payload).await) + } +} + +mod settings_save_system { + use super::*; + use agent_core::commands::settings::*; + use agent_core::events::EventBus; + use agent_core::runtime::project_path::project_path_key as normalize_project_path_key; + use agent_core::services::automation::AutomationScheduler; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SettingsSaveSystemRouteArgs { + payload: Value, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::settings::settings_save_system(args.payload, &state.automation_scheduler).await) + } +} + +mod sftp_cancel_transfer { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpCancelTransferRouteArgs { + session_id: String, + transfer_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_cancel_transfer(&state.sftp, args.session_id, args.transfer_id)) + } +} + +mod sftp_delete { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpDeleteRouteArgs { + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: String, + recursive: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_delete(&state.sftp, args.session_id, args.project_path_key, args.workdir, args.side, args.path, args.recursive).await) + } +} + +mod sftp_list { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpListRouteArgs { + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_list(&state.sftp, args.session_id, args.project_path_key, args.workdir, args.side, args.path).await) + } +} + +mod sftp_mkdir { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpMkdirRouteArgs { + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_mkdir(&state.sftp, args.session_id, args.project_path_key, args.workdir, args.side, args.path).await) + } +} + +mod sftp_read_text { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpReadTextRouteArgs { + session_id: String, + project_path_key: Option, + path: String, + offset: Option, + max_bytes: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_read_text(&state.sftp, args.session_id, args.project_path_key, args.path, args.offset, args.max_bytes).await) + } +} + +mod sftp_rename { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpRenameRouteArgs { + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + from_path: String, + to_path: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_rename(&state.sftp, args.session_id, args.project_path_key, args.workdir, args.side, args.from_path, args.to_path).await) + } +} + +mod sftp_stat { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpStatRouteArgs { + session_id: String, + project_path_key: Option, + workdir: String, + side: String, + path: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_stat(&state.sftp, args.session_id, args.project_path_key, args.workdir, args.side, args.path).await) + } +} + +mod sftp_transfer { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpTransferRouteArgs { + session_id: String, + project_path_key: Option, + workdir: String, + direction: String, + source_path: String, + target_path: String, + recursive: Option, + overwrite: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_transfer(&state.sftp, args.session_id, args.project_path_key, args.workdir, args.direction, args.source_path, args.target_path, args.recursive, args.overwrite).await) + } +} + +mod sftp_transfer_status { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpTransferStatusRouteArgs { + session_id: String, + transfer_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_transfer_status(&state.sftp, args.session_id, args.transfer_id)) + } +} + +mod sftp_write_text { + use super::*; + use agent_core::commands::sftp::*; + use agent_core::runtime::sftp::{ + SftpActionResponse, SftpListResponse, SftpReadTextResponse, SftpSessionRegistry, + SftpStatResponse, SftpTransferResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SftpWriteTextRouteArgs { + session_id: String, + project_path_key: Option, + path: String, + content: String, + overwrite: Option, + create_parent_dirs: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::sftp::sftp_write_text(&state.sftp, args.session_id, args.project_path_key, args.path, args.content, args.overwrite, args.create_parent_dirs).await) + } +} + +mod shell_run { + use super::*; + use agent_core::commands::shell::*; + use agent_core::runtime::shell_runner::{run_shell_script, ShellRunRegistry, ShellRunResponse}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct ShellRunRouteArgs { + workdir: String, + command: String, + cwd: Option, + timeout_ms: Option, + max_timeout_ms: Option, + provider_id: Option, + run_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::shell::shell_run(&state.shell_runs, args.workdir, args.command, args.cwd, args.timeout_ms, args.max_timeout_ms, args.provider_id, args.run_id).await) + } +} + +mod ssh_terminal_tab_close { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SshTerminalTabCloseRouteArgs { + tab_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::ssh_terminal_tab_close(&state.terminals, args.tab_id)) + } +} + +mod ssh_terminal_tab_open { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SshTerminalTabOpenRouteArgs { + session_id: String, + kind: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::ssh_terminal_tab_open(&state.terminals, args.session_id, args.kind)) + } +} + +mod ssh_terminal_tabs_list { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct SshTerminalTabsListRouteArgs { + project_path_key: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::ssh_terminal_tabs_list(&state.terminals, args.project_path_key)) + } +} + +mod subagent_identity_list { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentIdentityListRouteArgs { + input: SubagentIdentityListInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_identity_list(args.input).await) + } +} + +mod subagent_identity_upsert { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentIdentityUpsertRouteArgs { + input: SubagentIdentityUpsertInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_identity_upsert(args.input).await) + } +} + +mod subagent_message_append { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentMessageAppendRouteArgs { + input: SubagentMessageAppendInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_message_append(args.input).await) + } +} + +mod subagent_message_list { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentMessageListRouteArgs { + input: SubagentMessageListInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_message_list(args.input).await) + } +} + +mod subagent_run_list { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentRunListRouteArgs { + input: SubagentRunListInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_run_list(args.input).await) + } +} + +mod subagent_run_load { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentRunLoadRouteArgs { + input: SubagentRunLoadInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_run_load(args.input).await) + } +} + +mod subagent_run_prune { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentRunPruneRouteArgs { + input: SubagentRunPruneInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_run_prune(args.input).await) + } +} + +mod subagent_run_save { + use super::*; + use agent_core::commands::subagent_store::*; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentRunSaveRouteArgs { + input: SubagentRunSaveInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_store::subagent_run_save(args.input).await) + } +} + +mod subagent_worktree_apply { + use super::*; + use agent_core::commands::subagent_worktree::*; + use agent_core::runtime::process::configure_child_process_group; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentWorktreeApplyRouteArgs { + input: SubagentWorktreeApplyInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_worktree::subagent_worktree_apply(args.input).await) + } +} + +mod subagent_worktree_cleanup { + use super::*; + use agent_core::commands::subagent_worktree::*; + use agent_core::runtime::process::configure_child_process_group; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentWorktreeCleanupRouteArgs { + input: SubagentWorktreeCleanupInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_worktree::subagent_worktree_cleanup(args.input).await) + } +} + +mod subagent_worktree_create { + use super::*; + use agent_core::commands::subagent_worktree::*; + use agent_core::runtime::process::configure_child_process_group; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentWorktreeCreateRouteArgs { + input: SubagentWorktreeCreateInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_worktree::subagent_worktree_create(args.input).await) + } +} + +mod subagent_worktree_status { + use super::*; + use agent_core::commands::subagent_worktree::*; + use agent_core::runtime::process::configure_child_process_group; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct SubagentWorktreeStatusRouteArgs { + input: SubagentWorktreeStatusInput, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::subagent_worktree::subagent_worktree_status(args.input).await) + } +} + +mod terminal_answer_ssh_prompt { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalAnswerSshPromptRouteArgs { + prompt_id: String, + prompt_answer: Option, + trust_host_key: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_answer_ssh_prompt(&state.terminals, args.prompt_id, args.prompt_answer, args.trust_host_key).await) + } +} + +mod terminal_cancel_ssh_prompt { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalCancelSshPromptRouteArgs { + prompt_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_cancel_ssh_prompt(&state.terminals, args.prompt_id)) + } +} + +mod terminal_close { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalCloseRouteArgs { + session_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_close(&state.terminals, &state.sftp, args.session_id)) + } +} + +mod terminal_close_project { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalCloseProjectRouteArgs { + project_path_key: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_close_project(&state.terminals, &state.sftp, args.project_path_key)) + } +} + +mod terminal_create { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalCreateRouteArgs { + cwd: String, + project_path_key: Option, + shell: Option, + title: Option, + cols: Option, + rows: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_create(&state.terminals, args.cwd, args.project_path_key, args.shell, args.title, args.cols, args.rows)) + } +} + +mod terminal_create_ssh { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalCreateSshRouteArgs { + cwd: String, + project_path_key: Option, + ssh_host_id: String, + title: Option, + cols: Option, + rows: Option, + sftp_enabled: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_create_ssh(&state.terminals, args.cwd, args.project_path_key, args.ssh_host_id, args.title, args.cols, args.rows, args.sftp_enabled).await) + } +} + +mod terminal_list { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalListRouteArgs { + project_path_key: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(Ok::<_, String>(agent_core::commands::terminal::terminal_list(&state.terminals, args.project_path_key))) + } +} + +mod terminal_read_tail { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalReadTailRouteArgs { + project_path_key: String, + session_id: Option, + max_bytes: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_read_tail(&state.terminals, args.project_path_key, args.session_id, args.max_bytes)) + } +} + +mod terminal_rename { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalRenameRouteArgs { + session_id: String, + title: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_rename(&state.terminals, args.session_id, args.title)) + } +} + +mod terminal_shell_options { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + pub async fn handle() -> Response { + respond(Ok::<_, String>(agent_core::commands::terminal::terminal_shell_options())) + } +} + +mod terminal_ssh_exec { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalSshExecRouteArgs { + session_id: String, + command: String, + cwd: Option, + timeout_ms: Option, + max_bytes: Option, + run_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_ssh_exec(&state.terminals, &state.shell_runs, args.session_id, args.command, args.cwd, args.timeout_ms, args.max_bytes, args.run_id).await) + } +} + +mod terminal_ssh_latency { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalSshLatencyRouteArgs { + session_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_ssh_latency(&state.terminals, args.session_id).await) + } +} + +mod terminal_ssh_local_forward_check_port { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalSshLocalForwardCheckPortRouteArgs { + local_port: u32, + } + + pub async fn handle( + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_ssh_local_forward_check_port(args.local_port).await) + } +} + +mod terminal_ssh_local_forward_list { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalSshLocalForwardListRouteArgs { + session_id: Option, + project_path_key: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_ssh_local_forward_list(&state.terminals, args.session_id, args.project_path_key)) + } +} + +mod terminal_ssh_local_forward_start { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalSshLocalForwardStartRouteArgs { + session_id: String, + project_path_key: Option, + remote_host: String, + remote_port: u32, + local_port: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_ssh_local_forward_start(&state.terminals, args.session_id, args.project_path_key, args.remote_host, args.remote_port, args.local_port).await) + } +} + +mod terminal_ssh_local_forward_stop { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalSshLocalForwardStopRouteArgs { + forward_id: String, + session_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_ssh_local_forward_stop(&state.terminals, args.forward_id, args.session_id).await) + } +} + +mod terminal_ssh_reconnect { + use super::*; + use agent_core::commands::terminal::*; + use agent_core::runtime::sftp::SftpSessionRegistry; + use agent_core::runtime::shell_runner::ShellRunRegistry; + use agent_core::runtime::terminal::{ + normalize_ssh_local_forward_local_port, ssh_local_forward_local_port_available, + terminal_shell_options as runtime_terminal_shell_options, SshLocalForwardActionResponse, + SshLocalForwardListResponse, SshTerminalTabsSnapshot, TerminalListResponse, + TerminalReadTailResponse, TerminalSessionRecord, TerminalSessionRegistry, + TerminalShellOptionsResponse, TerminalSnapshotResponse, TerminalSshCreateResponse, + TerminalSshExecResponse, TerminalSshLatencyResponse, TerminalStreamSnapshotResponse, +}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TerminalSshReconnectRouteArgs { + session_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::terminal::terminal_ssh_reconnect(&state.terminals, args.session_id).await) + } +} + +mod tunnel_check { + use super::*; + use agent_core::services::tunnel::{TunnelCreateInput, TunnelState, TunnelStore, TunnelUpdateInput}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TunnelCheckRouteArgs { + tunnel_id: Option, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::tunnel::tunnel_check(args.tunnel_id, &state.tunnels).await) + } +} + +mod tunnel_close { + use super::*; + use agent_core::services::tunnel::{TunnelCreateInput, TunnelState, TunnelStore, TunnelUpdateInput}; + + #[derive(Deserialize)] + #[serde(rename_all = "snake_case")] + pub struct TunnelCloseRouteArgs { + tunnel_id: String, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::tunnel::tunnel_close(args.tunnel_id, &state.tunnels).await) + } +} + +mod tunnel_create { + use super::*; + use agent_core::services::tunnel::{TunnelCreateInput, TunnelState, TunnelStore, TunnelUpdateInput}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct TunnelCreateRouteArgs { + input: TunnelCreateInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::tunnel::tunnel_create(args.input, &state.tunnels).await) + } +} + +mod tunnel_state { + use super::*; + use agent_core::services::tunnel::{TunnelCreateInput, TunnelState, TunnelStore, TunnelUpdateInput}; + + pub async fn handle( + State(state): State, + ) -> Response { + respond(agent_core::commands::tunnel::tunnel_state(&state.tunnels)) + } +} + +mod tunnel_update { + use super::*; + use agent_core::services::tunnel::{TunnelCreateInput, TunnelState, TunnelStore, TunnelUpdateInput}; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct TunnelUpdateRouteArgs { + input: TunnelUpdateInput, + } + + pub async fn handle( + State(state): State, + Json(args): Json, + ) -> Response { + respond(agent_core::commands::tunnel::tunnel_update(args.input, &state.tunnels).await) + } +} + +pub fn gen_router() -> Router { + Router::new() + .route("/automation_claim_prompt_runs", post(automation_claim_prompt_runs::handle)) + .route("/automation_clear_runs", post(automation_clear_runs::handle)) + .route("/automation_complete_prompt_run", post(automation_complete_prompt_run::handle)) + .route("/automation_cron_apply", post(automation_cron_apply::handle)) + .route("/automation_hooks_apply", post(automation_hooks_apply::handle)) + .route("/automation_list_runs", post(automation_list_runs::handle)) + .route("/automation_release_prompt_run", post(automation_release_prompt_run::handle)) + .route("/automation_run_cron_now", post(automation_run_cron_now::handle)) + .route("/automation_snapshot", post(automation_snapshot::handle)) + .route("/chat_history_append_segment", post(chat_history_append_segment::handle)) + .route("/chat_history_branch", post(chat_history_branch::handle)) + .route("/chat_history_delete", post(chat_history_delete::handle)) + .route("/chat_history_get_window", post(chat_history_get_window::handle)) + .route("/chat_history_list", post(chat_history_list::handle)) + .route("/chat_history_rename", post(chat_history_rename::handle)) + .route("/chat_history_replace_from_message", post(chat_history_replace_from_message::handle)) + .route("/chat_history_search", post(chat_history_search::handle)) + .route("/chat_history_set_model", post(chat_history_set_model::handle)) + .route("/chat_history_set_pinned", post(chat_history_set_pinned::handle)) + .route("/chat_history_share_get", post(chat_history_share_get::handle)) + .route("/chat_history_share_set", post(chat_history_share_set::handle)) + .route("/chat_history_shared_list", post(chat_history_shared_list::handle)) + .route("/chat_history_upsert", post(chat_history_upsert::handle)) + .route("/chat_history_upsert_active_segment", post(chat_history_upsert_active_segment::handle)) + .route("/chat_history_workdirs", post(chat_history_workdirs::handle)) + .route("/cron_validate_expression", post(cron_validate_expression::handle)) + .route("/fs_create_dir", post(fs_create_dir::handle)) + .route("/fs_delete", post(fs_delete::handle)) + .route("/fs_edit_text", post(fs_edit_text::handle)) + .route("/fs_glob", post(fs_glob::handle)) + .route("/fs_grep", post(fs_grep::handle)) + .route("/fs_list", post(fs_list::handle)) + .route("/fs_list_dirs", post(fs_list_dirs::handle)) + .route("/fs_mention_list", post(fs_mention_list::handle)) + .route("/fs_path_status", post(fs_path_status::handle)) + .route("/fs_read_editable_text", post(fs_read_editable_text::handle)) + .route("/fs_read_image_source", post(fs_read_image_source::handle)) + .route("/fs_read_text", post(fs_read_text::handle)) + .route("/fs_read_workspace_image", post(fs_read_workspace_image::handle)) + .route("/fs_rename", post(fs_rename::handle)) + .route("/fs_roots", post(fs_roots::handle)) + .route("/fs_write_text", post(fs_write_text::handle)) + .route("/git_add_to_gitignore", post(git_add_to_gitignore::handle)) + .route("/git_branches", post(git_branches::handle)) + .route("/git_clone_repository", post(git_clone_repository::handle)) + .route("/git_clone_repository_cancel", post(git_clone_repository_cancel::handle)) + .route("/git_clone_repository_dismiss", post(git_clone_repository_dismiss::handle)) + .route("/git_clone_repository_start", post(git_clone_repository_start::handle)) + .route("/git_clone_repository_tasks", post(git_clone_repository_tasks::handle)) + .route("/git_commit", post(git_commit::handle)) + .route("/git_commit_details", post(git_commit_details::handle)) + .route("/git_commit_diff", post(git_commit_diff::handle)) + .route("/git_compare_commit_with_remote", post(git_compare_commit_with_remote::handle)) + .route("/git_create_branch", post(git_create_branch::handle)) + .route("/git_delete_branch", post(git_delete_branch::handle)) + .route("/git_diff", post(git_diff::handle)) + .route("/git_discard", post(git_discard::handle)) + .route("/git_discard_all", post(git_discard_all::handle)) + .route("/git_discover_repositories", post(git_discover_repositories::handle)) + .route("/git_fetch", post(git_fetch::handle)) + .route("/git_init", post(git_init::handle)) + .route("/git_list_remote_branches", post(git_list_remote_branches::handle)) + .route("/git_log", post(git_log::handle)) + .route("/git_pull", post(git_pull::handle)) + .route("/git_push", post(git_push::handle)) + .route("/git_rename_branch", post(git_rename_branch::handle)) + .route("/git_set_remote", post(git_set_remote::handle)) + .route("/git_stage", post(git_stage::handle)) + .route("/git_stage_all", post(git_stage_all::handle)) + .route("/git_stash_pop", post(git_stash_pop::handle)) + .route("/git_stash_push", post(git_stash_push::handle)) + .route("/git_status", post(git_status::handle)) + .route("/git_switch_branch", post(git_switch_branch::handle)) + .route("/git_unstage", post(git_unstage::handle)) + .route("/git_unstage_all", post(git_unstage_all::handle)) + .route("/hook_cancel_scope", post(hook_cancel_scope::handle)) + .route("/hook_run_http_requests", post(hook_run_http_requests::handle)) + .route("/hook_run_script", post(hook_run_script::handle)) + .route("/managed_process_clear", post(managed_process_clear::handle)) + .route("/managed_process_read_log", post(managed_process_read_log::handle)) + .route("/managed_process_snapshot", post(managed_process_snapshot::handle)) + .route("/managed_process_start", post(managed_process_start::handle)) + .route("/managed_process_status", post(managed_process_status::handle)) + .route("/managed_process_stop", post(managed_process_stop::handle)) + .route("/mcp_call_tool", post(mcp_call_tool::handle)) + .route("/mcp_list_tools", post(mcp_list_tools::handle)) + .route("/mcp_restart_server", post(mcp_restart_server::handle)) + .route("/mcp_runtime_status", post(mcp_runtime_status::handle)) + .route("/mcp_stop_server", post(mcp_stop_server::handle)) + .route("/mcp_test_server", post(mcp_test_server::handle)) + .route("/memory_accept", post(memory_accept::handle)) + .route("/memory_apply_batch", post(memory_apply_batch::handle)) + .route("/memory_delete", post(memory_delete::handle)) + .route("/memory_delete_project", post(memory_delete_project::handle)) + .route("/memory_index_overview", post(memory_index_overview::handle)) + .route("/memory_list", post(memory_list::handle)) + .route("/memory_organize_due_claim", post(memory_organize_due_claim::handle)) + .route("/memory_organize_due_complete", post(memory_organize_due_complete::handle)) + .route("/memory_organize_run_clear_history", post(memory_organize_run_clear_history::handle)) + .route("/memory_organize_run_create", post(memory_organize_run_create::handle)) + .route("/memory_organize_run_list", post(memory_organize_run_list::handle)) + .route("/memory_organize_run_read", post(memory_organize_run_read::handle)) + .route("/memory_organize_run_update", post(memory_organize_run_update::handle)) + .route("/memory_paths_info", post(memory_paths_info::handle)) + .route("/memory_quota_summary", post(memory_quota_summary::handle)) + .route("/memory_read", post(memory_read::handle)) + .route("/memory_recent_rejections", post(memory_recent_rejections::handle)) + .route("/memory_search", post(memory_search::handle)) + .route("/memory_today_daily", post(memory_today_daily::handle)) + .route("/memory_today_local_date", post(memory_today_local_date::handle)) + .route("/memory_update", post(memory_update::handle)) + .route("/memory_wipe_all", post(memory_wipe_all::handle)) + .route("/memory_write", post(memory_write::handle)) + .route("/runtime_cancel", post(runtime_cancel::handle)) + .route("/settings_apply_ssh_patch", post(settings_apply_ssh_patch::handle)) + .route("/settings_list_ccswitch_providers", post(settings_list_ccswitch_providers::handle)) + .route("/settings_list_cherry_studio_providers", post(settings_list_cherry_studio_providers::handle)) + .route("/settings_list_cherry_studio_providers_from_path", post(settings_list_cherry_studio_providers_from_path::handle)) + .route("/settings_load_all", post(settings_load_all::handle)) + .route("/settings_reset_ssh_known_host", post(settings_reset_ssh_known_host::handle)) + .route("/settings_save_agents", post(settings_save_agents::handle)) + .route("/settings_save_mcp", post(settings_save_mcp::handle)) + .route("/settings_save_memory", post(settings_save_memory::handle)) + .route("/settings_save_providers", post(settings_save_providers::handle)) + .route("/settings_save_remote", post(settings_save_remote::handle)) + .route("/settings_save_ssh", post(settings_save_ssh::handle)) + .route("/settings_save_system", post(settings_save_system::handle)) + .route("/sftp_cancel_transfer", post(sftp_cancel_transfer::handle)) + .route("/sftp_delete", post(sftp_delete::handle)) + .route("/sftp_list", post(sftp_list::handle)) + .route("/sftp_mkdir", post(sftp_mkdir::handle)) + .route("/sftp_read_text", post(sftp_read_text::handle)) + .route("/sftp_rename", post(sftp_rename::handle)) + .route("/sftp_stat", post(sftp_stat::handle)) + .route("/sftp_transfer", post(sftp_transfer::handle)) + .route("/sftp_transfer_status", post(sftp_transfer_status::handle)) + .route("/sftp_write_text", post(sftp_write_text::handle)) + .route("/shell_run", post(shell_run::handle)) + .route("/ssh_terminal_tab_close", post(ssh_terminal_tab_close::handle)) + .route("/ssh_terminal_tab_open", post(ssh_terminal_tab_open::handle)) + .route("/ssh_terminal_tabs_list", post(ssh_terminal_tabs_list::handle)) + .route("/subagent_identity_list", post(subagent_identity_list::handle)) + .route("/subagent_identity_upsert", post(subagent_identity_upsert::handle)) + .route("/subagent_message_append", post(subagent_message_append::handle)) + .route("/subagent_message_list", post(subagent_message_list::handle)) + .route("/subagent_run_list", post(subagent_run_list::handle)) + .route("/subagent_run_load", post(subagent_run_load::handle)) + .route("/subagent_run_prune", post(subagent_run_prune::handle)) + .route("/subagent_run_save", post(subagent_run_save::handle)) + .route("/subagent_worktree_apply", post(subagent_worktree_apply::handle)) + .route("/subagent_worktree_cleanup", post(subagent_worktree_cleanup::handle)) + .route("/subagent_worktree_create", post(subagent_worktree_create::handle)) + .route("/subagent_worktree_status", post(subagent_worktree_status::handle)) + .route("/terminal_answer_ssh_prompt", post(terminal_answer_ssh_prompt::handle)) + .route("/terminal_cancel_ssh_prompt", post(terminal_cancel_ssh_prompt::handle)) + .route("/terminal_close", post(terminal_close::handle)) + .route("/terminal_close_project", post(terminal_close_project::handle)) + .route("/terminal_create", post(terminal_create::handle)) + .route("/terminal_create_ssh", post(terminal_create_ssh::handle)) + .route("/terminal_list", post(terminal_list::handle)) + .route("/terminal_read_tail", post(terminal_read_tail::handle)) + .route("/terminal_rename", post(terminal_rename::handle)) + .route("/terminal_shell_options", post(terminal_shell_options::handle)) + .route("/terminal_ssh_exec", post(terminal_ssh_exec::handle)) + .route("/terminal_ssh_latency", post(terminal_ssh_latency::handle)) + .route("/terminal_ssh_local_forward_check_port", post(terminal_ssh_local_forward_check_port::handle)) + .route("/terminal_ssh_local_forward_list", post(terminal_ssh_local_forward_list::handle)) + .route("/terminal_ssh_local_forward_start", post(terminal_ssh_local_forward_start::handle)) + .route("/terminal_ssh_local_forward_stop", post(terminal_ssh_local_forward_stop::handle)) + .route("/terminal_ssh_reconnect", post(terminal_ssh_reconnect::handle)) + .route("/tunnel_check", post(tunnel_check::handle)) + .route("/tunnel_close", post(tunnel_close::handle)) + .route("/tunnel_create", post(tunnel_create::handle)) + .route("/tunnel_state", post(tunnel_state::handle)) + .route("/tunnel_update", post(tunnel_update::handle)) +} + +/// 已挂路由的命令名。契约测试拿它和 backend.txt 清单比对, +/// 「新增 command 未加路由」必须导致测试失败。 +pub const ROUTED_COMMANDS: &[&str] = &[ + "automation_claim_prompt_runs", + "automation_clear_runs", + "automation_complete_prompt_run", + "automation_cron_apply", + "automation_hooks_apply", + "automation_list_runs", + "automation_release_prompt_run", + "automation_run_cron_now", + "automation_snapshot", + "chat_history_append_segment", + "chat_history_branch", + "chat_history_delete", + "chat_history_get_window", + "chat_history_list", + "chat_history_rename", + "chat_history_replace_from_message", + "chat_history_search", + "chat_history_set_model", + "chat_history_set_pinned", + "chat_history_share_get", + "chat_history_share_set", + "chat_history_shared_list", + "chat_history_upsert", + "chat_history_upsert_active_segment", + "chat_history_workdirs", + "cron_validate_expression", + "fs_create_dir", + "fs_delete", + "fs_edit_text", + "fs_glob", + "fs_grep", + "fs_list", + "fs_list_dirs", + "fs_mention_list", + "fs_path_status", + "fs_read_editable_text", + "fs_read_image_source", + "fs_read_text", + "fs_read_workspace_image", + "fs_rename", + "fs_roots", + "fs_write_text", + "git_add_to_gitignore", + "git_branches", + "git_clone_repository", + "git_clone_repository_cancel", + "git_clone_repository_dismiss", + "git_clone_repository_start", + "git_clone_repository_tasks", + "git_commit", + "git_commit_details", + "git_commit_diff", + "git_compare_commit_with_remote", + "git_create_branch", + "git_delete_branch", + "git_diff", + "git_discard", + "git_discard_all", + "git_discover_repositories", + "git_fetch", + "git_init", + "git_list_remote_branches", + "git_log", + "git_pull", + "git_push", + "git_rename_branch", + "git_set_remote", + "git_stage", + "git_stage_all", + "git_stash_pop", + "git_stash_push", + "git_status", + "git_switch_branch", + "git_unstage", + "git_unstage_all", + "hook_cancel_scope", + "hook_run_http_requests", + "hook_run_script", + "managed_process_clear", + "managed_process_read_log", + "managed_process_snapshot", + "managed_process_start", + "managed_process_status", + "managed_process_stop", + "mcp_call_tool", + "mcp_list_tools", + "mcp_restart_server", + "mcp_runtime_status", + "mcp_stop_server", + "mcp_test_server", + "memory_accept", + "memory_apply_batch", + "memory_delete", + "memory_delete_project", + "memory_index_overview", + "memory_list", + "memory_organize_due_claim", + "memory_organize_due_complete", + "memory_organize_run_clear_history", + "memory_organize_run_create", + "memory_organize_run_list", + "memory_organize_run_read", + "memory_organize_run_update", + "memory_paths_info", + "memory_quota_summary", + "memory_read", + "memory_recent_rejections", + "memory_search", + "memory_today_daily", + "memory_today_local_date", + "memory_update", + "memory_wipe_all", + "memory_write", + "runtime_cancel", + "settings_apply_ssh_patch", + "settings_list_ccswitch_providers", + "settings_list_cherry_studio_providers", + "settings_list_cherry_studio_providers_from_path", + "settings_load_all", + "settings_reset_ssh_known_host", + "settings_save_agents", + "settings_save_mcp", + "settings_save_memory", + "settings_save_providers", + "settings_save_remote", + "settings_save_ssh", + "settings_save_system", + "sftp_cancel_transfer", + "sftp_delete", + "sftp_list", + "sftp_mkdir", + "sftp_read_text", + "sftp_rename", + "sftp_stat", + "sftp_transfer", + "sftp_transfer_status", + "sftp_write_text", + "shell_run", + "ssh_terminal_tab_close", + "ssh_terminal_tab_open", + "ssh_terminal_tabs_list", + "subagent_identity_list", + "subagent_identity_upsert", + "subagent_message_append", + "subagent_message_list", + "subagent_run_list", + "subagent_run_load", + "subagent_run_prune", + "subagent_run_save", + "subagent_worktree_apply", + "subagent_worktree_cleanup", + "subagent_worktree_create", + "subagent_worktree_status", + "terminal_answer_ssh_prompt", + "terminal_cancel_ssh_prompt", + "terminal_close", + "terminal_close_project", + "terminal_create", + "terminal_create_ssh", + "terminal_list", + "terminal_read_tail", + "terminal_rename", + "terminal_shell_options", + "terminal_ssh_exec", + "terminal_ssh_latency", + "terminal_ssh_local_forward_check_port", + "terminal_ssh_local_forward_list", + "terminal_ssh_local_forward_start", + "terminal_ssh_local_forward_stop", + "terminal_ssh_reconnect", + "tunnel_check", + "tunnel_close", + "tunnel_create", + "tunnel_state", + "tunnel_update", +]; diff --git a/crates/agent-backend/src/state.rs b/crates/agent-backend/src/state.rs index dd19986f4..552816d34 100644 --- a/crates/agent-backend/src/state.rs +++ b/crates/agent-backend/src/state.rs @@ -22,6 +22,7 @@ use agent_core::services::automation::{AutomationScheduler, AutomationStore}; use agent_core::services::memory::MemoryStore; use agent_core::services::power_activity::PowerActivityManager; use agent_core::services::provider_usage::ProviderUsageService; +use agent_core::services::tunnel::TunnelStore; use agent_core::services::workspace_watch::WorkspaceWatchService; #[derive(Clone)] @@ -40,6 +41,9 @@ pub struct AppState { pub hook_scopes: Arc, pub mcp: Arc, pub workspace_watch: Arc, + /// 隧道状态。数据面(真正的监听)藏在它内部持有的 `TunnelDataPlane` 里, + /// 所以这里只需要一个句柄——命令层不该直接碰监听。 + pub tunnels: Arc, /// 认证凭据。密码即 Bearer token(决策 7)。 pub auth: Arc, } diff --git a/crates/agent-backend/src/tunnel.rs b/crates/agent-backend/src/tunnel.rs new file mode 100644 index 000000000..c1a16d44a --- /dev/null +++ b/crates/agent-backend/src/tunnel.rs @@ -0,0 +1,816 @@ +//! 隧道数据面:每条隧道一个独立监听端口,原样反代到本机目标。 +//! +//! ## 为什么是「一隧道一端口」 +//! +//! 挂子路径(`/t//`)时,dev server 吐的 `" })) + .route( + "/echo", + post(|body: String| async move { format!("echoed:{body}") }), + ) + .route( + "/headers", + get(|headers: axum::http::HeaderMap| async move { + // 回显 Cookie,用来断言隧道自己的 cookie 没漏给上游。 + headers + .get(axum::http::header::COOKIE) + .and_then(|v| v.to_str().ok()) + .unwrap_or("") + .to_string() + }), + ); + let listener = tokio::net::TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0))) + .await + .expect("bind upstream"); + let addr = listener.local_addr().expect("upstream addr"); + tokio::spawn(async move { + let _ = axum::serve(listener, app).await; + }); + format!("http://127.0.0.1:{}", addr.port()) +} + +/// 不跟随重定向、不带 cookie jar 的裸客户端。 +fn raw_client() -> reqwest::Client { + reqwest::Client::builder() + .no_proxy() + .redirect(reqwest::redirect::Policy::none()) + .build() + .expect("build client") +} + +async fn start_tunnel(target: &str) -> (Arc, u16) { + let plane = Arc::new(TunnelDataPlane::new()); + let port = plane + .start(TunnelBinding { + id: "tun_test".to_string(), + target_url: target.to_string(), + access_token: TOKEN.to_string(), + }) + .expect("start tunnel"); + // 给 axum::serve 一点时间真正开始 accept。 + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + (plane, port) +} + +#[tokio::test] +async fn tunnel_proxies_to_upstream_after_token_handshake() { + let upstream = spawn_upstream().await; + let (_plane, port) = start_tunnel(&upstream).await; + let client = raw_client(); + let base = format!("http://127.0.0.1:{port}"); + + // 1. 无凭证 → 401。端口能被扫到,但扫到了也进不去。 + let resp = client.get(&base).send().await.expect("request"); + assert_eq!(resp.status(), 401, "无 token 必须拒绝"); + + // 2. 带 token 首访 → 302 + Set-Cookie,且 token 从 Location 里消失。 + let resp = client + .get(format!("{base}/?t={TOKEN}")) + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 302); + let cookie = resp + .headers() + .get("set-cookie") + .and_then(|v| v.to_str().ok()) + .expect("Set-Cookie") + .to_string(); + assert!(cookie.contains("liveagent_tunnel_tun_test=")); + assert!(cookie.contains("HttpOnly"), "cookie 必须 HttpOnly:{cookie}"); + let location = resp + .headers() + .get("location") + .and_then(|v| v.to_str().ok()) + .expect("Location"); + assert_eq!(location, "/", "token 必须从地址栏消失"); + + // 3. 带 cookie → 真的拿到上游内容。 + let cookie_header = cookie.split(';').next().expect("cookie pair").to_string(); + let resp = client + .get(&base) + .header("cookie", &cookie_header) + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 200); + let body = resp.text().await.expect("body"); + assert!(body.contains("/assets/main.js"), "应拿到上游 HTML:{body}"); + + // 4. 关键断言:上游发的绝对路径 /assets/main.js 直接可用,无需任何重写。 + // 这就是「一隧道一端口」让 1000 行重写代码蒸发的原因。 + let resp = client + .get(format!("{base}/assets/main.js")) + .header("cookie", &cookie_header) + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 200, "绝对路径必须 1:1 命中,不需要前缀重写"); + assert_eq!(resp.text().await.expect("body"), "console.log('hi')"); +} + +#[tokio::test] +async fn tunnel_forwards_request_bodies_and_hides_its_own_cookie() { + let upstream = spawn_upstream().await; + let (_plane, port) = start_tunnel(&upstream).await; + let client = raw_client(); + let base = format!("http://127.0.0.1:{port}"); + let cookie_header = format!("liveagent_tunnel_tun_test={TOKEN}"); + + // POST body 要原样到上游。 + let resp = client + .post(format!("{base}/echo")) + .header("cookie", &cookie_header) + .body("payload-1234") + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 200); + assert_eq!(resp.text().await.expect("body"), "echoed:payload-1234"); + + // 隧道自己的 cookie 不能漏给上游;用户自己的要留着。 + let resp = client + .get(format!("{base}/headers")) + .header("cookie", format!("{cookie_header}; user_session=abc")) + .send() + .await + .expect("request"); + let seen = resp.text().await.expect("body"); + assert!( + !seen.contains(TOKEN), + "上游不该看到隧道 token,但收到了:{seen}" + ); + assert!(seen.contains("user_session=abc"), "用户自己的 cookie 要转发:{seen}"); +} + +#[tokio::test] +async fn stopping_a_tunnel_closes_its_port() { + let upstream = spawn_upstream().await; + let (plane, port) = start_tunnel(&upstream).await; + let client = raw_client(); + let base = format!("http://127.0.0.1:{port}"); + + assert_eq!(plane.running_count(), 1); + // 起着的时候连得上(401 也算连上了)。 + assert!(client.get(&base).send().await.is_ok()); + + plane.stop("tun_test"); + assert_eq!(plane.running_count(), 0); + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + + // 停掉之后端口不再服务。 + assert!( + client.get(&base).send().await.is_err(), + "隧道关掉后端口必须不再接受连接" + ); +} + +#[tokio::test] +async fn restarting_the_same_id_replaces_the_old_listener() { + let first = spawn_upstream().await; + let second = spawn_upstream().await; + let (plane, first_port) = start_tunnel(&first).await; + + // 同 id 再 start(tunnel_update 改目标走的就是这条路)。 + let second_port = plane + .start(TunnelBinding { + id: "tun_test".to_string(), + target_url: second.clone(), + access_token: TOKEN.to_string(), + }) + .expect("restart tunnel"); + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + + assert_eq!(plane.running_count(), 1, "同 id 不该留下两个监听"); + assert_ne!(first_port, second_port, "重启会拿到新端口"); + + let client = raw_client(); + let resp = client + .get(format!("http://127.0.0.1:{second_port}/assets/main.js")) + .header("cookie", format!("liveagent_tunnel_tun_test={TOKEN}")) + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 200, "新监听要指向新目标"); +} + +#[tokio::test] +async fn tunnel_rejects_a_token_from_another_tunnel() { + let upstream = spawn_upstream().await; + let (_plane, port) = start_tunnel(&upstream).await; + let client = raw_client(); + let base = format!("http://127.0.0.1:{port}"); + + // 另一条隧道的 cookie 名,值即使正确也不该放行。 + let resp = client + .get(&base) + .header("cookie", format!("liveagent_tunnel_tun_other={TOKEN}")) + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 401); + + // 错的 token 同样拒绝。 + let resp = client + .get(format!("{base}/?t=wrong-token")) + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 401); +} + +/// 重启后隧道要能自己回来。 +/// +/// 这条是 P2-31 用 curl 实测抓到的真实缺口:`main.rs` 建完 state 就直接 serve, +/// 从没调过 `TunnelStore::initialize()`,于是规格好好躺在库里但监听没起—— +/// 单测和契约测试全绿,重启后隧道却全没了。 +/// +/// 用 `TunnelStore` 走完整条路(建 → 落库 → 新 store 恢复),而不是只测数据面: +/// 漏掉的正是这两者之间的那一步。 +#[tokio::test] +async fn tunnels_are_restored_after_a_restart_with_fresh_ports() { + use agent_core::events::EventBus; + use agent_core::services::tunnel::{TunnelCreateInput, TunnelStore}; + + // 隔离 HOME:TunnelStore 落库到 settings 库,不能碰开发者真实数据。 + let dir = std::env::temp_dir().join(format!("agent-backend-tunnel-restart-{}", std::process::id())); + std::fs::create_dir_all(&dir).expect("创建临时 HOME 失败"); + std::env::set_var("HOME", &dir); + + let upstream = spawn_upstream().await; + + // 第一个进程:建一条隧道。 + let first_plane = Arc::new(TunnelDataPlane::new()); + let first = Arc::new(TunnelStore::new( + Arc::new(EventBus::new()), + Arc::clone(&first_plane) as Arc, + )); + first + .create(TunnelCreateInput { + target_url: upstream.clone(), + name: Some("restart-me".to_string()), + ttl_seconds: Some(3600), + project_path_key: None, + }) + .await + .expect("create tunnel"); + let before = first.state().expect("state"); + assert_eq!(before.tunnels.len(), 1); + let old_url = before.tunnels[0].public_url.clone(); + + // 模拟进程退出:数据面随之消失。 + drop(first); + drop(first_plane); + + // 第二个进程:全新 store + 全新数据面,只靠库恢复。 + let second_plane = Arc::new(TunnelDataPlane::new()); + let second = Arc::new(TunnelStore::new( + Arc::new(EventBus::new()), + Arc::clone(&second_plane) as Arc, + )); + second.initialize().await.expect("initialize"); + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + + let after = second.state().expect("state"); + assert_eq!(after.tunnels.len(), 1, "隧道必须从库里恢复"); + let restored = &after.tunnels[0]; + assert_eq!(restored.name, "restart-me"); + assert_eq!(restored.target_url, upstream + "/"); + assert_ne!(restored.port, 0, "恢复的隧道要有真实端口"); + assert_ne!( + restored.public_url, old_url, + "端口与 token 都是新的,链接必然变(见 store.rs 模块文档)" + ); + + // 决定性断言:恢复出来的隧道真的能转发。 + let client = raw_client(); + let resp = client + .get(format!( + "http://127.0.0.1:{}/assets/main.js", + restored.port + )) + .header( + "cookie", + format!( + "liveagent_tunnel_{}={}", + restored.id, + access_token_from_url(&restored.public_url) + ), + ) + .send() + .await + .expect("request"); + assert_eq!(resp.status(), 200, "恢复的隧道必须真的能转发"); + assert_eq!(resp.text().await.expect("body"), "console.log('hi')"); + + // 清理,避免影响同机的其他测试运行。 + second.close(restored.id.clone()).await.expect("close"); +} + +/// 从 `publicUrl` 里取回访问 token。 +fn access_token_from_url(url: &str) -> String { + url.split("?t=").nth(1).unwrap_or_default().to_string() +} diff --git a/crates/agent-core/src/commands/config/settings/db.rs b/crates/agent-core/src/commands/config/settings/db.rs index c2cc425f5..46c39c1e7 100644 --- a/crates/agent-core/src/commands/config/settings/db.rs +++ b/crates/agent-core/src/commands/config/settings/db.rs @@ -6,10 +6,7 @@ fn now_ms() -> i64 { } pub fn config_dir() -> Result { - let home = dirs::home_dir().ok_or_else(|| "无法定位用户目录".to_string())?; - let dir = home.join(format!(".{}", env!("CARGO_PKG_NAME"))); - fs::create_dir_all(&dir).map_err(|e| format!("创建配置目录失败:{e}"))?; - Ok(dir) + crate::storage::app_storage_dir() } fn default_project_dir() -> Result { diff --git a/crates/agent-core/src/commands/history/history_db.rs b/crates/agent-core/src/commands/history/history_db.rs index ae38c0cf7..8cdc4bc77 100644 --- a/crates/agent-core/src/commands/history/history_db.rs +++ b/crates/agent-core/src/commands/history/history_db.rs @@ -1,5 +1,5 @@ use rusqlite::{Connection, OptionalExtension}; -use std::{collections::HashSet, fs, path::PathBuf, sync::Mutex, time::Duration}; +use std::{collections::HashSet, path::PathBuf, sync::Mutex, time::Duration}; const DB_FILENAME: &str = "chat-history.sqlite3"; const HISTORY_DB_SCHEMA_VERSION: i64 = 2; @@ -7,10 +7,7 @@ const HISTORY_DB_SCHEMA_VERSION: i64 = 2; static HISTORY_DB_MIGRATION_LOCK: Mutex<()> = Mutex::new(()); fn history_db_dir() -> Result { - let home = dirs::home_dir().ok_or_else(|| "无法定位用户目录".to_string())?; - let dir = home.join(format!(".{}", env!("CARGO_PKG_NAME"))); - fs::create_dir_all(&dir).map_err(|e| format!("创建历史目录失败:{e}"))?; - Ok(dir) + crate::storage::app_storage_dir() } pub(crate) fn open_connection() -> Result { diff --git a/crates/agent-core/src/commands/integration/mod.rs b/crates/agent-core/src/commands/integration/mod.rs index aadeeb1fe..326bd954b 100644 --- a/crates/agent-core/src/commands/integration/mod.rs +++ b/crates/agent-core/src/commands/integration/mod.rs @@ -1,2 +1,3 @@ pub mod mcp; pub mod memory; +pub mod tunnel; diff --git a/crates/agent-core/src/commands/integration/tunnel.rs b/crates/agent-core/src/commands/integration/tunnel.rs new file mode 100644 index 000000000..255e3d466 --- /dev/null +++ b/crates/agent-core/src/commands/integration/tunnel.rs @@ -0,0 +1,73 @@ +//! 隧道命令。名字从 `gateway_tunnel_*` 改为 `tunnel_*`——新架构里没有 gateway。 +//! +//! 五个命令都很薄:参数校验和状态变更在 `services::tunnel::store`, +//! 这里只做「命令 → store 调用」的转发,外加 check 的异步探活编排。 + +use std::sync::Arc; + +use crate::services::tunnel::{ + probe, TunnelCreateInput, TunnelError, TunnelState, TunnelStore, TunnelUpdateInput, +}; + +/// 当前隧道快照。 +pub fn tunnel_state(store: &Arc) -> Result { + store.state() +} + +/// 建隧道。成功后立刻探一次活,让面板不用等 15s 节流窗口就有健康状态。 +pub async fn tunnel_create( + input: TunnelCreateInput, + store: &Arc, +) -> Result<(), TunnelError> { + let id = store.create(input).await?; + spawn_probe(store, Some(vec![id])); + Ok(()) +} + +/// 改隧道。 +pub async fn tunnel_update( + input: TunnelUpdateInput, + store: &Arc, +) -> Result<(), TunnelError> { + let id = store.update(input).await?; + spawn_probe(store, Some(vec![id])); + Ok(()) +} + +/// 关隧道。 +pub async fn tunnel_close( + tunnel_id: String, + store: &Arc, +) -> Result<(), TunnelError> { + store.close(tunnel_id).await?; + Ok(()) +} + +/// 触发探活。`tunnel_id` 为 None 表示探全部。 +/// +/// 显式 check 绕过节流(用户点了「检查」就该真去检查),且**同步等待**结果—— +/// 旧实现是异步的,前端只能靠 sleep 2.5s 再拉快照来碰运气。同步返回后快照 +/// 一定是新的,那个 sleep 可以删掉。 +pub async fn tunnel_check( + tunnel_id: Option, + store: &Arc, +) -> Result<(), TunnelError> { + let tunnel_id = tunnel_id + .map(|id| id.trim().to_string()) + .filter(|id| !id.is_empty()); + if let Some(id) = tunnel_id.as_ref() { + if !store.exists(id).map_err(TunnelError::internal)? { + return Err(TunnelError::not_found()); + } + } + probe::run_probes(store, tunnel_id.map(|id| vec![id]), true).await; + Ok(()) +} + +/// 后台探活:不阻塞变更命令的返回。 +fn spawn_probe(store: &Arc, tunnel_ids: Option>) { + let store = Arc::clone(store); + tokio::spawn(async move { + probe::run_probes(&store, tunnel_ids, true).await; + }); +} diff --git a/crates/agent-core/src/commands/mod.rs b/crates/agent-core/src/commands/mod.rs index 0dc2d6ec8..23c1d12bc 100644 --- a/crates/agent-core/src/commands/mod.rs +++ b/crates/agent-core/src/commands/mod.rs @@ -22,6 +22,7 @@ pub use history_commands::subagent_store; pub use integration_commands::mcp; pub use integration_commands::memory; +pub use integration_commands::tunnel; pub use runtime_commands::process; pub use runtime_commands::sftp; diff --git a/crates/agent-core/src/lib.rs b/crates/agent-core/src/lib.rs index a86e29195..d773e68d8 100644 --- a/crates/agent-core/src/lib.rs +++ b/crates/agent-core/src/lib.rs @@ -17,6 +17,7 @@ pub mod commands; pub mod events; pub mod runtime; pub mod services; +pub mod storage; use std::sync::OnceLock; diff --git a/crates/agent-core/src/runtime/managed_process.rs b/crates/agent-core/src/runtime/managed_process.rs index 1a42fd903..1bccbb72e 100644 --- a/crates/agent-core/src/runtime/managed_process.rs +++ b/crates/agent-core/src/runtime/managed_process.rs @@ -143,11 +143,7 @@ fn now_ms() -> u128 { } fn app_storage_dir() -> Result { - let home = - dirs::home_dir().ok_or_else(|| "Failed to locate the user home directory".to_string())?; - let dir = home.join(format!(".{}", env!("CARGO_PKG_NAME"))); - fs::create_dir_all(&dir).map_err(|err| format!("Failed to create app storage dir: {err}"))?; - Ok(dir) + crate::storage::app_storage_dir() } fn process_log_dir() -> Result { diff --git a/crates/agent-core/src/services/mod.rs b/crates/agent-core/src/services/mod.rs index 97cc38ff0..181b97d09 100644 --- a/crates/agent-core/src/services/mod.rs +++ b/crates/agent-core/src/services/mod.rs @@ -6,4 +6,5 @@ pub mod provider_models; pub mod provider_usage; pub mod skills; pub mod system_proxy; +pub mod tunnel; pub mod workspace_watch; diff --git a/crates/agent-core/src/services/skills/paths.rs b/crates/agent-core/src/services/skills/paths.rs index 6fa3b1a78..0d0a0fcb0 100644 --- a/crates/agent-core/src/services/skills/paths.rs +++ b/crates/agent-core/src/services/skills/paths.rs @@ -6,12 +6,7 @@ use std::path::{Component, Path, PathBuf}; const MAX_SKILL_NAME_LENGTH: usize = 64; pub fn app_storage_dir() -> Result { - let home = - dirs::home_dir().ok_or_else(|| "Failed to locate the user home directory".to_string())?; - let dir = home.join(format!(".{}", env!("CARGO_PKG_NAME"))); - fs::create_dir_all(&dir) - .map_err(|e| format!("Failed to create the application directory: {e}"))?; - Ok(dir) + crate::storage::app_storage_dir() } pub fn skills_root_dir() -> Result { diff --git a/crates/agent-core/src/services/tunnel/mod.rs b/crates/agent-core/src/services/tunnel/mod.rs new file mode 100644 index 000000000..8a6fda369 --- /dev/null +++ b/crates/agent-core/src/services/tunnel/mod.rs @@ -0,0 +1,287 @@ +//! 本机隧道:把工作机上的 dev server 暴露成一个可访问的 HTTP 端口。 +//! +//! ## 与旧实现的区别 +//! +//! 旧实现里隧道是「桌面端 → 公网 Gateway」的注册协议:端口注册到 Gateway, +//! Gateway 开 `/t/{slug}` 公开反代,把每个 HTTP 请求/WS 帧拆成 protobuf 帧 +//! 跨中继来回搬,再重写 HTML 属性、CSS `url()`、注入 shim、改 CSP +//! (`tunnel_rewrite.go`,约 1000 行)才能让子路径下的页面正常工作。 +//! +//! 新架构里后端**就在**那台工作机上,且自己就是网络入口。于是: +//! +//! | 旧 | 新 | +//! |---|---| +//! | slug 注册协议 | 没有。id 本地生成,不需要跟谁协商 | +//! | 跨中继 protobuf 分帧 | 没有。后端直接 TCP 连 `localhost:5173` | +//! | 子路径 `/t/{slug}/` + 1000 行重写 | **一隧道一端口,路径 1:1**,重写代码为 0 | +//! | 公开无认证入口 | 首访 token → cookie(见 `TunnelAccess`) | +//! +//! **一隧道一端口是「消除特殊情况」而不是省事**:挂子路径时 dev server 吐的 +//! ` - - diff --git a/crates/agent-gateway/web/package.json b/crates/agent-gateway/web/package.json deleted file mode 100644 index 6f90a755b..000000000 --- a/crates/agent-gateway/web/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@liveagent/gateway-webui", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "test": "node --test ../test/webui/*.test.mjs test/*.test.mjs", - "lint": "biome check src/", - "format": "biome format --write src/", - "lint:fix": "biome check --write src/" - }, - "dependencies": { - "@base-ui/react": "^1.6.0", - "@bufbuild/protobuf": "^2.12.1", - "@earendil-works/pi-ai": "^0.80.6", - "@git-diff-view/file": "^0.1.3", - "@git-diff-view/react": "^0.1.3", - "@iconify-json/gravity-ui": "^1.2.12", - "@sinclair/typebox": "^0.34.49", - "@streamdown/cjk": "^1.0.3", - "@streamdown/code": "^1.1.1", - "@streamdown/math": "^1.0.2", - "@streamdown/mermaid": "^1.0.2", - "@tanstack/react-virtual": "^3.14.6", - "@xterm/addon-fit": "^0.11.0", - "@xterm/xterm": "^6.0.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "docx-preview": "^0.4.0", - "katex": "^0.17.0", - "monaco-editor": "^0.55.1", - "react": "^19.2.4", - "react-complex-tree": "^2.6.1", - "react-dom": "^19.2.4", - "remark-breaks": "^4.0.0", - "streamdown": "^2.5.0", - "tailwind-merge": "^3.5.0", - "xlsx": "^0.18.5", - "yet-another-react-lightbox": "^3.31.0" - }, - "devDependencies": { - "@biomejs/biome": "^2.4.15", - "@bufbuild/protoc-gen-es": "2.12.1", - "@iconify-json/logos": "^1.2.11", - "@iconify-json/lucide": "^1.2.108", - "@iconify-json/material-icon-theme": "1.2.67", - "@svgr/core": "^8.1.0", - "@svgr/plugin-jsx": "^8.1.0", - "@tailwindcss/postcss": "4.2.2", - "@tailwindcss/typography": "^0.5.19", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", - "postcss": "^8.5.8", - "tailwindcss": "4.2.2", - "typescript": "~6.0.2", - "unplugin-icons": "^23.0.1", - "vite": "^8.0.5" - } -} diff --git a/crates/agent-gateway/web/pnpm-lock.yaml b/crates/agent-gateway/web/pnpm-lock.yaml deleted file mode 100644 index 3dfdee44d..000000000 --- a/crates/agent-gateway/web/pnpm-lock.yaml +++ /dev/null @@ -1,5479 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@base-ui/react': - specifier: ^1.6.0 - version: 1.6.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@bufbuild/protobuf': - specifier: ^2.12.1 - version: 2.12.1 - '@earendil-works/pi-ai': - specifier: ^0.80.6 - version: 0.80.6(ws@8.21.0)(zod@4.4.3) - '@git-diff-view/file': - specifier: ^0.1.3 - version: 0.1.3 - '@git-diff-view/react': - specifier: ^0.1.3 - version: 0.1.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@iconify-json/gravity-ui': - specifier: ^1.2.12 - version: 1.2.12 - '@sinclair/typebox': - specifier: ^0.34.49 - version: 0.34.49 - '@streamdown/cjk': - specifier: ^1.0.3 - version: 1.0.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.5)(unified@11.0.5) - '@streamdown/code': - specifier: ^1.1.1 - version: 1.1.1(react@19.2.5) - '@streamdown/math': - specifier: ^1.0.2 - version: 1.0.2(react@19.2.5) - '@streamdown/mermaid': - specifier: ^1.0.2 - version: 1.0.2(react@19.2.5) - '@tanstack/react-virtual': - specifier: ^3.14.6 - version: 3.14.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@xterm/addon-fit': - specifier: ^0.11.0 - version: 0.11.0 - '@xterm/xterm': - specifier: ^6.0.0 - version: 6.0.0 - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - docx-preview: - specifier: ^0.4.0 - version: 0.4.0 - katex: - specifier: ^0.17.0 - version: 0.17.0 - monaco-editor: - specifier: ^0.55.1 - version: 0.55.1 - react: - specifier: ^19.2.4 - version: 19.2.5 - react-complex-tree: - specifier: ^2.6.1 - version: 2.6.2(react@19.2.5) - react-dom: - specifier: ^19.2.4 - version: 19.2.5(react@19.2.5) - remark-breaks: - specifier: ^4.0.0 - version: 4.0.0 - streamdown: - specifier: ^2.5.0 - version: 2.5.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - tailwind-merge: - specifier: ^3.5.0 - version: 3.5.0 - xlsx: - specifier: ^0.18.5 - version: 0.18.5 - yet-another-react-lightbox: - specifier: ^3.31.0 - version: 3.31.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - devDependencies: - '@biomejs/biome': - specifier: ^2.4.15 - version: 2.5.2 - '@bufbuild/protoc-gen-es': - specifier: 2.12.1 - version: 2.12.1(@bufbuild/protobuf@2.12.1) - '@iconify-json/logos': - specifier: ^1.2.11 - version: 1.2.11 - '@iconify-json/lucide': - specifier: ^1.2.108 - version: 1.2.108 - '@iconify-json/material-icon-theme': - specifier: 1.2.67 - version: 1.2.67 - '@svgr/core': - specifier: ^8.1.0 - version: 8.1.0(typescript@6.0.2) - '@svgr/plugin-jsx': - specifier: ^8.1.0 - version: 8.1.0(@svgr/core@8.1.0(typescript@6.0.2)) - '@tailwindcss/postcss': - specifier: 4.2.2 - version: 4.2.2 - '@tailwindcss/typography': - specifier: ^0.5.19 - version: 0.5.20(tailwindcss@4.2.2) - '@types/react': - specifier: ^19.2.14 - version: 19.2.14 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.14) - '@vitejs/plugin-react': - specifier: ^6.0.1 - version: 6.0.1(vite@8.0.8(@types/node@26.1.1)(jiti@2.6.1)) - postcss: - specifier: ^8.5.8 - version: 8.5.9 - tailwindcss: - specifier: 4.2.2 - version: 4.2.2 - typescript: - specifier: ~6.0.2 - version: 6.0.2 - unplugin-icons: - specifier: ^23.0.1 - version: 23.0.1(@svgr/core@8.1.0(typescript@6.0.2)) - vite: - specifier: ^8.0.5 - version: 8.0.8(@types/node@26.1.1)(jiti@2.6.1) - -packages: - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@antfu/install-pkg@1.1.0': - resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - - '@anthropic-ai/sdk@0.91.1': - resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} - hasBin: true - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - peerDependenciesMeta: - zod: - optional: true - - '@aws-crypto/sha256-browser@5.2.0': - resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - - '@aws-crypto/sha256-js@5.2.0': - resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/supports-web-crypto@5.2.0': - resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - - '@aws-crypto/util@5.2.0': - resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - - '@aws-sdk/client-bedrock-runtime@3.1048.0': - resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/core@3.975.1': - resolution: {integrity: sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-env@3.972.57': - resolution: {integrity: sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-http@3.972.59': - resolution: {integrity: sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-ini@3.973.1': - resolution: {integrity: sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-login@3.972.63': - resolution: {integrity: sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-node@3.972.66': - resolution: {integrity: sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-process@3.972.57': - resolution: {integrity: sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-sso@3.973.1': - resolution: {integrity: sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.972.63': - resolution: {integrity: sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/eventstream-handler-node@3.972.26': - resolution: {integrity: sha512-RE1fu7Nn05vG0EUJM+8Sde2GFecC658WGaC/asPzLF6K4x3H5ZaDBcQtHRE67Gdgb1VZpyUUliYejHFK1qt0Uw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-eventstream@3.972.22': - resolution: {integrity: sha512-jtkgmhevnpzC1WeS+Y/sgymYbaQ6qg7pVOUl5cUT/8MiLptqrtnXQlNV80m+j2WIx5MIL7kVHIZNxxcK2tfUEQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-websocket@3.972.39': - resolution: {integrity: sha512-CS1spxRSezmTmI3PD+3Xrnp6KryTSEz0EefA8u6uGd0s2I0uXseWHALDI/03Wi0IUczXNWo2QrZEaHDuJNby/Q==} - engines: {node: '>= 14.0.0'} - - '@aws-sdk/nested-clients@3.997.31': - resolution: {integrity: sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.996.39': - resolution: {integrity: sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/token-providers@3.1048.0': - resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/token-providers@3.1083.0': - resolution: {integrity: sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/types@3.974.0': - resolution: {integrity: sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-locate-window@3.965.8': - resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/xml-builder@3.972.34': - resolution: {integrity: sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==} - engines: {node: '>=20.0.0'} - - '@aws/lambda-invoke-store@0.3.0': - resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} - engines: {node: '>=18.0.0'} - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.3': - resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.2': - resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@base-ui/react@1.6.0': - resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@date-fns/tz': ^1.2.0 - '@types/react': ^17 || ^18 || ^19 - date-fns: ^4.0.0 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@date-fns/tz': - optional: true - '@types/react': - optional: true - date-fns: - optional: true - - '@base-ui/utils@0.3.1': - resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - - '@biomejs/biome@2.5.2': - resolution: {integrity: sha512-VQ3RCqr7JmDIX+w6stWYl+g/3bYofN3q2wDBHUKKc/c7i5QWrFKFBZYCYPWTE6agsUPMIZZe6/CMmVUfUAhkKA==} - engines: {node: '>=14.21.3'} - hasBin: true - - '@biomejs/cli-darwin-arm64@2.5.2': - resolution: {integrity: sha512-e7P3P7EkwFc/KiX2AHw4YDLIBOMfG9CPCAwy52k5Bp0dfhkozx9hf6wCmIr2QeXy2XeccJ3V/Sg+hDmzYEqxSg==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [darwin] - - '@biomejs/cli-darwin-x64@2.5.2': - resolution: {integrity: sha512-ymzMvjC1Jg0b9K0D26ZdARqFQXs7MocfLC5FOCGfkC0Ss+ACUJkX5364ZM5nT4NLZanHRZNVrZEy+Ibwcvux/g==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [darwin] - - '@biomejs/cli-linux-arm64-musl@2.5.2': - resolution: {integrity: sha512-w+ANG0ZvTu9IeEg9QnstoOnk6L0fpwJifW6aHR18+cb5Z39bkANItYjAfMrnvce5tmMK+IQ6nPX7/kQFdam5iw==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@biomejs/cli-linux-arm64@2.5.2': - resolution: {integrity: sha512-t7sseOmqND57uUWTwlawU6BYj+J06T/9EkydzBhkrgw/FK3QVhjU2wsJR0frljrKZ0/I8A/rYw7284QgqjQfIQ==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@biomejs/cli-linux-x64-musl@2.5.2': - resolution: {integrity: sha512-VArNLAzND063tF+XY0yPyM+DyahpzOMzOAvb7qs259nhjJWRjvjZdssuA+Rfl+l07+NOesKZ0Xu2yFrXyBMtzw==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@biomejs/cli-linux-x64@2.5.2': - resolution: {integrity: sha512-M/lOZrewzTCRDINbjhQ1gYYru37KlD3kJBQwwKCG0ckz5E9IZwIoJ3X0wBwRXA+yBDIwWUuPBHS67HzJY4dTfA==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@biomejs/cli-win32-arm64@2.5.2': - resolution: {integrity: sha512-kbjFFKyZlzYnAuw7sRy5qDoFG6zrP40UK08oPQsWK0ct3NMnGSt+Bs1iviEEyEIP57N5MrykGXdO/wRiaR4lww==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [win32] - - '@biomejs/cli-win32-x64@2.5.2': - resolution: {integrity: sha512-4InchVpdVmdkkkgjQqKpgvyu+VPnoF/7RPSw5YATgEVpt2j72wcCAeV5TwaE9ZGJUZWZn7v2CwSAj6CrMJEx8A==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [win32] - - '@braintree/sanitize-url@7.1.2': - resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - - '@bufbuild/protobuf@2.12.1': - resolution: {integrity: sha512-BvAMfS6LrgZiryOAZ4pBYucu4wG/Ei/9o9DZ9akbREnMLbPJiom2i8b9C8IsKErQoiKqVhrerzt3kOT/RrzLHg==} - - '@bufbuild/protoc-gen-es@2.12.1': - resolution: {integrity: sha512-SWa7XvRYRouMo+vBQmpNFZ+ZEqQ8AC0LpL4QWAo1gvstLhFh/Y7Nf/a+MK7ZxDq5LZSThwfk974L1sFxO3OaGw==} - engines: {node: '>=20'} - hasBin: true - peerDependencies: - '@bufbuild/protobuf': 2.12.1 - peerDependenciesMeta: - '@bufbuild/protobuf': - optional: true - - '@bufbuild/protoplugin@2.12.1': - resolution: {integrity: sha512-PY58KxQVAD1BnnKtStOctsMoegEVGfBnY5AOqVQOIu711nA13oYtTqJM8df5lUQg2J1DR3XxUXptE+fWX5oLdA==} - - '@chevrotain/cst-dts-gen@12.0.0': - resolution: {integrity: sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==} - - '@chevrotain/gast@12.0.0': - resolution: {integrity: sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==} - - '@chevrotain/regexp-to-ast@12.0.0': - resolution: {integrity: sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==} - - '@chevrotain/types@12.0.0': - resolution: {integrity: sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==} - - '@chevrotain/utils@12.0.0': - resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==} - - '@earendil-works/pi-ai@0.80.6': - resolution: {integrity: sha512-7xfLk8sANBp+bpPEbjoOZTbPxsa+++b1JXAoSJsNa3vbs9AHHEclmvg54XLQcxH+fuwaeti/g2jeIfJ+mVYLpA==} - engines: {node: '>=22.19.0'} - hasBin: true - - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - - '@emnapi/runtime@1.9.2': - resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - - '@git-diff-view/core@0.1.5': - resolution: {integrity: sha512-xvMZnD0k8BlmP3RqSocQrPVVzgUxr8KEl0wT5TTIm9c2B5tODHat69zidUvFWIoaYvHwDu4PfVcdGj4c+PrL6g==} - - '@git-diff-view/file@0.1.3': - resolution: {integrity: sha512-ECafXpjH543lLTWtv7CaPrLlQEld3bBplPCUlly4uzO8Usbk4qqdtVJLmOiHIHMGXYMOIO4Ic5YSwseZnLgWUg==} - - '@git-diff-view/lowlight@0.1.5': - resolution: {integrity: sha512-6sxEJcGIUHzJc6Rx1kQ1TqAi0g6ABiGG8z5xhlLHWcQwDmGzB2wTISbyRxNXTwQTwJIMX5u5Cj+2xl+a84APxQ==} - - '@git-diff-view/react@0.1.3': - resolution: {integrity: sha512-AKfkGGx0ulXsNE8ITfsC7fDRFT3pPlQJjwGlEq7+yC1D95E5mSGP/9GHLCEpuTXmcNv6D6h+4j/Lq2/DtgaQvg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@google/genai@1.52.0': - resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} - engines: {node: '>=20.0.0'} - peerDependencies: - '@modelcontextprotocol/sdk': ^1.25.2 - peerDependenciesMeta: - '@modelcontextprotocol/sdk': - optional: true - - '@iconify-json/gravity-ui@1.2.12': - resolution: {integrity: sha512-/66CorNnUoFs66nb8FJ+ZPq6Zog9NwR13ml90qijI0Hj3bVqQIdiWZ9npSyNWRgJe452LFmWZrdWQONYBbJ7XA==} - - '@iconify-json/logos@1.2.11': - resolution: {integrity: sha512-fOo4pGEatuyuCFNL+cwquYMa2Im0oJHRHV7lt/Qqs5Ode/lPImHCQcfTtPzZj7qYMPb/h8YHN3TG54uEowrjNQ==} - - '@iconify-json/lucide@1.2.108': - resolution: {integrity: sha512-jnmMx7xxShfsKeNNJhn47IKj3gD/AbRz+poKLIPn4rSIXw+yVbXCfUBXza/Jo9YIEEFajBk6Zayet8DqGCvX6w==} - - '@iconify-json/material-icon-theme@1.2.67': - resolution: {integrity: sha512-SouqLxahwVOuIVqED8Spl1wSy3DM7sYwNOPxFW7Eh4hVNqR4L4zTkEXFP4V6bQegkhl9kFWbNGlYKDFjXh92iQ==} - - '@iconify/types@2.0.0': - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - - '@iconify/utils@3.1.0': - resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@mermaid-js/parser@1.1.0': - resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} - - '@mistralai/mistralai@2.2.6': - resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - - '@napi-rs/wasm-runtime@1.1.3': - resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - - '@opentelemetry/api@1.9.0': - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} - engines: {node: '>=8.0.0'} - - '@opentelemetry/semantic-conventions@1.42.0': - resolution: {integrity: sha512-icc5xCzndZfhuJMy5oqk5AvloWquR7jtae74qzpkKkhGp8BivK+oCcEXgGnjCdTfp8hA44l+w8gE8yYJbocJJw==} - engines: {node: '>=14'} - - '@oxc-project/types@0.124.0': - resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.5': - resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} - - '@protobufjs/eventemitter@1.1.1': - resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} - - '@protobufjs/fetch@1.1.1': - resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - - '@protobufjs/utf8@1.1.2': - resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} - - '@rolldown/binding-android-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.15': - resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.15': - resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': - resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': - resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': - resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': - resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': - resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': - resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.0-rc.15': - resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} - - '@rolldown/pluginutils@1.0.0-rc.7': - resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - - '@shikijs/core@3.23.0': - resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} - - '@shikijs/engine-javascript@3.23.0': - resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} - - '@shikijs/engine-oniguruma@3.23.0': - resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} - - '@shikijs/langs@3.23.0': - resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} - - '@shikijs/themes@3.23.0': - resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} - - '@shikijs/types@3.23.0': - resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} - - '@shikijs/vscode-textmate@10.0.2': - resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - - '@sinclair/typebox@0.34.49': - resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} - - '@smithy/core@3.29.2': - resolution: {integrity: sha512-DXUk6yU0C1Q1tYvJh1VCtl8QOBcSoZpKwjTPkxT6A4MUQYHvgeKGByL8mrEdxnvhdf9nq5GyzmRb5n/vPgu3Lw==} - engines: {node: '>=18.0.0'} - - '@smithy/credential-provider-imds@4.4.7': - resolution: {integrity: sha512-UEMLOoA0Fl4uYBxh6l0uN0H6EJe/A89OGeDNTteQeXpJ20BcpfIr4wlCY9pel1jEAUHAxaYwuqrYlrKdXE1GKQ==} - engines: {node: '>=18.0.0'} - - '@smithy/fetch-http-handler@5.6.4': - resolution: {integrity: sha512-psnst7NZWdAEvJvyW8YZEE7xNVMyLrQFfHtyrVFrxNyy+dKWkQ+rqC6oI5ZhxThpUy9RSfEshgm34zqbOxzsRw==} - engines: {node: '>=18.0.0'} - - '@smithy/is-array-buffer@2.2.0': - resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} - engines: {node: '>=14.0.0'} - - '@smithy/node-http-handler@4.7.3': - resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} - engines: {node: '>=18.0.0'} - - '@smithy/node-http-handler@4.9.4': - resolution: {integrity: sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA==} - engines: {node: '>=18.0.0'} - - '@smithy/signature-v4@5.6.3': - resolution: {integrity: sha512-8qVKKzqh7naF27ePmx0SkUfnGP/wBI9dyaeAmhHvopnbIlItUAmB/e6PkPCU3rRb2v9BY8D4EZXSoydSibatvw==} - engines: {node: '>=18.0.0'} - - '@smithy/types@4.16.0': - resolution: {integrity: sha512-aVUabzlBBmY0PfvVgLKQSOGFIL5/7R54JE3uD9a5Ay/jSED61SkuAcCYENNXJzYUvJ1NPrWO0P+rAXHCkbBUKw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-buffer-from@2.2.0': - resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.0.0'} - - '@streamdown/cjk@1.0.3': - resolution: {integrity: sha512-WRg8HR/gHbBoTgsMd91OKFUClIoDcEFVofJvluvEAyjx3KpU0aGgD9tGDqHkHj14ShoMSkX0IYetWGegTcwIJw==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - - '@streamdown/code@1.1.1': - resolution: {integrity: sha512-i7HTNuDgZWb+VdrNVOam9gQhIc5MSSDXKWXgbUrn/4vSRaSMM+Rtl10MQj4wLWPNpF7p80waJsAqFP8HZfb0Jg==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - - '@streamdown/math@1.0.2': - resolution: {integrity: sha512-r8Ur9/lBuFnzZAFdEWrLUF2s/gRwRRRwruqltdZibyjbCBnuW7SJbFm26nXqvpJPW/gzpBUMrBVBzd88z05D5g==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - - '@streamdown/mermaid@1.0.2': - resolution: {integrity: sha512-Fr/4sBWnAeSnxM3PcrV/+DiZe5oPMq9gOkUIAH7ZauJeuwrZ/DVzD4g0zlav6AH0axh2m/sOfrfLtY5aLT7niw==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - - '@svgr/babel-plugin-add-jsx-attribute@8.0.0': - resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': - resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': - resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': - resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-svg-dynamic-title@8.0.0': - resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-svg-em-dimensions@8.0.0': - resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-transform-react-native-svg@8.1.0': - resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-transform-svg-component@8.0.0': - resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-preset@8.1.0': - resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/core@8.1.0': - resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} - engines: {node: '>=14'} - - '@svgr/hast-util-to-babel-ast@8.0.0': - resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} - engines: {node: '>=14'} - - '@svgr/plugin-jsx@8.1.0': - resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' - - '@tailwindcss/node@4.2.2': - resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} - - '@tailwindcss/oxide-android-arm64@4.2.2': - resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.2.2': - resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.2.2': - resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.2.2': - resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} - engines: {node: '>= 20'} - - '@tailwindcss/postcss@4.2.2': - resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} - - '@tailwindcss/typography@0.5.20': - resolution: {integrity: sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==} - peerDependencies: - tailwindcss: '>=3.0.0 || >=4.0.0 || insiders' - - '@tanstack/react-virtual@3.14.6': - resolution: {integrity: sha512-4+Uq8m0/gzO4kMCHUEpTtGX1RnONK0C+g88b2ltwPMWUBiaVarBuWKoPJaz7gj1cKCVRAdyu+U8GcKhwCc2beA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/virtual-core@3.17.4': - resolution: {integrity: sha512-nGm5KteqxasUdThLc2izl6dHUqLv0LQj7Nuyo5gYalTPf/U8a9ermvsl7reT+6ioBW1l8WfpP/mcU338nLXpqw==} - - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - - '@types/d3-array@3.2.2': - resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} - - '@types/d3-axis@3.0.6': - resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - - '@types/d3-brush@3.0.6': - resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - - '@types/d3-chord@3.0.6': - resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - - '@types/d3-color@3.1.3': - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - - '@types/d3-contour@3.0.6': - resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - - '@types/d3-delaunay@6.0.4': - resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - - '@types/d3-dispatch@3.0.7': - resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} - - '@types/d3-drag@3.0.7': - resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} - - '@types/d3-dsv@3.0.7': - resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} - - '@types/d3-ease@3.0.2': - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - - '@types/d3-fetch@3.0.7': - resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} - - '@types/d3-force@3.0.10': - resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} - - '@types/d3-format@3.0.4': - resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - - '@types/d3-geo@3.1.0': - resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} - - '@types/d3-hierarchy@3.1.7': - resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} - - '@types/d3-interpolate@3.0.4': - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - - '@types/d3-path@3.1.1': - resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} - - '@types/d3-polygon@3.0.2': - resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} - - '@types/d3-quadtree@3.0.6': - resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - - '@types/d3-random@3.0.3': - resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} - - '@types/d3-scale-chromatic@3.1.0': - resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} - - '@types/d3-scale@4.0.9': - resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} - - '@types/d3-selection@3.0.11': - resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} - - '@types/d3-shape@3.1.8': - resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} - - '@types/d3-time-format@4.0.3': - resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} - - '@types/d3-time@3.0.4': - resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} - - '@types/d3-timer@3.0.2': - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - - '@types/d3-transition@3.0.9': - resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} - - '@types/d3-zoom@3.0.8': - resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - - '@types/d3@7.4.3': - resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - - '@types/debug@4.1.13': - resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/geojson@7946.0.16': - resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - - '@types/katex@0.16.8': - resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@26.1.1': - resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} - - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - - '@types/retry@0.12.0': - resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@typescript/vfs@1.6.4': - resolution: {integrity: sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==} - peerDependencies: - typescript: '*' - - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher - - '@upsetjs/venn.js@2.0.0': - resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} - - '@vitejs/plugin-react@6.0.1': - resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - vite: ^8.0.0 - peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: - optional: true - - '@vue/reactivity@3.5.35': - resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==} - - '@vue/shared@3.5.35': - resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==} - - '@xterm/addon-fit@0.11.0': - resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} - - '@xterm/xterm@6.0.0': - resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - adler-32@1.3.1: - resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} - engines: {node: '>=0.8'} - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.10.30: - resolution: {integrity: sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg==} - engines: {node: '>=6.0.0'} - hasBin: true - - bignumber.js@9.3.1: - resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - - bowser@2.14.1: - resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001793: - resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - cfb@1.2.2: - resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} - engines: {node: '>=0.8'} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - - chevrotain-allstar@0.4.1: - resolution: {integrity: sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==} - peerDependencies: - chevrotain: ^12.0.0 - - chevrotain@12.0.0: - resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==} - engines: {node: '>=22.0.0'} - - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - codepage@1.15.0: - resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} - engines: {node: '>=0.8'} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confbox@0.2.4: - resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cose-base@1.0.3: - resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} - - cose-base@2.2.0: - resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - - crc-32@1.2.2: - resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} - engines: {node: '>=0.8'} - hasBin: true - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - cytoscape-cose-bilkent@4.1.0: - resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape-fcose@2.2.0: - resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape@3.33.2: - resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==} - engines: {node: '>=0.10'} - - d3-array@2.12.1: - resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} - - d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} - - d3-axis@3.0.0: - resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} - engines: {node: '>=12'} - - d3-brush@3.0.0: - resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} - engines: {node: '>=12'} - - d3-chord@3.0.1: - resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} - engines: {node: '>=12'} - - d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - - d3-contour@4.0.2: - resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} - engines: {node: '>=12'} - - d3-delaunay@6.0.4: - resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} - engines: {node: '>=12'} - - d3-dispatch@3.0.1: - resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} - engines: {node: '>=12'} - - d3-drag@3.0.0: - resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} - engines: {node: '>=12'} - - d3-dsv@3.0.1: - resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} - engines: {node: '>=12'} - hasBin: true - - d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - - d3-fetch@3.0.1: - resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} - engines: {node: '>=12'} - - d3-force@3.0.0: - resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} - engines: {node: '>=12'} - - d3-format@3.1.2: - resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} - engines: {node: '>=12'} - - d3-geo@3.1.1: - resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} - engines: {node: '>=12'} - - d3-hierarchy@3.1.2: - resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} - engines: {node: '>=12'} - - d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} - - d3-path@1.0.9: - resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} - - d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - - d3-polygon@3.0.1: - resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} - engines: {node: '>=12'} - - d3-quadtree@3.0.1: - resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} - engines: {node: '>=12'} - - d3-random@3.0.1: - resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} - engines: {node: '>=12'} - - d3-sankey@0.12.3: - resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} - - d3-scale-chromatic@3.1.0: - resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} - engines: {node: '>=12'} - - d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} - - d3-selection@3.0.0: - resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} - engines: {node: '>=12'} - - d3-shape@1.3.7: - resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} - - d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - - d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} - - d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} - - d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - - d3-transition@3.0.1: - resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} - engines: {node: '>=12'} - peerDependencies: - d3-selection: 2 - 3 - - d3-zoom@3.0.0: - resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} - engines: {node: '>=12'} - - d3@7.9.0: - resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} - engines: {node: '>=12'} - - dagre-d3-es@7.0.14: - resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} - - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - - dayjs@1.11.20: - resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - - delaunator@5.1.0: - resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - diff@8.0.4: - resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} - engines: {node: '>=0.3.1'} - - docx-preview@0.4.0: - resolution: {integrity: sha512-OdKtE/uj3M4RfGarLkGjahUzRg8/kBp0Sraj1r1NAY1tp/sTpHOBqDrzVf9onMBt9vxP6SdQ6bpLCUCsFwjgcA==} - - dompurify@3.2.7: - resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} - - dompurify@3.3.3: - resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} - - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - - electron-to-chromium@1.5.357: - resolution: {integrity: sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g==} - - enhanced-resolve@5.20.1: - resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} - engines: {node: '>=10.13.0'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - - frac@1.1.2: - resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} - engines: {node: '>=0.8'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - gaxios@7.2.0: - resolution: {integrity: sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==} - engines: {node: '>=18'} - - gcp-metadata@8.1.2: - resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} - engines: {node: '>=18'} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} - engines: {node: '>=18'} - - google-auth-library@10.9.0: - resolution: {integrity: sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==} - engines: {node: '>=18'} - - google-logging-utils@1.1.3: - resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} - engines: {node: '>=14'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - hachure-fill@0.5.2: - resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} - - hast-util-from-dom@5.0.1: - resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} - - hast-util-from-html-isomorphic@2.0.0: - resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} - - hast-util-from-html@2.0.3: - resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} - - hast-util-from-parse5@8.0.3: - resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} - - hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} - - hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - - hast-util-raw@9.1.0: - resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} - - hast-util-sanitize@5.0.2: - resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} - - hast-util-to-html@9.0.5: - resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} - - hast-util-to-jsx-runtime@2.3.6: - resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - - hast-util-to-parse5@8.0.1: - resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} - - hast-util-to-text@4.0.2: - resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} - - hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - - hastscript@9.0.1: - resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - - highlight.js@11.11.1: - resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} - engines: {node: '>=12.0.0'} - - html-url-attributes@3.0.1: - resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - - html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - inline-style-parser@0.2.7: - resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} - - internmap@1.0.1: - resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} - - internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} - hasBin: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-bigint@1.0.0: - resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-to-ts@3.1.1: - resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} - engines: {node: '>=16'} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - - jwa@2.0.1: - resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - - jws@4.0.1: - resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - - katex@0.16.45: - resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} - hasBin: true - - katex@0.17.0: - resolution: {integrity: sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw==} - hasBin: true - - khroma@2.1.0: - resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - - langium@4.2.2: - resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==} - engines: {node: '>=20.10.0', npm: '>=10.2.3'} - - layout-base@1.0.2: - resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} - - layout-base@2.0.1: - resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} - engines: {node: '>=14'} - - lodash-es@4.18.1: - resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} - - long@5.3.2: - resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - lowlight@3.3.0: - resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - markdown-table@3.0.4: - resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - - marked@14.0.0: - resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} - engines: {node: '>= 18'} - hasBin: true - - marked@16.4.2: - resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} - engines: {node: '>= 20'} - hasBin: true - - marked@17.0.6: - resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} - engines: {node: '>= 20'} - hasBin: true - - mdast-util-find-and-replace@3.0.2: - resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - - mdast-util-from-markdown@2.0.3: - resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - - mdast-util-gfm-footnote@2.1.0: - resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} - - mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} - - mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - - mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - - mdast-util-gfm@3.1.0: - resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} - - mdast-util-math@3.0.0: - resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} - - mdast-util-mdx-expression@2.0.1: - resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} - - mdast-util-mdx-jsx@3.2.0: - resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} - - mdast-util-mdxjs-esm@2.0.1: - resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} - - mdast-util-newline-to-break@2.0.0: - resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==} - - mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - - mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - - mdast-util-to-markdown@2.1.2: - resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} - - mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - - mermaid@11.14.0: - resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==} - - micromark-core-commonmark@2.0.3: - resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - - micromark-extension-cjk-friendly-gfm-strikethrough@2.0.1: - resolution: {integrity: sha512-wVC0zwjJNqQeX+bb07YTPu/CvSAyCTafyYb7sMhX1r62/Lw5M/df3JyYaANyp8g15c1ypJRFSsookTqA1IDsUg==} - engines: {node: '>=18'} - peerDependencies: - micromark: ^4.0.0 - micromark-util-types: ^2.0.0 - peerDependenciesMeta: - micromark-util-types: - optional: true - - micromark-extension-cjk-friendly-util@3.0.1: - resolution: {integrity: sha512-GcbXqTTHOsiZHyF753oIddP/J2eH8j9zpyQPhkof6B2JNxfEJabnQqxbCgzJNuNes0Y2jTNJ3LiYPSXr6eJA8w==} - engines: {node: '>=18'} - peerDependencies: - micromark-util-types: '*' - peerDependenciesMeta: - micromark-util-types: - optional: true - - micromark-extension-cjk-friendly@2.0.1: - resolution: {integrity: sha512-OkzoYVTL1ChbvQ8Cc1ayTIz7paFQz8iS9oIYmewncweUSwmWR+hkJF9spJ1lxB90XldJl26A1F4IkPOKS3bDXw==} - engines: {node: '>=18'} - peerDependencies: - micromark: ^4.0.0 - micromark-util-types: ^2.0.0 - peerDependenciesMeta: - micromark-util-types: - optional: true - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - - micromark-extension-gfm-footnote@2.1.0: - resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - - micromark-extension-gfm-table@2.1.1: - resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - - micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - - micromark-extension-math@3.1.0: - resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} - - micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - - micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - - micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - - micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - - micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - - micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - - micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - - micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - - micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - - micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-subtokenize@2.1.0: - resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - - micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - - micromark@4.0.2: - resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - - monaco-editor@0.55.1: - resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - deprecated: Use your platform's native DOMException instead - - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-releases@2.0.44: - resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} - - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - oniguruma-parser@0.12.1: - resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} - - oniguruma-to-es@4.3.5: - resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} - - openai@6.26.0: - resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true - - p-retry@4.6.2: - resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} - engines: {node: '>=8'} - - package-manager-detector@1.6.0: - resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} - - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - partial-json@0.1.7: - resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} - - path-data-parser@0.1.0: - resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pkg-types@2.3.1: - resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} - - points-on-curve@0.2.0: - resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} - - points-on-path@0.2.1: - resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} - - postcss-selector-parser@6.0.10: - resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} - engines: {node: '>=4'} - - postcss@8.5.9: - resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} - engines: {node: ^10 || ^12 || >=14} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - - protobufjs@7.6.5: - resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} - engines: {node: '>=12.0.0'} - - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - - react-complex-tree@2.6.2: - resolution: {integrity: sha512-6SEnSCMpxRlvTp4BUMEuQAv9Fiwo8EPr1HHjpJrk326JHIBw4S3f4M7nLhbzdFqbZ+2xfwKSswOFZaAjKWyWSw==} - peerDependencies: - react: '>=16.0.0' - - react-dom@19.2.5: - resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} - peerDependencies: - react: ^19.2.5 - - react@19.2.5: - resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} - engines: {node: '>=0.10.0'} - - reactivity-store@0.4.0: - resolution: {integrity: sha512-uL9uoREOBg2o4zUa8vMU0AbvAOk0osPloizscmyZqMvJzcuuKX3ELFYYr1DX8gAcfvlhPduz4QuLZn1eChCu4Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - regex-recursion@6.0.2: - resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} - - regex-utilities@2.3.0: - resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - - regex@6.1.0: - resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - - rehype-harden@1.1.8: - resolution: {integrity: sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==} - - rehype-katex@7.0.1: - resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} - - rehype-raw@7.0.0: - resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} - - rehype-sanitize@6.0.0: - resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} - - remark-breaks@4.0.0: - resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==} - - remark-cjk-friendly-gfm-strikethrough@2.0.1: - resolution: {integrity: sha512-pWKj25O2eLXIL1aBupayl1fKhco+Brw8qWUWJPVB9EBzbQNd7nGLj0nLmJpggWsGLR5j5y40PIdjxby9IEYTuA==} - engines: {node: '>=18'} - peerDependencies: - '@types/mdast': ^4.0.0 - unified: ^11.0.0 - peerDependenciesMeta: - '@types/mdast': - optional: true - - remark-cjk-friendly@2.0.1: - resolution: {integrity: sha512-6WwkoQyZf/4j5k53zdFYrR8Ca+UVn992jXdLUSBDZR4eBpFhKyVxmA4gUHra/5fesjGIxrDhHesNr/sVoiiysA==} - engines: {node: '>=18'} - peerDependencies: - '@types/mdast': ^4.0.0 - unified: ^11.0.0 - peerDependenciesMeta: - '@types/mdast': - optional: true - - remark-gfm@4.0.1: - resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} - - remark-math@6.0.0: - resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} - - remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - - remark-rehype@11.1.2: - resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - - remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - - remend@1.3.0: - resolution: {integrity: sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw==} - - reselect@5.2.0: - resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - - robust-predicates@3.0.3: - resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - - rolldown@1.0.0-rc.15: - resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - roughjs@4.6.6: - resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - - rw@1.3.3: - resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - - shiki@3.23.0: - resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} - - snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - ssf@0.11.2: - resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} - engines: {node: '>=0.8'} - - streamdown@2.5.0: - resolution: {integrity: sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - style-to-js@1.1.21: - resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - - style-to-object@1.0.14: - resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - - stylis@4.3.6: - resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} - - svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - - tailwind-merge@3.5.0: - resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} - - tailwindcss@4.2.2: - resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} - - tapable@2.3.2: - resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} - engines: {node: '>=6'} - - tinyexec@1.1.1: - resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} - engines: {node: '>=18'} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - ts-algebra@2.0.0: - resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} - - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} - engines: {node: '>=6.10'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - typebox@1.1.38: - resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==} - - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@6.0.2: - resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - - undici-types@8.3.0: - resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - - unist-util-find-after@5.0.0: - resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-remove-position@5.0.0: - resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - - unplugin-icons@23.0.1: - resolution: {integrity: sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==} - peerDependencies: - '@svgr/core': '>=7.0.0' - '@svgx/core': ^1.0.1 - '@vue/compiler-sfc': ^3.0.2 - svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 - peerDependenciesMeta: - '@svgr/core': - optional: true - '@svgx/core': - optional: true - '@vue/compiler-sfc': - optional: true - svelte: - optional: true - - unplugin@2.3.11: - resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} - engines: {node: '>=18.12.0'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - vfile-location@5.0.3: - resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - vite@8.0.8: - resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.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 - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - - web-namespaces@2.0.1: - resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - - wmf@1.0.2: - resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} - engines: {node: '>=0.8'} - - word@0.3.0: - resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} - engines: {node: '>=0.8'} - - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xlsx@0.18.5: - resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} - engines: {node: '>=0.8'} - hasBin: true - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yet-another-react-lightbox@3.31.0: - resolution: {integrity: sha512-qcj8Vz5Gpbl2/XCXW/4vAdLGganPHEL1D5VgHxBQWbPF8ZeomH1DSN2ci++kv/1hMlccpUkg/nNx2CrMf9cLAg==} - engines: {node: '>=14'} - peerDependencies: - '@types/react': ^16 || ^17 || ^18 || ^19 - '@types/react-dom': ^16 || ^17 || ^18 || ^19 - react: ^16.8.0 || ^17 || ^18 || ^19 - react-dom: ^16.8.0 || ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - zod-to-json-schema@3.25.2: - resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} - peerDependencies: - zod: ^3.25.28 || ^4 - - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@alloc/quick-lru@5.2.0': {} - - '@antfu/install-pkg@1.1.0': - dependencies: - package-manager-detector: 1.6.0 - tinyexec: 1.1.1 - - '@anthropic-ai/sdk@0.91.1(zod@4.4.3)': - dependencies: - json-schema-to-ts: 3.1.1 - optionalDependencies: - zod: 4.4.3 - - '@aws-crypto/sha256-browser@5.2.0': - dependencies: - '@aws-crypto/sha256-js': 5.2.0 - '@aws-crypto/supports-web-crypto': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.0 - '@aws-sdk/util-locate-window': 3.965.8 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-crypto/sha256-js@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.0 - tslib: 2.8.1 - - '@aws-crypto/supports-web-crypto@5.2.0': - dependencies: - tslib: 2.8.1 - - '@aws-crypto/util@5.2.0': - dependencies: - '@aws-sdk/types': 3.974.0 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-sdk/client-bedrock-runtime@3.1048.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.975.1 - '@aws-sdk/credential-provider-node': 3.972.66 - '@aws-sdk/eventstream-handler-node': 3.972.26 - '@aws-sdk/middleware-eventstream': 3.972.22 - '@aws-sdk/middleware-websocket': 3.972.39 - '@aws-sdk/token-providers': 3.1048.0 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/fetch-http-handler': 5.6.4 - '@smithy/node-http-handler': 4.9.4 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/core@3.975.1': - dependencies: - '@aws-sdk/types': 3.974.0 - '@aws-sdk/xml-builder': 3.972.34 - '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.29.2 - '@smithy/signature-v4': 5.6.3 - '@smithy/types': 4.16.0 - bowser: 2.14.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.972.57': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.972.59': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/fetch-http-handler': 5.6.4 - '@smithy/node-http-handler': 4.9.4 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.973.1': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/credential-provider-env': 3.972.57 - '@aws-sdk/credential-provider-http': 3.972.59 - '@aws-sdk/credential-provider-login': 3.972.63 - '@aws-sdk/credential-provider-process': 3.972.57 - '@aws-sdk/credential-provider-sso': 3.973.1 - '@aws-sdk/credential-provider-web-identity': 3.972.63 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/credential-provider-imds': 4.4.7 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-login@3.972.63': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-node@3.972.66': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.57 - '@aws-sdk/credential-provider-http': 3.972.59 - '@aws-sdk/credential-provider-ini': 3.973.1 - '@aws-sdk/credential-provider-process': 3.972.57 - '@aws-sdk/credential-provider-sso': 3.973.1 - '@aws-sdk/credential-provider-web-identity': 3.972.63 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/credential-provider-imds': 4.4.7 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-process@3.972.57': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-sso@3.973.1': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/token-providers': 3.1083.0 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-web-identity@3.972.63': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/eventstream-handler-node@3.972.26': - dependencies: - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-eventstream@3.972.22': - dependencies: - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-websocket@3.972.39': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/fetch-http-handler': 5.6.4 - '@smithy/signature-v4': 5.6.3 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/nested-clients@3.997.31': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/signature-v4-multi-region': 3.996.39 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/fetch-http-handler': 5.6.4 - '@smithy/node-http-handler': 4.9.4 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.996.39': - dependencies: - '@aws-sdk/types': 3.974.0 - '@smithy/signature-v4': 5.6.3 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.1048.0': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.1083.0': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/types@3.974.0': - dependencies: - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws-sdk/util-locate-window@3.965.8': - dependencies: - tslib: 2.8.1 - - '@aws-sdk/xml-builder@3.972.34': - dependencies: - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@aws/lambda-invoke-store@0.3.0': {} - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.3': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.3 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/helper-validator-option@7.29.7': {} - - '@babel/helpers@7.29.2': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.3': - dependencies: - '@babel/types': 7.29.0 - - '@babel/runtime@7.29.7': {} - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@base-ui/react@1.6.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@babel/runtime': 7.29.7 - '@base-ui/utils': 0.3.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@floating-ui/utils': 0.2.11 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - use-sync-external-store: 1.6.0(react@19.2.5) - optionalDependencies: - '@types/react': 19.2.14 - - '@base-ui/utils@0.3.1(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@babel/runtime': 7.29.7 - '@floating-ui/utils': 0.2.11 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - reselect: 5.2.0 - use-sync-external-store: 1.6.0(react@19.2.5) - optionalDependencies: - '@types/react': 19.2.14 - - '@biomejs/biome@2.5.2': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.5.2 - '@biomejs/cli-darwin-x64': 2.5.2 - '@biomejs/cli-linux-arm64': 2.5.2 - '@biomejs/cli-linux-arm64-musl': 2.5.2 - '@biomejs/cli-linux-x64': 2.5.2 - '@biomejs/cli-linux-x64-musl': 2.5.2 - '@biomejs/cli-win32-arm64': 2.5.2 - '@biomejs/cli-win32-x64': 2.5.2 - - '@biomejs/cli-darwin-arm64@2.5.2': - optional: true - - '@biomejs/cli-darwin-x64@2.5.2': - optional: true - - '@biomejs/cli-linux-arm64-musl@2.5.2': - optional: true - - '@biomejs/cli-linux-arm64@2.5.2': - optional: true - - '@biomejs/cli-linux-x64-musl@2.5.2': - optional: true - - '@biomejs/cli-linux-x64@2.5.2': - optional: true - - '@biomejs/cli-win32-arm64@2.5.2': - optional: true - - '@biomejs/cli-win32-x64@2.5.2': - optional: true - - '@braintree/sanitize-url@7.1.2': {} - - '@bufbuild/protobuf@2.12.1': {} - - '@bufbuild/protoc-gen-es@2.12.1(@bufbuild/protobuf@2.12.1)': - dependencies: - '@bufbuild/protoplugin': 2.12.1 - optionalDependencies: - '@bufbuild/protobuf': 2.12.1 - transitivePeerDependencies: - - supports-color - - '@bufbuild/protoplugin@2.12.1': - dependencies: - '@bufbuild/protobuf': 2.12.1 - '@typescript/vfs': 1.6.4(typescript@5.4.5) - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@chevrotain/cst-dts-gen@12.0.0': - dependencies: - '@chevrotain/gast': 12.0.0 - '@chevrotain/types': 12.0.0 - - '@chevrotain/gast@12.0.0': - dependencies: - '@chevrotain/types': 12.0.0 - - '@chevrotain/regexp-to-ast@12.0.0': {} - - '@chevrotain/types@12.0.0': {} - - '@chevrotain/utils@12.0.0': {} - - '@earendil-works/pi-ai@0.80.6(ws@8.21.0)(zod@4.4.3)': - dependencies: - '@anthropic-ai/sdk': 0.91.1(zod@4.4.3) - '@aws-sdk/client-bedrock-runtime': 3.1048.0 - '@google/genai': 1.52.0 - '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) - '@opentelemetry/api': 1.9.0 - '@smithy/node-http-handler': 4.7.3 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - openai: 6.26.0(ws@8.21.0)(zod@4.4.3) - partial-json: 0.1.7 - typebox: 1.1.38 - transitivePeerDependencies: - - '@modelcontextprotocol/sdk' - - bufferutil - - supports-color - - utf-8-validate - - ws - - zod - - '@emnapi/core@1.9.2': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.9.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@floating-ui/core@1.7.5': - dependencies: - '@floating-ui/utils': 0.2.11 - - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 - - '@floating-ui/react-dom@2.1.8(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@floating-ui/dom': 1.7.6 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@floating-ui/utils@0.2.11': {} - - '@git-diff-view/core@0.1.5': - dependencies: - '@git-diff-view/lowlight': 0.1.5 - fast-diff: 1.3.0 - highlight.js: 11.11.1 - lowlight: 3.3.0 - - '@git-diff-view/file@0.1.3': - dependencies: - '@git-diff-view/core': 0.1.5 - diff: 8.0.4 - fast-diff: 1.3.0 - highlight.js: 11.11.1 - lowlight: 3.3.0 - - '@git-diff-view/lowlight@0.1.5': - dependencies: - '@types/hast': 3.0.4 - highlight.js: 11.11.1 - lowlight: 3.3.0 - - '@git-diff-view/react@0.1.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@git-diff-view/core': 0.1.5 - '@types/hast': 3.0.4 - fast-diff: 1.3.0 - highlight.js: 11.11.1 - lowlight: 3.3.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - reactivity-store: 0.4.0(react@19.2.5) - use-sync-external-store: 1.6.0(react@19.2.5) - - '@google/genai@1.52.0': - dependencies: - google-auth-library: 10.9.0 - p-retry: 4.6.2 - protobufjs: 7.6.5 - ws: 8.21.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@iconify-json/gravity-ui@1.2.12': - dependencies: - '@iconify/types': 2.0.0 - - '@iconify-json/logos@1.2.11': - dependencies: - '@iconify/types': 2.0.0 - - '@iconify-json/lucide@1.2.108': - dependencies: - '@iconify/types': 2.0.0 - - '@iconify-json/material-icon-theme@1.2.67': - dependencies: - '@iconify/types': 2.0.0 - - '@iconify/types@2.0.0': {} - - '@iconify/utils@3.1.0': - dependencies: - '@antfu/install-pkg': 1.1.0 - '@iconify/types': 2.0.0 - mlly: 1.8.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@mermaid-js/parser@1.1.0': - dependencies: - langium: 4.2.2 - - '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/semantic-conventions': 1.42.0 - ws: 8.21.0 - zod: 4.4.3 - zod-to-json-schema: 3.25.2(zod@4.4.3) - optionalDependencies: - '@opentelemetry/api': 1.9.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': - dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@tybys/wasm-util': 0.10.1 - optional: true - - '@opentelemetry/api@1.9.0': {} - - '@opentelemetry/semantic-conventions@1.42.0': {} - - '@oxc-project/types@0.124.0': {} - - '@protobufjs/aspromise@1.1.2': {} - - '@protobufjs/base64@1.1.2': {} - - '@protobufjs/codegen@2.0.5': {} - - '@protobufjs/eventemitter@1.1.1': {} - - '@protobufjs/fetch@1.1.1': - dependencies: - '@protobufjs/aspromise': 1.1.2 - - '@protobufjs/float@1.0.2': {} - - '@protobufjs/path@1.1.2': {} - - '@protobufjs/pool@1.1.0': {} - - '@protobufjs/utf8@1.1.2': {} - - '@rolldown/binding-android-arm64@1.0.0-rc.15': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.15': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.15': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.15': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': - dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': - optional: true - - '@rolldown/pluginutils@1.0.0-rc.15': {} - - '@rolldown/pluginutils@1.0.0-rc.7': {} - - '@shikijs/core@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - hast-util-to-html: 9.0.5 - - '@shikijs/engine-javascript@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.5 - - '@shikijs/engine-oniguruma@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - - '@shikijs/langs@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - - '@shikijs/themes@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - - '@shikijs/types@3.23.0': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - '@shikijs/vscode-textmate@10.0.2': {} - - '@sinclair/typebox@0.34.49': {} - - '@smithy/core@3.29.2': - dependencies: - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@smithy/credential-provider-imds@4.4.7': - dependencies: - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@5.6.4': - dependencies: - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@smithy/is-array-buffer@2.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/node-http-handler@4.7.3': - dependencies: - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@smithy/node-http-handler@4.9.4': - dependencies: - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@smithy/signature-v4@5.6.3': - dependencies: - '@smithy/core': 3.29.2 - '@smithy/types': 4.16.0 - tslib: 2.8.1 - - '@smithy/types@4.16.0': - dependencies: - tslib: 2.8.1 - - '@smithy/util-buffer-from@2.2.0': - dependencies: - '@smithy/is-array-buffer': 2.2.0 - tslib: 2.8.1 - - '@smithy/util-utf8@2.3.0': - dependencies: - '@smithy/util-buffer-from': 2.2.0 - tslib: 2.8.1 - - '@streamdown/cjk@1.0.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.5)(unified@11.0.5)': - dependencies: - react: 19.2.5 - remark-cjk-friendly: 2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) - remark-cjk-friendly-gfm-strikethrough: 2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) - unist-util-visit: 5.1.0 - transitivePeerDependencies: - - '@types/mdast' - - micromark - - micromark-util-types - - unified - - '@streamdown/code@1.1.1(react@19.2.5)': - dependencies: - react: 19.2.5 - shiki: 3.23.0 - - '@streamdown/math@1.0.2(react@19.2.5)': - dependencies: - katex: 0.16.45 - react: 19.2.5 - rehype-katex: 7.0.1 - remark-math: 6.0.0 - transitivePeerDependencies: - - supports-color - - '@streamdown/mermaid@1.0.2(react@19.2.5)': - dependencies: - mermaid: 11.14.0 - react: 19.2.5 - - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - - '@svgr/babel-preset@8.1.0(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.0) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) - - '@svgr/core@8.1.0(typescript@6.0.2)': - dependencies: - '@babel/core': 7.29.0 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@6.0.2) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript - - '@svgr/hast-util-to-babel-ast@8.0.0': - dependencies: - '@babel/types': 7.29.0 - entities: 4.5.0 - - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.2))': - dependencies: - '@babel/core': 7.29.0 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) - '@svgr/core': 8.1.0(typescript@6.0.2) - '@svgr/hast-util-to-babel-ast': 8.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - - '@tailwindcss/node@4.2.2': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.20.1 - jiti: 2.6.1 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.2 - - '@tailwindcss/oxide-android-arm64@4.2.2': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.2.2': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide@4.2.2': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-x64': 4.2.2 - '@tailwindcss/oxide-freebsd-x64': 4.2.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-x64-musl': 4.2.2 - '@tailwindcss/oxide-wasm32-wasi': 4.2.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - - '@tailwindcss/postcss@4.2.2': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - postcss: 8.5.9 - tailwindcss: 4.2.2 - - '@tailwindcss/typography@0.5.20(tailwindcss@4.2.2)': - dependencies: - postcss-selector-parser: 6.0.10 - tailwindcss: 4.2.2 - - '@tanstack/react-virtual@3.14.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': - dependencies: - '@tanstack/virtual-core': 3.17.4 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - - '@tanstack/virtual-core@3.17.4': {} - - '@tybys/wasm-util@0.10.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/d3-array@3.2.2': {} - - '@types/d3-axis@3.0.6': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-brush@3.0.6': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-chord@3.0.6': {} - - '@types/d3-color@3.1.3': {} - - '@types/d3-contour@3.0.6': - dependencies: - '@types/d3-array': 3.2.2 - '@types/geojson': 7946.0.16 - - '@types/d3-delaunay@6.0.4': {} - - '@types/d3-dispatch@3.0.7': {} - - '@types/d3-drag@3.0.7': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-dsv@3.0.7': {} - - '@types/d3-ease@3.0.2': {} - - '@types/d3-fetch@3.0.7': - dependencies: - '@types/d3-dsv': 3.0.7 - - '@types/d3-force@3.0.10': {} - - '@types/d3-format@3.0.4': {} - - '@types/d3-geo@3.1.0': - dependencies: - '@types/geojson': 7946.0.16 - - '@types/d3-hierarchy@3.1.7': {} - - '@types/d3-interpolate@3.0.4': - dependencies: - '@types/d3-color': 3.1.3 - - '@types/d3-path@3.1.1': {} - - '@types/d3-polygon@3.0.2': {} - - '@types/d3-quadtree@3.0.6': {} - - '@types/d3-random@3.0.3': {} - - '@types/d3-scale-chromatic@3.1.0': {} - - '@types/d3-scale@4.0.9': - dependencies: - '@types/d3-time': 3.0.4 - - '@types/d3-selection@3.0.11': {} - - '@types/d3-shape@3.1.8': - dependencies: - '@types/d3-path': 3.1.1 - - '@types/d3-time-format@4.0.3': {} - - '@types/d3-time@3.0.4': {} - - '@types/d3-timer@3.0.2': {} - - '@types/d3-transition@3.0.9': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-zoom@3.0.8': - dependencies: - '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.11 - - '@types/d3@7.4.3': - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-axis': 3.0.6 - '@types/d3-brush': 3.0.6 - '@types/d3-chord': 3.0.6 - '@types/d3-color': 3.1.3 - '@types/d3-contour': 3.0.6 - '@types/d3-delaunay': 6.0.4 - '@types/d3-dispatch': 3.0.7 - '@types/d3-drag': 3.0.7 - '@types/d3-dsv': 3.0.7 - '@types/d3-ease': 3.0.2 - '@types/d3-fetch': 3.0.7 - '@types/d3-force': 3.0.10 - '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.0 - '@types/d3-hierarchy': 3.1.7 - '@types/d3-interpolate': 3.0.4 - '@types/d3-path': 3.1.1 - '@types/d3-polygon': 3.0.2 - '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.3 - '@types/d3-scale': 4.0.9 - '@types/d3-scale-chromatic': 3.1.0 - '@types/d3-selection': 3.0.11 - '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.4 - '@types/d3-time-format': 4.0.3 - '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.9 - '@types/d3-zoom': 3.0.8 - - '@types/debug@4.1.13': - dependencies: - '@types/ms': 2.1.0 - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.8 - - '@types/estree@1.0.8': {} - - '@types/geojson@7946.0.16': {} - - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/katex@0.16.8': {} - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/ms@2.1.0': {} - - '@types/node@26.1.1': - dependencies: - undici-types: 8.3.0 - - '@types/react-dom@19.2.3(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - - '@types/react@19.2.14': - dependencies: - csstype: 3.2.3 - - '@types/retry@0.12.0': {} - - '@types/trusted-types@2.0.7': - optional: true - - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - - '@typescript/vfs@1.6.4(typescript@5.4.5)': - dependencies: - debug: 4.4.3 - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@ungap/structured-clone@1.3.0': {} - - '@upsetjs/venn.js@2.0.0': - optionalDependencies: - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - - '@vitejs/plugin-react@6.0.1(vite@8.0.8(@types/node@26.1.1)(jiti@2.6.1))': - dependencies: - '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.8(@types/node@26.1.1)(jiti@2.6.1) - - '@vue/reactivity@3.5.35': - dependencies: - '@vue/shared': 3.5.35 - - '@vue/shared@3.5.35': {} - - '@xterm/addon-fit@0.11.0': {} - - '@xterm/xterm@6.0.0': {} - - acorn@8.16.0: {} - - adler-32@1.3.1: {} - - agent-base@7.1.4: {} - - argparse@2.0.1: {} - - bail@2.0.2: {} - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.10.30: {} - - bignumber.js@9.3.1: {} - - bowser@2.14.1: {} - - browserslist@4.28.2: - dependencies: - baseline-browser-mapping: 2.10.30 - caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.357 - node-releases: 2.0.44 - update-browserslist-db: 1.2.3(browserslist@4.28.2) - - buffer-equal-constant-time@1.0.1: {} - - callsites@3.1.0: {} - - camelcase@6.3.0: {} - - caniuse-lite@1.0.30001793: {} - - ccount@2.0.1: {} - - cfb@1.2.2: - dependencies: - adler-32: 1.3.1 - crc-32: 1.2.2 - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - chevrotain-allstar@0.4.1(chevrotain@12.0.0): - dependencies: - chevrotain: 12.0.0 - lodash-es: 4.18.1 - - chevrotain@12.0.0: - dependencies: - '@chevrotain/cst-dts-gen': 12.0.0 - '@chevrotain/gast': 12.0.0 - '@chevrotain/regexp-to-ast': 12.0.0 - '@chevrotain/types': 12.0.0 - '@chevrotain/utils': 12.0.0 - - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - - clsx@2.1.1: {} - - codepage@1.15.0: {} - - comma-separated-tokens@2.0.3: {} - - commander@7.2.0: {} - - commander@8.3.0: {} - - confbox@0.1.8: {} - - confbox@0.2.4: {} - - convert-source-map@2.0.0: {} - - core-util-is@1.0.3: {} - - cose-base@1.0.3: - dependencies: - layout-base: 1.0.2 - - cose-base@2.2.0: - dependencies: - layout-base: 2.0.1 - - cosmiconfig@8.3.6(typescript@6.0.2): - dependencies: - import-fresh: 3.3.1 - js-yaml: 4.1.1 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 6.0.2 - - crc-32@1.2.2: {} - - cssesc@3.0.0: {} - - csstype@3.2.3: {} - - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2): - dependencies: - cose-base: 1.0.3 - cytoscape: 3.33.2 - - cytoscape-fcose@2.2.0(cytoscape@3.33.2): - dependencies: - cose-base: 2.2.0 - cytoscape: 3.33.2 - - cytoscape@3.33.2: {} - - d3-array@2.12.1: - dependencies: - internmap: 1.0.1 - - d3-array@3.2.4: - dependencies: - internmap: 2.0.3 - - d3-axis@3.0.0: {} - - d3-brush@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - - d3-chord@3.0.1: - dependencies: - d3-path: 3.1.0 - - d3-color@3.1.0: {} - - d3-contour@4.0.2: - dependencies: - d3-array: 3.2.4 - - d3-delaunay@6.0.4: - dependencies: - delaunator: 5.1.0 - - d3-dispatch@3.0.1: {} - - d3-drag@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-selection: 3.0.0 - - d3-dsv@3.0.1: - dependencies: - commander: 7.2.0 - iconv-lite: 0.6.3 - rw: 1.3.3 - - d3-ease@3.0.1: {} - - d3-fetch@3.0.1: - dependencies: - d3-dsv: 3.0.1 - - d3-force@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-quadtree: 3.0.1 - d3-timer: 3.0.1 - - d3-format@3.1.2: {} - - d3-geo@3.1.1: - dependencies: - d3-array: 3.2.4 - - d3-hierarchy@3.1.2: {} - - d3-interpolate@3.0.1: - dependencies: - d3-color: 3.1.0 - - d3-path@1.0.9: {} - - d3-path@3.1.0: {} - - d3-polygon@3.0.1: {} - - d3-quadtree@3.0.1: {} - - d3-random@3.0.1: {} - - d3-sankey@0.12.3: - dependencies: - d3-array: 2.12.1 - d3-shape: 1.3.7 - - d3-scale-chromatic@3.1.0: - dependencies: - d3-color: 3.1.0 - d3-interpolate: 3.0.1 - - d3-scale@4.0.2: - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.2 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - - d3-selection@3.0.0: {} - - d3-shape@1.3.7: - dependencies: - d3-path: 1.0.9 - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - - d3-time-format@4.1.0: - dependencies: - d3-time: 3.1.0 - - d3-time@3.1.0: - dependencies: - d3-array: 3.2.4 - - d3-timer@3.0.1: {} - - d3-transition@3.0.1(d3-selection@3.0.0): - dependencies: - d3-color: 3.1.0 - d3-dispatch: 3.0.1 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-timer: 3.0.1 - - d3-zoom@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - - d3@7.9.0: - dependencies: - d3-array: 3.2.4 - d3-axis: 3.0.0 - d3-brush: 3.0.0 - d3-chord: 3.0.1 - d3-color: 3.1.0 - d3-contour: 4.0.2 - d3-delaunay: 6.0.4 - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-dsv: 3.0.1 - d3-ease: 3.0.1 - d3-fetch: 3.0.1 - d3-force: 3.0.0 - d3-format: 3.1.2 - d3-geo: 3.1.1 - d3-hierarchy: 3.1.2 - d3-interpolate: 3.0.1 - d3-path: 3.1.0 - d3-polygon: 3.0.1 - d3-quadtree: 3.0.1 - d3-random: 3.0.1 - d3-scale: 4.0.2 - d3-scale-chromatic: 3.1.0 - d3-selection: 3.0.0 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - d3-timer: 3.0.1 - d3-transition: 3.0.1(d3-selection@3.0.0) - d3-zoom: 3.0.0 - - dagre-d3-es@7.0.14: - dependencies: - d3: 7.9.0 - lodash-es: 4.18.1 - - data-uri-to-buffer@4.0.1: {} - - dayjs@1.11.20: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - - delaunator@5.1.0: - dependencies: - robust-predicates: 3.0.3 - - dequal@2.0.3: {} - - detect-libc@2.1.2: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - diff@8.0.4: {} - - docx-preview@0.4.0: - dependencies: - jszip: 3.10.1 - - dompurify@3.2.7: - optionalDependencies: - '@types/trusted-types': 2.0.7 - - dompurify@3.3.3: - optionalDependencies: - '@types/trusted-types': 2.0.7 - - dot-case@3.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - - ecdsa-sig-formatter@1.0.11: - dependencies: - safe-buffer: 5.1.2 - - electron-to-chromium@1.5.357: {} - - enhanced-resolve@5.20.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.2 - - entities@4.5.0: {} - - entities@6.0.1: {} - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - escalade@3.2.0: {} - - escape-string-regexp@5.0.0: {} - - estree-util-is-identifier-name@3.0.0: {} - - exsolve@1.0.8: {} - - extend@3.0.2: {} - - fast-diff@1.3.0: {} - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - - frac@1.1.2: {} - - fsevents@2.3.3: - optional: true - - gaxios@7.2.0: - dependencies: - extend: 3.0.2 - https-proxy-agent: 7.0.6 - node-fetch: 3.3.2 - transitivePeerDependencies: - - supports-color - - gcp-metadata@8.1.2: - dependencies: - gaxios: 7.2.0 - google-logging-utils: 1.1.3 - json-bigint: 1.0.0 - transitivePeerDependencies: - - supports-color - - gensync@1.0.0-beta.2: {} - - get-east-asian-width@1.5.0: {} - - google-auth-library@10.9.0: - dependencies: - base64-js: 1.5.1 - ecdsa-sig-formatter: 1.0.11 - gaxios: 7.2.0 - gcp-metadata: 8.1.2 - google-logging-utils: 1.1.3 - jws: 4.0.1 - transitivePeerDependencies: - - supports-color - - google-logging-utils@1.1.3: {} - - graceful-fs@4.2.11: {} - - hachure-fill@0.5.2: {} - - hast-util-from-dom@5.0.1: - dependencies: - '@types/hast': 3.0.4 - hastscript: 9.0.1 - web-namespaces: 2.0.1 - - hast-util-from-html-isomorphic@2.0.0: - dependencies: - '@types/hast': 3.0.4 - hast-util-from-dom: 5.0.1 - hast-util-from-html: 2.0.3 - unist-util-remove-position: 5.0.0 - - hast-util-from-html@2.0.3: - dependencies: - '@types/hast': 3.0.4 - devlop: 1.1.0 - hast-util-from-parse5: 8.0.3 - parse5: 7.3.0 - vfile: 6.0.3 - vfile-message: 4.0.3 - - hast-util-from-parse5@8.0.3: - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - devlop: 1.1.0 - hastscript: 9.0.1 - property-information: 7.1.0 - vfile: 6.0.3 - vfile-location: 5.0.3 - web-namespaces: 2.0.1 - - hast-util-is-element@3.0.0: - dependencies: - '@types/hast': 3.0.4 - - hast-util-parse-selector@4.0.0: - dependencies: - '@types/hast': 3.0.4 - - hast-util-raw@9.1.0: - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - '@ungap/structured-clone': 1.3.0 - hast-util-from-parse5: 8.0.3 - hast-util-to-parse5: 8.0.1 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.1 - parse5: 7.3.0 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - - hast-util-sanitize@5.0.2: - dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.3.0 - unist-util-position: 5.0.0 - - hast-util-to-html@9.0.5: - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 3.0.0 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.1 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - - hast-util-to-jsx-runtime@2.3.6: - dependencies: - '@types/estree': 1.0.8 - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.21 - unist-util-position: 5.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - hast-util-to-parse5@8.0.1: - dependencies: - '@types/hast': 3.0.4 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - - hast-util-to-text@4.0.2: - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - hast-util-is-element: 3.0.0 - unist-util-find-after: 5.0.0 - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.4 - - hastscript@9.0.1: - dependencies: - '@types/hast': 3.0.4 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 4.0.0 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - - highlight.js@11.11.1: {} - - html-url-attributes@3.0.1: {} - - html-void-elements@3.0.0: {} - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - immediate@3.0.6: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - inherits@2.0.4: {} - - inline-style-parser@0.2.7: {} - - internmap@1.0.1: {} - - internmap@2.0.3: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-arrayish@0.2.1: {} - - is-decimal@2.0.1: {} - - is-hexadecimal@2.0.1: {} - - is-plain-obj@4.1.0: {} - - isarray@1.0.0: {} - - jiti@2.6.1: {} - - js-tokens@4.0.0: {} - - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - - jsesc@3.1.0: {} - - json-bigint@1.0.0: - dependencies: - bignumber.js: 9.3.1 - - json-parse-even-better-errors@2.3.1: {} - - json-schema-to-ts@3.1.1: - dependencies: - '@babel/runtime': 7.29.7 - ts-algebra: 2.0.0 - - json5@2.2.3: {} - - jszip@3.10.1: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - - jwa@2.0.1: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.1.2 - - jws@4.0.1: - dependencies: - jwa: 2.0.1 - safe-buffer: 5.1.2 - - katex@0.16.45: - dependencies: - commander: 8.3.0 - - katex@0.17.0: - dependencies: - commander: 8.3.0 - - khroma@2.1.0: {} - - langium@4.2.2: - dependencies: - '@chevrotain/regexp-to-ast': 12.0.0 - chevrotain: 12.0.0 - chevrotain-allstar: 0.4.1(chevrotain@12.0.0) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - - layout-base@1.0.2: {} - - layout-base@2.0.1: {} - - lie@3.3.0: - dependencies: - immediate: 3.0.6 - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - 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 - - lines-and-columns@1.2.4: {} - - local-pkg@1.1.2: - dependencies: - mlly: 1.8.2 - pkg-types: 2.3.1 - quansync: 0.2.11 - - lodash-es@4.18.1: {} - - long@5.3.2: {} - - longest-streak@3.1.0: {} - - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - - lowlight@3.3.0: - dependencies: - '@types/hast': 3.0.4 - devlop: 1.1.0 - highlight.js: 11.11.1 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - markdown-table@3.0.4: {} - - marked@14.0.0: {} - - marked@16.4.2: {} - - marked@17.0.6: {} - - mdast-util-find-and-replace@3.0.2: - dependencies: - '@types/mdast': 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - mdast-util-from-markdown@2.0.3: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0: - dependencies: - mdast-util-from-markdown: 2.0.3 - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.0 - mdast-util-gfm-strikethrough: 2.0.0 - mdast-util-gfm-table: 2.0.0 - mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-math@3.0.0: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - longest-streak: 3.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - unist-util-remove-position: 5.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-expression@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@3.2.0: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-newline-to-break@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-find-and-replace: 3.0.2 - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.1 - - mdast-util-to-hast@13.2.1: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.1.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - mermaid@11.14.0: - dependencies: - '@braintree/sanitize-url': 7.1.2 - '@iconify/utils': 3.1.0 - '@mermaid-js/parser': 1.1.0 - '@types/d3': 7.4.3 - '@upsetjs/venn.js': 2.0.0 - cytoscape: 3.33.2 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2) - cytoscape-fcose: 2.2.0(cytoscape@3.33.2) - d3: 7.9.0 - d3-sankey: 0.12.3 - dagre-d3-es: 7.0.14 - dayjs: 1.11.20 - dompurify: 3.3.3 - katex: 0.16.45 - khroma: 2.1.0 - lodash-es: 4.18.1 - marked: 16.4.2 - roughjs: 4.6.6 - stylis: 4.3.6 - ts-dedent: 2.2.0 - uuid: 11.1.0 - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-cjk-friendly-gfm-strikethrough@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2): - dependencies: - devlop: 1.1.0 - get-east-asian-width: 1.5.0 - micromark: 4.0.2 - micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - - micromark-extension-cjk-friendly-util@3.0.1(micromark-util-types@2.0.2): - dependencies: - get-east-asian-width: 1.5.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - - micromark-extension-cjk-friendly@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2): - dependencies: - devlop: 1.1.0 - micromark: 4.0.2 - micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) - micromark-util-chunked: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-table@2.1.1: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-math@3.1.0: - dependencies: - '@types/katex': 0.16.8 - devlop: 1.1.0 - katex: 0.16.45 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2: - dependencies: - '@types/debug': 4.1.13 - debug: 4.4.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.3 - - monaco-editor@0.55.1: - dependencies: - dompurify: 3.2.7 - marked: 14.0.0 - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.8.1 - - node-domexception@1.0.0: {} - - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - - node-releases@2.0.44: {} - - obug@2.1.1: {} - - oniguruma-parser@0.12.1: {} - - oniguruma-to-es@4.3.5: - dependencies: - oniguruma-parser: 0.12.1 - regex: 6.1.0 - regex-recursion: 6.0.2 - - openai@6.26.0(ws@8.21.0)(zod@4.4.3): - optionalDependencies: - ws: 8.21.0 - zod: 4.4.3 - - p-retry@4.6.2: - dependencies: - '@types/retry': 0.12.0 - retry: 0.13.1 - - package-manager-detector@1.6.0: {} - - pako@1.0.11: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.29.0 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - parse5@7.3.0: - dependencies: - entities: 6.0.1 - - partial-json@0.1.7: {} - - path-data-parser@0.1.0: {} - - path-type@4.0.0: {} - - pathe@2.0.3: {} - - picocolors@1.1.1: {} - - picomatch@4.0.4: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - pkg-types@2.3.1: - dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 - pathe: 2.0.3 - - points-on-curve@0.2.0: {} - - points-on-path@0.2.1: - dependencies: - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - - postcss-selector-parser@6.0.10: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss@8.5.9: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - process-nextick-args@2.0.1: {} - - property-information@7.1.0: {} - - protobufjs@7.6.5: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.5 - '@protobufjs/eventemitter': 1.1.1 - '@protobufjs/fetch': 1.1.1 - '@protobufjs/float': 1.0.2 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.2 - '@types/node': 26.1.1 - long: 5.3.2 - - quansync@0.2.11: {} - - react-complex-tree@2.6.2(react@19.2.5): - dependencies: - react: 19.2.5 - - react-dom@19.2.5(react@19.2.5): - dependencies: - react: 19.2.5 - scheduler: 0.27.0 - - react@19.2.5: {} - - reactivity-store@0.4.0(react@19.2.5): - dependencies: - '@vue/reactivity': 3.5.35 - '@vue/shared': 3.5.35 - react: 19.2.5 - use-sync-external-store: 1.6.0(react@19.2.5) - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - regex-recursion@6.0.2: - dependencies: - regex-utilities: 2.3.0 - - regex-utilities@2.3.0: {} - - regex@6.1.0: - dependencies: - regex-utilities: 2.3.0 - - rehype-harden@1.1.8: - dependencies: - unist-util-visit: 5.1.0 - - rehype-katex@7.0.1: - dependencies: - '@types/hast': 3.0.4 - '@types/katex': 0.16.8 - hast-util-from-html-isomorphic: 2.0.0 - hast-util-to-text: 4.0.2 - katex: 0.16.45 - unist-util-visit-parents: 6.0.2 - vfile: 6.0.3 - - rehype-raw@7.0.0: - dependencies: - '@types/hast': 3.0.4 - hast-util-raw: 9.1.0 - vfile: 6.0.3 - - rehype-sanitize@6.0.0: - dependencies: - '@types/hast': 3.0.4 - hast-util-sanitize: 5.0.2 - - remark-breaks@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-newline-to-break: 2.0.0 - unified: 11.0.5 - - remark-cjk-friendly-gfm-strikethrough@2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): - dependencies: - micromark-extension-cjk-friendly-gfm-strikethrough: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2) - unified: 11.0.5 - optionalDependencies: - '@types/mdast': 4.0.4 - transitivePeerDependencies: - - micromark - - micromark-util-types - - remark-cjk-friendly@2.0.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): - dependencies: - micromark-extension-cjk-friendly: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2) - unified: 11.0.5 - optionalDependencies: - '@types/mdast': 4.0.4 - transitivePeerDependencies: - - micromark - - micromark-util-types - - remark-gfm@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-gfm: 3.1.0 - micromark-extension-gfm: 3.0.0 - remark-parse: 11.0.0 - remark-stringify: 11.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-math@6.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-math: 3.0.0 - micromark-extension-math: 3.1.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-parse@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 - micromark-util-types: 2.0.2 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-rehype@11.1.2: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.1 - unified: 11.0.5 - vfile: 6.0.3 - - remark-stringify@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.2 - unified: 11.0.5 - - remend@1.3.0: {} - - reselect@5.2.0: {} - - resolve-from@4.0.0: {} - - retry@0.13.1: {} - - robust-predicates@3.0.3: {} - - rolldown@1.0.0-rc.15: - dependencies: - '@oxc-project/types': 0.124.0 - '@rolldown/pluginutils': 1.0.0-rc.15 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.15 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 - '@rolldown/binding-darwin-x64': 1.0.0-rc.15 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 - - roughjs@4.6.6: - dependencies: - hachure-fill: 0.5.2 - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - points-on-path: 0.2.1 - - rw@1.3.3: {} - - safe-buffer@5.1.2: {} - - safer-buffer@2.1.2: {} - - scheduler@0.27.0: {} - - semver@6.3.1: {} - - setimmediate@1.0.5: {} - - shiki@3.23.0: - dependencies: - '@shikijs/core': 3.23.0 - '@shikijs/engine-javascript': 3.23.0 - '@shikijs/engine-oniguruma': 3.23.0 - '@shikijs/langs': 3.23.0 - '@shikijs/themes': 3.23.0 - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - snake-case@3.0.4: - dependencies: - dot-case: 3.0.4 - tslib: 2.8.1 - - source-map-js@1.2.1: {} - - space-separated-tokens@2.0.2: {} - - ssf@0.11.2: - dependencies: - frac: 1.1.2 - - streamdown@2.5.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): - dependencies: - clsx: 2.1.1 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - marked: 17.0.6 - mermaid: 11.14.0 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - rehype-harden: 1.1.8 - rehype-raw: 7.0.0 - rehype-sanitize: 6.0.0 - remark-gfm: 4.0.1 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - remend: 1.3.0 - tailwind-merge: 3.5.0 - unified: 11.0.5 - unist-util-visit: 5.1.0 - unist-util-visit-parents: 6.0.2 - transitivePeerDependencies: - - supports-color - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - style-to-js@1.1.21: - dependencies: - style-to-object: 1.0.14 - - style-to-object@1.0.14: - dependencies: - inline-style-parser: 0.2.7 - - stylis@4.3.6: {} - - svg-parser@2.0.4: {} - - tailwind-merge@3.5.0: {} - - tailwindcss@4.2.2: {} - - tapable@2.3.2: {} - - tinyexec@1.1.1: {} - - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - trim-lines@3.0.1: {} - - trough@2.2.0: {} - - ts-algebra@2.0.0: {} - - ts-dedent@2.2.0: {} - - tslib@2.8.1: {} - - typebox@1.1.38: {} - - typescript@5.4.5: {} - - typescript@6.0.2: {} - - ufo@1.6.3: {} - - undici-types@8.3.0: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unist-util-find-after@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-remove-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-visit: 5.1.0 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - unplugin-icons@23.0.1(@svgr/core@8.1.0(typescript@6.0.2)): - dependencies: - '@antfu/install-pkg': 1.1.0 - '@iconify/utils': 3.1.0 - local-pkg: 1.1.2 - obug: 2.1.1 - unplugin: 2.3.11 - optionalDependencies: - '@svgr/core': 8.1.0(typescript@6.0.2) - - unplugin@2.3.11: - dependencies: - '@jridgewell/remapping': 2.3.5 - acorn: 8.16.0 - picomatch: 4.0.4 - webpack-virtual-modules: 0.6.2 - - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - use-sync-external-store@1.6.0(react@19.2.5): - dependencies: - react: 19.2.5 - - util-deprecate@1.0.2: {} - - uuid@11.1.0: {} - - vfile-location@5.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile: 6.0.3 - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - vite@8.0.8(@types/node@26.1.1)(jiti@2.6.1): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.9 - rolldown: 1.0.0-rc.15 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 26.1.1 - fsevents: 2.3.3 - jiti: 2.6.1 - - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - vscode-uri@3.1.0: {} - - web-namespaces@2.0.1: {} - - web-streams-polyfill@3.3.3: {} - - webpack-virtual-modules@0.6.2: {} - - wmf@1.0.2: {} - - word@0.3.0: {} - - ws@8.21.0: {} - - xlsx@0.18.5: - dependencies: - adler-32: 1.3.1 - cfb: 1.2.2 - codepage: 1.15.0 - crc-32: 1.2.2 - ssf: 0.11.2 - wmf: 1.0.2 - word: 0.3.0 - - yallist@3.1.1: {} - - yet-another-react-lightbox@3.31.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): - dependencies: - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - - zod-to-json-schema@3.25.2(zod@4.4.3): - dependencies: - zod: 4.4.3 - - zod@4.4.3: {} - - zwitch@2.0.4: {} diff --git a/crates/agent-gateway/web/pnpm-workspace.yaml b/crates/agent-gateway/web/pnpm-workspace.yaml deleted file mode 100644 index 3576b805e..000000000 --- a/crates/agent-gateway/web/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -allowBuilds: - '@google/genai': true - protobufjs: true diff --git a/crates/agent-gateway/web/postcss.config.js b/crates/agent-gateway/web/postcss.config.js deleted file mode 100644 index c2ddf7482..000000000 --- a/crates/agent-gateway/web/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; diff --git a/crates/agent-gateway/web/public/favicon.svg b/crates/agent-gateway/web/public/favicon.svg deleted file mode 100644 index bd918a1b6..000000000 --- a/crates/agent-gateway/web/public/favicon.svg +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/crates/agent-gateway/web/public/icon-simple.png b/crates/agent-gateway/web/public/icon-simple.png deleted file mode 100644 index d2b6468ca32db949288aca644a0c21afa1b5c572..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 998167 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

5{aSaXBpy&4G+|wpLVWIci2X`j7gtN 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�>h_Go!{D6rLW`RImH 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%0DughCy;^I@zgPfpH-# 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�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�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 B{-M8gCrZ6t15&2 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 z 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

+aC&lz z*=!C}6%3i$GqUw+wYC47=uHBZv~}5UIz^FmWT*8|IJSh zT01ZEMP8N_s)j+#+;ufgXuGl2)*hR+_qXxww}|Q#c#>nVpzd=Ov|jZZ6k>m?b<8XM zTwjqlNU~xCW#y~jM1qu~_T$uvKnxcyq!=7T%kP`xC*w-YJgJgPdj@-6G110(3F~nv zxly8*G~oZ#JYH+DitK9eYm|20SZRjodw`JhqC3W?O2!kDeo56Z$6c5lrd1!55h6BH zgzl*-lO;Y}Q2>H9ZXpl5sO6v=8@p1U?F;pZC=gsiYLXS3yG5Qk)oFqxT-19O2}eO! z=_vz2_vbFifzL^kUCL0RLH_V+{(jnRJLsd-3A_3hz)LQ4 zZ4OPHseUif3WXMg7->*NL&)$xmXNGNV>n7H9tLQ&+>&sC;fi2?@DQokO9I2d-aumi z;y5{tS)|Ez{$P;}KJ3@n12Q^AcaS9-NZe!&0e)I?Hk@wT!Z>AF-MX@ty>;{E#&>-4 zrNz%aca&c{DQg%iF*RUBuIaZs2^OPh3i{GW=@zLJL2eo(&CX_Z2uiO>7Uo3^uo28* zA{m~*0;IRW{hd_EglI55uR;o1On97P=UinWDR<8!$Ct$^dTJjj$4*1}w6gYZP$3qq zm{@LtrkH*P67sy{R~;(rr0({O6MKn`k`d_az_yv(Qys3cHqI~|XJBR&RfR>tc<%lY zo_}zJEJ?7tKEeLx8m{fFL7ygXw6BI)E+5Y*AsB7qAthv^6s>FKd6f zvI(xJS#-TD*}acs}=K*-&1o6)pR`*ORJwl8a=LV3L%6g{C{odsO zks@6_Ry*Mi~e| zzLvrT4g}fx&sa(;N%#qRmJp%2KtR&Ug1yJ;6=6a2BUa2S z^-c+*xU!3EFOnS@tXs~KBlC{>*ipbIJm9|?>UF(GuUT{)xIgMVH&pxZ=8FQed5+of zDNavMQ7-bDxs(`~@u(>ixEVOfdRa5#*tO3Hk!@INpgHE9(4^8sg-|i|;hb$EDVZ)& zpnh*Hp<2wan9qQ+#5l`v_0b!6{8iWSw%0#$`hhn-#_xXO=Fw}eYxVEv*a#gb19(@UN0Q~@YF^|#kgA^g@x;=vpz|_dih4k414<4twp=M6%cLMy`k<)S z*fQhUa~raIBQG(vCP*skMSPP2S+*Y3Lk@F+B5KK2lIVKa4FQltNmv@OCB+K#4w#rR zX|RwupMl-WAk-+-ov}QFeOL<~(Mvl_I|U{!&;0Nmh)H%<8X)sXRqjDHX!VSXMNqF2 zfDnKsV-jaUv~oeKC_t-@f)p#dNK#iac5kvgVck zlmbjSUh1`%7(%|t55`2l@|NrC@7~|cp8UqWv&(nSs&QqxOC|N(o>YNB%&-KHiO61j zf)Q_nkb^cJVQzeUV?Bo!r?P>wRGZ^EC!%9&&2kXei4dGpk^xIB=E6STG*n$n2gbH` zVC@&?NVWu2%Os-cJ1(pxpmvu@_j; zv~+WI2dJ~Qf9=yWMVbH;GXgWCv=t7|avYq@@zkA%m?R0V?QP<*%bU1;=N{G)i#I%R z4OcJiVP|WE^>T`{yuk4y$H}ZfURJPNGv(UGmeI@p8?3gz7TYIK5`ct&1cZbEG6~YO zp0FEZU>P`@<*=u7kO8tR!{xOM*PpnCH@)f_4rV#-K0LwgJNNP8y#w5N=>ZOpXE>Y9 zk>@!WwHF^Hgd|{??ajQ{w?+0EB+ELc%9CXep0%t?)xJTKK6J7XWw;5>^}RI|$Ze4H zm@dVS_44Fsj~Y(I3?P_cQsEFiiiYXvy8ebM6A#qlL-h{_J@B2~gS)XF3;2M=9uQIk zLDU<{JUfu?+%qE?l%=pmRWs+n*x9{|(YSsO^Tk3bEF8}{_WgSKe63En4h`sryZcK{ zP(eS;-1S>i#deb{YVT18NMvRuzXBbbC`Z zDYT;`-MD>}|HHrctDn2}@y|bv?X6A6%fvd@A)Fiuj$?vre2{I5+S>LF|p4aC-Tx=3Gu)P_dW~B zZ(9X5Jwye@e73;s^b98_r{YiBL0F4N#U9in9_Z3uFo7`rZy+`@~JW>#a|meDLkBtKRV+<}W6| z3$vnp;p}YwU@@PUm96Y3F^#8d0!k7Xs=+y@id0nfKR@-EW2^?x?#sPO;$tYb zo;;aOzi`QyiUxoojtQ6|M)pIDq_A0bj}l1|3k^;awUmq%A|I+3Y~TK zEN*Z;d!UJrDX-ui6=I<<`;FxaAP_rj1`af8(dlR4dcyK13|kC}P=`=Di#8smsBAL$ z3mo}Re`rTMy_QW1+;_*DpezY~rhuOWbMWiJULXoA8loMCym^=fV>V=B%=*NR3?|$S zSWfK}7|mhn`Kllug~B_u?WDkjIljI$xWw?lgmBZsOLs3V=V2^Q+7(PI>3A6Ir+h}4 znrbS^cgNt0g`eJKX}VULZ*zex3Rek2ZW}<%!lO>&n|@8ef|B z_iAQn3y@n!c1uoiPXV7vYQeBpnnT=3Zz+NUP*C=kU$Vc>$@?$O3!gLC$eU@?v*#M87jv)Ng6A~++IYOTG2 zMGdW~IOKscvZ*3z0pb950_xW!JNNx(0(rvR*!J!xSBleyB>lz>@9=`9v_ZWiU#!77 z11c&U-n$KAVCT|hj3zZWSLC@E)_LjcBEX2fy@FKmLd-6S70Zf)>KQfuC4j3OYrE^X z{p@Xg?33RZeb<|BzIT+Fk5wfK!m!Oxyk}{YK&yuT@qph*pytz!xbHLO+z-o0je!O2 z{=$=AdCx!kjV~wJcmx_JKvCA=n(ok)S{b}+OMzY<*c4N?{HSZW{hxM z(^(Bf{VDsVuLtF%ZzOq=eCmeCviD(W&mfYDtbOa@Oy&bryQbyV~8BfLL%LU zg2jBdV_roe8=#O)K@bD+`uecdPiP{>9996bqv}YPQwGnIbUH{Qh}`&qK`Ecd^p3*7 ziyZ)FLbNnelS+3OLhvEE`=Pf!Z6pyIT48lGB24o?n^*$0g8Im9m1BR`_m$D0A|e5i@QIZ{hwe@^Fp z-FT^3!p|LXL2Ffv(K!>AIsU{v*SGMd%&t3E@b=IUIv6o9ww943tJ4_Chod30u2hla zey#I!T42*n?G%Z!a^`-)y;e0X96%^3yzevn8VHJV6lr+^QsAp7`krfx@`27}z;m5f z47s2VxRZWpd7j_f9cBObt?L^I*zS)(Zkk=-Uw!gGxN!4Zu0=JOqs)~lDC7&FMuBR@=jWwgx{Y;H?6C&}| zmDM%vv7?D&hb9+na7i~89Dx+1cb35J$xjyMP#8~OzX>(oL*9l=5UG8n4mLFW?uy@Q?9}`-hFpU&ryRz?}!jc=7H-ym0qEUb=sX!-Er?&SuE-9Jwt4 zGDwW+f+P%ekW1s~)2~Ycfg3NaI^EcMb)O%X;113R!8j$B4F8ZXFL{1XL$qagz!G6` z1=F>7dFjS_;!YZM7VK25jUxnrMNgXFgODQq)J-X*fuJuqUYe0gofSL9y{~eg($^3* zJQI5k4I58%%sG@65ANIs2-w-XjLCEiwiZQM%3g@W)1+mBRaqWz$K@fqf z3N#ua8E5#_&wOL!PyLa%{JrXRT?SMkd`fS(9OgiXsSU0?$TC6 z9&m0?&Ej0=0B%Rh4=y+za)7t~>-D?A(tlRjiU@KIIl!c2DdG+Twa1tAFZ8(z!NjCy z64C>5wPblQU*P2U6sJe0DD&DkFde78VuV@wWX2LYd=DmGsytdju|zE4t`-C~jb9?_ zkO!g^ToH;z2`);kC&1&^_VLCyJU09O_q?h2p0_@6{MKue?=!%0UKESjtT?q5swAnC z&dHE%S-D9<;EKdP-+SEwUUop&R+0CZ&>6Ql>Qye;gSE&_rQs#L+e=J7++#|#D z0oVfp!G4me+JJZ{JDNH8Caua;bJ>8pe;?jk5Mbg)QC8=jvf=f|!O>Wt@!_RUW2Llc zygbuD3b|qc5t%Q&c5m{IooV{teDnV7Loc2bdwI!766#=L+sZDT%7y}#DeSG&-q(^9 zTh$5xoCRa8Ju5By0a-5hz&h-`+FQn7yTo$Zu1(9y3wCJ%C5I-{$z>o0X6?@~s(2!I zO)x2M+yML8Ye8kKoKEx;3W|XB0BPN?PTFF}XN8Pdyyap#3U#HZ({K*HGE z*}$VWui?>0ZeaiBb?jc=$L97Hrt52%OeUC)NA>Ap+kuR&ec%X4lUg2=w1&_y{kvoP zk%a5**k5bfV8Rs^d4aR}qGQC3k`#~cZ{pF*TbRA>CJv8hxPN$pyY~-q``!cGe{h6@ zqci059A!~<_sb-vF|Do>Urk_Q*D*euWL((@Xl58Yp1?i}lVicqI(St~lC6@5v)A+z zBMfwt&tf5+%u3;6fV9sV$vq)pj1tEERD7dg^|55MGl$Y({Ru-maFR8OtA1g{nj+^p zh5!>5LlFuDF+gU*cyO$n0Nf_;)$7dEK?SKnQZWwh-hs8irAzymOef&8GZbYd)G{o$ zc0>Y$)$`$iy%`HxA1h|J=6bQO&4RTSYnOKLrRVSAcmCj+wRgVe@@A6KVO6mhkkbXw zHWIaqBO{9hTTy^zZLN(uYkYpLTbv6qLtnLH(XsO@gtDub1cgXxugW*FQ^&+i_7enTsEq$120Kz(8lZtxJ0 z6GT$Rm_ux*L7bZgOb}=Y&X=R2IEY2(3JqKhhoJyNikCikT84v$B&5_cqvM4g3yVku54f^3w=isz!CD~9GRtK` z?f~B}s06UbeyVRjQKjBHj@ZviWeSEj`1_C2$5|(3QhQPf49rC%STX?v-g~+M(tqZ@ zqvWR6>2pCm17|-_d5*nw)@ft%rp3st$@iC|Ov*>JPI}apcMi=7RtQyOVis5^)cKeP2 z40q0oMQJfBa#SaC*v61NOAW4H*~b2*ZM^338#p|j;laaWJa}+~`}YrVbbN-hmQ9z} za#@p@1~@bgIidH>bAc{a3^Iazm--+H1&oJ=$W9Ot^?I||v?QWH@L4ef=~~|TA=s%} z0?VEvt`ClJd@Hq03R5Uhhdgt6H|06tve5w;VDdpw?J0#gGSa12f_*<<=CC1a$lX_^ zgNl98X}aMV_&4B1LZFIqc=tBQFn0I$F_}(Lou1VUO(Bc*Q%)(!;Nej2h+xW7zyO_v zw-<{cK2d+m*;-`dDNgSn;g^5!Yug|G!0SKp`kPyCsjNM0tsN3N=U|u_)Bz*VQPuI9 zPpdozX4W38((akYfY>522Bqcg|KD$X<&l5(iKl2|b6S7qs+3>^2j?du+1LYK0z4pg zdQNFM5C9U~9!h~yPy+_3Fm;>xN zW*%PdBz@Fe!M&$2(XWCrAWBgc6^cAZ%8VOZYk2ePALS3e`whoG`rbDkzxBrY_W?LA z%Ia`l@|*}r2}rWqx3%$kwdC|;L=;gTQrvz<*_ycnQ`gf_+araTt+b@(U*^&0*{uvy z7hYZ~;uiQcF#8WFcLu>Y4w8M+^Y;7Erpxi5;Xr(?>wANU7s0A@s(x`%v`Hlqh}ky~ zeX5GHSy*}S)!sm1dTPi+X6TwVz(BqL%x`Sbdhyi7VRw0M0qBNQw8e%+JedgLzY7tz zjGIi0C~NQ_rNGT5XVTWE$x_g#kecffjUR>l3F67F z9hjs*A|FZb@2DXIE97Ew0!=zebOit|1Xc|u;Br`dD%lmTl!K)|*tsX5;qU3*)n=o1 z->)9WB+1Va-VV+hV@Ab{w7vcT5Qs=NxuKM&&h(u)C~M|M@CpD{6-fkwW!ohHlIH_7 z&?Hef6`II6U=u08Vo4By(Q zKMCJQjTzCTOsK-MeImwf0Mwz{?hhewF1Y6f(`m#1PMtiDoJlfJ#k z*s(0jA(qs+{{2((7kHs|^`6u=Br;6F4RuhVkJ^lxB*M_G84fh50Wo|x1Li)!fIFFo z-w_I2uX|PoZc2$@B?v5#WCExQ_Ow8v@zY@uVCHi3e0onOCqSqDZ&eG_-hBpKR(SaE zKEC(6-i6}_5ApTi{S>N%k(d&ON@QD8WSe7fS)nQ_zs%)mC# zq-EFHewVJ;BDZx4ZDnmOfo;BTbCP2Fk$pUN^GeOOn=NpBa)yHkhq!_fYCO1&HXam1wB~Ho}Uuya1d9m#t~(Ki)OhCx%FrpN=zs6lE+!3{Y5-XG?SB9 zO!cLaItkWbC!2Q8bMg`wIoOnlFu4I+RnosdIzZL~01C+UhA<*uu|qM>)u6uxFCQu^ z23+^=rk4L7+^s$5b}wJWv>M~=Z0;}^$&*@XsXF`gbD5_>ZkPmzLpR{NJPZF!=R9BE z-NGlnatpung=aQjck``NA{<)VoUbO#W{cK|hT2;Z_M38RYf{P$b??a;a1IXysar9I z4AO*de&n;yed6zZ^o!e-wV1Atz_}HkZRBM~l9x23|) zRzgxj%8G&Lvmip8YVtVp?jFHjP`pm8pimGh^^#TLUO|!MN?vhFtBXQMIbfPSyC^Hn zPS0?Bc!c?E0Ruud%AD7c`)-UQpF`mZ@@(fNA}5y(AF=^rp(YT|pkXk!>~^j!@A;;*rP+65fiP4Q#mfTbhLb^Yp{9PA4ac(CA81yq$_D&fcxke# z`)8fp!I-W{q4QcZkw1X5ZB)sST$V_n?7gCen*+_%#-w5jxf;VNG+^rP7h#E4Tb%OQ zeoLArEBHjknrQ`AvM-?=V&(8aa7MD!1Z>IOa!ovW2w0#n@TLw$%k>5T zE0JKA-9u`8cy#JAaFGnIx{)HLnAKk(Y$1@@Xf4RpUPYZG0DfdS1tkO|&jh7ix`+pzyNv&)p-80(ZjN>=K0J2fKomcd#Z`?ck z<)t3IyG-5*bqOm)0@qoMO6@K-W~dA}^4Q z$EYgCKmSL+h`V3>0+Nw|p$e$V?piD*%~ZeHTuTNQC2V2q>zJ$|+uXv{Ygcjo##KD> z*fm_gc@>XbyMjx5+t}P#!(^1g7=x;+kQXKLszjcbSQG`Ssww_!6s{kY5TmTP2H==c zl#HUXD7pSK2EkfJW$P4L7mUo(6j_qMqzTvpiz3JA$s7+J9^v5O5$?V85C_L6n9p() zMS-fUKZ|MhQ)8%+Cn|GuR9`cbA1%UjM8ewE>y>Y^f~rd5*+dT)(u9cfakmC;$F;zoYumcfaQT zr7^ybZFN{$J|fhq!t4z13@l9Z*({L^7bE#RtG3sOglY~pR;s2?Ow#*y@{%CD2q@Qn zuZt&Ke2?(^f-rAL1BT(CJ!XcjZ1;|bEd@9lKIqEf$qqE$Ai9{bRQqYFs={Ki5c0XGnNx_x=WH;>7)&OU3o)cTK-pT%=kpqfAs?6|B?;1j zz%H9AN$Tf#p*PY~elX_qxzAYBYb`KpW%kX3*Vfivku_fwB^HbMa#qfg2?L`}z3bQg z%loG+%Wi$G{PQ&Jy!r3(c#JH|UXlA%mL>8$7v2FHgc*#-c0S4|8@*!JwOB0bqT0|v z0uVdiS#jWJGMV@+uX7fx%>BE_%C35K(^40-O0YJazI@vQ0F|{^%;$q)8nUT@^pa~# zr@@TrWP&73Uzul^FHq!#H!)+y+9RTXGOKTFjWHNc#-iYMK`qw<&9Vi7lEj!xcTb9+ z{^FhEKXvOkzm!*KKzr>xpc?455fL+fDQ%Gej}Io=2K4sg%^ZNiGJ629R@N~>U>{^t zO(d*AxTn|&P|=FsoyG5fACVH2HoO1cH?1lxf}Nk$f(}Yr7mF%!oemi-c*KHq!^$K( z)Z7QJS2f-o7(lQiS7O8xswFc{0?Ym0Zk^A=mH`9gd5-CH19$E}#IOD1pU2U+o`#v$ zk*T#cMBPZB@n?fcKuHRdr7%g>`Fh#BLRBzqQ38g6@dz82c5vhRHN5&&kKpEwE4X^~ z5_WdCu)Vd0wY3pONmj3?szOm(EXopjUV~&sWl^zh20lPhR>-YIS=BOFcGhfV9nhq* z-8gIW#DW!$I;O#9y~n8;lVNPKRm|4!7)zH&QRuMr?Ma-Bxw&!l1p>c ziS;co987f$6!UJdL%_U>LvivhAOR-;Oo4u#Um=Cer@t}V)t7k+|gA>u@_ux#sYjqxGRn2X^bp0_* z*ETUfouR4mFvJ%S6a>8PCXruCbtfY|MYMElTZKE zpZVAq$7^e2z*I17VF8%VZ_rt5hWelB`h)4-N?4tc??ULY$H-xmJIpoVH8xa_sJ|yf zoni-xBRVVs0nbZqMWonyq!|6KDu>*#ix#95V6YD!Y4DGA;1UtY7%=KV(&^DDj!#a( zRf!}?ni`tLmZhE12ugv*aCT1i>N^5^Abr@(U%glD5SUPN*-5Gj4eGPW0?6kDsA62- zUdQ|2{@U4}`Tlnw{OEgM^W)(XYsdZ>vS^hd#RVzUl&;9IYfx6V6gF^fR-<~ zcMV#k5ut2wrBMHub{h#tJg`E3Lfy?xJR{Xrq-{fA-!ds;@!CWG7}xY#qycg;YFZ}A zgkzT5&4s8jOpbVn5A!`p8ih65Z4GbAx z&Oe%2<}$8;kUyjAJ4MRWoh}S|`Ak!3yiXnjS0!@Bd{R1DAbGU(mkZEoX0+*Yw0xNo zX?Z?TBZGH=6ngp3QBBy$t6c*F4)P=1qwHUAa8P@p5TU9p-@USt{^aG2-M{&jJF_Rh zdiQwmU{Osi6B06!{EfcolF6JTB%Pps-C1t$vkfY;{1Feg`DR_=%H9N{vM4ieb_W$H#}bw!ec9{OE`9 zZ+`CQvAA;=G#NJ^**HzSso!EIR3+DdbCQCx6g*jjNk%Z-nBXrKID2q{uig4QzV_tj zfW!bB6YOknO=`92}hB?!5Tzv}uC!uZjSE?5p=>jv@u1 zJMz8X1j~p*B|~7=E&aUo9_xbgB%DIr2DSP6lt6{WOD{f$z3Y!)GF^u~Et|nM`vaMP z`x9wDK+Vx}&jK$hkc9a>I}fmzSk}L#EK7{m)^O|AZT!pM`NG;eUw{2Cyyp7m-<;3O zxoK=TTH6YN&p9=-ArthZz0d#>1Vf2P8129SwH{2XB1jXIla(_fAHdW6njsWj2`a0ha;d}moF z3~RQJ>yIn;EY{YC^?R;dHdsh@S;+lero9M@a@d^|XwExeyBP+RmBnnf!12K`%0*r) zYMNm?JF@T|vuDwV2;MCF4SvhAr8fL0qR0V}^Yh8y4ACI5bt>b-+#c}N2(!|d2s8fTy{iKwI);)FQl>Uh;tTycp3i!%2! zb$zOXjkNDT>OxbrLK@x6k*%&4qfO`k9khfEr~ZEM=QDu(=UYEVib?=w`Qp49znY~k!$Oke>nbvSWm?B4N+{=^Y0FAw8%Febg z*+%39KCo*A5{lf81fN)!anFi5Xc=ad;^5!_uYU9~9DL}9@acd0OQ=qcL6dPO4+A9) zb1ng!r2cQ18lh$r#xvLz#`R$SmeniM%L~R~3rN*6Yr)<15?x z>7cu(oVLJEQZtO%qOgp`YyqySE)c>PgG)P8T-tpUd3gh8XLB5%%y4jcjE4`8Yk#`a z85VhgvM6CoTZ4SvpoaSCzjd3vp0Vl;Mc5CZlI!0(shu25Gxz>(;K&NuE%fgaN0=dz zXe>btiZi}K+2)!t2!|<40eck=`Dq0#HwUM+(iYz{40omNP_z z+#w~Hvc$vN&tvE64U8vKp}5o3Zfs(!x5m_8`*HC78@20GXV3gCvtnc@k4N zwJrEAxC2aKSxIinJE^p2j;0ASIDk8|@FSP7X74XOpXWF}JjML<3~UHVma6DtQi;^8 zo?pPT%#-d*<{>xCSQ5#$6p+~rX2p%QjQL#F<*H6A&2tUY-7&Jj5rzmHCSSf8 zdr`xE_9BF-o36s$NZV;Fh3K46o)cll)_I%TRlx-s0Bqe!DiK3@Lk|LB+NaL-4uLXf zZ%>qddQH8IZAEW8>4`;yQgN@q3LH`)Ux_FH84YPc#0+=^?}^xRCMlU%_UyumqSC8C zdiMlamcgk92byDc-h&lE8r`4-0_9c*gIL_dRxL;_kWW0kkGdWe2*=P%V`$}95Hx;f z0TL|rlBBS`fdr)4vscphj{@mWy(dgBABv*90=RBuYmuOyRec&)-&InKcWz$#`LDcq^yF7xI=gweU?8c4@CLM{W_s7!yMuOSgN?F4}holQaReE>QGnSd^9*=<>xl(tewoE$q936x2x2Rj4 zqLsLj*($bv=i?$7HN@=M(KCFnaBpws{yX}lT$T0;*Yi%#t0L#Kd(KDBU=9tHn1%cq zARtL`aCC@wzU9rB9UtNI|LP;)`5b0#+DI)8kR$+-y4f)Gf2?aOzyP?c8FQudQ8P&b z+MFWWm>}Z{R2Hb_1h`C#afXe_7}xjr zP?S~OAfC=}aCnNN!xJ1GpWx)=4D;C>MNxMCjnv5Dj4=(TfpqEtbWlpS@~uos7K5Hn zV&|`+KrDqvI8!tq;S(xqKwjpO>U*;DlG;(EXtM+?#n0eK;Gv$|vIAa-pkjhgH*#fO z?Cx^mUVJ)VnPJbQKY|R_ndRXWylXqa+B4a-=}MsZq10khq`^E|5~dD5&Y2)zUS!w@JHWw^oBk20j{dU z(^+}SfKAi7cWA=GdnsxQcChT@X$U_d4jbPi;NeHZp6&7Xm!lyip#X@GRhsZ%T>&Im zrJ|=LzJ&}un2lk9zK8s|a2xJPIj;X)OVCJrf0dBoB$k`VGp~FT5S(KBn==8PM3z-B z4K1M|4E9f~@iLB>1}=HX43Su(*{c+5&?EglBT)5(nx$nV8CDGeyN;#jb5rzYngo@8 zb^h>=I5-=K@fw0FI8fuP7@!x`%S-U!=~ocp^5zKh*qn zoYbkTIWca%>H60Dw%13W{L%|2JNM3tjiRDPCTHTbdj)EVkO?>iq@w=_*_;q$u|JFu`#Q6lUTbEcJKtBP+X1#Nvy?ORUneCnASkJ* zWHT7Y;=o4!d^O!oRQnod#@x!{cLo z;QjBz+36|1@oT>hUX(CvDJ%gXQ{RJzIS13Q<~u^ZZLYKGxd&_k%cyJ#a0Rjqpk}Jk zWQ1foMgjuL0=GOYmvW>{OHsw$LKiK;5Q2DSz4zznYHPj1?G0F1O@)>&&|El?E|7-iRxjj{y$ zt@O3Bm@kSt@%7;$9v&Ry;P4b@vl;S5fwHQvp`toK!ql;pPGelNXJrrzwOW7SnI>8; zN6)B8kRCkd)+Um<))Nzc-Dt#2r(f`H-&5RoFS=-d_!Nx}oBq^gq<^U&zL zUG-0F0ZcN;ivmXn$C#bYK!%WJSr-!Mor~;SK2U@$>k?!*z&nzuq9mAdq)NJ0e(e4k zS#C1y>pw4x5>;-oHeJVezwa&hi+}8UZ~c33zx;l~_NXYTBM^{fsdMJDsAmN8n*;2D zkn&HzH!A@pD*$5+K-hv!t{s_Fj*uMG(&}QLK6r(E7AY<&aFTVTW*K9E<$0d{i=t^7 zA?vl85jkiOIVgL(NNQl@=d+WF7BwngvA3pWP3iy|y8Ku8lgAD~lYvRXPffE+h=+qG zNRDdy-)Dyxqdd6C)9DjP_~Wf6@;>` z`J9w`3ZQ4~k>AMkav^2g%c0354@@+8b*y4JOOsan89@sC@bxTX)5chT6#E$*QnFmc ze~bnXCLbsloyGtwYxp|)$s>iO!snFwyUAcf;M{p}>~L<8Zlod@C(-%s3%cL{3dW|d z0#Kd{kQ+$X?F(2EjM}g!cZGL|hg8<}yIEFzYk!)&_5H8fdh<8#&3@rq4`J-Hm?Ts)_*UYwj3Wq!QyAHM`4}l(WHo#*5G8G$*)PVCG4_|y9 zyVo8;lBKBfvh!~8Z6zY%9KsgR2WGSnou3;MKxvx&z~(t7n;ZDOufB+1{@t%`zWp^< zHb+TvSX8!_i!Yl!D`nmCVW;2Ccjq+wilO$@>VAbWFr(4_FMi^if9q#|<#TKEiZR{F zz(vupkR&KaL-&Y>4szGiPlo*TU>SKfGhPkko@?Z=RW-A@`)Mo}J^a8Va?U;S1+$Uo z-!ms&&pCg&u`lan25W~$4oY5v%a^%Vp_zJ6iL=u=jt`D%mRy!dz?;MGK#;BEzCT2f zq|N}Vp8Meh1-3ZSNJ_VNmo>Aqp6*~i%YlmU#H(-MPk;EkPX6N$zw6-U2HrngEN&4Q zq)E~THW7jYi9Y3?9dF1XwIPeJDssH8!!Pbo;_DtY^`(mx+Xq+c;M`xJ6G>IqOo8;LkZQ?S&=R7YWyMYjE;xpufcvLp z9BA%78p~=S3lzGX!L1%N534+=91V$FNdBQ7*XYfVRoytz8ZLcP^w zq!>6tA+N3Qs+Q)ofi2g=>)78PmVmfeK@KlH4>TaU3b3;TP~lJ$%fZkI3hb@J;9Mv+ zK9Kww@Oz-(Iz{x*b!7>pBIH-2iIO&ui~xYV!sn*J7d^yamImQW!BKl|kbjdWvLYC9Kdna;Z#kN9zpWqsv;s}+M5sr z(d5$Xy+PZ0$E!V9w!Nek1x_%9PKgwQWmlq4Js1v`&chLgvYS|}*+8Ua_71z)adhgN z%lOb$u+!Un%{bUMBH+fN@n`FB6^2UlObcZBV|ZB%91Sqi!01q#cV>aV3f?bn0j$a$;U zuV)I>2TquayC1t26M{MCD$6(*H2fz1i_)q@C_ul@Yw_N#9Q5Mgh%(oJ>lNNCw_E3Me2sB3UY&M1iA>4te&){P~D1i+=CZhqh{`Fd+U7o)@VlJ=xRn&n3XYi%S6SK^dB z@KVCfkf1)v)|2YeG8UtstrfSyqM&6*)Qg`&0T}F0THHcb8xEg0ft6R3ce|8mR|?vt zEDclOS25BJea$ix|FF`I&SR|>e_eUtzVH%Uj5*3;^b?G!E6}2ez~^E>^ErcA2B^_2 z(^$_V3nh?$uVP@dTX_aiF2s1sk@&BnS++VqDDL?<%xoqvQG1Dl_bbPcRTD?nArvH8 zORU&}@n3fyZ@d0Gj4@!#RaI5Du5XRszA+ws_^JD6f9si}`IW=mf-E61s-SRWrNZWo z`_Km0D%P@2O6*hNTzIng%CQY2wQoYK6mhEUw)eh))r}O(0TpQvHl3Ol@U`>S3QJ>W z)r_<9UqdjfPF-gk))0VN^-!^u1AA8Iq7z3%?xR>p

onii~7v&qAYE=dQmIDKqg_ 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�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≠K#W-b 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

PDCNJGmhk&;|evLeuTpE-S=)>gj_XDaua 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

  • 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≠TUa~n;>jy%4MJYcT<0Wk( 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%0A4=1+4Y7^|6M+#ckp2Co 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эA~>(G`*K?j7}4|noc-u&SC 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

      V8f}sW=t`w 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 s%w4Y`?DSyxGu_P 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 diff --git a/crates/agent-gateway/web/src/App.tsx b/crates/agent-gateway/web/src/App.tsx deleted file mode 100644 index eca7bf60f..000000000 --- a/crates/agent-gateway/web/src/App.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import GatewayApp from "@/app/GatewayApp"; - -export default GatewayApp; diff --git a/crates/agent-gateway/web/src/app/AgentSelector.tsx b/crates/agent-gateway/web/src/app/AgentSelector.tsx deleted file mode 100644 index 8ea4efca2..000000000 --- a/crates/agent-gateway/web/src/app/AgentSelector.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { useEffect, useState } from "react"; -import { Check } from "@/components/icons"; -import { DropdownMenuItem, DropdownMenuLabel } from "@/components/ui/dropdown-menu"; -import { useLocale } from "@/i18n"; -import type { GatewayWebSocketClientLike } from "@/lib/gatewaySocket"; -import type { AgentStatus } from "@/lib/gatewayTypes"; - -function truncateMiddle(value: string, maxLength = 30): string { - if (value.length <= maxLength) return value; - const headLength = Math.ceil((maxLength - 1) / 2); - const tailLength = Math.floor((maxLength - 1) / 2); - return `${value.slice(0, headLength)}…${value.slice(-tailLength)}`; -} - -function agentLabel(agent: AgentStatus): string { - return agent.name?.trim() || agent.agent_id?.trim() || "Agent"; -} - -function sortAgents(agents: AgentStatus[], activeAgent: string): AgentStatus[] { - return [...agents].sort((left, right) => { - const leftID = left.agent_id?.trim() || ""; - const rightID = right.agent_id?.trim() || ""; - if ((leftID === activeAgent) !== (rightID === activeAgent)) { - return leftID === activeAgent ? -1 : 1; - } - if (left.online !== right.online) { - return left.online ? -1 : 1; - } - return agentLabel(left).localeCompare(agentLabel(right)); - }); -} - -// AgentSelector 渲染头像菜单内的 Agent 目录。目录由打标 status 事件与 -// agent_list 响应驱动;单客户端显示身份与状态,多客户端才显示切换列表。 -export function AgentSelector({ - api, - onAgentChange, -}: { - api: GatewayWebSocketClientLike; - onAgentChange?: (agentId: string) => void; -}) { - const { t } = useLocale(); - const [agents, setAgents] = useState([]); - const [activeAgent, setActiveAgent] = useState(() => api.getActiveAgent()); - - useEffect(() => { - const unsubscribe = api.subscribeAgents(setAgents); - // 主动拉一次目录:离线/仅签发凭证的 Agent 不会有 status 事件。 - api - .listAgents() - .then(() => { - const agentId = api.getActiveAgent(); - setActiveAgent(agentId); - onAgentChange?.(agentId); - }) - .catch(() => { - // 目录拉取失败不阻塞页面;status 事件仍会渐进填充在线条目。 - }); - return unsubscribe; - }, [api, onAgentChange]); - - if (agents.length === 0) { - return null; - } - - const handleChange = (agentId: string) => { - setActiveAgent(agentId); - api.setActiveAgent(agentId); - onAgentChange?.(agentId); - }; - - const sortedAgents = sortAgents(agents, activeAgent); - - if (sortedAgents.length === 1) { - const [agent] = sortedAgents; - const agentID = agent.agent_id?.trim() || ""; - const name = agent.name?.trim() || ""; - const statusLabel = agent.online - ? t("settings.devicesOnlineStatus") - : t("settings.devicesOfflineStatus"); - return ( -

      - - {t("settings.devicesTitle")} - -
      -
      - - {statusLabel} - - - - {name || truncateMiddle(agentID)} - - - {truncateMiddle(agentID)} - - - - {statusLabel} - -
      -
      -
      - ); - } - - return ( - <> - - {t("settings.devicesTitle")} - -
      - {sortedAgents.map((agent) => { - const agentID = agent.agent_id?.trim() || ""; - const name = agent.name?.trim() || ""; - const selected = agentID === activeAgent; - const statusLabel = agent.online - ? t("settings.devicesOnlineStatus") - : t("settings.devicesOfflineStatus"); - return ( - handleChange(agentID)} - > - - {statusLabel} - - - - {name || truncateMiddle(agentID)} - - {name ? ( - - {truncateMiddle(agentID)} - - ) : null} - - {selected ? : null} - - ); - })} -
      - - ); -} diff --git a/crates/agent-gateway/web/src/app/FileDropOverlay.tsx b/crates/agent-gateway/web/src/app/FileDropOverlay.tsx deleted file mode 100644 index 169112666..000000000 --- a/crates/agent-gateway/web/src/app/FileDropOverlay.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Ban, Upload } from "@/components/icons"; - -type FileDropOverlayProps = { - canDropUpload: boolean; - title: string; - description: string; - limitHint: string; -}; - -export function FileDropOverlay({ - canDropUpload, - title, - description, - limitHint, -}: FileDropOverlayProps) { - return ( - - } - > - - - ) : null} - {workspaceSshTerminalMounted && terminalClient && sftpClient ? ( - - {translate("workspaceSshTerminal.loading", locale)} - - } - > - - - ) : null} - - ); -} diff --git a/crates/agent-gateway/web/src/app/chatDraft.ts b/crates/agent-gateway/web/src/app/chatDraft.ts deleted file mode 100644 index a125d7b4a..000000000 --- a/crates/agent-gateway/web/src/app/chatDraft.ts +++ /dev/null @@ -1,129 +0,0 @@ -import type { - MentionComposerCommitMention, - MentionComposerDraft, - MentionComposerGitFileMention, - MentionComposerLargePaste, -} from "@/components/chat/MentionComposer"; -import { formatCodeMentionToken, formatFileMentionToken } from "@/lib/chat/mentionReferences"; -import type { PendingUploadedFile } from "@/lib/chat/uploadedFiles"; -import { withPastedTextDisplayMetadata } from "@/lib/chat/uploadedFiles"; -import { importReadableFiles } from "@/lib/uploadReadableFiles"; - -function buildPastedTextFileName(paste: MentionComposerLargePaste, index: number) { - const baseName = paste.label - .trim() - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .replace(/^-+|-+$/g, ""); - return `${baseName || `pasted-text-${index + 1}`}.txt`; -} - -function escapeComposerCommitLinkLabel(value: string) { - return value.replace(/\\/g, "\\\\").replace(/]/g, "\\]"); -} - -function formatComposerCommitLinkDestination(value: string) { - const normalized = value.replace(/\\/g, "/"); - if (/[\s()<>]/.test(normalized)) { - return `<${normalized.replace(//g, "%3E")}>`; - } - return normalized; -} - -function formatComposerCommitMention(commit: MentionComposerCommitMention) { - const shortSha = commit.shortSha || commit.sha.slice(0, 7); - const subject = commit.subject.trim() || shortSha; - const label = `commit ${shortSha}: ${subject}`; - if (commit.githubUrl?.trim()) { - return `[${escapeComposerCommitLinkLabel(label)}](${formatComposerCommitLinkDestination(commit.githubUrl.trim())})`; - } - return `${label} (${commit.sha})`; -} - -function formatComposerGitFileMention(file: MentionComposerGitFileMention) { - const refLabel = file.refName || file.shortSha || file.commitSha.slice(0, 7); - const label = `git file ${refLabel}: ${file.path}`; - if (file.githubUrl?.trim()) { - return `[${escapeComposerCommitLinkLabel(label)}](${formatComposerCommitLinkDestination(file.githubUrl.trim())})`; - } - return `${label} (${file.commitSha})`; -} - -export function buildTextFromComposerDraft( - draft: MentionComposerDraft, - pastedFileById?: Map, -) { - return draft.segments - .map((segment) => { - if (segment.type === "text") { - return segment.text; - } - if (segment.type === "fileMention") { - return formatFileMentionToken(segment.reference); - } - if (segment.type === "skillMention") { - return `$${segment.skill.name}`; - } - if (segment.type === "commitMention") { - return formatComposerCommitMention(segment.commit); - } - if (segment.type === "gitFileMention") { - return formatComposerGitFileMention(segment.file); - } - if (segment.type === "codeMention") { - return formatCodeMentionToken(segment.reference); - } - const file = pastedFileById?.get(segment.paste.id); - return file ? `[${segment.paste.label}: ${file.relativePath}]` : segment.paste.text; - }) - .join("") - .replace(/\u00A0/g, " "); -} - -export async function importPastedTextsAsFiles(params: { - token: string; - agentId: string; - workdir: string; - pastes: MentionComposerLargePaste[]; -}) { - const { token, agentId, workdir, pastes } = params; - const normalizedWorkdir = workdir.trim(); - if (!normalizedWorkdir) { - throw new Error("项目目录未选择,无法发送大段粘贴内容。"); - } - if (pastes.length === 0) { - return { - files: [], - fileByPasteId: new Map(), - }; - } - - const textFiles = pastes.map( - (paste, index) => - new File([paste.text], buildPastedTextFileName(paste, index), { - type: "text/plain", - }), - ); - const response = await importReadableFiles(token, agentId, normalizedWorkdir, textFiles); - if (response.files.length !== pastes.length) { - const skipped = response.skipped.length > 0 ? `\n${response.skipped.join("\n")}` : ""; - throw new Error(`部分大段粘贴内容未能导入为附件。${skipped}`); - } - - const files = response.files.map((file, index) => { - const paste = pastes[index]; - return paste ? withPastedTextDisplayMetadata(file, paste) : file; - }); - - const fileByPasteId = new Map(); - files.forEach((file, index) => { - const paste = pastes[index]; - if (paste) { - fileByPasteId.set(paste.id, file); - } - }); - return { - files, - fileByPasteId, - }; -} diff --git a/crates/agent-gateway/web/src/app/chatEventUtils.ts b/crates/agent-gateway/web/src/app/chatEventUtils.ts deleted file mode 100644 index ce251f78c..000000000 --- a/crates/agent-gateway/web/src/app/chatEventUtils.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { ChatEvent, GatewaySelectedModel } from "@/lib/gatewayTypes"; -import { - type AppSettings, - normalizeSelectedModelForProviders, - parseSelectedModelJson, - type SelectedModel, -} from "@/lib/settings"; - -import type { ModelProviderSource, TunnelManagerToolChange } from "./types"; - -export function asErrorMessage(error: unknown, fallback: string) { - if (error instanceof Error && error.message.trim()) return error.message.trim(); - const text = String(error ?? "").trim(); - return text || fallback; -} - -export function isAbortError(error: unknown) { - if ( - (error instanceof DOMException && error.name === "AbortError") || - (error instanceof Error && error.name === "AbortError") - ) { - return true; - } - const message = error instanceof Error ? error.message : String(error ?? ""); - const normalized = message.trim().toLowerCase(); - return ( - normalized.includes("cancelled") || - normalized.includes("canceled") || - normalized.includes("已取消") || - normalized.includes("abort") || - normalized.includes("aborted") - ); -} - -export function readChatEventTitle(event: ChatEvent): string { - if ("title" in event && typeof event.title === "string") { - return event.title.trim(); - } - return ""; -} - -export function isChatEventTitleFinal(event: ChatEvent) { - return event.type === "done" || ("titleFinal" in event && event.titleFinal === true); -} - -function asRecord(value: unknown): Record { - return value && typeof value === "object" && !Array.isArray(value) - ? (value as Record) - : {}; -} - -export function readTunnelManagerToolChange(event: ChatEvent): TunnelManagerToolChange | null { - if (event.type !== "tool_result" || event.isError === true) { - return null; - } - const details = asRecord(event.details); - if (details.kind !== "tunnel_manager") { - return null; - } - const action = typeof details.action === "string" ? details.action.trim() : ""; - if (action !== "create" && action !== "close") { - return null; - } - const tunnel = asRecord(details.tunnel); - const projectPathKey = - (typeof tunnel.projectPathKey === "string" ? tunnel.projectPathKey.trim() : "") || - (typeof tunnel.project_path_key === "string" ? tunnel.project_path_key.trim() : "") || - event.workdir?.trim() || - ""; - return { action, projectPathKey }; -} - -// 会话生效模型的唯一派生点:本地未持久化的切换(override)> -// history-sync 带回的会话持久化选择 > 全局默认(新会话语义)。 -// 前两级都按当前 providers 校验,失效则逐级回退。 -export function resolveActiveModelSelection(params: { - settings: AppSettings; - override?: SelectedModel; - persistedSelectedModelJson?: string; -}): SelectedModel | undefined { - const { settings, override, persistedSelectedModelJson } = params; - return ( - normalizeSelectedModelForProviders(override, settings.customProviders) ?? - normalizeSelectedModelForProviders( - parseSelectedModelJson(persistedSelectedModelJson), - settings.customProviders, - ) ?? - settings.selectedModel - ); -} - -export function buildGatewaySelectedModel( - selectedModel: SelectedModel | undefined, - providers: ModelProviderSource[], -): GatewaySelectedModel | undefined { - if (!selectedModel) { - return undefined; - } - - const provider = providers.find((item) => item.id === selectedModel.customProviderId); - if (!provider) { - return undefined; - } - - return { - customProviderId: provider.id, - model: selectedModel.model, - providerType: provider.type, - }; -} - -export function buildGatewaySystemSettings(settings: AppSettings, workdirOverride?: string) { - return { - executionMode: settings.system.executionMode, - workdir: workdirOverride ?? settings.system.workdir.trim(), - }; -} diff --git a/crates/agent-gateway/web/src/app/constants.ts b/crates/agent-gateway/web/src/app/constants.ts deleted file mode 100644 index 3d4dc898f..000000000 --- a/crates/agent-gateway/web/src/app/constants.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const MAX_UPLOAD_FILES = 9; - -export const PROTECTED_DRAFT_CONVERSATION = "__protected_draft__"; -export const HISTORY_LIST_PAGE_SIZE = 80; -export const HISTORY_DETAIL_INITIAL_MAX_MESSAGES = 360; -// One "load earlier history" click grows the loaded window by this many -// persisted messages (same size as the initial tail window). -export const HISTORY_DETAIL_LOAD_EARLIER_PAGE_MESSAGES = 360; -export const PROJECT_HISTORY_DELETE_PAGE_SIZE = 200; -export const SHARED_HISTORY_LIST_PAGE_SIZE = 200; -export const CHAT_RUNTIME_PREPARE_TIMEOUT_MS = 2_500; -export const CHAT_RUNTIME_FOREGROUND_PREPARE_TIMEOUT_MS = 1_500; -export const CHAT_RUNTIME_KEEP_WARM_INTERVAL_MS = 10_000; -export const CHAT_RUNTIME_PREPARING_STATUS = "Preparing request..."; - -export const DEFAULT_BROWSER_TITLE = "LiveAgent Gateway"; -export const NEW_CONVERSATION_BROWSER_TITLE = "LiveAgent"; -export const SHARED_HISTORY_BROWSER_TITLE = "分享会话"; -export const SKILLS_HUB_BROWSER_TITLE = "Skills Hub"; -export const MCP_HUB_BROWSER_TITLE = "MCP Hub"; - -export const MOBILE_SIDEBAR_MEDIA_QUERY = "(max-width: 820px)"; diff --git a/crates/agent-gateway/web/src/app/domUtils.ts b/crates/agent-gateway/web/src/app/domUtils.ts deleted file mode 100644 index 7c7f32056..000000000 --- a/crates/agent-gateway/web/src/app/domUtils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { DragEvent } from "react"; - -export function dragEventHasFiles(event: DragEvent) { - return Array.from(event.dataTransfer.types).includes("Files"); -} diff --git a/crates/agent-gateway/web/src/app/historyUtils.ts b/crates/agent-gateway/web/src/app/historyUtils.ts deleted file mode 100644 index b1e88fb90..000000000 --- a/crates/agent-gateway/web/src/app/historyUtils.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { formatConversationTitle } from "@/lib/chatUi"; -import type { ConversationSummary } from "@/lib/gatewayTypes"; -import { - type AppSettings, - DEFAULT_WORKSPACE_PROJECT_ID, - resolveWorkspaceProjects, - type WorkspaceProject, -} from "@/lib/settings"; -import { buildGatewaySettingsSyncPayload } from "@/lib/settings/sync"; - -function isLocalDraftConversationId(id: string) { - return id.trim().startsWith("__local_draft__:"); -} - -import { fallbackWorkspaceProjectName } from "@/lib/workspaceProjects"; - -import { MOBILE_SIDEBAR_MEDIA_QUERY } from "./constants"; - -export function formatTranslation(template: string, values: Record) { - return Object.entries(values).reduce( - (text, [key, value]) => text.replaceAll(`{${key}}`, String(value)), - template, - ); -} - -export function getDefaultWorkspaceProjectPath(system: AppSettings["system"]) { - return ( - system.workspaceProjects.find((project) => project.id === DEFAULT_WORKSPACE_PROJECT_ID)?.path || - system.workdir - ); -} - -export function createWorkspaceProjectFromPath(path: string, kind: WorkspaceProject["kind"]) { - const now = Date.now(); - return { - id: `${kind}-${now}-${Math.random().toString(36).slice(2, 8)}`, - name: fallbackWorkspaceProjectName(path), - path, - kind, - createdAt: now, - updatedAt: now, - } satisfies WorkspaceProject; -} - -export function hasSettingsSyncChanged(prev: AppSettings, next: AppSettings) { - return ( - JSON.stringify(buildGatewaySettingsSyncPayload(prev)) !== - JSON.stringify(buildGatewaySettingsSyncPayload(next)) - ); -} - -export function resolveAppWorkspaceProjects(settings: AppSettings): AppSettings { - return { - ...settings, - system: resolveWorkspaceProjects( - settings.system, - getDefaultWorkspaceProjectPath(settings.system), - ), - }; -} - -export function resolveConversationTitle( - summary: ConversationSummary | null, - fallbackConversationId: string, -) { - return formatConversationTitle(summary, fallbackConversationId); -} - -export function hasLocalDraftConversation(params: { - conversationId: string; - selectedHistoryId: string; - requestedConversationId?: string; - chatMessageCount: number; - pendingUploadCount: number; - draftPinned: boolean; -}) { - const { - conversationId, - selectedHistoryId, - requestedConversationId = "", - chatMessageCount, - pendingUploadCount, - draftPinned, - } = params; - - const isDraftConversation = conversationId === "" || isLocalDraftConversationId(conversationId); - const isDraftSelected = selectedHistoryId === "" || selectedHistoryId === conversationId; - - return ( - isDraftConversation && - isDraftSelected && - requestedConversationId === "" && - (draftPinned || chatMessageCount > 0 || pendingUploadCount > 0) - ); -} - -export function resolveVisibleConversationId(selectedHistoryId: string, conversationId: string) { - const selectedId = selectedHistoryId.trim(); - if (selectedId) { - return selectedId; - } - return conversationId.trim(); -} - -export function isMobileSidebarLayout() { - if (typeof window === "undefined") { - return false; - } - return window.matchMedia(MOBILE_SIDEBAR_MEDIA_QUERY).matches; -} - -export function shouldOpenSidebarByDefault() { - return !isMobileSidebarLayout(); -} diff --git a/crates/agent-gateway/web/src/app/hooks/useGatewayClients.ts b/crates/agent-gateway/web/src/app/hooks/useGatewayClients.ts deleted file mode 100644 index 8f21de7e1..000000000 --- a/crates/agent-gateway/web/src/app/hooks/useGatewayClients.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useMemo } from "react"; - -import { getGatewayWebSocketClient } from "@/lib/gatewaySocket"; -import { createGatewayGitClient } from "@/lib/git/gatewayGitClient"; -import { createGatewaySftpClient } from "@/lib/sftp/gatewaySftpClient"; -import { createGatewayTerminalClient } from "@/lib/terminal/gatewayTerminalClient"; - -export function useGatewayClients(token: string) { - const api = useMemo(() => (token ? getGatewayWebSocketClient(token) : null), [token]); - const terminalClient = useMemo(() => (api ? createGatewayTerminalClient(api) : null), [api]); - const sftpClient = useMemo(() => (api ? createGatewaySftpClient(api) : null), [api]); - const gitClient = useMemo(() => (api ? createGatewayGitClient(api) : null), [api]); - - return { - api, - terminalClient, - sftpClient, - gitClient, - }; -} diff --git a/crates/agent-gateway/web/src/app/hooks/useGatewaySession.ts b/crates/agent-gateway/web/src/app/hooks/useGatewaySession.ts deleted file mode 100644 index c9712470a..000000000 --- a/crates/agent-gateway/web/src/app/hooks/useGatewaySession.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; - -import { normalizeGatewayAccessToken, verifyGatewayAccessToken } from "@/lib/gatewayAuth"; -import { resetGatewayWebSocketClient } from "@/lib/gatewaySocket"; -import { clearToken, loadToken, saveToken } from "@/lib/storage"; - -import { asErrorMessage } from "../chatEventUtils"; - -export function useGatewaySession(historyShareToken: string | null) { - const initialStoredTokenRef = useRef(historyShareToken ? "" : loadToken()); - const [token, setToken] = useState(""); - const [loginToken, setLoginToken] = useState(initialStoredTokenRef.current); - const [authSubmitting, setAuthSubmitting] = useState( - () => normalizeGatewayAccessToken(initialStoredTokenRef.current) !== "", - ); - const [authError, setAuthError] = useState(null); - - useEffect(() => { - const storedToken = normalizeGatewayAccessToken(initialStoredTokenRef.current); - if (!storedToken) { - return; - } - - let cancelled = false; - setAuthError(null); - resetGatewayWebSocketClient(); - - void verifyGatewayAccessToken(storedToken) - .then((verifiedToken) => { - if (cancelled) { - return; - } - initialStoredTokenRef.current = verifiedToken; - saveToken(verifiedToken); - setLoginToken(verifiedToken); - setToken(verifiedToken); - }) - .catch((error) => { - if (cancelled) { - return; - } - initialStoredTokenRef.current = ""; - clearToken(); - resetGatewayWebSocketClient(); - setToken(""); - setAuthError(asErrorMessage(error, "Access Token 验证失败。")); - setLoginToken(storedToken); - }) - .finally(() => { - if (!cancelled) { - setAuthSubmitting(false); - } - }); - - return () => { - cancelled = true; - }; - }, []); - - const login = useCallback(async () => { - const draftToken = loginToken; - const normalizedToken = normalizeGatewayAccessToken(draftToken); - if (!normalizedToken) { - setAuthError("请输入 Access Token。"); - return; - } - - setAuthSubmitting(true); - setAuthError(null); - resetGatewayWebSocketClient(); - - try { - const verifiedToken = await verifyGatewayAccessToken(draftToken); - initialStoredTokenRef.current = verifiedToken; - saveToken(verifiedToken); - setLoginToken(verifiedToken); - setToken(verifiedToken); - } catch (error) { - initialStoredTokenRef.current = ""; - clearToken(); - resetGatewayWebSocketClient(); - setToken(""); - setAuthError(asErrorMessage(error, "Access Token 验证失败。")); - } finally { - setAuthSubmitting(false); - } - }, [loginToken]); - - const clearSession = useCallback(() => { - clearToken(); - resetGatewayWebSocketClient(); - initialStoredTokenRef.current = ""; - setAuthSubmitting(false); - setAuthError(null); - setLoginToken(""); - setToken(""); - }, []); - - return { - token, - loginToken, - authSubmitting, - authError, - setToken, - setLoginToken, - setAuthError, - login, - clearSession, - }; -} diff --git a/crates/agent-gateway/web/src/app/hooks/useGatewaySettingsSync.ts b/crates/agent-gateway/web/src/app/hooks/useGatewaySettingsSync.ts deleted file mode 100644 index 2973a94f6..000000000 --- a/crates/agent-gateway/web/src/app/hooks/useGatewaySettingsSync.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; - -import { - type CronSnapshot, - feedCronSnapshot, - feedHooksSnapshot, - type HooksSnapshot, - initAutomation, -} from "@/lib/automation"; -import { applyFontFamilies } from "@/lib/fontFamily"; -import type { GatewayWebSocketClientLike } from "@/lib/gatewaySocket"; -import { setPreferredMonacoNlsLocale } from "@/lib/monacoNls"; -import { - type AppSettings, - normalizeSettings, - resolveEffectiveTheme, - subscribeToSystemThemePreference, -} from "@/lib/settings"; -import { - applyGatewaySettingsSyncPayload, - buildGatewaySettingsSyncUpdatePayload, - type GatewaySettingsSyncPayload, - redactSettingsForWebStorage, -} from "@/lib/settings/sync"; -import { loadToken } from "@/lib/storage"; -import { loadWebSettings, persistWebSettings, type WebSettingsSaveState } from "@/lib/webSettings"; - -import { asErrorMessage } from "../chatEventUtils"; -import { hasSettingsSyncChanged, resolveAppWorkspaceProjects } from "../historyUtils"; - -export function useGatewaySettingsSync(params: { - token: string; - api: GatewayWebSocketClientLike | null; - activeAgentId: string; -}) { - const { token, api, activeAgentId } = params; - const [settings, setSettingsState] = useState(() => loadWebSettings(loadToken())); - const [settingsSyncReady, setSettingsSyncReady] = useState(() => token.trim() === ""); - const [settingsSyncError, setSettingsSyncError] = useState(null); - const [settingsSaveState, setSettingsSaveState] = useState({ - status: "saved", - }); - const settingsSaveSequenceRef = useRef(0); - const settingsSaveChainRef = useRef>(Promise.resolve()); - // Mirrors `settings` so setSettings/applyGatewaySettings can read the latest value - // synchronously without passing a (side-effecting) function into setSettingsState — - // React 18 StrictMode double-invokes functional state updaters in development, - // which would otherwise run those side effects (and any non-idempotent work like - // crypto.randomUUID() inside caller updaters) twice per call. - const settingsRef = useRef(settings); - settingsRef.current = settings; - const [systemThemeVersion, setSystemThemeVersion] = useState(0); - - // Monaco reads NLS globals while the lazy editor module imports monaco-editor. - setPreferredMonacoNlsLocale(settings.locale); - - useEffect(() => { - if (settings.theme !== "system") return; - return subscribeToSystemThemePreference(() => { - setSystemThemeVersion((version) => version + 1); - }); - }, [settings.theme]); - - useEffect(() => { - const root = document.documentElement; - root.classList.toggle("dark", resolveEffectiveTheme(settings.theme) === "dark"); - }, [settings.theme, systemThemeVersion]); - - useEffect(() => { - applyFontFamilies({ - interfaceFontFamily: settings.customSettings.interfaceFontFamily, - chatFontFamily: settings.customSettings.chatFontFamily, - codeFontFamily: settings.customSettings.codeFontFamily, - }); - }, [ - settings.customSettings.interfaceFontFamily, - settings.customSettings.chatFontFamily, - settings.customSettings.codeFontFamily, - ]); - - useEffect(() => { - setSettingsState((prev) => - resolveAppWorkspaceProjects( - normalizeSettings({ - ...prev, - remote: { - ...prev.remote, - gatewayUrl: window.location.origin, - token: token.trim(), - enabled: token.trim() !== "" || prev.remote.enabled, - }, - }), - ), - ); - }, [token]); - - const queueSettingsSave = useCallback( - (prev: AppSettings, next: AppSettings, fallback: string, syncGateway: boolean) => { - const saveSequence = ++settingsSaveSequenceRef.current; - setSettingsSaveState({ status: "saving" }); - const redactedNext = redactSettingsForWebStorage(next); - const gatewayUpdate = - syncGateway && api - ? buildGatewaySettingsSyncUpdatePayload(prev, next, { - includeProviderApiKeyUpdates: true, - }) - : null; - - settingsSaveChainRef.current = settingsSaveChainRef.current - .catch(() => undefined) - .then(() => { - persistWebSettings(redactedNext); - }) - .then(async () => { - if (gatewayUpdate && Object.keys(gatewayUpdate).length > 0) { - await api?.updateSettings(gatewayUpdate); - } - }) - .then(() => { - if (settingsSaveSequenceRef.current === saveSequence) { - setSettingsSaveState({ status: "saved" }); - } - }) - .catch((error) => { - if (syncGateway && api) { - void api - .getSettings() - .then((payload) => { - const current = settingsRef.current; - const refreshed = redactSettingsForWebStorage( - resolveAppWorkspaceProjects(applyGatewaySettingsSyncPayload(current, payload)), - ); - settingsRef.current = refreshed; - persistWebSettings(refreshed); - setSettingsState(refreshed); - }) - .catch(() => undefined); - } - if (settingsSaveSequenceRef.current === saveSequence) { - setSettingsSaveState({ - status: "error", - message: asErrorMessage(error, fallback), - }); - } - }); - }, - [api], - ); - - const applyGatewaySettings = useCallback( - (payload: GatewaySettingsSyncPayload) => { - // Automation snapshots ride along on the settings-sync channel but are - // desktop-owned state with their own revision — feed them straight into - // the automation store instead of the settings state. - const automation = payload as { - automationCron?: CronSnapshot; - automationHooks?: HooksSnapshot; - }; - if (automation.automationCron) { - feedCronSnapshot(automation.automationCron); - } - if (automation.automationHooks) { - feedHooksSnapshot(automation.automationHooks); - } - const prev = settingsRef.current; - const rawNext = resolveAppWorkspaceProjects(applyGatewaySettingsSyncPayload(prev, payload)); - const next = redactSettingsForWebStorage(rawNext); - if (!hasSettingsSyncChanged(prev, next)) { - return; - } - settingsRef.current = next; - setSettingsState(next); - queueSettingsSave(prev, next, "同步桌面端设置失败。", false); - }, - [queueSettingsSave], - ); - - const setSettings = useCallback( - (updater: (prev: AppSettings) => AppSettings) => { - const prev = settingsRef.current; - const updated = updater(prev); - if (updated === prev) return; - const rawNext = resolveAppWorkspaceProjects(normalizeSettings(updated)); - const next = redactSettingsForWebStorage(rawNext); - settingsRef.current = next; - setSettingsState(next); - queueSettingsSave(prev, rawNext, "保存 WebUI 设置失败。", true); - }, - [queueSettingsSave], - ); - - useEffect(() => { - if (!api) { - setSettingsSyncReady(token.trim() === ""); - setSettingsSyncError(null); - return; - } - - let cancelled = false; - setSettingsSyncReady(false); - setSettingsSyncError(null); - // Best-effort: the desktop may be offline; the settings-sync push - // populates the store once it connects. - void initAutomation().catch(() => undefined); - const unsubscribe = api.subscribeSettings((payload) => { - if (cancelled) { - return; - } - applyGatewaySettings(payload); - setSettingsSyncError(null); - }); - - void api - .getSettings() - .then((payload) => { - if (!cancelled) { - applyGatewaySettings(payload); - setSettingsSyncReady(true); - setSettingsSyncError(null); - } - }) - .catch((error) => { - if (!cancelled) { - setSettingsSyncError(asErrorMessage(error, "同步桌面端设置失败")); - setSettingsSyncReady(true); - } - }); - - return () => { - cancelled = true; - unsubscribe(); - }; - }, [api, activeAgentId, applyGatewaySettings, token]); - - return { - settings, - setSettings, - settingsSyncReady, - settingsSyncError, - settingsSaveState, - }; -} diff --git a/crates/agent-gateway/web/src/app/hooks/usePendingUploads.ts b/crates/agent-gateway/web/src/app/hooks/usePendingUploads.ts deleted file mode 100644 index 2af7b1b8a..000000000 --- a/crates/agent-gateway/web/src/app/hooks/usePendingUploads.ts +++ /dev/null @@ -1,393 +0,0 @@ -import { type DragEvent, type RefObject, useCallback, useEffect, useRef, useState } from "react"; - -import type { MentionComposerHandle } from "@/components/chat/MentionComposer"; -import type { NotifyItem } from "@/components/chat/NotifyToast"; -import { t as translate } from "@/i18n"; -import type { PendingUploadedFile } from "@/lib/chat/uploadedFiles"; -import { mergePendingUploadedFiles } from "@/lib/chat/uploadedFiles"; -import { registerLocalUploadedImagePreviews } from "@/lib/chat/uploadedImagePreview"; -import { - clipboardHasFileSignal, - extractClipboardFiles, - readClipboardFiles, -} from "@/lib/clipboardFiles"; -import type { AppSettings } from "@/lib/settings"; -import { importReadableFiles } from "@/lib/uploadReadableFiles"; - -import { asErrorMessage } from "../chatEventUtils"; -import { MAX_UPLOAD_FILES } from "../constants"; -import { dragEventHasFiles } from "../domUtils"; -import { formatTranslation } from "../historyUtils"; - -type UsePendingUploadsParams = { - token: string; - resolveAgentID: () => Promise; - historyShareToken: string | null; - settingsSyncReady: boolean; - settingsOpen: boolean; - activeView: "chat" | "skills-hub" | "mcp-hub"; - locale: AppSettings["locale"]; - executionMode: AppSettings["system"]["executionMode"]; - conversationId: string; - selectedHistoryId: string; - displayedConversationWorkdirRef: RefObject; - composerRef: RefObject; - // Upload feedback goes to the top-right toast stack, never into the - // transcript area — a failed upload is not conversation output. - addNotify: (type: NotifyItem["type"], message: string) => void; -}; - -export function usePendingUploads(params: UsePendingUploadsParams) { - const { - token, - resolveAgentID, - historyShareToken, - settingsSyncReady, - settingsOpen, - activeView, - locale, - executionMode, - conversationId, - selectedHistoryId, - displayedConversationWorkdirRef, - composerRef, - addNotify, - } = params; - - const [pendingUploadedFiles, setPendingUploadedFiles] = useState([]); - const [isUploadingFiles, setIsUploadingFiles] = useState(false); - const [isFileDropActive, setIsFileDropActive] = useState(false); - const fileInputRef = useRef(null); - const pendingUploadedFilesRef = useRef(pendingUploadedFiles); - const pendingUploadsByConversationRef = useRef>(new Map()); - const isUploadingFilesRef = useRef(isUploadingFiles); - const uploadDragDepthRef = useRef(0); - const displayedConversationIdRef = useRef(""); - // Render-assigned mirror: an in-flight import settling between a render and - // its effects must still see the latest mode when it decides whether its - // result is stale. - const executionModeRef = useRef(executionMode); - executionModeRef.current = executionMode; - - const displayedConversationId = (selectedHistoryId || conversationId).trim(); - displayedConversationIdRef.current = displayedConversationId; - - const setUploadingFiles = useCallback((active: boolean) => { - isUploadingFilesRef.current = active; - setIsUploadingFiles(active); - }, []); - - const isDisplayedConversation = useCallback((targetConversationId: string) => { - const conversationIdValue = targetConversationId.trim(); - return conversationIdValue !== "" && displayedConversationIdRef.current === conversationIdValue; - }, []); - - const getPendingUploadsForConversation = useCallback( - (targetConversationId: string) => { - const conversationIdValue = targetConversationId.trim(); - if (!conversationIdValue || isDisplayedConversation(conversationIdValue)) { - return pendingUploadedFilesRef.current; - } - return pendingUploadsByConversationRef.current.get(conversationIdValue) ?? []; - }, - [isDisplayedConversation], - ); - - const setPendingUploadsForConversation = useCallback( - (targetConversationId: string, nextFiles: PendingUploadedFile[]) => { - const conversationIdValue = targetConversationId.trim(); - const normalizedFiles = nextFiles.slice(); - if (conversationIdValue) { - if (normalizedFiles.length > 0) { - pendingUploadsByConversationRef.current.set(conversationIdValue, normalizedFiles); - } else { - pendingUploadsByConversationRef.current.delete(conversationIdValue); - } - } - if (!conversationIdValue || isDisplayedConversation(conversationIdValue)) { - pendingUploadedFilesRef.current = normalizedFiles; - setPendingUploadedFiles(normalizedFiles); - } - }, - [isDisplayedConversation], - ); - - const updatePendingUploadsForConversation = useCallback( - ( - targetConversationId: string, - updater: (current: PendingUploadedFile[]) => PendingUploadedFile[], - ) => { - const conversationIdValue = targetConversationId.trim(); - const currentFiles = getPendingUploadsForConversation(conversationIdValue); - const nextFiles = updater(currentFiles); - setPendingUploadsForConversation(conversationIdValue, nextFiles); - return nextFiles; - }, - [getPendingUploadsForConversation, setPendingUploadsForConversation], - ); - - // A draft conversation got its real id: re-key its stored uploads without - // touching the rendered state — the displayed id flips to `nextId` in the - // same commit, so the switch effect below re-reads the moved entry. - const moveConversationUploads = useCallback((previousId: string, nextId: string) => { - const previous = previousId.trim(); - const next = nextId.trim(); - if (!previous || !next || previous === next) { - return; - } - const files = pendingUploadsByConversationRef.current.get(previous); - if (files === undefined) { - return; - } - pendingUploadsByConversationRef.current.delete(previous); - pendingUploadsByConversationRef.current.set(next, files); - }, []); - - const clearPendingUploads = useCallback(() => { - pendingUploadedFilesRef.current = []; - pendingUploadsByConversationRef.current.clear(); - isUploadingFilesRef.current = false; - uploadDragDepthRef.current = 0; - setPendingUploadedFiles([]); - setIsUploadingFiles(false); - setIsFileDropActive(false); - }, []); - - useEffect(() => { - const nextFiles = displayedConversationId - ? (pendingUploadsByConversationRef.current.get(displayedConversationId) ?? []) - : []; - pendingUploadedFilesRef.current = nextFiles; - setPendingUploadedFiles(nextFiles); - }, [displayedConversationId]); - - const handleImportReadableFiles = useCallback( - async (filesToImport: File[]) => { - if (filesToImport.length === 0) { - return; - } - if (isUploadingFilesRef.current) { - addNotify("warning", translate("chat.upload.uploading", locale)); - return; - } - if (executionMode === "text") { - addNotify("warning", translate("chat.upload.onlyInTools", locale)); - return; - } - const workdir = displayedConversationWorkdirRef.current.trim(); - if (!workdir) { - addNotify("warning", translate("chat.upload.requireWorkdir", locale)); - return; - } - const targetConversationId = displayedConversationIdRef.current; - if (!targetConversationId) { - addNotify("warning", "请先选择或创建会话后再上传文件。"); - return; - } - - const currentUploads = getPendingUploadsForConversation(targetConversationId); - setPendingUploadsForConversation(targetConversationId, currentUploads); - const remainingFileSlots = Math.max(0, MAX_UPLOAD_FILES - currentUploads.length); - if (remainingFileSlots === 0) { - addNotify( - "warning", - formatTranslation(translate("chat.upload.maxFilesIgnored", locale), { - max: MAX_UPLOAD_FILES, - count: filesToImport.length, - }), - ); - return; - } - - const importBatch = filesToImport.slice(0, remainingFileSlots); - const ignoredForLimit = filesToImport.length - importBatch.length; - setUploadingFiles(true); - try { - const agentID = await resolveAgentID(); - const result = await importReadableFiles(token, agentID, workdir, importBatch); - // An import that settles after its upload context was invalidated - // must not resurrect cleared attachments: files picked inside the - // old workspace are not readable from the new one. - if ( - (await resolveAgentID()) !== agentID || - executionModeRef.current === "text" || - (isDisplayedConversation(targetConversationId) && - displayedConversationWorkdirRef.current.trim() !== workdir) - ) { - addNotify("warning", "上传目标已失效,已忽略本次导入的文件"); - return; - } - registerLocalUploadedImagePreviews({ - workspaceRoot: workdir, - uploadedFiles: result.files, - sourceFiles: importBatch, - }); - - if (result.files.length > 0) { - updatePendingUploadsForConversation(targetConversationId, (current) => - mergePendingUploadedFiles(current, result.files).slice(0, MAX_UPLOAD_FILES), - ); - if (isDisplayedConversation(targetConversationId)) { - composerRef.current?.focus(); - } - } - - if (result.files.length === 0 && result.skipped.length > 0) { - addNotify("error", `所选文件均无法导入:\n${result.skipped.join("\n")}`); - } else if (result.skipped.length > 0) { - addNotify("warning", `以下文件已跳过:\n${result.skipped.join("\n")}`); - } - if (ignoredForLimit > 0) { - addNotify( - "warning", - formatTranslation(translate("chat.upload.maxFilesIgnored", locale), { - max: MAX_UPLOAD_FILES, - count: ignoredForLimit, - }), - ); - } - } catch (error) { - addNotify("error", asErrorMessage(error, "导入文件失败")); - } finally { - setUploadingFiles(false); - } - }, - [ - addNotify, - composerRef, - displayedConversationWorkdirRef, - executionMode, - getPendingUploadsForConversation, - isDisplayedConversation, - locale, - resolveAgentID, - setPendingUploadsForConversation, - setUploadingFiles, - token, - updatePendingUploadsForConversation, - ], - ); - - useEffect(() => { - if ( - !token || - historyShareToken || - !settingsSyncReady || - settingsOpen || - activeView !== "chat" - ) { - return; - } - - const handleDocumentPaste = (event: globalThis.ClipboardEvent) => { - if (event.defaultPrevented) return; - const clipboardFiles = extractClipboardFiles(event.clipboardData); - if (clipboardFiles.length > 0) { - event.preventDefault(); - event.stopPropagation(); - void handleImportReadableFiles(clipboardFiles); - return; - } - if (!clipboardHasFileSignal(event.clipboardData)) return; - - event.preventDefault(); - event.stopPropagation(); - void readClipboardFiles() - .then((files) => { - if (files.length === 0) { - addNotify("warning", "无法读取剪贴板中的文件,请尝试拖拽或点击上传。"); - return; - } - return handleImportReadableFiles(files); - }) - .catch((error) => { - addNotify("error", asErrorMessage(error, "读取剪贴板文件失败")); - }); - }; - - document.addEventListener("paste", handleDocumentPaste, true); - return () => { - document.removeEventListener("paste", handleDocumentPaste, true); - }; - }, [ - activeView, - addNotify, - handleImportReadableFiles, - historyShareToken, - settingsOpen, - settingsSyncReady, - token, - ]); - - const handleFileDragEnter = useCallback((event: DragEvent) => { - if (!dragEventHasFiles(event)) return; - event.preventDefault(); - event.stopPropagation(); - uploadDragDepthRef.current += 1; - setIsFileDropActive(true); - }, []); - - const handleFileDragOver = useCallback( - (event: DragEvent, canDropUpload: boolean) => { - if (!dragEventHasFiles(event)) return; - event.preventDefault(); - event.stopPropagation(); - event.dataTransfer.dropEffect = canDropUpload ? "copy" : "none"; - setIsFileDropActive(true); - }, - [], - ); - - const handleFileDragLeave = useCallback((event: DragEvent) => { - if (!dragEventHasFiles(event)) return; - event.preventDefault(); - event.stopPropagation(); - uploadDragDepthRef.current = Math.max(0, uploadDragDepthRef.current - 1); - if (uploadDragDepthRef.current === 0) { - setIsFileDropActive(false); - } - }, []); - - const handleFileDrop = useCallback( - ( - event: DragEvent, - options: { - canDropUpload: boolean; - disabledMessage: string; - }, - ) => { - if (!dragEventHasFiles(event)) return; - event.preventDefault(); - event.stopPropagation(); - uploadDragDepthRef.current = 0; - setIsFileDropActive(false); - - const files = Array.from(event.dataTransfer.files ?? []); - if (files.length === 0) return; - if (!options.canDropUpload) { - addNotify("warning", options.disabledMessage); - return; - } - void handleImportReadableFiles(files); - }, - [addNotify, handleImportReadableFiles], - ); - - return { - pendingUploadedFiles, - isUploadingFiles, - isFileDropActive, - fileInputRef, - setUploadingFiles, - getPendingUploadsForConversation, - setPendingUploadsForConversation, - updatePendingUploadsForConversation, - moveConversationUploads, - clearPendingUploads, - handleImportReadableFiles, - handleFileDragEnter, - handleFileDragOver, - handleFileDragLeave, - handleFileDrop, - }; -} diff --git a/crates/agent-gateway/web/src/app/hooks/useProjectToolsRuntime.ts b/crates/agent-gateway/web/src/app/hooks/useProjectToolsRuntime.ts deleted file mode 100644 index a3fdd571b..000000000 --- a/crates/agent-gateway/web/src/app/hooks/useProjectToolsRuntime.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; - -import type { WorkspaceCodeEditorOpenRequest } from "@/components/workspace-editor/WorkspaceCodeEditorOverlay"; -import type { WorkspaceFilePreviewOpenRequest } from "@/components/workspace-editor/WorkspaceFilePreviewOverlay"; -import type { WorkspaceSshTerminalOpenRequest } from "@/components/workspace-editor/WorkspaceSshTerminalOverlay"; -import { isWorkspacePreviewPath } from "@/components/workspace-editor/workspaceImagePreview"; -import { - applyTerminalEventToSessions, - replaceTerminalSessionsForProject, - sortTerminalSessions, - terminalSessionBelongsToProject, -} from "@/lib/terminal/sessionStore"; -import type { TerminalClient, TerminalSession } from "@/lib/terminal/types"; - -type UseProjectToolsRuntimeParams = { - terminalClient: TerminalClient | null; - settingsSyncReady: boolean; - isAgentMode: boolean; - webTerminalSessionsEnabled: boolean; - statusOnline?: boolean; - statusSessionId?: string | null; - terminalProjectPath: string; - terminalProjectPathKey: string; - rightDockFileTreeOpen: boolean; - rightDockSshTunnelOpen: boolean; -}; - -export function useProjectToolsRuntime(params: UseProjectToolsRuntimeParams) { - const { - terminalClient, - settingsSyncReady, - isAgentMode, - webTerminalSessionsEnabled, - statusOnline, - statusSessionId, - terminalProjectPath, - terminalProjectPathKey, - rightDockFileTreeOpen, - rightDockSshTunnelOpen, - } = params; - - const previousRightDockFileTreeOpenRef = useRef(false); - const [workspaceEditorMounted, setWorkspaceEditorMounted] = useState(false); - const [workspaceEditorOpen, setWorkspaceEditorOpen] = useState(false); - const [workspaceEditorCleanupPending, setWorkspaceEditorCleanupPending] = useState(false); - const [workspaceEditorOpenRequest, setWorkspaceEditorOpenRequest] = - useState(null); - const [workspaceEditorCloseRequestId, setWorkspaceEditorCloseRequestId] = useState(0); - const workspaceEditorRequestIdRef = useRef(0); - const [workspaceFilePreviewMounted, setWorkspaceFilePreviewMounted] = useState(false); - const [workspaceFilePreviewOpen, setWorkspaceFilePreviewOpen] = useState(false); - const [workspaceFilePreviewOpenRequest, setWorkspaceFilePreviewOpenRequest] = - useState(null); - const workspaceFilePreviewRequestIdRef = useRef(0); - const [workspaceSshTerminalMounted, setWorkspaceSshTerminalMounted] = useState(false); - const [workspaceSshTerminalOpen, setWorkspaceSshTerminalOpen] = useState(false); - const [workspaceSshTerminalOpenRequest, setWorkspaceSshTerminalOpenRequest] = - useState(null); - const workspaceSshTerminalRequestIdRef = useRef(0); - const [terminalSessions, setTerminalSessions] = useState([]); - const [terminalSessionsLoaded, setTerminalSessionsLoaded] = useState(false); - const terminalSessionsVersionRef = useRef(0); - const terminalStatusSessionIdRef = useRef(""); - - const hideWorkspaceSshTerminalOverlay = useCallback(() => { - setWorkspaceSshTerminalOpen(false); - }, []); - - const openWorkspaceSshTerminalRequest = useCallback( - (request: WorkspaceSshTerminalOpenRequest) => { - setWorkspaceFilePreviewOpen(false); - setWorkspaceEditorOpen(false); - setWorkspaceSshTerminalMounted(true); - setWorkspaceSshTerminalOpen(true); - setWorkspaceSshTerminalOpenRequest(request); - }, - [], - ); - - const requestWorkspaceEditorClose = useCallback(() => { - setWorkspaceEditorCloseRequestId((current) => current + 1); - }, []); - - const handleWorkspaceEditorHide = useCallback(() => { - setWorkspaceEditorOpen(false); - }, []); - - const handleWorkspaceEditorClosed = useCallback(() => { - setWorkspaceEditorOpen(false); - setWorkspaceEditorMounted(false); - setWorkspaceEditorCleanupPending(false); - setWorkspaceEditorOpenRequest(null); - setWorkspaceEditorCloseRequestId(0); - }, []); - - const openWorkspaceEditorFile = useCallback( - (request: Omit) => { - hideWorkspaceSshTerminalOverlay(); - setWorkspaceFilePreviewOpen(false); - workspaceEditorRequestIdRef.current += 1; - setWorkspaceEditorCleanupPending(false); - setWorkspaceEditorMounted(true); - setWorkspaceEditorOpen(true); - setWorkspaceEditorOpenRequest({ - id: workspaceEditorRequestIdRef.current, - ...request, - }); - }, - [hideWorkspaceSshTerminalOverlay], - ); - - const openWorkspaceFilePreview = useCallback( - (request: Omit) => { - hideWorkspaceSshTerminalOverlay(); - setWorkspaceEditorOpen(false); - workspaceFilePreviewRequestIdRef.current += 1; - setWorkspaceFilePreviewMounted(true); - setWorkspaceFilePreviewOpen(true); - setWorkspaceFilePreviewOpenRequest({ - id: workspaceFilePreviewRequestIdRef.current, - ...request, - }); - }, - [hideWorkspaceSshTerminalOverlay], - ); - - const handleOpenWorkspaceFile = useCallback( - (path: string, imagePaths?: string[]) => { - if (!terminalProjectPath || !terminalProjectPathKey) return; - const request = { - projectPathKey: terminalProjectPathKey, - workdir: terminalProjectPath, - path, - imagePaths, - }; - if (isWorkspacePreviewPath(path)) { - openWorkspaceFilePreview(request); - return; - } - openWorkspaceEditorFile(request); - }, - [ - openWorkspaceEditorFile, - openWorkspaceFilePreview, - terminalProjectPath, - terminalProjectPathKey, - ], - ); - - const handleOpenSshTerminal = useCallback( - (session: TerminalSession, kind: WorkspaceSshTerminalOpenRequest["kind"] = "bash") => { - if (session.kind !== "ssh") return; - workspaceSshTerminalRequestIdRef.current += 1; - openWorkspaceSshTerminalRequest({ - id: workspaceSshTerminalRequestIdRef.current, - sessionId: session.id, - kind, - }); - }, - [openWorkspaceSshTerminalRequest], - ); - - const requestWorkspaceFilePreviewClose = useCallback(() => { - setWorkspaceFilePreviewOpen(false); - }, []); - - const handleWorkspaceFilePreviewClosed = useCallback(() => { - setWorkspaceFilePreviewOpen(false); - setWorkspaceFilePreviewMounted(false); - setWorkspaceFilePreviewOpenRequest(null); - }, []); - - useEffect(() => { - const previousOpen = previousRightDockFileTreeOpenRef.current; - previousRightDockFileTreeOpenRef.current = rightDockFileTreeOpen; - if (rightDockFileTreeOpen && workspaceEditorCleanupPending) { - setWorkspaceEditorCleanupPending(false); - } - if (previousOpen && !rightDockFileTreeOpen && workspaceEditorMounted) { - setWorkspaceEditorCleanupPending(true); - setWorkspaceEditorOpen(true); - requestWorkspaceEditorClose(); - } - if (previousOpen && !rightDockFileTreeOpen && workspaceFilePreviewMounted) { - requestWorkspaceFilePreviewClose(); - } - }, [ - rightDockFileTreeOpen, - requestWorkspaceEditorClose, - requestWorkspaceFilePreviewClose, - workspaceEditorCleanupPending, - workspaceEditorMounted, - workspaceFilePreviewMounted, - ]); - - const projectTerminalSessions = useMemo( - () => - terminalProjectPathKey - ? terminalSessions.filter((session) => - terminalSessionBelongsToProject(session, terminalProjectPathKey), - ) - : [], - [terminalProjectPathKey, terminalSessions], - ); - - const handleProjectTerminalSessionsChange = useCallback((sessions: TerminalSession[]) => { - terminalSessionsVersionRef.current += 1; - setTerminalSessions(sortTerminalSessions(sessions)); - }, []); - - useEffect(() => { - // Loaded flips false whenever the gates or the gateway session identity - // change, and true only once list() settles below — RightDockPanel uses it - // to defer terminal-tab GC until the session list is actually known. - setTerminalSessionsLoaded(false); - if (!terminalClient) { - terminalSessionsVersionRef.current += 1; - setTerminalSessions([]); - return; - } - if (!settingsSyncReady) { - return; - } - if (!isAgentMode || !webTerminalSessionsEnabled || statusOnline === false) { - terminalSessionsVersionRef.current += 1; - setTerminalSessions([]); - return; - } - if (statusOnline !== true) { - return; - } - const normalizedStatusSessionId = statusSessionId?.trim() ?? ""; - if ( - normalizedStatusSessionId && - terminalStatusSessionIdRef.current !== normalizedStatusSessionId - ) { - const hadPreviousSession = terminalStatusSessionIdRef.current !== ""; - terminalStatusSessionIdRef.current = normalizedStatusSessionId; - if (hadPreviousSession) { - terminalSessionsVersionRef.current += 1; - setTerminalSessions([]); - } - } - let cancelled = false; - const requestVersion = terminalSessionsVersionRef.current; - void terminalClient - .list() - .then((sessions) => { - if (!cancelled && terminalSessionsVersionRef.current === requestVersion) { - setTerminalSessions(sortTerminalSessions(sessions)); - } - }) - .catch(() => undefined) - .finally(() => { - if (!cancelled) { - setTerminalSessionsLoaded(true); - } - }); - return () => { - cancelled = true; - }; - }, [ - isAgentMode, - settingsSyncReady, - statusOnline, - statusSessionId, - terminalClient, - webTerminalSessionsEnabled, - ]); - - useEffect(() => { - if (!terminalClient) return; - return terminalClient.subscribe((event) => { - if (event.kind === "output") return; - terminalSessionsVersionRef.current += 1; - setTerminalSessions((current) => applyTerminalEventToSessions(current, event)); - }); - }, [terminalClient]); - - useEffect(() => { - // One catch-up fetch when the ssh-tunnel tab becomes usable (opened, agent - // back online, gateway session ready). Ongoing freshness is event-driven: - // the terminalClient.subscribe effect above applies created/updated/closed - // broadcasts, and SshTunnelPanel's own active-gated reconcile feeds - // list() results back through onSessionsReconcile -> onSessionsChange. - // A parallel 5s poll here only duplicated that traffic. - if (!terminalClient) return; - if (!settingsSyncReady) return; - if (!isAgentMode || !webTerminalSessionsEnabled || statusOnline !== true) return; - if (!rightDockSshTunnelOpen || !terminalProjectPathKey) return; - - let cancelled = false; - void terminalClient - .list(terminalProjectPathKey) - .then((sessions) => { - if (cancelled) return; - terminalSessionsVersionRef.current += 1; - setTerminalSessions((current) => - replaceTerminalSessionsForProject(current, terminalProjectPathKey, sessions), - ); - }) - .catch(() => undefined); - return () => { - cancelled = true; - }; - }, [ - isAgentMode, - rightDockSshTunnelOpen, - settingsSyncReady, - statusOnline, - terminalClient, - terminalProjectPathKey, - webTerminalSessionsEnabled, - ]); - - const resetTerminalSessions = useCallback(() => { - terminalSessionsVersionRef.current += 1; - terminalStatusSessionIdRef.current = ""; - setTerminalSessions([]); - setTerminalSessionsLoaded(false); - }, []); - - return { - workspaceEditorMounted, - workspaceEditorOpen, - workspaceEditorCleanupPending, - workspaceEditorOpenRequest, - workspaceEditorCloseRequestId, - workspaceFilePreviewMounted, - workspaceFilePreviewOpen, - workspaceFilePreviewOpenRequest, - workspaceSshTerminalMounted, - workspaceSshTerminalOpen, - workspaceSshTerminalOpenRequest, - terminalSessions, - terminalSessionsLoaded, - setTerminalSessions, - terminalSessionsVersionRef, - terminalStatusSessionIdRef, - projectTerminalSessions, - openWorkspaceEditorFile, - openWorkspaceFilePreview, - handleWorkspaceEditorHide, - handleWorkspaceEditorClosed, - requestWorkspaceFilePreviewClose, - handleWorkspaceFilePreviewClosed, - handleOpenWorkspaceFile, - handleOpenSshTerminal, - handleProjectTerminalSessionsChange, - resetTerminalSessions, - hideWorkspaceSshTerminalOverlay, - }; -} diff --git a/crates/agent-gateway/web/src/app/sidebar/GatewaySidebarContainer.tsx b/crates/agent-gateway/web/src/app/sidebar/GatewaySidebarContainer.tsx deleted file mode 100644 index 938a1062c..000000000 --- a/crates/agent-gateway/web/src/app/sidebar/GatewaySidebarContainer.tsx +++ /dev/null @@ -1,385 +0,0 @@ -// Sidebar container for the web end: owns every useSidebarSelector -// subscription plus the rename UI state, so store commits (activity ticks, -// list updates, per-row mutations) re-render this subtree only — never -// GatewayApp. Renders the per-end view. - -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { ChatHistorySidebar } from "@/components/chat/ChatHistorySidebar"; -import { useLocale } from "@/i18n"; -import type { ChatHistorySummary } from "@/lib/chat/chatHistory"; -import type { WorkspaceProject } from "@/lib/settings"; -import type { SidebarBatchDeleteOptions } from "@/lib/sidebar/batchDelete"; -import { deleteSidebarConversations } from "@/lib/sidebar/batchDelete"; -import { - selectConversations, - selectListState, - selectProjectActivityInputs, - selectRunningConversationIds, - sidebarShallowEqual, -} from "@/lib/sidebar/selectors"; -import type { SidebarSnapshot, SidebarStore } from "@/lib/sidebar/store"; -import type { SidebarErrorCode } from "@/lib/sidebar/types"; -import { useSidebarSelector } from "@/lib/sidebar/useSidebarSelector"; -import { sortWorkspaceProjectsByActivity } from "@/lib/workspaceProjects"; - -function selectMutations(snapshot: SidebarSnapshot) { - return snapshot.mutations; -} - -function selectMutationErrors(snapshot: SidebarSnapshot) { - return snapshot.mutationErrors; -} - -function selectConversationIndex(snapshot: SidebarSnapshot) { - return snapshot.byId; -} - -// Transport-shaped list errors merely restate "the read path is down or -// congested right now"; the page-level banner and Online/Offline pill own -// that story, so the sidebar never repeats it — including the stale copy -// that lingers until the next reconcile tick or reconnect refetch lands. -// Three sources produce this class of message: -// - browser⇄gateway socket failures (mirrors isRecoverableGatewayTransportError -// in lib/gatewaySocket.ts) plus the client-side request timeout, which the -// status poll ignores under fresh inbound activity for the same reason; -// - the Go hub rejecting a roundtrip while the desktop agent is briefly -// offline or re-registering ("agent offline", websocket_roundtrip.go) — -// the socket stays up in that window, so connectionLost never covers it; -// - gateway-side context outcomes on the hub⇄agent roundtrip -// ("request timed out"/"request canceled", websocket_roundtrip.go). -// Genuine desktop read failures arrive as other strings and still surface. -function isGatewayTransportErrorDetail(detail: string | null | undefined) { - const message = (detail ?? "").trim(); - return ( - message.startsWith("Gateway WebSocket disconnected") || - message === "Gateway WebSocket is not connected" || - message.startsWith("Gateway transport stalled") || - message.startsWith("Gateway WebSocket request timed out") || - message === "agent offline" || - message === "request timed out" || - message === "request canceled" - ); -} - -// Stable identity wrapper so callback props from GatewayApp (recreated per -// render) never churn effects or the memo'd view rows. -function useStableCallback( - handler: (...args: Args) => Return, -): (...args: Args) => Return { - const handlerRef = useRef(handler); - handlerRef.current = handler; - return useCallback((...args: Args) => handlerRef.current(...args), []); -} - -export type GatewaySidebarContainerProps = { - store: SidebarStore; - currentConversationId: string; - isOpen: boolean; - fontScale?: number; - activeView: "chat" | "skills-hub" | "mcp-hub"; - showProjects: boolean; - // Merged (settings + history workdirs), unsorted: sorting happens here on - // the store's activity snapshot so project reordering never re-renders - // GatewayApp. - projects: WorkspaceProject[]; - activeProjectId?: string; - missingProjectPathKeys: ReadonlySet; - projectRenamingId: string | null; - projectRenameDraft: string; - projectsCollapsed: boolean; - recentCollapsed: boolean; - canShareConversations: boolean; - sharedConversationCount: number; - // GatewayApp-level sidebar errors (project removal flow); store errors are - // derived locally and take precedence. - externalErrorMessage: string | null; - // Gateway socket dropped after having been connected: transport-shaped - // error cards are suppressed because the page banner owns that messaging. - connectionLost: boolean; - // Workspace and recent-conversation interactions are available only while - // both the browser transport and the desktop Agent are confirmed online. - sectionsDisabled: boolean; - isLocalDraftConversationId: (id: string) => boolean; - onProjectsCollapsedChange: (collapsed: boolean) => void; - onRecentCollapsedChange: (collapsed: boolean) => void; - onCreateProject: () => void; - onSelectProject: (project: WorkspaceProject) => void; - onNewConversationForProject: (project: WorkspaceProject) => void; - onBrowseProjectInFileTree: (project: WorkspaceProject) => void; - onStartRenamingProject: (project: WorkspaceProject) => void; - onProjectRenameDraftChange: (value: string) => void; - onCommitProjectRename: () => void; - onCancelProjectRename: () => void; - onSetProjectPinned: (project: WorkspaceProject, isPinned: boolean) => void; - onRemoveProject: (project: WorkspaceProject) => void; - onArchiveProject: (project: WorkspaceProject) => void; - onUnarchiveProject: (project: WorkspaceProject) => void; - archivedProjectPathKeys?: ReadonlySet; - onNewConversation: () => void; - onSelectConversation: (id: string) => void; - onShareConversation: (item: ChatHistorySummary) => void; - onOpenSharedConversations: () => void; - // User-initiated removal of a local draft row (never hits the backend). - onLocalDraftDeleted: (id: string) => void; - // Conversations that left the authoritative index (remote delete, local - // delete confirmation, reconcile drop): GatewayApp cleans caches and - // migrates the selection when the displayed conversation vanished. - onConversationsRemoved: (ids: readonly string[]) => void; - onCloseSidebar: () => void; - onOpenSettings: () => void; - onOpenSkillsHub: () => void; - onOpenMcpHub: () => void; -}; - -export function GatewaySidebarContainer(props: GatewaySidebarContainerProps) { - const { - store, - projects, - externalErrorMessage, - connectionLost, - sectionsDisabled, - isLocalDraftConversationId, - } = props; - const { t } = useLocale(); - - const items = useSidebarSelector(store, selectConversations); - const listState = useSidebarSelector(store, selectListState, sidebarShallowEqual); - const scopeKey = useSidebarSelector(store, (snapshot) => snapshot.scopeKey); - const runningConversationIds = useSidebarSelector(store, selectRunningConversationIds); - const mutations = useSidebarSelector(store, selectMutations); - const mutationErrors = useSidebarSelector(store, selectMutationErrors); - const projectActivityInputs = useSidebarSelector( - store, - selectProjectActivityInputs, - sidebarShallowEqual, - ); - const conversationIndex = useSidebarSelector(store, selectConversationIndex); - - // --- Rename UI state (moved out of GatewayApp) --------------------------- - const [renamingId, setRenamingId] = useState(null); - const [renameDraft, setRenameDraft] = useState(""); - - useEffect(() => { - if (!sectionsDisabled) { - return; - } - setRenamingId(null); - setRenameDraft(""); - }, [sectionsDisabled]); - - const clearMutationErrors = useCallback(() => { - for (const id of store.getSnapshot().mutationErrors.keys()) { - store.clearMutationError(id); - } - }, [store]); - - const handleStartRenaming = useStableCallback((item: ChatHistorySummary) => { - if (sectionsDisabled) { - return; - } - setRenamingId(item.id); - setRenameDraft(item.title); - }); - - const handleCommitRename = useStableCallback(() => { - if (sectionsDisabled) { - setRenamingId(null); - setRenameDraft(""); - return; - } - if (!renamingId) { - return; - } - const conversationId = renamingId; - const title = renameDraft.trim(); - setRenamingId(null); - setRenameDraft(""); - if (!title || title === store.peek(conversationId)?.title) { - return; - } - clearMutationErrors(); - void store.rename(conversationId, title); - }); - - const handleCancelRename = useStableCallback(() => { - setRenamingId(null); - setRenameDraft(""); - }); - - const handleSetPinned = useStableCallback((id: string, isPinned: boolean) => { - if (sectionsDisabled) { - return; - } - clearMutationErrors(); - void store.setPinned(id, isPinned); - }); - - const handleDeleteConversation = useStableCallback((id: string) => { - if (sectionsDisabled) { - return; - } - clearMutationErrors(); - const existing = store.peek(id); - if (existing?.isPending === true || isLocalDraftConversationId(id)) { - store.removeLocal(id); - props.onLocalDraftDeleted(id); - return; - } - void store.remove(id); - }); - - const handleDeleteConversations = useStableCallback( - async (ids: readonly string[], options?: SidebarBatchDeleteOptions) => { - if (sectionsDisabled) { - return { deletedIds: [], failedIds: [...ids], skippedIds: [] }; - } - clearMutationErrors(); - return deleteSidebarConversations( - ids, - async (id) => { - const existing = store.peek(id); - if (existing?.isPending === true || isLocalDraftConversationId(id)) { - store.removeLocal(id); - props.onLocalDraftDeleted(id); - return true; - } - return store.remove(id); - }, - options, - ); - }, - ); - - const handleLoadMore = useStableCallback(() => { - if (sectionsDisabled) { - return; - } - void store.loadMore(); - }); - - // --- Authoritative-removal watcher --------------------------------------- - // byId is the cross-scope index: entries only leave it on delete events, - // confirmed local deletes, or authoritative reconcile drops — a scope - // switch does not evict, so this never fires for out-of-scope selections. - const onConversationsRemoved = useStableCallback(props.onConversationsRemoved); - const knownConversationIdsRef = useRef | null>(null); - useEffect(() => { - const previous = knownConversationIdsRef.current; - const next = new Set(conversationIndex.keys()); - knownConversationIdsRef.current = next; - if (!previous || previous.size === 0) { - return; - } - const removed: string[] = []; - for (const id of previous) { - if (!next.has(id)) { - removed.push(id); - } - } - if (removed.length > 0) { - onConversationsRemoved(removed); - } - }, [conversationIndex, onConversationsRemoved]); - - // --- Errors --------------------------------------------------------------- - const translateErrorCode = useCallback( - (code: SidebarErrorCode) => t(`chat.history.${code}`), - [t], - ); - const listErrorMessage = useMemo(() => { - if (connectionLost) { - return null; - } - if (listState.error && !isGatewayTransportErrorDetail(listState.errorDetail)) { - return listState.errorDetail?.trim() || translateErrorCode(listState.error); - } - return null; - }, [connectionLost, listState.error, listState.errorDetail, translateErrorCode]); - const actionErrorMessage = useMemo(() => { - if (connectionLost) { - return null; - } - let lastMutationError: SidebarErrorCode | null = null; - for (const code of mutationErrors.values()) { - lastMutationError = code; - } - if (lastMutationError) { - return translateErrorCode(lastMutationError); - } - return externalErrorMessage; - }, [connectionLost, externalErrorMessage, mutationErrors, translateErrorCode]); - - // --- Projects ------------------------------------------------------------- - const sortedProjects = useMemo( - () => - sortWorkspaceProjectsByActivity(projects, { - projectActivityUpdatedAts: projectActivityInputs.workdirActivity, - runningProjectPathKeys: projectActivityInputs.runningWorkdirPathKeys, - }), - [projectActivityInputs.runningWorkdirPathKeys, projectActivityInputs.workdirActivity, projects], - ); - - return ( - - ); -} diff --git a/crates/agent-gateway/web/src/app/sidebar/gatewaySidebarAvailability.ts b/crates/agent-gateway/web/src/app/sidebar/gatewaySidebarAvailability.ts deleted file mode 100644 index 97ad4af3c..000000000 --- a/crates/agent-gateway/web/src/app/sidebar/gatewaySidebarAvailability.ts +++ /dev/null @@ -1,39 +0,0 @@ -export type GatewaySidebarStatusFreshnessState = { - socketConnected: boolean; - agentStatusFresh: boolean; -}; - -export type GatewaySidebarStatusFreshnessEvent = - | { type: "connection"; connected: boolean } - | { type: "status" }; - -export const INITIAL_GATEWAY_SIDEBAR_STATUS_FRESHNESS: GatewaySidebarStatusFreshnessState = { - socketConnected: false, - agentStatusFresh: false, -}; - -export function reduceGatewaySidebarStatusFreshness( - state: GatewaySidebarStatusFreshnessState, - event: GatewaySidebarStatusFreshnessEvent, -): GatewaySidebarStatusFreshnessState { - if (event.type === "connection") { - return { - socketConnected: event.connected, - // Every authenticated socket starts a new status epoch. The previous - // socket's cached online verdict cannot make the new path interactive. - agentStatusFresh: false, - }; - } - return { - ...state, - agentStatusFresh: state.socketConnected, - }; -} - -export function shouldDisableGatewaySidebarSections(input: { - connectionLost: boolean; - agentStatusFresh: boolean; - agentOnline: boolean | null | undefined; -}): boolean { - return input.connectionLost || !input.agentStatusFresh || input.agentOnline !== true; -} diff --git a/crates/agent-gateway/web/src/app/types.ts b/crates/agent-gateway/web/src/app/types.ts deleted file mode 100644 index 2bb6df7cd..000000000 --- a/crates/agent-gateway/web/src/app/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { HistoryMessageRef } from "@/lib/chat/conversationState"; -import type { ChatCommandOutcome } from "@/lib/chat/stream/chatCommandPipeline"; -import type { PendingUploadedFile } from "@/lib/chat/uploadedFiles"; -import type { ChatRuntimeControls, CustomProvider } from "@/lib/settings"; - -export type OverlayState = "closed" | "entering" | "open" | "leaving"; - -export type SendChatOptions = { - conversationId?: string; - clientRequestId?: string; - uploadedFiles?: PendingUploadedFile[]; - runtimeControls?: ChatRuntimeControls; - workdir?: string; - editMessageRef?: HistoryMessageRef; - queuePolicy?: "auto" | "append" | "interrupt"; - // false for queue-destined sends: no transcript echo, the queue panel owns - // the prompt until it actually runs. - optimisticEcho?: boolean; -}; - -export type SendChatFn = ( - message: string, - options?: SendChatOptions, -) => Promise; - -export type ModelProviderSource = Pick; - -export type TunnelManagerToolChange = { - action: "create" | "close"; - projectPathKey: string; -}; diff --git a/crates/agent-gateway/web/src/assets/font/OpenAISans-Semibold.woff2 b/crates/agent-gateway/web/src/assets/font/OpenAISans-Semibold.woff2 deleted file mode 100644 index 895cfa926510e18bc5218ec623873b4217676799..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50920 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 diff --git a/crates/agent-gateway/web/src/components/AppErrorBoundary.tsx b/crates/agent-gateway/web/src/components/AppErrorBoundary.tsx deleted file mode 100644 index fb937dce6..000000000 --- a/crates/agent-gateway/web/src/components/AppErrorBoundary.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Component, type ErrorInfo, type ReactNode } from "react"; -import { useLocale } from "../i18n"; -import { Button } from "./ui/button"; - -type FallbackLabels = { - title: string; - description: string; - reload: string; - copy: string; -}; - -type ErrorBoundaryInnerProps = { - children: ReactNode; - labels: FallbackLabels; -}; - -type ErrorBoundaryInnerState = { - error: Error | null; - componentStack: string; -}; - -class ErrorBoundaryInner extends Component { - state: ErrorBoundaryInnerState = { error: null, componentStack: "" }; - - static getDerivedStateFromError(error: Error): Partial { - return { error }; - } - - componentDidCatch(error: Error, info: ErrorInfo) { - console.error("[AppErrorBoundary]", error, info.componentStack); - this.setState({ componentStack: info.componentStack ?? "" }); - } - - render() { - const { error } = this.state; - if (!error) { - return this.props.children; - } - return ( -

      -
      {this.props.labels.title}
      -
      - {this.props.labels.description} -
      -
      - {error.message} - {import.meta.env.DEV && this.state.componentStack - ? `\n${this.state.componentStack}` - : null} -
      -
      - - -
      -
      - ); - } -} - -export function AppErrorBoundary(props: { children: ReactNode }) { - const { t } = useLocale(); - return ( - - {props.children} - - ); -} diff --git a/crates/agent-gateway/web/src/components/CliIdentityUpdateHost.tsx b/crates/agent-gateway/web/src/components/CliIdentityUpdateHost.tsx deleted file mode 100644 index 03fe7ba59..000000000 --- a/crates/agent-gateway/web/src/components/CliIdentityUpdateHost.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useCallback, useEffect, useRef } from "react"; -import { - checkCliIdentityVersions, - cliIdentityProvidersNeedingCheck, - mergeCliIdentityCheckResults, -} from "../lib/providers/cliIdentityUpdates"; -import { type AppSettings, updateCustomSettings } from "../lib/settings"; - -const CHECK_TIMEOUT_MS = 8_000; -const RECHECK_TICK_MS = 60 * 60 * 1_000; - -export function CliIdentityUpdateHost(props: { - settings: AppSettings; - setSettings: (updater: (previous: AppSettings) => AppSettings) => void; -}) { - const { settings, setSettings } = props; - const identitiesRef = useRef(settings.customSettings.providerIdentities); - const runningRef = useRef(false); - const abortRef = useRef(null); - identitiesRef.current = settings.customSettings.providerIdentities; - - const runCheck = useCallback(async () => { - if (runningRef.current) return; - const providerIds = cliIdentityProvidersNeedingCheck(identitiesRef.current); - if (providerIds.length === 0) return; - runningRef.current = true; - const controller = new AbortController(); - abortRef.current = controller; - const timeout = window.setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS); - try { - const results = await checkCliIdentityVersions(providerIds, controller.signal); - setSettings((previous) => { - const merged = mergeCliIdentityCheckResults( - previous.customSettings.providerIdentities, - results, - ); - return merged.changed - ? updateCustomSettings(previous, { providerIdentities: merged.identities }) - : previous; - }); - } finally { - window.clearTimeout(timeout); - if (abortRef.current === controller) abortRef.current = null; - runningRef.current = false; - } - }, [setSettings]); - - useEffect(() => { - void runCheck(); - const interval = window.setInterval(() => void runCheck(), RECHECK_TICK_MS); - const onFocus = () => void runCheck(); - window.addEventListener("focus", onFocus); - return () => { - window.clearInterval(interval); - window.removeEventListener("focus", onFocus); - abortRef.current?.abort(); - }; - }, [runCheck]); - - return null; -} diff --git a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx b/crates/agent-gateway/web/src/components/GatewayTranscript.tsx deleted file mode 100644 index f3a110549..000000000 --- a/crates/agent-gateway/web/src/components/GatewayTranscript.tsx +++ /dev/null @@ -1,1916 +0,0 @@ -import { useVirtualizer } from "@tanstack/react-virtual"; -import { - type Dispatch, - type MutableRefObject, - memo, - type SetStateAction, - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from "react"; -import { ImagePreview, type ImagePreviewSlide } from "@/components/chat/ImagePreview"; -import { Markdown } from "@/components/Markdown"; -import { useLocale } from "@/i18n/LocaleContext"; -import type { ChatFileLink } from "@/lib/chat/chatFileLinks"; -import { normalizeLiveToolStatus, VIBING_STATUS } from "@/lib/chat/chatPageHelpers"; -import type { HistoryMessageRef } from "@/lib/chat/conversationState"; -import { getRoundText, getRoundToolTrace } from "@/lib/chat/uiMessages"; -import { - formatUploadedFileSize, - type PendingUploadedFile, - parsePastedTextDisplayReferences, -} from "@/lib/chat/uploadedFiles"; -import { - getUploadedImagePreviewCacheKey, - loadUploadedImagePreview, - readUploadedImagePreviewCache, - type UploadedImagePreviewLoader, -} from "@/lib/chat/uploadedImagePreview"; -import { - buildGitHubCommitUrl, - type CommitDetailsLoader, - type CommitDisplayReference, - UserMessageContent, -} from "@/lib/chat/userMessageContent"; -import type { GitClient } from "@/lib/git/types"; -import { DEFAULT_CHAT_TRANSCRIPT_WIDTH } from "@/lib/settings"; -import { cn } from "@/lib/shared/utils"; -import { extractLiveRange } from "@/lib/transcript-virtual/liveRangeExtractor"; -import { createLiveRowScrollAdjustPolicy } from "@/lib/transcript-virtual/liveScrollAdjustPolicy"; -import { - buildTranscriptLayoutKey, - createTranscriptMeasurementsLru, -} from "@/lib/transcript-virtual/measurementsLru"; -import { - CHECKPOINT_ROW_ESTIMATE_PX, - estimateAssistantRowHeight, - estimateUserRowHeight, - measureEstimateText, -} from "@/lib/transcript-virtual/rowEstimates"; -import { - AssistantAvatar, - AssistantBubble, - AssistantStatus, - CompactingText, - RetryDetailsBlock, - VibingText, -} from "@/pages/chat/AssistantBubble"; -import type { RetryAttemptRecord, TranscriptRow } from "../lib/chat/transcript/types"; - -import type { GatewayTranscriptRound } from "../lib/chatUi"; -import type { SectionId } from "../pages/settings/types"; -import { ChatEmptyState } from "./chat/ChatEmptyState"; -import { getUploadedFileTypeIcon } from "./chat/fileTypeIcons"; -import { - Check, - CheckCircle2, - ChevronDown, - Copy, - GitBranch, - Loader2, - Pencil, - RefreshCw, - X, -} from "./icons"; -import { ConfirmActionPopover } from "./ui/confirm-action-popover"; - -type GatewayTranscriptProps = { - conversationId?: string; - // The whole transcript as one row list, rendered by one virtualizer. Rows - // come from one store assembly, so a row can never render twice. - rows: readonly TranscriptRow[]; - // Index of the first unfolded-turn row (-1 when everything is folded); - // rows at or after it are force-mounted so a streaming reply never - // unmounts mid-run. - liveStartIndex?: number; - // Key of the actively streaming turn (caret / live structural state). - activeTurnKey?: string | null; - contentWidth?: number; - // Whether the scroll-follow engine is attached to the bottom; gates the - // virtualizer's resize-compensation carve-out for live-row growth. - isViewportFollowing?: () => boolean; - // Imperative jump handle for the floor navigation rail. - navRef?: MutableRefObject; - // Reports the user row at the viewport's top edge (the "current floor"). - onAnchorUserRowChange?: (rowKey: string | null) => void; - error?: string | null; - toolStatus?: string | null; - toolStatusIsCompaction?: boolean; - // Live run's stream-retry history; renders as an expandable details block - // under the live status (mirrors the desktop app). - retryAttempts?: readonly RetryAttemptRecord[]; - isStreaming?: boolean; - isLoading?: boolean; - loadingTitle?: string; - hasModels?: boolean; - onOpenSettings?: (section?: SectionId) => void; - hasMoreHistory?: boolean; - isLoadingMoreHistory?: boolean; - onLoadEarlierHistory?: () => void; - isAgentMode?: boolean; - showUsage?: boolean; - usageContextWindow?: number; - workspaceRoot?: string; - gitClient?: GitClient | null; - onOpenFileLink?: (link: ChatFileLink) => void; - onLoadUploadedImagePreview?: UploadedImagePreviewLoader; - onResendFromEdit?: ( - messageRef: HistoryMessageRef, - text: string, - uploadedFiles: PendingUploadedFile[], - ) => void; - onBranchConversation?: (messageRef: HistoryMessageRef) => void; - // Anchor messageId of the branch request in flight; the matching row shows - // a spinner and every branch button disables until it settles. - branchPendingMessageId?: string | null; - onSuggestionSelect?: (text: string) => void; - suggestionsDisabled?: boolean; - readOnly?: boolean; - redactToolContent?: boolean; -}; - -// Stream-born rows keep Streamdown's streaming render mode forever — even -// after their turn folds — so the streaming→static mode flip (and its full -// re-parse) can never happen. History-born rows render static from the -// start. -function rowRenderMode(row: Extract) { - return row.origin === "stream" ? ("streaming" as const) : ("static" as const); -} - -export type GatewayTranscriptNavHandle = { - // Aligns the row to the viewport top and keeps re-aligning for a few - // frames while dynamic measurements land (convergent, cancelled by user - // scroll input). - scrollToRowKey: (rowKey: string) => void; -}; - -const TRANSCRIPT_ROW_ESTIMATED_HEIGHT = 260; -const TRANSCRIPT_ROW_GAP = 18; -const TRANSCRIPT_ROW_OVERSCAN_COUNT = 5; - -// Measured row heights survive conversation switches: saved on unmount, -// restored (width-gated) on the next open so the switch lays out with exact -// heights instead of estimates. -const transcriptMeasurementsLru = createTranscriptMeasurementsLru(); - -type GatewayTranscriptVirtualItem = - | { key: string; kind: "loadRemoteHistory" } - | { key: string; kind: "row"; row: TranscriptRow } - | { key: string; kind: "pendingBubble" }; - -function resolveNearestScrollViewport(element: HTMLElement | null) { - return element?.closest("[data-scroll-viewport]") as HTMLDivElement | null; -} - -function LiveStatusFooter(props: { status: string; isCompaction?: boolean }) { - const { status, isCompaction = false } = props; - return ( -
      - {isCompaction ? ( - - ) : status === VIBING_STATUS ? ( - - ) : ( - {status} - )} -
      - ); -} - -function shouldShowLiveStatusForRounds(rounds: GatewayTranscriptRound[]) { - const activeRound = rounds[rounds.length - 1]; - if (!activeRound) { - return true; - } - const visibleToolKeys = new Set( - getRoundToolTrace(activeRound).map((item) => `${item.toolCall.id}\u0000${item.toolCall.name}`), - ); - - for (let index = activeRound.blocks.length - 1; index >= 0; index -= 1) { - const block = activeRound.blocks[index]; - if (!block) { - continue; - } - if (block.kind === "tool") { - if (visibleToolKeys.has(`${block.item.toolCall.id}\u0000${block.item.toolCall.name}`)) { - return true; - } - continue; - } - if (block.kind === "hostedSearch") { - return false; - } - if (block.text.trim() === "") { - continue; - } - return block.kind !== "text"; - } - - return true; -} - -function HistoryLoadingState(props: { title?: string }) { - const title = props.title?.trim(); - return ( -
      -
      -
      -
      - -
      -
      - 正在加载会话历史 -
      - {title ? ( -
      - {title} -
      - ) : null} -
      -
      -
      - ); -} - -function CheckpointCard(props: { - item: Extract; - readOnly?: boolean; -}) { - const { item, readOnly = false } = props; - const [expanded, setExpanded] = useState(false); - const isExpanded = expanded; - const messageCountLabel = - item.coveredMessageCount > 0 ? `${item.coveredMessageCount} 条消息` : "已压缩"; - const headerContent = ( - <> -
      - -
      - -
      -
      - - 上下文检查点 - - - {messageCountLabel} - -
      -
      - {item.generatedBy.providerId} · {item.generatedBy.model} -
      -
      - - - - ); - - return ( -
      - - ); -} - -function useGatewayUploadedImagePreview( - file?: PendingUploadedFile, - workspaceRoot?: string, - loader?: UploadedImagePreviewLoader, -) { - const normalizedWorkspaceRoot = typeof workspaceRoot === "string" ? workspaceRoot.trim() : ""; - const absolutePath = typeof file?.absolutePath === "string" ? file.absolutePath.trim() : ""; - const cacheKey = file ? getUploadedImagePreviewCacheKey(normalizedWorkspaceRoot, file) : ""; - const [imageSrc, setImageSrc] = useState(() => { - if (!file || !normalizedWorkspaceRoot) return null; - return readUploadedImagePreviewCache(normalizedWorkspaceRoot, file); - }); - - useEffect(() => { - if (!file || !cacheKey || !normalizedWorkspaceRoot) { - setImageSrc(null); - return; - } - - const cached = readUploadedImagePreviewCache(normalizedWorkspaceRoot, file); - if (cached !== undefined) { - setImageSrc(cached); - return; - } - if (!absolutePath || !loader) { - setImageSrc(null); - return; - } - - let cancelled = false; - setImageSrc(undefined); - void loadUploadedImagePreview({ - workspaceRoot: normalizedWorkspaceRoot, - file, - loader, - }).then((value) => { - if (!cancelled) { - setImageSrc(value); - } - }); - return () => { - cancelled = true; - }; - }, [absolutePath, cacheKey, file, loader, normalizedWorkspaceRoot]); - - return { - imageSrc: imageSrc ?? null, - isLoading: Boolean(cacheKey && absolutePath && loader) && imageSrc === undefined, - }; -} - -function GatewayUserImageAttachmentCard(props: { - file: PendingUploadedFile; - imageSrc: string | null; - isLoading: boolean; - compact: boolean; - onRemove?: (relativePath: string) => void; - removeLabel?: string; - previewLabel: string; - closePreviewLabel: string; -}) { - const { - file, - imageSrc, - isLoading, - compact, - onRemove, - removeLabel, - previewLabel, - closePreviewLabel, - } = props; - const [previewOpen, setPreviewOpen] = useState(false); - const labeledPreview = `${previewLabel}: ${file.fileName}`; - const FallbackIcon = getUploadedFileTypeIcon(file); - const previewSlides = useMemo( - () => - imageSrc - ? [ - { - src: imageSrc, - alt: file.fileName, - title: file.fileName, - }, - ] - : [], - [file.fileName, imageSrc], - ); - return ( -
      - {onRemove ? ( - - ) : null} - {imageSrc ? ( - <> - - {previewOpen ? ( - setPreviewOpen(false)} - /> - ) : null} - - ) : ( -
      -
      - {isLoading ? null : } -
      -
      - )} -
      -
      -
      - {file.fileName} -
      -
      - - {formatUploadedFileSize(file.sizeBytes)} - -
      -
      - ); -} - -function GatewayUserFileAttachmentCard(props: { - file: PendingUploadedFile; - onRemove?: (relativePath: string) => void; - removeLabel?: string; - compact: boolean; -}) { - const { file, onRemove, removeLabel, compact } = props; - const TypeIcon = getUploadedFileTypeIcon(file); - return ( -
      -
      - -
      -
      -
      - {file.fileName} -
      -
      - {formatUploadedFileSize(file.sizeBytes)} -
      -
      - {onRemove ? ( - - ) : null} -
      - ); -} - -function GatewayUserAttachmentCard(props: { - file: PendingUploadedFile; - workspaceRoot?: string; - onLoadUploadedImagePreview?: UploadedImagePreviewLoader; - compactImageLayout: boolean; - compactFileLayout: boolean; - onRemove?: (relativePath: string) => void; - removeLabel?: string; - previewLabel: string; - closePreviewLabel: string; -}) { - const { - file, - workspaceRoot, - onLoadUploadedImagePreview, - compactImageLayout, - compactFileLayout, - onRemove, - removeLabel, - previewLabel, - closePreviewLabel, - } = props; - const shouldPreviewImage = - file.kind === "image" && typeof workspaceRoot === "string" && workspaceRoot.trim(); - const { imageSrc, isLoading } = useGatewayUploadedImagePreview( - shouldPreviewImage ? file : undefined, - shouldPreviewImage ? workspaceRoot : undefined, - onLoadUploadedImagePreview, - ); - - if (shouldPreviewImage) { - return ( - - ); - } - - return ( - - ); -} - -function GatewayUserAttachmentCards(props: { - files: PendingUploadedFile[]; - workspaceRoot?: string; - onLoadUploadedImagePreview?: UploadedImagePreviewLoader; - onRemove?: (relativePath: string) => void; - removeLabel?: string; -}) { - const { files, workspaceRoot, onLoadUploadedImagePreview, onRemove, removeLabel } = props; - const { t } = useLocale(); - if (files.length === 0) return null; - - const imageFiles = files.filter((file) => file.kind === "image"); - const otherFiles = files.filter((file) => file.kind !== "image"); - const compactImageLayout = imageFiles.length > 1; - const compactFileLayout = otherFiles.length > 1; - const previewLabel = t("chat.upload.previewImage"); - const closePreviewLabel = t("chat.upload.closePreview"); - - return ( -
      - {imageFiles.length > 0 ? ( -
      - {imageFiles.map((file) => ( - - ))} -
      - ) : null} - {otherFiles.length > 0 ? ( -
      - {otherFiles.map((file) => ( - - ))} -
      - ) : null} -
      - ); -} - -function splitUserAttachmentsForDisplay(files: PendingUploadedFile[], text: string) { - const pastedTextReferences = parsePastedTextDisplayReferences(text); - if (pastedTextReferences.length === 0 || files.length === 0) { - return { - visibleFiles: files, - pastedTextFiles: [], - }; - } - - const pastedTextPaths = new Set(pastedTextReferences.map((reference) => reference.relativePath)); - const pastedTextFiles: PendingUploadedFile[] = []; - const visibleFiles: PendingUploadedFile[] = []; - - for (const file of files) { - if (pastedTextPaths.has(file.relativePath)) { - pastedTextFiles.push(file); - } else { - visibleFiles.push(file); - } - } - - return { - visibleFiles, - pastedTextFiles, - }; -} - -function formatMessageTimestamp(timestamp: number | undefined, now = new Date()): string { - if (!timestamp || !Number.isFinite(timestamp) || timestamp <= 0) return ""; - const date = new Date(timestamp); - const pad = (value: number) => String(value).padStart(2, "0"); - const time = `${pad(date.getHours())}:${pad(date.getMinutes())}`; - if ( - date.getFullYear() === now.getFullYear() && - date.getMonth() === now.getMonth() && - date.getDate() === now.getDate() - ) { - return time; - } - const monthDay = `${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; - if (date.getFullYear() === now.getFullYear()) { - return `${monthDay} ${time}`; - } - return `${date.getFullYear()}-${monthDay} ${time}`; -} - -function GatewayUserMessageBubbleBody(props: { - text: string; - attachments: PendingUploadedFile[]; - workspaceRoot?: string; - onLoadUploadedImagePreview?: UploadedImagePreviewLoader; - loadCommitDetails?: CommitDetailsLoader; -}) { - const { text, attachments, workspaceRoot, onLoadUploadedImagePreview, loadCommitDetails } = props; - const { visibleFiles, pastedTextFiles } = splitUserAttachmentsForDisplay(attachments, text); - - return ( -
      - - {text ? ( - - ) : null} -
      - ); -} - -const MIN_EDIT_BUBBLE_HEIGHT_PX = 72; - -function resizeEditableTextarea(textarea: HTMLTextAreaElement | null) { - if (!textarea) { - return; - } - textarea.style.height = "0px"; - textarea.style.height = `${Math.max(textarea.scrollHeight, MIN_EDIT_BUBBLE_HEIGHT_PX)}px`; -} - -const EditableUserMessageBubble = memo(function EditableUserMessageBubble(props: { - initialText: string; - attachments: PendingUploadedFile[]; - workspaceRoot?: string; - onLoadUploadedImagePreview?: UploadedImagePreviewLoader; - onCancel: () => void; - onSubmit: (text: string, attachments: PendingUploadedFile[]) => void; -}) { - const { - initialText, - attachments, - workspaceRoot, - onLoadUploadedImagePreview, - onCancel, - onSubmit, - } = props; - const { t } = useLocale(); - const [draftText, setDraftText] = useState(initialText); - const [draftAttachments, setDraftAttachments] = useState(attachments); - const textareaRef = useRef(null); - - useLayoutEffect(() => { - const textarea = textareaRef.current; - if (!textarea) { - return; - } - resizeEditableTextarea(textarea); - textarea.focus(); - textarea.selectionStart = textarea.selectionEnd = textarea.value.length; - }, []); - - useEffect(() => { - setDraftAttachments(attachments); - }, [attachments]); - - useLayoutEffect(() => { - resizeEditableTextarea(textareaRef.current); - }, [draftText]); - - // A large paste is stored as an uploaded text file *plus* a - // "[Pasted text N: path]" marker inlined into the message text (rendered - // as a chip once sent, see GatewayUserMessageBubbleBody above). Editing - // must hide that same file's attachment card while its marker is still - // present in the text, otherwise the paste shows up twice: once as a - // card, once as raw marker text in the textarea below. The full - // (unfiltered) list — including pasted-text files — is still what gets - // submitted, so nothing is lost on resend; only the card list is - // narrowed for display. - const visibleAttachments = useMemo( - () => splitUserAttachmentsForDisplay(draftAttachments, draftText).visibleFiles, - [draftAttachments, draftText], - ); - - const canSubmit = draftText.trim().length > 0 || draftAttachments.length > 0; - - return ( -
      - { - setDraftAttachments((current) => - current.filter((file) => file.relativePath !== relativePath), - ); - }} - removeLabel={t("settings.delete")} - /> -