From 9be1e588fc01b6caabbe653ba54ab4062cba1b17 Mon Sep 17 00:00:00 2001 From: namtranii Date: Mon, 6 Apr 2026 15:28:04 +0700 Subject: [PATCH 01/12] feat: add cowork mode (frontend + tauri rust) --- frontend/.env.example | 1 + frontend/package.json | 1 + frontend/pnpm-lock.yaml | 15 + frontend/src-tauri/Cargo.lock | 1345 +++++++++--- frontend/src-tauri/Cargo.toml | 8 + frontend/src-tauri/capabilities/migrated.json | 7 +- .../src/cowork/agent_presets/homepage.rs | 14 + .../src-tauri/src/cowork/agent_presets/mod.rs | 52 + .../src/cowork/agent_presets/organize.rs | 238 +++ .../src/cowork/agent_presets/shared.rs | 236 +++ .../src-tauri/src/cowork/agent_remote/auth.rs | 53 + .../cowork/agent_remote/desktop_dispatcher.rs | 297 +++ .../src/cowork/agent_remote/mapper.rs | 404 ++++ .../src-tauri/src/cowork/agent_remote/mod.rs | 18 + .../src/cowork/agent_remote/payload.rs | 95 + .../src/cowork/agent_remote/prompt.rs | 17 + .../src/cowork/agent_remote/service.rs | 274 +++ .../src/cowork/agent_remote/session_api.rs | 239 +++ .../src/cowork/agent_remote/socket.rs | 599 ++++++ .../src/cowork/agent_remote/types.rs | 142 ++ frontend/src-tauri/src/cowork/chat.rs | 226 ++ .../src-tauri/src/cowork/chat_commands.rs | 30 + .../src/cowork/desktop_skills/mod.rs | 27 + .../src/cowork/desktop_tools/apply_patch.rs | 263 +++ .../src/cowork/desktop_tools/bash.rs | 280 +++ .../src/cowork/desktop_tools/edit.rs | 90 + .../src/cowork/desktop_tools/glob.rs | 181 ++ .../src/cowork/desktop_tools/grep.rs | 258 +++ .../src/cowork/desktop_tools/list_dir.rs | 266 +++ .../src-tauri/src/cowork/desktop_tools/mod.rs | 306 +++ .../src/cowork/desktop_tools/read.rs | 79 + .../src/cowork/desktop_tools/todo_write.rs | 57 + .../src/cowork/desktop_tools/write.rs | 61 + .../src/cowork/homepage/chat_sessions.rs | 311 +++ frontend/src-tauri/src/cowork/homepage/mod.rs | 1 + frontend/src-tauri/src/cowork/mod.rs | 12 + .../src/cowork/organize/capabilities/mod.rs | 25 + .../src/cowork/organize/chat_prompt.rs | 100 + .../src/cowork/organize/file_tree.rs | 296 +++ frontend/src-tauri/src/cowork/organize/mod.rs | 4 + .../src-tauri/src/cowork/organize/sessions.rs | 475 +++++ frontend/src-tauri/src/cowork/runtime.rs | 32 + .../src-tauri/src/cowork/session_gateway.rs | 535 +++++ frontend/src-tauri/src/cowork/string_utils.rs | 8 + frontend/src-tauri/src/cowork/time_utils.rs | 9 + frontend/src-tauri/src/main.rs | 24 +- frontend/src-tauri/tauri.conf.json | 7 +- frontend/src/app/router.tsx | 13 + frontend/src/app/routes/cowork.tsx | 7 + frontend/src/app/routes/home.tsx | 7 + frontend/src/app/routes/login.tsx | 123 +- frontend/src/app/routes/signup.tsx | 96 +- .../src/components/agent-setting/index.tsx | 4 +- .../components/agent-setting/tool-setting.tsx | 5 +- .../src/components/agent/chat-message.tsx | 9 +- .../src/components/agent/search-browser.tsx | 26 +- frontend/src/components/chat-header.tsx | 3 +- .../cowork/chat/cowork-chat-box.tsx | 288 +++ .../chat/cowork-chatmessage-contract.ts | 13 + .../chat/use-cowork-chatmessage-adapter.ts | 222 ++ .../components/cowork/cowork-build-panel.tsx | 1 + .../cowork-build/cowork-build-panel.tsx | 57 + .../cowork-build/cowork-build.renderers.tsx | 147 ++ .../cowork-build/cowork-build.shared.tsx | 430 ++++ .../cowork/cowork-build/cowork-build.types.ts | 57 + .../src/components/cowork/cowork-header.tsx | 39 + .../src/components/cowork/cowork-main.tsx | 103 + .../src/components/cowork/cowork-page.tsx | 1872 +++++++++++++++++ .../src/components/cowork/cowork-sidebar.tsx | 146 ++ .../src/components/cowork/cowork-tabs.tsx | 352 ++++ .../src/components/cowork/cowork.constants.ts | 13 + .../cowork/modes/organize-file-folder.tsx | 539 +++++ .../cowork-organize-build.tsx | 76 + .../cowork-organize-result.tsx | 40 + .../cowork-organize-source.tsx | 40 + .../cowork-organize-steps.tsx | 77 + .../cowork-organize-tree-icons.tsx | 183 ++ .../cowork-organize-tree-view.tsx | 301 +++ .../organize-tree-utils.ts | 5 + frontend/src/components/home-mobile.tsx | 59 +- .../src/components/layouts/root-layout.tsx | 4 +- frontend/src/components/question-input.tsx | 28 +- .../src/components/question-mode-selector.tsx | 104 +- frontend/src/components/ui/popover.tsx | 10 +- frontend/src/constants/question-mode.ts | 5 + frontend/src/contexts/auth-context.tsx | 16 +- frontend/src/hooks/use-cowork-auth-sync.tsx | 69 + .../src/hooks/use-websocket-auth-sync.tsx | 42 +- frontend/src/lib/axios.ts | 9 +- frontend/src/services/auth.service.ts | 24 + frontend/src/services/cowork.service.ts | 177 ++ frontend/src/state/api/session.api.ts | 12 +- frontend/src/state/api/user.api.ts | 6 +- frontend/src/typings/agent.ts | 4 +- frontend/src/typings/cowork.ts | 182 ++ frontend/src/utils/auth-token.ts | 24 + frontend/src/utils/is-tauri.ts | 4 + frontend/src/utils/question-mode.ts | 46 + frontend/src/vite-env.d.ts | 1 + 99 files changed, 13740 insertions(+), 398 deletions(-) create mode 100644 frontend/src-tauri/src/cowork/agent_presets/homepage.rs create mode 100644 frontend/src-tauri/src/cowork/agent_presets/mod.rs create mode 100644 frontend/src-tauri/src/cowork/agent_presets/organize.rs create mode 100644 frontend/src-tauri/src/cowork/agent_presets/shared.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/auth.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/desktop_dispatcher.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/mapper.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/mod.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/payload.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/prompt.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/service.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/session_api.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/socket.rs create mode 100644 frontend/src-tauri/src/cowork/agent_remote/types.rs create mode 100644 frontend/src-tauri/src/cowork/chat.rs create mode 100644 frontend/src-tauri/src/cowork/chat_commands.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_skills/mod.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/apply_patch.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/bash.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/edit.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/glob.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/grep.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/list_dir.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/mod.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/read.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/todo_write.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/write.rs create mode 100644 frontend/src-tauri/src/cowork/homepage/chat_sessions.rs create mode 100644 frontend/src-tauri/src/cowork/homepage/mod.rs create mode 100644 frontend/src-tauri/src/cowork/mod.rs create mode 100644 frontend/src-tauri/src/cowork/organize/capabilities/mod.rs create mode 100644 frontend/src-tauri/src/cowork/organize/chat_prompt.rs create mode 100644 frontend/src-tauri/src/cowork/organize/file_tree.rs create mode 100644 frontend/src-tauri/src/cowork/organize/mod.rs create mode 100644 frontend/src-tauri/src/cowork/organize/sessions.rs create mode 100644 frontend/src-tauri/src/cowork/runtime.rs create mode 100644 frontend/src-tauri/src/cowork/session_gateway.rs create mode 100644 frontend/src-tauri/src/cowork/string_utils.rs create mode 100644 frontend/src-tauri/src/cowork/time_utils.rs create mode 100644 frontend/src/app/routes/cowork.tsx create mode 100644 frontend/src/components/cowork/chat/cowork-chat-box.tsx create mode 100644 frontend/src/components/cowork/chat/cowork-chatmessage-contract.ts create mode 100644 frontend/src/components/cowork/chat/use-cowork-chatmessage-adapter.ts create mode 100644 frontend/src/components/cowork/cowork-build-panel.tsx create mode 100644 frontend/src/components/cowork/cowork-build/cowork-build-panel.tsx create mode 100644 frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx create mode 100644 frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx create mode 100644 frontend/src/components/cowork/cowork-build/cowork-build.types.ts create mode 100644 frontend/src/components/cowork/cowork-header.tsx create mode 100644 frontend/src/components/cowork/cowork-main.tsx create mode 100644 frontend/src/components/cowork/cowork-page.tsx create mode 100644 frontend/src/components/cowork/cowork-sidebar.tsx create mode 100644 frontend/src/components/cowork/cowork-tabs.tsx create mode 100644 frontend/src/components/cowork/cowork.constants.ts create mode 100644 frontend/src/components/cowork/modes/organize-file-folder.tsx create mode 100644 frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx create mode 100644 frontend/src/components/cowork/organize-file-folder/cowork-organize-result.tsx create mode 100644 frontend/src/components/cowork/organize-file-folder/cowork-organize-source.tsx create mode 100644 frontend/src/components/cowork/organize-file-folder/cowork-organize-steps.tsx create mode 100644 frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-icons.tsx create mode 100644 frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-view.tsx create mode 100644 frontend/src/components/cowork/organize-file-folder/organize-tree-utils.ts create mode 100644 frontend/src/hooks/use-cowork-auth-sync.tsx create mode 100644 frontend/src/services/cowork.service.ts create mode 100644 frontend/src/typings/cowork.ts create mode 100644 frontend/src/utils/auth-token.ts create mode 100644 frontend/src/utils/is-tauri.ts create mode 100644 frontend/src/utils/question-mode.ts diff --git a/frontend/.env.example b/frontend/.env.example index 9a373a548..05610637a 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -3,3 +3,4 @@ VITE_GOOGLE_CLIENT_ID="" # google oauth client id VITE_DISABLE_BUILD_OPTIMIZATIONS=false # set true to disable minify/treeshake VITE_THEME="" # set to "sage" for sage theme, leave empty for default VITE_PORT=1420 # frontend dev server port +VITE_FRONTEND_URL=http://localhost:1420 # frontend url for desktop app \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index cbb3d71a3..3341506da 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,6 +49,7 @@ "@tailwindcss/vite": "^4.1.5", "@tanstack/react-table": "^8.21.3", "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-dialog": "^2.6.0", "@tauri-apps/plugin-process": "^2.2.1", "@tauri-apps/plugin-shell": "^2.2.1", "@types/hast": "^3.0.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 0bf002b7f..c56a9b5bb 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: '@tauri-apps/api': specifier: ^2.5.0 version: 2.7.0 + '@tauri-apps/plugin-dialog': + specifier: ^2.6.0 + version: 2.7.0 '@tauri-apps/plugin-process': specifier: ^2.2.1 version: 2.3.0 @@ -1678,6 +1681,9 @@ packages: resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} + '@tauri-apps/api@2.10.1': + resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==} + '@tauri-apps/api@2.7.0': resolution: {integrity: sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg==} @@ -1752,6 +1758,9 @@ packages: engines: {node: '>= 10'} hasBin: true + '@tauri-apps/plugin-dialog@2.7.0': + resolution: {integrity: sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw==} + '@tauri-apps/plugin-process@2.3.0': resolution: {integrity: sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==} @@ -6060,6 +6069,8 @@ snapshots: '@tanstack/table-core@8.21.3': {} + '@tauri-apps/api@2.10.1': {} + '@tauri-apps/api@2.7.0': {} '@tauri-apps/cli-darwin-arm64@2.7.1': @@ -6109,6 +6120,10 @@ snapshots: '@tauri-apps/cli-win32-ia32-msvc': 2.7.1 '@tauri-apps/cli-win32-x64-msvc': 2.7.1 + '@tauri-apps/plugin-dialog@2.7.0': + dependencies: + '@tauri-apps/api': 2.10.1 + '@tauri-apps/plugin-process@2.3.0': dependencies: '@tauri-apps/api': 2.7.0 diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index ab859b485..e75e87950 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -6,10 +6,18 @@ version = 4 name = "II-Agent" version = "0.1.0" dependencies = [ + "chrono", + "futures-util", + "glob", + "regex", + "reqwest 0.12.28", + "rust_socketio", "serde", "serde_json", + "sha2", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-process", "tauri-plugin-shell", ] @@ -29,6 +37,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -74,6 +88,39 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "atk" version = "0.18.2" @@ -97,12 +144,29 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom 0.2.16", + "instant", + "rand 0.8.5", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -138,11 +202,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -226,7 +290,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "cairo-sys-rs", "glib", "libc", @@ -284,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" dependencies = [ "serde", - "toml", + "toml 0.8.22", ] [[package]] @@ -343,9 +407,11 @@ checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-link", + "wasm-bindgen", + "windows-link 0.1.1", ] [[package]] @@ -374,6 +440,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -396,10 +472,23 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.0", - "core-foundation", + "bitflags 2.11.0", + "core-foundation 0.10.0", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.0", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -409,8 +498,8 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.0", - "core-foundation", + "bitflags 2.11.0", + "core-foundation 0.10.0", "libc", ] @@ -529,6 +618,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + [[package]] name = "deranged" version = "0.4.0" @@ -580,22 +675,18 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "dispatch2" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", + "block2 0.6.1", + "libc", "objc2 0.6.1", ] @@ -612,9 +703,9 @@ dependencies = [ [[package]] name = "dlopen2" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" dependencies = [ "dlopen2_derive", "libc", @@ -678,7 +769,7 @@ dependencies = [ "cc", "memchr", "rustc_version", - "toml", + "toml 0.8.22", "vswhom", "winreg", ] @@ -714,6 +805,22 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fdeflate" version = "0.3.7" @@ -749,6 +856,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -756,7 +872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -770,6 +886,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -802,6 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1003,8 +1126,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1014,9 +1139,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -1063,7 +1190,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "futures-channel", "futures-core", "futures-executor", @@ -1173,6 +1300,25 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1181,9 +1327,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1257,41 +1403,82 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", + "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1320,9 +1507,9 @@ dependencies = [ [[package]] name = "ico" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" dependencies = [ "byteorder", "png", @@ -1486,13 +1673,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.16.1", "serde", + "serde_core", ] [[package]] @@ -1504,12 +1692,31 @@ dependencies = [ "cfb", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -1582,10 +1789,12 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1618,7 +1827,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "serde", "unicode-segmentation", ] @@ -1631,16 +1840,10 @@ checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 2.9.0", + "indexmap 2.13.0", "selectors", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libappindicator" version = "0.9.0" @@ -1687,10 +1890,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.7.5" @@ -1713,6 +1922,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -1813,13 +2028,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "jni-sys", "log", "ndk-sys", @@ -1923,38 +2155,10 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "block2 0.6.1", - "libc", "objc2 0.6.1", - "objc2-cloud-kit", - "objc2-core-data", "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.1", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" -dependencies = [ - "bitflags 2.9.0", - "objc2 0.6.1", - "objc2-foundation 0.3.1", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" -dependencies = [ - "bitflags 2.9.0", - "objc2 0.6.1", "objc2-foundation 0.3.1", ] @@ -1964,7 +2168,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "dispatch2", "objc2 0.6.1", ] @@ -1975,21 +2179,8 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "bitflags 2.9.0", - "dispatch2", - "objc2 0.6.1", + "bitflags 2.11.0", "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" -dependencies = [ - "objc2 0.6.1", - "objc2-foundation 0.3.1", ] [[package]] @@ -2013,7 +2204,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -2025,20 +2216,8 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "block2 0.6.1", - "libc", - "objc2 0.6.1", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" -dependencies = [ - "bitflags 2.9.0", "objc2 0.6.1", "objc2-core-foundation", ] @@ -2049,7 +2228,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -2061,31 +2240,20 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", ] -[[package]] -name = "objc2-quartz-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" -dependencies = [ - "bitflags 2.9.0", - "objc2 0.6.1", - "objc2-foundation 0.3.1", -] - [[package]] name = "objc2-ui-kit" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "objc2 0.6.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -2097,7 +2265,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "block2 0.6.1", "objc2 0.6.1", "objc2-app-kit", @@ -2133,37 +2301,81 @@ dependencies = [ ] [[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_pipe" -version = "1.2.1" +name = "openssl" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", "libc", - "windows-sys 0.59.0", + "once_cell", + "openssl-macros", + "openssl-sys", ] [[package]] -name = "pango" -version = "0.18.3" +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] -name = "pango-sys" -version = "0.18.0" +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" dependencies = [ @@ -2367,7 +2579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" dependencies = [ "base64 0.22.1", - "indexmap 2.9.0", + "indexmap 2.13.0", "quick-xml", "serde", "time", @@ -2483,6 +2695,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.40" @@ -2523,6 +2790,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2543,6 +2820,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2561,6 +2848,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2591,7 +2887,7 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", ] [[package]] @@ -2636,40 +2932,166 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", + "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", - "once_cell", + "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", - "windows-registry", + "webpki-roots", +] + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2 0.6.1", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.1", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rust_engineio" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d3572ceba6c5d79eecedf3be93640ca9512fa4100dff6a70f96c514adf4f1f" +dependencies = [ + "adler32", + "async-stream", + "async-trait", + "base64 0.21.7", + "bytes", + "futures-util", + "http", + "native-tls", + "reqwest 0.12.28", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-tungstenite", + "tungstenite", + "url", +] + +[[package]] +name = "rust_socketio" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6a8672db895d567b3c0b8a4c0d3e98113ebb32badf6ce66004e743e5ee1e1e" +dependencies = [ + "adler32", + "backoff", + "base64 0.21.7", + "bytes", + "log", + "native-tls", + "rand 0.8.5", + "rust_engineio", + "serde", + "serde_json", + "thiserror 1.0.69", + "url", ] [[package]] @@ -2678,6 +3100,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2687,6 +3115,54 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -2708,6 +3184,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -2741,6 +3226,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.24.0" @@ -2770,10 +3278,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -2788,11 +3297,20 @@ dependencies = [ "typeid", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2842,6 +3360,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2864,7 +3391,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", + "indexmap 2.13.0", "serde", "serde_derive", "serde_json", @@ -2886,9 +3413,9 @@ dependencies = [ [[package]] name = "serialize-to-javascript" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" dependencies = [ "serde", "serde_json", @@ -2897,13 +3424,13 @@ dependencies = [ [[package]] name = "serialize-to-javascript-impl" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.101", ] [[package]] @@ -2916,6 +3443,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2994,13 +3532,13 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "bytemuck", "cfg_aliases", - "core-graphics", - "foreign-types", + "core-graphics 0.24.0", + "foreign-types 0.5.0", "js-sys", "log", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", + "objc2-quartz-core", "raw-window-handle", "redox_syscall", "wasm-bindgen", @@ -3071,6 +3609,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -3124,6 +3668,27 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3133,28 +3698,28 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml", + "toml 0.8.22", "version-compare", ] [[package]] name = "tao" -version = "0.34.0" +version = "0.34.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" dependencies = [ - "bitflags 2.9.0", - "core-foundation", - "core-graphics", + "bitflags 2.11.0", + "block2 0.6.1", + "core-foundation 0.10.0", + "core-graphics 0.25.0", "crossbeam-channel", - "dispatch", + "dispatch2", "dlopen2", "dpi", "gdkwayland-sys", "gdkx11-sys", "gtk", "jni", - "lazy_static", "libc", "log", "ndk", @@ -3166,7 +3731,6 @@ dependencies = [ "once_cell", "parking_lot", "raw-window-handle", - "scopeguard", "tao-macros", "unicode-segmentation", "url", @@ -3195,12 +3759,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.6.2" +version = "2.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" +checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" dependencies = [ "anyhow", "bytes", + "cookie", "dirs", "dunce", "embed_plist", @@ -3218,10 +3783,11 @@ dependencies = [ "objc2-app-kit", "objc2-foundation 0.3.1", "objc2-ui-kit", + "objc2-web-kit", "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.2", "serde", "serde_json", "serde_repr", @@ -3236,7 +3802,6 @@ dependencies = [ "tokio", "tray-icon", "url", - "urlpattern", "webkit2gtk", "webview2-com", "window-vibrancy", @@ -3245,9 +3810,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.3.0" +version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" +checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" dependencies = [ "anyhow", "cargo_toml", @@ -3261,15 +3826,15 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "toml", + "toml 0.9.12+spec-1.1.0", "walkdir", ] [[package]] name = "tauri-codegen" -version = "2.3.0" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" +checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" dependencies = [ "base64 0.22.1", "brotli", @@ -3294,9 +3859,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.3.1" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" +checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3308,9 +3873,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.3.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" +checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" dependencies = [ "anyhow", "glob", @@ -3319,10 +3884,50 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml", + "toml 0.9.12+spec-1.1.0", "walkdir", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.12", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.12", + "toml 0.9.12+spec-1.1.0", + "url", +] + [[package]] name = "tauri-plugin-process" version = "2.3.0" @@ -3356,9 +3961,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.7.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" +checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2" dependencies = [ "cookie", "dpi", @@ -3367,20 +3972,23 @@ dependencies = [ "jni", "objc2 0.6.1", "objc2-ui-kit", + "objc2-web-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror 2.0.12", "url", + "webkit2gtk", + "webview2-com", "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.7.1" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" +checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" dependencies = [ "gtk", "http", @@ -3388,7 +3996,6 @@ dependencies = [ "log", "objc2 0.6.1", "objc2-app-kit", - "objc2-foundation 0.3.1", "once_cell", "percent-encoding", "raw-window-handle", @@ -3405,9 +4012,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.5.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" +checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" dependencies = [ "anyhow", "brotli", @@ -3434,7 +4041,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.12", - "toml", + "toml 0.9.12+spec-1.1.0", "url", "urlpattern", "uuid", @@ -3448,8 +4055,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" dependencies = [ "embed-resource", - "indexmap 2.9.0", - "toml", + "indexmap 2.13.0", + "toml 0.8.22", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.60.2", ] [[package]] @@ -3544,6 +4164,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.44.2" @@ -3559,6 +4194,40 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.15" @@ -3579,11 +4248,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.8", + "toml_datetime 0.6.9", "toml_edit 0.22.26", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.1.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + [[package]] name = "toml_datetime" version = "0.6.9" @@ -3593,14 +4277,23 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.9.0", - "toml_datetime", + "indexmap 2.13.0", + "toml_datetime 0.6.9", "winnow 0.5.40", ] @@ -3610,8 +4303,8 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.9.0", - "toml_datetime", + "indexmap 2.13.0", + "toml_datetime 0.6.9", "winnow 0.5.40", ] @@ -3621,12 +4314,21 @@ version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.13.0", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.8", + "toml_datetime 0.6.9", "toml_write", - "winnow 0.7.9", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +dependencies = [ + "winnow 1.0.1", ] [[package]] @@ -3635,6 +4337,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +[[package]] +name = "toml_writer" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" + [[package]] name = "tower" version = "0.5.2" @@ -3650,6 +4358,24 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -3709,6 +4435,26 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.3" @@ -3774,6 +4520,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -3826,6 +4578,12 @@ dependencies = [ "serde", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -3900,48 +4658,32 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.101", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "19280959e2844181895ef62f065c63e0ca07ece4771b53d89bfdb967d97cbf05" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3949,22 +4691,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.101", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633" dependencies = [ "unicode-ident", ] @@ -3982,11 +4724,34 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749466a37ee189057f54748b200186b59a03417a117267baf3fd89cecc9fb837" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -3994,9 +4759,9 @@ dependencies = [ [[package]] name = "webkit2gtk" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -4018,9 +4783,9 @@ dependencies = [ [[package]] name = "webkit2gtk-sys" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" dependencies = [ "bitflags 1.3.2", "cairo-sys-rs", @@ -4036,6 +4801,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.38.0" @@ -4127,7 +4901,7 @@ dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.1", "windows-numerics", ] @@ -4148,8 +4922,8 @@ checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", + "windows-link 0.1.1", + "windows-result 0.3.2", "windows-strings 0.4.0", ] @@ -4160,7 +4934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -4191,6 +4965,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -4198,18 +4978,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.1", ] [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-result", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -4218,16 +4998,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] -name = "windows-strings" -version = "0.3.1" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4236,7 +5016,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ - "windows-link", + "windows-link 0.1.1", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -4275,6 +5064,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -4323,10 +5130,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -4343,7 +5151,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" dependencies = [ - "windows-link", + "windows-link 0.1.1", ] [[package]] @@ -4537,13 +5345,19 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" + [[package]] name = "winreg" version = "0.52.0" @@ -4560,7 +5374,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", ] [[package]] @@ -4577,14 +5391,15 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "wry" -version = "0.52.1" +version = "0.54.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" +checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" dependencies = [ "base64 0.22.1", "block2 0.6.1", "cookie", "crossbeam-channel", + "dirs", "dpi", "dunce", "gdkx11", @@ -4705,6 +5520,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 8730f8348..58c97db42 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -14,8 +14,16 @@ tauri-build = { version = "2.0.3", features = [] } tauri = { version = "2.1.1", features = [] } serde = { version = "1", features = ["derive"] } serde_json = "1" +chrono = { version = "0.4", features = ["clock"] } tauri-plugin-shell = "2" tauri-plugin-process = "2.3.0" +tauri-plugin-dialog = "2.6.0" +reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] } +futures-util = "0.3" +rust_socketio = "0.6" +glob = "0.3" +regex = "1" +sha2 = "0.10" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/frontend/src-tauri/capabilities/migrated.json b/frontend/src-tauri/capabilities/migrated.json index 9e7c17be9..52103c575 100644 --- a/frontend/src-tauri/capabilities/migrated.json +++ b/frontend/src-tauri/capabilities/migrated.json @@ -7,7 +7,10 @@ ], "permissions": [ "core:default", + "core:window:allow-set-focus", + "dialog:default", "process:default", - "shell:default" + "shell:default", + "shell:allow-open" ] -} \ No newline at end of file +} diff --git a/frontend/src-tauri/src/cowork/agent_presets/homepage.rs b/frontend/src-tauri/src/cowork/agent_presets/homepage.rs new file mode 100644 index 000000000..facaae060 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_presets/homepage.rs @@ -0,0 +1,14 @@ +use super::shared; +use crate::cowork::chat::CoworkAgentOverrides; + +pub fn homepage_builtin_agent_overrides(_prompt_context: Option<&str>) -> CoworkAgentOverrides { + CoworkAgentOverrides { + system_prompt: Some( + "You are the Cowork homepage agent inside II Agent desktop. Help the user reason over local cowork context, use tools deliberately, and keep responses concise and actionable." + .to_string(), + ), + tool_names: Some(shared::base_tool_names()), + skill_names: None, + runtime_options: Some(shared::default_runtime_options("homepage_cowork_agent")), + } +} diff --git a/frontend/src-tauri/src/cowork/agent_presets/mod.rs b/frontend/src-tauri/src/cowork/agent_presets/mod.rs new file mode 100644 index 000000000..c2ec29de7 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_presets/mod.rs @@ -0,0 +1,52 @@ +pub mod homepage; +pub mod organize; +pub mod shared; + +use crate::cowork::chat::{CoworkAgentOverrides, CoworkChatScope, CoworkChatToolSettings}; +use crate::cowork::desktop_skills::DesktopSkill; +use crate::cowork::desktop_tools::{ + DesktopExecutionScopeLoaderFn, DesktopExecutionScopeRefreshFn, DesktopTool, +}; +use shared::DesktopCapabilities; + +pub struct DesktopRuntimePreset { + pub tools: Vec, + pub skills: Vec, + pub load_execution_scope: DesktopExecutionScopeLoaderFn, + pub refresh_scope: Option, +} + +pub struct ResolvedDesktopPreset { + pub capabilities: DesktopCapabilities, + pub runtime: DesktopRuntimePreset, +} + +pub fn resolve_agent_overrides( + scope: CoworkChatScope, + prompt_context: Option<&str>, + tools: Option<&CoworkChatToolSettings>, + runtime_overrides: Option, +) -> CoworkAgentOverrides { + let builtin = match scope { + CoworkChatScope::Homepage => homepage::homepage_builtin_agent_overrides(prompt_context), + CoworkChatScope::OrganizeFileFolder => { + organize::organize_builtin_agent_overrides(prompt_context) + } + }; + + let merged = shared::merge_agent_overrides(builtin, runtime_overrides); + match scope { + CoworkChatScope::Homepage => shared::apply_tool_settings(merged, tools), + CoworkChatScope::OrganizeFileFolder => shared::lock_tool_names_to_desktop(merged), + } +} + +pub fn resolve_desktop_preset(scope: CoworkChatScope) -> Option { + match scope { + CoworkChatScope::Homepage => None, + CoworkChatScope::OrganizeFileFolder => Some(ResolvedDesktopPreset { + capabilities: organize::build_organize_desktop_capabilities(), + runtime: organize::build_organize_desktop_runtime(), + }), + } +} diff --git a/frontend/src-tauri/src/cowork/agent_presets/organize.rs b/frontend/src-tauri/src/cowork/agent_presets/organize.rs new file mode 100644 index 000000000..2dbf16615 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_presets/organize.rs @@ -0,0 +1,238 @@ +use super::shared; +use super::shared::{DesktopCapabilities, DesktopSkillCapability, DesktopToolCapability}; +use super::DesktopRuntimePreset; +use crate::cowork::chat::CoworkAgentOverrides; +use crate::cowork::desktop_skills::DesktopSkill; +use crate::cowork::desktop_tools::{DesktopExecutionScope, DesktopTool}; +use crate::cowork::organize::capabilities; +use crate::cowork::organize::sessions; +use crate::cowork::session_gateway::{self, LocalCoworkSession}; +use std::fs; +use tauri::AppHandle; + +pub fn organize_builtin_agent_overrides(prompt_context: Option<&str>) -> CoworkAgentOverrides { + CoworkAgentOverrides { + system_prompt: Some(build_organize_system_prompt(prompt_context)), + tool_names: Some(build_organize_tool_names()), + skill_names: None, + runtime_options: Some(shared::default_runtime_options("organize_cowork_agent")), + } +} + +pub fn build_organize_desktop_capabilities() -> DesktopCapabilities { + DesktopCapabilities { + tools: merge_tool_capabilities( + shared::desktop_built_tools(), + capabilities::desktop_tool_capabilities(), + ), + skills: build_organize_desktop_skills(), + } +} + +pub fn build_organize_desktop_runtime() -> DesktopRuntimePreset { + DesktopRuntimePreset { + tools: build_organize_runtime_tools(), + skills: build_organize_runtime_skills(), + load_execution_scope: load_organize_execution_scope, + refresh_scope: Some(refresh_organize_execution_scope), + } +} + +fn build_organize_desktop_tools() -> Vec { + build_organize_desktop_capabilities().tools +} + +fn build_organize_desktop_skills() -> Vec { + merge_skill_capabilities( + shared::desktop_built_skills(), + capabilities::desktop_skill_capabilities(), + ) +} + +fn build_organize_tool_names() -> Vec { + build_organize_desktop_tools() + .into_iter() + .map(|tool| tool.name) + .collect() +} + +fn build_organize_runtime_tools() -> Vec { + merge_runtime_tools( + shared::desktop_runtime_tools(), + capabilities::desktop_tools(), + ) +} + +fn build_organize_runtime_skills() -> Vec { + merge_runtime_skills( + shared::desktop_runtime_skills(), + capabilities::desktop_runtime_skills(), + ) +} + +fn build_organize_system_prompt(prompt_context: Option<&str>) -> String { + let mut prompt = "You are the Cowork organize agent inside II Agent desktop.\n\ +Focus on analyzing and reorganizing the user's local file and folder structure with careful, tool-assisted reasoning.\n\ +Treat this mode as organize-file-folder scope for a desktop-selected folder.\n\ +Use only the built desktop tools provided for local file inspection and edits.\n\ +Do not assume backend-only, browser, connector, repository, or web tools exist in this mode." + .to_string(); + + if let Some(source_root) = extract_prompt_value(prompt_context, "Input folder path:") + .or_else(|| extract_prompt_value(prompt_context, "Source root:")) + { + prompt.push_str("\n\n[Organize scope]"); + prompt.push_str("\nMode scope: organize-file-folder"); + prompt.push_str("\nInput folder path: "); + prompt.push_str(&source_root); + + if let Some(result_root) = extract_prompt_value(prompt_context, "Result root:") { + prompt.push_str("\nResult folder path: "); + prompt.push_str(&result_root); + } + } + + prompt +} + +fn extract_prompt_value(prompt_context: Option<&str>, label: &str) -> Option { + let prompt_context = prompt_context?; + + prompt_context.lines().find_map(|line| { + line.strip_prefix(label) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string) + }) +} + +fn merge_tool_capabilities( + common_tools: Vec, + mode_tools: Vec, +) -> Vec { + let mut merged = common_tools; + for tool in mode_tools { + if merged.iter().any(|existing| existing.name == tool.name) { + continue; + } + merged.push(tool); + } + merged +} + +fn merge_runtime_tools( + common_tools: Vec, + mode_tools: Vec, +) -> Vec { + let mut merged = common_tools; + for tool in mode_tools { + if merged.iter().any(|existing| existing.name() == tool.name()) { + continue; + } + merged.push(tool); + } + merged +} + +fn merge_skill_capabilities( + common_skills: Vec, + mode_skills: Vec, +) -> Vec { + let mut merged = common_skills; + for skill in mode_skills { + if merged.iter().any(|existing| existing.name == skill.name) { + continue; + } + merged.push(skill); + } + merged +} + +fn merge_runtime_skills( + common_skills: Vec, + mode_skills: Vec, +) -> Vec { + let mut merged = common_skills; + for skill in mode_skills { + if merged + .iter() + .any(|existing| existing.name() == skill.name()) + { + continue; + } + merged.push(skill); + } + merged +} + +fn load_organize_execution_scope( + app: &AppHandle, + local_session_id: &str, +) -> Result { + let local_session = session_gateway::load_local_session( + app, + crate::cowork::chat::CoworkChatScope::OrganizeFileFolder, + local_session_id, + )?; + let LocalCoworkSession::Organize(detail) = local_session else { + return Err("Desktop organize tool execution requires an organize session".to_string()); + }; + + let root_path = detail.organize_tree_pair.source_root.clone(); + let canonical_root = fs::canonicalize(&root_path).map_err(|error| { + format!( + "Failed to resolve organize source_root {}: {}", + root_path, error + ) + })?; + + Ok(DesktopExecutionScope::new(canonical_root)) +} + +fn refresh_organize_execution_scope( + app: &AppHandle, + local_session_id: &str, + _execution_scope: &DesktopExecutionScope, +) -> Result<(), String> { + let local_session = session_gateway::load_local_session( + app, + crate::cowork::chat::CoworkChatScope::OrganizeFileFolder, + local_session_id, + )?; + let LocalCoworkSession::Organize(mut detail) = local_session else { + return Ok(()); + }; + sessions::sync_result_tree_from_disk(&mut detail)?; + session_gateway::persist_local_session(app, LocalCoworkSession::Organize(detail))?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build_organize_system_prompt_embeds_local_scope() { + let prompt = build_organize_system_prompt(Some( + "[Local organize scope]\nInput folder path: C:/Users/demo/Documents\nResult root: C:/Users/demo/Documents", + )); + + assert!(prompt.contains("Mode scope: organize-file-folder")); + assert!(prompt.contains("Input folder path: C:/Users/demo/Documents")); + assert!(prompt.contains("Result folder path: C:/Users/demo/Documents")); + assert!(prompt.contains("Use only the built desktop tools")); + } + + #[test] + fn build_organize_desktop_capabilities_match_override_tool_names() { + let overrides = organize_builtin_agent_overrides(None); + let capabilities = build_organize_desktop_capabilities(); + let capability_tool_names = capabilities + .tools + .into_iter() + .map(|tool| tool.name) + .collect::>(); + + assert_eq!(overrides.tool_names, Some(capability_tool_names)); + } +} diff --git a/frontend/src-tauri/src/cowork/agent_presets/shared.rs b/frontend/src-tauri/src/cowork/agent_presets/shared.rs new file mode 100644 index 000000000..ae1d574fd --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_presets/shared.rs @@ -0,0 +1,236 @@ +use crate::cowork::chat::{CoworkAgentOverrides, CoworkChatToolSettings}; +use crate::cowork::desktop_skills::DesktopSkill; +use crate::cowork::desktop_tools::{resolve_desktop_tool_name, DesktopTool}; +use serde::Serialize; +use serde_json::{Map, Value}; + +#[derive(Debug, Clone, Serialize)] +pub struct DesktopCapabilities { + pub tools: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub skills: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DesktopToolCapability { + pub name: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub aliases: Vec, + pub display_name: String, + pub description: String, + pub input_schema: Value, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DesktopSkillCapability { + pub name: String, + pub description: String, +} + +pub const TOOL_WEB_SEARCH: &str = "web_search"; +pub const TOOL_WEB_VISIT: &str = "web_visit"; + +pub fn base_tool_names() -> Vec { + vec![TOOL_WEB_SEARCH.to_string(), TOOL_WEB_VISIT.to_string()] +} + +pub fn desktop_built_tools() -> Vec { + crate::cowork::desktop_tools::common_desktop_tool_capabilities() +} + +pub fn desktop_runtime_tools() -> Vec { + crate::cowork::desktop_tools::common_desktop_tools() +} + +pub fn desktop_built_tool_names() -> Vec { + crate::cowork::desktop_tools::common_desktop_tool_names() +} + +pub fn desktop_built_skills() -> Vec { + crate::cowork::desktop_skills::common_desktop_skill_capabilities() +} + +pub fn desktop_runtime_skills() -> Vec { + crate::cowork::desktop_skills::common_desktop_skills() +} + +pub fn default_runtime_options(agent_name: &str) -> Value { + Value::Object(Map::from_iter([ + ("name".to_string(), Value::String(agent_name.to_string())), + ("retries".to_string(), Value::from(0)), + ("delay_between_retries".to_string(), Value::from(1)), + ("exponential_backoff".to_string(), Value::Bool(false)), + ("stream".to_string(), Value::Bool(true)), + ("stream_events".to_string(), Value::Bool(true)), + ("store_events".to_string(), Value::Bool(true)), + ("delegate_to_all_members".to_string(), Value::Bool(false)), + ("stream_member_events".to_string(), Value::Bool(true)), + ("store_member_responses".to_string(), Value::Bool(false)), + ])) +} + +pub fn merge_agent_overrides( + builtin: CoworkAgentOverrides, + runtime: Option, +) -> CoworkAgentOverrides { + let Some(runtime) = runtime else { + return builtin; + }; + + CoworkAgentOverrides { + system_prompt: runtime.system_prompt.or(builtin.system_prompt), + tool_names: runtime.tool_names.or(builtin.tool_names), + skill_names: runtime.skill_names.or(builtin.skill_names), + runtime_options: merge_runtime_options( + builtin.runtime_options, + runtime.runtime_options, + ), + } +} + +pub fn apply_tool_settings( + mut overrides: CoworkAgentOverrides, + tools: Option<&CoworkChatToolSettings>, +) -> CoworkAgentOverrides { + let mut tool_names = overrides.tool_names.take().unwrap_or_else(base_tool_names); + let effective_tools = tools.cloned().unwrap_or(CoworkChatToolSettings { + web_search: true, + web_visit: true, + image_search: false, + code_interpreter: None, + generate_image: None, + generate_video: None, + }); + + set_tool_enabled(&mut tool_names, TOOL_WEB_SEARCH, effective_tools.web_search); + set_tool_enabled(&mut tool_names, TOOL_WEB_VISIT, effective_tools.web_visit); + + overrides.tool_names = Some(tool_names); + overrides +} + +pub fn lock_tool_names_to_desktop(mut overrides: CoworkAgentOverrides) -> CoworkAgentOverrides { + let allowed_tool_names = desktop_built_tool_names(); + let requested_tool_names = overrides.tool_names.take(); + let mut filtered_tool_names = Vec::new(); + + for requested_tool_name in requested_tool_names.unwrap_or_else(|| allowed_tool_names.clone()) { + let Some(canonical_tool_name) = resolve_desktop_tool_name(&requested_tool_name) else { + continue; + }; + + if allowed_tool_names + .iter() + .any(|allowed_tool_name| allowed_tool_name == &canonical_tool_name) + && !filtered_tool_names + .iter() + .any(|value| value == &canonical_tool_name) + { + filtered_tool_names.push(canonical_tool_name); + } + } + + overrides.tool_names = Some(if filtered_tool_names.is_empty() { + allowed_tool_names + } else { + filtered_tool_names + }); + overrides +} + +fn merge_runtime_options(builtin: Option, runtime: Option) -> Option { + match (builtin, runtime) { + (None, None) => None, + (Some(base), None) => Some(base), + (None, Some(runtime)) => Some(runtime), + (Some(base), Some(runtime)) => Some(merge_json_values(base, runtime)), + } +} + +fn merge_json_values(base: Value, runtime: Value) -> Value { + match (base, runtime) { + (Value::Object(mut base_map), Value::Object(runtime_map)) => { + for (key, value) in runtime_map { + let merged_value = match base_map.remove(&key) { + Some(existing) => merge_json_values(existing, value), + None => value, + }; + base_map.insert(key, merged_value); + } + Value::Object(base_map) + } + (_, runtime) => runtime, + } +} + +fn set_tool_enabled(tool_names: &mut Vec, tool_name: &str, enabled: bool) { + if enabled { + if !tool_names.iter().any(|value| value == tool_name) { + tool_names.push(tool_name.to_string()); + } + } else { + tool_names.retain(|value| value != tool_name); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cowork::desktop_tools::{TOOL_APPLY_PATCH, TOOL_BASH, TOOL_READ}; + + #[test] + fn lock_tool_names_to_desktop_filters_out_backend_and_web_tools() { + let overrides = CoworkAgentOverrides { + system_prompt: None, + tool_names: Some(vec![ + TOOL_READ.to_string(), + TOOL_WEB_SEARCH.to_string(), + "register_port".to_string(), + TOOL_APPLY_PATCH.to_string(), + ]), + skill_names: None, + runtime_options: None, + }; + + let locked = lock_tool_names_to_desktop(overrides); + + assert_eq!( + locked.tool_names, + Some(vec![TOOL_READ.to_string(), TOOL_APPLY_PATCH.to_string()]) + ); + } + + #[test] + fn lock_tool_names_to_desktop_falls_back_to_desktop_toolset_when_needed() { + let overrides = CoworkAgentOverrides { + system_prompt: None, + tool_names: Some(vec![ + TOOL_WEB_SEARCH.to_string(), + "unknown_tool".to_string(), + ]), + skill_names: None, + runtime_options: None, + }; + + let locked = lock_tool_names_to_desktop(overrides); + + assert_eq!(locked.tool_names, Some(desktop_built_tool_names())); + } + + #[test] + fn lock_tool_names_to_desktop_maps_aliases_to_canonical_names() { + let overrides = CoworkAgentOverrides { + system_prompt: None, + tool_names: Some(vec!["read_file".to_string(), "bash".to_string()]), + skill_names: None, + runtime_options: None, + }; + + let locked = lock_tool_names_to_desktop(overrides); + + assert_eq!( + locked.tool_names, + Some(vec![TOOL_READ.to_string(), TOOL_BASH.to_string()]) + ); + } +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/auth.rs b/frontend/src-tauri/src/cowork/agent_remote/auth.rs new file mode 100644 index 000000000..d647af96e --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/auth.rs @@ -0,0 +1,53 @@ +use crate::cowork::string_utils::normalize_optional_string; +use std::sync::Mutex; +use tauri::State; + +pub const AUTH_REQUIRED_MESSAGE: &str = + "Current user token is not available in Cowork desktop mode. Please sign in again."; + +#[derive(Debug, Default, Clone)] +pub struct RemoteAuthContext { + pub access_token: Option, + pub api_base_url: Option, +} + +#[derive(Debug, Default)] +pub struct RemoteAuthState { + pub inner: Mutex, +} + +#[tauri::command] +pub fn sync_cowork_auth_context( + state: State<'_, RemoteAuthState>, + access_token: Option, + api_base_url: String, +) -> Result<(), String> { + let normalized_base_url = normalize_api_base_url(&api_base_url)?; + let normalized_token = access_token.and_then(|token| normalize_optional_string(&token)); + + let mut auth_context = state + .inner + .lock() + .map_err(|_| "Failed to lock cowork remote auth context".to_string())?; + + auth_context.access_token = normalized_token; + auth_context.api_base_url = Some(normalized_base_url); + + Ok(()) +} + +pub fn get_auth_context(state: &State<'_, RemoteAuthState>) -> RemoteAuthContext { + state + .inner + .lock() + .map(|guard| guard.clone()) + .unwrap_or_default() +} + +fn normalize_api_base_url(value: &str) -> Result { + let normalized = value.trim().trim_end_matches('/').to_string(); + if normalized.is_empty() { + return Err("Cowork API base URL is required".to_string()); + } + Ok(normalized) +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/desktop_dispatcher.rs b/frontend/src-tauri/src/cowork/agent_remote/desktop_dispatcher.rs new file mode 100644 index 000000000..192fd8079 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/desktop_dispatcher.rs @@ -0,0 +1,297 @@ +use crate::cowork::agent_presets::DesktopRuntimePreset; +use crate::cowork::chat::{ + emit_cowork_stream_event, CoworkChatEvent, CoworkChatRunStatus, CoworkChatRuntimeEvent, + CoworkChatScope, +}; +use crate::cowork::desktop_skills::DesktopSkill; +use crate::cowork::desktop_tools::{ + find_desktop_tool, DesktopExecutionScope, DesktopTool, DesktopToolContext, DesktopToolRuntime, +}; +use crate::cowork::session_gateway; +use crate::cowork::time_utils::now_iso; +use serde::Deserialize; +use serde_json::{json, Value}; +use tauri::AppHandle; + +#[derive(Debug, Deserialize)] +struct PausedToolDescriptor { + #[serde(default)] + tool_call_id: Option, + #[serde(default)] + tool_name: Option, + #[serde(default)] + tool_input: Option, + #[serde(default)] + requires_confirmation: Option, + #[serde(default)] + requires_user_input: Option, + #[serde(default)] + external_execution_required: Option, +} + +struct DesktopDispatcher<'a> { + app: &'a AppHandle, + local_session_id: &'a str, + preset: &'a DesktopRuntimePreset, + execution_scope: DesktopExecutionScope, + runtime: &'a mut DesktopToolRuntime, +} + +impl<'a> DesktopDispatcher<'a> { + fn new( + app: &'a AppHandle, + runtime_preset: &'a DesktopRuntimePreset, + local_session_id: &'a str, + runtime: &'a mut DesktopToolRuntime, + ) -> Result { + let execution_scope = (runtime_preset.load_execution_scope)(app, local_session_id)?; + Ok(Self { + app, + local_session_id, + preset: runtime_preset, + execution_scope, + runtime, + }) + } + + fn find_tool(&self, tool_name: &str) -> Result { + find_desktop_tool(&self.preset.tools, tool_name) + .cloned() + .ok_or_else(|| { + let available_skills = self.skill_names().join(", "); + if available_skills.is_empty() { + format!("Desktop tool is not available for this preset: {tool_name}") + } else { + format!( + "Desktop tool is not available for this preset: {tool_name}. Available desktop skills: {available_skills}" + ) + } + }) + } + + fn skill_names(&self) -> Vec<&str> { + self.preset.skills.iter().map(DesktopSkill::name).collect() + } + + fn tool_context(&mut self) -> DesktopToolContext<'_> { + DesktopToolContext::new( + self.app, + self.local_session_id, + &self.execution_scope, + self.runtime, + self.preset.refresh_scope, + ) + } +} + +pub fn auto_execute_external_tools( + app: &AppHandle, + scope: CoworkChatScope, + runtime_preset: Option<&DesktopRuntimePreset>, + local_session_id: &str, + content: &Value, + runtime: &mut DesktopToolRuntime, +) -> Result>, String> { + let Some(runtime_preset) = runtime_preset else { + return Ok(None); + }; + + let tools = parse_external_tools(content)?; + if tools.is_empty() { + return Ok(None); + } + + let mut dispatcher = DesktopDispatcher::new(app, runtime_preset, local_session_id, runtime)?; + emit_desktop_runtime_status(app, scope, local_session_id, CoworkChatRunStatus::Thinking); + let mut tool_results = Vec::new(); + let mut should_refresh_scope = false; + + for tool in tools { + let tool_name = tool.tool_name.clone().unwrap_or_default(); + let tool_input = tool + .tool_input + .clone() + .unwrap_or(Value::Object(Default::default())); + let tool_call_id = tool.tool_call_id.clone().unwrap_or_default(); + + let desktop_tool = dispatcher.find_tool(&tool_name)?; + emit_desktop_tool_call_event( + app, + scope, + local_session_id, + &tool_call_id, + &desktop_tool, + &tool_name, + &tool_input, + ); + let execution_result = { + let mut tool_context = dispatcher.tool_context(); + desktop_tool.execute(&mut tool_context, &tool_input) + }; + let is_error = execution_result.is_err(); + let llm_content = execution_result.unwrap_or_else(|error| error); + emit_desktop_tool_result_event( + app, + scope, + local_session_id, + &tool_call_id, + &desktop_tool, + &tool_name, + &tool_input, + &llm_content, + is_error, + ); + + if desktop_tool.refreshes_scope() && !is_error { + should_refresh_scope = true; + } + + tool_results.push(json!({ + "tool_call_id": tool_call_id, + "tool_name": tool_name, + "tool_input": tool_input, + "llm_content": llm_content, + "user_display_content": Value::Null, + "is_error": is_error, + "is_interrupted": false, + })); + } + + if should_refresh_scope { + let tool_context = dispatcher.tool_context(); + let _ = tool_context.refresh_scope_snapshot(); + } + + Ok(Some(tool_results)) +} + +fn parse_external_tools(content: &Value) -> Result, String> { + let tools_value = content + .get("tools") + .and_then(Value::as_array) + .ok_or_else(|| "Tool confirmation event did not include tools".to_string())?; + + let mut external_tools = Vec::new(); + for item in tools_value { + let tool: PausedToolDescriptor = serde_json::from_value(item.clone()) + .map_err(|error| format!("Failed to parse external tool request: {error}"))?; + if tool.requires_confirmation.unwrap_or(false) || tool.requires_user_input.unwrap_or(false) + { + return Ok(Vec::new()); + } + if tool.external_execution_required.unwrap_or(false) { + let has_tool_call_id = tool + .tool_call_id + .as_deref() + .map(str::trim) + .map(|value| !value.is_empty()) + .unwrap_or(false); + if !has_tool_call_id { + return Err( + "External desktop tool request did not include a tool_call_id".to_string(), + ); + } + external_tools.push(tool); + } + } + + Ok(external_tools) +} + +fn emit_desktop_runtime_status( + app: &AppHandle, + scope: CoworkChatScope, + local_session_id: &str, + status: CoworkChatRunStatus, +) { + let _ = session_gateway::persist_stream_status(app, scope, local_session_id, status); + let event = + session_gateway::build_status_updated_event(scope, local_session_id.to_string(), status); + let _ = emit_cowork_stream_event(app, &event); +} + +fn emit_desktop_tool_call_event( + app: &AppHandle, + scope: CoworkChatScope, + local_session_id: &str, + tool_call_id: &str, + tool: &DesktopTool, + tool_name: &str, + tool_input: &Value, +) { + emit_desktop_runtime_event( + app, + scope, + local_session_id, + "tool_call", + format!("desktop-tool-call:{tool_call_id}"), + json!({ + "tool_call_id": tool_call_id, + "tool_name": tool_name, + "tool_display_name": tool.display_name(), + "display_name": tool.display_name(), + "tool_input": tool_input, + "agent_name": "desktop", + }), + ); +} + +fn emit_desktop_tool_result_event( + app: &AppHandle, + scope: CoworkChatScope, + local_session_id: &str, + tool_call_id: &str, + tool: &DesktopTool, + tool_name: &str, + tool_input: &Value, + result: &str, + is_error: bool, +) { + emit_desktop_runtime_event( + app, + scope, + local_session_id, + "tool_result", + format!("desktop-tool-result:{tool_call_id}"), + json!({ + "tool_call_id": tool_call_id, + "tool_name": tool_name, + "tool_display_name": tool.display_name(), + "display_name": tool.display_name(), + "tool_input": tool_input, + "result": result, + "is_error": is_error, + "agent_name": "desktop", + }), + ); +} + +fn emit_desktop_runtime_event( + app: &AppHandle, + scope: CoworkChatScope, + local_session_id: &str, + runtime_event_type: &str, + runtime_event_id: String, + content: Value, +) { + let created_at = now_iso(); + let event = CoworkChatEvent::Runtime(CoworkChatRuntimeEvent { + event_type: "runtime.event".to_string(), + scope, + session_id: local_session_id.to_string(), + runtime_event_type: runtime_event_type.to_string(), + runtime_event_id: Some(runtime_event_id), + runtime_created_at: Some(created_at.clone()), + run_status: Some("running".to_string()), + emitted_at: created_at, + content, + }); + if let CoworkChatEvent::Runtime(runtime_event_payload) = &event { + let _ = session_gateway::persist_stream_runtime_event( + app, + runtime_event_payload, + Some(CoworkChatRunStatus::Thinking), + ); + } + let _ = emit_cowork_stream_event(app, &event); +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/mapper.rs b/frontend/src-tauri/src/cowork/agent_remote/mapper.rs new file mode 100644 index 000000000..6075aed07 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/mapper.rs @@ -0,0 +1,404 @@ +use super::prompt::extract_visible_user_content; +use super::socket::normalize_event_name; +use super::types::{RemoteSessionEventRecord, RemoteSessionFile}; +use crate::cowork::chat::{ + CoworkAgentRuntimeKind, CoworkChatFile, CoworkChatMessage, CoworkChatMessageRole, + CoworkChatRunStatus, CoworkChatRuntimeEvent, CoworkChatScope, +}; +use crate::cowork::runtime::CoworkRuntimeSessionSnapshot; +use crate::cowork::string_utils::normalize_optional_string; +use crate::cowork::time_utils::{generate_message_id, now_iso}; +use reqwest::StatusCode; +use serde_json::Value; + +pub fn map_remote_snapshot( + local_scope: CoworkChatScope, + local_session_id: &str, + runtime_session_id: &str, + updated_at: Option, + events: &[RemoteSessionEventRecord], + files: &[RemoteSessionFile], + run_status: Option<&str>, +) -> CoworkRuntimeSessionSnapshot { + let effective_updated_at = updated_at + .or_else(|| { + events + .iter() + .rev() + .find_map(|event| event.created_at.clone()) + }) + .unwrap_or_else(now_iso); + + CoworkRuntimeSessionSnapshot { + runtime_kind: CoworkAgentRuntimeKind::Remote, + runtime_session_id: runtime_session_id.to_string(), + updated_at: effective_updated_at, + messages: map_remote_messages(events), + files: collect_remote_files(files, ""), + run_status: map_remote_run_status(run_status), + runtime_events: map_runtime_events(local_scope, local_session_id, events, run_status), + } +} + +fn map_runtime_events( + local_scope: CoworkChatScope, + local_session_id: &str, + events: &[RemoteSessionEventRecord], + run_status: Option<&str>, +) -> Vec { + events + .iter() + .map(|event| CoworkChatRuntimeEvent { + event_type: "runtime.event".to_string(), + scope: local_scope, + session_id: local_session_id.to_string(), + runtime_event_type: normalize_event_name(&event.event_type), + runtime_event_id: Some(event.id.clone()), + runtime_created_at: event.created_at.clone(), + run_status: run_status.map(ToString::to_string), + emitted_at: event.created_at.clone().unwrap_or_else(now_iso), + content: event.content.clone(), + }) + .collect() +} + +pub fn extract_error_message(content: &Value) -> String { + content + .get("message") + .and_then(Value::as_str) + .or_else(|| content.get("error").and_then(Value::as_str)) + .or_else(|| content.get("detail").and_then(Value::as_str)) + .unwrap_or_default() + .to_string() +} + +pub fn build_backend_error_message(status: StatusCode, body: &str, auth_required: &str) -> String { + match status { + StatusCode::UNAUTHORIZED => auth_required.to_string(), + StatusCode::PAYMENT_REQUIRED => { + "Current user does not have enough credits to run Cowork AI.".to_string() + } + StatusCode::NOT_FOUND => extract_error_detail(body) + .unwrap_or_else(|| format!("Cowork AI backend request failed with status {status}")), + _ => extract_error_detail(body) + .unwrap_or_else(|| format!("Cowork AI backend request failed with status {status}")), + } +} + +pub fn is_terminal_run_status(value: &str) -> bool { + matches!(value, "completed" | "aborted" | "failed" | "error") +} + +pub fn is_waiting_run_status(value: &str) -> bool { + matches!(value, "paused" | "waiting_for_input") +} + +fn map_remote_messages(events: &[RemoteSessionEventRecord]) -> Vec { + let mut mapped_messages = Vec::new(); + let mut thinking_buffer = String::new(); + let mut response_buffer = String::new(); + let mut thinking_started_at: Option = None; + let mut response_started_at: Option = None; + let mut thinking_anchor: Option = None; + let mut response_anchor: Option = None; + + let flush_thinking = |messages: &mut Vec, + buffer: &mut String, + started_at: &mut Option, + anchor: &mut Option| { + if let Some(content) = normalize_optional_string(buffer) { + messages.push(CoworkChatMessage { + id: anchor + .as_deref() + .map(|value| build_transcript_message_id("thinking", value)) + .unwrap_or_else(generate_message_id), + role: CoworkChatMessageRole::Assistant, + content, + created_at: started_at.clone().unwrap_or_else(now_iso), + is_think_message: Some(true), + }); + } + buffer.clear(); + *started_at = None; + *anchor = None; + }; + + let flush_response = |messages: &mut Vec, + buffer: &mut String, + started_at: &mut Option, + anchor: &mut Option| { + if let Some(content) = normalize_optional_string(buffer) { + messages.push(CoworkChatMessage { + id: anchor + .as_deref() + .map(|value| build_transcript_message_id("response", value)) + .unwrap_or_else(generate_message_id), + role: CoworkChatMessageRole::Assistant, + content, + created_at: started_at.clone().unwrap_or_else(now_iso), + is_think_message: None, + }); + } + buffer.clear(); + *started_at = None; + *anchor = None; + }; + + for event in events { + let normalized_type = normalize_event_name(&event.event_type); + match normalized_type.as_str() { + "user_message" | "session_user_message" => { + flush_thinking( + &mut mapped_messages, + &mut thinking_buffer, + &mut thinking_started_at, + &mut thinking_anchor, + ); + flush_response( + &mut mapped_messages, + &mut response_buffer, + &mut response_started_at, + &mut response_anchor, + ); + let text = extract_event_text(&event.content); + let visible_text = extract_visible_user_content(&text); + if !visible_text.is_empty() { + mapped_messages.push(CoworkChatMessage { + id: event.id.clone(), + role: CoworkChatMessageRole::User, + content: visible_text, + created_at: event.created_at.clone().unwrap_or_else(now_iso), + is_think_message: None, + }); + } + } + "agent_thinking_delta" => { + if thinking_started_at.is_none() { + thinking_started_at = event.created_at.clone(); + thinking_anchor = Some(event.id.clone()); + } + thinking_buffer.push_str(&extract_event_text(&event.content)); + } + "agent_thinking" => { + if let Some(text) = normalize_optional_string(&extract_event_text(&event.content)) { + thinking_buffer.clear(); + thinking_buffer.push_str(&text); + thinking_started_at = event.created_at.clone(); + thinking_anchor = Some(event.id.clone()); + } + flush_thinking( + &mut mapped_messages, + &mut thinking_buffer, + &mut thinking_started_at, + &mut thinking_anchor, + ); + } + "agent_response_delta" => { + if response_started_at.is_none() { + response_started_at = event.created_at.clone(); + response_anchor = Some(event.id.clone()); + } + response_buffer.push_str(&extract_event_text(&event.content)); + } + "agent_response" => { + if let Some(text) = normalize_optional_string(&extract_event_text(&event.content)) { + response_buffer.clear(); + response_buffer.push_str(&text); + response_started_at = event.created_at.clone(); + response_anchor = Some(event.id.clone()); + } + flush_response( + &mut mapped_messages, + &mut response_buffer, + &mut response_started_at, + &mut response_anchor, + ); + } + "complete" | "sub_agent_complete" => { + if response_buffer.is_empty() { + response_buffer.push_str(&extract_event_text(&event.content)); + response_started_at = event.created_at.clone(); + response_anchor = Some(event.id.clone()); + } + flush_response( + &mut mapped_messages, + &mut response_buffer, + &mut response_started_at, + &mut response_anchor, + ); + } + "error" => { + flush_thinking( + &mut mapped_messages, + &mut thinking_buffer, + &mut thinking_started_at, + &mut thinking_anchor, + ); + flush_response( + &mut mapped_messages, + &mut response_buffer, + &mut response_started_at, + &mut response_anchor, + ); + let text = extract_error_message(&event.content); + if !text.is_empty() { + mapped_messages.push(CoworkChatMessage { + id: event.id.clone(), + role: CoworkChatMessageRole::Assistant, + content: text, + created_at: event.created_at.clone().unwrap_or_else(now_iso), + is_think_message: None, + }); + } + } + _ => {} + } + } + + flush_thinking( + &mut mapped_messages, + &mut thinking_buffer, + &mut thinking_started_at, + &mut thinking_anchor, + ); + flush_response( + &mut mapped_messages, + &mut response_buffer, + &mut response_started_at, + &mut response_anchor, + ); + + mapped_messages +} + +#[cfg(test)] +mod tests { + use super::map_remote_snapshot; + use crate::cowork::chat::CoworkChatScope; + use serde_json::json; + + use super::super::types::RemoteSessionEventRecord; + + #[test] + fn snapshot_includes_user_messages_from_session_user_message_events() { + let snapshot = map_remote_snapshot( + CoworkChatScope::Homepage, + "local-session", + "remote-session", + Some("2026-04-06T00:00:00Z".to_string()), + &[RemoteSessionEventRecord { + id: "evt-1".to_string(), + event_type: "session.user_message".to_string(), + content: json!({ + "text": "Please organize these files" + }), + created_at: Some("2026-04-06T00:00:00Z".to_string()), + }], + &[], + Some("completed"), + ); + + assert_eq!(snapshot.messages.len(), 1); + assert_eq!(snapshot.messages[0].role, crate::cowork::chat::CoworkChatMessageRole::User); + assert_eq!(snapshot.messages[0].content, "Please organize these files"); + } + + #[test] + fn snapshot_maps_reasoning_events_to_thinking_messages() { + let snapshot = map_remote_snapshot( + CoworkChatScope::Homepage, + "local-session", + "remote-session", + Some("2026-04-06T00:00:00Z".to_string()), + &[RemoteSessionEventRecord { + id: "evt-2".to_string(), + event_type: "agent.reasoning.delta".to_string(), + content: json!({ + "text": "Inspecting files..." + }), + created_at: Some("2026-04-06T00:00:00Z".to_string()), + }], + &[], + Some("running"), + ); + + assert_eq!(snapshot.messages.len(), 1); + assert_eq!( + snapshot.messages[0].role, + crate::cowork::chat::CoworkChatMessageRole::Assistant + ); + assert_eq!(snapshot.messages[0].content, "Inspecting files..."); + assert_eq!(snapshot.messages[0].is_think_message, Some(true)); + } +} + +fn collect_remote_files( + files: &[RemoteSessionFile], + fallback_created_at: &str, +) -> Vec { + let created_at = normalize_optional_string(fallback_created_at).unwrap_or_else(now_iso); + files + .iter() + .map(|file| CoworkChatFile { + id: file.id.clone(), + file_name: normalize_optional_string(&file.name) + .or_else(|| { + file.url + .as_ref() + .and_then(|url| normalize_optional_string(url)) + }) + .unwrap_or_else(|| file.id.clone()), + file_size: file.size, + content_type: normalize_optional_string(&file.content_type).unwrap_or_default(), + created_at: created_at.clone(), + }) + .collect() +} + +fn extract_event_text(content: &Value) -> String { + content + .get("text") + .and_then(Value::as_str) + .or_else(|| content.get("message").and_then(Value::as_str)) + .or_else(|| content.get("content").and_then(Value::as_str)) + .or_else(|| content.get("delta").and_then(Value::as_str)) + .unwrap_or_default() + .to_string() +} + +pub fn map_remote_run_status(run_status: Option<&str>) -> CoworkChatRunStatus { + match run_status { + Some("running") => CoworkChatRunStatus::Thinking, + Some("paused") | Some("waiting_for_input") => CoworkChatRunStatus::WaitingForInput, + Some("aborted") | Some("cancelled") | Some("failed") | Some("error") => { + CoworkChatRunStatus::Stopped + } + _ => CoworkChatRunStatus::Completed, + } +} + +fn build_transcript_message_id(kind: &str, anchor: &str) -> String { + let normalized_anchor = anchor.split_whitespace().collect::>().join("-"); + format!("cowork-transcript:{kind}:{normalized_anchor}") +} + +fn extract_error_detail(body: &str) -> Option { + let parsed = serde_json::from_str::(body).ok()?; + + parsed + .get("detail") + .and_then(Value::as_str) + .map(ToString::to_string) + .or_else(|| { + parsed + .get("message") + .and_then(Value::as_str) + .map(ToString::to_string) + }) + .or_else(|| { + parsed + .get("error") + .and_then(Value::as_str) + .map(ToString::to_string) + }) +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/mod.rs b/frontend/src-tauri/src/cowork/agent_remote/mod.rs new file mode 100644 index 000000000..6743f52cc --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/mod.rs @@ -0,0 +1,18 @@ +pub mod auth; +mod desktop_dispatcher; +mod mapper; +mod payload; +mod prompt; +pub mod service; +mod session_api; +mod socket; +mod types; + +pub(super) use payload::build_remote_command; +pub(super) use prompt::build_remote_content; +pub(super) use session_api::{ + ensure_remote_session_uses_v1, fetch_remote_session_info, fetch_remote_session_snapshot, + resolve_remote_model_selection, should_recreate_remote_session, +}; +pub(super) use socket::run_remote_agent_request; +pub(super) use types::{V1_BACKEND_REQUIRED_MESSAGE, V1_REQUIRED_MESSAGE}; diff --git a/frontend/src-tauri/src/cowork/agent_remote/payload.rs b/frontend/src-tauri/src/cowork/agent_remote/payload.rs new file mode 100644 index 000000000..17b37eb80 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/payload.rs @@ -0,0 +1,95 @@ +use super::types::{ + RemoteAgentCommandContent, RemoteAgentToolArgs, RemoteModelSelection, REMOTE_AGENT_TYPE, + REMOTE_BUILD_MODE, +}; +use crate::cowork::agent_presets::shared::DesktopCapabilities; +use crate::cowork::chat::{ + CoworkAgentOverrides, CoworkChatToolSettings, CoworkGitHubRepositoryContext, +}; +use crate::cowork::string_utils::normalize_optional_string; +use serde_json::Value; + +pub fn build_remote_command( + model_id: String, + model_selection: RemoteModelSelection, + text: String, + resume: bool, + tools: Option<&CoworkChatToolSettings>, + metadata: Option, + desktop_capabilities: Option, + github_repository: Option, + agent_overrides: Option<&CoworkAgentOverrides>, +) -> Result { + let skill_names = + sanitize_name_list(agent_overrides.and_then(|overrides| overrides.skill_names.as_ref())); + let tool_names = + sanitize_name_list(agent_overrides.and_then(|overrides| overrides.tool_names.as_ref())); + + serde_json::to_value(RemoteAgentCommandContent { + model_id, + provider: model_selection.provider, + source: model_selection.source, + agent_type: REMOTE_AGENT_TYPE, + tool_args: map_remote_tool_args(tools), + thinking_tokens: 0, + text, + resume, + files: Vec::new(), + metadata, + desktop_capabilities, + github_repository, + build_mode: REMOTE_BUILD_MODE, + system_prompt: build_system_prompt(agent_overrides), + tool_names, + skill_names, + agent_config: agent_overrides.and_then(|overrides| overrides.runtime_options.clone()), + }) + .map_err(|error| format!("Failed to encode Cowork agent command: {error}")) +} + +fn map_remote_tool_args(tools: Option<&CoworkChatToolSettings>) -> RemoteAgentToolArgs { + let media_generation = tools + .map(|settings| { + settings.generate_image.unwrap_or(false) || settings.generate_video.unwrap_or(false) + }) + .unwrap_or(false); + + RemoteAgentToolArgs { + task_agent: false, + deep_research: false, + pdf: true, + media_generation, + audio_generation: false, + browser: false, + enable_reviewer: false, + design_document: false, + codex_tools: false, + claude_code: false, + } +} + +fn build_system_prompt(agent_overrides: Option<&CoworkAgentOverrides>) -> Option { + agent_overrides + .and_then(|overrides| overrides.system_prompt.clone()) + .as_deref() + .and_then(normalize_optional_string) +} + +fn sanitize_name_list(values: Option<&Vec>) -> Option> { + let mut normalized = Vec::new(); + if let Some(items) = values { + for item in items { + if let Some(value) = normalize_optional_string(item) { + if !normalized.contains(&value) { + normalized.push(value); + } + } + } + } + + if normalized.is_empty() { + None + } else { + Some(normalized) + } +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/prompt.rs b/frontend/src-tauri/src/cowork/agent_remote/prompt.rs new file mode 100644 index 000000000..ef2ed0507 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/prompt.rs @@ -0,0 +1,17 @@ +use crate::cowork::string_utils::normalize_optional_string; + +const USER_REQUEST_MARKER: &str = "\n\n[User request]\n"; + +pub fn build_remote_content(content: &str, prompt_context: Option<&str>) -> String { + match prompt_context.and_then(normalize_optional_string) { + Some(context) => format!("{context}{USER_REQUEST_MARKER}{content}"), + None => content.to_string(), + } +} + +pub fn extract_visible_user_content(content: &str) -> String { + content + .split_once(USER_REQUEST_MARKER) + .map(|(_, user_request)| user_request.trim().to_string()) + .unwrap_or_else(|| content.trim().to_string()) +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/service.rs b/frontend/src-tauri/src/cowork/agent_remote/service.rs new file mode 100644 index 000000000..6c29e77c3 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/service.rs @@ -0,0 +1,274 @@ +use super::auth::{self, RemoteAuthState}; +use super::{ + build_remote_command, build_remote_content, ensure_remote_session_uses_v1, + fetch_remote_session_info, fetch_remote_session_snapshot, resolve_remote_model_selection, + run_remote_agent_request, should_recreate_remote_session, V1_BACKEND_REQUIRED_MESSAGE, + V1_REQUIRED_MESSAGE, +}; +use crate::cowork::agent_presets; +use crate::cowork::chat::{ + CoworkAgentRuntimeKind, CoworkChatMessageRole, CoworkChatSendMessageRequest, + CoworkChatSendMessageResponse, +}; +use crate::cowork::session_gateway; +use crate::cowork::time_utils::now_iso; +use reqwest::Client; +use serde_json::{json, Value}; +use tauri::{AppHandle, State}; + +pub async fn send_remote_chat_message( + app: AppHandle, + state: State<'_, RemoteAuthState>, + request: CoworkChatSendMessageRequest, +) -> Result { + let normalized_content = request.content.trim().to_string(); + if normalized_content.is_empty() { + return Err("Cowork message content is required".to_string()); + } + + let normalized_model_id = request.model_id.trim().to_string(); + if normalized_model_id.is_empty() { + return Err("Cowork model_id is required".to_string()); + } + + let (mut local_session, is_new_session) = + session_gateway::load_or_create_local_session(&app, &request)?; + ensure_remote_runtime_session(local_session.base())?; + + let user_message = session_gateway::build_local_message( + CoworkChatMessageRole::User, + normalized_content.clone(), + false, + None, + ); + + { + let base = local_session.base_mut(); + base.messages.push(user_message); + base.updated_at = now_iso(); + base.run_status = crate::cowork::chat::CoworkChatRunStatus::Thinking; + if base.preview.trim().is_empty() { + base.preview = normalized_content.clone(); + } + } + + local_session = session_gateway::persist_local_session(&app, local_session)?; + session_gateway::emit_local_session_started(&app, &local_session, is_new_session); + + let auth_context = auth::get_auth_context(&state); + let access_token = match auth_context.access_token { + Some(token) => token, + None => { + local_session = persist_runtime_error_with_latest( + &app, + local_session, + auth::AUTH_REQUIRED_MESSAGE.to_string(), + )?; + session_gateway::emit_local_terminal_state(&app, &local_session, true); + return Ok(session_gateway::build_send_response( + &local_session, + is_new_session, + )); + } + }; + let api_base_url = auth_context + .api_base_url + .ok_or_else(|| "Cowork API base URL is not configured".to_string())?; + + let client = Client::new(); + let mut active_runtime_session_id = local_session.base().runtime_session_id.clone(); + if let Some(runtime_session_id) = active_runtime_session_id.clone() { + match fetch_remote_session_info(&client, &api_base_url, &access_token, &runtime_session_id) + .await + { + Ok(session_info) => { + if ensure_remote_session_uses_v1(&session_info).is_err() { + session_gateway::clear_runtime_binding(&mut local_session); + local_session = session_gateway::persist_local_session(&app, local_session)?; + active_runtime_session_id = None; + } + } + Err(error_message) => { + if should_recreate_remote_session(&error_message) { + session_gateway::clear_runtime_binding(&mut local_session); + local_session = session_gateway::persist_local_session(&app, local_session)?; + session_gateway::emit_session_updated(&app, &local_session); + active_runtime_session_id = None; + } else { + local_session = + persist_runtime_error_with_latest(&app, local_session, error_message)?; + session_gateway::emit_local_terminal_state(&app, &local_session, true); + return Ok(session_gateway::build_send_response( + &local_session, + is_new_session, + )); + } + } + } + } + + let should_resume_remote = active_runtime_session_id.is_some(); + let model_selection = match resolve_remote_model_selection( + &client, + &api_base_url, + &access_token, + &normalized_model_id, + ) + .await + { + Ok(selection) => selection, + Err(error_message) => { + local_session = persist_runtime_error_with_latest(&app, local_session, error_message)?; + session_gateway::emit_local_terminal_state(&app, &local_session, true); + return Ok(session_gateway::build_send_response( + &local_session, + is_new_session, + )); + } + }; + + let prompt_context = + session_gateway::resolve_prompt_context(&local_session, request.prompt_context.clone()); + let resolved_agent_overrides = agent_presets::resolve_agent_overrides( + local_session.base().scope, + prompt_context.as_deref(), + request.tools.as_ref(), + request.agent_overrides.clone(), + ); + let outbound_content = build_remote_content(&normalized_content, prompt_context.as_deref()); + let runtime_metadata = build_remote_runtime_metadata(&local_session); + let resolved_desktop_preset = agent_presets::resolve_desktop_preset(local_session.base().scope); + let runtime_desktop_capabilities = resolved_desktop_preset + .as_ref() + .map(|preset| preset.capabilities.clone()); + + let remote_command = match build_remote_command( + normalized_model_id, + model_selection, + outbound_content, + should_resume_remote, + request.tools.as_ref(), + runtime_metadata, + runtime_desktop_capabilities, + request.github_repository.clone(), + Some(&resolved_agent_overrides), + ) { + Ok(command) => command, + Err(error_message) => { + local_session = persist_runtime_error_with_latest(&app, local_session, error_message)?; + session_gateway::emit_local_terminal_state(&app, &local_session, true); + return Ok(session_gateway::build_send_response( + &local_session, + is_new_session, + )); + } + }; + + let run_outcome = match run_remote_agent_request( + &api_base_url, + &access_token, + active_runtime_session_id, + remote_command, + app.clone(), + local_session.base().scope, + resolved_desktop_preset.map(|preset| preset.runtime), + local_session.base().id.clone(), + ) + .await + { + Ok(outcome) => outcome, + Err(error_message) => { + local_session = persist_runtime_error_with_latest(&app, local_session, error_message)?; + session_gateway::emit_local_terminal_state(&app, &local_session, true); + return Ok(session_gateway::build_send_response( + &local_session, + is_new_session, + )); + } + }; + + local_session = reload_latest_local_session(&app, &local_session); + + let runtime_snapshot = match fetch_remote_session_snapshot( + &client, + &api_base_url, + &access_token, + local_session.base().scope, + &local_session.base().id, + &run_outcome.runtime_session_id, + ) + .await + { + Ok(snapshot) => snapshot, + Err(error_message) => { + let display_error = if !should_resume_remote && error_message == V1_REQUIRED_MESSAGE { + V1_BACKEND_REQUIRED_MESSAGE.to_string() + } else { + error_message + }; + local_session = persist_runtime_error_with_latest(&app, local_session, display_error)?; + session_gateway::emit_local_terminal_state(&app, &local_session, true); + return Ok(session_gateway::build_send_response( + &local_session, + is_new_session, + )); + } + }; + + local_session = reload_latest_local_session(&app, &local_session); + session_gateway::apply_runtime_session_snapshot(&mut local_session, runtime_snapshot); + session_gateway::sync_organize_result_tree(&mut local_session)?; + local_session = session_gateway::persist_local_session(&app, local_session)?; + session_gateway::emit_local_terminal_state(&app, &local_session, false); + + Ok(session_gateway::build_send_response( + &local_session, + is_new_session, + )) +} + +fn ensure_remote_runtime_session( + session: &crate::cowork::chat::CoworkChatSessionDetail, +) -> Result<(), String> { + match session.runtime_kind { + Some(CoworkAgentRuntimeKind::Remote) | None => Ok(()), + Some(CoworkAgentRuntimeKind::Local) => Err( + "This Cowork session is already bound to the local runtime and cannot be sent through the remote runtime." + .to_string(), + ), + } +} + +fn reload_latest_local_session( + app: &AppHandle, + session: &session_gateway::LocalCoworkSession, +) -> session_gateway::LocalCoworkSession { + session_gateway::load_local_session(app, session.base().scope, &session.base().id) + .unwrap_or_else(|_| session.clone()) +} + +fn persist_runtime_error_with_latest( + app: &AppHandle, + session: session_gateway::LocalCoworkSession, + error_message: String, +) -> Result { + let latest_session = reload_latest_local_session(app, &session); + session_gateway::persist_runtime_error(app, latest_session, error_message) +} + +fn build_remote_runtime_metadata(session: &session_gateway::LocalCoworkSession) -> Option { + match session { + session_gateway::LocalCoworkSession::Homepage(_) => None, + session_gateway::LocalCoworkSession::Organize(detail) => Some(json!({ + "cowork": { + "scope": "organize-file-folder", + "execution_context": "desktop", + "tool_runtime": "desktop_builtin", + "tool_binding_mode": "desktop_only", + "local_session_id": detail.base.id.clone(), + "source_root": detail.organize_tree_pair.source_root.clone(), + "result_root": detail.organize_tree_pair.result_root.clone(), + } + })), + } +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/session_api.rs b/frontend/src-tauri/src/cowork/agent_remote/session_api.rs new file mode 100644 index 000000000..40394f71d --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/session_api.rs @@ -0,0 +1,239 @@ +use super::mapper::{build_backend_error_message, map_remote_snapshot}; +use super::types::{ + RemoteAvailableModelsResponse, RemoteModelSelection, RemoteSessionEventsResponse, + RemoteSessionFile, RemoteSessionInfo, RemoteSessionState, V1_REQUIRED_MESSAGE, +}; +use crate::cowork::agent_remote::auth::AUTH_REQUIRED_MESSAGE; +use crate::cowork::chat::CoworkChatScope; +use crate::cowork::runtime::CoworkRuntimeSessionSnapshot; +use crate::cowork::string_utils::normalize_optional_string; +use reqwest::{header::AUTHORIZATION, Client}; + +pub async fn fetch_remote_session_info( + client: &Client, + api_base_url: &str, + access_token: &str, + remote_session_id: &str, +) -> Result { + let response = client + .get(format!("{api_base_url}/v1/sessions/{remote_session_id}")) + .header(AUTHORIZATION, format!("Bearer {access_token}")) + .send() + .await + .map_err(|error| format!("Failed to load Cowork agent session info: {error}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = response + .text() + .await + .unwrap_or_else(|_| "Unknown backend error".to_string()); + return Err(build_backend_error_message( + status, + &body, + AUTH_REQUIRED_MESSAGE, + )); + } + + response + .json::() + .await + .map_err(|error| format!("Failed to decode Cowork agent session info: {error}")) +} + +pub fn ensure_remote_session_uses_v1(session_info: &RemoteSessionInfo) -> Result<(), String> { + match session_info.api_version.as_deref() { + Some("v1") | None => Ok(()), + Some(_) => Err(V1_REQUIRED_MESSAGE.to_string()), + } +} + +pub fn should_recreate_remote_session(error_message: &str) -> bool { + error_message == V1_REQUIRED_MESSAGE + || error_message.contains("status 404") + || error_message.contains("not found or access denied") +} + +pub async fn resolve_remote_model_selection( + client: &Client, + api_base_url: &str, + access_token: &str, + model_id: &str, +) -> Result { + let response = client + .get(format!("{api_base_url}/v1/user-settings/models")) + .header(AUTHORIZATION, format!("Bearer {access_token}")) + .send() + .await + .map_err(|error| format!("Failed to load available AI models for Cowork: {error}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = response + .text() + .await + .unwrap_or_else(|_| "Unknown backend error".to_string()); + return Err(build_backend_error_message( + status, + &body, + AUTH_REQUIRED_MESSAGE, + )); + } + + let payload = response + .json::() + .await + .map_err(|error| format!("Failed to decode available AI models for Cowork: {error}"))?; + + let model = payload + .models + .into_iter() + .find(|model| model.id == model_id) + .ok_or_else(|| { + format!("The selected AI model `{model_id}` is not available for the current user.") + })?; + + let source = model + .source + .and_then(|value| normalize_optional_string(&value)) + .ok_or_else(|| { + format!("The selected AI model `{model_id}` does not provide a valid source.") + })?; + + if source != "user" && source != "system" { + return Err(format!( + "The selected AI model `{model_id}` returned an unsupported source `{source}`." + )); + } + + let provider = model + .provider + .and_then(|value| normalize_optional_string(&value)) + .ok_or_else(|| { + format!("The selected AI model `{model_id}` does not provide a valid provider.") + })?; + + let provider_lower = provider.to_lowercase(); + if !matches!( + provider_lower.as_str(), + "openai" | "anthropic" | "gemini" | "google" | "cerebras" | "custom" + ) { + return Err(format!( + "The selected AI model `{model_id}` returned an unsupported provider `{provider}`." + )); + } + + Ok(RemoteModelSelection { + source, + provider: provider_lower, + }) +} + +pub async fn fetch_remote_session_snapshot( + client: &Client, + api_base_url: &str, + access_token: &str, + local_scope: CoworkChatScope, + local_session_id: &str, + runtime_session_id: &str, +) -> Result { + let remote_session = + fetch_remote_session_state(client, api_base_url, access_token, runtime_session_id).await?; + + Ok(map_remote_snapshot( + local_scope, + local_session_id, + runtime_session_id, + remote_session.session_info.updated_at.clone(), + &remote_session.event_response.events, + &remote_session.files, + remote_session.event_response.run_status.as_deref(), + )) +} + +async fn fetch_remote_session_state( + client: &Client, + api_base_url: &str, + access_token: &str, + remote_session_id: &str, +) -> Result { + let session_info = + fetch_remote_session_info(client, api_base_url, access_token, remote_session_id).await?; + ensure_remote_session_uses_v1(&session_info)?; + let event_response = + fetch_remote_session_events(client, api_base_url, access_token, remote_session_id).await?; + let files = + fetch_remote_session_files(client, api_base_url, access_token, remote_session_id).await?; + + Ok(RemoteSessionState { + session_info, + event_response, + files, + }) +} + +async fn fetch_remote_session_events( + client: &Client, + api_base_url: &str, + access_token: &str, + remote_session_id: &str, +) -> Result { + let response = client + .get(format!( + "{api_base_url}/v1/sessions/{remote_session_id}/events" + )) + .header(AUTHORIZATION, format!("Bearer {access_token}")) + .send() + .await + .map_err(|error| format!("Failed to load Cowork agent session events: {error}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = response + .text() + .await + .unwrap_or_else(|_| "Unknown backend error".to_string()); + return Err(build_backend_error_message( + status, + &body, + AUTH_REQUIRED_MESSAGE, + )); + } + + response + .json::() + .await + .map_err(|error| format!("Failed to decode Cowork agent session events: {error}")) +} + +async fn fetch_remote_session_files( + client: &Client, + api_base_url: &str, + access_token: &str, + remote_session_id: &str, +) -> Result, String> { + let response = client + .get(format!("{api_base_url}/v1/sessions/{remote_session_id}/files")) + .header(AUTHORIZATION, format!("Bearer {access_token}")) + .send() + .await + .map_err(|error| format!("Failed to load Cowork agent session files: {error}"))?; + + if !response.status().is_success() { + let status = response.status(); + let body = response + .text() + .await + .unwrap_or_else(|_| "Unknown backend error".to_string()); + return Err(build_backend_error_message( + status, + &body, + AUTH_REQUIRED_MESSAGE, + )); + } + + response + .json::>() + .await + .map_err(|error| format!("Failed to decode Cowork agent session files: {error}")) +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/socket.rs b/frontend/src-tauri/src/cowork/agent_remote/socket.rs new file mode 100644 index 000000000..aba5fd871 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/socket.rs @@ -0,0 +1,599 @@ +use super::desktop_dispatcher::auto_execute_external_tools; +use super::mapper::{ + extract_error_message, is_terminal_run_status, is_waiting_run_status, map_remote_run_status, +}; +use super::types::{ + RemoteAgentRunOutcome, RemoteSocketChatEvent, RemoteSocketSignal, REMOTE_SOCKET_MESSAGE_TYPE, + SOCKET_EVENT_TIMEOUT_SECS, +}; +use crate::cowork::agent_presets::DesktopRuntimePreset; +use crate::cowork::chat::{ + emit_cowork_stream_event, CoworkChatEvent, CoworkChatRunStatus, CoworkChatRuntimeEvent, + CoworkChatScope, +}; +use crate::cowork::desktop_tools::DesktopToolRuntime; +use crate::cowork::session_gateway; +use crate::cowork::string_utils::normalize_optional_string; +use crate::cowork::time_utils::now_iso; +use rust_socketio::{ClientBuilder as SocketClientBuilder, Payload, TransportType}; +use serde_json::{json, Value}; +use std::sync::mpsc; +use std::time::{Duration, Instant}; +use tauri::async_runtime::spawn_blocking; +use tauri::AppHandle; + +pub async fn run_remote_agent_request( + api_base_url: &str, + access_token: &str, + remote_session_id: Option, + command_payload: Value, + app: AppHandle, + scope: CoworkChatScope, + desktop_runtime_preset: Option, + local_session_id: String, +) -> Result { + let api_base_url = api_base_url.to_string(); + let access_token = access_token.to_string(); + let remote_session_id_for_auth = remote_session_id.clone(); + let stream_context = RemoteStreamContext { + app, + scope, + local_session_id, + }; + + spawn_blocking(move || { + let (tx, rx) = mpsc::channel::(); + let tx_chat = tx.clone(); + let tx_error = tx.clone(); + let mut last_status = Some(CoworkChatRunStatus::Thinking); + let mut desktop_runtime = DesktopToolRuntime::default(); + + let auth_payload = if let Some(session_id) = remote_session_id_for_auth.clone() { + json!({ + "token": access_token, + "session_uuid": session_id, + }) + } else { + json!({ + "token": access_token, + }) + }; + + let socket = SocketClientBuilder::new(api_base_url.as_str()) + .transport_type(TransportType::Websocket) + .auth(auth_payload) + .on("chat_event", move |payload, _socket| { + if let Some(event) = parse_socket_payload(payload).and_then(parse_socket_chat_event) + { + let _ = tx_chat.send(RemoteSocketSignal::ChatEvent(event)); + } + }) + .on("error", move |payload, _socket| { + let message = parse_socket_payload(payload) + .and_then(|value| { + let extracted = extract_error_message(&value); + if extracted.is_empty() { + None + } else { + Some(extracted) + } + }) + .unwrap_or_else(|| "Cowork agent socket returned an unknown error".to_string()); + let _ = tx_error.send(RemoteSocketSignal::ClientError(message)); + }) + .connect() + .map_err(|error| format!("Failed to connect Cowork agent socket: {error}"))?; + + let join_payload = if let Some(session_id) = remote_session_id.clone() { + json!({ "session_uuid": session_id }) + } else { + json!({}) + }; + + socket + .emit("join_session", join_payload) + .map_err(|error| format!("Failed to join Cowork agent session: {error}"))?; + + let active_session_id = wait_for_socket_session( + &rx, + remote_session_id.clone(), + &stream_context, + &mut last_status, + )?; + + // Inject "command" into content so the backend discriminated union can resolve it. + let mut enriched_content = command_payload.clone(); + if let Some(obj) = enriched_content.as_object_mut() { + obj.insert( + "command".to_string(), + Value::String(REMOTE_SOCKET_MESSAGE_TYPE.to_string()), + ); + } + + socket + .emit( + "chat_message", + json!({ + "session_uuid": active_session_id, + "content": enriched_content, + }), + ) + .map_err(|error| format!("Failed to send Cowork agent message: {error}"))?; + + let continue_session_id = active_session_id.clone(); + let mut continue_run = + |run_id: &str, external_tool_results: Vec| -> Result<(), String> { + socket + .emit( + "chat_message", + json!({ + "session_uuid": continue_session_id, + "content": { + "command": "cowork_continue_run", + "run_id": run_id, + "confirmed": true, + "external_tool_results": external_tool_results, + } + }), + ) + .map_err(|error| format!("Failed to continue Cowork agent run: {error}")) + }; + + wait_for_remote_run_completion( + &rx, + &stream_context, + &mut last_status, + desktop_runtime_preset.as_ref(), + &mut desktop_runtime, + &mut continue_run, + )?; + let _ = socket.disconnect(); + + Ok(RemoteAgentRunOutcome { + runtime_session_id: active_session_id, + }) + }) + .await + .map_err(|error| format!("Cowork agent worker failed: {error}"))? +} + +fn wait_for_socket_session( + receiver: &mpsc::Receiver, + fallback_session_id: Option, + stream_context: &RemoteStreamContext, + last_status: &mut Option, +) -> Result { + if let Some(session_id) = fallback_session_id { + return Ok(session_id); + } + + let started_at = Instant::now(); + let timeout = Duration::from_secs(SOCKET_EVENT_TIMEOUT_SECS); + + while started_at.elapsed() < timeout { + match receiver.recv_timeout(Duration::from_millis(250)) { + Ok(RemoteSocketSignal::ChatEvent(event)) => { + emit_remote_socket_event(stream_context, &event, last_status); + if event.event_type == "system" { + if let Some(session_id) = event + .content + .get("session_id") + .and_then(Value::as_str) + .and_then(normalize_optional_string) + { + return Ok(session_id); + } + } else if event.event_type == "error" { + let message = extract_error_message(&event.content); + if !message.is_empty() { + return Err(message); + } + } + } + Ok(RemoteSocketSignal::ClientError(message)) => return Err(message), + Err(mpsc::RecvTimeoutError::Timeout) => {} + Err(mpsc::RecvTimeoutError::Disconnected) => { + return Err( + "Cowork agent socket disconnected before session initialization".to_string(), + ); + } + } + } + + Err("Timed out while waiting for Cowork agent session initialization".to_string()) +} + +fn wait_for_remote_run_completion( + receiver: &mpsc::Receiver, + stream_context: &RemoteStreamContext, + last_status: &mut Option, + desktop_runtime_preset: Option<&DesktopRuntimePreset>, + desktop_runtime: &mut DesktopToolRuntime, + continue_run: &mut dyn FnMut(&str, Vec) -> Result<(), String>, +) -> Result<(), String> { + let started_at = Instant::now(); + let timeout = Duration::from_secs(SOCKET_EVENT_TIMEOUT_SECS); + let mut pending_continue_run_id: Option = None; + + while started_at.elapsed() < timeout { + match receiver.recv_timeout(Duration::from_millis(250)) { + Ok(RemoteSocketSignal::ChatEvent(event)) => { + emit_remote_socket_event(stream_context, &event, last_status); + if event.event_type == "error" { + let message = extract_error_message(&event.content); + if !message.is_empty() { + return Err(message); + } + } + + if event.event_type == "tool_confirmation" { + if let Some(run_id) = event.run_id.as_deref() { + if let Some(external_tool_results) = auto_execute_external_tools( + &stream_context.app, + stream_context.scope, + desktop_runtime_preset, + &stream_context.local_session_id, + &event.content, + desktop_runtime, + )? { + continue_run(run_id, external_tool_results)?; + pending_continue_run_id = Some(run_id.to_string()); + continue; + } + } + } + + if should_ignore_terminal_after_continue( + &event, + pending_continue_run_id.as_deref(), + ) { + continue; + } + if should_clear_pending_continue(&event, pending_continue_run_id.as_deref()) { + pending_continue_run_id = None; + } + + if let Some(run_status) = event.run_status.as_deref() { + if is_terminal_run_status(run_status) || is_waiting_run_status(run_status) { + return Ok(()); + } + } + + match event.event_type.as_str() { + "stream_complete" + | "complete" + | "agent_response_interrupted" + | "tool_confirmation" => { + return Ok(()); + } + _ => {} + } + } + Ok(RemoteSocketSignal::ClientError(message)) => return Err(message), + Err(mpsc::RecvTimeoutError::Timeout) => {} + Err(mpsc::RecvTimeoutError::Disconnected) => { + return Err("Cowork agent socket disconnected before the run completed".to_string()); + } + } + } + + Err("Timed out while waiting for Cowork agent run completion".to_string()) +} + +fn should_ignore_terminal_after_continue( + event: &RemoteSocketChatEvent, + pending_continue_run_id: Option<&str>, +) -> bool { + let Some(pending_run_id) = pending_continue_run_id else { + return false; + }; + if event.run_id.as_deref() != Some(pending_run_id) { + return false; + } + + matches!(event.event_type.as_str(), "stream_complete" | "complete") + || matches!( + event.run_status.as_deref(), + Some(run_status) if is_terminal_run_status(run_status) + ) +} + +fn should_clear_pending_continue( + event: &RemoteSocketChatEvent, + pending_continue_run_id: Option<&str>, +) -> bool { + let Some(pending_run_id) = pending_continue_run_id else { + return false; + }; + if event.run_id.as_deref() != Some(pending_run_id) { + return false; + } + + matches!( + event.event_type.as_str(), + "agent_continue" + | "processing" + | "agent_thinking_start" + | "tool_call" + | "tool_result" + | "agent_thinking_delta" + | "agent_thinking" + | "agent_response_delta" + | "agent_response" + | "tool_confirmation" + ) || matches!(event.run_status.as_deref(), Some("running")) +} + +fn parse_socket_payload(payload: Payload) -> Option { + #[allow(deprecated)] + match payload { + Payload::Text(values) => values.into_iter().next(), + Payload::String(text) => serde_json::from_str::(&text).ok(), + Payload::Binary(_) => None, + } +} + +/// Map backend dotted event names (e.g. "agent.stream.complete") to the short +/// names the Cowork runtime uses for status derivation and terminal detection. +pub fn normalize_event_name(raw: &str) -> String { + match raw { + // System / connection events + "connection.established" => "system".to_string(), + "system.error" => "error".to_string(), + "system.pong" => "pong".to_string(), + + // Agent lifecycle + "agent.processing" => "processing".to_string(), + "agent.reasoning.start" => "agent_thinking_start".to_string(), + "agent.reasoning.delta" => "agent_thinking_delta".to_string(), + "agent.reasoning" => "agent_thinking".to_string(), + "agent.thinking.delta" => "agent_thinking_delta".to_string(), + "agent.thinking" => "agent_thinking".to_string(), + "agent.response.delta" => "agent_response_delta".to_string(), + "agent.response" => "agent_response".to_string(), + "agent.response.interrupted" => "agent_response_interrupted".to_string(), + "agent.continue" => "agent_continue".to_string(), + "agent.stream.complete" => "stream_complete".to_string(), + "agent.complete" => "complete".to_string(), + "agent.error" => "error".to_string(), + + // Tool events + "tool.call" => "tool_call".to_string(), + "tool.result" => "tool_result".to_string(), + "tool.confirmation" => "tool_confirmation".to_string(), + "tool.user_input" => "waiting_for_user_input".to_string(), + + // Metrics / user message + "metrics.updated" => "metrics_update".to_string(), + "session.user_message" => "user_message".to_string(), + "user.message" => "user_message".to_string(), + + // Fallback: strip "agent." / "system." prefix and replace dots with underscores + other => { + let stripped = other + .strip_prefix("agent.") + .or_else(|| other.strip_prefix("system.")) + .or_else(|| other.strip_prefix("tool.")) + .unwrap_or(other); + stripped.replace('.', "_") + } + } +} + +#[cfg(test)] +mod tests { + use super::{ + normalize_event_name, parse_socket_chat_event, should_clear_pending_continue, + should_ignore_terminal_after_continue, + }; + use super::super::types::RemoteSocketChatEvent; + use serde_json::json; + + #[test] + fn normalizes_session_user_message_to_user_message() { + assert_eq!(normalize_event_name("session.user_message"), "user_message"); + } + + #[test] + fn normalizes_legacy_user_message_to_user_message() { + assert_eq!(normalize_event_name("user.message"), "user_message"); + } + + #[test] + fn normalizes_reasoning_events_to_thinking_events() { + assert_eq!(normalize_event_name("agent.reasoning.start"), "agent_thinking_start"); + assert_eq!(normalize_event_name("agent.reasoning.delta"), "agent_thinking_delta"); + assert_eq!(normalize_event_name("agent.reasoning"), "agent_thinking"); + } + + #[test] + fn ignores_stale_stream_complete_after_auto_continue() { + let event = RemoteSocketChatEvent { + event_id: Some("evt-1".to_string()), + event_type: "stream_complete".to_string(), + content: json!({}), + created_at: Some("2026-04-06T00:00:00Z".to_string()), + run_status: None, + run_id: Some("run-1".to_string()), + }; + + assert!(should_ignore_terminal_after_continue(&event, Some("run-1"))); + assert!(!should_ignore_terminal_after_continue(&event, Some("run-2"))); + } + + #[test] + fn clears_pending_continue_once_resumed_events_arrive() { + let event = RemoteSocketChatEvent { + event_id: Some("evt-2".to_string()), + event_type: "agent_continue".to_string(), + content: json!({}), + created_at: Some("2026-04-06T00:00:01Z".to_string()), + run_status: Some("running".to_string()), + run_id: Some("run-1".to_string()), + }; + + assert!(should_clear_pending_continue(&event, Some("run-1"))); + assert!(!should_clear_pending_continue(&event, Some("run-2"))); + } + + #[test] + fn parses_run_id_from_content_when_top_level_run_id_is_missing() { + let event = parse_socket_chat_event(json!({ + "id": "evt-3", + "name": "agent.tool.confirmation", + "content": { + "run_id": "run-1", + "tools": [] + } + })) + .expect("socket event should parse"); + + assert_eq!(event.run_id.as_deref(), Some("run-1")); + assert_eq!(event.event_type, "tool_confirmation"); + } +} + +fn parse_socket_chat_event(value: Value) -> Option { + let record = value.as_object()?; + let event_id = record + .get("id") + .and_then(Value::as_str) + .and_then(normalize_optional_string); + + // Backend sends "name" (e.g. "agent.stream.complete"), fall back to "type" + let raw_event_type = record + .get("name") + .and_then(Value::as_str) + .or_else(|| record.get("type").and_then(Value::as_str))?; + let event_type = normalize_event_name(raw_event_type); + + let content = record.get("content").cloned().unwrap_or(Value::Null); + + // Backend sends "timestamp" as a float (epoch seconds), convert to ISO string + let created_at = record + .get("created_at") + .and_then(Value::as_str) + .and_then(normalize_optional_string) + .or_else(|| { + record + .get("timestamp") + .and_then(Value::as_str) + .and_then(normalize_optional_string) + }) + .or_else(|| { + // timestamp may be a numeric epoch — just stringify it + record.get("timestamp").and_then(|v| { + if v.is_f64() || v.is_i64() || v.is_u64() { + Some(v.to_string()) + } else { + None + } + }) + }); + + // run_status may be top-level or inside content + let run_status = record + .get("run_status") + .and_then(Value::as_str) + .and_then(normalize_optional_string) + .or_else(|| { + content + .get("run_status") + .and_then(Value::as_str) + .and_then(normalize_optional_string) + }); + + let run_id = record + .get("run_id") + .and_then(Value::as_str) + .and_then(normalize_optional_string) + .or_else(|| { + content + .get("run_id") + .and_then(Value::as_str) + .and_then(normalize_optional_string) + }); + + Some(RemoteSocketChatEvent { + event_id, + event_type, + content, + created_at, + run_status, + run_id, + }) +} + +#[derive(Clone)] +struct RemoteStreamContext { + app: AppHandle, + scope: CoworkChatScope, + local_session_id: String, +} + +fn emit_remote_socket_event( + stream_context: &RemoteStreamContext, + event: &RemoteSocketChatEvent, + last_status: &mut Option, +) { + let derived_status = derive_status_from_remote_event(event); + let runtime_event = CoworkChatEvent::Runtime(CoworkChatRuntimeEvent { + event_type: "runtime.event".to_string(), + scope: stream_context.scope, + session_id: stream_context.local_session_id.clone(), + runtime_event_type: event.event_type.clone(), + runtime_event_id: event.event_id.clone(), + runtime_created_at: event.created_at.clone(), + run_status: event.run_status.clone(), + emitted_at: now_iso(), + content: event.content.clone(), + }); + + if let CoworkChatEvent::Runtime(runtime_event_payload) = &runtime_event { + let _ = session_gateway::persist_stream_runtime_event( + &stream_context.app, + runtime_event_payload, + derived_status, + ); + } + let _ = emit_cowork_stream_event(&stream_context.app, &runtime_event); + + if let Some(status) = derived_status { + if last_status.as_ref() == Some(&status) { + return; + } + + *last_status = Some(status); + let status_event = session_gateway::build_status_updated_event( + stream_context.scope, + stream_context.local_session_id.clone(), + status, + ); + let _ = emit_cowork_stream_event(&stream_context.app, &status_event); + } +} + +fn derive_status_from_remote_event(event: &RemoteSocketChatEvent) -> Option { + if let Some(run_status) = event.run_status.as_deref() { + return Some(map_remote_run_status(Some(run_status))); + } + + match event.event_type.as_str() { + "processing" + | "agent_thinking_start" + | "agent_thinking_delta" + | "agent_thinking" + | "agent_response_delta" + | "agent_response" + | "agent_continue" + | "tool_call" + | "tool_result" + | "system" => Some(CoworkChatRunStatus::Thinking), + "tool_confirmation" | "waiting_for_user_input" => { + Some(CoworkChatRunStatus::WaitingForInput) + } + "complete" | "stream_complete" => Some(CoworkChatRunStatus::Completed), + "error" | "agent_response_interrupted" => Some(CoworkChatRunStatus::Stopped), + _ => None, + } +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/types.rs b/frontend/src-tauri/src/cowork/agent_remote/types.rs new file mode 100644 index 000000000..2aaf3eb80 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/types.rs @@ -0,0 +1,142 @@ +use crate::cowork::agent_presets::shared::DesktopCapabilities; +use crate::cowork::chat::CoworkGitHubRepositoryContext; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +pub const V1_REQUIRED_MESSAGE: &str = + "Cowork backend session is not using api_version=v1, so IIAgent is not active for this session."; +pub const V1_BACKEND_REQUIRED_MESSAGE: &str = + "Cowork backend is still creating sessions without api_version=v1. Enable the backend agent_v1_version_toggle, then start a new Cowork chat session."; +pub const SOCKET_EVENT_TIMEOUT_SECS: u64 = 180; +pub const REMOTE_AGENT_TYPE: &str = "general"; +pub const REMOTE_BUILD_MODE: &str = "build"; +pub const REMOTE_SOCKET_MESSAGE_TYPE: &str = "cowork_query"; + +#[derive(Debug, Serialize)] +pub(super) struct RemoteAgentCommandContent { + pub model_id: String, + pub provider: String, + pub source: String, + pub agent_type: &'static str, + pub tool_args: RemoteAgentToolArgs, + pub thinking_tokens: u32, + pub text: String, + pub resume: bool, + pub files: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub desktop_capabilities: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_repository: Option, + pub build_mode: &'static str, + #[serde(skip_serializing_if = "Option::is_none")] + pub system_prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_names: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub skill_names: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_config: Option, +} + +#[derive(Debug, Serialize)] +pub(super) struct RemoteAgentToolArgs { + pub task_agent: bool, + pub deep_research: bool, + pub pdf: bool, + pub media_generation: bool, + pub audio_generation: bool, + pub browser: bool, + pub enable_reviewer: bool, + pub design_document: bool, + pub codex_tools: bool, + pub claude_code: bool, +} + +#[derive(Debug)] +pub struct RemoteAgentRunOutcome { + pub runtime_session_id: String, +} + +#[derive(Debug, Deserialize)] +pub struct RemoteSessionInfo { + #[serde(default)] + pub api_version: Option, + #[serde(default)] + pub(super) updated_at: Option, +} + +#[derive(Debug, Deserialize)] +pub(super) struct RemoteAvailableModelsResponse { + #[serde(default)] + pub models: Vec, +} + +#[derive(Debug, Deserialize)] +pub(super) struct RemoteAvailableModel { + pub id: String, + #[serde(default, alias = "api_type")] + pub provider: Option, + #[serde(default)] + pub source: Option, +} + +pub struct RemoteModelSelection { + pub source: String, + pub provider: String, +} + +#[derive(Debug, Deserialize)] +pub(super) struct RemoteSessionEventsResponse { + #[serde(default)] + pub events: Vec, + #[serde(default)] + pub run_status: Option, +} + +#[derive(Debug, Deserialize)] +pub(super) struct RemoteSessionEventRecord { + pub id: String, + /// Backend sends "event_type" (dotted name like "agent.response"); + /// also accept legacy "type" field via alias. + #[serde(default, alias = "type")] + pub event_type: String, + #[serde(default)] + pub content: Value, + #[serde(default, alias = "timestamp")] + pub created_at: Option, +} + +#[derive(Debug, Deserialize)] +pub(super) struct RemoteSessionFile { + pub id: String, + pub name: String, + pub size: u64, + #[serde(default)] + pub content_type: String, + #[serde(default)] + pub url: Option, +} + +#[derive(Debug, Clone)] +pub(super) struct RemoteSocketChatEvent { + pub event_id: Option, + pub event_type: String, + pub content: Value, + pub created_at: Option, + pub run_status: Option, + pub run_id: Option, +} + +#[derive(Debug)] +pub(super) enum RemoteSocketSignal { + ChatEvent(RemoteSocketChatEvent), + ClientError(String), +} + +pub(super) struct RemoteSessionState { + pub session_info: RemoteSessionInfo, + pub event_response: RemoteSessionEventsResponse, + pub files: Vec, +} diff --git a/frontend/src-tauri/src/cowork/chat.rs b/frontend/src-tauri/src/cowork/chat.rs new file mode 100644 index 000000000..5d0256af1 --- /dev/null +++ b/frontend/src-tauri/src/cowork/chat.rs @@ -0,0 +1,226 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tauri::{AppHandle, Emitter}; + +pub const COWORK_STREAM_EVENT_NAME: &str = "cowork://stream"; + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +pub enum CoworkChatScope { + #[serde(rename = "homepage")] + Homepage, + #[serde(rename = "organize-file-folder", alias = "organize_file_folder")] + OrganizeFileFolder, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum CoworkChatMessageRole { + User, + Assistant, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum CoworkChatRunStatus { + Idle, + Thinking, + WaitingForInput, + Completed, + Stopped, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum CoworkAgentRuntimeKind { + Remote, + Local, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatMessage { + pub id: String, + pub role: CoworkChatMessageRole, + pub content: String, + pub created_at: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_think_message: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatFile { + pub id: String, + pub file_name: String, + pub file_size: u64, + pub content_type: String, + pub created_at: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatSessionSummary { + pub id: String, + pub scope: CoworkChatScope, + pub title: String, + pub preview: String, + pub updated_at: String, + pub message_count: usize, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct CoworkChatSessionDetail { + pub id: String, + pub scope: CoworkChatScope, + pub title: String, + pub preview: String, + pub updated_at: String, + pub message_count: usize, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub runtime_kind: Option, + #[serde(skip_serializing_if = "Option::is_none", alias = "remote_session_id")] + pub runtime_session_id: Option, + pub messages: Vec, + #[serde(default, alias = "remote_events")] + pub runtime_events: Vec, + pub files: Vec, + pub run_status: CoworkChatRunStatus, +} + +impl CoworkChatSessionDetail { + pub fn normalize_runtime_binding(&mut self) { + if self.runtime_kind.is_none() + && (self.runtime_session_id.is_some() || !self.runtime_events.is_empty()) + { + self.runtime_kind = Some(CoworkAgentRuntimeKind::Remote); + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkGitHubRepositoryContext { + pub owner: String, + pub name: String, + pub full_name: String, + pub default_branch: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatToolSettings { + pub web_search: bool, + pub web_visit: bool, + pub image_search: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub code_interpreter: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub generate_image: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub generate_video: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct CoworkAgentOverrides { + #[serde(skip_serializing_if = "Option::is_none")] + pub system_prompt: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_names: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub skill_names: Option>, + #[serde(skip_serializing_if = "Option::is_none", alias = "agent_config")] + pub runtime_options: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct CoworkChatSendMessageRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub runtime_kind: Option, + pub scope: CoworkChatScope, + pub content: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt_context: Option, + pub model_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub tools: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_repository: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_overrides: Option, +} + +impl CoworkChatSendMessageRequest { + pub fn requested_runtime_kind(&self) -> CoworkAgentRuntimeKind { + self.runtime_kind.unwrap_or(CoworkAgentRuntimeKind::Remote) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatSessionEvent { + #[serde(rename = "type")] + pub event_type: String, + pub session: CoworkChatSessionSummary, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatStatusEvent { + #[serde(rename = "type")] + pub event_type: String, + pub scope: CoworkChatScope, + pub session_id: String, + pub status: CoworkChatRunStatus, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatMessageEvent { + #[serde(rename = "type")] + pub event_type: String, + pub scope: CoworkChatScope, + pub session_id: String, + pub message: CoworkChatMessage, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkChatFilesEvent { + #[serde(rename = "type")] + pub event_type: String, + pub scope: CoworkChatScope, + pub session_id: String, + pub files: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct CoworkChatRuntimeEvent { + #[serde(rename = "type")] + pub event_type: String, + pub scope: CoworkChatScope, + pub session_id: String, + #[serde(alias = "remote_event_type")] + pub runtime_event_type: String, + #[serde(skip_serializing_if = "Option::is_none", alias = "remote_event_id")] + pub runtime_event_id: Option, + #[serde(skip_serializing_if = "Option::is_none", alias = "remote_created_at")] + pub runtime_created_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub run_status: Option, + pub emitted_at: String, + pub content: Value, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(untagged)] +pub enum CoworkChatEvent { + Session(CoworkChatSessionEvent), + Message(CoworkChatMessageEvent), + Files(CoworkChatFilesEvent), + Status(CoworkChatStatusEvent), + Runtime(CoworkChatRuntimeEvent), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct CoworkChatSendMessageResponse { + pub session_id: String, + pub events: Vec, +} + +pub fn emit_cowork_stream_event(app: &AppHandle, event: &CoworkChatEvent) -> Result<(), String> { + app.emit(COWORK_STREAM_EVENT_NAME, event) + .map_err(|error| format!("Failed to emit cowork stream event: {error}")) +} diff --git a/frontend/src-tauri/src/cowork/chat_commands.rs b/frontend/src-tauri/src/cowork/chat_commands.rs new file mode 100644 index 000000000..956b0acf5 --- /dev/null +++ b/frontend/src-tauri/src/cowork/chat_commands.rs @@ -0,0 +1,30 @@ +use crate::cowork::chat::{CoworkChatRunStatus, CoworkChatScope, CoworkChatSessionDetail}; +use crate::cowork::session_gateway; +use crate::cowork::time_utils::now_iso; +use tauri::AppHandle; + +#[tauri::command] +pub fn stop_cowork_chat_session( + app: AppHandle, + scope: CoworkChatScope, + session_id: String, +) -> Result { + let mut local_session = session_gateway::load_local_session(&app, scope, &session_id)?; + + { + let base = local_session.base_mut(); + if matches!( + base.run_status, + CoworkChatRunStatus::Thinking | CoworkChatRunStatus::WaitingForInput + ) { + base.run_status = CoworkChatRunStatus::Stopped; + base.updated_at = now_iso(); + } + } + + session_gateway::sync_organize_result_tree(&mut local_session)?; + local_session = session_gateway::persist_local_session(&app, local_session)?; + session_gateway::emit_local_terminal_state(&app, &local_session, false); + + Ok(local_session.base().clone()) +} diff --git a/frontend/src-tauri/src/cowork/desktop_skills/mod.rs b/frontend/src-tauri/src/cowork/desktop_skills/mod.rs new file mode 100644 index 000000000..4e36da41c --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_skills/mod.rs @@ -0,0 +1,27 @@ +use crate::cowork::agent_presets::shared::DesktopSkillCapability; + +#[derive(Clone)] +pub struct DesktopSkill { + capability: DesktopSkillCapability, +} + +impl DesktopSkill { + pub fn name(&self) -> &str { + self.capability.name.as_str() + } + + pub fn into_capability(self) -> DesktopSkillCapability { + self.capability + } +} + +pub fn common_desktop_skills() -> Vec { + Vec::new() +} + +pub fn common_desktop_skill_capabilities() -> Vec { + common_desktop_skills() + .into_iter() + .map(DesktopSkill::into_capability) + .collect() +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/apply_patch.rs b/frontend/src-tauri/src/cowork/desktop_tools/apply_patch.rs new file mode 100644 index 000000000..48f1bcf84 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/apply_patch.rs @@ -0,0 +1,263 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_APPLY_PATCH}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; +use std::path::Path; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_APPLY_PATCH.to_string(), + aliases: Vec::new(), + display_name: "Apply local patch".to_string(), + description: "Apply a structured patch to local files inside the selected desktop folder. Use absolute paths in patch headers.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The patch envelope starting with *** Begin Patch and ending with *** End Patch." + } + }, + "required": ["input"] + }), + }, + execute, + true, + ) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let input = tool_input + .get("input") + .and_then(Value::as_str) + .ok_or_else(|| "apply_patch requires input".to_string())?; + let operations = parse_patch_operations(input)?; + let mut summaries = Vec::new(); + for operation in operations { + match operation { + PatchOperation::AddFile { path, lines } => { + let resolved_path = ctx.resolve_scoped_path(&path, true)?; + if let Some(parent) = resolved_path.parent() { + std::fs::create_dir_all(parent).map_err(|error| { + format!( + "Failed to create parent directory {}: {}", + parent.display(), + error + ) + })?; + } + std::fs::write(&resolved_path, lines.join("\n")).map_err(|error| { + format!("Failed to create {}: {}", resolved_path.display(), error) + })?; + summaries.push(format!("Added {}", resolved_path.display())); + } + PatchOperation::DeleteFile { path } => { + let resolved_path = ctx.resolve_scoped_path(&path, false)?; + std::fs::remove_file(&resolved_path).map_err(|error| { + format!("Failed to delete {}: {}", resolved_path.display(), error) + })?; + summaries.push(format!("Deleted {}", resolved_path.display())); + } + PatchOperation::UpdateFile { + path, + move_to, + hunks, + } => { + let resolved_path = ctx.resolve_scoped_path(&path, false)?; + let contents = std::fs::read_to_string(&resolved_path).map_err(|error| { + format!("Failed to read {}: {}", resolved_path.display(), error) + })?; + let mut lines = contents + .lines() + .map(ToString::to_string) + .collect::>(); + let mut cursor = 0usize; + for hunk in hunks { + cursor = apply_patch_hunk(&mut lines, &hunk, cursor, &resolved_path)?; + } + + let output_path = move_to + .as_deref() + .map(|target| ctx.resolve_scoped_path(target, true)) + .transpose()? + .unwrap_or_else(|| resolved_path.clone()); + if let Some(parent) = output_path.parent() { + std::fs::create_dir_all(parent).map_err(|error| { + format!( + "Failed to create parent directory {}: {}", + parent.display(), + error + ) + })?; + } + std::fs::write(&output_path, lines.join("\n")).map_err(|error| { + format!("Failed to write {}: {}", output_path.display(), error) + })?; + if output_path != resolved_path { + let _ = std::fs::remove_file(&resolved_path); + summaries.push(format!( + "Updated {} and moved to {}", + resolved_path.display(), + output_path.display() + )); + } else { + summaries.push(format!("Updated {}", output_path.display())); + } + } + } + } + + Ok(summaries.join("\n")) +} + +enum PatchOperation { + AddFile { + path: String, + lines: Vec, + }, + DeleteFile { + path: String, + }, + UpdateFile { + path: String, + move_to: Option, + hunks: Vec>, + }, +} + +fn parse_patch_operations(input: &str) -> Result, String> { + let lines = input.lines().collect::>(); + if lines.first().copied() != Some("*** Begin Patch") { + return Err("apply_patch input must start with *** Begin Patch".to_string()); + } + if lines.last().copied() != Some("*** End Patch") { + return Err("apply_patch input must end with *** End Patch".to_string()); + } + + let mut operations = Vec::new(); + let mut index = 1usize; + while index + 1 < lines.len() { + let line = lines[index]; + if let Some(path) = line.strip_prefix("*** Add File: ") { + index += 1; + let mut add_lines = Vec::new(); + while index < lines.len() && !lines[index].starts_with("*** ") { + let content = lines[index] + .strip_prefix('+') + .ok_or_else(|| "apply_patch Add File lines must start with +".to_string())?; + add_lines.push(content.to_string()); + index += 1; + } + operations.push(PatchOperation::AddFile { + path: path.to_string(), + lines: add_lines, + }); + continue; + } + + if let Some(path) = line.strip_prefix("*** Delete File: ") { + operations.push(PatchOperation::DeleteFile { + path: path.to_string(), + }); + index += 1; + continue; + } + + if let Some(path) = line.strip_prefix("*** Update File: ") { + index += 1; + let mut move_to = None; + if index < lines.len() { + if let Some(target) = lines[index].strip_prefix("*** Move to: ") { + move_to = Some(target.to_string()); + index += 1; + } + } + + let mut hunks = Vec::new(); + while index < lines.len() && !lines[index].starts_with("*** ") { + if lines[index].starts_with("@@") { + index += 1; + let mut hunk_lines = Vec::new(); + while index < lines.len() + && !lines[index].starts_with("@@") + && !lines[index].starts_with("*** ") + { + if lines[index] == "*** End of File" { + index += 1; + break; + } + hunk_lines.push(lines[index].to_string()); + index += 1; + } + hunks.push(hunk_lines); + continue; + } + return Err(format!("Unexpected apply_patch line: {}", lines[index])); + } + + operations.push(PatchOperation::UpdateFile { + path: path.to_string(), + move_to, + hunks, + }); + continue; + } + + return Err(format!("Unsupported apply_patch header: {}", line)); + } + + Ok(operations) +} + +fn apply_patch_hunk( + lines: &mut Vec, + hunk_lines: &[String], + start_cursor: usize, + file_path: &Path, +) -> Result { + let mut old_block = Vec::new(); + let mut new_block = Vec::new(); + for line in hunk_lines { + let (prefix, content) = line.split_at(1); + match prefix { + " " => { + old_block.push(content.to_string()); + new_block.push(content.to_string()); + } + "-" => old_block.push(content.to_string()), + "+" => new_block.push(content.to_string()), + _ => return Err(format!("Unsupported apply_patch hunk line: {}", line)), + } + } + + if old_block.is_empty() { + return Err(format!( + "apply_patch hunks without context are not supported for {}", + file_path.display() + )); + } + + let match_index = find_line_block(lines, &old_block, start_cursor).ok_or_else(|| { + format!( + "apply_patch could not match hunk in {}", + file_path.display() + ) + })?; + lines.splice( + match_index..match_index + old_block.len(), + new_block.clone(), + ); + Ok(match_index + new_block.len()) +} + +fn find_line_block(lines: &[String], block: &[String], start_cursor: usize) -> Option { + if block.len() > lines.len() { + return None; + } + for start in start_cursor..=lines.len().saturating_sub(block.len()) { + if lines[start..start + block.len()] == *block { + return Some(start); + } + } + None +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/bash.rs b/frontend/src-tauri/src/cowork/desktop_tools/bash.rs new file mode 100644 index 000000000..2e97031ac --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/bash.rs @@ -0,0 +1,280 @@ +use super::{DesktopBashSession, DesktopTool, DesktopToolContext, TOOL_BASH}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::thread; +use std::time::{Duration, Instant}; + +const DEFAULT_BASH_TIMEOUT_SECS: u64 = 60; +const MAX_BASH_TIMEOUT_SECS: u64 = 180; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_BASH.to_string(), + aliases: Vec::new(), + display_name: "Run or inspect desktop shell sessions".to_string(), + description: "Run a local shell command or inspect saved shell session output inside the selected desktop folder. Use action='run' to execute commands, action='view' to inspect saved sessions, or action='list_sessions' to list available sessions. This tool runs on the desktop app, not on the Python backend.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["run", "view", "list_sessions"], + "description": "The Bash action to perform. Defaults to 'run'." + }, + "session_name": { + "type": "string", + "description": "Logical shell session name used to store or inspect command output. Defaults to 'default'." + }, + "session_names": { + "type": "array", + "items": {"type": "string"}, + "description": "Session names to inspect when action='view'." + }, + "command": { + "type": "string", + "description": "The shell command to execute when action='run'." + }, + "cwd": { + "type": "string", + "description": "Optional working directory for action='run' within the selected desktop scope. It may be absolute or relative to the scope root." + }, + "shell": { + "type": "string", + "description": "Optional shell executable for action='run'. Supported values include powershell, pwsh, cmd, sh, bash, and zsh. Defaults to powershell on Windows and sh on other systems." + }, + "description": { + "type": "string", + "description": "Optional short description of what the command does when action='run'." + }, + "wait_for_output": { + "type": "boolean", + "description": "Whether to wait for command completion when action='run'. Defaults to true. Background execution is not supported yet." + }, + "timeout": { + "type": "integer", + "description": "Optional timeout in seconds for action='run'. Defaults to 60 and is capped at 180." + } + }, + "required": [] + }), + }, + execute, + true, + ) + .with_aliases(&["bash"]) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let action = tool_input + .get("action") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("run"); + + match action { + "run" => execute_run(ctx, tool_input), + "view" => execute_view(ctx, tool_input), + "list_sessions" => execute_list_sessions(ctx), + other => Err(format!( + "Unsupported Bash action '{}'. Supported actions: run, view, list_sessions", + other + )), + } +} + +fn execute_run(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let session_name = tool_input + .get("session_name") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or("default") + .to_string(); + let command = tool_input + .get("command") + .and_then(Value::as_str) + .ok_or_else(|| "Bash action='run' requires command".to_string())?; + let wait_for_output = tool_input + .get("wait_for_output") + .and_then(Value::as_bool) + .unwrap_or(true); + if !wait_for_output { + return Err( + "Background Bash execution is not supported in desktop cowork mode yet".to_string(), + ); + } + + let timeout_secs = tool_input + .get("timeout") + .and_then(Value::as_u64) + .unwrap_or(DEFAULT_BASH_TIMEOUT_SECS) + .min(MAX_BASH_TIMEOUT_SECS); + let working_directory = tool_input + .get("cwd") + .and_then(Value::as_str) + .map(|value| ctx.resolve_scoped_path(value, false)) + .transpose()? + .unwrap_or_else(|| ctx.working_directory().to_path_buf()); + let requested_shell = tool_input + .get("shell") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()); + + let mut process = build_command_process(requested_shell, command, &working_directory)?; + process.stdout(Stdio::piped()).stderr(Stdio::piped()); + let mut child = process + .spawn() + .map_err(|error| format!("Failed to start Bash command: {error}"))?; + let deadline = Instant::now() + Duration::from_secs(timeout_secs); + + loop { + if child + .try_wait() + .map_err(|error| format!("Failed to poll Bash command: {error}"))? + .is_some() + { + let output = child + .wait_with_output() + .map_err(|error| format!("Failed to collect Bash output: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let combined = format_output(stdout.as_ref(), stderr.as_ref(), output.status.code()); + ctx.bash_sessions_mut().insert( + session_name, + DesktopBashSession { + last_output: combined.clone(), + last_exit_code: output.status.code(), + }, + ); + if output.status.success() { + return Ok(combined); + } + return Err(combined); + } + + if Instant::now() >= deadline { + let _ = child.kill(); + let output = child + .wait_with_output() + .map_err(|error| format!("Failed to collect timed out Bash output: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let combined = format!( + "{}\nTimed out after {} seconds", + format_output(stdout.as_ref(), stderr.as_ref(), output.status.code()), + timeout_secs + ); + ctx.bash_sessions_mut().insert( + session_name, + DesktopBashSession { + last_output: combined.clone(), + last_exit_code: output.status.code(), + }, + ); + return Err(combined); + } + + thread::sleep(Duration::from_millis(100)); + } +} + +fn execute_view(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let session_names = tool_input + .get("session_names") + .and_then(Value::as_array) + .ok_or_else(|| "Bash action='view' requires session_names".to_string())?; + let mut results = Vec::new(); + for session_name in session_names { + let Some(session_name) = session_name.as_str() else { + continue; + }; + if let Some(state) = ctx.bash_sessions().get(session_name) { + results.push(format!( + "[{}] exit_code={:?}\n{}", + session_name, state.last_exit_code, state.last_output + )); + } else { + results.push(format!( + "[{}] No desktop bash session output available", + session_name + )); + } + } + Ok(results.join("\n\n")) +} + +fn execute_list_sessions(ctx: &mut DesktopToolContext<'_>) -> Result { + let session_names = ctx.bash_sessions().keys().cloned().collect::>(); + serde_json::to_string_pretty(&session_names) + .map_err(|error| format!("Failed to encode bash session list: {error}")) +} + +fn format_output(stdout: &str, stderr: &str, exit_code: Option) -> String { + let mut sections = Vec::new(); + if !stdout.trim().is_empty() { + sections.push(format!("stdout:\n{}", stdout.trim_end())); + } + if !stderr.trim().is_empty() { + sections.push(format!("stderr:\n{}", stderr.trim_end())); + } + sections.push(format!("exit_code: {:?}", exit_code)); + sections.join("\n\n") +} + +fn build_command_process( + requested_shell: Option<&str>, + command: &str, + working_directory: &PathBuf, +) -> Result { + let shell = requested_shell.unwrap_or(default_shell_name()); + let mut process = match shell { + "powershell" => { + let mut command_builder = Command::new("powershell"); + command_builder + .arg("-NoProfile") + .arg("-Command") + .arg(command); + command_builder + } + "pwsh" => { + let mut command_builder = Command::new("pwsh"); + command_builder + .arg("-NoProfile") + .arg("-Command") + .arg(command); + command_builder + } + "cmd" => { + let mut command_builder = Command::new("cmd"); + command_builder.arg("/C").arg(command); + command_builder + } + "sh" | "bash" | "zsh" => { + let mut command_builder = Command::new(shell); + command_builder.arg("-lc").arg(command); + command_builder + } + _ => { + return Err(format!( + "Unsupported shell '{}'. Supported shells: powershell, pwsh, cmd, sh, bash, zsh", + shell + )) + } + }; + + process.current_dir(working_directory); + Ok(process) +} + +fn default_shell_name() -> &'static str { + if cfg!(target_os = "windows") { + "powershell" + } else { + "sh" + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/edit.rs b/frontend/src-tauri/src/cowork/desktop_tools/edit.rs new file mode 100644 index 000000000..e81def53d --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/edit.rs @@ -0,0 +1,90 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_EDIT}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_EDIT.to_string(), + aliases: Vec::new(), + display_name: "Edit local file".to_string(), + description: "Perform an exact string replacement inside a local file within the selected desktop folder.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "The file path to edit. It may be absolute or relative to the current desktop scope." + }, + "path": { + "type": "string", + "description": "Alias for file_path. It may be absolute or relative to the current desktop scope." + }, + "old_string": { + "type": "string", + "description": "The exact text to replace." + }, + "new_string": { + "type": "string", + "description": "The replacement text." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches instead of only the first unique match." + } + }, + "required": ["old_string", "new_string"] + }), + }, + execute, + true, + ) + .with_aliases(&["edit_file"]) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let file_path = tool_input + .get("file_path") + .or_else(|| tool_input.get("path")) + .and_then(Value::as_str) + .ok_or_else(|| "Edit requires file_path or path".to_string())?; + let old_string = tool_input + .get("old_string") + .and_then(Value::as_str) + .ok_or_else(|| "Edit requires old_string".to_string())?; + let new_string = tool_input + .get("new_string") + .and_then(Value::as_str) + .ok_or_else(|| "Edit requires new_string".to_string())?; + let replace_all = tool_input + .get("replace_all") + .and_then(Value::as_bool) + .unwrap_or(false); + let resolved_path = ctx.resolve_scoped_path(file_path, false)?; + let contents = std::fs::read_to_string(&resolved_path) + .map_err(|error| format!("Failed to read {}: {}", resolved_path.display(), error))?; + + let match_count = contents.matches(old_string).count(); + if match_count == 0 { + return Err(format!( + "Edit could not find the requested text in {}", + resolved_path.display() + )); + } + if !replace_all && match_count > 1 { + return Err(format!( + "Edit found {} matches in {}; use replace_all or provide more context", + match_count, + resolved_path.display() + )); + } + + let updated = if replace_all { + contents.replace(old_string, new_string) + } else { + contents.replacen(old_string, new_string, 1) + }; + std::fs::write(&resolved_path, updated) + .map_err(|error| format!("Failed to write {}: {}", resolved_path.display(), error))?; + Ok(format!("Edited {}", resolved_path.display())) +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/glob.rs b/frontend/src-tauri/src/cowork/desktop_tools/glob.rs new file mode 100644 index 000000000..a536ac5e0 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/glob.rs @@ -0,0 +1,181 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_GLOB}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; +use std::fs; +use std::path::{Path, PathBuf}; + +const DEFAULT_LIMIT: usize = 100; +const COMMON_SKIP_DIRS: &[&str] = &[".git", "node_modules", "target", "__pycache__"]; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_GLOB.to_string(), + aliases: Vec::new(), + display_name: "Find local files by glob".to_string(), + description: "Find files inside the selected local desktop folder using a glob pattern such as '**/*.rs' or 'src/**/*.ts'. This runs on the desktop app.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "The glob pattern to match relative to the chosen search path, for example '**/*.rs'." + }, + "path": { + "type": "string", + "description": "Optional base directory path within the selected desktop scope. It may be absolute or relative to the scope root." + }, + "limit": { + "type": "integer", + "description": "Maximum number of matches to return. Defaults to 100." + }, + "include_hidden": { + "type": "boolean", + "description": "If true, include hidden files and common ignored directories. Defaults to false." + } + }, + "required": ["pattern"] + }), + }, + execute, + false, + ) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let pattern = tool_input + .get("pattern") + .and_then(Value::as_str) + .ok_or_else(|| "glob requires pattern".to_string())? + .trim(); + if pattern.is_empty() { + return Err("glob pattern cannot be empty".to_string()); + } + + let matcher = + ::glob::Pattern::new(pattern).map_err(|error| format!("Invalid glob pattern: {error}"))?; + let search_root = resolve_search_root(ctx, tool_input.get("path").and_then(Value::as_str))?; + let limit = tool_input + .get("limit") + .and_then(Value::as_u64) + .unwrap_or(DEFAULT_LIMIT as u64) as usize; + let include_hidden = tool_input + .get("include_hidden") + .and_then(Value::as_bool) + .unwrap_or(false); + + if limit == 0 { + return Err("glob limit must be 1 or greater".to_string()); + } + + let mut matches = Vec::new(); + let mut truncated = false; + collect_matches( + &search_root, + &search_root, + &matcher, + include_hidden, + limit, + &mut matches, + &mut truncated, + )?; + + if matches.is_empty() { + return Ok(format!("No local files matched glob pattern: {pattern}")); + } + + if truncated { + matches.push(format!("[glob truncated at {} results]", limit)); + } + + Ok(matches.join("\n")) +} + +fn resolve_search_root( + ctx: &DesktopToolContext<'_>, + path: Option<&str>, +) -> Result { + let resolved = match path { + Some(value) => ctx.resolve_scoped_path(value, false)?, + None => ctx.working_directory().to_path_buf(), + }; + + if !resolved.is_dir() { + return Err(format!( + "glob path is not a directory: {}", + resolved.display() + )); + } + + Ok(resolved) +} + +#[allow(clippy::too_many_arguments)] +fn collect_matches( + root: &Path, + current: &Path, + matcher: &::glob::Pattern, + include_hidden: bool, + limit: usize, + matches: &mut Vec, + truncated: &mut bool, +) -> Result<(), String> { + if matches.len() >= limit { + *truncated = true; + return Ok(()); + } + + let entries = fs::read_dir(current) + .map_err(|error| format!("Failed to read directory {}: {}", current.display(), error))?; + + for entry_result in entries { + let entry = + entry_result.map_err(|error| format!("Failed to read directory entry: {}", error))?; + let path = entry.path(); + let file_name = entry.file_name().to_string_lossy().to_string(); + if should_skip_name(&file_name, include_hidden) { + continue; + } + + let relative_path = path + .strip_prefix(root) + .map_err(|error| format!("Failed to compute relative path: {error}"))?; + let relative_pattern_path = normalize_path_for_pattern(relative_path); + if path.is_file() && matcher.matches(&relative_pattern_path) { + if matches.len() < limit { + matches.push(path.display().to_string()); + } else { + *truncated = true; + } + } + + if path.is_dir() { + collect_matches( + root, + &path, + matcher, + include_hidden, + limit, + matches, + truncated, + )?; + if matches.len() >= limit { + break; + } + } + } + + Ok(()) +} + +fn should_skip_name(name: &str, include_hidden: bool) -> bool { + if include_hidden { + return false; + } + + name.starts_with('.') || COMMON_SKIP_DIRS.iter().any(|value| value == &name) +} + +fn normalize_path_for_pattern(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/grep.rs b/frontend/src-tauri/src/cowork/desktop_tools/grep.rs new file mode 100644 index 000000000..5eb034c89 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/grep.rs @@ -0,0 +1,258 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_GREP}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; +use std::fs; +use std::path::{Path, PathBuf}; + +const DEFAULT_MATCH_LIMIT: usize = 50; +const COMMON_SKIP_DIRS: &[&str] = &[".git", "node_modules", "target", "__pycache__"]; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_GREP.to_string(), + aliases: Vec::new(), + display_name: "Search local files by regex".to_string(), + description: "Search local files inside the selected desktop folder using a regex pattern. Returns absolute file paths with line numbers. This runs on the desktop app.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "The regex pattern to search for." + }, + "path": { + "type": "string", + "description": "Optional file or directory path within the selected desktop scope. It may be absolute or relative to the scope root." + }, + "glob": { + "type": "string", + "description": "Optional glob filter relative to the search directory, for example '**/*.rs' or '*.ts'." + }, + "case_insensitive": { + "type": "boolean", + "description": "If true, perform a case-insensitive regex search." + }, + "context": { + "type": "integer", + "description": "Number of context lines before and after each match. Defaults to 0." + }, + "limit": { + "type": "integer", + "description": "Maximum number of regex matches to return. Defaults to 50." + }, + "include_hidden": { + "type": "boolean", + "description": "If true, include hidden files and common ignored directories. Defaults to false." + } + }, + "required": ["pattern"] + }), + }, + execute, + false, + ) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let pattern = tool_input + .get("pattern") + .and_then(Value::as_str) + .ok_or_else(|| "grep requires pattern".to_string())?; + let regex = build_regex(pattern, tool_input)?; + let search_path = resolve_search_path(ctx, tool_input.get("path").and_then(Value::as_str))?; + let matcher = tool_input + .get("glob") + .and_then(Value::as_str) + .map(::glob::Pattern::new) + .transpose() + .map_err(|error| format!("Invalid grep glob filter: {error}"))?; + let context_lines = tool_input + .get("context") + .and_then(Value::as_u64) + .unwrap_or(0) as usize; + let limit = tool_input + .get("limit") + .and_then(Value::as_u64) + .unwrap_or(DEFAULT_MATCH_LIMIT as u64) as usize; + let include_hidden = tool_input + .get("include_hidden") + .and_then(Value::as_bool) + .unwrap_or(false); + + if limit == 0 { + return Err("grep limit must be 1 or greater".to_string()); + } + + let mut results = Vec::new(); + let mut match_count = 0usize; + + if search_path.is_file() { + search_file( + &search_path, + ®ex, + context_lines, + limit, + &mut match_count, + &mut results, + )?; + } else { + search_directory( + &search_path, + &search_path, + ®ex, + matcher.as_ref(), + context_lines, + limit, + include_hidden, + &mut match_count, + &mut results, + )?; + } + + if results.is_empty() { + return Ok(format!("No local matches found for regex: {pattern}")); + } + + Ok(results.join("\n")) +} + +fn build_regex(pattern: &str, tool_input: &Value) -> Result { + let case_insensitive = tool_input + .get("case_insensitive") + .and_then(Value::as_bool) + .unwrap_or(false); + regex::RegexBuilder::new(pattern) + .case_insensitive(case_insensitive) + .build() + .map_err(|error| format!("Invalid regex pattern: {error}")) +} + +fn resolve_search_path( + ctx: &DesktopToolContext<'_>, + path: Option<&str>, +) -> Result { + match path { + Some(value) => ctx.resolve_scoped_path(value, false), + None => Ok(ctx.working_directory().to_path_buf()), + } +} + +#[allow(clippy::too_many_arguments)] +fn search_directory( + root: &Path, + current: &Path, + regex: ®ex::Regex, + matcher: Option<&::glob::Pattern>, + context_lines: usize, + limit: usize, + include_hidden: bool, + match_count: &mut usize, + results: &mut Vec, +) -> Result<(), String> { + if *match_count >= limit { + return Ok(()); + } + + let entries = fs::read_dir(current) + .map_err(|error| format!("Failed to read directory {}: {}", current.display(), error))?; + + for entry_result in entries { + let entry = + entry_result.map_err(|error| format!("Failed to read directory entry: {}", error))?; + let path = entry.path(); + let file_name = entry.file_name().to_string_lossy().to_string(); + if should_skip_name(&file_name, include_hidden) { + continue; + } + + if path.is_dir() { + search_directory( + root, + &path, + regex, + matcher, + context_lines, + limit, + include_hidden, + match_count, + results, + )?; + } else if matches_glob(root, &path, matcher)? { + search_file(&path, regex, context_lines, limit, match_count, results)?; + } + + if *match_count >= limit { + break; + } + } + + Ok(()) +} + +fn search_file( + path: &Path, + regex: ®ex::Regex, + context_lines: usize, + limit: usize, + match_count: &mut usize, + results: &mut Vec, +) -> Result<(), String> { + let content = match fs::read_to_string(path) { + Ok(value) => value, + Err(_) => return Ok(()), + }; + let lines = content.lines().collect::>(); + + for (index, line) in lines.iter().enumerate() { + if !regex.is_match(line) { + continue; + } + + *match_count += 1; + if *match_count > limit { + break; + } + + let start = index.saturating_sub(context_lines); + let end = (index + context_lines + 1).min(lines.len()); + for line_index in start..end { + let marker = if line_index == index { ">" } else { ":" }; + results.push(format!( + "{}:{}{} {}", + path.display(), + line_index + 1, + marker, + lines[line_index] + )); + } + if context_lines > 0 { + results.push("---".to_string()); + } + } + + Ok(()) +} + +fn matches_glob( + root: &Path, + path: &Path, + matcher: Option<&::glob::Pattern>, +) -> Result { + let Some(matcher) = matcher else { + return Ok(true); + }; + + let relative_path = path + .strip_prefix(root) + .map_err(|error| format!("Failed to compute relative path: {error}"))?; + Ok(matcher.matches(&relative_path.to_string_lossy().replace('\\', "/"))) +} + +fn should_skip_name(name: &str, include_hidden: bool) -> bool { + if include_hidden { + return false; + } + + name.starts_with('.') || COMMON_SKIP_DIRS.iter().any(|value| value == &name) +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/list_dir.rs b/frontend/src-tauri/src/cowork/desktop_tools/list_dir.rs new file mode 100644 index 000000000..6d7c71666 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/list_dir.rs @@ -0,0 +1,266 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_LIST_DIR}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; +use std::fs; +use std::path::{Path, PathBuf}; + +const DEFAULT_MAX_DEPTH: usize = 3; +const DEFAULT_LIMIT: usize = 500; +const COMMON_SKIP_DIRS: &[&str] = &[".git", "node_modules", "target", "__pycache__"]; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_LIST_DIR.to_string(), + aliases: Vec::new(), + display_name: "List local directory".to_string(), + description: "List files and subdirectories inside the selected local desktop folder. Supports recursive listing and returns scoped absolute paths.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Optional directory path within the selected desktop scope. It may be absolute or relative to the current scope root." + }, + "recursive": { + "type": "boolean", + "description": "If true, list contents recursively. Defaults to false." + }, + "max_depth": { + "type": "integer", + "description": "Maximum depth for recursive listing. Defaults to 3." + }, + "limit": { + "type": "integer", + "description": "Maximum number of entries to return. Defaults to 500." + }, + "include_hidden": { + "type": "boolean", + "description": "If true, include hidden files and common ignored directories. Defaults to false." + } + }, + "required": [] + }), + }, + execute, + false, + ) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let directory_path = resolve_directory(ctx, tool_input.get("path").and_then(Value::as_str))?; + let recursive = tool_input + .get("recursive") + .and_then(Value::as_bool) + .unwrap_or(false); + let include_hidden = tool_input + .get("include_hidden") + .and_then(Value::as_bool) + .unwrap_or(false); + let max_depth = tool_input + .get("max_depth") + .and_then(Value::as_u64) + .unwrap_or(DEFAULT_MAX_DEPTH as u64) as usize; + let limit = tool_input + .get("limit") + .and_then(Value::as_u64) + .unwrap_or(DEFAULT_LIMIT as u64) as usize; + + if limit == 0 { + return Err("list_dir limit must be 1 or greater".to_string()); + } + + let mut results = Vec::new(); + let mut truncated = false; + if recursive { + list_recursive( + &directory_path, + 0, + max_depth, + include_hidden, + limit, + &mut results, + &mut truncated, + )?; + } else { + list_single( + &directory_path, + include_hidden, + &mut results, + limit, + &mut truncated, + )?; + } + + if results.is_empty() { + return Ok(format!("Directory is empty: {}", directory_path.display())); + } + + if truncated { + results.push(format!("[list_dir truncated at {} entries]", limit)); + } + + Ok(results.join("\n")) +} + +fn resolve_directory(ctx: &DesktopToolContext<'_>, path: Option<&str>) -> Result { + let resolved = match path { + Some(value) => ctx.resolve_scoped_path(value, false)?, + None => ctx.working_directory().to_path_buf(), + }; + + if !resolved.is_dir() { + return Err(format!("Path is not a directory: {}", resolved.display())); + } + + Ok(resolved) +} + +fn list_single( + directory_path: &Path, + include_hidden: bool, + results: &mut Vec, + limit: usize, + truncated: &mut bool, +) -> Result<(), String> { + let mut entries = collect_entries(directory_path, include_hidden)?; + sort_entries(&mut entries); + + for entry in entries { + if results.len() >= limit { + *truncated = true; + break; + } + results.push(render_entry(&entry.path, 0, entry.is_dir, entry.size)); + } + + Ok(()) +} + +fn list_recursive( + directory_path: &Path, + depth: usize, + max_depth: usize, + include_hidden: bool, + limit: usize, + results: &mut Vec, + truncated: &mut bool, +) -> Result<(), String> { + if depth > max_depth || results.len() >= limit { + if results.len() >= limit { + *truncated = true; + } + return Ok(()); + } + + let mut entries = collect_entries(directory_path, include_hidden)?; + sort_entries(&mut entries); + + for entry in entries { + if results.len() >= limit { + *truncated = true; + break; + } + + results.push(render_entry(&entry.path, depth, entry.is_dir, entry.size)); + if entry.is_dir { + list_recursive( + &entry.path, + depth + 1, + max_depth, + include_hidden, + limit, + results, + truncated, + )?; + } + } + + Ok(()) +} + +#[derive(Clone)] +struct DirectoryEntry { + path: PathBuf, + is_dir: bool, + size: u64, +} + +fn collect_entries( + directory_path: &Path, + include_hidden: bool, +) -> Result, String> { + let entries = fs::read_dir(directory_path).map_err(|error| { + format!( + "Failed to read directory {}: {}", + directory_path.display(), + error + ) + })?; + let mut collected = Vec::new(); + + for entry_result in entries { + let entry = + entry_result.map_err(|error| format!("Failed to read directory entry: {}", error))?; + let name = entry.file_name().to_string_lossy().to_string(); + if should_skip_name(&name, include_hidden) { + continue; + } + + let metadata = entry.metadata().map_err(|error| { + format!( + "Failed to read metadata for {}: {}", + entry.path().display(), + error + ) + })?; + collected.push(DirectoryEntry { + path: entry.path(), + is_dir: metadata.is_dir(), + size: metadata.len(), + }); + } + + Ok(collected) +} + +fn sort_entries(entries: &mut [DirectoryEntry]) { + entries.sort_by(|left, right| match (left.is_dir, right.is_dir) { + (true, false) => std::cmp::Ordering::Less, + (false, true) => std::cmp::Ordering::Greater, + _ => left.path.cmp(&right.path), + }); +} + +fn should_skip_name(name: &str, include_hidden: bool) -> bool { + if include_hidden { + return false; + } + + name.starts_with('.') || COMMON_SKIP_DIRS.iter().any(|value| value == &name) +} + +fn render_entry(path: &Path, depth: usize, is_dir: bool, size: u64) -> String { + let indent = " ".repeat(depth); + let label = path + .file_name() + .map(|value| value.to_string_lossy().to_string()) + .unwrap_or_else(|| path.display().to_string()); + if is_dir { + format!("{indent}[dir] {label}/") + } else { + format!("{indent}[file] {label} ({})", format_size(size)) + } +} + +fn format_size(size: u64) -> String { + if size < 1024 { + format!("{size} B") + } else if size < 1024 * 1024 { + format!("{:.1} KB", size as f64 / 1024.0) + } else if size < 1024 * 1024 * 1024 { + format!("{:.1} MB", size as f64 / (1024.0 * 1024.0)) + } else { + format!("{:.1} GB", size as f64 / (1024.0 * 1024.0 * 1024.0)) + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/mod.rs b/frontend/src-tauri/src/cowork/desktop_tools/mod.rs new file mode 100644 index 000000000..a740d67f7 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/mod.rs @@ -0,0 +1,306 @@ +mod apply_patch; +mod bash; +mod edit; +mod glob; +mod grep; +mod list_dir; +mod read; +mod todo_write; +mod write; + +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::Value; +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; +use tauri::AppHandle; + +pub const TOOL_BASH: &str = "Bash"; +pub const TOOL_READ: &str = "Read"; +pub const TOOL_WRITE: &str = "Write"; +pub const TOOL_EDIT: &str = "Edit"; +pub const TOOL_APPLY_PATCH: &str = "apply_patch"; +pub const TOOL_TODO_WRITE: &str = "TodoWrite"; +pub const TOOL_GLOB: &str = "glob"; +pub const TOOL_GREP: &str = "grep"; +pub const TOOL_LIST_DIR: &str = "list_dir"; + +pub type DesktopToolExecuteFn = + for<'a> fn(&mut DesktopToolContext<'a>, &Value) -> Result; +pub type DesktopExecutionScopeLoaderFn = + fn(&AppHandle, &str) -> Result; +pub type DesktopExecutionScopeRefreshFn = + fn(&AppHandle, &str, &DesktopExecutionScope) -> Result<(), String>; + +#[derive(Clone)] +pub struct DesktopTool { + capability: DesktopToolCapability, + execute: DesktopToolExecuteFn, + refreshes_scope: bool, +} + +impl DesktopTool { + pub fn new( + capability: DesktopToolCapability, + execute: DesktopToolExecuteFn, + refreshes_scope: bool, + ) -> Self { + Self { + capability, + execute, + refreshes_scope, + } + } + + pub fn name(&self) -> &str { + self.capability.name.as_str() + } + + pub fn display_name(&self) -> &str { + self.capability.display_name.as_str() + } + + pub fn aliases(&self) -> &[String] { + self.capability.aliases.as_slice() + } + + pub fn into_capability(self) -> DesktopToolCapability { + self.capability + } + + pub fn refreshes_scope(&self) -> bool { + self.refreshes_scope + } + + pub fn execute( + &self, + ctx: &mut DesktopToolContext<'_>, + input: &Value, + ) -> Result { + (self.execute)(ctx, input) + } + + pub fn with_aliases(mut self, aliases: &[&str]) -> Self { + self.capability.aliases = aliases.iter().map(|value| value.to_string()).collect(); + self + } +} + +#[derive(Default)] +pub struct DesktopToolRuntime { + bash_sessions: HashMap, + todos: Option, +} + +#[derive(Clone, Default)] +pub(crate) struct DesktopBashSession { + pub(crate) last_output: String, + pub(crate) last_exit_code: Option, +} + +pub struct DesktopExecutionScope { + canonical_root: PathBuf, +} + +pub struct DesktopToolContext<'a> { + app: &'a AppHandle, + local_session_id: &'a str, + execution_scope: &'a DesktopExecutionScope, + runtime: &'a mut DesktopToolRuntime, + refresh_scope: Option, +} + +impl<'a> DesktopToolContext<'a> { + pub fn new( + app: &'a AppHandle, + local_session_id: &'a str, + execution_scope: &'a DesktopExecutionScope, + runtime: &'a mut DesktopToolRuntime, + refresh_scope: Option, + ) -> Self { + Self { + app, + local_session_id, + execution_scope, + runtime, + refresh_scope, + } + } + + pub fn working_directory(&self) -> &Path { + &self.execution_scope.canonical_root + } + + pub fn resolve_scoped_path( + &self, + file_path: &str, + allow_missing: bool, + ) -> Result { + resolve_scoped_path(self.execution_scope, file_path, allow_missing) + } + + pub fn refresh_scope_snapshot(&self) -> Result<(), String> { + match self.refresh_scope { + Some(refresh_scope) => { + refresh_scope(self.app, self.local_session_id, self.execution_scope) + } + None => Ok(()), + } + } + + pub(crate) fn bash_sessions(&self) -> &HashMap { + &self.runtime.bash_sessions + } + + pub(crate) fn bash_sessions_mut(&mut self) -> &mut HashMap { + &mut self.runtime.bash_sessions + } + + pub(crate) fn set_todos(&mut self, todos: Value) { + self.runtime.todos = Some(todos); + } +} + +impl DesktopExecutionScope { + pub fn new(canonical_root: PathBuf) -> Self { + Self { canonical_root } + } +} + +pub fn common_desktop_tools() -> Vec { + vec![ + bash::desktop_tool(), + list_dir::desktop_tool(), + glob::desktop_tool(), + grep::desktop_tool(), + read::desktop_tool(), + write::desktop_tool(), + edit::desktop_tool(), + apply_patch::desktop_tool(), + todo_write::desktop_tool(), + ] +} + +pub fn common_desktop_tool_capabilities() -> Vec { + common_desktop_tools() + .into_iter() + .map(DesktopTool::into_capability) + .collect() +} + +pub fn common_desktop_tool_names() -> Vec { + common_desktop_tool_capabilities() + .into_iter() + .map(|tool| tool.name) + .collect() +} + +pub fn find_desktop_tool<'a>(tools: &'a [DesktopTool], tool_name: &str) -> Option<&'a DesktopTool> { + let normalized = tool_name.trim().to_lowercase(); + tools.iter().find(|tool| { + tool.name().eq_ignore_ascii_case(&normalized) + || tool + .aliases() + .iter() + .any(|alias| alias.eq_ignore_ascii_case(&normalized)) + }) +} + +pub fn resolve_desktop_tool_name(tool_name: &str) -> Option { + let normalized = tool_name.trim().to_lowercase(); + if normalized.is_empty() { + return None; + } + + common_desktop_tools().into_iter().find_map(|tool| { + if tool.name().eq_ignore_ascii_case(&normalized) + || tool + .aliases() + .iter() + .any(|alias| alias.eq_ignore_ascii_case(&normalized)) + { + Some(tool.name().to_string()) + } else { + None + } + }) +} + +fn resolve_scoped_path( + execution_scope: &DesktopExecutionScope, + file_path: &str, + allow_missing: bool, +) -> Result { + let raw_path = PathBuf::from(file_path); + let scoped_path = if raw_path.is_absolute() { + raw_path + } else { + execution_scope.canonical_root.join(raw_path) + }; + + if scoped_path.exists() { + let canonical = fs::canonicalize(&scoped_path) + .map_err(|error| format!("Failed to resolve {}: {}", scoped_path.display(), error))?; + ensure_within_scope(execution_scope, &canonical)?; + return Ok(canonical); + } + + if !allow_missing { + return Err(format!("Path does not exist: {}", scoped_path.display())); + } + + let (existing_ancestor, relative_suffix) = split_existing_ancestor(&scoped_path)?; + if relative_suffix + .components() + .any(|component| matches!(component, std::path::Component::ParentDir)) + { + return Err(format!( + "Desktop tool path cannot escape desktop scope: {}", + scoped_path.display() + )); + } + let canonical_ancestor = fs::canonicalize(&existing_ancestor).map_err(|error| { + format!( + "Failed to resolve ancestor {}: {}", + existing_ancestor.display(), + error + ) + })?; + ensure_within_scope(execution_scope, &canonical_ancestor)?; + Ok(canonical_ancestor.join(relative_suffix)) +} + +fn split_existing_ancestor(path: &Path) -> Result<(PathBuf, PathBuf), String> { + let mut current = path.to_path_buf(); + let mut suffix = PathBuf::new(); + + while !current.exists() { + let file_name = current + .file_name() + .map(PathBuf::from) + .ok_or_else(|| format!("Unable to resolve path ancestor for {}", path.display()))?; + suffix = if suffix.as_os_str().is_empty() { + file_name + } else { + PathBuf::from(file_name).join(suffix) + }; + current = current + .parent() + .map(PathBuf::from) + .ok_or_else(|| format!("Unable to resolve path ancestor for {}", path.display()))?; + } + + Ok((current, suffix)) +} + +fn ensure_within_scope(execution_scope: &DesktopExecutionScope, path: &Path) -> Result<(), String> { + if path.starts_with(&execution_scope.canonical_root) { + Ok(()) + } else { + Err(format!( + "Desktop tool path {} is outside desktop scope {}", + path.display(), + execution_scope.canonical_root.display() + )) + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/read.rs b/frontend/src-tauri/src/cowork/desktop_tools/read.rs new file mode 100644 index 000000000..4ee0db327 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/read.rs @@ -0,0 +1,79 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_READ}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; + +const DEFAULT_READ_LIMIT: usize = 2000; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_READ.to_string(), + aliases: Vec::new(), + display_name: "Read local file".to_string(), + description: "Read a text file from the selected local desktop folder. The path may be absolute or relative to the current desktop scope.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "The file path to read. It may be absolute or relative to the current desktop scope." + }, + "path": { + "type": "string", + "description": "Alias for file_path. May be absolute or relative to the current desktop scope." + }, + "limit": { + "type": "integer", + "description": "Optional number of lines to read. Defaults to 2000." + }, + "offset": { + "type": "integer", + "description": "Optional 1-based line offset to start reading from." + } + }, + "required": [] + }), + }, + execute, + false, + ) + .with_aliases(&["read_file"]) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let file_path = tool_input + .get("file_path") + .or_else(|| tool_input.get("path")) + .and_then(Value::as_str) + .ok_or_else(|| "Read requires file_path or path".to_string())?; + let resolved_path = ctx.resolve_scoped_path(file_path, false)?; + let contents = std::fs::read_to_string(&resolved_path) + .map_err(|error| format!("Failed to read {}: {}", resolved_path.display(), error))?; + let offset = tool_input + .get("offset") + .and_then(Value::as_u64) + .unwrap_or(1) as usize; + let limit = tool_input + .get("limit") + .and_then(Value::as_u64) + .unwrap_or(DEFAULT_READ_LIMIT as u64) as usize; + if offset == 0 { + return Err("Read offset must be 1 or greater".to_string()); + } + if limit == 0 { + return Err("Read limit must be 1 or greater".to_string()); + } + + let lines = contents.lines().collect::>(); + let start = offset.saturating_sub(1); + if start >= lines.len() { + return Ok(String::new()); + } + let end = start.saturating_add(limit).min(lines.len()); + let mut rendered = Vec::new(); + for (index, line) in lines[start..end].iter().enumerate() { + rendered.push(format!("{}\t{}", start + index + 1, line)); + } + + Ok(rendered.join("\n")) +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/todo_write.rs b/frontend/src-tauri/src/cowork/desktop_tools/todo_write.rs new file mode 100644 index 000000000..850cbaf7b --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/todo_write.rs @@ -0,0 +1,57 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_TODO_WRITE}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_TODO_WRITE.to_string(), + aliases: Vec::new(), + display_name: "Write desktop todo list".to_string(), + description: + "Store and update a structured todo list for the current desktop cowork run." + .to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "todos": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "content": {"type": "string"}, + "status": { + "type": "string", + "enum": ["pending", "in_progress", "completed"] + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"] + } + }, + "required": ["id", "content", "status", "priority"] + }, + "description": "The updated todo list for the current run." + } + }, + "required": ["todos"] + }), + }, + execute, + false, + ) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let todos = tool_input + .get("todos") + .cloned() + .ok_or_else(|| "TodoWrite requires todos".to_string())?; + let todo_count = todos.as_array().map(Vec::len).unwrap_or(0); + ctx.set_todos(todos); + Ok(format!( + "Stored {} desktop todo items for this cowork run", + todo_count + )) +} diff --git a/frontend/src-tauri/src/cowork/desktop_tools/write.rs b/frontend/src-tauri/src/cowork/desktop_tools/write.rs new file mode 100644 index 000000000..7cb8bca76 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/write.rs @@ -0,0 +1,61 @@ +use super::{DesktopTool, DesktopToolContext, TOOL_WRITE}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use serde_json::{json, Value}; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_WRITE.to_string(), + aliases: Vec::new(), + display_name: "Write local file".to_string(), + description: "Write or overwrite a local file within the selected desktop folder." + .to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "The file path to write. It may be absolute or relative to the current desktop scope." + }, + "path": { + "type": "string", + "description": "Alias for file_path. It may be absolute or relative to the current desktop scope." + }, + "content": { + "type": "string", + "description": "The full content to write to the file." + } + }, + "required": ["content"] + }), + }, + execute, + true, + ) + .with_aliases(&["write_file"]) +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let file_path = tool_input + .get("file_path") + .or_else(|| tool_input.get("path")) + .and_then(Value::as_str) + .ok_or_else(|| "Write requires file_path or path".to_string())?; + let content = tool_input + .get("content") + .and_then(Value::as_str) + .ok_or_else(|| "Write requires content".to_string())?; + let resolved_path = ctx.resolve_scoped_path(file_path, true)?; + if let Some(parent) = resolved_path.parent() { + std::fs::create_dir_all(parent).map_err(|error| { + format!( + "Failed to create parent directory {}: {}", + parent.display(), + error + ) + })?; + } + std::fs::write(&resolved_path, content) + .map_err(|error| format!("Failed to write {}: {}", resolved_path.display(), error))?; + Ok(format!("Wrote {}", resolved_path.display())) +} diff --git a/frontend/src-tauri/src/cowork/homepage/chat_sessions.rs b/frontend/src-tauri/src/cowork/homepage/chat_sessions.rs new file mode 100644 index 000000000..440cda676 --- /dev/null +++ b/frontend/src-tauri/src/cowork/homepage/chat_sessions.rs @@ -0,0 +1,311 @@ +use crate::cowork::chat::{ + CoworkChatRunStatus, CoworkChatScope, CoworkChatSessionDetail, CoworkChatSessionSummary, +}; +use chrono::{SecondsFormat, Utc}; +use std::{fs, path::PathBuf}; +use tauri::{AppHandle, Manager}; + +const STORE_DIR_NAME: &str = "cowork"; +const SESSION_STORE_DIR_NAME: &str = "chat-sessions"; + +#[tauri::command] +pub fn list_homepage_chat_sessions( + app: AppHandle, +) -> Result, String> { + let mut sessions = load_sessions(&app)?; + sort_sessions(&mut sessions); + + Ok(sessions.into_iter().map(build_summary).collect()) +} + +#[tauri::command] +pub fn get_homepage_chat_session( + app: AppHandle, + session_id: String, +) -> Result { + load_session(&app, &session_id) +} + +#[tauri::command] +pub fn create_homepage_chat_session( + app: AppHandle, + title: String, +) -> Result { + let normalized_title = title.trim(); + if normalized_title.is_empty() { + return Err("Session title is required".to_string()); + } + + let now = now_iso(); + let session = CoworkChatSessionDetail { + id: generate_session_id(), + scope: CoworkChatScope::Homepage, + title: normalized_title.to_string(), + preview: String::new(), + updated_at: now, + message_count: 0, + runtime_kind: None, + runtime_session_id: None, + messages: Vec::new(), + runtime_events: Vec::new(), + files: Vec::new(), + run_status: CoworkChatRunStatus::Idle, + }; + + write_session(&app, &session)?; + + Ok(session) +} + +#[tauri::command] +pub fn update_homepage_chat_session( + app: AppHandle, + session: CoworkChatSessionDetail, +) -> Result { + validate_session(&session)?; + + let mut normalized_session = CoworkChatSessionDetail { + message_count: session.messages.len(), + ..session + }; + normalized_session.normalize_runtime_binding(); + + write_session(&app, &normalized_session)?; + + Ok(normalized_session) +} + +#[tauri::command] +pub fn rename_homepage_chat_session( + app: AppHandle, + session_id: String, + title: String, +) -> Result { + let normalized_title = title.trim(); + if normalized_title.is_empty() { + return Err("Session title is required".to_string()); + } + + let mut session = load_session(&app, &session_id)?; + session.title = normalized_title.to_string(); + session.updated_at = now_iso(); + + write_session(&app, &session)?; + + Ok(session) +} + +#[tauri::command] +pub fn delete_homepage_chat_session(app: AppHandle, session_id: String) -> Result<(), String> { + delete_session(&app, &session_id) +} + +fn validate_session(session: &CoworkChatSessionDetail) -> Result<(), String> { + if session.scope != CoworkChatScope::Homepage { + return Err("Only homepage sessions can be persisted in chat-sessions".to_string()); + } + + Ok(()) +} + +fn build_summary(session: CoworkChatSessionDetail) -> CoworkChatSessionSummary { + let normalized_session = normalize_session(session); + + CoworkChatSessionSummary { + id: normalized_session.id, + scope: normalized_session.scope, + title: normalized_session.title, + preview: normalized_session.preview, + updated_at: normalized_session.updated_at, + message_count: normalized_session.message_count, + } +} + +fn generate_session_id() -> String { + format!("cowork-homepage-{}", Utc::now().timestamp_millis()) +} + +fn now_iso() -> String { + Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true) +} + +fn sort_sessions(sessions: &mut [CoworkChatSessionDetail]) { + sessions.sort_by(|left, right| { + right + .updated_at + .cmp(&left.updated_at) + .then_with(|| right.id.cmp(&left.id)) + }); +} + +fn load_sessions(app: &AppHandle) -> Result, String> { + let store_dir = session_store_dir_path(app)?; + let entries = fs::read_dir(&store_dir).map_err(|error| { + format!( + "Failed to read homepage chat sessions directory {}: {}", + store_dir.display(), + error + ) + })?; + let mut sessions = Vec::new(); + + for entry in entries { + let entry = entry.map_err(|error| { + format!( + "Failed to read an entry in homepage chat sessions directory {}: {}", + store_dir.display(), + error + ) + })?; + let path = entry.path(); + + if !path.is_file() || path.extension().and_then(|value| value.to_str()) != Some("json") { + continue; + } + + sessions.push(normalize_session(read_session_file(&path)?)); + } + + Ok(sessions) +} + +fn load_session(app: &AppHandle, session_id: &str) -> Result { + let session_file = session_file_path(app, session_id)?; + if !session_file.exists() { + return Err(format!( + "Homepage chat session not found: {}", + session_id.trim() + )); + } + + Ok(normalize_session(read_session_file(&session_file)?)) +} + +fn write_session(app: &AppHandle, session: &CoworkChatSessionDetail) -> Result<(), String> { + let session_file = session_file_path(app, &session.id)?; + let contents = serde_json::to_string_pretty(session).map_err(|error| { + format!( + "Failed to serialize homepage chat session {}: {}", + session.id, error + ) + })?; + + fs::write(&session_file, contents).map_err(|error| { + format!( + "Failed to write homepage chat session file {}: {}", + session_file.display(), + error + ) + }) +} + +fn delete_session(app: &AppHandle, session_id: &str) -> Result<(), String> { + let session_file = session_file_path(app, session_id)?; + if !session_file.exists() { + return Err(format!( + "Homepage chat session not found: {}", + session_id.trim() + )); + } + + fs::remove_file(&session_file).map_err(|error| { + format!( + "Failed to delete homepage chat session file {}: {}", + session_file.display(), + error + ) + }) +} + +fn read_session_file(path: &PathBuf) -> Result { + let contents = fs::read_to_string(path).map_err(|error| { + format!( + "Failed to read homepage chat session file {}: {}", + path.display(), + error + ) + })?; + + if contents.trim().is_empty() { + return Err(format!( + "Homepage chat session file is empty: {}", + path.display() + )); + } + + serde_json::from_str(&contents).map_err(|error| { + format!( + "Failed to parse homepage chat session file {}: {}", + path.display(), + error + ) + }) +} + +fn normalize_session(mut session: CoworkChatSessionDetail) -> CoworkChatSessionDetail { + session.normalize_runtime_binding(); + session +} + +fn normalize_session_id(session_id: &str) -> Result { + let trimmed = session_id.trim(); + if trimmed.is_empty() { + return Err("Homepage chat session id is required".to_string()); + } + + if trimmed == "." || trimmed == ".." { + return Err(format!("Invalid homepage chat session id: {}", trimmed)); + } + + if trimmed.chars().any(|character| { + character.is_control() + || matches!( + character, + '<' | '>' | ':' | '"' | '/' | '\\' | '|' | '?' | '*' + ) + }) { + return Err(format!("Invalid homepage chat session id: {}", trimmed)); + } + + Ok(trimmed.to_string()) +} + +fn store_root_dir_path(app: &AppHandle) -> Result { + let mut data_dir = app + .path() + .app_data_dir() + .map_err(|error| format!("Failed to resolve app data directory: {}", error))?; + + data_dir.push(STORE_DIR_NAME); + + fs::create_dir_all(&data_dir).map_err(|error| { + format!( + "Failed to create homepage chat session directory: {}", + error + ) + })?; + + Ok(data_dir) +} + +fn session_store_dir_path(app: &AppHandle) -> Result { + let mut store_root = store_root_dir_path(app)?; + store_root.push(SESSION_STORE_DIR_NAME); + + fs::create_dir_all(&store_root).map_err(|error| { + format!( + "Failed to create homepage chat sessions directory {}: {}", + store_root.display(), + error + ) + })?; + + Ok(store_root) +} + +fn session_file_path(app: &AppHandle, session_id: &str) -> Result { + let normalized_session_id = normalize_session_id(session_id)?; + let mut store_dir = session_store_dir_path(app)?; + store_dir.push(format!("{normalized_session_id}.json")); + Ok(store_dir) +} diff --git a/frontend/src-tauri/src/cowork/homepage/mod.rs b/frontend/src-tauri/src/cowork/homepage/mod.rs new file mode 100644 index 000000000..45b822db7 --- /dev/null +++ b/frontend/src-tauri/src/cowork/homepage/mod.rs @@ -0,0 +1 @@ +pub mod chat_sessions; diff --git a/frontend/src-tauri/src/cowork/mod.rs b/frontend/src-tauri/src/cowork/mod.rs new file mode 100644 index 000000000..2c39619a0 --- /dev/null +++ b/frontend/src-tauri/src/cowork/mod.rs @@ -0,0 +1,12 @@ +pub mod agent_presets; +pub mod agent_remote; +pub mod chat; +pub mod chat_commands; +pub mod desktop_skills; +pub mod desktop_tools; +pub mod homepage; +pub mod organize; +pub mod runtime; +pub mod session_gateway; +pub mod string_utils; +pub mod time_utils; diff --git a/frontend/src-tauri/src/cowork/organize/capabilities/mod.rs b/frontend/src-tauri/src/cowork/organize/capabilities/mod.rs new file mode 100644 index 000000000..2a38a2ff7 --- /dev/null +++ b/frontend/src-tauri/src/cowork/organize/capabilities/mod.rs @@ -0,0 +1,25 @@ +use crate::cowork::agent_presets::shared::{DesktopSkillCapability, DesktopToolCapability}; +use crate::cowork::desktop_skills::DesktopSkill; +use crate::cowork::desktop_tools::DesktopTool; + +pub fn desktop_tools() -> Vec { + Vec::new() +} + +pub fn desktop_tool_capabilities() -> Vec { + desktop_tools() + .into_iter() + .map(DesktopTool::into_capability) + .collect() +} + +pub fn desktop_runtime_skills() -> Vec { + Vec::new() +} + +pub fn desktop_skill_capabilities() -> Vec { + desktop_runtime_skills() + .into_iter() + .map(DesktopSkill::into_capability) + .collect() +} diff --git a/frontend/src-tauri/src/cowork/organize/chat_prompt.rs b/frontend/src-tauri/src/cowork/organize/chat_prompt.rs new file mode 100644 index 000000000..d45394f40 --- /dev/null +++ b/frontend/src-tauri/src/cowork/organize/chat_prompt.rs @@ -0,0 +1,100 @@ +use crate::cowork::organize::sessions::CoworkChatSessionDetail; + +pub fn build_organize_prompt_context(session: &CoworkChatSessionDetail) -> String { + format!( + "You are assisting in II Cowork organize-file-folder mode.\n\ +You are working on the user's real local desktop folder.\n\ +Your job is to inspect, understand, clean up, and reorganize files inside that folder based on the user's request.\n\n\ +[Operating rules]\n\ +- Work only inside the selected local folder.\n\ +- Understand the current structure before changing it.\n\ +- Always read relevant files before modifying them when the decision depends on file content, meaning, or purpose.\n\ +- Do not reorganize semantic content based only on filenames when content inspection is needed.\n\ +- For purely structural tasks such as grouping by extension, renaming obvious folders, or moving generated files, you may act from directory structure alone when that is sufficient.\n\ +- Keep changes scoped, intentional, and easy to explain.\n\ +- Preserve user content unless the request clearly asks for renaming, regrouping, cleanup, or rewrites.\n\ +- If no file changes are needed, explain that clearly instead of forcing edits.\n\ +- When you do make changes, summarize the affected paths and the reason for each group of changes.\n\n\ +[Recommended approach]\n\ +1. Start with `list_dir` to inspect the current folder layout.\n\ +2. Use `glob` and `grep` to narrow down relevant files.\n\ +3. Use `Read` on files that matter before making content-aware decisions.\n\ +4. Form a short plan.\n\ +5. Make only the necessary changes.\n\ +6. Summarize what changed and why.\n\n\ +[Desktop tools available]\n\ +- `list_dir` - List directories and files inside the selected folder\n\ +- `glob` - Find files by path patterns or extensions\n\ +- `grep` - Search file contents by text pattern\n\ +- `Read` - Read local text files\n\ +- `Write` - Create or overwrite local files\n\ +- `Edit` - Make exact text replacements in local files\n\ +- `apply_patch` - Apply structured multi-file edits\n\ +- `Bash` - Execute shell commands inside the selected folder\n\ +- `TodoWrite` - Keep a short task checklist during the run\n\n\ +[Local organize scope and context]\n\ +Mode scope: organize-file-folder\n\ +Input folder path: {}\n", + session.organize_tree_pair.source_root, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cowork::organize::file_tree::{FileTreeNode, FileTreeNodeKind}; + + fn sample_folder(name: &str) -> FileTreeNode { + FileTreeNode { + id: format!("folder::{name}"), + name: name.to_string(), + kind: FileTreeNodeKind::Folder, + extension: None, + size: None, + children: Some(Vec::new()), + } + } + + fn sample_session() -> CoworkChatSessionDetail { + CoworkChatSessionDetail { + base: crate::cowork::chat::CoworkChatSessionDetail { + id: "cowork-organize-1".to_string(), + scope: crate::cowork::chat::CoworkChatScope::OrganizeFileFolder, + title: "demo".to_string(), + preview: "demo".to_string(), + updated_at: "2026-04-04T00:00:00.000Z".to_string(), + message_count: 0, + runtime_kind: None, + runtime_session_id: None, + messages: Vec::new(), + runtime_events: Vec::new(), + files: Vec::new(), + run_status: crate::cowork::chat::CoworkChatRunStatus::Idle, + }, + organize_tree_pair: crate::cowork::organize::sessions::CoworkOrganizeTreePair { + source_root: "C:/demo".to_string(), + result_root: "C:/demo".to_string(), + source_tree: sample_folder("demo"), + result_tree: None, + }, + } + } + + #[test] + fn build_organize_prompt_context_includes_scope_and_tool_guidance() { + let prompt = build_organize_prompt_context(&sample_session()); + + assert!(prompt.contains("[Operating rules]")); + assert!(prompt.contains("[Recommended approach]")); + assert!(prompt.contains("[Desktop tools available]")); + assert!(prompt.contains("Input folder path: C:/demo")); + assert!(prompt.contains("Always read relevant files before modifying them")); + assert!(prompt.contains("Start with `list_dir` to inspect the current folder layout")); + assert!( + prompt.contains("- `list_dir` - List directories and files inside the selected folder") + ); + assert!(prompt.contains("- `Read` - Read local text files")); + assert!(prompt.contains("- `Edit` - Make exact text replacements in local files")); + assert!(prompt.contains("- `Bash` - Execute shell commands inside the selected folder")); + } +} diff --git a/frontend/src-tauri/src/cowork/organize/file_tree.rs b/frontend/src-tauri/src/cowork/organize/file_tree.rs new file mode 100644 index 000000000..04f81aed8 --- /dev/null +++ b/frontend/src-tauri/src/cowork/organize/file_tree.rs @@ -0,0 +1,296 @@ +use serde::{Deserialize, Serialize}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +const DEFAULT_MAX_DEPTH: usize = 10; +const DEFAULT_MAX_ENTRIES: usize = 10_000; + +#[derive(Debug, Deserialize)] +pub struct ReadPathTreeOptions { + pub max_depth: Option, + pub max_entries: Option, + pub include_hidden: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum FileTreeNodeKind { + Folder, + File, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct FileTreeNode { + pub id: String, + pub name: String, + pub kind: FileTreeNodeKind, + #[serde(skip_serializing_if = "Option::is_none")] + pub extension: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option>, +} + +struct TraversalState { + max_depth: usize, + remaining_entries: usize, + max_entries: usize, + include_hidden: bool, +} + +impl TraversalState { + fn new(options: Option) -> Self { + let options = options.unwrap_or(ReadPathTreeOptions { + max_depth: None, + max_entries: None, + include_hidden: None, + }); + + let max_depth = options.max_depth.unwrap_or(DEFAULT_MAX_DEPTH); + let max_entries = options.max_entries.unwrap_or(DEFAULT_MAX_ENTRIES).max(1); + + Self { + max_depth, + remaining_entries: max_entries, + max_entries, + include_hidden: options.include_hidden.unwrap_or(false), + } + } + + fn claim_entry(&mut self) -> Result<(), String> { + if self.remaining_entries == 0 { + return Err(format!( + "Path tree exceeded the entry limit ({}) before traversal finished", + self.max_entries + )); + } + + self.remaining_entries -= 1; + Ok(()) + } +} + +#[tauri::command] +pub fn read_path_tree( + path: String, + options: Option, +) -> Result { + let trimmed = path.trim(); + if trimmed.is_empty() { + return Err("Path is required".to_string()); + } + + let root_path = PathBuf::from(trimmed); + if !root_path.exists() { + return Err(format!("Path does not exist: {}", trimmed)); + } + + let metadata = fs::symlink_metadata(&root_path) + .map_err(|error| format!("Failed to read metadata for {}: {}", trimmed, error))?; + let mut state = TraversalState::new(options); + + build_node(&root_path, metadata, 0, &mut state) +} + +fn build_node( + path: &Path, + metadata: fs::Metadata, + depth: usize, + state: &mut TraversalState, +) -> Result { + state.claim_entry()?; + + let is_symlink = metadata.file_type().is_symlink(); + let is_dir = metadata.is_dir() && !is_symlink; + let name = node_name(path); + let id = normalize_path(path); + + if !is_dir { + return Ok(FileTreeNode { + id, + name, + kind: FileTreeNodeKind::File, + extension: file_extension(path), + size: Some(format_bytes(metadata.len())), + children: None, + }); + } + + if depth >= state.max_depth { + return Ok(FileTreeNode { + id, + name, + kind: FileTreeNodeKind::Folder, + extension: None, + size: None, + children: Some(Vec::new()), + }); + } + + let mut children = Vec::new(); + let mut entries = fs::read_dir(path) + .map_err(|error| format!("Failed to read directory {}: {}", path.display(), error))? + .filter_map(|entry| entry.ok()) + .filter(|entry| state.include_hidden || !is_hidden_entry(entry)) + .filter_map(|entry| { + let child_path = entry.path(); + let metadata = fs::symlink_metadata(&child_path).ok()?; + Some((child_path, metadata)) + }) + .collect::>(); + + entries.sort_by(|(left_path, left_metadata), (right_path, right_metadata)| { + let left_is_dir = left_metadata.is_dir() && !left_metadata.file_type().is_symlink(); + let right_is_dir = right_metadata.is_dir() && !right_metadata.file_type().is_symlink(); + + right_is_dir + .cmp(&left_is_dir) + .then_with(|| { + node_name(left_path) + .to_lowercase() + .cmp(&node_name(right_path).to_lowercase()) + }) + .then_with(|| node_name(left_path).cmp(&node_name(right_path))) + }); + + for (child_path, child_metadata) in entries { + let child_node = build_node(&child_path, child_metadata, depth + 1, state)?; + children.push(child_node); + } + + Ok(FileTreeNode { + id, + name, + kind: FileTreeNodeKind::Folder, + extension: None, + size: None, + children: Some(children), + }) +} + +fn node_name(path: &Path) -> String { + path.file_name() + .map(|value| value.to_string_lossy().to_string()) + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| normalize_path(path)) +} + +fn normalize_path(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} + +fn file_extension(path: &Path) -> Option { + path.extension() + .and_then(|value| value.to_str()) + .map(|value| value.to_lowercase()) + .filter(|value| !value.is_empty()) +} + +fn is_hidden_entry(entry: &fs::DirEntry) -> bool { + let is_dot_file = entry + .file_name() + .to_str() + .map(|name| name.starts_with('.')) + .unwrap_or(false); + + if is_dot_file { + return true; + } + + #[cfg(target_os = "windows")] + { + use std::os::windows::fs::MetadataExt; + + const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2; + + if let Ok(metadata) = entry.metadata() { + return metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN != 0; + } + } + + false +} + +fn format_bytes(bytes: u64) -> String { + const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"]; + + if bytes < 1024 { + return format!("{} B", bytes); + } + + let mut value = bytes as f64; + let mut unit_index = 0usize; + + while value >= 1024.0 && unit_index < UNITS.len() - 1 { + value /= 1024.0; + unit_index += 1; + } + + if value >= 100.0 || value.fract() < 0.05 { + format!("{:.0} {}", value, UNITS[unit_index]) + } else { + format!("{:.1} {}", value, UNITS[unit_index]) + } +} + +#[cfg(test)] +mod tests { + use super::{build_node, format_bytes, FileTreeNodeKind, TraversalState}; + use std::{ + fs, + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, + }; + + fn temp_test_dir() -> PathBuf { + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after unix epoch") + .as_nanos(); + + std::env::temp_dir().join(format!("ii-agent-tauri-tree-{unique}")) + } + + #[test] + fn formats_bytes_for_ui() { + assert_eq!(format_bytes(512), "512 B"); + assert_eq!(format_bytes(1_536), "1.5 KB"); + assert_eq!(format_bytes(104_857_600), "100 MB"); + } + + #[test] + fn builds_directory_tree() { + let root = temp_test_dir(); + let nested = root.join("nested"); + let file_path = nested.join("demo.txt"); + + fs::create_dir_all(&nested).expect("should create test directories"); + fs::write(&file_path, b"hello world").expect("should create test file"); + + let metadata = fs::symlink_metadata(&root).expect("should read root metadata"); + let mut state = TraversalState::new(None); + let tree = build_node(&root, metadata, 0, &mut state).expect("should build tree"); + + assert_eq!(tree.kind, FileTreeNodeKind::Folder); + assert_eq!(tree.name, root.to_string_lossy().replace('\\', "/")); + + let children = tree.children.expect("folder should include children"); + assert_eq!(children.len(), 1); + assert_eq!(children[0].name, "nested"); + assert_eq!(children[0].kind, FileTreeNodeKind::Folder); + + let nested_children = children[0] + .children + .clone() + .expect("nested folder should include children"); + assert_eq!(nested_children.len(), 1); + assert_eq!(nested_children[0].name, "demo.txt"); + assert_eq!(nested_children[0].kind, FileTreeNodeKind::File); + assert_eq!(nested_children[0].extension.as_deref(), Some("txt")); + + fs::remove_dir_all(&root).expect("should clean up test directory"); + } +} diff --git a/frontend/src-tauri/src/cowork/organize/mod.rs b/frontend/src-tauri/src/cowork/organize/mod.rs new file mode 100644 index 000000000..26ff8d49b --- /dev/null +++ b/frontend/src-tauri/src/cowork/organize/mod.rs @@ -0,0 +1,4 @@ +pub mod capabilities; +pub mod chat_prompt; +pub mod file_tree; +pub mod sessions; diff --git a/frontend/src-tauri/src/cowork/organize/sessions.rs b/frontend/src-tauri/src/cowork/organize/sessions.rs new file mode 100644 index 000000000..8e55e6359 --- /dev/null +++ b/frontend/src-tauri/src/cowork/organize/sessions.rs @@ -0,0 +1,475 @@ +pub use crate::cowork::chat::{CoworkChatRunStatus, CoworkChatScope, CoworkChatSessionSummary}; + +use crate::cowork::chat::CoworkChatSessionDetail as BaseCoworkChatSessionDetail; +use crate::cowork::organize::file_tree::{self, FileTreeNode, FileTreeNodeKind}; +use chrono::{SecondsFormat, Utc}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::{ + fs, + ops::{Deref, DerefMut}, + path::PathBuf, +}; +use tauri::{AppHandle, Manager}; + +const STORE_DIR_NAME: &str = "cowork"; +const SESSION_STORE_DIR_NAME: &str = "organize-sessions"; +const LEGACY_STORE_FILE_NAME: &str = "organize-sessions.json"; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CoworkOrganizeTreePair { + pub source_root: String, + pub result_root: String, + pub source_tree: FileTreeNode, + pub result_tree: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct CoworkChatSessionDetail { + #[serde(flatten)] + pub base: BaseCoworkChatSessionDetail, + pub organize_tree_pair: CoworkOrganizeTreePair, +} + +impl Deref for CoworkChatSessionDetail { + type Target = BaseCoworkChatSessionDetail; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl DerefMut for CoworkChatSessionDetail { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + +#[derive(Debug, Serialize, Deserialize, Default)] +struct OrganizeSessionStore { + sessions: Vec, +} + +#[tauri::command] +pub fn list_organize_sessions(app: AppHandle) -> Result, String> { + let mut sessions = load_sessions(&app)?; + sort_sessions(&mut sessions); + + Ok(sessions.into_iter().map(build_summary).collect()) +} + +#[tauri::command] +pub fn get_organize_session( + app: AppHandle, + session_id: String, +) -> Result { + load_session(&app, &session_id) +} + +#[tauri::command] +pub fn create_organize_session( + app: AppHandle, + tree_pair: CoworkOrganizeTreePair, +) -> Result { + validate_tree_pair(&tree_pair)?; + + let now = now_iso(); + let session = CoworkChatSessionDetail { + base: BaseCoworkChatSessionDetail { + id: generate_session_id(), + scope: CoworkChatScope::OrganizeFileFolder, + title: build_session_title(&tree_pair.source_root), + preview: build_session_preview(&tree_pair.source_root), + updated_at: now, + message_count: 0, + runtime_kind: None, + runtime_session_id: None, + messages: Vec::new(), + runtime_events: Vec::new(), + files: Vec::new(), + run_status: CoworkChatRunStatus::Idle, + }, + organize_tree_pair: tree_pair, + }; + + write_session(&app, &session)?; + + Ok(session) +} + +#[tauri::command] +pub fn update_organize_session( + app: AppHandle, + session: CoworkChatSessionDetail, +) -> Result { + validate_session(&session)?; + + let normalized_session = normalize_session(session); + + write_session(&app, &normalized_session)?; + + Ok(normalized_session) +} + +#[tauri::command] +pub fn rename_organize_session( + app: AppHandle, + session_id: String, + title: String, +) -> Result { + let normalized_title = title.trim(); + if normalized_title.is_empty() { + return Err("Session title is required".to_string()); + } + + let mut session = load_session(&app, &session_id)?; + + session.title = normalized_title.to_string(); + session.updated_at = now_iso(); + + write_session(&app, &session)?; + + Ok(session) +} + +#[tauri::command] +pub fn delete_organize_session(app: AppHandle, session_id: String) -> Result<(), String> { + delete_session(&app, &session_id) +} + +pub fn sync_result_tree_from_disk(session: &mut CoworkChatSessionDetail) -> Result<(), String> { + let latest_tree = + file_tree::read_path_tree(session.organize_tree_pair.source_root.clone(), None)?; + let source_hash = hash_tree(&session.organize_tree_pair.source_tree)?; + let latest_hash = hash_tree(&latest_tree)?; + + session.organize_tree_pair.result_root = session.organize_tree_pair.source_root.clone(); + session.organize_tree_pair.result_tree = if source_hash == latest_hash { + None + } else { + Some(latest_tree) + }; + + Ok(()) +} + +fn validate_session(session: &CoworkChatSessionDetail) -> Result<(), String> { + if session.scope != CoworkChatScope::OrganizeFileFolder { + return Err("Only organize-file-folder sessions can be persisted locally".to_string()); + } + + validate_tree_pair(&session.organize_tree_pair) +} + +fn hash_tree(tree: &FileTreeNode) -> Result { + let serialized = serde_json::to_vec(tree) + .map_err(|error| format!("Failed to serialize organize tree for hashing: {}", error))?; + let digest = Sha256::digest(serialized); + Ok(digest.iter().map(|value| format!("{value:02x}")).collect()) +} + +fn validate_tree_pair(tree_pair: &CoworkOrganizeTreePair) -> Result<(), String> { + if tree_pair.source_root.trim().is_empty() { + return Err("Organize session source_root is required".to_string()); + } + + if tree_pair.result_root.trim().is_empty() { + return Err("Organize session result_root is required".to_string()); + } + + if tree_pair.source_tree.kind != FileTreeNodeKind::Folder { + return Err("Organize session source_tree root must be a folder".to_string()); + } + + if let Some(result_tree) = &tree_pair.result_tree { + if result_tree.kind != FileTreeNodeKind::Folder { + return Err("Organize session result_tree root must be a folder".to_string()); + } + } + + Ok(()) +} + +fn build_summary(session: CoworkChatSessionDetail) -> CoworkChatSessionSummary { + let normalized_session = normalize_session(session); + + CoworkChatSessionSummary { + id: normalized_session.base.id, + scope: normalized_session.base.scope, + title: normalized_session.base.title, + preview: normalized_session.base.preview, + updated_at: normalized_session.base.updated_at, + message_count: normalized_session.base.message_count, + } +} + +fn build_session_title(source_root: &str) -> String { + let trimmed = source_root.trim().trim_end_matches(['\\', '/']); + let folder_name = trimmed + .rsplit(['\\', '/']) + .find(|segment| !segment.is_empty()) + .unwrap_or(trimmed); + + if folder_name.is_empty() { + "Organize session".to_string() + } else { + folder_name.to_string() + } +} + +fn build_session_preview(source_root: &str) -> String { + format!("Source: {}", build_session_title(source_root)) +} + +fn generate_session_id() -> String { + format!("cowork-organize-{}", Utc::now().timestamp_millis()) +} + +fn now_iso() -> String { + Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true) +} + +fn normalize_session(mut session: CoworkChatSessionDetail) -> CoworkChatSessionDetail { + session.base.normalize_runtime_binding(); + session.base.message_count = session.base.messages.len(); + session.base.preview = build_session_preview(&session.organize_tree_pair.source_root); + if session.base.messages.is_empty() + && session.base.runtime_events.is_empty() + && session.base.runtime_session_id.is_none() + && session.base.run_status == CoworkChatRunStatus::Completed + { + session.base.run_status = CoworkChatRunStatus::Idle; + } + + session +} + +fn sort_sessions(sessions: &mut [CoworkChatSessionDetail]) { + sessions.sort_by(|left, right| { + right + .updated_at + .cmp(&left.updated_at) + .then_with(|| right.id.cmp(&left.id)) + }); +} + +fn load_sessions(app: &AppHandle) -> Result, String> { + migrate_legacy_store(app)?; + + let store_dir = session_store_dir_path(app)?; + let entries = fs::read_dir(&store_dir).map_err(|error| { + format!( + "Failed to read organize sessions directory {}: {}", + store_dir.display(), + error + ) + })?; + let mut sessions = Vec::new(); + + for entry in entries { + let entry = entry.map_err(|error| { + format!( + "Failed to read an entry in organize sessions directory {}: {}", + store_dir.display(), + error + ) + })?; + let path = entry.path(); + + if !path.is_file() || path.extension().and_then(|value| value.to_str()) != Some("json") { + continue; + } + + sessions.push(normalize_session(read_session_file(&path)?)); + } + + Ok(sessions) +} + +fn load_session(app: &AppHandle, session_id: &str) -> Result { + migrate_legacy_store(app)?; + + let session_file = session_file_path(app, session_id)?; + if !session_file.exists() { + return Err(format!("Organize session not found: {}", session_id.trim())); + } + + Ok(normalize_session(read_session_file(&session_file)?)) +} + +fn write_session(app: &AppHandle, session: &CoworkChatSessionDetail) -> Result<(), String> { + migrate_legacy_store(app)?; + + let session_file = session_file_path(app, &session.id)?; + let contents = serde_json::to_string_pretty(session).map_err(|error| { + format!( + "Failed to serialize organize session {}: {}", + session.id, error + ) + })?; + + fs::write(&session_file, contents).map_err(|error| { + format!( + "Failed to write organize session file {}: {}", + session_file.display(), + error + ) + }) +} + +fn delete_session(app: &AppHandle, session_id: &str) -> Result<(), String> { + migrate_legacy_store(app)?; + + let session_file = session_file_path(app, session_id)?; + if !session_file.exists() { + return Err(format!("Organize session not found: {}", session_id.trim())); + } + + fs::remove_file(&session_file).map_err(|error| { + format!( + "Failed to delete organize session file {}: {}", + session_file.display(), + error + ) + }) +} + +fn read_session_file(path: &PathBuf) -> Result { + let contents = fs::read_to_string(path).map_err(|error| { + format!( + "Failed to read organize session file {}: {}", + path.display(), + error + ) + })?; + + if contents.trim().is_empty() { + return Err(format!( + "Organize session file is empty: {}", + path.display() + )); + } + + serde_json::from_str(&contents).map_err(|error| { + format!( + "Failed to parse organize session file {}: {}", + path.display(), + error + ) + }) +} + +fn migrate_legacy_store(app: &AppHandle) -> Result<(), String> { + let legacy_store_file = legacy_store_file_path(app)?; + if !legacy_store_file.exists() { + return Ok(()); + } + + let contents = fs::read_to_string(&legacy_store_file) + .map_err(|error| format!("Failed to read organize session store: {}", error))?; + + if contents.trim().is_empty() { + fs::remove_file(&legacy_store_file).map_err(|error| { + format!( + "Failed to remove empty legacy organize session store {}: {}", + legacy_store_file.display(), + error + ) + })?; + return Ok(()); + } + + let legacy_store: OrganizeSessionStore = serde_json::from_str(&contents) + .map_err(|error| format!("Failed to parse organize session store: {}", error))?; + + for session in legacy_store.sessions { + let session_file = session_file_path(app, &session.id)?; + let session_contents = serde_json::to_string_pretty(&session).map_err(|error| { + format!( + "Failed to serialize migrated organize session {}: {}", + session.id, error + ) + })?; + + fs::write(&session_file, session_contents).map_err(|error| { + format!( + "Failed to write migrated organize session file {}: {}", + session_file.display(), + error + ) + })?; + } + + fs::remove_file(&legacy_store_file).map_err(|error| { + format!( + "Failed to remove legacy organize session store {}: {}", + legacy_store_file.display(), + error + ) + }) +} + +fn normalize_session_id(session_id: &str) -> Result { + let trimmed = session_id.trim(); + if trimmed.is_empty() { + return Err("Organize session id is required".to_string()); + } + + if trimmed == "." || trimmed == ".." { + return Err(format!("Invalid organize session id: {}", trimmed)); + } + + if trimmed.chars().any(|character| { + character.is_control() + || matches!( + character, + '<' | '>' | ':' | '"' | '/' | '\\' | '|' | '?' | '*' + ) + }) { + return Err(format!("Invalid organize session id: {}", trimmed)); + } + + Ok(trimmed.to_string()) +} + +fn store_root_dir_path(app: &AppHandle) -> Result { + let mut data_dir = app + .path() + .app_data_dir() + .map_err(|error| format!("Failed to resolve app data directory: {}", error))?; + + data_dir.push(STORE_DIR_NAME); + + fs::create_dir_all(&data_dir) + .map_err(|error| format!("Failed to create organize session directory: {}", error))?; + + Ok(data_dir) +} + +fn session_store_dir_path(app: &AppHandle) -> Result { + let mut store_root = store_root_dir_path(app)?; + store_root.push(SESSION_STORE_DIR_NAME); + + fs::create_dir_all(&store_root).map_err(|error| { + format!( + "Failed to create organize sessions directory {}: {}", + store_root.display(), + error + ) + })?; + + Ok(store_root) +} + +fn legacy_store_file_path(app: &AppHandle) -> Result { + let mut store_root = store_root_dir_path(app)?; + store_root.push(LEGACY_STORE_FILE_NAME); + Ok(store_root) +} + +fn session_file_path(app: &AppHandle, session_id: &str) -> Result { + let normalized_session_id = normalize_session_id(session_id)?; + let mut store_dir = session_store_dir_path(app)?; + store_dir.push(format!("{normalized_session_id}.json")); + Ok(store_dir) +} diff --git a/frontend/src-tauri/src/cowork/runtime.rs b/frontend/src-tauri/src/cowork/runtime.rs new file mode 100644 index 000000000..fc0550e01 --- /dev/null +++ b/frontend/src-tauri/src/cowork/runtime.rs @@ -0,0 +1,32 @@ +use crate::cowork::agent_remote::{auth::RemoteAuthState, service as remote_service}; +use crate::cowork::chat::{ + CoworkAgentRuntimeKind, CoworkChatFile, CoworkChatMessage, CoworkChatRunStatus, + CoworkChatRuntimeEvent, CoworkChatSendMessageRequest, CoworkChatSendMessageResponse, +}; +use tauri::{AppHandle, State}; + +pub struct CoworkRuntimeSessionSnapshot { + pub runtime_kind: CoworkAgentRuntimeKind, + pub runtime_session_id: String, + pub updated_at: String, + pub messages: Vec, + pub files: Vec, + pub run_status: CoworkChatRunStatus, + pub runtime_events: Vec, +} + +#[tauri::command] +pub async fn send_cowork_chat_message( + app: AppHandle, + remote_auth_state: State<'_, RemoteAuthState>, + request: CoworkChatSendMessageRequest, +) -> Result { + match request.requested_runtime_kind() { + CoworkAgentRuntimeKind::Remote => { + remote_service::send_remote_chat_message(app, remote_auth_state, request).await + } + CoworkAgentRuntimeKind::Local => { + Err("Cowork local runtime is not implemented yet.".to_string()) + } + } +} diff --git a/frontend/src-tauri/src/cowork/session_gateway.rs b/frontend/src-tauri/src/cowork/session_gateway.rs new file mode 100644 index 000000000..6cbde365e --- /dev/null +++ b/frontend/src-tauri/src/cowork/session_gateway.rs @@ -0,0 +1,535 @@ +use crate::cowork::chat::{ + emit_cowork_stream_event, CoworkChatEvent, CoworkChatFile, CoworkChatFilesEvent, + CoworkChatMessage, CoworkChatMessageEvent, CoworkChatMessageRole, CoworkChatRunStatus, + CoworkChatRuntimeEvent, CoworkChatScope, CoworkChatSendMessageRequest, + CoworkChatSendMessageResponse, CoworkChatSessionDetail, CoworkChatSessionEvent, + CoworkChatSessionSummary, CoworkChatStatusEvent, +}; +use crate::cowork::homepage::chat_sessions as homepage_sessions; +use crate::cowork::organize::chat_prompt as organize_chat_prompt; +use crate::cowork::organize::sessions as organize_sessions; +use crate::cowork::runtime::CoworkRuntimeSessionSnapshot; +use crate::cowork::time_utils::{generate_message_id, now_iso}; +use tauri::AppHandle; + +#[derive(Clone)] +pub enum LocalCoworkSession { + Homepage(CoworkChatSessionDetail), + Organize(organize_sessions::CoworkChatSessionDetail), +} + +impl LocalCoworkSession { + pub fn base(&self) -> &CoworkChatSessionDetail { + match self { + Self::Homepage(session) => session, + Self::Organize(session) => &session.base, + } + } + + pub fn base_mut(&mut self) -> &mut CoworkChatSessionDetail { + match self { + Self::Homepage(session) => session, + Self::Organize(session) => &mut session.base, + } + } +} + +trait FeatureSessionStore { + fn load(&self, app: &AppHandle, session_id: &str) -> Result; + + fn load_or_create( + &self, + app: &AppHandle, + request: &CoworkChatSendMessageRequest, + ) -> Result<(LocalCoworkSession, bool), String>; + + fn save( + &self, + app: &AppHandle, + session: LocalCoworkSession, + ) -> Result; +} + +struct HomepageSessionStore; +struct OrganizeSessionStore; + +static HOMEPAGE_SESSION_STORE: HomepageSessionStore = HomepageSessionStore; +static ORGANIZE_SESSION_STORE: OrganizeSessionStore = OrganizeSessionStore; + +impl FeatureSessionStore for HomepageSessionStore { + fn load(&self, app: &AppHandle, session_id: &str) -> Result { + homepage_sessions::get_homepage_chat_session(app.clone(), session_id.to_string()) + .map(LocalCoworkSession::Homepage) + } + + fn load_or_create( + &self, + app: &AppHandle, + request: &CoworkChatSendMessageRequest, + ) -> Result<(LocalCoworkSession, bool), String> { + if let Some(session_id) = request.session_id.as_ref() { + let session = + homepage_sessions::get_homepage_chat_session(app.clone(), session_id.clone())?; + Ok((LocalCoworkSession::Homepage(session), false)) + } else { + let title = build_homepage_session_title(&request.content); + let session = homepage_sessions::create_homepage_chat_session(app.clone(), title)?; + Ok((LocalCoworkSession::Homepage(session), true)) + } + } + + fn save( + &self, + app: &AppHandle, + session: LocalCoworkSession, + ) -> Result { + let LocalCoworkSession::Homepage(detail) = session else { + return Err("Homepage session store received a non-homepage session".to_string()); + }; + + homepage_sessions::update_homepage_chat_session(app.clone(), detail) + .map(LocalCoworkSession::Homepage) + } +} + +impl FeatureSessionStore for OrganizeSessionStore { + fn load(&self, app: &AppHandle, session_id: &str) -> Result { + organize_sessions::get_organize_session(app.clone(), session_id.to_string()) + .map(LocalCoworkSession::Organize) + } + + fn load_or_create( + &self, + app: &AppHandle, + request: &CoworkChatSendMessageRequest, + ) -> Result<(LocalCoworkSession, bool), String> { + let session_id = request + .session_id + .as_ref() + .ok_or_else(|| "Organize cowork session_id is required".to_string())?; + let session = organize_sessions::get_organize_session(app.clone(), session_id.clone())?; + Ok((LocalCoworkSession::Organize(session), false)) + } + + fn save( + &self, + app: &AppHandle, + session: LocalCoworkSession, + ) -> Result { + let LocalCoworkSession::Organize(detail) = session else { + return Err("Organize session store received a non-organize session".to_string()); + }; + + organize_sessions::update_organize_session(app.clone(), detail) + .map(LocalCoworkSession::Organize) + } +} + +pub fn load_or_create_local_session( + app: &AppHandle, + request: &CoworkChatSendMessageRequest, +) -> Result<(LocalCoworkSession, bool), String> { + let (mut session, is_new) = + resolve_store_for_scope(request.scope).load_or_create(app, request)?; + normalize_local_session_runtime(&mut session); + Ok((session, is_new)) +} + +pub fn persist_local_session( + app: &AppHandle, + mut session: LocalCoworkSession, +) -> Result { + normalize_local_session_runtime(&mut session); + let mut persisted = resolve_store_for_session(&session).save(app, session)?; + normalize_local_session_runtime(&mut persisted); + Ok(persisted) +} + +pub fn load_local_session( + app: &AppHandle, + scope: CoworkChatScope, + session_id: &str, +) -> Result { + let mut session = resolve_store_for_scope(scope).load(app, session_id)?; + normalize_local_session_runtime(&mut session); + Ok(session) +} + +pub fn build_send_response( + session: &LocalCoworkSession, + include_session_created: bool, +) -> CoworkChatSendMessageResponse { + let base = session.base(); + let mut events = Vec::new(); + + if include_session_created { + events.push(build_session_created_event(base)); + } + + events.push(build_session_updated_event(base)); + events.push(build_status_updated_event( + base.scope, + base.id.clone(), + base.run_status, + )); + + CoworkChatSendMessageResponse { + session_id: base.id.clone(), + events, + } +} + +pub fn persist_runtime_error( + app: &AppHandle, + mut session: LocalCoworkSession, + error_message: String, +) -> Result { + let base = session.base_mut(); + + base.messages.push(build_local_message( + CoworkChatMessageRole::Assistant, + error_message, + false, + None, + )); + base.updated_at = now_iso(); + base.preview = build_preview_from_messages(&base.messages); + base.message_count = base.messages.len(); + base.run_status = CoworkChatRunStatus::Stopped; + + sync_organize_result_tree(&mut session)?; + persist_local_session(app, session) +} + +pub fn persist_stream_status( + app: &AppHandle, + scope: CoworkChatScope, + session_id: &str, + status: CoworkChatRunStatus, +) -> Result<(), String> { + let mut session = load_local_session(app, scope, session_id)?; + let base = session.base_mut(); + + base.run_status = status; + base.updated_at = now_iso(); + persist_local_session(app, session).map(|_| ()) +} + +pub fn persist_stream_runtime_event( + app: &AppHandle, + event: &CoworkChatRuntimeEvent, + status: Option, +) -> Result<(), String> { + let mut session = load_local_session(app, event.scope, &event.session_id)?; + let base = session.base_mut(); + + if !base + .runtime_events + .iter() + .any(|current| is_same_runtime_event(current, event)) + { + base.runtime_events.push(event.clone()); + sort_runtime_events(&mut base.runtime_events); + } + + base.updated_at = event.emitted_at.clone(); + if let Some(next_status) = status { + base.run_status = next_status; + } + + persist_local_session(app, session).map(|_| ()) +} + +pub fn apply_runtime_session_snapshot( + session: &mut LocalCoworkSession, + runtime_snapshot: CoworkRuntimeSessionSnapshot, +) { + let base = session.base_mut(); + + base.runtime_kind = Some(runtime_snapshot.runtime_kind); + base.runtime_session_id = Some(runtime_snapshot.runtime_session_id); + base.messages = runtime_snapshot.messages; + base.message_count = base.messages.len(); + base.files = runtime_snapshot.files; + base.runtime_events = + merge_runtime_events(base.runtime_events.clone(), runtime_snapshot.runtime_events); + base.updated_at = runtime_snapshot.updated_at; + base.preview = build_preview_from_messages(&base.messages); + base.run_status = runtime_snapshot.run_status; +} + +pub fn clear_runtime_binding(session: &mut LocalCoworkSession) { + let base = session.base_mut(); + base.runtime_kind = None; + base.runtime_session_id = None; +} + +pub fn sync_organize_result_tree(session: &mut LocalCoworkSession) -> Result<(), String> { + match session { + LocalCoworkSession::Homepage(_) => Ok(()), + LocalCoworkSession::Organize(detail) => { + organize_sessions::sync_result_tree_from_disk(detail) + } + } +} + +pub fn resolve_prompt_context( + session: &LocalCoworkSession, + explicit_prompt_context: Option, +) -> Option { + explicit_prompt_context.or_else(|| match session { + LocalCoworkSession::Homepage(_) => None, + LocalCoworkSession::Organize(detail) => { + Some(organize_chat_prompt::build_organize_prompt_context(detail)) + } + }) +} + +pub fn build_local_message( + role: CoworkChatMessageRole, + content: String, + is_think_message: bool, + created_at: Option, +) -> CoworkChatMessage { + CoworkChatMessage { + id: generate_message_id(), + role, + content, + created_at: created_at.unwrap_or_else(now_iso), + is_think_message: is_think_message.then_some(true), + } +} + +pub fn build_session_created_event(session: &CoworkChatSessionDetail) -> CoworkChatEvent { + CoworkChatEvent::Session(CoworkChatSessionEvent { + event_type: "session.created".to_string(), + session: build_summary(session), + }) +} + +pub fn build_session_updated_event(session: &CoworkChatSessionDetail) -> CoworkChatEvent { + CoworkChatEvent::Session(CoworkChatSessionEvent { + event_type: "session.updated".to_string(), + session: build_summary(session), + }) +} + +pub fn build_status_updated_event( + scope: CoworkChatScope, + session_id: String, + status: CoworkChatRunStatus, +) -> CoworkChatEvent { + CoworkChatEvent::Status(CoworkChatStatusEvent { + event_type: "status.updated".to_string(), + scope, + session_id, + status, + }) +} + +pub fn build_message_created_event( + scope: CoworkChatScope, + session_id: String, + message: CoworkChatMessage, +) -> CoworkChatEvent { + CoworkChatEvent::Message(CoworkChatMessageEvent { + event_type: "message.created".to_string(), + scope, + session_id, + message, + }) +} + +pub fn build_files_updated_event( + scope: CoworkChatScope, + session_id: String, + files: Vec, +) -> CoworkChatEvent { + CoworkChatEvent::Files(CoworkChatFilesEvent { + event_type: "files.updated".to_string(), + scope, + session_id, + files, + }) +} + +pub fn emit_local_session_started( + app: &AppHandle, + session: &LocalCoworkSession, + include_session_created: bool, +) { + let base = session.base(); + + if include_session_created { + let _ = emit_cowork_stream_event(app, &build_session_created_event(base)); + } + + let _ = emit_cowork_stream_event(app, &build_session_updated_event(base)); + + if let Some(message) = base.messages.last().cloned() { + let _ = emit_cowork_stream_event( + app, + &build_message_created_event(base.scope, base.id.clone(), message), + ); + } + + let _ = emit_cowork_stream_event( + app, + &build_status_updated_event(base.scope, base.id.clone(), base.run_status), + ); +} + +pub fn emit_session_updated(app: &AppHandle, session: &LocalCoworkSession) { + let _ = emit_cowork_stream_event(app, &build_session_updated_event(session.base())); +} + +pub fn emit_local_terminal_state( + app: &AppHandle, + session: &LocalCoworkSession, + include_latest_message: bool, +) { + let base = session.base(); + + let _ = emit_cowork_stream_event(app, &build_session_updated_event(base)); + + if include_latest_message { + if let Some(message) = base.messages.last().cloned() { + let _ = emit_cowork_stream_event( + app, + &build_message_created_event(base.scope, base.id.clone(), message), + ); + } + } + + let _ = emit_cowork_stream_event( + app, + &build_files_updated_event(base.scope, base.id.clone(), base.files.clone()), + ); + + let _ = emit_cowork_stream_event( + app, + &build_status_updated_event(base.scope, base.id.clone(), base.run_status), + ); +} + +fn normalize_local_session_runtime(session: &mut LocalCoworkSession) { + session.base_mut().normalize_runtime_binding(); +} + +fn merge_runtime_events( + existing_events: Vec, + incoming_events: Vec, +) -> Vec { + let mut merged = existing_events; + + for event in incoming_events { + if !merged + .iter() + .any(|current| is_same_runtime_event(current, &event)) + { + merged.push(event); + } + } + + sort_runtime_events(&mut merged); + merged +} + +fn sort_runtime_events(events: &mut [CoworkChatRuntimeEvent]) { + events.sort_by(|left, right| { + let left_time = left + .runtime_created_at + .as_deref() + .unwrap_or(left.emitted_at.as_str()); + let right_time = right + .runtime_created_at + .as_deref() + .unwrap_or(right.emitted_at.as_str()); + + left_time + .cmp(right_time) + .then_with(|| left.runtime_event_type.cmp(&right.runtime_event_type)) + .then_with(|| left.runtime_event_id.cmp(&right.runtime_event_id)) + .then_with(|| left.emitted_at.cmp(&right.emitted_at)) + }); +} + +fn is_same_runtime_event(left: &CoworkChatRuntimeEvent, right: &CoworkChatRuntimeEvent) -> bool { + match ( + left.runtime_event_id.as_deref(), + right.runtime_event_id.as_deref(), + ) { + (Some(left_id), Some(right_id)) => { + left.runtime_event_type == right.runtime_event_type && left_id == right_id + } + _ => { + left.runtime_event_type == right.runtime_event_type + && left.runtime_created_at == right.runtime_created_at + && left.emitted_at == right.emitted_at + && left.content == right.content + } + } +} + +fn resolve_store_for_scope(scope: CoworkChatScope) -> &'static dyn FeatureSessionStore { + match scope { + CoworkChatScope::Homepage => &HOMEPAGE_SESSION_STORE, + CoworkChatScope::OrganizeFileFolder => &ORGANIZE_SESSION_STORE, + } +} + +fn resolve_store_for_session(session: &LocalCoworkSession) -> &'static dyn FeatureSessionStore { + match session { + LocalCoworkSession::Homepage(_) => &HOMEPAGE_SESSION_STORE, + LocalCoworkSession::Organize(_) => &ORGANIZE_SESSION_STORE, + } +} + +fn build_summary(session: &CoworkChatSessionDetail) -> CoworkChatSessionSummary { + CoworkChatSessionSummary { + id: session.id.clone(), + scope: session.scope, + title: session.title.clone(), + preview: session.preview.clone(), + updated_at: session.updated_at.clone(), + message_count: session.message_count, + } +} + +fn build_preview_from_messages(messages: &[CoworkChatMessage]) -> String { + messages + .iter() + .rev() + .find(|message| { + message.role == CoworkChatMessageRole::Assistant && !message.content.trim().is_empty() + }) + .or_else(|| { + messages + .iter() + .rev() + .find(|message| !message.content.trim().is_empty()) + }) + .map(|message| truncate_preview(&message.content)) + .unwrap_or_default() +} + +fn build_homepage_session_title(content: &str) -> String { + let normalized = content.split_whitespace().collect::>().join(" "); + let title: String = normalized.chars().take(60).collect(); + + if title.trim().is_empty() { + "New cowork chat".to_string() + } else { + title.trim().to_string() + } +} + +fn truncate_preview(value: &str) -> String { + let normalized = value.split_whitespace().collect::>().join(" "); + if normalized.chars().count() <= 120 { + normalized + } else { + let truncated: String = normalized.chars().take(117).collect(); + format!("{truncated}...") + } +} diff --git a/frontend/src-tauri/src/cowork/string_utils.rs b/frontend/src-tauri/src/cowork/string_utils.rs new file mode 100644 index 000000000..d9f88c3ef --- /dev/null +++ b/frontend/src-tauri/src/cowork/string_utils.rs @@ -0,0 +1,8 @@ +pub fn normalize_optional_string(value: &str) -> Option { + let trimmed = value.trim(); + if trimmed.is_empty() { + None + } else { + Some(trimmed.to_string()) + } +} diff --git a/frontend/src-tauri/src/cowork/time_utils.rs b/frontend/src-tauri/src/cowork/time_utils.rs new file mode 100644 index 000000000..e20a06758 --- /dev/null +++ b/frontend/src-tauri/src/cowork/time_utils.rs @@ -0,0 +1,9 @@ +use chrono::{SecondsFormat, Utc}; + +pub fn now_iso() -> String { + Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true) +} + +pub fn generate_message_id() -> String { + format!("cowork-msg-{}", Utc::now().timestamp_millis()) +} diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index d6ce642e6..b33ace627 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -2,6 +2,8 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command +mod cowork; + #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) @@ -9,9 +11,29 @@ fn greet(name: &str) -> String { fn main() { tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_process::init()) - .invoke_handler(tauri::generate_handler![greet]) + .manage(cowork::agent_remote::auth::RemoteAuthState::default()) + .invoke_handler(tauri::generate_handler![ + greet, + cowork::agent_remote::auth::sync_cowork_auth_context, + cowork::runtime::send_cowork_chat_message, + cowork::chat_commands::stop_cowork_chat_session, + cowork::homepage::chat_sessions::list_homepage_chat_sessions, + cowork::homepage::chat_sessions::get_homepage_chat_session, + cowork::homepage::chat_sessions::create_homepage_chat_session, + cowork::homepage::chat_sessions::update_homepage_chat_session, + cowork::homepage::chat_sessions::rename_homepage_chat_session, + cowork::homepage::chat_sessions::delete_homepage_chat_session, + cowork::organize::file_tree::read_path_tree, + cowork::organize::sessions::list_organize_sessions, + cowork::organize::sessions::get_organize_session, + cowork::organize::sessions::create_organize_session, + cowork::organize::sessions::update_organize_session, + cowork::organize::sessions::rename_organize_session, + cowork::organize::sessions::delete_organize_session + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index b821db87f..1982f72cd 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -22,6 +22,9 @@ "plugins": { "process": { "active": true + }, + "shell": { + "open": true } }, "app": { @@ -33,8 +36,8 @@ "title": "II Agent", "width": 1000, "height": 600, - "dragDropEnabled": false + "dragDropEnabled": true } ] } -} \ No newline at end of file +} diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index 8e9392cc5..2a419d22f 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -306,6 +306,19 @@ const createAppRouter = () => } } }, + { + path: 'cowork', + async lazy() { + const { Component } = await import('@/app/routes/cowork') + return { + Component: () => ( + + + + ) + } + } + }, { path: ':sessionId', async lazy() { diff --git a/frontend/src/app/routes/cowork.tsx b/frontend/src/app/routes/cowork.tsx new file mode 100644 index 000000000..23ea68630 --- /dev/null +++ b/frontend/src/app/routes/cowork.tsx @@ -0,0 +1,7 @@ +import CoworkPage from '@/components/cowork/cowork-page' + +export function CoworkRoute() { + return +} + +export const Component = CoworkRoute diff --git a/frontend/src/app/routes/home.tsx b/frontend/src/app/routes/home.tsx index 9486047da..851b8a12b 100644 --- a/frontend/src/app/routes/home.tsx +++ b/frontend/src/app/routes/home.tsx @@ -37,6 +37,7 @@ import { selectCurrentQuestion, selectQuestionMode, setCurrentQuestion, + setQuestionMode, setSelectedGitHubRepository, useAppDispatch, useAppSelector @@ -61,6 +62,12 @@ function HomePageContent() { const questionMode = useAppSelector(selectQuestionMode) const isChatMode = questionMode === QUESTION_MODE.CHAT + useEffect(() => { + if (questionMode === QUESTION_MODE.COWORK) { + dispatch(setQuestionMode(QUESTION_MODE.AGENT)) + } + }, [dispatch, questionMode]) + const toggleTheme = () => { setTheme(theme === 'dark' ? 'light' : 'dark') } diff --git a/frontend/src/app/routes/login.tsx b/frontend/src/app/routes/login.tsx index 8b278afef..ec0d91f69 100644 --- a/frontend/src/app/routes/login.tsx +++ b/frontend/src/app/routes/login.tsx @@ -1,6 +1,6 @@ import { useGoogleLogin } from '@react-oauth/google' import { useCallback, useEffect, useMemo, useRef } from 'react' -import { Link, useNavigate } from 'react-router' +import { Link, useNavigate, useSearchParams } from 'react-router' import { useForm } from 'react-hook-form' import { z } from 'zod' import { zodResolver } from '@hookform/resolvers/zod' @@ -11,7 +11,6 @@ import { Button } from '@/components/ui/button' import { Icon } from '@/components/ui/icon' import { Form, FormControl, FormField, FormItem } from '@/components/ui/form' import { Input } from '@/components/ui/input' -import { ACCESS_TOKEN } from '@/constants/auth' import { authService } from '@/services/auth.service' import { useAppDispatch } from '@/state/store' import { setUser } from '@/state/slice/user' @@ -19,6 +18,10 @@ import { fetchWishlist } from '@/state/slice/favorites' import { fetchPins } from '@/state/slice/pins' import { toast } from 'sonner' import { useIsSageTheme } from '@/hooks/use-is-sage-theme' +import { storeAccessToken } from '@/utils/auth-token' + +const isTauri = !!(window as unknown as { __TAURI_INTERNALS__: unknown }) + .__TAURI_INTERNALS__ type IiAuthPayload = { access_token: string @@ -30,10 +33,53 @@ type IiAuthPayload = { export function LoginPage() { const { t } = useTranslation() const navigate = useNavigate() + const [searchParams] = useSearchParams() const { loginWithAuthCode } = useAuth() const dispatch = useAppDispatch() const isSage = useIsSageTheme() + const apiBaseUrl = useMemo( + () => import.meta.env.VITE_API_URL || 'http://localhost:8000', + [] + ) + + // System browser opened this page with desktop_state → fetch Google OAuth URL and redirect + const desktopState = searchParams.get('desktop_state') + useEffect(() => { + if (!desktopState || searchParams.get('desktop_auth')) return + authService + .getDesktopGoogleLoginUrl(desktopState) + .then((url) => { + window.location.href = url + }) + .catch((err) => { + console.error('Failed to get Google login URL:', err) + }) + }, [desktopState, searchParams]) + + if (desktopState && !searchParams.get('desktop_auth')) { + return null + } + + // Backend redirected back here after Google login completes + if (searchParams.get('desktop_auth') === 'success') { + return ( +
+

+ {t('auth.loginSuccessful', { + defaultValue: 'Login successful!' + })} +

+

+ {t('auth.closeTabMessage', { + defaultValue: + 'You can close this tab and return to the app.' + })} +

+
+ ) + } + const FormSchema = useMemo( () => z.object({ @@ -85,10 +131,6 @@ export function LoginPage() { } }) - const apiBaseUrl = useMemo( - () => import.meta.env.VITE_API_URL || 'http://localhost:8000', - [] - ) const apiOrigin = useMemo(() => { try { return new URL(apiBaseUrl).origin @@ -113,17 +155,24 @@ export function LoginPage() { authHandledRef.current = true try { - localStorage.setItem(ACCESS_TOKEN, payload.access_token) - window.dispatchEvent(new CustomEvent('auth-token-set')) + storeAccessToken(payload.access_token) const userRes = await authService.getCurrentUser() dispatch(setUser(userRes)) dispatch(fetchWishlist()) dispatch(fetchPins()) + // Focus desktop app window after login + if (isTauri) { + const { getCurrentWindow } = await import( + '@tauri-apps/api/window' + ) + await getCurrentWindow().setFocus() + } + navigate('/') } catch (error) { - console.error('Failed to finalize II login:', error) + console.error('Failed to finalize login:', error) authHandledRef.current = false } }, @@ -141,7 +190,11 @@ export function LoginPage() { payload?: IiAuthPayload } - if (!data || data.type !== 'ii-auth-success') { + if ( + !data || + (data.type !== 'ii-auth-success' && + data.type !== 'google-auth-success') + ) { return } @@ -154,13 +207,19 @@ export function LoginPage() { useEffect(() => { const hash = window.location.hash - if (!hash || !hash.includes('ii-auth=')) { - return - } + if (!hash) return + + // Support both II and Google auth hash-fragment fallbacks + const authKey = hash.includes('ii-auth=') + ? 'ii-auth' + : hash.includes('google-auth=') + ? 'google-auth' + : null + if (!authKey) return const params = new URLSearchParams(hash.slice(1)) - const encoded = params.get('ii-auth') - params.delete('ii-auth') + const encoded = params.get(authKey) + params.delete(authKey) const cleanHash = params.toString() const cleanUrl = `${window.location.pathname}${window.location.search}${cleanHash ? `#${cleanHash}` : ''}` @@ -176,11 +235,36 @@ export function LoginPage() { ) as IiAuthPayload void handleAuthSuccess(payload) } catch (error) { - console.error('Failed to parse II auth payload from hash:', error) + console.error('Failed to parse auth payload from hash:', error) authHandledRef.current = false } }, [handleAuthSuccess]) + const loginWithGoogleDesktop = useCallback(async () => { + authHandledRef.current = false + + const state = crypto.randomUUID() + const frontendOrigin = + import.meta.env.VITE_FRONTEND_URL || 'http://localhost:1420' + const url = `${frontendOrigin}/login?desktop_state=${state}` + + const { open } = await import('@tauri-apps/plugin-shell') + await open(url) + + // Poll for token until backend stores it after Google callback + const poll = setInterval(async () => { + try { + const token = await authService.pollDesktopToken(state) + if (!token) return + clearInterval(poll) + void handleAuthSuccess(token) + } catch { + // keep polling + } + }, 2000) + setTimeout(() => clearInterval(poll), 5 * 60 * 1000) + }, [handleAuthSuccess]) + const loginWithII = useCallback(() => { authHandledRef.current = false @@ -326,7 +410,9 @@ export function LoginPage() {

- {t('auth.privacyNotice')}{' '} -

+ {t('auth.privacyNotice')}

>({ resolver: zodResolver(FormSchema), @@ -50,6 +68,80 @@ export function SignupPage() { } }) + const apiBaseUrl = useMemo( + () => import.meta.env.VITE_API_URL || 'http://localhost:8000', + [] + ) + + const handleAuthSuccess = useCallback( + async (payload: AuthPayload | null | undefined) => { + if (!payload || typeof payload.access_token !== 'string') { + authHandledRef.current = false + return + } + if (authHandledRef.current) return + authHandledRef.current = true + + try { + storeAccessToken(payload.access_token) + const userRes = await authService.getCurrentUser() + dispatch(setUser(userRes)) + dispatch(fetchWishlist()) + navigate('/') + } catch (error) { + console.error('Failed to finalize Google login:', error) + authHandledRef.current = false + } + }, + [dispatch, navigate] + ) + + const apiOrigin = useMemo(() => { + try { + return new URL(apiBaseUrl).origin + } catch { + return apiBaseUrl + } + }, [apiBaseUrl]) + + useEffect(() => { + const handler = (event: MessageEvent) => { + if (event.origin !== apiOrigin) return + const data = event.data as { + type?: string + payload?: AuthPayload + } + if (!data || data.type !== 'google-auth-success') return + void handleAuthSuccess(data.payload) + } + window.addEventListener('message', handler) + return () => window.removeEventListener('message', handler) + }, [apiOrigin, handleAuthSuccess]) + + const loginWithGoogleDesktop = useCallback(async () => { + authHandledRef.current = false + + const state = crypto.randomUUID() + const frontendOrigin = + import.meta.env.VITE_FRONTEND_URL || 'http://localhost:1420' + const url = `${frontendOrigin}/login?desktop_state=${state}` + + const { open } = await import('@tauri-apps/plugin-shell') + await open(url) + + const poll = setInterval(async () => { + try { + const token = await authService.pollDesktopToken(state) + if (!token) return + clearInterval(poll) + void handleAuthSuccess(token) + } catch { + // keep polling + } + }, 2000) + setTimeout(() => clearInterval(poll), 5 * 60 * 1000) + }, [handleAuthSuccess]) + const onSubmit = async (data: z.infer) => { console.log(data) } @@ -179,7 +271,9 @@ export function SignupPage() {

- {questionMode === QUESTION_MODE.AGENT + {isAgenticQuestionMode(questionMode) ? t('agent.settings') : t('agent.chatSettings')} diff --git a/frontend/src/components/cowork/chat/cowork-chat-box.tsx b/frontend/src/components/cowork/chat/cowork-chat-box.tsx new file mode 100644 index 000000000..7871a752e --- /dev/null +++ b/frontend/src/components/cowork/chat/cowork-chat-box.tsx @@ -0,0 +1,288 @@ +import { AnimatePresence, motion } from 'framer-motion' +import { useMemo, useRef, useState } from 'react' +import clsx from 'clsx' +import ChatMessage from '@/components/agent/chat-message' +import { Button } from '@/components/ui/button' +import { useAppDispatch } from '@/state' +import { setCurrentQuestion } from '@/state' +import type { ActionStep } from '@/typings/agent' +import type { + CoworkChatScope, + CoworkChatFile, + CoworkChatSessionDetail, + CoworkLiveSessionState +} from '@/typings/cowork' +import { useCoworkChatMessageAdapter } from './use-cowork-chatmessage-adapter' + +interface CoworkChatBoxProps { + className?: string + isVisible?: boolean + scope: CoworkChatScope + activeSession: CoworkChatSessionDetail | null + liveSession?: CoworkLiveSessionState | null + isLoading?: boolean + isSending?: boolean + isInputLocked?: boolean + onSendMessage: (content: string) => Promise | void + onStopSession?: () => Promise | void + onSelectAction?: (action: ActionStep) => void +} + +type CoworkChatTab = 'chat' | 'files' + +const formatFileSize = (size: number) => { + if (size >= 1024 * 1024) { + return `${(size / (1024 * 1024)).toFixed(1)} MB` + } + if (size >= 1024) { + return `${Math.round(size / 1024)} KB` + } + return `${size} B` +} + +const CoworkChatBox = ({ + className = '', + isVisible = true, + scope, + activeSession, + liveSession = null, + isLoading = false, + isSending = false, + isInputLocked = false, + onSendMessage, + onStopSession, + onSelectAction +}: CoworkChatBoxProps) => { + const dispatch = useAppDispatch() + const [activeTab, setActiveTab] = useState('chat') + const messagesEndRef = useRef(null) + + useCoworkChatMessageAdapter({ + activeSession, + liveSession, + isLoading, + isSending + }) + + const sessionFiles = activeSession?.files ?? [] + const sessionContentKey = useMemo( + () => activeSession?.id ?? `empty-${scope}`, + [activeSession?.id, scope] + ) + const emptyStateDescription = + scope === 'organize-file-folder' + ? 'Start a Cowork chat session to discuss structure, naming, and file organization.' + : 'Start a new Cowork chat to discuss your task.' + const responsiveChatBoxWidthClass = 'md:w-[clamp(320px,38vw,600px)]' + + if (!isVisible) return null + + return ( +
+
+ + +
+ +
+
+ + + { + if (action) { + onSelectAction?.(action) + } + }} + setCurrentQuestion={(value) => + dispatch(setCurrentQuestion(value)) + } + handleKeyDown={(event) => { + dispatch( + setCurrentQuestion( + event.currentTarget.value + ) + ) + }} + handleQuestionSubmit={(question) => { + if (isInputLocked || isSending) { + return + } + dispatch(setCurrentQuestion('')) + void onSendMessage(question) + }} + handleEnhancePrompt={() => {}} + handleCancel={() => { + void onStopSession?.() + }} + handleEditMessage={() => {}} + connectWebSocket={() => {}} + handleReviewSession={() => {}} + submitDisabled={isInputLocked} + /> + + + + {activeSession === null && !isSending && !isLoading && ( +
+
+

+ New chat +

+

+ {emptyStateDescription} +

+
+
+ )} + +
+
+
+ Switching session... +
+
+
+
+
+ +
+
+
+ + +
+

+ All files +

+

+ Files associated with the active Cowork + session. +

+
+
+ {sessionFiles.length > 0 ? ( + sessionFiles.map( + (file: CoworkChatFile) => ( +
+
+
+

+ {file.file_name} +

+

+ { + file.content_type + } +

+
+
+ {formatFileSize( + file.file_size + )} +
+
+
+ ) + ) + ) : ( +
+ No files in this Cowork session yet. +
+ )} +
+
+
+
+
+
+ Switching session... +
+
+
+
+
+
+
+ ) +} + +export default CoworkChatBox diff --git a/frontend/src/components/cowork/chat/cowork-chatmessage-contract.ts b/frontend/src/components/cowork/chat/cowork-chatmessage-contract.ts new file mode 100644 index 000000000..be940f65b --- /dev/null +++ b/frontend/src/components/cowork/chat/cowork-chatmessage-contract.ts @@ -0,0 +1,13 @@ +export type CoworkTranscriptMessageKind = 'thinking' | 'response' + +const normalizeCoworkTranscriptAnchor = (anchor: string) => + anchor.replace(/\s+/g, '-').trim() + +export const buildCoworkTranscriptMessageId = ( + kind: CoworkTranscriptMessageKind, + anchor: string +) => `cowork-transcript:${kind}:${normalizeCoworkTranscriptAnchor(anchor)}` + +export const resolveCoworkTranscriptAnchor = ( + ...candidates: Array +) => candidates.find((candidate) => typeof candidate === 'string' && candidate.trim()) diff --git a/frontend/src/components/cowork/chat/use-cowork-chatmessage-adapter.ts b/frontend/src/components/cowork/chat/use-cowork-chatmessage-adapter.ts new file mode 100644 index 000000000..c08195b04 --- /dev/null +++ b/frontend/src/components/cowork/chat/use-cowork-chatmessage-adapter.ts @@ -0,0 +1,222 @@ +import { useEffect, useMemo } from 'react' +import { + setCurrentQuestion, + setEditingMessage, + setLoading, + setMessages, + setQuestionMode, + setRunStatus, + setWorkspaceInfo, + useAppDispatch +} from '@/state' +import { QUESTION_MODE, type Message } from '@/typings/agent' +import type { + CoworkChatSessionDetail, + CoworkLiveSessionState +} from '@/typings/cowork' +import { + buildCoworkTranscriptMessageId, + resolveCoworkTranscriptAnchor +} from './cowork-chatmessage-contract' + +const mapPersistedMessages = ( + activeSession: CoworkChatSessionDetail | null +): Message[] => + (activeSession?.messages ?? []).map((message) => ({ + id: message.id, + role: message.role, + content: message.content, + timestamp: new Date(message.created_at).getTime(), + isThinkMessage: message.is_think_message + })) + +const mapLiveMessages = ( + liveSession: CoworkLiveSessionState | null +): Message[] => { + if (!liveSession) { + return [] + } + + const nextMessages: Message[] = [] + + if (liveSession.thinking.trim()) { + nextMessages.push({ + id: + liveSession.thinking_message_id ?? + buildCoworkTranscriptMessageId( + 'thinking', + resolveCoworkTranscriptAnchor( + liveSession.thinking_started_at, + liveSession.session_id + ) ?? liveSession.session_id + ), + role: 'assistant', + content: liveSession.thinking, + timestamp: new Date( + liveSession.thinking_started_at ?? Date.now() + ).getTime(), + isThinkMessage: true + }) + } + + if (liveSession.response.trim()) { + nextMessages.push({ + id: + liveSession.response_message_id ?? + buildCoworkTranscriptMessageId( + 'response', + resolveCoworkTranscriptAnchor( + liveSession.response_started_at, + liveSession.session_id + ) ?? liveSession.session_id + ), + role: 'assistant', + content: liveSession.response, + timestamp: new Date( + liveSession.response_started_at ?? Date.now() + ).getTime() + }) + } + + return nextMessages +} + +const sortTimelineMessages = (messages: Message[]) => + messages + .map((message, index) => ({ message, index })) + .sort((left, right) => { + if (left.message.timestamp === right.message.timestamp) { + return left.index - right.index + } + + return left.message.timestamp - right.message.timestamp + }) + .map(({ message }) => message) + +const buildCoworkMessages = ({ + activeSession, + liveSession +}: { + activeSession: CoworkChatSessionDetail | null + liveSession: CoworkLiveSessionState | null +}): Message[] => { + const persistedMessages = mapPersistedMessages(activeSession) + const eventMessages = liveSession?.event_messages ?? [] + const persistedMessageIds = new Set( + persistedMessages.map((message) => message.id) + ) + const liveMessages = mapLiveMessages(liveSession).filter( + (message) => !persistedMessageIds.has(message.id) + ) + const liveEventMessages = eventMessages.filter( + (message) => !persistedMessageIds.has(message.id) + ) + + if (liveEventMessages.length === 0 && liveMessages.length === 0) { + return persistedMessages + } + + return sortTimelineMessages([ + ...persistedMessages, + ...liveEventMessages, + ...liveMessages + ]) +} + +const mapCoworkRunStatus = ({ + activeSession, + liveSession, + isSending +}: { + activeSession: CoworkChatSessionDetail | null + liveSession: CoworkLiveSessionState | null + isSending: boolean +}) => { + if (activeSession?.run_status === 'completed') { + return 'completed' + } + + if (activeSession?.run_status === 'waiting_for_input') { + return 'paused' + } + + if (activeSession?.run_status === 'stopped') { + return 'aborted' + } + + if ( + isSending || + activeSession?.run_status === 'thinking' || + Boolean( + liveSession?.thinking.trim() || + liveSession?.response.trim() || + liveSession?.event_messages.length || + liveSession?.tool_calls.length + ) + ) { + return 'running' + } + + return null +} + +interface UseCoworkChatMessageAdapterOptions { + activeSession: CoworkChatSessionDetail | null + liveSession?: CoworkLiveSessionState | null + isLoading?: boolean + isSending?: boolean +} + +export const useCoworkChatMessageAdapter = ({ + activeSession, + liveSession = null, + isLoading = false, + isSending = false +}: UseCoworkChatMessageAdapterOptions) => { + const dispatch = useAppDispatch() + + const messages = useMemo( + () => buildCoworkMessages({ activeSession, liveSession }), + [activeSession, liveSession] + ) + const runStatus = useMemo( + () => mapCoworkRunStatus({ activeSession, liveSession, isSending }), + [activeSession, liveSession, isSending] + ) + const isChatLoading = Boolean( + isLoading || + runStatus === 'running' || + activeSession?.run_status === 'thinking' + ) + + useEffect(() => { + dispatch(setQuestionMode(QUESTION_MODE.COWORK)) + dispatch(setWorkspaceInfo('')) + }, [dispatch]) + + useEffect(() => { + dispatch(setMessages(messages)) + }, [dispatch, messages]) + + useEffect(() => { + dispatch(setRunStatus(runStatus)) + }, [dispatch, runStatus]) + + useEffect(() => { + dispatch(setLoading(isChatLoading)) + }, [dispatch, isChatLoading]) + + useEffect(() => { + dispatch(setEditingMessage(undefined)) + }, [dispatch, activeSession?.id]) + + useEffect(() => { + return () => { + dispatch(setMessages([])) + dispatch(setRunStatus(null)) + dispatch(setLoading(false)) + dispatch(setCurrentQuestion('')) + dispatch(setEditingMessage(undefined)) + } + }, [dispatch]) +} diff --git a/frontend/src/components/cowork/cowork-build-panel.tsx b/frontend/src/components/cowork/cowork-build-panel.tsx new file mode 100644 index 000000000..59c8172d7 --- /dev/null +++ b/frontend/src/components/cowork/cowork-build-panel.tsx @@ -0,0 +1 @@ +export { default } from './cowork-build/cowork-build-panel' diff --git a/frontend/src/components/cowork/cowork-build/cowork-build-panel.tsx b/frontend/src/components/cowork/cowork-build/cowork-build-panel.tsx new file mode 100644 index 000000000..295229523 --- /dev/null +++ b/frontend/src/components/cowork/cowork-build/cowork-build-panel.tsx @@ -0,0 +1,57 @@ +import type { ReactNode } from 'react' +import { Icon } from '@/components/ui/icon' + +interface CoworkBuildPanelProps { + headerLabel: string + viewport: ReactNode + controller?: ReactNode + footerText?: string +} + +const CoworkBuildPanel = ({ + headerLabel, + viewport, + controller, + footerText +}: CoworkBuildPanelProps) => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + {headerLabel} + +
+
+
+
+ {viewport} +
+
+ {controller} +
+ {footerText && ( +

+ {footerText} +

+ )} +
+
+ ) +} + +export default CoworkBuildPanel diff --git a/frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx b/frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx new file mode 100644 index 000000000..ceb10537b --- /dev/null +++ b/frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx @@ -0,0 +1,147 @@ +import Browser from '@/components/agent/browser' +import SearchBrowser from '@/components/agent/search-browser' +import CodeEditor from '@/components/code-editor' +import Terminal from '@/components/terminal' +import { TOOL, type ActionStep } from '@/typings/agent' +import type { + CoworkBuildRendererDefinition, + CoworkBuildRendererKey, + CoworkBuildRendererContext +} from './cowork-build.types' + +export const coworkBuildEventToolGroups = { + terminal: new Set([ + TOOL.BASH, + TOOL.BASH_INIT, + TOOL.BASH_VIEW, + TOOL.BASH_STOP, + TOOL.BASH_KILL, + TOOL.BASH_WRITE_TO_PROCESS, + TOOL.LS, + TOOL.GLOB, + TOOL.GREP + ]), + code: new Set([ + TOOL.READ, + TOOL.WRITE, + TOOL.EDIT, + TOOL.MULTI_EDIT, + TOOL.APPLY_PATCH, + TOOL.STR_REPLACE_BASED_EDIT + ]), + search: new Set([TOOL.WEB_SEARCH, TOOL.WEB_BATCH_SEARCH]), + browser: new Set([ + TOOL.VISIT, + TOOL.VISIT_COMPRESS, + TOOL.BROWSER_USE, + TOOL.BROWSER_CLICK, + TOOL.BROWSER_CLOSE, + TOOL.BROWSER_CONSOLE_MESSAGES, + TOOL.BROWSER_DRAG, + TOOL.BROWSER_EVALUATE, + TOOL.BROWSER_HANDLE_DIALOG, + TOOL.BROWSER_HOVER, + TOOL.BROWSER_NAVIGATE, + TOOL.BROWSER_NETWORK_REQUESTS, + TOOL.BROWSER_PRESS_KEY, + TOOL.BROWSER_SELECT_OPTION, + TOOL.BROWSER_SNAPSHOT, + TOOL.BROWSER_TAKE_SCREENSHOT, + TOOL.BROWSER_TYPE, + TOOL.BROWSER_WAIT_FOR, + TOOL.BROWSER_TAB_CLOSE, + TOOL.BROWSER_TAB_LIST, + TOOL.BROWSER_TAB_NEW, + TOOL.BROWSER_TAB_SELECT, + TOOL.BROWSER_MOUSE_CLICK_XY, + TOOL.BROWSER_MOUSE_DRAG_XY, + TOOL.BROWSER_MOUSE_MOVE_XY, + TOOL.BROWSER_NAVIGATION, + TOOL.BROWSER_WAIT, + TOOL.BROWSER_VIEW_INTERACTIVE_ELEMENTS, + TOOL.BROWSER_SCROLL_DOWN, + TOOL.BROWSER_SCROLL_UP, + TOOL.BROWSER_SWITCH_TAB, + TOOL.BROWSER_OPEN_NEW_TAB, + TOOL.BROWSER_GET_SELECT_OPTIONS, + TOOL.BROWSER_SELECT_DROPDOWN_OPTION, + TOOL.BROWSER_RESTART, + TOOL.BROWSER_ENTER_TEXT, + TOOL.BROWSER_ENTER_MULTI_TEXTS + ]) +} satisfies Record> + +export const coworkBuildRendererCatalog: Record< + CoworkBuildRendererKey, + CoworkBuildRendererDefinition +> = { + terminal: { + key: 'terminal', + matches: (action: ActionStep) => + coworkBuildEventToolGroups.terminal.has(action.type), + render: ({ currentAction }: CoworkBuildRendererContext) => ( + + ) + }, + code: { + key: 'code', + matches: (action: ActionStep) => + coworkBuildEventToolGroups.code.has(action.type), + render: ({ + currentAction, + previewContent, + previewPath + }: CoworkBuildRendererContext) => ( + + ) + }, + search: { + key: 'search', + matches: (action: ActionStep) => + coworkBuildEventToolGroups.search.has(action.type), + render: ({ + searchKeyword, + searchResults + }: CoworkBuildRendererContext) => ( + + ) + }, + browser: { + key: 'browser', + matches: (action: ActionStep) => + coworkBuildEventToolGroups.browser.has(action.type), + render: ({ + browserUrl, + browserRaw + }: CoworkBuildRendererContext) => ( + + ) + } +} + +export const pickCoworkBuildRenderers = ( + ...keys: CoworkBuildRendererKey[] +): CoworkBuildRendererDefinition[] => + keys.map((key) => coworkBuildRendererCatalog[key]) diff --git a/frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx b/frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx new file mode 100644 index 000000000..73b066e85 --- /dev/null +++ b/frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx @@ -0,0 +1,430 @@ +import { useEffect, useMemo, useState } from 'react' +import Action from '@/components/agent/action' +import { Button } from '@/components/ui/button' +import { Icon } from '@/components/ui/icon' +import { Slider } from '@/components/ui/slider' +import { parseJson } from '@/lib/utils' +import { TOOL, type ActionStep } from '@/typings/agent' +import type { + CoworkBuildActionMessage, + CoworkBuildRendererContext, + CoworkBuildRendererDefinition, + CoworkBuildState, + UseCoworkBuildStateOptions +} from './cowork-build.types' +import { coworkBuildEventToolGroups } from './cowork-build.renderers' + +const stringifyValue = (value: unknown) => { + if (typeof value === 'string') return value + if (value === null || value === undefined) return '' + + try { + return JSON.stringify(value, null, 2) + } catch { + return String(value) + } +} + +const getPreviewPath = (action?: ActionStep) => { + if (action?.type === TOOL.APPLY_PATCH && action.data.tool_input?.changes) { + const firstPath = Object.keys(action.data.tool_input.changes)[0] + if (firstPath) { + return firstPath + } + } + + return ( + action?.data.tool_input?.path || + action?.data.tool_input?.file_path || + action?.data.tool_input?.file || + action?.data.tool_input?.filename + ) +} + +const getPreviewContent = (action?: ActionStep) => { + if (!action) return '' + + if (action.type === TOOL.WRITE) { + return stringifyValue(action.data.tool_input?.content) + } + + if (action.type === TOOL.EDIT || action.type === TOOL.MULTI_EDIT) { + return stringifyValue( + action.data.tool_input?.new_string ?? action.data.result + ) + } + + if (action.type === TOOL.STR_REPLACE_BASED_EDIT) { + return stringifyValue( + action.data.tool_input?.file_text ?? action.data.result + ) + } + + if (action.type === TOOL.APPLY_PATCH && action.data.tool_input?.changes) { + const firstPath = Object.keys(action.data.tool_input.changes)[0] + const firstChange = firstPath + ? action.data.tool_input.changes[firstPath] + : undefined + + return stringifyValue( + firstChange?.update?.unified_diff || + firstChange?.add?.content || + firstChange?.delete?.content || + action.data.result + ) + } + + return stringifyValue(action.data.result) +} + +const getRequestedActionIndex = ( + actionMessages: CoworkBuildActionMessage[], + requestedAction?: ActionStep | null +) => { + if (!requestedAction) { + return -1 + } + + const requestedToolCallId = requestedAction.data.tool_call_id + if (requestedToolCallId) { + const toolCallIndex = actionMessages.findIndex( + (message) => + message.action.data.tool_call_id === requestedToolCallId + ) + if (toolCallIndex >= 0) { + return toolCallIndex + } + } + + const typeAndInputIndex = actionMessages.findIndex( + (message) => + message.action.type === requestedAction.type && + message.action.data.tool_input === requestedAction.data.tool_input + ) + + if (typeAndInputIndex >= 0) { + return typeAndInputIndex + } + + return actionMessages.findIndex( + (message) => + message.action.type === requestedAction.type && + message.action.data.tool_name === requestedAction.data.tool_name + ) +} + +export const useCoworkBuildState = ({ + liveSession = null, + requestedAction = null, + requestedActionToken = 0 +}: UseCoworkBuildStateOptions): CoworkBuildState => { + const actionMessages = useMemo( + () => + ((liveSession?.event_messages ?? []).filter( + (message) => message.action + ) as CoworkBuildActionMessage[]), + [liveSession?.event_messages] + ) + const totalSteps = actionMessages.length + const [currentStep, setCurrentStep] = useState(0) + const [isLiveUpdate, setIsLiveUpdate] = useState(true) + + useEffect(() => { + setCurrentStep(0) + setIsLiveUpdate(true) + }, [liveSession?.session_id]) + + useEffect(() => { + if (!liveSession?.is_awaiting_turn_action) { + return + } + + setCurrentStep(0) + setIsLiveUpdate(true) + }, [liveSession?.is_awaiting_turn_action]) + + useEffect(() => { + if (isLiveUpdate && totalSteps > 0) { + setCurrentStep(totalSteps) + } + }, [isLiveUpdate, totalSteps]) + + useEffect(() => { + const requestedIndex = getRequestedActionIndex( + actionMessages, + requestedAction + ) + + if (requestedIndex >= 0) { + setCurrentStep(requestedIndex + 1) + setIsLiveUpdate(false) + } + }, [actionMessages, requestedAction, requestedActionToken]) + + const step = + totalSteps > 0 + ? currentStep > 0 + ? Math.min(currentStep, totalSteps) + : totalSteps + : 0 + const hasActionHistory = totalSteps > 0 + const isAwaitingTurnAction = + Boolean(liveSession?.is_awaiting_turn_action) && isLiveUpdate + const selectedAction = + step > 0 ? actionMessages[step - 1]?.action : undefined + + const currentAction = + (!isAwaitingTurnAction ? selectedAction : undefined) ?? + (!isAwaitingTurnAction ? liveSession?.current_action : undefined) + + const currentToolCall = useMemo(() => { + const toolCalls = liveSession?.tool_calls ?? [] + + if (!currentAction?.data.tool_call_id) { + return toolCalls.at(-1) + } + + return ( + toolCalls.find( + (toolCall) => toolCall.id === currentAction.data.tool_call_id + ) ?? toolCalls.at(-1) + ) + }, [currentAction, liveSession?.tool_calls]) + + const previewPath = getPreviewPath(currentAction) + const previewContent = getPreviewContent(currentAction) + const searchKeyword = + currentAction?.data.tool_input?.query || + currentAction?.data.tool_input?.queries?.join(', ') + const searchResults = + currentAction && + coworkBuildEventToolGroups.search.has(currentAction.type) && + currentAction.data.result + ? typeof currentAction.data.result === 'string' + ? parseJson(currentAction.data.result) + : currentAction.data.result + : undefined + const browserUrl = + currentAction && + coworkBuildEventToolGroups.browser.has(currentAction.type) + ? currentAction.data.tool_input?.url || + currentAction.data.tool_input?.urls?.[0] + : undefined + const browserRaw = + currentAction && browserUrl !== undefined + ? typeof currentAction.data.result === 'string' + ? currentAction.data.result + : stringifyValue(currentAction.data.result) + : undefined + const fallbackContent = currentToolCall?.result || currentToolCall?.input || '' + + return { + actionMessages, + totalSteps, + step, + isLiveUpdate, + hasActionHistory, + isAwaitingTurnAction, + currentAction, + currentToolCall, + fallbackContent, + previewPath, + previewContent, + browserUrl, + browserRaw, + searchKeyword, + searchResults, + setStep: (nextStep, liveUpdate = false) => { + setCurrentStep(nextStep) + setIsLiveUpdate(liveUpdate) + }, + jumpToLatest: () => { + setCurrentStep(totalSteps) + setIsLiveUpdate(true) + } + } +} + +interface CoworkBuildControllerProps { + step: number + totalSteps: number + isLiveUpdate: boolean + onStepChange: (step: number, liveUpdate?: boolean) => void + onJumpToLatest: () => void +} + +export const CoworkBuildController = ({ + step, + totalSteps, + isLiveUpdate, + onStepChange, + onJumpToLatest +}: CoworkBuildControllerProps) => { + if (totalSteps === 0) { + return null + } + + return ( +
+
+
+ +
+ {step} + / + {totalSteps} +
+ +
+ + onStepChange(value[0], value[0] >= totalSteps) + } + max={totalSteps} + step={1} + /> +
+ {isLiveUpdate && step === totalSteps ? ( +
+ Live update +
+ ) : ( + + )} +
+ ) +} + +export const CoworkBuildViewport = ({ + state, + renderers, + renderUnsupportedAction = true, + unsupportedMessage = 'This event does not have a dedicated build renderer yet.', + emptyLabel = 'Cowork build', + emptyTitle = 'Waiting for streaming events' +}: { + state: CoworkBuildState + renderers: CoworkBuildRendererDefinition[] + renderUnsupportedAction?: boolean + unsupportedMessage?: string + emptyLabel?: string + emptyTitle?: string +}) => { + const { + currentAction, + currentToolCall, + fallbackContent, + previewPath, + previewContent, + browserUrl, + browserRaw, + searchKeyword, + searchResults + } = state + + if (!currentAction) { + return ( +
+
+
+
+ +
+ +
+
+

+ {emptyLabel} +

+

+ {emptyTitle} +

+
+
+
+
+ {fallbackContent ? ( +

+ {fallbackContent} +

+ ) : null} +
+
+ ) + } + + const context: CoworkBuildRendererContext = { + currentAction, + currentToolCall, + previewPath, + previewContent, + browserUrl, + browserRaw, + searchKeyword, + searchResults + } + + const renderer = renderers.find((entry) => entry.matches(currentAction)) + if (renderer) { + return <>{renderer.render(context)} + } + + if (!renderUnsupportedAction) { + return ( +
+ {unsupportedMessage} +
+ ) + } + + return ( +
+
+ {}} + /> +

+ {unsupportedMessage} +

+ {(currentToolCall?.result || currentToolCall?.input) && ( +
+                        {currentToolCall?.result ?? currentToolCall?.input}
+                    
+ )} +
+
+ ) +} diff --git a/frontend/src/components/cowork/cowork-build/cowork-build.types.ts b/frontend/src/components/cowork/cowork-build/cowork-build.types.ts new file mode 100644 index 000000000..5dbecdc2e --- /dev/null +++ b/frontend/src/components/cowork/cowork-build/cowork-build.types.ts @@ -0,0 +1,57 @@ +import type { ReactNode } from 'react' +import type { ActionStep, Message } from '@/typings/agent' +import type { CoworkLiveSessionState, CoworkLiveToolCall } from '@/typings/cowork' + +export type CoworkBuildActionMessage = Message & { + action: ActionStep +} + +export type CoworkBuildRendererKey = 'terminal' | 'code' | 'search' | 'browser' + +export type CoworkBuildSearchResult = + | string + | Record + | Record[] + +export interface CoworkBuildRendererContext { + currentAction: ActionStep + currentToolCall?: CoworkLiveToolCall + previewPath?: string + previewContent: string + browserUrl?: string + browserRaw?: string + searchKeyword?: string + searchResults?: CoworkBuildSearchResult +} + +export interface CoworkBuildRendererDefinition { + key: string + matches: (action: ActionStep) => boolean + render: (context: CoworkBuildRendererContext) => ReactNode +} + +export interface UseCoworkBuildStateOptions { + liveSession?: CoworkLiveSessionState | null + requestedAction?: ActionStep | null + requestedActionToken?: number +} + +export interface CoworkBuildState { + actionMessages: CoworkBuildActionMessage[] + totalSteps: number + step: number + isLiveUpdate: boolean + hasActionHistory: boolean + isAwaitingTurnAction: boolean + currentAction?: ActionStep + currentToolCall?: CoworkLiveToolCall + fallbackContent: string + previewPath?: string + previewContent: string + browserUrl?: string + browserRaw?: string + searchKeyword?: string + searchResults?: CoworkBuildSearchResult + setStep: (step: number, liveUpdate?: boolean) => void + jumpToLatest: () => void +} diff --git a/frontend/src/components/cowork/cowork-header.tsx b/frontend/src/components/cowork/cowork-header.tsx new file mode 100644 index 000000000..e8abea150 --- /dev/null +++ b/frontend/src/components/cowork/cowork-header.tsx @@ -0,0 +1,39 @@ +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router' +import ButtonIcon from '@/components/button-icon' +import { Logo } from '@/components/logo' +import { ENABLE_BETA } from '@/constants/features' +import { useIsSageTheme } from '@/hooks/use-is-sage-theme' + +const CoworkHeader = () => { + const { t } = useTranslation() + const navigate = useNavigate() + const isSage = useIsSageTheme() + + return ( +
+ navigate('/')} + /> + +
+ + II-Cowork + +
+
+ ) +} + +export default CoworkHeader diff --git a/frontend/src/components/cowork/cowork-main.tsx b/frontend/src/components/cowork/cowork-main.tsx new file mode 100644 index 000000000..21ad0c726 --- /dev/null +++ b/frontend/src/components/cowork/cowork-main.tsx @@ -0,0 +1,103 @@ +import type { + CoworkChatSessionDetail, + CoworkLiveSessionState +} from '@/typings/cowork' +import type { ActionStep } from '@/typings/agent' +import { Icon } from '@/components/ui/icon' +import { cn } from '@/lib/utils' +import { COWORK_MODES, type CoworkModeId } from './cowork.constants' +import OrganizeFileFolderMode from './modes/organize-file-folder' + +interface CoworkMainProps { + activeMode: CoworkModeId | null + organizeSession: CoworkChatSessionDetail | null + organizeLiveSession: CoworkLiveSessionState | null + isOrganizeSessionLoading: boolean + isOrganizeChatSending: boolean + onSelectMode: (mode: CoworkModeId) => void + organizeModeResetVersion: number + onOrganizeWorkflowActiveChange: (active: boolean) => void + onOrganizeSessionCreated: (session: CoworkChatSessionDetail) => void + requestedOrganizeAction?: ActionStep | null + requestedOrganizeActionToken?: number +} + +const CoworkMain = ({ + activeMode, + organizeSession, + organizeLiveSession, + isOrganizeSessionLoading, + isOrganizeChatSending, + onSelectMode, + organizeModeResetVersion, + onOrganizeWorkflowActiveChange, + onOrganizeSessionCreated, + requestedOrganizeAction = null, + requestedOrganizeActionToken = 0 +}: CoworkMainProps) => { + if (activeMode === 'organize-file-folder') { + return ( + + ) + } + + return ( +
+
+
+

+ II-Cowork +

+

+ Homepage +

+

+ Choose a mode to enter the Cowork workflow. +

+
+ +
+ {COWORK_MODES.map((mode) => ( + + ))} +
+
+
+ ) +} + +export default CoworkMain diff --git a/frontend/src/components/cowork/cowork-page.tsx b/frontend/src/components/cowork/cowork-page.tsx new file mode 100644 index 000000000..36b070224 --- /dev/null +++ b/frontend/src/components/cowork/cowork-page.tsx @@ -0,0 +1,1872 @@ +import { listen } from '@tauri-apps/api/event' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { DesignModeProvider } from '@/components/design-mode' +import RightSidebar from '@/components/right-sidebar' +import { coworkService } from '@/services/cowork.service' +import Sidebar from '@/components/sidebar' +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle +} from '@/components/ui/alert-dialog' +import { Button } from '@/components/ui/button' +import { SidebarProvider } from '@/components/ui/sidebar' +import { + useAppSelector, + selectAvailableModels, + selectSelectedModel, + selectChatToolSettings, + selectSelectedGitHubRepository +} from '@/state' +import { toast } from 'sonner' +import type { ActionStep, Message } from '@/typings/agent' +import { TOOL } from '@/typings/agent' +import type { + CoworkChatLiveEvent, + CoworkChatMessage, + CoworkChatScope, + CoworkChatSessionDetail, + CoworkChatSessionSummary, + CoworkLiveActivity, + CoworkLiveActivityStatus, + CoworkLiveSessionState, + CoworkLiveToolCall +} from '@/typings/cowork' +import CoworkChatBox from './chat/cowork-chat-box' +import { + buildCoworkTranscriptMessageId, + resolveCoworkTranscriptAnchor +} from './chat/cowork-chatmessage-contract' +import CoworkHeader from './cowork-header' +import CoworkMain from './cowork-main' +import { type CoworkModeId } from './cowork.constants' +import CoworkSidebar from './cowork-sidebar' +import CoworkTabs from './cowork-tabs' + +const HOMEPAGE_SCOPE: CoworkChatScope = 'homepage' +const ORGANIZE_SCOPE: CoworkChatScope = 'organize-file-folder' + +const createScopeRecord = ( + initialValue: T +): Record => ({ + homepage: initialValue, + 'organize-file-folder': initialValue +}) + +const upsertChatSessionSummary = ( + sessions: CoworkChatSessionSummary[], + nextSession: CoworkChatSessionSummary +) => + [ + ...sessions.filter((session) => session.id !== nextSession.id), + nextSession + ].sort( + (left, right) => + new Date(right.updated_at).getTime() - + new Date(left.updated_at).getTime() + ) + +const buildChatSessionSummary = ( + session: CoworkChatSessionDetail +): CoworkChatSessionSummary => ({ + id: session.id, + scope: session.scope, + title: session.title, + preview: session.preview, + updated_at: session.updated_at, + message_count: session.messages.length +}) + +const MAX_LIVE_ACTIVITIES = 40 + +const buildSessionDetailFromSummary = ( + summary: CoworkChatSessionSummary, + current?: CoworkChatSessionDetail | null +): CoworkChatSessionDetail => ({ + id: summary.id, + scope: summary.scope, + title: summary.title, + preview: summary.preview, + updated_at: summary.updated_at, + message_count: summary.message_count, + runtime_kind: current?.runtime_kind, + runtime_session_id: current?.runtime_session_id, + messages: current?.messages ?? [], + runtime_events: current?.runtime_events ?? [], + files: current?.files ?? [], + run_status: current?.run_status ?? 'idle', + organize_tree_pair: current?.organize_tree_pair +}) + +const appendMessageIfMissing = ( + messages: CoworkChatMessage[], + nextMessage: CoworkChatMessage +) => { + if (messages.some((message) => message.id === nextMessage.id)) { + return messages + } + return [...messages, nextMessage] +} + +const stringifyLiveValue = (value: unknown) => { + if (typeof value === 'string') { + return value.trim() || undefined + } + + if (value === null || value === undefined) { + return undefined + } + + try { + const serialized = JSON.stringify(value, null, 2) + return serialized === '{}' || serialized === '[]' + ? undefined + : serialized + } catch { + return String(value) + } +} + +const readString = (record: Record, key: string) => { + const value = record[key] + return typeof value === 'string' && value.trim() ? value : undefined +} + +const normalizeCoworkToolNameForUi = (toolName?: string) => { + const normalized = toolName?.trim() + if (!normalized) { + return undefined + } + + switch (normalized.toLowerCase()) { + case 'ls': + return TOOL.LS + case 'bash': + return TOOL.BASH + case 'bashinit': + case 'bash_init': + return TOOL.BASH_INIT + case 'bashview': + case 'bash_view': + return TOOL.BASH_VIEW + case 'bashstop': + case 'bash_stop': + return TOOL.BASH_STOP + case 'bashkill': + case 'bash_kill': + return TOOL.BASH_KILL + case 'bashlist': + case 'bash_list': + return TOOL.BASH_LIST + case 'bashwritetoprocess': + case 'bash_write_to_process': + return TOOL.BASH_WRITE_TO_PROCESS + case 'read': + case 'read_file': + return TOOL.READ + case 'write': + case 'write_file': + return TOOL.WRITE + case 'edit': + case 'edit_file': + return TOOL.EDIT + case 'apply_patch': + return TOOL.APPLY_PATCH + case 'todowrite': + case 'todo_write': + return TOOL.TODO_WRITE + case 'glob': + return TOOL.GLOB + case 'grep': + case 'astgrep': + return TOOL.GREP + case 'multiedit': + case 'multi_edit': + return TOOL.MULTI_EDIT + case 'list_dir': + return TOOL.LS + default: + return normalized + } +} + +const readRecord = (value: unknown): Record | undefined => { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return undefined + } + + return value as Record +} + +const toTimestamp = (value?: string) => { + if (!value) { + return Date.now() + } + + const timestamp = new Date(value).getTime() + return Number.isNaN(timestamp) ? Date.now() : timestamp +} + +const inferToolDisplayName = (content: Record) => { + const skillName = + typeof content.tool_input === 'object' && + content.tool_input !== null && + 'skill' in content.tool_input && + typeof content.tool_input.skill === 'string' + ? content.tool_input.skill + : undefined + const toolName = + readString(content, 'display_name') ?? + readString(content, 'tool_display_name') ?? + readString(content, 'tool_name') ?? + 'Tool' + + if (toolName === 'Skill' && skillName) { + return `Skill: ${skillName}` + } + + return toolName +} + +const HIDDEN_TOOL_MESSAGE_TYPES = new Set([ + TOOL.SEQUENTIAL_THINKING, + TOOL.MESSAGE_USER, + TOOL.SUBMIT_PLAN, + TOOL.SUBMIT_PLAN_MODIFICATION_SUGGESTIONS, + TOOL.RETURN_CONTROL_TO_USER +]) + +const buildCoworkActionStep = ( + content: Record, + overrides?: Partial +): ActionStep | undefined => { + const rawToolName = readString(content, 'tool_name') + const toolName = normalizeCoworkToolNameForUi(rawToolName) + + if (!toolName) { + return undefined + } + + return { + type: toolName as TOOL, + data: { + ...(content as Record), + ...overrides, + tool_name: toolName, + tool_display_name: + readString(content, 'display_name') ?? + readString(content, 'tool_display_name') ?? + inferToolDisplayName(content), + raw_tool_name: rawToolName, + tool_logo: readString(content, 'tool_logo'), + tool_input: readRecord(content.tool_input) as + | ActionStep['data']['tool_input'] + | undefined + } as ActionStep['data'] + } +} + +const upsertEventMessage = (messages: Message[], nextMessage: Message) => { + const existingIndex = messages.findIndex( + (message) => message.id === nextMessage.id + ) + + if (existingIndex === -1) { + return [...messages, nextMessage] + } + + const nextMessages = [...messages] + nextMessages[existingIndex] = { + ...nextMessages[existingIndex], + ...nextMessage + } + return nextMessages +} + +const buildToolEventMessageId = ( + sessionId: string, + toolCallId: string | undefined, + toolName: string, + emittedAt: string +) => + toolCallId + ? `${sessionId}:tool:${toolCallId}` + : `${sessionId}:tool:${toolName}:${emittedAt}` + +const syncActionEventMessage = ( + messages: Message[], + action: ActionStep, + sessionId: string, + emittedAt: string +) => { + const toolCallId = action.data.tool_call_id + const nextMessageId = buildToolEventMessageId( + sessionId, + toolCallId, + action.type, + emittedAt + ) + + let matchIndex = -1 + + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index] + if (!message.action) { + continue + } + + if ( + toolCallId && + message.action.data.tool_call_id && + message.action.data.tool_call_id === toolCallId + ) { + matchIndex = index + break + } + + if ( + !toolCallId && + message.action.type === action.type && + !message.action.data.isResult + ) { + matchIndex = index + break + } + } + + if (matchIndex === -1) { + return { + messages: upsertEventMessage(messages, { + id: nextMessageId, + role: 'assistant', + action, + timestamp: toTimestamp(emittedAt) + }), + currentAction: action + } + } + + const nextMessages = [...messages] + nextMessages[matchIndex] = { + ...nextMessages[matchIndex], + action + } + + return { + messages: nextMessages, + currentAction: action + } +} + +const inferToolStatus = ( + remoteEventType: string, + isError = false +): CoworkLiveActivityStatus => { + if (isError || remoteEventType === 'error') { + return 'error' + } + if ( + remoteEventType === 'tool_confirmation' || + remoteEventType === 'waiting_for_user_input' + ) { + return 'waiting' + } + if ( + remoteEventType === 'tool_result' || + remoteEventType === 'complete' || + remoteEventType === 'stream_complete' || + remoteEventType === 'sub_agent_complete' + ) { + return 'completed' + } + return 'running' +} + +const appendActivity = ( + activities: CoworkLiveActivity[], + activity: CoworkLiveActivity +) => [...activities, activity].slice(-MAX_LIVE_ACTIVITIES) + +const appendRuntimeEventIfMissing = ( + events: CoworkChatSessionDetail['runtime_events'], + nextEvent: CoworkChatSessionDetail['runtime_events'][number] +) => { + const alreadyExists = events.some((event) => { + if (event.runtime_event_id && nextEvent.runtime_event_id) { + return ( + event.runtime_event_type === nextEvent.runtime_event_type && + event.runtime_event_id === nextEvent.runtime_event_id + ) + } + + return ( + event.runtime_event_type === nextEvent.runtime_event_type && + event.runtime_created_at === nextEvent.runtime_created_at && + event.emitted_at === nextEvent.emitted_at + ) + }) + + if (alreadyExists) { + return events + } + + return [...events, nextEvent].sort((left, right) => { + const leftTime = left.runtime_created_at ?? left.emitted_at + const rightTime = right.runtime_created_at ?? right.emitted_at + + if (leftTime === rightTime) { + return `${left.runtime_event_type}:${left.runtime_event_id ?? ''}`.localeCompare( + `${right.runtime_event_type}:${right.runtime_event_id ?? ''}` + ) + } + + return leftTime.localeCompare(rightTime) + }) +} + +const reduceCoworkLiveEvent = ( + current: CoworkLiveSessionState | undefined, + event: CoworkChatLiveEvent +): CoworkLiveSessionState | undefined => { + if (event.type !== 'runtime.event') { + return current + } + + const content = event.content ?? {} + const nextState: CoworkLiveSessionState = current ?? { + session_id: event.session_id, + scope: event.scope, + thinking: '', + response: '', + is_awaiting_turn_action: false, + tool_calls: [], + activities: [], + event_messages: [] + } + + const toolCallId = readString(content, 'tool_call_id') + const toolDisplayName = inferToolDisplayName(content) + const skillName = + typeof content.tool_input === 'object' && + content.tool_input !== null && + 'skill' in content.tool_input && + typeof content.tool_input.skill === 'string' + ? content.tool_input.skill + : undefined + const agentName = readString(content, 'agent_name') + const emittedAt = event.emitted_at + const resolveTranscriptId = ( + kind: 'thinking' | 'response', + currentMessageId: string | undefined + ) => + currentMessageId ?? + buildCoworkTranscriptMessageId( + kind, + resolveCoworkTranscriptAnchor( + event.runtime_event_id, + event.runtime_created_at, + emittedAt + ) ?? emittedAt + ) + + switch (event.runtime_event_type) { + case 'reasoning_delta': + case 'agent_thinking_delta': { + const delta = readString(content, 'text') + if (!delta) return nextState + return { + ...nextState, + thinking: `${nextState.thinking}${delta}`, + thinking_message_id: resolveTranscriptId( + 'thinking', + nextState.thinking_message_id + ), + thinking_started_at: nextState.thinking_started_at ?? emittedAt, + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } + case 'reasoning': + case 'agent_thinking': { + const text = readString(content, 'text') + return { + ...nextState, + thinking: text ?? nextState.thinking, + thinking_message_id: resolveTranscriptId( + 'thinking', + nextState.thinking_message_id + ), + thinking_started_at: nextState.thinking_started_at ?? emittedAt, + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } + case 'agent_response_delta': { + const delta = readString(content, 'text') + if (!delta) return nextState + return { + ...nextState, + response: `${nextState.response}${delta}`, + response_message_id: resolveTranscriptId( + 'response', + nextState.response_message_id + ), + response_started_at: nextState.response_started_at ?? emittedAt, + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } + case 'agent_response': { + const text = readString(content, 'text') + return { + ...nextState, + response: text ?? nextState.response, + response_message_id: resolveTranscriptId( + 'response', + nextState.response_message_id + ), + response_started_at: nextState.response_started_at ?? emittedAt, + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } + case 'tool_call': { + const input = stringifyLiveValue(content.tool_input) + const nextToolCall: CoworkLiveToolCall = { + id: toolCallId ?? `${event.session_id}:${toolDisplayName}`, + name: readString(content, 'tool_name') ?? toolDisplayName, + display_name: toolDisplayName, + input, + status: 'running', + logo: readString(content, 'tool_logo'), + skill_name: skillName, + agent_name: agentName + } + + const toolCalls = [ + ...nextState.tool_calls.filter( + (tool) => tool.id !== nextToolCall.id + ), + nextToolCall + ] + const action = buildCoworkActionStep(content) + const actionSync = + action && !HIDDEN_TOOL_MESSAGE_TYPES.has(action.type) + ? syncActionEventMessage( + nextState.event_messages, + action, + event.session_id, + emittedAt + ) + : null + + return { + ...nextState, + tool_calls: toolCalls, + is_awaiting_turn_action: actionSync + ? false + : nextState.is_awaiting_turn_action, + event_messages: + actionSync?.messages ?? nextState.event_messages, + current_action: + actionSync?.currentAction ?? nextState.current_action, + activities: appendActivity(nextState.activities, { + id: `${event.session_id}:${emittedAt}:tool-call:${nextToolCall.id}`, + runtime_event_type: event.runtime_event_type, + title: toolDisplayName, + detail: input, + timestamp: emittedAt, + status: 'running', + tool_call_id: nextToolCall.id, + tool_name: nextToolCall.name, + skill_name: skillName, + agent_name: agentName + }), + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } + case 'tool_result': { + const result = stringifyLiveValue(content.result) + const status = inferToolStatus( + event.runtime_event_type, + content.is_error === true + ) + const nextToolId = + toolCallId ?? `${event.session_id}:${toolDisplayName}` + const existingToolCall = nextState.tool_calls.find( + (tool) => tool.id === nextToolId + ) + + const toolCalls = [ + ...nextState.tool_calls.filter( + (tool) => tool.id !== nextToolId + ), + { + id: nextToolId, + name: + readString(content, 'tool_name') ?? + existingToolCall?.name ?? + toolDisplayName, + display_name: toolDisplayName, + input: + existingToolCall?.input ?? + stringifyLiveValue(content.tool_input), + result, + status, + logo: + readString(content, 'tool_logo') ?? + existingToolCall?.logo, + skill_name: skillName ?? existingToolCall?.skill_name, + agent_name: agentName ?? existingToolCall?.agent_name + } + ] + const action = buildCoworkActionStep(content, { + result: content.result as ActionStep['data']['result'], + isResult: true + }) + const actionSync = + action && !HIDDEN_TOOL_MESSAGE_TYPES.has(action.type) + ? syncActionEventMessage( + nextState.event_messages, + action, + event.session_id, + emittedAt + ) + : null + + return { + ...nextState, + tool_calls: toolCalls, + is_awaiting_turn_action: actionSync + ? false + : nextState.is_awaiting_turn_action, + event_messages: + actionSync?.messages ?? nextState.event_messages, + current_action: + actionSync?.currentAction ?? nextState.current_action, + activities: appendActivity(nextState.activities, { + id: `${event.session_id}:${emittedAt}:tool-result:${nextToolId}`, + runtime_event_type: event.runtime_event_type, + title: `${toolDisplayName} finished`, + detail: result, + timestamp: emittedAt, + status, + tool_call_id: nextToolId, + tool_name: + readString(content, 'tool_name') ?? toolDisplayName, + skill_name: skillName, + agent_name: agentName + }), + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } + case 'tool_confirmation': + case 'waiting_for_user_input': + case 'reasoning_start': + case 'agent_thinking_start': + case 'sub_agent_complete': + case 'processing': + case 'complete': + case 'stream_complete': + case 'system': + case 'error': + case 'agent_response_interrupted': + case 'model_compact': { + const detail = + readString(content, 'message') ?? + readString(content, 'text') ?? + stringifyLiveValue(content.result) ?? + stringifyLiveValue(content.summary) + const titleMap: Record = { + tool_confirmation: 'Waiting for confirmation', + waiting_for_user_input: 'Waiting for input', + reasoning_start: 'Thinking', + agent_thinking_start: 'Thinking', + sub_agent_complete: agentName + ? `Sub-agent finished: ${agentName}` + : 'Sub-agent finished', + processing: 'Processing', + complete: 'Run completed', + stream_complete: 'Stream completed', + system: 'System event', + error: 'Run error', + agent_response_interrupted: 'Run interrupted', + model_compact: 'Session compacted' + } + + return { + ...nextState, + activities: appendActivity(nextState.activities, { + id: `${event.session_id}:${emittedAt}:${event.runtime_event_type}`, + runtime_event_type: event.runtime_event_type, + title: titleMap[event.runtime_event_type] ?? 'Activity', + detail, + timestamp: emittedAt, + status: inferToolStatus( + event.runtime_event_type, + event.runtime_event_type === 'error' + ), + agent_name: agentName + }), + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } + default: + return { + ...nextState, + latest_runtime_event_type: event.runtime_event_type, + last_event_at: emittedAt + } + } +} + +const replayPersistedLiveSession = ( + session: CoworkChatSessionDetail | null | undefined +): CoworkLiveSessionState | null => { + if (!session || session.runtime_events.length === 0) { + return null + } + + const replayedState = session.runtime_events.reduce< + CoworkLiveSessionState | undefined + >((state, event) => reduceCoworkLiveEvent(state, event), undefined) + + if (!replayedState) { + return null + } + + return { + ...replayedState, + thinking: '', + response: '', + thinking_message_id: undefined, + response_message_id: undefined, + thinking_started_at: undefined, + response_started_at: undefined + } +} + +const CoworkPage = () => { + const [activeMode, setActiveMode] = useState(null) + const [isSidebarOpen, setIsSidebarOpen] = useState(false) + const [isChatSessionsBoardOpen, setIsChatSessionsBoardOpen] = + useState(false) + const [organizeModeResetVersion, setOrganizeModeResetVersion] = useState(0) + const [isSessionsBoardOpen, setIsSessionsBoardOpen] = useState(false) + const [isOrganizeWorkflowActive, setIsOrganizeWorkflowActive] = + useState(false) + const [pendingDeleteSession, setPendingDeleteSession] = + useState(null) + const [isDeletingSession, setIsDeletingSession] = useState(false) + const [chatSessionsByScope, setChatSessionsByScope] = useState< + Record + >(() => createScopeRecord([])) + const [activeChatSessionIds, setActiveChatSessionIds] = useState< + Record + >(() => createScopeRecord(null)) + const [activeChatSessionsByScope, setActiveChatSessionsByScope] = useState< + Record + >(() => createScopeRecord(null)) + const [isChatSessionsLoadingByScope, setIsChatSessionsLoadingByScope] = + useState>(() => + createScopeRecord(true) + ) + const [isChatSessionLoadingByScope, setIsChatSessionLoadingByScope] = + useState>(() => + createScopeRecord(false) + ) + const [isChatSendingByScope, setIsChatSendingByScope] = useState< + Record + >(() => createScopeRecord(false)) + const [liveSessionsById, setLiveSessionsById] = useState< + Record + >({}) + const [requestedOrganizeAction, setRequestedOrganizeAction] = + useState(null) + const [requestedOrganizeActionToken, setRequestedOrganizeActionToken] = + useState(0) + const recoveringSessionIdsRef = useRef>(new Set()) + const suppressedStreamingSessionIdsRef = useRef>(new Set()) + const availableModels = useAppSelector(selectAvailableModels) + const selectedModelId = useAppSelector(selectSelectedModel) + const chatToolSettings = useAppSelector(selectChatToolSettings) + const selectedGitHubRepository = useAppSelector( + selectSelectedGitHubRepository + ) + + const currentChatScope: CoworkChatScope = + activeMode === 'organize-file-folder' ? ORGANIZE_SCOPE : HOMEPAGE_SCOPE + + const currentActiveChatSession = activeChatSessionsByScope[currentChatScope] + const replayedCurrentLiveSession = useMemo( + () => replayPersistedLiveSession(currentActiveChatSession), + [currentActiveChatSession] + ) + const currentLiveSession = currentActiveChatSession + ? (liveSessionsById[currentActiveChatSession.id] ?? + replayedCurrentLiveSession ?? + null) + : null + const isCurrentChatSessionLoading = + isChatSessionLoadingByScope[currentChatScope] + const isCurrentChatSending = isChatSendingByScope[currentChatScope] + const isCurrentChatInputLocked = + activeMode === 'organize-file-folder' && !isOrganizeWorkflowActive + + const homepageChatSessions = chatSessionsByScope[HOMEPAGE_SCOPE] + const homepageActiveChatSessionId = activeChatSessionIds[HOMEPAGE_SCOPE] + const organizeChatSessions = chatSessionsByScope[ORGANIZE_SCOPE] + const organizeActiveChatSessionId = activeChatSessionIds[ORGANIZE_SCOPE] + const organizeActiveChatSession = activeChatSessionsByScope[ORGANIZE_SCOPE] + const replayedOrganizeLiveSession = useMemo( + () => replayPersistedLiveSession(organizeActiveChatSession), + [organizeActiveChatSession] + ) + const organizeLiveSession = organizeActiveChatSession + ? (liveSessionsById[organizeActiveChatSession.id] ?? + replayedOrganizeLiveSession ?? + null) + : null + const isOrganizeSessionLoading = isChatSessionLoadingByScope[ORGANIZE_SCOPE] + + const resetOrganizeSessionState = useCallback(() => { + setActiveChatSessionIds((prev) => ({ + ...prev, + [ORGANIZE_SCOPE]: null + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [ORGANIZE_SCOPE]: null + })) + }, []) + + const setScopeLoading = useCallback( + (scope: CoworkChatScope, isLoading: boolean) => { + setIsChatSessionsLoadingByScope((prev) => ({ + ...prev, + [scope]: isLoading + })) + }, + [] + ) + + const setScopeSessionLoading = useCallback( + (scope: CoworkChatScope, isLoading: boolean) => { + setIsChatSessionLoadingByScope((prev) => ({ + ...prev, + [scope]: isLoading + })) + }, + [] + ) + + const setScopeSending = useCallback( + (scope: CoworkChatScope, isSending: boolean) => { + setIsChatSendingByScope((prev) => ({ + ...prev, + [scope]: isSending + })) + }, + [] + ) + + const prepareLiveRunState = useCallback( + ( + sessionId: string | null | undefined, + session?: CoworkChatSessionDetail | null + ) => { + if (!sessionId) { + return + } + + setLiveSessionsById((prev) => { + const current = prev[sessionId] + const replayed = current + ? null + : replayPersistedLiveSession(session) + const baseline: CoworkLiveSessionState = current ?? + replayed ?? { + session_id: sessionId, + scope: session?.scope ?? currentChatScope, + thinking: '', + response: '', + is_awaiting_turn_action: false, + tool_calls: [], + activities: [], + event_messages: [] + } + + return { + ...prev, + [sessionId]: { + ...baseline, + thinking: '', + response: '', + is_awaiting_turn_action: true, + thinking_message_id: undefined, + response_message_id: undefined, + thinking_started_at: undefined, + response_started_at: undefined, + tool_calls: [], + current_action: undefined + } + } + }) + }, + [currentChatScope] + ) + + const finalizeLiveRunState = useCallback( + (sessionId: string | null | undefined) => { + if (!sessionId) { + return + } + + setLiveSessionsById((prev) => { + const current = prev[sessionId] + if (!current) { + return prev + } + + return { + ...prev, + [sessionId]: { + ...current, + thinking: '', + response: '', + is_awaiting_turn_action: false, + thinking_message_id: undefined, + response_message_id: undefined, + thinking_started_at: undefined, + response_started_at: undefined + } + } + }) + }, + [] + ) + + const removeLiveSessionState = useCallback( + (sessionId: string | null | undefined) => { + if (!sessionId) { + return + } + + setLiveSessionsById((prev) => { + if (!prev[sessionId]) { + return prev + } + + const next = { ...prev } + delete next[sessionId] + return next + }) + }, + [] + ) + + const handleStopCurrentChatSession = useCallback(async () => { + const activeSession = currentActiveChatSession + + if (!activeSession) { + return + } + + if ( + activeSession.run_status !== 'thinking' && + activeSession.run_status !== 'waiting_for_input' + ) { + return + } + + try { + suppressedStreamingSessionIdsRef.current.add(activeSession.id) + const stoppedSession = await coworkService.stopChatSession( + activeSession.id, + activeSession.scope + ) + recoveringSessionIdsRef.current.delete(stoppedSession.id) + removeLiveSessionState(stoppedSession.id) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [stoppedSession.scope]: stoppedSession + })) + setChatSessionsByScope((prev) => ({ + ...prev, + [stoppedSession.scope]: upsertChatSessionSummary( + prev[stoppedSession.scope], + buildChatSessionSummary(stoppedSession) + ) + })) + } catch (error) { + console.error('Failed to stop Cowork session', error) + suppressedStreamingSessionIdsRef.current.delete(activeSession.id) + toast.error( + error instanceof Error + ? error.message + : 'Unable to stop the Cowork session right now.' + ) + } + }, [currentActiveChatSession, removeLiveSessionState]) + + useEffect(() => { + const session = currentActiveChatSession + if (!session) { + return + } + + const hasActiveLiveStream = Boolean(liveSessionsById[session.id]) + + if ( + hasActiveLiveStream || + isCurrentChatSending || + (session.run_status !== 'thinking' && + session.run_status !== 'waiting_for_input') + ) { + recoveringSessionIdsRef.current.delete(session.id) + return + } + + if (recoveringSessionIdsRef.current.has(session.id)) { + return + } + + recoveringSessionIdsRef.current.add(session.id) + void handleStopCurrentChatSession() + }, [ + currentActiveChatSession, + handleStopCurrentChatSession, + isCurrentChatSending, + liveSessionsById + ]) + + useEffect(() => { + let unlisten: (() => void) | undefined + + void listen('cowork://stream', ({ payload }) => { + if (!payload) { + return + } + + const eventSessionId = + payload.type === 'session.created' || + payload.type === 'session.updated' + ? payload.session.id + : 'session_id' in payload + ? payload.session_id + : undefined + + if ( + eventSessionId && + suppressedStreamingSessionIdsRef.current.has(eventSessionId) + ) { + return + } + + if ( + payload.type === 'session.created' || + payload.type === 'session.updated' + ) { + const session = payload.session + const shouldActivateSession = payload.type === 'session.created' + setChatSessionsByScope((prev) => ({ + ...prev, + [session.scope]: upsertChatSessionSummary( + prev[session.scope], + session + ) + })) + if (shouldActivateSession) { + setActiveChatSessionIds((prev) => ({ + ...prev, + [session.scope]: session.id + })) + } + setActiveChatSessionsByScope((prev) => { + const currentSession = prev[session.scope] + const shouldMerge = + shouldActivateSession || + currentSession?.id === session.id + + if (!shouldMerge) { + return prev + } + + return { + ...prev, + [session.scope]: buildSessionDetailFromSummary( + session, + currentSession + ) + } + }) + return + } + + if (payload.type === 'message.created') { + setActiveChatSessionsByScope((prev) => { + const currentSession = prev[payload.scope] + if ( + !currentSession || + currentSession.id !== payload.session_id + ) { + return prev + } + + const nextSession = { + ...currentSession, + messages: appendMessageIfMissing( + currentSession.messages, + payload.message + ), + message_count: + currentSession.messages.length + + (currentSession.messages.some( + (message) => message.id === payload.message.id + ) + ? 0 + : 1), + updated_at: payload.message.created_at, + preview: + payload.message.role === 'assistant' + ? payload.message.content + : currentSession.preview + } + + return { + ...prev, + [payload.scope]: nextSession + } + }) + return + } + + if (payload.type === 'files.updated') { + setActiveChatSessionsByScope((prev) => { + const currentSession = prev[payload.scope] + if ( + !currentSession || + currentSession.id !== payload.session_id + ) { + return prev + } + + return { + ...prev, + [payload.scope]: { + ...currentSession, + files: payload.files + } + } + }) + return + } + + if (payload.type === 'status.updated') { + setActiveChatSessionsByScope((prev) => { + const currentSession = prev[payload.scope] + if ( + !currentSession || + currentSession.id !== payload.session_id + ) { + return prev + } + + return { + ...prev, + [payload.scope]: { + ...currentSession, + run_status: payload.status + } + } + }) + } + + if (payload.type === 'runtime.event') { + setActiveChatSessionsByScope((prev) => { + const currentSession = prev[payload.scope] + if ( + !currentSession || + currentSession.id !== payload.session_id + ) { + return prev + } + + return { + ...prev, + [payload.scope]: { + ...currentSession, + runtime_events: appendRuntimeEventIfMissing( + currentSession.runtime_events, + payload + ), + updated_at: payload.emitted_at + } + } + }) + setLiveSessionsById((prev) => { + const nextState = reduceCoworkLiveEvent( + prev[payload.session_id], + payload + ) + if (!nextState) { + return prev + } + + return { + ...prev, + [payload.session_id]: nextState + } + }) + } + }).then((dispose) => { + unlisten = dispose + }) + + return () => { + unlisten?.() + } + }, []) + + const refreshChatSessions = useCallback( + async (scope: CoworkChatScope) => { + setScopeLoading(scope, true) + try { + const sessions = await coworkService.getChatSessions(scope) + setChatSessionsByScope((prev) => ({ + ...prev, + [scope]: sessions + })) + } finally { + setScopeLoading(scope, false) + } + }, + [setScopeLoading] + ) + + const loadChatSession = useCallback( + async (sessionId: string, scope: CoworkChatScope) => { + setScopeSessionLoading(scope, true) + + try { + let session = await coworkService.getChatSession( + sessionId, + scope + ) + if ( + session.run_status === 'thinking' || + session.run_status === 'waiting_for_input' + ) { + session = await coworkService.stopChatSession( + session.id, + session.scope + ) + } + setActiveChatSessionIds((prev) => ({ + ...prev, + [session.scope]: session.id + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [session.scope]: session + })) + setChatSessionsByScope((prev) => ({ + ...prev, + [session.scope]: upsertChatSessionSummary( + prev[session.scope], + { + id: session.id, + scope: session.scope, + title: session.title, + preview: session.preview, + updated_at: session.updated_at, + message_count: session.messages.length + } + ) + })) + } finally { + setScopeSessionLoading(scope, false) + } + }, + [setScopeSessionLoading] + ) + + useEffect(() => { + void Promise.all([ + refreshChatSessions(HOMEPAGE_SCOPE), + refreshChatSessions(ORGANIZE_SCOPE) + ]) + }, [refreshChatSessions]) + + const handleGoHomepage = () => { + setActiveMode(null) + setIsSidebarOpen(false) + setIsChatSessionsBoardOpen(false) + setIsSessionsBoardOpen(false) + setIsOrganizeWorkflowActive(false) + } + + const handleSelectMode = (mode: CoworkModeId) => { + setActiveMode(mode) + setIsChatSessionsBoardOpen(false) + setIsSessionsBoardOpen(false) + + if (mode === 'organize-file-folder') { + resetOrganizeSessionState() + setOrganizeModeResetVersion((prev) => prev + 1) + setIsOrganizeWorkflowActive(false) + setRequestedOrganizeAction(null) + setRequestedOrganizeActionToken(0) + } + } + + const handleChatSessionsBoardOpenChange = (open: boolean) => { + setIsChatSessionsBoardOpen(open) + if (open) { + setIsSessionsBoardOpen(false) + } + } + + const handleSessionsBoardOpenChange = (open: boolean) => { + setIsSessionsBoardOpen(open) + } + + const handleOrganizeWorkflowActiveChange = (active: boolean) => { + setIsOrganizeWorkflowActive(active) + } + + const handleOpenModeFirstPage = () => { + if (activeMode === 'organize-file-folder') { + resetOrganizeSessionState() + setOrganizeModeResetVersion((prev) => prev + 1) + setIsOrganizeWorkflowActive(false) + } + + setIsChatSessionsBoardOpen(false) + setIsSessionsBoardOpen(false) + } + + const handleSelectSession = async (sessionId: string) => { + setIsChatSessionsBoardOpen(false) + setIsSessionsBoardOpen(false) + + if (sessionId !== activeChatSessionIds[ORGANIZE_SCOPE]) { + await loadChatSession(sessionId, ORGANIZE_SCOPE) + } + } + + const handleRenameSession = useCallback( + async (sessionId: string) => { + const currentSummary = chatSessionsByScope[ORGANIZE_SCOPE].find( + (session) => session.id === sessionId + ) + const nextTitle = window.prompt( + 'Rename session', + currentSummary?.title ?? '' + ) + + if (!nextTitle || !nextTitle.trim()) { + return + } + + const renamedSession = await coworkService.renameOrganizeSession( + sessionId, + nextTitle + ) + + setChatSessionsByScope((prev) => ({ + ...prev, + [ORGANIZE_SCOPE]: upsertChatSessionSummary( + prev[ORGANIZE_SCOPE], + buildChatSessionSummary(renamedSession) + ) + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [ORGANIZE_SCOPE]: + prev[ORGANIZE_SCOPE]?.id === renamedSession.id + ? renamedSession + : prev[ORGANIZE_SCOPE] + })) + }, + [chatSessionsByScope] + ) + + const handleRenameChatSession = useCallback( + async (sessionId: string) => { + const currentSummary = chatSessionsByScope[HOMEPAGE_SCOPE].find( + (session) => session.id === sessionId + ) + const nextTitle = window.prompt( + 'Rename chat session', + currentSummary?.title ?? '' + ) + + if (!nextTitle || !nextTitle.trim()) { + return + } + + const renamedSession = + await coworkService.renameHomepageChatSession( + sessionId, + nextTitle + ) + + setChatSessionsByScope((prev) => ({ + ...prev, + [HOMEPAGE_SCOPE]: upsertChatSessionSummary( + prev[HOMEPAGE_SCOPE], + buildChatSessionSummary(renamedSession) + ) + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [HOMEPAGE_SCOPE]: + prev[HOMEPAGE_SCOPE]?.id === renamedSession.id + ? renamedSession + : prev[HOMEPAGE_SCOPE] + })) + }, + [chatSessionsByScope] + ) + + const handleDeleteSession = useCallback( + (sessionId: string) => { + const currentSummary = chatSessionsByScope[ORGANIZE_SCOPE].find( + (session) => session.id === sessionId + ) + + setPendingDeleteSession( + currentSummary ?? { + id: sessionId, + scope: ORGANIZE_SCOPE, + title: sessionId, + preview: '', + updated_at: '', + message_count: 0 + } + ) + }, + [chatSessionsByScope] + ) + + const handleDeleteChatSession = useCallback( + (sessionId: string) => { + const currentSummary = chatSessionsByScope[HOMEPAGE_SCOPE].find( + (session) => session.id === sessionId + ) + + setPendingDeleteSession( + currentSummary ?? { + id: sessionId, + scope: HOMEPAGE_SCOPE, + title: sessionId, + preview: '', + updated_at: '', + message_count: 0 + } + ) + }, + [chatSessionsByScope] + ) + + const handleConfirmDeleteSession = useCallback(async () => { + if (!pendingDeleteSession || isDeletingSession) { + return + } + + const sessionId = pendingDeleteSession.id + const sessionScope = pendingDeleteSession.scope + setIsDeletingSession(true) + + try { + if (sessionScope === HOMEPAGE_SCOPE) { + await coworkService.deleteHomepageChatSession(sessionId) + } else { + await coworkService.deleteOrganizeSession(sessionId) + } + + setChatSessionsByScope((prev) => ({ + ...prev, + [sessionScope]: prev[sessionScope].filter( + (session) => session.id !== sessionId + ) + })) + + if (sessionScope === HOMEPAGE_SCOPE) { + if (activeChatSessionIds[HOMEPAGE_SCOPE] === sessionId) { + setActiveChatSessionIds((prev) => ({ + ...prev, + [HOMEPAGE_SCOPE]: null + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [HOMEPAGE_SCOPE]: null + })) + } + } else if (activeChatSessionIds[ORGANIZE_SCOPE] === sessionId) { + resetOrganizeSessionState() + setOrganizeModeResetVersion((prev) => prev + 1) + setIsOrganizeWorkflowActive(false) + } + + removeLiveSessionState(sessionId) + setPendingDeleteSession(null) + } finally { + setIsDeletingSession(false) + } + }, [ + activeChatSessionIds, + isDeletingSession, + pendingDeleteSession, + removeLiveSessionState, + resetOrganizeSessionState + ]) + + const handleDeleteDialogOpenChange = useCallback( + (open: boolean) => { + if (!open && !isDeletingSession) { + setPendingDeleteSession(null) + } + }, + [isDeletingSession] + ) + + const handleOrganizeSessionCreated = useCallback( + (session: CoworkChatSessionDetail) => { + setChatSessionsByScope((prev) => ({ + ...prev, + [ORGANIZE_SCOPE]: upsertChatSessionSummary( + prev[ORGANIZE_SCOPE], + buildChatSessionSummary(session) + ) + })) + setActiveChatSessionIds((prev) => ({ + ...prev, + [ORGANIZE_SCOPE]: session.id + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [ORGANIZE_SCOPE]: session + })) + }, + [] + ) + + const handleSelectCoworkAction = useCallback( + (action: ActionStep) => { + if (currentChatScope !== ORGANIZE_SCOPE) { + return + } + + setRequestedOrganizeAction(action) + setRequestedOrganizeActionToken((prev) => prev + 1) + }, + [currentChatScope] + ) + + const handleSelectChatSession = async (sessionId: string) => { + setActiveMode(null) + setIsChatSessionsBoardOpen(false) + setIsSessionsBoardOpen(false) + + if (sessionId !== homepageActiveChatSessionId) { + await loadChatSession(sessionId, HOMEPAGE_SCOPE) + } + } + + const handleNewChatSession = () => { + setActiveMode(null) + setIsChatSessionsBoardOpen(false) + setIsSessionsBoardOpen(false) + setActiveChatSessionIds((prev) => ({ + ...prev, + [HOMEPAGE_SCOPE]: null + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [HOMEPAGE_SCOPE]: null + })) + } + + const handleSendChatMessage = useCallback( + async (content: string) => { + const scope = currentChatScope + const currentSessionId = activeChatSessionIds[scope] + const currentSession = activeChatSessionsByScope[scope] + const selectedModel = + availableModels.find((model) => model.id === selectedModelId) ?? + availableModels[0] + + if (!selectedModel) { + toast.error( + 'No AI model is configured. Please add a model in settings first.' + ) + return + } + + if (scope === ORGANIZE_SCOPE) { + setRequestedOrganizeAction(null) + setRequestedOrganizeActionToken((prev) => prev + 1) + } + + setScopeSending(scope, true) + prepareLiveRunState(currentSessionId, currentSession) + + try { + const response = await coworkService.sendChatMessage({ + session_id: currentSessionId, + runtime_kind: currentSession?.runtime_kind ?? 'remote', + scope, + content, + model_id: selectedModel.id, + tools: chatToolSettings, + github_repository: selectedGitHubRepository + }) + + let latestSummary: CoworkChatSessionSummary | null = null + + for (const event of response.events) { + if ( + event.type === 'session.created' || + event.type === 'session.updated' + ) { + latestSummary = event.session + } + } + + let hydratedSession = await coworkService.getChatSession( + response.session_id, + scope + ) + const isSuppressed = + suppressedStreamingSessionIdsRef.current.has( + hydratedSession.id + ) + let resolvedSession = isSuppressed + ? await coworkService.stopChatSession( + hydratedSession.id, + scope + ) + : hydratedSession + + if ( + scope === ORGANIZE_SCOPE && + resolvedSession.organize_tree_pair?.source_root + ) { + resolvedSession = await coworkService.getChatSession( + resolvedSession.id, + scope + ) + } + + const sessionSummary = + latestSummary ?? buildChatSessionSummary(resolvedSession) + + setChatSessionsByScope((prev) => ({ + ...prev, + [scope]: upsertChatSessionSummary( + prev[scope], + sessionSummary + ) + })) + setActiveChatSessionIds((prev) => ({ + ...prev, + [scope]: resolvedSession.id + })) + setActiveChatSessionsByScope((prev) => ({ + ...prev, + [scope]: resolvedSession + })) + finalizeLiveRunState(resolvedSession.id) + suppressedStreamingSessionIdsRef.current.delete( + resolvedSession.id + ) + } catch (error) { + console.error('Failed to send Cowork chat message', error) + toast.error( + error instanceof Error + ? error.message + : 'Unable to send the Cowork message right now.' + ) + } finally { + if (currentSessionId) { + suppressedStreamingSessionIdsRef.current.delete( + currentSessionId + ) + } + setScopeSending(scope, false) + } + }, + [ + activeChatSessionIds, + activeChatSessionsByScope, + availableModels, + chatToolSettings, + finalizeLiveRunState, + currentChatScope, + prepareLiveRunState, + selectedGitHubRepository, + selectedModelId, + setScopeSending + ] + ) + + return ( + +
+ +
+ + +
+
+ setIsSidebarOpen(true)} + onGoHomepage={handleGoHomepage} + onOpenModeFirstPage={ + handleOpenModeFirstPage + } + isChatSessionsBoardOpen={ + isChatSessionsBoardOpen + } + onChatSessionsBoardOpenChange={ + handleChatSessionsBoardOpenChange + } + chatSessions={homepageChatSessions} + activeChatSessionId={ + homepageActiveChatSessionId + } + isChatSessionsLoading={ + isChatSessionsLoadingByScope[ + HOMEPAGE_SCOPE + ] + } + onNewChatSession={handleNewChatSession} + onSelectChatSession={ + handleSelectChatSession + } + onRenameChatSession={ + handleRenameChatSession + } + onDeleteChatSession={ + handleDeleteChatSession + } + isSessionsBoardOpen={isSessionsBoardOpen} + onSessionsBoardOpenChange={ + handleSessionsBoardOpenChange + } + modeSessions={organizeChatSessions} + activeModeSessionId={ + organizeActiveChatSessionId + } + isModeSessionsLoading={ + isChatSessionsLoadingByScope[ + ORGANIZE_SCOPE + ] + } + onSelectSession={handleSelectSession} + onRenameSession={handleRenameSession} + onDeleteSession={handleDeleteSession} + /> +
+ +
+
+ +
+
+
+ + + + + + + {pendingDeleteSession?.scope === HOMEPAGE_SCOPE + ? 'Delete chat session?' + : 'Delete organize session?'} + + + {`Session "${pendingDeleteSession?.title ?? ''}" will be removed from this device.`} + + + + + Cancel + + + + + +
+
+ ) +} + +export default CoworkPage diff --git a/frontend/src/components/cowork/cowork-sidebar.tsx b/frontend/src/components/cowork/cowork-sidebar.tsx new file mode 100644 index 000000000..13a24b8f2 --- /dev/null +++ b/frontend/src/components/cowork/cowork-sidebar.tsx @@ -0,0 +1,146 @@ +import { Icon } from '@/components/ui/icon' +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle +} from '@/components/ui/sheet' +import { cn } from '@/lib/utils' +import { + COWORK_HOME_LABEL, + COWORK_MODES, + type CoworkModeId +} from './cowork.constants' + +interface CoworkSidebarProps { + open: boolean + activeMode: CoworkModeId | null + onOpenChange: (open: boolean) => void + onGoHomepage: () => void + onSelectMode: (mode: CoworkModeId) => void +} + +const CoworkSidebar = ({ + open, + activeMode, + onOpenChange, + onGoHomepage, + onSelectMode +}: CoworkSidebarProps) => { + return ( + + + +
+
+ + II-Cowork + + + Workspace navigation + +
+ + + +
+
+
+ +
+

+ Modes +

+
+ {COWORK_MODES.map((mode) => { + const isActive = activeMode === mode.id + + return ( + + ) + })} +
+
+
+
+
+ ) +} + +export default CoworkSidebar diff --git a/frontend/src/components/cowork/cowork-tabs.tsx b/frontend/src/components/cowork/cowork-tabs.tsx new file mode 100644 index 000000000..84e591bd9 --- /dev/null +++ b/frontend/src/components/cowork/cowork-tabs.tsx @@ -0,0 +1,352 @@ +import ButtonIcon from '@/components/button-icon' +import { Pencil, Plus, Trash2 } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@/components/ui/popover' +import { + COWORK_HOME_LABEL, + getCoworkModeLabel, + type CoworkModeId +} from './cowork.constants' +import type { CoworkChatSessionSummary } from '@/typings/cowork' + +interface CoworkTabsProps { + activeMode: CoworkModeId | null + onOpenSidebar: () => void + onGoHomepage: () => void + onOpenModeFirstPage: () => void + isChatSessionsBoardOpen: boolean + onChatSessionsBoardOpenChange: (open: boolean) => void + chatSessions: CoworkChatSessionSummary[] + activeChatSessionId: string | null + isChatSessionsLoading: boolean + onNewChatSession: () => void + onSelectChatSession: (sessionId: string) => void + onRenameChatSession: (sessionId: string) => void + onDeleteChatSession: (sessionId: string) => void + isSessionsBoardOpen: boolean + onSessionsBoardOpenChange: (open: boolean) => void + modeSessions: CoworkChatSessionSummary[] + activeModeSessionId: string | null + isModeSessionsLoading: boolean + onSelectSession: (sessionId: string) => void + onRenameSession: (sessionId: string) => void + onDeleteSession: (sessionId: string) => void +} + +const getSessionPreviewLabel = (preview: string) => { + const trimmedPreview = preview.trim() + + if (!trimmedPreview.startsWith('Source:')) { + return trimmedPreview + } + + const normalizedPath = trimmedPreview + .slice('Source:'.length) + .trim() + .replace(/[\\/]+$/, '') + + if (!normalizedPath) { + return trimmedPreview + } + + const folderName = normalizedPath + .split(/[\\/]/) + .filter(Boolean) + .at(-1) + + return `Source: ${folderName || normalizedPath}` +} + +const CoworkTabs = ({ + activeMode, + onOpenSidebar, + onGoHomepage, + onOpenModeFirstPage, + isChatSessionsBoardOpen, + onChatSessionsBoardOpenChange, + chatSessions, + activeChatSessionId, + isChatSessionsLoading, + onNewChatSession, + onSelectChatSession, + onRenameChatSession, + onDeleteChatSession, + isSessionsBoardOpen, + onSessionsBoardOpenChange, + modeSessions, + activeModeSessionId, + isModeSessionsLoading, + onSelectSession, + onRenameSession, + onDeleteSession +}: CoworkTabsProps) => { + const currentLabel = activeMode ? getCoworkModeLabel(activeMode) : null + + return ( +
+
+
+ + + {currentLabel && ( + + )} +
+ {activeMode === null && ( + + + + + +
+

+ Chat Sessions +

+
+
+ + {isChatSessionsLoading ? ( +
+ Loading chat sessions... +
+ ) : chatSessions.length > 0 ? ( + chatSessions.map((session) => ( +
+ onSelectChatSession(session.id) + } + onKeyDown={(event) => { + if ( + event.key === 'Enter' || + event.key === ' ' + ) { + event.preventDefault() + onSelectChatSession( + session.id + ) + } + }} + > +
+

+ {session.title} +

+

+ {session.preview} +

+
+
+ + +
+
+ )) + ) : ( +
+ No chat sessions yet. +
+ )} +
+
+
+ )} + {activeMode === 'organize-file-folder' && ( +
+ + + + + +
+

+ Sessions +

+
+
+ {isModeSessionsLoading ? ( +
+ Loading mode sessions... +
+ ) : modeSessions.length > 0 ? ( + modeSessions.map((session) => ( +
+ onSelectSession(session.id) + } + onKeyDown={(event) => { + if ( + event.key === 'Enter' || + event.key === ' ' + ) { + event.preventDefault() + onSelectSession( + session.id + ) + } + }} + > +
+

+ {session.title} +

+

+ {getSessionPreviewLabel( + session.preview + )} +

+
+
+ + +
+
+ )) + ) : ( +
+ No mode sessions yet. +
+ )} +
+
+
+
+ )} +
+
+ ) +} + +export default CoworkTabs diff --git a/frontend/src/components/cowork/cowork.constants.ts b/frontend/src/components/cowork/cowork.constants.ts new file mode 100644 index 000000000..2946f9dcc --- /dev/null +++ b/frontend/src/components/cowork/cowork.constants.ts @@ -0,0 +1,13 @@ +export const COWORK_HOME_LABEL = 'Homepage' + +export const COWORK_MODES = [ + { + id: 'organize-file-folder', + label: 'Organize file/folder' + } +] as const + +export type CoworkModeId = (typeof COWORK_MODES)[number]['id'] + +export const getCoworkModeLabel = (mode: CoworkModeId | null) => + COWORK_MODES.find((item) => item.id === mode)?.label ?? COWORK_HOME_LABEL diff --git a/frontend/src/components/cowork/modes/organize-file-folder.tsx b/frontend/src/components/cowork/modes/organize-file-folder.tsx new file mode 100644 index 000000000..21b047eb6 --- /dev/null +++ b/frontend/src/components/cowork/modes/organize-file-folder.tsx @@ -0,0 +1,539 @@ +import { AnimatePresence, motion } from 'framer-motion' +import { FolderOpen, LoaderCircle } from 'lucide-react' +import { useEffect, useLayoutEffect, useMemo, useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { coworkService } from '@/services/cowork.service' +import type { + CoworkChatSessionDetail, + CoworkLiveSessionState, + CoworkOrganizeTreeNode, + CoworkOrganizeTreePair +} from '@/typings/cowork' +import type { ActionStep } from '@/typings/agent' +import { cn } from '@/lib/utils' +import { getCurrentWindow } from '@tauri-apps/api/window' +import CoworkOrganizeBuild from '../organize-file-folder/cowork-organize-build' +import CoworkOrganizeResult from '../organize-file-folder/cowork-organize-result' +import CoworkOrganizeSource from '../organize-file-folder/cowork-organize-source' +import { ORGANIZE_TREE_READ_OPTIONS } from '../organize-file-folder/organize-tree-utils' +import CoworkOrganizeSteps, { + type CoworkOrganizeStep +} from '../organize-file-folder/cowork-organize-steps' + +const organizeStepTransition = { + duration: 0.14, + ease: 'easeOut' +} as const + +interface OrganizeFileFolderModeProps { + resetVersion?: number + session?: CoworkChatSessionDetail | null + liveSession?: CoworkLiveSessionState | null + isSessionLoading?: boolean + isSending?: boolean + onWorkflowActiveChange?: (active: boolean) => void + onSessionCreated?: (session: CoworkChatSessionDetail) => void + requestedBuildAction?: ActionStep | null + requestedBuildActionToken?: number +} + +const OrganizeFileFolderMode = ({ + resetVersion = 0, + session = null, + liveSession = null, + isSessionLoading = false, + isSending = false, + onWorkflowActiveChange, + onSessionCreated, + requestedBuildAction = null, + requestedBuildActionToken = 0 +}: OrganizeFileFolderModeProps) => { + const [sourcePath, setSourcePath] = useState('') + const [isLoadingPath, setIsLoadingPath] = useState(false) + const [loadError, setLoadError] = useState(null) + const [isDragActive, setIsDragActive] = useState(false) + const [loadedTreePair, setLoadedTreePair] = + useState(null) + const [isProcessing, setIsProcessing] = useState(false) + const [activeStep, setActiveStep] = useState('source') + + useEffect(() => { + onWorkflowActiveChange?.(isProcessing) + }, [isProcessing, onWorkflowActiveChange]) + + useLayoutEffect(() => { + setSourcePath('') + setLoadError(null) + setIsDragActive(false) + setLoadedTreePair(null) + setIsLoadingPath(false) + setIsProcessing(false) + setActiveStep('source') + }, [resetVersion]) + + useLayoutEffect(() => { + if (!session) return + setSourcePath(session.organize_tree_pair?.source_root ?? '') + setLoadedTreePair(null) + setActiveStep('source') + setIsProcessing(true) + }, [session?.id, session?.organize_tree_pair?.source_root]) + + const organizeTreePair = loadedTreePair ?? session?.organize_tree_pair + const modeResultTree: CoworkOrganizeTreeNode | null = + organizeTreePair?.result_tree ?? null + const hasStreamingBuildActivity = Boolean( + liveSession?.thinking.trim() || + liveSession?.response.trim() || + liveSession?.current_action || + liveSession?.tool_calls.length || + liveSession?.is_awaiting_turn_action + ) + const isRunCompleted = session?.run_status === 'completed' + const sessionContentKey = useMemo( + () => loadedTreePair?.source_root ?? session?.id ?? 'organize-entry', + [loadedTreePair?.source_root, session?.id] + ) + + useEffect(() => { + if (!isProcessing) { + return + } + + if (isRunCompleted) { + setActiveStep('result') + return + } + + if (requestedBuildAction) { + setActiveStep('build') + return + } + + if ( + isSending || + hasStreamingBuildActivity || + session?.run_status === 'thinking' || + session?.run_status === 'waiting_for_input' + ) { + setActiveStep('build') + } + }, [ + hasStreamingBuildActivity, + isProcessing, + isRunCompleted, + isSending, + requestedBuildAction, + requestedBuildActionToken, + session?.run_status + ]) + + const resolveFolderPath = async (candidatePaths: string[]) => { + let lastError: Error | null = null + + for (const rawPath of candidatePaths) { + const nextPath = rawPath.trim() + if (!nextPath) { + continue + } + + try { + const probeTree = await coworkService.readPathTree(nextPath, { + max_depth: 0, + max_entries: 1, + include_hidden: false + }) + + if (probeTree.kind === 'folder') { + return nextPath + } + } catch (error) { + lastError = + error instanceof Error + ? error + : new Error('Failed to inspect the dropped path.') + } + } + + throw ( + lastError ?? + new Error( + 'Please choose or drop a folder. Files are not supported.' + ) + ) + } + + const getErrorMessage = (error: unknown, fallback: string) => { + if (error instanceof Error && error.message.trim()) { + return error.message + } + + if (typeof error === 'string' && error.trim()) { + return error + } + + if ( + typeof error === 'object' && + error !== null && + 'message' in error && + typeof error.message === 'string' && + error.message.trim() + ) { + return error.message + } + + return fallback + } + + const handleLoadPath = async (pathValue?: string | string[]) => { + if (isLoadingPath) { + return + } + + setIsLoadingPath(true) + setLoadError(null) + + try { + const resolvedPath = await resolveFolderPath( + Array.isArray(pathValue) ? pathValue : [pathValue ?? sourcePath] + ) + + setSourcePath(resolvedPath) + + const sourceTree = await coworkService.readPathTree(resolvedPath, { + ...ORGANIZE_TREE_READ_OPTIONS + }) + + const treePair = { + source_root: resolvedPath, + result_root: resolvedPath, + source_tree: sourceTree, + result_tree: null + } + const persistedSession = + await coworkService.createOrganizeSession(treePair) + + setLoadedTreePair({ + source_root: + persistedSession.organize_tree_pair?.source_root ?? + resolvedPath, + result_root: + persistedSession.organize_tree_pair?.result_root ?? + resolvedPath, + source_tree: + persistedSession.organize_tree_pair?.source_tree ?? + sourceTree, + result_tree: + persistedSession.organize_tree_pair?.result_tree ?? null + }) + onSessionCreated?.(persistedSession) + setActiveStep('source') + setIsProcessing(true) + } catch (error) { + setLoadError( + getErrorMessage( + error, + 'Failed to load the path from your machine.' + ) + ) + } finally { + setIsLoadingPath(false) + } + } + + useEffect(() => { + if (isProcessing) { + return + } + + let isMounted = true + + const registerListener = async () => { + const currentWindow = getCurrentWindow() + const unlisten = await currentWindow.onDragDropEvent((event) => { + if (!isMounted) { + return + } + + if ( + event.payload.type === 'enter' || + event.payload.type === 'over' + ) { + setIsDragActive(true) + return + } + + if (event.payload.type === 'leave') { + setIsDragActive(false) + return + } + + if (event.payload.type === 'drop') { + setIsDragActive(false) + if (event.payload.paths.length > 0) { + void handleLoadPath(event.payload.paths) + } + } + }) + + if (!isMounted) { + unlisten() + } + + return unlisten + } + + let cleanup: (() => void) | undefined + + void registerListener().then((unlisten) => { + cleanup = unlisten + }) + + return () => { + isMounted = false + cleanup?.() + } + }, [isProcessing]) + + const handlePickFolder = async () => { + if (isLoadingPath) { + return + } + + setLoadError(null) + + try { + const pickedPath = await coworkService.pickFolderPath(sourcePath) + if (!pickedPath) { + return + } + + await handleLoadPath(pickedPath) + } catch (error) { + setLoadError( + getErrorMessage(error, 'Failed to open the folder picker.') + ) + } + } + + return ( + + {!isProcessing ? ( + +
+
+

+ Organize file/folder +

+

+ Start a new organization process +

+ {/*

+ Enter a local path, browse with the native picker, or + drop a folder anywhere on this screen. +

*/} +
+ +
+ + setSourcePath(event.target.value) + } + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.preventDefault() + void handleLoadPath() + } + }} + placeholder="/home/user/folder" + className="h-10 flex-1 rounded-full border-neutral-300 bg-white px-4 dark:border-white/15 dark:bg-black" + /> + +
+
+
+ + + +
+

+ Drag and drop folders only +

+

+ Or open the native folder picker to + choose a folder from your machine. +

+
+ +
+
+ {loadError && ( +

+ {loadError} +

+ )} +
+
+ + ) : ( + +
+ +
+ + + {activeStep === 'source' && ( + + )} + {activeStep === 'build' && ( + + )} + {activeStep === 'result' && ( + + )} + + +
+
+
+ Switching session... +
+
+
+
+
+
+ )} + + ) +} + +export default OrganizeFileFolderMode diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx b/frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx new file mode 100644 index 000000000..730b8026f --- /dev/null +++ b/frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx @@ -0,0 +1,76 @@ +import type { ActionStep } from '@/typings/agent' +import type { + CoworkChatSessionDetail, + CoworkLiveSessionState +} from '@/typings/cowork' +import CoworkBuildPanel from '../cowork-build/cowork-build-panel' +import { pickCoworkBuildRenderers } from '../cowork-build/cowork-build.renderers' +import { + CoworkBuildController, + CoworkBuildViewport, + useCoworkBuildState +} from '../cowork-build/cowork-build.shared' + +interface CoworkOrganizeBuildProps { + session?: CoworkChatSessionDetail | null + liveSession?: CoworkLiveSessionState | null + isRunning?: boolean + requestedAction?: ActionStep | null + requestedActionToken?: number +} + +const organizeBuildRenderers = pickCoworkBuildRenderers('terminal', 'code') + +const CoworkOrganizeBuild = ({ + session = null, + liveSession = null, + requestedAction = null, + requestedActionToken = 0 +}: CoworkOrganizeBuildProps) => { + const buildState = useCoworkBuildState({ + liveSession, + requestedAction, + requestedActionToken + }) + const isAwaitingNextAction = + buildState.isAwaitingTurnAction && buildState.hasActionHistory + const headerLabel = + buildState.currentAction?.data.tool_display_name || + buildState.currentAction?.data.tool_name || + (session?.run_status === 'completed' + ? 'Organize run completed' + : isAwaitingNextAction + ? 'Generating' + : 'Cowork build') + + return ( + + } + controller={ + + } + footerText="Browse organize events one step at a time." + /> + ) +} + +export default CoworkOrganizeBuild diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-result.tsx b/frontend/src/components/cowork/organize-file-folder/cowork-organize-result.tsx new file mode 100644 index 000000000..2d5dec359 --- /dev/null +++ b/frontend/src/components/cowork/organize-file-folder/cowork-organize-result.tsx @@ -0,0 +1,40 @@ +import type { CoworkOrganizeTreeNode } from '@/typings/cowork' +import CoworkOrganizeTreeView from './cowork-organize-tree-view' + +interface CoworkOrganizeResultProps { + rootResult?: string + tree?: CoworkOrganizeTreeNode | null +} + +const CoworkOrganizeResult = ({ + rootResult = 'root_result', + tree = null +}: CoworkOrganizeResultProps) => { + if (!tree) { + return ( +
+
+

+ Result{' '} + + {rootResult} + +

+

+ No mode-specific result is available yet. +

+
+
+ ) + } + + return ( + + ) +} + +export default CoworkOrganizeResult diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-source.tsx b/frontend/src/components/cowork/organize-file-folder/cowork-organize-source.tsx new file mode 100644 index 000000000..93600a5d4 --- /dev/null +++ b/frontend/src/components/cowork/organize-file-folder/cowork-organize-source.tsx @@ -0,0 +1,40 @@ +import type { CoworkOrganizeTreeNode } from '@/typings/cowork' +import CoworkOrganizeTreeView from './cowork-organize-tree-view' + +interface CoworkOrganizeSourceProps { + rootSource?: string + tree?: CoworkOrganizeTreeNode | null +} + +const CoworkOrganizeSource = ({ + rootSource = 'root_source', + tree = null +}: CoworkOrganizeSourceProps) => { + if (!tree) { + return ( +
+
+

+ Source{' '} + + {rootSource} + +

+

+ Load a local path above to render its source tree. +

+
+
+ ) + } + + return ( + + ) +} + +export default CoworkOrganizeSource diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-steps.tsx b/frontend/src/components/cowork/organize-file-folder/cowork-organize-steps.tsx new file mode 100644 index 000000000..87d042135 --- /dev/null +++ b/frontend/src/components/cowork/organize-file-folder/cowork-organize-steps.tsx @@ -0,0 +1,77 @@ +import clsx from 'clsx' +import { Icon } from '@/components/ui/icon' + +export type CoworkOrganizeStep = 'source' | 'build' | 'result' + +interface CoworkOrganizeStepsProps { + activeStep: CoworkOrganizeStep + onSelectStep: (step: CoworkOrganizeStep) => void +} + +const steps: { + id: CoworkOrganizeStep + label: string + icon: string +}[] = [ + { id: 'source', label: 'Source', icon: 'folder-open' }, + { id: 'build', label: 'Build', icon: 'wrench' }, + { id: 'result', label: 'Result', icon: 'ai-magic' } +] + +const CoworkOrganizeSteps = ({ + activeStep, + onSelectStep +}: CoworkOrganizeStepsProps) => { + return ( +
+ {steps.map((step, index) => { + const isActive = activeStep === step.id + + return ( +
+ + {index < steps.length - 1 && ( + + )} +
+ ) + })} +
+ ) +} + +export default CoworkOrganizeSteps diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-icons.tsx b/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-icons.tsx new file mode 100644 index 000000000..a0b4d3827 --- /dev/null +++ b/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-icons.tsx @@ -0,0 +1,183 @@ +import type { LucideIcon } from 'lucide-react' +import { + Archive, + Database, + File, + FileCode2, + FileJson2, + FileText, + Folder, + FolderOpen, + Image, + Settings2, + Video +} from 'lucide-react' + +type TreeVisualGroup = + | 'folder' + | 'code' + | 'config' + | 'docs' + | 'data' + | 'media' + | 'archive' + | 'default' + +export interface TreeNodeVisual { + Icon: LucideIcon + label: string + chipClassName: string + containerClassName: string + iconClassName: string +} + +const VISUALS: Record = { + folder: { + Icon: Folder, + label: 'Folder', + chipClassName: + 'border-firefly/20 bg-firefly/10 text-firefly dark:border-sky-blue/20 dark:bg-sky-blue/10 dark:text-sky-blue', + containerClassName: + 'border-firefly/20 bg-firefly/10 dark:border-sky-blue/20 dark:bg-sky-blue/10', + iconClassName: 'text-firefly dark:text-sky-blue' + }, + code: { + Icon: FileCode2, + label: 'Code', + chipClassName: + 'border-violet-500/20 bg-violet-500/10 text-violet-600 dark:border-violet-400/20 dark:bg-violet-400/10 dark:text-violet-300', + containerClassName: + 'border-violet-500/20 bg-violet-500/10 dark:border-violet-400/20 dark:bg-violet-400/10', + iconClassName: 'text-violet-600 dark:text-violet-300' + }, + config: { + Icon: Settings2, + label: 'Config', + chipClassName: + 'border-amber-500/20 bg-amber-500/10 text-amber-700 dark:border-amber-300/20 dark:bg-amber-300/10 dark:text-amber-200', + containerClassName: + 'border-amber-500/20 bg-amber-500/10 dark:border-amber-300/20 dark:bg-amber-300/10', + iconClassName: 'text-amber-700 dark:text-amber-200' + }, + docs: { + Icon: FileText, + label: 'Docs', + chipClassName: + 'border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:border-emerald-300/20 dark:bg-emerald-300/10 dark:text-emerald-200', + containerClassName: + 'border-emerald-500/20 bg-emerald-500/10 dark:border-emerald-300/20 dark:bg-emerald-300/10', + iconClassName: 'text-emerald-700 dark:text-emerald-200' + }, + data: { + Icon: Database, + label: 'Data', + chipClassName: + 'border-cyan-500/20 bg-cyan-500/10 text-cyan-700 dark:border-cyan-300/20 dark:bg-cyan-300/10 dark:text-cyan-200', + containerClassName: + 'border-cyan-500/20 bg-cyan-500/10 dark:border-cyan-300/20 dark:bg-cyan-300/10', + iconClassName: 'text-cyan-700 dark:text-cyan-200' + }, + media: { + Icon: Image, + label: 'Media', + chipClassName: + 'border-pink-500/20 bg-pink-500/10 text-pink-700 dark:border-pink-300/20 dark:bg-pink-300/10 dark:text-pink-200', + containerClassName: + 'border-pink-500/20 bg-pink-500/10 dark:border-pink-300/20 dark:bg-pink-300/10', + iconClassName: 'text-pink-700 dark:text-pink-200' + }, + archive: { + Icon: Archive, + label: 'Archive', + chipClassName: + 'border-slate-500/20 bg-slate-500/10 text-slate-700 dark:border-slate-300/20 dark:bg-slate-300/10 dark:text-slate-200', + containerClassName: + 'border-slate-500/20 bg-slate-500/10 dark:border-slate-300/20 dark:bg-slate-300/10', + iconClassName: 'text-slate-700 dark:text-slate-200' + }, + default: { + Icon: File, + label: 'File', + chipClassName: + 'border-neutral-300 bg-neutral-100 text-neutral-700 dark:border-white/15 dark:bg-white/5 dark:text-white/70', + containerClassName: + 'border-neutral-300 bg-neutral-100 dark:border-white/15 dark:bg-white/5', + iconClassName: 'text-neutral-700 dark:text-white/70' + } +} + +const extensionGroups: Record = { + folder: [], + code: [ + 'ts', + 'tsx', + 'js', + 'jsx', + 'py', + 'css', + 'scss', + 'html', + 'sh', + 'ps1' + ], + config: ['env', 'yaml', 'yml', 'ini', 'toml'], + docs: ['md', 'mdx', 'txt'], + data: ['json', 'sql', 'csv'], + media: ['png', 'jpg', 'jpeg', 'svg', 'gif', 'mp4', 'webm'], + archive: ['zip', 'tar', 'gz'], + default: [] +} + +const getVisualGroup = (extension?: string): TreeVisualGroup => { + const normalized = extension?.replace('.', '').toLowerCase() + + if (!normalized) return 'default' + + if (normalized === 'json') return 'data' + if (normalized === 'mp4' || normalized === 'webm') return 'media' + + return ( + (Object.entries(extensionGroups).find(([, extensions]) => + extensions.includes(normalized) + )?.[0] as TreeVisualGroup | undefined) ?? 'default' + ) +} + +export const getTreeNodeVisual = ({ + kind, + extension, + expanded +}: { + kind: 'folder' | 'file' + extension?: string + expanded?: boolean +}): TreeNodeVisual => { + if (kind === 'folder') { + return { + ...VISUALS.folder, + Icon: expanded ? FolderOpen : Folder + } + } + + const group = getVisualGroup(extension) + const visual = VISUALS[group] + + if (group === 'media' && (extension === 'mp4' || extension === 'webm')) { + return { + ...visual, + Icon: Video, + label: 'Video' + } + } + + if (group === 'data' && extension === 'json') { + return { + ...visual, + Icon: FileJson2, + label: 'JSON' + } + } + + return visual +} + diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-view.tsx b/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-view.tsx new file mode 100644 index 000000000..f9361c869 --- /dev/null +++ b/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-view.tsx @@ -0,0 +1,301 @@ +import { useEffect, useMemo, useState } from 'react' +import { ChevronDown, ChevronRight } from 'lucide-react' +import type { CoworkOrganizeTreeNode } from '@/typings/cowork' +import { cn } from '@/lib/utils' +import { getTreeNodeVisual } from './cowork-organize-tree-icons' + +export type OrganizeTreeNode = CoworkOrganizeTreeNode + +interface FlattenedTreeNode extends OrganizeTreeNode { + path: string +} + +interface CoworkOrganizeTreeViewProps { + label: string + rootPath: string + tree: OrganizeTreeNode +} + +const getAllFolderIds = (node: OrganizeTreeNode): string[] => [ + ...(node.kind === 'folder' ? [node.id] : []), + ...(node.children?.flatMap((child) => getAllFolderIds(child)) ?? []) +] + +const flattenTree = ( + node: OrganizeTreeNode, + rootPath: string, + parentPath = '' +): FlattenedTreeNode[] => { + const currentPath = parentPath ? `${parentPath}/${node.name}` : rootPath + + return [ + { ...node, path: currentPath }, + ...(node.children?.flatMap((child) => + flattenTree(child, rootPath, currentPath) + ) ?? []) + ] +} + +const getDirectChildCounts = (node: OrganizeTreeNode) => ({ + folders: + node.children?.filter((child) => child.kind === 'folder').length ?? 0, + files: node.children?.filter((child) => child.kind === 'file').length ?? 0 +}) + +const renderTreeNode = ({ + node, + depth, + expandedIds, + selectedId, + onToggle, + onSelect +}: { + node: OrganizeTreeNode + depth: number + expandedIds: Set + selectedId: string + onToggle: (id: string) => void + onSelect: (id: string) => void +}) => { + const hasChildren = Boolean(node.children?.length) + const isExpanded = expandedIds.has(node.id) + const isSelected = selectedId === node.id + const visual = getTreeNodeVisual({ + kind: node.kind, + extension: node.extension, + expanded: isExpanded + }) + + return ( +
+ + {hasChildren && + isExpanded && + node.children?.map((child) => + renderTreeNode({ + node: child, + depth: depth + 1, + expandedIds, + selectedId, + onToggle, + onSelect + }) + )} +
+ ) +} + +const CoworkOrganizeTreeView = ({ + label, + rootPath, + tree +}: CoworkOrganizeTreeViewProps) => { + const [expandedIds, setExpandedIds] = useState>( + () => new Set(getAllFolderIds(tree)) + ) + const [selectedId, setSelectedId] = useState(() => tree.id) + + useEffect(() => { + setExpandedIds(new Set(getAllFolderIds(tree))) + setSelectedId(tree.id) + }, [tree]) + + const flatTree = useMemo(() => flattenTree(tree, rootPath), [rootPath, tree]) + const selectedNode = useMemo( + () => flatTree.find((node) => node.id === selectedId) ?? flatTree[0], + [flatTree, selectedId] + ) + const selectedChildCounts = useMemo( + () => getDirectChildCounts(selectedNode), + [selectedNode] + ) + + return ( +
+
+
+

+ {label}{' '} + + {rootPath} + +

+
+
+
+
+
+ {renderTreeNode({ + node: tree, + depth: 0, + expandedIds, + selectedId, + onToggle: (id) => + setExpandedIds((prev) => { + const next = new Set(prev) + if (next.has(id)) next.delete(id) + else next.add(id) + return next + }), + onSelect: setSelectedId + })} +
+
+ +
+
+

+ Selected +

+
+ + {(() => { + const visual = getTreeNodeVisual({ + kind: selectedNode.kind, + extension: selectedNode.extension, + expanded: expandedIds.has(selectedNode.id) + }) + return ( + + ) + })()} + +
+

+ {selectedNode.name} +

+

+ {selectedNode.path} +

+
+
+
+ +
+

+ File details +

+
+ {( + selectedNode.kind === 'folder' + ? [ + { + label: 'Node type', + value: 'Folder' + }, + { + label: 'Subfolders', + value: selectedChildCounts.folders + }, + { + label: 'Files', + value: selectedChildCounts.files + } + ] + : [ + { + label: 'Node type', + value: + selectedNode.extension?.toUpperCase() ?? + 'File' + }, + { + label: 'Size', + value: selectedNode.size ?? '-' + }, + { + label: 'Children', + value: 0 + } + ] + ).map((item) => ( +
+ + {item.label} + + + {item.value} + +
+ ))} +
+
+
+
+
+ ) +} + +export default CoworkOrganizeTreeView + + diff --git a/frontend/src/components/cowork/organize-file-folder/organize-tree-utils.ts b/frontend/src/components/cowork/organize-file-folder/organize-tree-utils.ts new file mode 100644 index 000000000..e5c04921b --- /dev/null +++ b/frontend/src/components/cowork/organize-file-folder/organize-tree-utils.ts @@ -0,0 +1,5 @@ +export const ORGANIZE_TREE_READ_OPTIONS = { + max_depth: 6, + max_entries: 5000, + include_hidden: false +} as const diff --git a/frontend/src/components/home-mobile.tsx b/frontend/src/components/home-mobile.tsx index 3e07cd24b..78bd9b2b7 100644 --- a/frontend/src/components/home-mobile.tsx +++ b/frontend/src/components/home-mobile.tsx @@ -5,6 +5,7 @@ import { useState, type KeyboardEvent as ReactKeyboardEvent } from 'react' +import { useNavigate } from 'react-router' import { Icon } from '@/components/ui/icon' import { @@ -36,6 +37,11 @@ import { MiniTool } from '@/constants/media-tools' import { MediaTemplateExplorer } from './media/media-template-explorer' import SwitchLanguage from './switch-language' import LearnMore from './learn-more' +import { + COWORK_ROUTE, + isAgenticQuestionMode +} from '@/utils/question-mode' +import { isTauri } from '@/utils/is-tauri' interface HomeMobileProps { currentQuestion: string @@ -177,6 +183,7 @@ const HomeMobile = ({ }: HomeMobileProps) => { const { t } = useTranslation() const dispatch = useAppDispatch() + const navigate = useNavigate() const questionMode = useAppSelector(selectQuestionMode) const selectedModel = useAppSelector(selectSelectedModel) const availableModels = useAppSelector(selectAvailableModels) @@ -210,6 +217,11 @@ const HomeMobile = ({ () => questionMode === QUESTION_MODE.CHAT, [questionMode] ) + const isAgenticMode = useMemo( + () => isAgenticQuestionMode(questionMode), + [questionMode] + ) + const featureMode = isChatMode ? QUESTION_MODE.CHAT : QUESTION_MODE.AGENT const selectedSuggestionType = useMemo(() => { if ( @@ -220,14 +232,14 @@ const HomeMobile = ({ } if ( - questionMode === QUESTION_MODE.AGENT && + isAgenticMode && selectedFeature !== AGENT_TYPE.GENERAL ) { return selectedFeature } return null - }, [chatMediaPreference.enabled, questionMode, selectedFeature]) + }, [chatMediaPreference.enabled, isAgenticMode, questionMode, selectedFeature]) const suggestionsToRender = useMemo(() => { if (!selectedSuggestionType) return [] @@ -265,6 +277,16 @@ const HomeMobile = ({ dispatch(setQuestionMode(QUESTION_MODE.CHAT)) } + const handleSwitchToAgentMode = () => { + clearMediaPreference() + dispatch(setQuestionMode(QUESTION_MODE.AGENT)) + } + + const handleSwitchToCowork = () => { + clearMediaPreference() + navigate(COWORK_ROUTE) + } + const handleMediaTemplateSelect = (template: MediaTemplate | undefined) => { setTimeout(() => { scrollRef.current?.scrollIntoView({ behavior: 'smooth' }) @@ -381,11 +403,9 @@ const HomeMobile = ({ + {isTauri && ( + + )}
@@ -426,7 +467,7 @@ const HomeMobile = ({
handleSelectFeature(tile)} diff --git a/frontend/src/components/layouts/root-layout.tsx b/frontend/src/components/layouts/root-layout.tsx index 3054efb18..249fe4cf7 100644 --- a/frontend/src/components/layouts/root-layout.tsx +++ b/frontend/src/components/layouts/root-layout.tsx @@ -7,12 +7,14 @@ import { import { useNavigationLeaveSession } from '@/hooks/use-navigation-leave-session' import { useWebSocketAuthSync } from '@/hooks/use-websocket-auth-sync' import { ChatProvider } from '@/hooks/use-chat-query' +import { useCoworkAuthSync } from '@/hooks/use-cowork-auth-sync' function RootLayoutContent() { useNavigationLeaveSession() // Establish WebSocket connection immediately when auth token is available useWebSocketAuthSync() - + useCoworkAuthSync() + return } diff --git a/frontend/src/components/question-input.tsx b/frontend/src/components/question-input.tsx index 7aaaae6b3..b1bc5389d 100644 --- a/frontend/src/components/question-input.tsx +++ b/frontend/src/components/question-input.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { useLocation, useParams } from 'react-router' +import { useLocation, useNavigate, useParams } from 'react-router' import { type MiniTool } from '@/constants/media-tools' import { getMediaTypeConfig } from '@/constants/media-type-config' @@ -74,6 +74,10 @@ import clsx from 'clsx' import { useMediaModels } from '@/hooks/use-media-models' import { useChat } from '@/hooks/use-chat-query' import { StorybookStylePicker } from './media/image/image-settings-picker' +import { + COWORK_ROUTE, + isAgenticQuestionMode +} from '@/utils/question-mode' interface QuestionInputProps { value: string @@ -84,6 +88,7 @@ interface QuestionInputProps { textareaClassName?: string placeholder?: string isDisabled?: boolean + submitDisabled?: boolean handleEnhancePrompt?: (payload: { prompt: string onSuccess: (res: string) => void @@ -128,6 +133,7 @@ const QuestionInput = ({ handleKeyDown, handleSubmit, isDisabled, + submitDisabled = false, handleEnhancePrompt, handleCancel, onFilesChange, @@ -159,6 +165,7 @@ const QuestionInput = ({ }: QuestionInputProps) => { const { t } = useTranslation() const dispatch = useAppDispatch() + const navigate = useNavigate() const requireClearFiles = useAppSelector(selectRequireClearFiles) const uploadedFiles = useAppSelector(selectUploadedFiles) const currentMessageFileIds = useAppSelector(selectCurrentMessageFileIds) @@ -214,6 +221,7 @@ const QuestionInput = ({ const isChatRoute = normalizedPathname === '/chat' || normalizedPathname.endsWith('/chat') const isSessionView = Boolean(sessionId) || isChatRoute + const isAgenticMode = isAgenticQuestionMode(questionMode) const textareaRef = useRef(null) const clearedAttachmentIdsRef = useRef>(new Set()) @@ -353,6 +361,7 @@ const QuestionInput = ({ if ( !submissionValue || isDisabled || + submitDisabled || isCreatingSession || files?.some((file) => file.loading) || isUploading @@ -516,6 +525,12 @@ const QuestionInput = ({ } const handleSelectMode = (mode: QUESTION_MODE) => { + if (mode === QUESTION_MODE.COWORK) { + clearMediaPreference() + navigate(COWORK_ROUTE) + return + } + dispatch(setQuestionMode(mode)) setTimeout(() => { textareaRef.current?.focus() @@ -523,7 +538,7 @@ const QuestionInput = ({ if (mode === QUESTION_MODE.CHAT) { dispatch(setSelectedFeature(AGENT_TYPE.GENERAL)) } - if (mode === QUESTION_MODE.AGENT) { + if (isAgenticQuestionMode(mode)) { clearMediaPreference() } } @@ -1250,7 +1265,7 @@ const QuestionInput = ({
)} {!hideBuildModeSelector && - questionMode === QUESTION_MODE.AGENT && + isAgenticMode && (selectedFeature === AGENT_TYPE.GENERAL || selectedFeature === AGENT_TYPE.WEBSITE_BUILD || @@ -1339,7 +1354,7 @@ const QuestionInput = ({ } /> - {questionMode === QUESTION_MODE.AGENT && ( + {isAgenticMode && ( { @@ -1379,6 +1394,7 @@ const QuestionInput = ({ (!currentTextareaValue.trim() && !hasMiniToolSelection) || isDisabled || + submitDisabled || isCreatingSession || files?.some((file) => file.loading) || isUploading || @@ -1452,7 +1468,7 @@ const QuestionInput = ({
{!hideSuggestions && - questionMode === QUESTION_MODE.AGENT && + isAgenticMode && selectedFeature !== AGENT_TYPE.GENERAL && (
diff --git a/frontend/src/components/question-mode-selector.tsx b/frontend/src/components/question-mode-selector.tsx index 7e0c33622..85cd5c2c0 100644 --- a/frontend/src/components/question-mode-selector.tsx +++ b/frontend/src/components/question-mode-selector.tsx @@ -3,6 +3,7 @@ import { QUESTION_MODE } from '@/typings' import { cn } from '@/lib/utils' import { useTranslation } from 'react-i18next' import { useIsSageTheme } from '@/hooks/use-is-sage-theme' +import { isTauri } from '@/utils/is-tauri' interface ModeSelectorProps { selectedMode: QUESTION_MODE @@ -13,65 +14,60 @@ interface ModeSelectorProps { const ModeSelector = ({ selectedMode, hide, onSelect }: ModeSelectorProps) => { const { t } = useTranslation() const isSage = useIsSageTheme() + const modes = [ + { + type: QUESTION_MODE.CHAT, + icon: 'chat-fill', + label: t('question.mode.chat') + }, + { + type: QUESTION_MODE.AGENT, + icon: 'agent-fill', + label: t('question.mode.agent') + }, + { + type: QUESTION_MODE.COWORK, + icon: 'messages', + label: 'II-Cowork' + } + ].filter((mode) => isTauri || mode.type !== QUESTION_MODE.COWORK) if (hide) return null return (
- - + {modes.map((mode) => { + const isActive = selectedMode === mode.type + + return ( + + ) + })}
) } diff --git a/frontend/src/components/ui/popover.tsx b/frontend/src/components/ui/popover.tsx index 006345edf..6a1759529 100644 --- a/frontend/src/components/ui/popover.tsx +++ b/frontend/src/components/ui/popover.tsx @@ -19,8 +19,11 @@ function PopoverContent({ className, align = "center", sideOffset = 4, + instant = false, ...props -}: React.ComponentProps) { +}: React.ComponentProps & { + instant?: boolean +}) { return ( state.user) // Derive isAuthenticated from the presence of a valid access token - const isAuthenticated = !!localStorage.getItem(ACCESS_TOKEN) + const isAuthenticated = !!getStoredAccessToken() const fetchAvailableModels = useCallback(async () => { try { @@ -62,7 +66,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { useEffect(() => { const initializeAuth = async () => { try { - const accessToken = localStorage.getItem(ACCESS_TOKEN) + const accessToken = getStoredAccessToken() if (accessToken) { try { @@ -108,9 +112,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { }) // Store the access token immediately to trigger WebSocket connection - localStorage.setItem(ACCESS_TOKEN, res.access_token) - // Dispatch a custom event to notify WebSocket to connect immediately - window.dispatchEvent(new CustomEvent('auth-token-set')) + storeAccessToken(res.access_token) // Get user information using the access token (in parallel with WebSocket connection) const userRes = await authService.getCurrentUser() @@ -126,7 +128,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { } const logout = () => { - localStorage.removeItem(ACCESS_TOKEN) + clearAccessToken() dispatch(clearUser()) dispatch(clearFavorites()) dispatch(clearPins()) diff --git a/frontend/src/hooks/use-cowork-auth-sync.tsx b/frontend/src/hooks/use-cowork-auth-sync.tsx new file mode 100644 index 000000000..5e74deea0 --- /dev/null +++ b/frontend/src/hooks/use-cowork-auth-sync.tsx @@ -0,0 +1,69 @@ +import { useEffect, useRef } from 'react' +import { invoke } from '@tauri-apps/api/core' +import { + AUTH_TOKEN_CLEARED_EVENT, + AUTH_TOKEN_SET_EVENT, + getStoredAccessToken +} from '@/utils/auth-token' + +const isTauri = + typeof window !== 'undefined' && + !!(window as unknown as { __TAURI_INTERNALS__?: unknown }) + .__TAURI_INTERNALS__ + +type SyncPayload = { + accessToken: string | null + apiBaseUrl: string +} + +export function useCoworkAuthSync() { + const lastPayloadRef = useRef(null) + + useEffect(() => { + if (!isTauri) return undefined + + const apiBaseUrl = + import.meta.env.VITE_API_URL || 'http://localhost:8000' + + const syncAuthContext = async () => { + const payload: SyncPayload = { + accessToken: getStoredAccessToken(), + apiBaseUrl + } + + if ( + lastPayloadRef.current?.accessToken === payload.accessToken && + lastPayloadRef.current?.apiBaseUrl === payload.apiBaseUrl + ) { + return + } + + await invoke('sync_cowork_auth_context', payload) + lastPayloadRef.current = payload + } + + const syncSilently = () => { + void syncAuthContext().catch((error) => { + console.error('Failed to sync cowork auth context:', error) + }) + } + + syncSilently() + + const handleStorageChange = (event: StorageEvent) => { + if (event.key) { + syncSilently() + } + } + + window.addEventListener('storage', handleStorageChange) + window.addEventListener(AUTH_TOKEN_SET_EVENT, syncSilently) + window.addEventListener(AUTH_TOKEN_CLEARED_EVENT, syncSilently) + + return () => { + window.removeEventListener('storage', handleStorageChange) + window.removeEventListener(AUTH_TOKEN_SET_EVENT, syncSilently) + window.removeEventListener(AUTH_TOKEN_CLEARED_EVENT, syncSilently) + } + }, []) +} diff --git a/frontend/src/hooks/use-websocket-auth-sync.tsx b/frontend/src/hooks/use-websocket-auth-sync.tsx index 3daef8e12..79e856a49 100644 --- a/frontend/src/hooks/use-websocket-auth-sync.tsx +++ b/frontend/src/hooks/use-websocket-auth-sync.tsx @@ -1,6 +1,11 @@ import { useEffect } from 'react' -import { ACCESS_TOKEN } from '@/constants/auth' import { useWebSocketContext } from '@/contexts/websocket-context' +import { ACCESS_TOKEN } from '@/constants/auth' +import { + AUTH_TOKEN_CLEARED_EVENT, + AUTH_TOKEN_SET_EVENT, + getStoredAccessToken +} from '@/utils/auth-token' /** * Hook that monitors auth token changes and reconnects the WebSocket @@ -17,25 +22,50 @@ export function useWebSocketAuthSync() { // Detect token changes from other browser tabs const handleStorageChange = (e: StorageEvent) => { if (e.key === ACCESS_TOKEN && e.newValue && !socket?.connected) { - console.log('WebSocket: Token changed via storage event, reconnecting...') + console.log( + 'WebSocket: Token changed via storage event, reconnecting...' + ) connectSocket() + return + } + + if (e.key === ACCESS_TOKEN && !e.newValue && socket?.connected) { + console.log( + 'WebSocket: Token cleared via storage event, disconnecting...' + ) + socket.disconnect() } } // Detect token set in the same tab (e.g. after login or token refresh) const handleAuthTokenSet = () => { - const token = localStorage.getItem(ACCESS_TOKEN) + const token = getStoredAccessToken() if (token && !socket?.connected) { console.log('WebSocket: Auth token set event, reconnecting...') connectSocket() } } + const handleAuthTokenCleared = () => { + if (socket?.connected) { + console.log('WebSocket: Auth token cleared, disconnecting...') + socket.disconnect() + } + } + window.addEventListener('storage', handleStorageChange) - window.addEventListener('auth-token-set', handleAuthTokenSet) + window.addEventListener(AUTH_TOKEN_SET_EVENT, handleAuthTokenSet) + window.addEventListener( + AUTH_TOKEN_CLEARED_EVENT, + handleAuthTokenCleared + ) return () => { window.removeEventListener('storage', handleStorageChange) - window.removeEventListener('auth-token-set', handleAuthTokenSet) + window.removeEventListener(AUTH_TOKEN_SET_EVENT, handleAuthTokenSet) + window.removeEventListener( + AUTH_TOKEN_CLEARED_EVENT, + handleAuthTokenCleared + ) } }, [connectSocket, socket?.connected]) -} \ No newline at end of file +} diff --git a/frontend/src/lib/axios.ts b/frontend/src/lib/axios.ts index 70e6286e8..5892450e9 100644 --- a/frontend/src/lib/axios.ts +++ b/frontend/src/lib/axios.ts @@ -1,5 +1,5 @@ -import { ACCESS_TOKEN } from '@/constants/auth' import axios from 'axios' +import { clearAccessToken, getStoredAccessToken } from '@/utils/auth-token' const axiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000', @@ -10,7 +10,7 @@ const axiosInstance = axios.create({ axiosInstance.interceptors.request.use( (config) => { - const token = localStorage.getItem(ACCESS_TOKEN) + const token = getStoredAccessToken() if (token) { config.headers.Authorization = `Bearer ${token}` } @@ -30,10 +30,11 @@ axiosInstance.interceptors.response.use( // Only logout if it's NOT a connector-specific endpoint // Connector endpoints return 401 when the connector token is invalid, // not when the user session is invalid - const isConnectorEndpoint = error.config?.url?.includes('/v1/connectors/') + const isConnectorEndpoint = + error.config?.url?.includes('/connectors/') if (!isConnectorEndpoint) { - localStorage.removeItem(ACCESS_TOKEN) + clearAccessToken() // Don't redirect to login if we're on a share route if (!window.location.pathname.startsWith('/share/')) { window.location.href = '/login' diff --git a/frontend/src/services/auth.service.ts b/frontend/src/services/auth.service.ts index 86711808f..66885c15e 100644 --- a/frontend/src/services/auth.service.ts +++ b/frontend/src/services/auth.service.ts @@ -17,6 +17,30 @@ class AuthService { return response.data } + /** Fetch the Google OAuth URL for desktop login (no browser redirect to backend). */ + async getDesktopGoogleLoginUrl( + desktopState: string + ): Promise { + const response = await axiosInstance.get<{ url: string }>( + '/auth/oauth/google/desktop/login-url', + { params: { desktop_state: desktopState } } + ) + return response.data.url + } + + /** Poll for a desktop auth token stored by the backend after Google login. */ + async pollDesktopToken( + state: string + ): Promise { + const response = await axiosInstance.get< + { status: 'pending' } | GoogleAuthResponse + >('/auth/oauth/google/poll', { params: { state } }) + if ('status' in response.data && response.data.status === 'pending') { + return null + } + return response.data as GoogleAuthResponse + } + async logout(): Promise { await axiosInstance.post('/api/auth/logout') } diff --git a/frontend/src/services/cowork.service.ts b/frontend/src/services/cowork.service.ts new file mode 100644 index 000000000..da3d9d64e --- /dev/null +++ b/frontend/src/services/cowork.service.ts @@ -0,0 +1,177 @@ +import { invoke } from '@tauri-apps/api/core' +import { open } from '@tauri-apps/plugin-dialog' +import type { + CoworkChatScope, + CoworkChatSendMessageRequest, + CoworkChatSendMessageResponse, + CoworkChatSessionDetail, + CoworkChatSessionSummary, + CoworkOrganizeTreeNode, + CoworkOrganizeTreePair +} from '@/typings/cowork' + +const HOMEPAGE_SCOPE: CoworkChatScope = 'homepage' +const ORGANIZE_SCOPE: CoworkChatScope = 'organize-file-folder' + +interface ReadPathTreeOptions { + max_depth?: number + max_entries?: number + include_hidden?: boolean +} + +class CoworkService { + async pickFolderPath(defaultPath?: string): Promise { + const selectedPath = await open({ + directory: true, + multiple: false, + recursive: true, + title: 'Choose a source folder', + defaultPath: defaultPath?.trim() || undefined + }) + + if (Array.isArray(selectedPath)) { + return selectedPath[0] ?? null + } + + return selectedPath ?? null + } + + async readPathTree( + path: string, + options?: ReadPathTreeOptions + ): Promise { + return invoke('read_path_tree', { + path, + options + }) + } + + async createOrganizeSession( + treePair: CoworkOrganizeTreePair + ): Promise { + return invoke('create_organize_session', { + treePair + }) + } + + async createHomepageChatSession( + title: string + ): Promise { + return invoke('create_homepage_chat_session', { + title + }) + } + + async updateOrganizeSession( + session: CoworkChatSessionDetail + ): Promise { + return invoke('update_organize_session', { + session + }) + } + + async updateHomepageChatSession( + session: CoworkChatSessionDetail + ): Promise { + return invoke('update_homepage_chat_session', { + session + }) + } + + async renameHomepageChatSession( + sessionId: string, + title: string + ): Promise { + return invoke('rename_homepage_chat_session', { + sessionId, + title + }) + } + + async deleteHomepageChatSession(sessionId: string): Promise { + return invoke('delete_homepage_chat_session', { + sessionId + }) + } + + async renameOrganizeSession( + sessionId: string, + title: string + ): Promise { + return invoke('rename_organize_session', { + sessionId, + title + }) + } + + async deleteOrganizeSession(sessionId: string): Promise { + return invoke('delete_organize_session', { + sessionId + }) + } + + async getChatSessions( + scope: CoworkChatScope + ): Promise { + if (scope === HOMEPAGE_SCOPE) { + return invoke( + 'list_homepage_chat_sessions' + ) + } + + if (scope === ORGANIZE_SCOPE) { + return invoke('list_organize_sessions') + } + + return [] + } + + async getChatSession( + sessionId: string, + scope: CoworkChatScope = HOMEPAGE_SCOPE + ): Promise { + if (scope === HOMEPAGE_SCOPE) { + return invoke( + 'get_homepage_chat_session', + { + sessionId + } + ) + } + + if (scope === ORGANIZE_SCOPE) { + return invoke('get_organize_session', { + sessionId + }) + } + + throw new Error(`Unsupported cowork scope: ${scope}`) + } + + async sendChatMessage( + payload: CoworkChatSendMessageRequest + ): Promise { + return invoke( + 'send_cowork_chat_message', + { + request: { + ...payload, + session_id: payload.session_id?.trim() || null, + runtime_kind: payload.runtime_kind ?? 'remote' + } + } + ) + } + + async stopChatSession( + sessionId: string, + scope: CoworkChatScope + ): Promise { + return invoke('stop_cowork_chat_session', { + scope, + sessionId + }) + } +} + +export const coworkService = new CoworkService() diff --git a/frontend/src/state/api/session.api.ts b/frontend/src/state/api/session.api.ts index 1cd4776a0..a794da001 100644 --- a/frontend/src/state/api/session.api.ts +++ b/frontend/src/state/api/session.api.ts @@ -5,8 +5,12 @@ import type { FetchBaseQueryError } from '@reduxjs/toolkit/query' import type { ISession } from '@/typings/agent' -import type { UpdateSessionRequest, ForkSessionRequest, ForkSessionResponse } from '@/typings/session' -import { ACCESS_TOKEN } from '@/constants/auth' +import type { + UpdateSessionRequest, + ForkSessionRequest, + ForkSessionResponse +} from '@/typings/session' +import { getStoredAccessToken, clearAccessToken } from '@/utils/auth-token' import { normalizeSession, normalizeSessions } from '@/services/session-normalizer' const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' @@ -14,7 +18,7 @@ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' const baseQuery = fetchBaseQuery({ baseUrl: `${API_URL}/v1`, prepareHeaders: (headers) => { - const token = localStorage.getItem(ACCESS_TOKEN) + const token = getStoredAccessToken() if (token) { headers.set('Authorization', `Bearer ${token}`) } @@ -30,7 +34,7 @@ const baseQueryWithReauth: BaseQueryFn< > = async (args, api, extraOptions) => { const result = await baseQuery(args, api, extraOptions) if (result.error && result.error.status === 401) { - localStorage.removeItem(ACCESS_TOKEN) + clearAccessToken() // Don't redirect to login if we're on a share route if (!window.location.pathname.startsWith('/share/')) { window.location.href = '/login' diff --git a/frontend/src/state/api/user.api.ts b/frontend/src/state/api/user.api.ts index a40b64e7d..5f252819b 100644 --- a/frontend/src/state/api/user.api.ts +++ b/frontend/src/state/api/user.api.ts @@ -11,14 +11,14 @@ import type { ReservationHistoryResponse, SessionUsageDetailResponse } from '@/typings/user' -import { ACCESS_TOKEN } from '@/constants/auth' +import { getStoredAccessToken, clearAccessToken } from '@/utils/auth-token' const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' const baseQuery = fetchBaseQuery({ baseUrl: `${API_URL}/v1`, prepareHeaders: (headers) => { - const token = localStorage.getItem(ACCESS_TOKEN) + const token = getStoredAccessToken() if (token) { headers.set('Authorization', `Bearer ${token}`) } @@ -34,7 +34,7 @@ const baseQueryWithReauth: BaseQueryFn< > = async (args, api, extraOptions) => { const result = await baseQuery(args, api, extraOptions) if (result.error && result.error.status === 401) { - localStorage.removeItem(ACCESS_TOKEN) + clearAccessToken() // Don't redirect to login if we're on a share route if (!window.location.pathname.startsWith('/share/')) { window.location.href = '/login' diff --git a/frontend/src/typings/agent.ts b/frontend/src/typings/agent.ts index cbfaa8b2d..11b067887 100644 --- a/frontend/src/typings/agent.ts +++ b/frontend/src/typings/agent.ts @@ -24,7 +24,8 @@ export enum VIEW_MODE { export enum QUESTION_MODE { AGENT = 'agent', - CHAT = 'chat' + CHAT = 'chat', + COWORK = 'cowork' } export enum BUILD_MODE { @@ -570,6 +571,7 @@ export interface ISession { metadata?: { media?: ChatMediaPreference fork_info?: ForkInfo + question_mode?: QUESTION_MODE [key: string]: unknown } } diff --git a/frontend/src/typings/cowork.ts b/frontend/src/typings/cowork.ts new file mode 100644 index 000000000..7dc13f139 --- /dev/null +++ b/frontend/src/typings/cowork.ts @@ -0,0 +1,182 @@ +import type { ActionStep, Message } from './agent' + +export type CoworkChatMessageRole = 'user' | 'assistant' +export type CoworkChatScope = 'homepage' | 'organize-file-folder' + +export interface CoworkGitHubRepositoryContext { + owner: string + name: string + full_name: string + default_branch: string +} + +export interface CoworkChatToolSettings { + web_search: boolean + web_visit: boolean + image_search: boolean + code_interpreter?: boolean + generate_image?: boolean + generate_video?: boolean +} + +export interface CoworkOrganizeTreeNode { + id: string + name: string + kind: 'folder' | 'file' + extension?: string + size?: string + children?: CoworkOrganizeTreeNode[] +} + +export interface CoworkOrganizeTreePair { + source_root: string + result_root: string + source_tree: CoworkOrganizeTreeNode + result_tree: CoworkOrganizeTreeNode | null +} + +export interface CoworkChatMessage { + id: string + role: CoworkChatMessageRole + content: string + created_at: string + is_think_message?: boolean +} + +export interface CoworkChatSessionSummary { + id: string + scope: CoworkChatScope + title: string + preview: string + updated_at: string + message_count: number +} + +export type CoworkChatRunStatus = + | 'idle' + | 'thinking' + | 'waiting_for_input' + | 'completed' + | 'stopped' + +export type CoworkAgentRuntimeKind = 'remote' | 'local' + +export interface CoworkChatFile { + id: string + file_name: string + file_size: number + content_type: string + created_at: string +} + +export interface CoworkChatSessionDetail extends CoworkChatSessionSummary { + runtime_kind?: CoworkAgentRuntimeKind + runtime_session_id?: string + messages: CoworkChatMessage[] + runtime_events: CoworkRuntimeEventPayload[] + files: CoworkChatFile[] + run_status: CoworkChatRunStatus + organize_tree_pair?: CoworkOrganizeTreePair +} + +export type CoworkChatEvent = + | { + type: 'session.created' | 'session.updated' + session: CoworkChatSessionSummary + } + | { + type: 'message.created' + scope: CoworkChatScope + session_id: string + message: CoworkChatMessage + } + | { + type: 'files.updated' + scope: CoworkChatScope + session_id: string + files: CoworkChatFile[] + } + | { + type: 'status.updated' + scope: CoworkChatScope + session_id: string + status: CoworkChatRunStatus + } + +export interface CoworkRuntimeEventPayload { + type: 'runtime.event' + scope: CoworkChatScope + session_id: string + runtime_event_type: string + runtime_event_id?: string + runtime_created_at?: string + run_status?: string + emitted_at: string + content: Record +} + +export type CoworkChatLiveEvent = CoworkChatEvent | CoworkRuntimeEventPayload + +export type CoworkLiveActivityStatus = + | 'running' + | 'completed' + | 'waiting' + | 'error' + +export interface CoworkLiveToolCall { + id: string + name: string + display_name: string + input?: string + result?: string + status: CoworkLiveActivityStatus + logo?: string + skill_name?: string + agent_name?: string +} + +export interface CoworkLiveActivity { + id: string + runtime_event_type: string + title: string + detail?: string + timestamp: string + status?: CoworkLiveActivityStatus + tool_call_id?: string + tool_name?: string + skill_name?: string + agent_name?: string +} + +export interface CoworkLiveSessionState { + session_id: string + scope: CoworkChatScope + thinking: string + response: string + is_awaiting_turn_action?: boolean + thinking_message_id?: string + response_message_id?: string + thinking_started_at?: string + response_started_at?: string + tool_calls: CoworkLiveToolCall[] + activities: CoworkLiveActivity[] + event_messages: Message[] + current_action?: ActionStep + last_event_at?: string + latest_runtime_event_type?: string +} + +export interface CoworkChatSendMessageRequest { + session_id?: string | null + runtime_kind?: CoworkAgentRuntimeKind + scope: CoworkChatScope + content: string + model_id: string + tools?: CoworkChatToolSettings + github_repository?: CoworkGitHubRepositoryContext +} + +export interface CoworkChatSendMessageResponse { + session_id: string + events: CoworkChatEvent[] +} diff --git a/frontend/src/utils/auth-token.ts b/frontend/src/utils/auth-token.ts new file mode 100644 index 000000000..264aca446 --- /dev/null +++ b/frontend/src/utils/auth-token.ts @@ -0,0 +1,24 @@ +import { ACCESS_TOKEN } from '@/constants/auth' + +export const AUTH_TOKEN_SET_EVENT = 'auth-token-set' +export const AUTH_TOKEN_CLEARED_EVENT = 'auth-token-cleared' + +function dispatchAuthEvent(eventName: string) { + if (typeof window === 'undefined') return + window.dispatchEvent(new CustomEvent(eventName)) +} + +export function getStoredAccessToken(): string | null { + if (typeof window === 'undefined') return null + return localStorage.getItem(ACCESS_TOKEN) +} + +export function storeAccessToken(token: string): void { + localStorage.setItem(ACCESS_TOKEN, token) + dispatchAuthEvent(AUTH_TOKEN_SET_EVENT) +} + +export function clearAccessToken(): void { + localStorage.removeItem(ACCESS_TOKEN) + dispatchAuthEvent(AUTH_TOKEN_CLEARED_EVENT) +} diff --git a/frontend/src/utils/is-tauri.ts b/frontend/src/utils/is-tauri.ts new file mode 100644 index 000000000..ecb5967e9 --- /dev/null +++ b/frontend/src/utils/is-tauri.ts @@ -0,0 +1,4 @@ +export const isTauri = + typeof window !== 'undefined' && + !!(window as unknown as { __TAURI_INTERNALS__?: unknown }) + .__TAURI_INTERNALS__ diff --git a/frontend/src/utils/question-mode.ts b/frontend/src/utils/question-mode.ts new file mode 100644 index 000000000..8b3085778 --- /dev/null +++ b/frontend/src/utils/question-mode.ts @@ -0,0 +1,46 @@ +import { QUESTION_MODE } from '@/typings/agent' + +type QuestionModeValue = QUESTION_MODE | string | null | undefined + +export const COWORK_ROUTE = '/cowork' + +const normalizeQuestionMode = ( + mode: QuestionModeValue +): QUESTION_MODE | null => { + switch (mode) { + case QUESTION_MODE.CHAT: + return QUESTION_MODE.CHAT + case QUESTION_MODE.COWORK: + return QUESTION_MODE.COWORK + case QUESTION_MODE.AGENT: + return QUESTION_MODE.AGENT + default: + return null + } +} + +export const isAgenticQuestionMode = ( + mode: QuestionModeValue +): mode is QUESTION_MODE.AGENT => + normalizeQuestionMode(mode) === QUESTION_MODE.AGENT + +export const isCoworkQuestionMode = ( + mode: QuestionModeValue +): mode is QUESTION_MODE.COWORK => + normalizeQuestionMode(mode) === QUESTION_MODE.COWORK + +interface SessionRouteOptions { + sessionId: string + agentType?: string | null +} + +export const getSessionRoute = ({ + sessionId, + agentType +}: SessionRouteOptions) => { + if (agentType === 'chat') { + return `/chat?id=${sessionId}` + } + + return `/${sessionId}` +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 8bf2ae67a..85f7ebd6c 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -6,6 +6,7 @@ interface ImportMetaEnv { readonly VITE_API_URL: string readonly VITE_GOOGLE_CLIENT_ID?: string readonly VITE_STRIPE_PUBLISHABLE_KEY?: string + readonly VITE_FRONTEND_URL?: string } interface ImportMeta { From 74a2d5a15548baec7cbcff664da63f23372d900b Mon Sep 17 00:00:00 2001 From: namtranii Date: Mon, 6 Apr 2026 15:28:54 +0700 Subject: [PATCH 02/12] feat: add cowork mode (backend) --- src/ii_agent/agents/cowork/__init__.py | 1 + .../agents/cowork/desktop_proxy_tools.py | 160 ++++++++ src/ii_agent/agents/cowork/factory.py | 351 ++++++++++++++++++ src/ii_agent/agents/cowork/models.py | 59 +++ src/ii_agent/agents/factory/agent.py | 52 ++- src/ii_agent/auth/router.py | 98 +++++ .../realtime/handlers/cowork_continue_run.py | 234 ++++++++++++ .../realtime/handlers/cowork_query.py | 174 +++++++++ src/ii_agent/realtime/handlers/factory.py | 4 + src/ii_agent/realtime/schemas.py | 25 ++ src/ii_agent/sessions/types.py | 1 + 11 files changed, 1158 insertions(+), 1 deletion(-) create mode 100644 src/ii_agent/agents/cowork/__init__.py create mode 100644 src/ii_agent/agents/cowork/desktop_proxy_tools.py create mode 100644 src/ii_agent/agents/cowork/factory.py create mode 100644 src/ii_agent/agents/cowork/models.py create mode 100644 src/ii_agent/realtime/handlers/cowork_continue_run.py create mode 100644 src/ii_agent/realtime/handlers/cowork_query.py diff --git a/src/ii_agent/agents/cowork/__init__.py b/src/ii_agent/agents/cowork/__init__.py new file mode 100644 index 000000000..72132b582 --- /dev/null +++ b/src/ii_agent/agents/cowork/__init__.py @@ -0,0 +1 @@ +"""Cowork-specific agent creation and tools.""" diff --git a/src/ii_agent/agents/cowork/desktop_proxy_tools.py b/src/ii_agent/agents/cowork/desktop_proxy_tools.py new file mode 100644 index 000000000..da9697c08 --- /dev/null +++ b/src/ii_agent/agents/cowork/desktop_proxy_tools.py @@ -0,0 +1,160 @@ +"""Desktop proxy tools for Cowork desktop execution modes.""" + +from __future__ import annotations + +from typing import Any, Dict, Iterable, List, Optional + +from ii_agent.core.logger import logger +from ii_agent.agents.tools.function import Function + + +def _placeholder_tool_entrypoint(**_: Any) -> str: + return "This tool is executed by the II Agent desktop runtime." + + +def _build_external_function( + *, + name: str, + display_name: str, + description: str, + parameters: dict[str, Any], +) -> Function: + return Function( + name=name, + description=description, + parameters=parameters, + display_name=display_name, + entrypoint=_placeholder_tool_entrypoint, + skip_entrypoint_processing=True, + external_execution=True, + show_result=True, + ) + + +def _normalize_aliases(raw_aliases: Any) -> List[str]: + if not isinstance(raw_aliases, list): + return [] + + aliases: List[str] = [] + for alias in raw_aliases: + if not isinstance(alias, str): + continue + normalized = alias.strip() + if not normalized: + continue + aliases.append(normalized) + return aliases + + +def _normalize_requested_names( + requested_tool_names: Optional[Iterable[str]], +) -> Optional[set[str]]: + if not requested_tool_names: + return None + + normalized_names = { + tool_name.strip().lower() + for tool_name in requested_tool_names + if isinstance(tool_name, str) and tool_name.strip() + } + return normalized_names or None + + +def _iter_desktop_tool_descriptors( + desktop_capabilities: Optional[Dict[str, Any]], +) -> List[Dict[str, Any]]: + if not isinstance(desktop_capabilities, dict): + return [] + tools = desktop_capabilities.get("tools") + if not isinstance(tools, list): + return [] + return [tool for tool in tools if isinstance(tool, dict)] + + +def build_desktop_proxy_tools( + desktop_capabilities: Optional[Dict[str, Any]] = None, + requested_tool_names: Optional[Iterable[str]] = None, +) -> List[Function]: + requested_names = _normalize_requested_names(requested_tool_names) + tool_specs = _iter_desktop_tool_descriptors(desktop_capabilities) + if not tool_specs: + logger.warning( + "Cowork desktop tool runtime requested but no desktop capability descriptors were provided" + ) + return [] + + proxy_tools = [] + for tool_spec in tool_specs: + name = tool_spec.get("name") + description = tool_spec.get("description") + if not isinstance(name, str) or not name.strip(): + continue + if not isinstance(description, str) or not description.strip(): + continue + + display_name = tool_spec.get("display_name") + if not isinstance(display_name, str) or not display_name.strip(): + display_name = name + parameters = tool_spec.get("input_schema") + if not isinstance(parameters, dict): + parameters = {"type": "object", "properties": {}, "required": []} + aliases = _normalize_aliases(tool_spec.get("aliases")) + + names_to_publish: List[str] + if requested_names is None: + names_to_publish = [name] + else: + candidate_names = [name, *aliases] + names_to_publish = [] + for candidate_name in candidate_names: + if candidate_name.strip().lower() not in requested_names: + continue + if candidate_name in names_to_publish: + continue + names_to_publish.append(candidate_name) + if not names_to_publish: + continue + + for published_name in names_to_publish: + proxy_tools.append( + _build_external_function( + name=published_name, + display_name=display_name, + description=description, + parameters=parameters, + ) + ) + + return proxy_tools + + +def build_desktop_skill_context( + desktop_capabilities: Optional[Dict[str, Any]] = None, + requested_skill_names: Optional[Iterable[str]] = None, +) -> Optional[str]: + if not isinstance(desktop_capabilities, dict): + return None + + skills = desktop_capabilities.get("skills") + if not isinstance(skills, list) or not skills: + return None + + requested_names = _normalize_requested_names(requested_skill_names) + skill_lines: List[str] = [] + for skill in skills: + if not isinstance(skill, dict): + continue + name = skill.get("name") + description = skill.get("description") + if not isinstance(name, str) or not name.strip(): + continue + if requested_names is not None and name.strip().lower() not in requested_names: + continue + if not isinstance(description, str) or not description.strip(): + continue + skill_lines.append(f"- {name}: {description}") + + if not skill_lines: + return None + + return "Desktop skills available in this runtime:\n" + "\n".join(skill_lines) diff --git a/src/ii_agent/agents/cowork/factory.py b/src/ii_agent/agents/cowork/factory.py new file mode 100644 index 000000000..69341a713 --- /dev/null +++ b/src/ii_agent/agents/cowork/factory.py @@ -0,0 +1,351 @@ +"""Cowork-specific agent creation helpers.""" + +from typing import Any, Dict, List, Optional + +from ii_agent.core.config.llm_config import LLMConfig +from ii_agent.core.logger import logger +from ii_agent.agents.prompts.agent_prompts import get_system_prompt_for_agent_type +from ii_server.core.workspace import WorkspaceManager +from ii_agent.agents.agent import IIAgent +from ii_agent.agents.connector.base import BaseConnectorTool +from ii_agent.agents.cowork.desktop_proxy_tools import ( + build_desktop_proxy_tools, + build_desktop_skill_context, +) +from ii_agent.agents.factory.agent import AgentFactory +from ii_agent.agents.factory.tool_manager import AgentToolManager +from ii_agent.agents.factory.tools import AgentType, TOOL_CLASS_MAP +from ii_agent.agents.models.utils import get_model +from ii_agent.agents.sessions.base import SessionStore +from ii_agent.agents.skills.base import SkillCreator +from ii_agent.agents.skills.prompt_db import generate_skill_tool_description +from ii_agent.settings.llm import Provider + +# Hardcoded defaults for cowork agents +DEFAULT_MODEL_ID = "gpt-5.2" +DEFAULT_PROVIDER = Provider.OPENAI + +class CoworkAgentFactory: + """Factory for cowork-specific IIAgent creation with runtime overrides.""" + + def __init__(self, factory: AgentFactory): + self.factory = factory + + @staticmethod + def _normalize_name_list(values: Optional[List[str]]) -> Optional[set[str]]: + if not values: + return None + normalized = { + value.strip().lower() + for value in values + if isinstance(value, str) and value.strip() + } + return normalized or None + + def _apply_skill_name_overrides(self, skill_tool, skill_names: Optional[List[str]]): + requested_skill_names = self._normalize_name_list(skill_names) + if skill_tool is None or not requested_skill_names: + return skill_tool + + filtered_registry = { + name: skill + for name, skill in skill_tool._skills_registry.items() + if name.strip().lower() in requested_skill_names + } + missing_skill_names = requested_skill_names - { + name.strip().lower() for name in skill_tool._skills_registry.keys() + } + if missing_skill_names: + logger.warning( + f"Requested cowork skills were not found: {sorted(missing_skill_names)}" + ) + + if not filtered_registry: + logger.warning("Cowork skill override removed all available skills") + return None + + skill_tool._skills_registry = filtered_registry + skill_tool.description = generate_skill_tool_description(list(filtered_registry.values())) + return skill_tool + + def _add_requested_tools( + self, + agent_tools: List[Any], + requested_tool_names: Optional[List[str]], + ) -> List[Any]: + normalized_requested = self._normalize_name_list(requested_tool_names) + if not normalized_requested: + return agent_tools + + existing_names = { + tool.name.strip().lower() for tool in agent_tools if hasattr(tool, "name") + } + missing_tool_names = normalized_requested - existing_names + for tool_name in missing_tool_names: + requested_tool = None + for registered_tool_name in TOOL_CLASS_MAP.keys(): + if registered_tool_name.strip().lower() == tool_name: + requested_tool = AgentToolManager.convert_tool(registered_tool_name) + break + if requested_tool is None: + logger.warning(f"Requested cowork tool `{tool_name}` is not registered") + continue + agent_tools.append(requested_tool) + + return agent_tools + + def _filter_tools_by_name( + self, + agent_tools: List[Any], + requested_tool_names: Optional[List[str]], + ) -> List[Any]: + normalized_requested = self._normalize_name_list(requested_tool_names) + if not normalized_requested: + return agent_tools + + filtered_tools = [ + tool + for tool in agent_tools + if getattr(tool, "name", "").strip().lower() in normalized_requested + ] + found_tool_names = { + getattr(tool, "name", "").strip().lower() + for tool in filtered_tools + if hasattr(tool, "name") + } + missing_tool_names = normalized_requested - found_tool_names + if missing_tool_names: + logger.warning( + f"Requested cowork tools were not created: {sorted(missing_tool_names)}" + ) + + return filtered_tools + + @staticmethod + def _dedupe_tools_by_name(agent_tools: List[Any]) -> List[Any]: + unique_tools: List[Any] = [] + seen_names: set[str] = set() + for tool in agent_tools: + tool_name = getattr(tool, "name", None) + if not tool_name: + unique_tools.append(tool) + continue + normalized_name = tool_name.strip().lower() + if normalized_name in seen_names: + continue + seen_names.add(normalized_name) + unique_tools.append(tool) + return unique_tools + + @staticmethod + def _build_agent_runtime_config( + agent_type: AgentType, + agent_config: Optional[Dict[str, Any]], + ) -> Dict[str, Any]: + config_overrides = agent_config or {} + return { + "name": config_overrides.get("name", f"{agent_type.value}_agent"), + "description": config_overrides.get("description"), + "additional_context": config_overrides.get("additional_context"), + "tool_call_limit": config_overrides.get("tool_call_limit"), + "tool_choice": config_overrides.get("tool_choice"), + "retries": config_overrides.get("retries", 0), + "delay_between_retries": config_overrides.get("delay_between_retries", 1), + "exponential_backoff": config_overrides.get("exponential_backoff", False), + "stream": config_overrides.get("stream", True), + "stream_events": config_overrides.get("stream_events", True), + "store_events": config_overrides.get("store_events", True), + "delegate_to_all_members": config_overrides.get("delegate_to_all_members", False), + "stream_member_events": config_overrides.get("stream_member_events", True), + "store_member_responses": config_overrides.get("store_member_responses", False), + "role": config_overrides.get("role"), + } + + @staticmethod + def _get_cowork_metadata(metadata: Optional[Dict[str, Any]]) -> Dict[str, Any]: + if not isinstance(metadata, dict): + return {} + cowork_metadata = metadata.get("cowork") + return cowork_metadata if isinstance(cowork_metadata, dict) else {} + + @classmethod + def _is_desktop_execution_mode(cls, metadata: Optional[Dict[str, Any]]) -> bool: + cowork_metadata = cls._get_cowork_metadata(metadata) + return cowork_metadata.get("execution_context") == "desktop" + + @classmethod + def _uses_desktop_proxy_tools(cls, metadata: Optional[Dict[str, Any]]) -> bool: + cowork_metadata = cls._get_cowork_metadata(metadata) + return cls._is_desktop_execution_mode(metadata) and cowork_metadata.get( + "tool_runtime" + ) in { + "desktop_builtin", + "desktop_proxy", + } + + @classmethod + def _uses_desktop_tools_exclusively(cls, metadata: Optional[Dict[str, Any]]) -> bool: + cowork_metadata = cls._get_cowork_metadata(metadata) + binding_mode = cowork_metadata.get("tool_binding_mode") + if binding_mode is None: + binding_mode = "desktop_only" if cls._uses_desktop_proxy_tools(metadata) else None + return cls._uses_desktop_proxy_tools(metadata) and binding_mode == "desktop_only" + + async def create_agent( + self, + user_id: str, + session_id: str, + llm_config: LLMConfig, + agent_type: AgentType = AgentType.GENERAL, + workspace_manager: Optional[WorkspaceManager] = None, + session_store: Optional[SessionStore] = None, + tool_args: Optional[Dict[str, Any]] = None, + metadata: Optional[Dict[str, Any]] = None, + system_prompt: Optional[str] = None, + skill_creator: Optional[SkillCreator] = None, + connector_tool: Optional[BaseConnectorTool] = None, + tool_names: Optional[List[str]] = None, + skill_names: Optional[List[str]] = None, + desktop_capabilities: Optional[Dict[str, Any]] = None, + agent_config: Optional[Dict[str, Any]] = None, + ) -> IIAgent: + logger.info(f"Creating cowork {agent_type} agent for session {session_id}") + + # Override LLM config with hardcoded defaults + llm_config = llm_config.model_copy(update={ + "model": DEFAULT_MODEL_ID, + "provider": DEFAULT_PROVIDER, + }) + + tool_args = tool_args or {} + has_media = tool_args.get("media_generation", False) + has_task_agent = tool_args.get("task_agent", False) + has_researcher = tool_args.get("deep_research", False) + has_design_doc = tool_args.get("design_document", False) + + provider = llm_config.provider + model = get_model(provider, llm_config=llm_config) + + uses_desktop_proxy_tools = self._uses_desktop_proxy_tools(metadata) + uses_desktop_tools_exclusively = self._uses_desktop_tools_exclusively(metadata) + + if uses_desktop_tools_exclusively: + agent_tools = build_desktop_proxy_tools( + desktop_capabilities=desktop_capabilities, + requested_tool_names=tool_names, + ) + logger.info("Cowork desktop tool runtime enabled; using desktop proxy tools only") + else: + agent_tools = AgentToolManager.resolve_tools( + agent_type=agent_type, + model_name=model.id, + tool_args=tool_args, + ) + agent_tools = self._add_requested_tools(agent_tools, tool_names) + if uses_desktop_proxy_tools: + agent_tools.extend( + build_desktop_proxy_tools( + desktop_capabilities=desktop_capabilities, + requested_tool_names=tool_names, + ) + ) + logger.info("Cowork desktop tool runtime enabled; merging desktop proxy tools") + + if skill_creator is not None and not uses_desktop_tools_exclusively: + skill_tool = await skill_creator.create_skill_tool() + skill_tool = self._apply_skill_name_overrides(skill_tool, skill_names) + if skill_tool: + agent_tools.append(skill_tool) + logger.info(f"Added SkillTool with {len(skill_tool._skills_registry)} skills") + + if connector_tool is not None and not uses_desktop_tools_exclusively: + try: + connector_tools = await connector_tool.create_connector_tools( + workspace_manager=workspace_manager, + ) + if connector_tools: + logger.info( + f"[Cowork Factory] Received {len(connector_tools)} connector tools from loader" + ) + logger.debug( + f"[Cowork Factory] Connector tool names: {[t.name for t in connector_tools]}" + ) + agent_tools.extend(connector_tools) + except Exception as e: + logger.error( + f"[Cowork Factory] Failed to load connector tools: {e}", exc_info=True + ) + + agent_tools = self._filter_tools_by_name(agent_tools, tool_names) + agent_tools = self._dedupe_tools_by_name(agent_tools) + AgentToolManager.log_tool_summary(agent_tools, f"Cowork agent {agent_type.value}") + + if system_prompt is None: + workspace_path = ( + workspace_manager.workspace_path.as_posix() + if workspace_manager + else "/workspace" + ) + system_prompt = await get_system_prompt_for_agent_type( + agent_type=agent_type, + workspace_path=workspace_path, + design_document=has_design_doc, + researcher=has_researcher, + media=has_media, + a2a_agents=False, + task_agent=has_task_agent, + metadata=metadata, + provider=llm_config.provider if llm_config else None, + ) + + desktop_skill_context = build_desktop_skill_context( + desktop_capabilities=desktop_capabilities, + requested_skill_names=skill_names, + ) + if desktop_skill_context: + system_prompt = f"{system_prompt}\n\n{desktop_skill_context}" + + sub_agents = [] + if has_task_agent: + task_agent = await self.factory.create_task_agent_tool( + user_id=user_id, + session_id=session_id, + llm_config=llm_config, + tool_args=tool_args, + ) + sub_agents.append(task_agent) + + runtime_config = self._build_agent_runtime_config( + agent_type=agent_type, + agent_config=agent_config, + ) + + agent = IIAgent( + user_id=user_id, + session_id=session_id, + model=model, + name=runtime_config["name"], + description=runtime_config["description"], + additional_context=runtime_config["additional_context"], + tools=agent_tools, + tool_call_limit=runtime_config["tool_call_limit"], + tool_choice=runtime_config["tool_choice"], + system_message=system_prompt, + session_store=session_store, + metadata=metadata, + sub_agents=sub_agents, + retries=runtime_config["retries"], + delay_between_retries=runtime_config["delay_between_retries"], + exponential_backoff=runtime_config["exponential_backoff"], + stream=runtime_config["stream"], + stream_events=runtime_config["stream_events"], + store_events=runtime_config["store_events"], + delegate_to_all_members=runtime_config["delegate_to_all_members"], + stream_member_events=runtime_config["stream_member_events"], + store_member_responses=runtime_config["store_member_responses"], + role=runtime_config["role"], + ) + agent.set_id() + + logger.info(f"Created cowork {agent_type.value} agent with {len(agent_tools)} tools") + return agent diff --git a/src/ii_agent/agents/cowork/models.py b/src/ii_agent/agents/cowork/models.py new file mode 100644 index 000000000..c15b2041b --- /dev/null +++ b/src/ii_agent/agents/cowork/models.py @@ -0,0 +1,59 @@ +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel + +from ii_agent.realtime.schemas import QueryCommandContent + + +class CoworkAgentConfig(BaseModel): + """Runtime overrides for cowork-specific IIAgent creation.""" + + name: Optional[str] = None + description: Optional[str] = None + additional_context: Optional[str] = None + retries: Optional[int] = None + delay_between_retries: Optional[int] = None + exponential_backoff: Optional[bool] = None + stream: Optional[bool] = None + stream_events: Optional[bool] = None + store_events: Optional[bool] = None + tool_call_limit: Optional[int] = None + tool_choice: Optional[Any] = None + delegate_to_all_members: Optional[bool] = None + stream_member_events: Optional[bool] = None + store_member_responses: Optional[bool] = None + role: Optional[str] = None + + +class DesktopCapabilityToolDescriptor(BaseModel): + """Desktop tool descriptor sent by the desktop runtime.""" + + name: str + aliases: List[str] = [] + display_name: Optional[str] = None + description: str + input_schema: Dict[str, Any] = {} + + +class DesktopCapabilitySkillDescriptor(BaseModel): + """Desktop skill descriptor sent by the desktop runtime.""" + + name: str + description: str + + +class DesktopCapabilitiesContent(BaseModel): + """Desktop tool and skill catalog sent with cowork requests.""" + + tools: List[DesktopCapabilityToolDescriptor] = [] + skills: List[DesktopCapabilitySkillDescriptor] = [] + + +class CoworkQueryCommandContent(QueryCommandContent): + """Extended query contract for cowork-specific agent overrides.""" + + system_prompt: Optional[str] = None + tool_names: Optional[List[str]] = None + skill_names: Optional[List[str]] = None + agent_config: Optional[CoworkAgentConfig] = None + desktop_capabilities: Optional[DesktopCapabilitiesContent] = None diff --git a/src/ii_agent/agents/factory/agent.py b/src/ii_agent/agents/factory/agent.py index 99d952b47..694325e6f 100644 --- a/src/ii_agent/agents/factory/agent.py +++ b/src/ii_agent/agents/factory/agent.py @@ -1,6 +1,6 @@ """Agent factory for creating configured agent instances.""" -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional from uuid import UUID from ii_agent.core.config.settings import Settings, get_settings @@ -17,7 +17,9 @@ from ii_agent.agents.models.utils import get_model from ii_agent.agents.sessions import SessionStore from ii_agent.agents.tools.task import SYSTEM_PROMPT, TaskAgentTool, DESCRIPTION +from ii_agent.core.db import get_session_factory from ii_agent.core.logger import logger +from ii_agent.sessions.schemas import SessionInfo def _append_prompt_section(base_prompt: Optional[str], section: Optional[str]) -> Optional[str]: @@ -491,4 +493,52 @@ async def create_codex_agent_tool( return None + async def create_cowork_agent( + self, + session_info: SessionInfo, + llm_config: LLMConfig, + workspace_manager: WorkspaceManager, + agent_type: AgentType = AgentType.GENERAL, + tool_args: Optional[Dict[str, Any]] = None, + metadata: Optional[Dict[str, Any]] = None, + default_repository: Optional[Dict[str, str]] = None, + system_prompt: Optional[str] = None, + tool_names: Optional[List[str]] = None, + skill_names: Optional[List[str]] = None, + desktop_capabilities: Optional[Dict[str, Any]] = None, + agent_config: Optional[Dict[str, Any]] = None, + ) -> IIAgent: + from ii_agent.agents.skills.db_creator import DbSkillCreator + from ii_agent.agents.connector.connector_tool import ConnectorTool + from ii_agent.agents.cowork.factory import CoworkAgentFactory + from ii_agent.agents.sessions.store import AgentSessionStore + + logger.info( + f"[Agent Service] Creating Cowork V1 agent for user {session_info.user_id}" + ) + + skill_creator = DbSkillCreator(user_id=str(session_info.user_id)) + connector_tool = ConnectorTool( + user_id=str(session_info.user_id), default_repository=default_repository + ) + + cowork_factory = CoworkAgentFactory(factory=self) + return await cowork_factory.create_agent( + user_id=str(session_info.user_id), + session_id=str(session_info.id), + llm_config=llm_config, + agent_type=agent_type, + workspace_manager=workspace_manager, + session_store=AgentSessionStore(session_maker=get_session_factory()), + tool_args=tool_args, + metadata=metadata, + skill_creator=skill_creator, + connector_tool=connector_tool, + system_prompt=system_prompt, + tool_names=tool_names, + skill_names=skill_names, + desktop_capabilities=desktop_capabilities, + agent_config=agent_config, + ) + agent_factory = AgentFactory(config=get_settings()) diff --git a/src/ii_agent/auth/router.py b/src/ii_agent/auth/router.py index 7669a55ee..bf158eab9 100644 --- a/src/ii_agent/auth/router.py +++ b/src/ii_agent/auth/router.py @@ -10,6 +10,7 @@ import httpx from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse, RedirectResponse +from pydantic import BaseModel from fastapi_sso.sso.google import GoogleSSO from itsdangerous import URLSafeSerializer, BadSignature @@ -356,6 +357,28 @@ async def google_login(settings: SettingsDep): params={"prompt": "consent", "access_type": "offline"} ) +@router.get("/oauth/google/desktop/login-url") +async def google_desktop_login_url(desktop_state: str): + """Return the Google OAuth URL as JSON (no redirect). + + The desktop frontend fetches this, then redirects the browser itself + so the user never sees a backend URL in the address bar. + """ + settings = get_settings() + google_sso = GoogleSSO( + settings.oauth.google_client_id or "", + settings.oauth.google_client_secret or "", + redirect_uri=settings.oauth.google_redirect_uri, + ) + state_serializer = URLSafeSerializer(settings.oauth.session_secret_key, salt="desktop-google") + custom_state = state_serializer.dumps({"desktop_state": desktop_state}) + + async with google_sso: + url = await google_sso.get_login_url( + params={"prompt": "consent", "access_type": "offline"}, + state=custom_state, + ) + return {"url": url} @router.get("/oauth/google/callback") async def google_callback( @@ -444,12 +467,64 @@ async def google_callback( str(user_stored.role), ) + # Check if this was a desktop app login (desktop_state encoded in OAuth state). + desktop_state_value: Optional[str] = None + raw_state = request.query_params.get("state") + if raw_state: + try: + ds_serializer = URLSafeSerializer(settings.oauth.session_secret_key, salt="desktop-google") + decoded = ds_serializer.loads(raw_state) + if isinstance(decoded, dict): + desktop_state_value = decoded.get("desktop_state") + except BadSignature: + pass + + if desktop_state_value: + from ii_agent.core.redis.client import get_redis_client + redis_client = get_redis_client() + + token_payload = { + "access_token": token_payload["access_token"], + "refresh_token": token_payload["refresh_token"], + "token_type": "bearer", + "expires_in": token_payload["expires_in"], + } + await redis_client.setex( + f"desktop_auth:{desktop_state_value}", 300, json.dumps(token_payload) + ) + # Redirect to frontend — show "login successful" message. + # The desktop app is polling and will pick up the token automatically. + frontend_origin = settings.ii_frontend_url if settings.ii_frontend_url else "http://localhost:1420" + return RedirectResponse( + url=f"{frontend_origin}/login?desktop_auth=success", + status_code=302, + ) + return TokenResponse( access_token=token_payload["access_token"], refresh_token=token_payload["refresh_token"], expires_in=token_payload["expires_in"], ) +@router.get("/oauth/google/poll") +async def google_poll(state: str): + """Poll for desktop auth token. + + The desktop app calls this endpoint with the ``state`` it generated + before opening the system browser. Returns the token payload once + the user completes login, or 202 while still waiting. + """ + from ii_agent.core.redis.client import get_redis_client + redis_client = get_redis_client() + + data = await redis_client.get(f"desktop_auth:{state}") + if not data: + return {"status": "pending"} + + # Delete after first successful read so the token can't be replayed. + await redis_client.delete(f"desktop_auth:{state}") + return json.loads(data) + @router.get("/me", response_model=UserPublic) async def reader_user_me( @@ -469,3 +544,26 @@ async def reader_user_me( subscription_current_period_end=current_user.subscription_current_period_end, language=str(current_user.language or "en"), ) + +class UpdatePreferencesRequest(BaseModel): + has_memory: Optional[bool] = None + + +@router.patch("/me/preferences") +async def update_user_preferences( + current_user: CurrentUser, + db: DBSession, + body: UpdatePreferencesRequest, +) -> dict[str, Any]: + """Update user preference settings (memory, personalization).""" + import copy + + metadata = copy.deepcopy(current_user.user_metadata) if isinstance(current_user.user_metadata, dict) else {} + old_prefs = metadata.get("preferences", {}) + updates = body.model_dump(exclude_none=True) + metadata["preferences"] = {**old_prefs, **updates} + + current_user.user_metadata = metadata + await db.commit() + + return {"message": "Preferences updated successfully", "preferences": metadata.get("preferences", {})} diff --git a/src/ii_agent/realtime/handlers/cowork_continue_run.py b/src/ii_agent/realtime/handlers/cowork_continue_run.py new file mode 100644 index 000000000..cdf083ce6 --- /dev/null +++ b/src/ii_agent/realtime/handlers/cowork_continue_run.py @@ -0,0 +1,234 @@ +"""Handler for cowork_continue_run command. + +Adapted from the legacy ``server.socket.command.cowork_continue_run`` +to use the new ``BaseCommandHandler`` / pubsub / container pattern. +""" + +from __future__ import annotations + +from typing import Any +from uuid import UUID + +from ii_agent.agents.factory.agent import agent_factory +from ii_agent.agents.sessions import AgentSessionStore +from ii_agent.agents.types import AgentType +from ii_agent.core.db import get_db_session_local, get_session_factory +from ii_agent.core.logger import logger +from ii_agent.realtime.events.app_events import ( + AgentContinueEvent, + AgentProcessingEvent, + ErrorCode, +) +from ii_agent.realtime.handlers.base import CommandType +from ii_agent.realtime.handlers.continue_run import ContinueRunHandler +from ii_agent.realtime.schemas import CoworkContinueRunContent +from ii_agent.sessions.schemas import SessionInfo + + +class CoworkContinueRunHandler(ContinueRunHandler): + """Cowork-only continue handler with desktop external execution support.""" + + _content_type = CoworkContinueRunContent + + def get_command_type(self) -> CommandType: + return CommandType.COWORK_CONTINUE_RUN + + async def handle(self, content: CoworkContinueRunContent, session_info: SessionInfo) -> None: + if session_info.api_version != "v1": + await self._send_error_event( + session_info.id, + error_code=ErrorCode.UNSUPPORTED_API_VERSION, + message="Continue run is only supported for v1 API version", + ) + return + + run_id = content.run_id + confirmed = content.confirmed + user_input = content.user_input + external_tool_results = content.external_tool_results or [] + + # Send AGENT_CONTINUE event immediately + await self.send_event( + AgentContinueEvent( + session_id=UUID(str(session_info.id)), + content={ + "message": "Agent continuing...", + "confirmed": confirmed, + "run_id": run_id, + }, + ) + ) + + try: + session_store = AgentSessionStore(session_maker=get_session_factory()) + run_response = await session_store.get_by_run_id( + run_id=run_id, session_id=str(session_info.id) + ) + + if not run_response: + await self._send_error_event( + session_info.id, + error_code=ErrorCode.RUN_NOT_FOUND, + message=f"Run {run_id} not found", + ) + return + + run_task_data = await self._load_cowork_run_task_data(run_id) + + for tool in run_response.tools_requiring_confirmation: + tool.confirmed = bool(confirmed) + logger.info( + "Cowork continue confirmation for run %s tool_call_id=%s confirmed=%s", + run_id, + tool.tool_call_id, + confirmed, + ) + + for tool in run_response.tools_requiring_user_input: + if confirmed and user_input: + self._apply_user_input_to_tool(tool, user_input, run_id) + tool.answered = True + else: + tool.answered = False + + self._apply_external_tool_results( + run_response.tools, + external_tool_results, + run_id, + ) + + # Get model config — fall back to hardcoded default for cowork + llm_config = None + if session_info.model_setting_id: + try: + async with get_db_session_local() as db: + llm_config = ( + await self._container.model_setting_service.resolve_config_by_setting_id( + db, setting_id=session_info.model_setting_id + ) + ) + except (ValueError, Exception) as e: + logger.warning( + "Cowork continue_run model resolution failed: %s, using default", e + ) + + # Create cowork agent for continuation + agent = await agent_factory.create_cowork_agent( + session_info=session_info, + llm_config=llm_config, + workspace_manager=None, + agent_type=AgentType(session_info.agent_type) + if session_info.agent_type + else AgentType.GENERAL, + metadata=run_task_data.get("metadata"), + system_prompt=run_task_data.get("system_prompt"), + tool_names=run_task_data.get("tool_names"), + skill_names=run_task_data.get("skill_names"), + desktop_capabilities=run_task_data.get("desktop_capabilities"), + agent_config=run_task_data.get("agent_config"), + ) + + await self.send_event( + AgentProcessingEvent( + session_id=UUID(str(session_info.id)), + message="Resuming agent execution...", + content={ + "message": "Resuming agent execution...", + "run_id": run_id, + }, + ) + ) + + event_stream = agent.acontinue_run( + run_id=run_response.run_id, + updated_tools=run_response.tools, + stream=True, + stream_events=True, + ) + + await self.process_agent_event_stream( + event_stream, + session_info, + run_id=UUID(run_response.run_id), + is_user_key=llm_config.is_user_model(), + llm_config=llm_config, + ) + + except ValueError as error: + logger.error(f"ValueError in cowork_continue_run: {str(error)}") + await self._send_error_event( + session_info.id, + error_code=ErrorCode.VALIDATION_ERROR, + message=str(error), + ) + except Exception as error: + logger.error( + f"Error in cowork_continue_run handler: {str(error)}", + exc_info=True, + ) + await self._send_error_event( + session_info.id, + error_code=ErrorCode.EXECUTION_ERROR, + message=f"Failed to continue run: {str(error)}", + ) + + async def _load_cowork_run_task_data(self, run_id: str) -> dict[str, Any]: + try: + run_task_id = UUID(str(run_id)) + except ValueError: + return {} + + async with get_db_session_local() as db: + run_task = await self._container.run_task_service.get_task_by_id( + db, + task_id=run_task_id, + ) + + if not run_task or not isinstance(run_task.data, dict): + return {} + + return run_task.data + + @staticmethod + def _apply_external_tool_results( + tools: list[Any], + external_tool_results: list[dict[str, Any]], + run_id: str, + ) -> None: + if not external_tool_results: + return + + tool_results_by_id = { + str(result.get("tool_call_id")): result + for result in external_tool_results + if isinstance(result, dict) and result.get("tool_call_id") + } + + for tool in tools: + tool_call_id = getattr(tool, "tool_call_id", None) + if not tool_call_id: + continue + + external_result = tool_results_by_id.get(str(tool_call_id)) + if not external_result: + continue + + llm_content = external_result.get("llm_content") + user_display_content = external_result.get("user_display_content") + tool.result = llm_content if llm_content is not None else user_display_content + tool.tool_call_error = bool(external_result.get("is_error")) + + tool_input = external_result.get("tool_input") + if isinstance(tool_input, dict): + tool.tool_args = tool_input + + tool_name = external_result.get("tool_name") + if isinstance(tool_name, str) and tool_name.strip(): + tool.tool_name = tool_name + + logger.info( + "Cowork continue applied external tool result for run %s tool_call_id=%s error=%s", + run_id, + tool_call_id, + tool.tool_call_error, + ) diff --git a/src/ii_agent/realtime/handlers/cowork_query.py b/src/ii_agent/realtime/handlers/cowork_query.py new file mode 100644 index 000000000..74c636a33 --- /dev/null +++ b/src/ii_agent/realtime/handlers/cowork_query.py @@ -0,0 +1,174 @@ +"""Handler for cowork_query command with agent overrides. + +Adapted from the legacy ``server.socket.command.cowork_query`` +to use the new ``BaseCommandHandler`` / pubsub / container pattern. +""" + +from __future__ import annotations + +from ii_agent.agents.factory.agent import agent_factory +from ii_agent.agents.types import AgentType +from ii_agent.core.db import get_db_session_local +from ii_agent.core.logger import logger +from ii_agent.realtime.events.app_events import ErrorCode +from ii_agent.realtime.handlers.base import CommandType +from ii_agent.realtime.handlers.query import UserQueryHandler +from ii_agent.realtime.schemas import QueryCommandContent +from ii_agent.sessions.schemas import SessionInfo +from ii_agent.sessions.types import AppKind +from ii_agent.settings.llm.schemas import ModelConfig +from ii_agent.tasks.types import RunStatus, TaskType + + +class CoworkQueryHandler(UserQueryHandler): + """Handler for cowork-specific query command with agent overrides.""" + + COWORK_SESSION_AGENT_TYPE = "cowork" + + def get_command_type(self) -> CommandType: + return CommandType.COWORK_QUERY + + async def handle(self, content: QueryCommandContent, existing_session: SessionInfo) -> None: + query_command = content + + # Stamp the session as a cowork app_kind so it is excluded from the + # standard Project sidebar listing. Cowork sessions are managed by + # the desktop runtime and must never appear there. + await self._ensure_cowork_app_kind(existing_session.id) + + is_valid, session_info, llm_config = await self.validate_and_update_session( + existing_session, query_command + ) + + # For cowork mode, fall back to hardcoded model if resolution fails + if not is_valid or not llm_config: + if not session_info: + return + + await self._handle_cowork_query(query_command, session_info, llm_config) + + async def _handle_cowork_query( + self, + query_command: QueryCommandContent, + session_info: SessionInfo, + llm_config: ModelConfig, + ) -> None: + """Handle cowork query by delegating to the cowork agent factory.""" + plan_service = self._container.plan_service + run_service = self._container.run_task_service + + milestone_context = None + if query_command.milestone_ids and query_command.plan_context: + milestone_context = plan_service.get_milestone_context( + plan_context=query_command.plan_context, + milestone_ids=query_command.milestone_ids, + ) + + run_task = None + try: + async with get_db_session_local() as db: + run_task = await run_service.claim_task( + db, + session_id=session_info.id, + task_type=TaskType.AGENT_RUN, + data=query_command.model_dump(), + ) + user_event, _ = await self.create_user_message_event( + session_info, query_command, db, run_id=run_task.id + ) + await db.commit() + + await self.send_event(user_event) + except Exception as e: + logger.error(f"Failed to claim task: {e}", exc_info=True) + await self._send_error_event( + session_id=session_info.id, + error_code=ErrorCode.INTERNAL_ERROR, + message=str(e), + user_id=session_info.user_id, + ) + return + + final_status = RunStatus.FAILED + try: + agent = await agent_factory.create_cowork_agent( + session_info=session_info, + llm_config=llm_config, + workspace_manager=None, + agent_type=AgentType(session_info.agent_type) + if session_info.agent_type + else AgentType.GENERAL, + tool_args=query_command.tool_args, + metadata=query_command.metadata, + system_prompt=getattr(query_command, "system_prompt", None), + tool_names=getattr(query_command, "tool_names", None), + skill_names=getattr(query_command, "skill_names", None), + desktop_capabilities=getattr(query_command, "desktop_capabilities", None), + agent_config=getattr(query_command, "agent_config", None), + ) + + instruction_text = query_command.text + if milestone_context: + instruction_text = f"{milestone_context}\n\nUser instruction: {query_command.text}" + + event_stream = await agent.arun( + instruction_text, + stream=True, + stream_events=True, + run_id=str(run_task.id), + yield_run_output=False, + ) + + final_status = await self.process_agent_event_stream( + event_stream, + session_info, + run_id=run_task.id, + is_user_key=llm_config.is_user_model(), + llm_config=llm_config, + ) + + async with get_db_session_local() as db: + await plan_service.update_milestones_after_run( + db, + session_id=session_info.id, + milestone_ids=query_command.milestone_ids, + status=final_status, + ) + + except Exception as e: + logger.opt(exception=True).error("Error processing cowork query: {}", str(e)) + async with get_db_session_local() as db: + await run_service.transition_status( + db, task_id=run_task.id, to_status=RunStatus.FAILED + ) + await db.commit() + if query_command.milestone_ids: + async with get_db_session_local() as db: + await plan_service.reset_milestones_to_pending( + db, + session_id=session_info.id, + milestone_ids=query_command.milestone_ids, + ) + raise + + async def _ensure_cowork_app_kind(self, session_id) -> None: + """Persist ``app_kind = cowork`` on the session row if not already set. + + Cowork sessions must be invisible to the standard Project sidebar + listing. Stamping ``app_kind`` here is the canonical discriminator + used by ``SessionRepository.get_user_sessions``. + """ + try: + async with get_db_session_local() as db: + session = await self._container.session_service._session_repo.get_by_id( + db, session_id + ) + if session is None: + return + if session.app_kind != AppKind.COWORK: + session.app_kind = AppKind.COWORK + await db.commit() + except Exception as exc: + logger.warning( + "Failed to stamp cowork app_kind on session %s: %s", session_id, exc + ) diff --git a/src/ii_agent/realtime/handlers/factory.py b/src/ii_agent/realtime/handlers/factory.py index a27ceeccc..eb971de48 100644 --- a/src/ii_agent/realtime/handlers/factory.py +++ b/src/ii_agent/realtime/handlers/factory.py @@ -38,6 +38,8 @@ from ii_agent.realtime.handlers.design_save_state import DesignSaveStateHandler from ii_agent.realtime.handlers.design_sync_state import DesignSyncStateHandler from ii_agent.realtime.handlers.slide_deck_sync_state import SlideDeckSyncStateHandler +from ii_agent.realtime.handlers.cowork_query import CoworkQueryHandler +from ii_agent.realtime.handlers.cowork_continue_run import CoworkContinueRunHandler class CommandHandlerFactory: @@ -62,6 +64,7 @@ def _initialize_handlers(self) -> None: self._handlers = { CommandType.QUERY: query_handler, + CommandType.COWORK_QUERY: CoworkQueryHandler(pubsub=ps, container=ct), CommandType.PLAN: PlanHandler(pubsub=ps, container=ct), CommandType.SANDBOX_STATUS: SandboxStatusHandler(pubsub=ps, container=ct), CommandType.AWAKE_SANDBOX: AwakeSandboxHandler(pubsub=ps, container=ct), @@ -69,6 +72,7 @@ def _initialize_handlers(self) -> None: CommandType.PING: PingHandler(pubsub=ps, container=ct), CommandType.CANCEL: CancelHandler(pubsub=ps, container=ct), CommandType.CONTINUE_RUN: ContinueRunHandler(pubsub=ps, container=ct), + CommandType.COWORK_CONTINUE_RUN: CoworkContinueRunHandler(pubsub=ps, container=ct), CommandType.ENHANCE_PROMPT: EnhancePromptHandler(pubsub=ps, container=ct), CommandType.PUBLISH_PROJECT: PublishProjectHandler(pubsub=ps, container=ct), CommandType.PUBLISH_CLOUD_RUN: CloudRunPublishHandler(pubsub=ps, container=ct), diff --git a/src/ii_agent/realtime/schemas.py b/src/ii_agent/realtime/schemas.py index e0a2a4b5a..253d34643 100644 --- a/src/ii_agent/realtime/schemas.py +++ b/src/ii_agent/realtime/schemas.py @@ -25,6 +25,7 @@ class CommandType(StrEnum): INIT_AGENT = "init_agent" QUERY = "query" + COWORK_QUERY = "cowork_query" PLAN = "plan" WORKSPACE_INFO = "workspace_info" AWAKE_SANDBOX = "awake_sandbox" @@ -32,6 +33,7 @@ class CommandType(StrEnum): PING = "ping" CANCEL = "cancel" CONTINUE_RUN = "continue_run" + COWORK_CONTINUE_RUN = "cowork_continue_run" ENHANCE_PROMPT = "enhance_prompt" PUBLISH_PROJECT = "publish" PUBLISH_CLOUD_RUN = "publish_cloud_run" @@ -254,6 +256,27 @@ class ContinueRunContent(BaseModel): user_input: dict[str, str] = {} +class CoworkQueryCommandContent(BaseCommandQuery): + """Payload for the ``cowork_query`` command.""" + + command: Literal[CommandType.COWORK_QUERY] = CommandType.COWORK_QUERY + system_prompt: str | None = None + tool_names: list[str] | None = None + skill_names: list[str] | None = None + agent_config: dict[str, Any] | None = None + desktop_capabilities: dict[str, Any] | None = None + + +class CoworkContinueRunContent(BaseModel): + """Payload for cowork_continue_run command.""" + + command: Literal[CommandType.COWORK_CONTINUE_RUN] = CommandType.COWORK_CONTINUE_RUN + run_id: str + confirmed: bool + user_input: dict[str, str] = {} + external_tool_results: list[dict[str, Any]] | None = None + + # --------------------------------------------------------------------------- # Publish / deploy content models # --------------------------------------------------------------------------- @@ -425,11 +448,13 @@ class SlideDeckSyncStateContent(BaseModel): CommandContent = Annotated[ Union[ QueryCommandContent, + CoworkQueryCommandContent, PlanCommandContent, InitAgentContent, EnhancePromptContent, StartForkContent, ContinueRunContent, + CoworkContinueRunContent, PublishProjectContent, CloudRunPublishContent, SaveEnvContent, diff --git a/src/ii_agent/sessions/types.py b/src/ii_agent/sessions/types.py index dabd03280..844b62e06 100644 --- a/src/ii_agent/sessions/types.py +++ b/src/ii_agent/sessions/types.py @@ -16,3 +16,4 @@ class AppKind(StrEnum): AGENT = "agent" CHAT = "chat" + COWORK = "cowork" From 4db3880b3b162e35eab9d9909cd946c7d616afec Mon Sep 17 00:00:00 2001 From: namtranii Date: Fri, 10 Apr 2026 16:27:28 +0700 Subject: [PATCH 03/12] fix: chat message adapter and cowork chat functionality --- .../agent/chat-message-adapter-contract.ts | 49 ++++ .../src/components/agent/chat-message.tsx | 2 +- .../agent/use-chat-message-adapter-state.ts | 63 +++++ .../cowork/chat/cowork-chat-box.tsx | 8 + .../chat/use-cowork-chatmessage-adapter.ts | 181 +++++++++++---- .../src/components/cowork/cowork-page.tsx | 218 ++++++++++++++++-- 6 files changed, 459 insertions(+), 62 deletions(-) create mode 100644 frontend/src/components/agent/chat-message-adapter-contract.ts create mode 100644 frontend/src/components/agent/use-chat-message-adapter-state.ts diff --git a/frontend/src/components/agent/chat-message-adapter-contract.ts b/frontend/src/components/agent/chat-message-adapter-contract.ts new file mode 100644 index 000000000..383c8057b --- /dev/null +++ b/frontend/src/components/agent/chat-message-adapter-contract.ts @@ -0,0 +1,49 @@ +import type { + ActionStep, + AgentContext, + Message, + RunStatus +} from '@/typings/agent' + +export interface ChatMessageAdapterState { + messages: Message[] + runStatus: RunStatus | string | null + isLoading: boolean + workspaceInfo?: string + resetEditingOnKey?: string | null + clearCurrentQuestionOnCleanup?: boolean +} + +export interface ChatMessageAdapterHandlers { + handleQuestionSubmit: (question: string) => void + handleCancel: () => void + handleClickAction: ( + data: ActionStep | undefined, + showTabOnly?: boolean + ) => void + connectWebSocket: () => void +} + +export const CHAT_MESSAGE_MINIMAL_EVENT_CONTRACT = [ + 'session.user_message', + 'agent.reasoning.delta', + 'agent.reasoning', + 'agent.tool.call', + 'agent.tool.result', + 'agent.response.delta', + 'agent.response', + 'agent.sub_agent.complete', + 'run_status' +] as const + +export type ChatMessageMinimalEvent = + (typeof CHAT_MESSAGE_MINIMAL_EVENT_CONTRACT)[number] + +export type ChatMessageAdapterOutput = Pick< + ChatMessageAdapterState, + 'messages' | 'runStatus' | 'isLoading' +> + +export type ChatMessageAdapterMessage = Message +export type ChatMessageAdapterAction = ActionStep +export type ChatMessageAdapterAgentContext = AgentContext diff --git a/frontend/src/components/agent/chat-message.tsx b/frontend/src/components/agent/chat-message.tsx index 872d8ec19..0faeefb08 100644 --- a/frontend/src/components/agent/chat-message.tsx +++ b/frontend/src/components/agent/chat-message.tsx @@ -138,7 +138,7 @@ function debounce unknown>( return debounced } -interface ChatMessageProps { +export interface ChatMessageProps { isReplayMode: boolean messagesEndRef: React.RefObject handleClickAction: ( diff --git a/frontend/src/components/agent/use-chat-message-adapter-state.ts b/frontend/src/components/agent/use-chat-message-adapter-state.ts new file mode 100644 index 000000000..39d732d11 --- /dev/null +++ b/frontend/src/components/agent/use-chat-message-adapter-state.ts @@ -0,0 +1,63 @@ +import { useEffect } from 'react' + +import { + setCurrentQuestion, + setEditingMessage, + setLoading, + setMessages, + setRunStatus, + setWorkspaceInfo, + useAppDispatch +} from '@/state' +import type { ChatMessageAdapterState } from './chat-message-adapter-contract' + +export const useChatMessageAdapterState = ({ + messages, + runStatus, + isLoading, + workspaceInfo, + resetEditingOnKey, + clearCurrentQuestionOnCleanup = true +}: ChatMessageAdapterState) => { + const dispatch = useAppDispatch() + + useEffect(() => { + dispatch(setMessages(messages)) + }, [dispatch, messages]) + + useEffect(() => { + dispatch(setRunStatus(runStatus)) + }, [dispatch, runStatus]) + + useEffect(() => { + dispatch(setLoading(isLoading)) + }, [dispatch, isLoading]) + + useEffect(() => { + if (workspaceInfo === undefined) { + return + } + + dispatch(setWorkspaceInfo(workspaceInfo)) + }, [dispatch, workspaceInfo]) + + useEffect(() => { + if (resetEditingOnKey === undefined) { + return + } + + dispatch(setEditingMessage(undefined)) + }, [dispatch, resetEditingOnKey]) + + useEffect(() => { + return () => { + dispatch(setMessages([])) + dispatch(setRunStatus(null)) + dispatch(setLoading(false)) + if (clearCurrentQuestionOnCleanup) { + dispatch(setCurrentQuestion('')) + } + dispatch(setEditingMessage(undefined)) + } + }, [clearCurrentQuestionOnCleanup, dispatch]) +} diff --git a/frontend/src/components/cowork/chat/cowork-chat-box.tsx b/frontend/src/components/cowork/chat/cowork-chat-box.tsx index 7871a752e..d387de081 100644 --- a/frontend/src/components/cowork/chat/cowork-chat-box.tsx +++ b/frontend/src/components/cowork/chat/cowork-chat-box.tsx @@ -40,6 +40,8 @@ const formatFileSize = (size: number) => { return `${size} B` } +const COWORK_HIDE_UPLOAD_SCOPE_CLASS = 'cowork-chat-box--hide-upload' + const CoworkChatBox = ({ className = '', isVisible = true, @@ -81,10 +83,16 @@ const CoworkChatBox = ({
+
diff --git a/frontend/src/components/cowork/cowork.constants.ts b/frontend/src/components/cowork/cowork.constants.ts index 2946f9dcc..2530fb766 100644 --- a/frontend/src/components/cowork/cowork.constants.ts +++ b/frontend/src/components/cowork/cowork.constants.ts @@ -3,7 +3,7 @@ export const COWORK_HOME_LABEL = 'Homepage' export const COWORK_MODES = [ { id: 'organize-file-folder', - label: 'Organize file/folder' + label: 'Intelligent Folder' } ] as const diff --git a/frontend/src/components/cowork/modes/organize-file-folder.tsx b/frontend/src/components/cowork/modes/organize-file-folder.tsx index 21b047eb6..cf1372619 100644 --- a/frontend/src/components/cowork/modes/organize-file-folder.tsx +++ b/frontend/src/components/cowork/modes/organize-file-folder.tsx @@ -346,10 +346,10 @@ const OrganizeFileFolderMode = ({ )} >

- Organize file/folder + Intelligent Folder

- Start a new organization process + Start a new Intelligent Folder session

{/*

Enter a local path, browse with the native picker, or diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx b/frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx index 730b8026f..fef776b1d 100644 --- a/frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx +++ b/frontend/src/components/cowork/organize-file-folder/cowork-organize-build.tsx @@ -38,7 +38,7 @@ const CoworkOrganizeBuild = ({ buildState.currentAction?.data.tool_display_name || buildState.currentAction?.data.tool_name || (session?.run_status === 'completed' - ? 'Organize run completed' + ? 'Intelligent Folder run completed' : isAwaitingNextAction ? 'Generating' : 'Cowork build') @@ -51,7 +51,7 @@ const CoworkOrganizeBuild = ({ state={buildState} renderers={organizeBuildRenderers} renderUnsupportedAction - unsupportedMessage="This organize event does not have a dedicated build renderer yet." + unsupportedMessage="This Intelligent Folder event does not have a dedicated build renderer yet." emptyTitle={ isAwaitingNextAction ? 'Generating' @@ -68,7 +68,7 @@ const CoworkOrganizeBuild = ({ onJumpToLatest={buildState.jumpToLatest} /> } - footerText="Browse organize events one step at a time." + footerText="Browse Intelligent Folder events one step at a time." /> ) } From ac22b2d92767f248f9026babe59431c0da99d412 Mon Sep 17 00:00:00 2001 From: namtranii Date: Sat, 11 Apr 2026 13:58:17 +0700 Subject: [PATCH 05/12] chore: change to Intelligent Folder mode --- .../{organize.rs => intelligent_folder.rs} | 84 ++++----- .../src-tauri/src/cowork/agent_presets/mod.rs | 14 +- .../src/cowork/agent_remote/mapper.rs | 4 +- .../src/cowork/agent_remote/service.rs | 10 +- frontend/src-tauri/src/cowork/chat.rs | 4 +- .../src-tauri/src/cowork/chat_commands.rs | 2 +- .../capabilities/mod.rs | 0 .../chat_prompt.rs | 28 +-- .../file_tree.rs | 0 .../{organize => intelligent_folder}/mod.rs | 0 .../sessions.rs | 106 +++++------ frontend/src-tauri/src/cowork/mod.rs | 2 +- .../src-tauri/src/cowork/session_gateway.rs | 50 ++--- frontend/src-tauri/src/main.rs | 14 +- .../cowork/chat/cowork-chat-box.tsx | 4 +- .../src/components/cowork/cowork-main.tsx | 60 +++--- .../src/components/cowork/cowork-page.tsx | 176 +++++++++--------- .../src/components/cowork/cowork-tabs.tsx | 2 +- .../src/components/cowork/cowork.constants.ts | 2 +- .../cowork-folder-build.tsx} | 12 +- .../cowork-folder-result.tsx} | 16 +- .../cowork-folder-source.tsx} | 16 +- .../cowork-folder-steps.tsx} | 16 +- .../cowork-folder-tree-icons.tsx} | 0 .../cowork-folder-tree-view.tsx} | 26 +-- .../folder-tree-utils.ts} | 2 +- ...file-folder.tsx => intelligent-folder.tsx} | 82 ++++---- frontend/src/services/cowork.service.ts | 36 ++-- frontend/src/typings/cowork.ts | 14 +- 29 files changed, 391 insertions(+), 391 deletions(-) rename frontend/src-tauri/src/cowork/agent_presets/{organize.rs => intelligent_folder.rs} (65%) rename frontend/src-tauri/src/cowork/{organize => intelligent_folder}/capabilities/mod.rs (100%) rename frontend/src-tauri/src/cowork/{organize => intelligent_folder}/chat_prompt.rs (78%) rename frontend/src-tauri/src/cowork/{organize => intelligent_folder}/file_tree.rs (100%) rename frontend/src-tauri/src/cowork/{organize => intelligent_folder}/mod.rs (100%) rename frontend/src-tauri/src/cowork/{organize => intelligent_folder}/sessions.rs (75%) rename frontend/src/components/cowork/{organize-file-folder/cowork-organize-build.tsx => intelligent-folder/cowork-folder-build.tsx} (89%) rename frontend/src/components/cowork/{organize-file-folder/cowork-organize-result.tsx => intelligent-folder/cowork-folder-result.tsx} (75%) rename frontend/src/components/cowork/{organize-file-folder/cowork-organize-source.tsx => intelligent-folder/cowork-folder-source.tsx} (75%) rename frontend/src/components/cowork/{organize-file-folder/cowork-organize-steps.tsx => intelligent-folder/cowork-folder-steps.tsx} (89%) rename frontend/src/components/cowork/{organize-file-folder/cowork-organize-tree-icons.tsx => intelligent-folder/cowork-folder-tree-icons.tsx} (100%) rename frontend/src/components/cowork/{organize-file-folder/cowork-organize-tree-view.tsx => intelligent-folder/cowork-folder-tree-view.tsx} (95%) rename frontend/src/components/cowork/{organize-file-folder/organize-tree-utils.ts => intelligent-folder/folder-tree-utils.ts} (63%) rename frontend/src/components/cowork/modes/{organize-file-folder.tsx => intelligent-folder.tsx} (89%) diff --git a/frontend/src-tauri/src/cowork/agent_presets/organize.rs b/frontend/src-tauri/src/cowork/agent_presets/intelligent_folder.rs similarity index 65% rename from frontend/src-tauri/src/cowork/agent_presets/organize.rs rename to frontend/src-tauri/src/cowork/agent_presets/intelligent_folder.rs index 2dbf16615..f7967cc4e 100644 --- a/frontend/src-tauri/src/cowork/agent_presets/organize.rs +++ b/frontend/src-tauri/src/cowork/agent_presets/intelligent_folder.rs @@ -4,76 +4,76 @@ use super::DesktopRuntimePreset; use crate::cowork::chat::CoworkAgentOverrides; use crate::cowork::desktop_skills::DesktopSkill; use crate::cowork::desktop_tools::{DesktopExecutionScope, DesktopTool}; -use crate::cowork::organize::capabilities; -use crate::cowork::organize::sessions; +use crate::cowork::intelligent_folder::capabilities; +use crate::cowork::intelligent_folder::sessions; use crate::cowork::session_gateway::{self, LocalCoworkSession}; use std::fs; use tauri::AppHandle; -pub fn organize_builtin_agent_overrides(prompt_context: Option<&str>) -> CoworkAgentOverrides { +pub fn folder_builtin_agent_overrides(prompt_context: Option<&str>) -> CoworkAgentOverrides { CoworkAgentOverrides { - system_prompt: Some(build_organize_system_prompt(prompt_context)), - tool_names: Some(build_organize_tool_names()), + system_prompt: Some(build_folder_system_prompt(prompt_context)), + tool_names: Some(build_folder_tool_names()), skill_names: None, - runtime_options: Some(shared::default_runtime_options("organize_cowork_agent")), + runtime_options: Some(shared::default_runtime_options("folder_cowork_agent")), } } -pub fn build_organize_desktop_capabilities() -> DesktopCapabilities { +pub fn build_folder_desktop_capabilities() -> DesktopCapabilities { DesktopCapabilities { tools: merge_tool_capabilities( shared::desktop_built_tools(), capabilities::desktop_tool_capabilities(), ), - skills: build_organize_desktop_skills(), + skills: build_folder_desktop_skills(), } } -pub fn build_organize_desktop_runtime() -> DesktopRuntimePreset { +pub fn build_folder_desktop_runtime() -> DesktopRuntimePreset { DesktopRuntimePreset { - tools: build_organize_runtime_tools(), - skills: build_organize_runtime_skills(), - load_execution_scope: load_organize_execution_scope, - refresh_scope: Some(refresh_organize_execution_scope), + tools: build_folder_runtime_tools(), + skills: build_folder_runtime_skills(), + load_execution_scope: load_folder_execution_scope, + refresh_scope: Some(refresh_folder_execution_scope), } } -fn build_organize_desktop_tools() -> Vec { - build_organize_desktop_capabilities().tools +fn build_folder_desktop_tools() -> Vec { + build_folder_desktop_capabilities().tools } -fn build_organize_desktop_skills() -> Vec { +fn build_folder_desktop_skills() -> Vec { merge_skill_capabilities( shared::desktop_built_skills(), capabilities::desktop_skill_capabilities(), ) } -fn build_organize_tool_names() -> Vec { - build_organize_desktop_tools() +fn build_folder_tool_names() -> Vec { + build_folder_desktop_tools() .into_iter() .map(|tool| tool.name) .collect() } -fn build_organize_runtime_tools() -> Vec { +fn build_folder_runtime_tools() -> Vec { merge_runtime_tools( shared::desktop_runtime_tools(), capabilities::desktop_tools(), ) } -fn build_organize_runtime_skills() -> Vec { +fn build_folder_runtime_skills() -> Vec { merge_runtime_skills( shared::desktop_runtime_skills(), capabilities::desktop_runtime_skills(), ) } -fn build_organize_system_prompt(prompt_context: Option<&str>) -> String { - let mut prompt = "You are the Cowork organize agent inside II Agent desktop.\n\ +fn build_folder_system_prompt(prompt_context: Option<&str>) -> String { + let mut prompt = "You are the Cowork folder agent inside II Agent desktop.\n\ Focus on analyzing and reorganizing the user's local file and folder structure with careful, tool-assisted reasoning.\n\ -Treat this mode as organize-file-folder scope for a desktop-selected folder.\n\ +Treat this mode as intelligent-folder scope for a desktop-selected folder.\n\ Use only the built desktop tools provided for local file inspection and edits.\n\ Do not assume backend-only, browser, connector, repository, or web tools exist in this mode." .to_string(); @@ -81,8 +81,8 @@ Do not assume backend-only, browser, connector, repository, or web tools exist i if let Some(source_root) = extract_prompt_value(prompt_context, "Input folder path:") .or_else(|| extract_prompt_value(prompt_context, "Source root:")) { - prompt.push_str("\n\n[Organize scope]"); - prompt.push_str("\nMode scope: organize-file-folder"); + prompt.push_str("\n\n[Folder scope]"); + prompt.push_str("\nMode scope: intelligent-folder"); prompt.push_str("\nInput folder path: "); prompt.push_str(&source_root); @@ -165,23 +165,23 @@ fn merge_runtime_skills( merged } -fn load_organize_execution_scope( +fn load_folder_execution_scope( app: &AppHandle, local_session_id: &str, ) -> Result { let local_session = session_gateway::load_local_session( app, - crate::cowork::chat::CoworkChatScope::OrganizeFileFolder, + crate::cowork::chat::CoworkChatScope::IntelligentFolder, local_session_id, )?; - let LocalCoworkSession::Organize(detail) = local_session else { - return Err("Desktop organize tool execution requires an organize session".to_string()); + let LocalCoworkSession::Folder(detail) = local_session else { + return Err("Desktop folder tool execution requires an folder session".to_string()); }; - let root_path = detail.organize_tree_pair.source_root.clone(); + let root_path = detail.folder_tree_pair.source_root.clone(); let canonical_root = fs::canonicalize(&root_path).map_err(|error| { format!( - "Failed to resolve organize source_root {}: {}", + "Failed to resolve folder source_root {}: {}", root_path, error ) })?; @@ -189,21 +189,21 @@ fn load_organize_execution_scope( Ok(DesktopExecutionScope::new(canonical_root)) } -fn refresh_organize_execution_scope( +fn refresh_folder_execution_scope( app: &AppHandle, local_session_id: &str, _execution_scope: &DesktopExecutionScope, ) -> Result<(), String> { let local_session = session_gateway::load_local_session( app, - crate::cowork::chat::CoworkChatScope::OrganizeFileFolder, + crate::cowork::chat::CoworkChatScope::IntelligentFolder, local_session_id, )?; - let LocalCoworkSession::Organize(mut detail) = local_session else { + let LocalCoworkSession::Folder(mut detail) = local_session else { return Ok(()); }; sessions::sync_result_tree_from_disk(&mut detail)?; - session_gateway::persist_local_session(app, LocalCoworkSession::Organize(detail))?; + session_gateway::persist_local_session(app, LocalCoworkSession::Folder(detail))?; Ok(()) } @@ -212,21 +212,21 @@ mod tests { use super::*; #[test] - fn build_organize_system_prompt_embeds_local_scope() { - let prompt = build_organize_system_prompt(Some( - "[Local organize scope]\nInput folder path: C:/Users/demo/Documents\nResult root: C:/Users/demo/Documents", + fn build_folder_system_prompt_embeds_local_scope() { + let prompt = build_folder_system_prompt(Some( + "[Local folder scope]\nInput folder path: C:/Users/demo/Documents\nResult root: C:/Users/demo/Documents", )); - assert!(prompt.contains("Mode scope: organize-file-folder")); + assert!(prompt.contains("Mode scope: intelligent-folder")); assert!(prompt.contains("Input folder path: C:/Users/demo/Documents")); assert!(prompt.contains("Result folder path: C:/Users/demo/Documents")); assert!(prompt.contains("Use only the built desktop tools")); } #[test] - fn build_organize_desktop_capabilities_match_override_tool_names() { - let overrides = organize_builtin_agent_overrides(None); - let capabilities = build_organize_desktop_capabilities(); + fn build_folder_desktop_capabilities_match_override_tool_names() { + let overrides = folder_builtin_agent_overrides(None); + let capabilities = build_folder_desktop_capabilities(); let capability_tool_names = capabilities .tools .into_iter() diff --git a/frontend/src-tauri/src/cowork/agent_presets/mod.rs b/frontend/src-tauri/src/cowork/agent_presets/mod.rs index c2ec29de7..52d8f886a 100644 --- a/frontend/src-tauri/src/cowork/agent_presets/mod.rs +++ b/frontend/src-tauri/src/cowork/agent_presets/mod.rs @@ -1,5 +1,5 @@ pub mod homepage; -pub mod organize; +pub mod intelligent_folder; pub mod shared; use crate::cowork::chat::{CoworkAgentOverrides, CoworkChatScope, CoworkChatToolSettings}; @@ -29,24 +29,24 @@ pub fn resolve_agent_overrides( ) -> CoworkAgentOverrides { let builtin = match scope { CoworkChatScope::Homepage => homepage::homepage_builtin_agent_overrides(prompt_context), - CoworkChatScope::OrganizeFileFolder => { - organize::organize_builtin_agent_overrides(prompt_context) + CoworkChatScope::IntelligentFolder => { + intelligent_folder::folder_builtin_agent_overrides(prompt_context) } }; let merged = shared::merge_agent_overrides(builtin, runtime_overrides); match scope { CoworkChatScope::Homepage => shared::apply_tool_settings(merged, tools), - CoworkChatScope::OrganizeFileFolder => shared::lock_tool_names_to_desktop(merged), + CoworkChatScope::IntelligentFolder => shared::lock_tool_names_to_desktop(merged), } } pub fn resolve_desktop_preset(scope: CoworkChatScope) -> Option { match scope { CoworkChatScope::Homepage => None, - CoworkChatScope::OrganizeFileFolder => Some(ResolvedDesktopPreset { - capabilities: organize::build_organize_desktop_capabilities(), - runtime: organize::build_organize_desktop_runtime(), + CoworkChatScope::IntelligentFolder => Some(ResolvedDesktopPreset { + capabilities: intelligent_folder::build_folder_desktop_capabilities(), + runtime: intelligent_folder::build_folder_desktop_runtime(), }), } } diff --git a/frontend/src-tauri/src/cowork/agent_remote/mapper.rs b/frontend/src-tauri/src/cowork/agent_remote/mapper.rs index 6075aed07..8df5bc8a8 100644 --- a/frontend/src-tauri/src/cowork/agent_remote/mapper.rs +++ b/frontend/src-tauri/src/cowork/agent_remote/mapper.rs @@ -290,7 +290,7 @@ mod tests { id: "evt-1".to_string(), event_type: "session.user_message".to_string(), content: json!({ - "text": "Please organize these files" + "text": "Please folder these files" }), created_at: Some("2026-04-06T00:00:00Z".to_string()), }], @@ -300,7 +300,7 @@ mod tests { assert_eq!(snapshot.messages.len(), 1); assert_eq!(snapshot.messages[0].role, crate::cowork::chat::CoworkChatMessageRole::User); - assert_eq!(snapshot.messages[0].content, "Please organize these files"); + assert_eq!(snapshot.messages[0].content, "Please folder these files"); } #[test] diff --git a/frontend/src-tauri/src/cowork/agent_remote/service.rs b/frontend/src-tauri/src/cowork/agent_remote/service.rs index 6c29e77c3..d4baa723c 100644 --- a/frontend/src-tauri/src/cowork/agent_remote/service.rs +++ b/frontend/src-tauri/src/cowork/agent_remote/service.rs @@ -217,7 +217,7 @@ pub async fn send_remote_chat_message( local_session = reload_latest_local_session(&app, &local_session); session_gateway::apply_runtime_session_snapshot(&mut local_session, runtime_snapshot); - session_gateway::sync_organize_result_tree(&mut local_session)?; + session_gateway::sync_folder_result_tree(&mut local_session)?; local_session = session_gateway::persist_local_session(&app, local_session)?; session_gateway::emit_local_terminal_state(&app, &local_session, false); @@ -259,15 +259,15 @@ fn persist_runtime_error_with_latest( fn build_remote_runtime_metadata(session: &session_gateway::LocalCoworkSession) -> Option { match session { session_gateway::LocalCoworkSession::Homepage(_) => None, - session_gateway::LocalCoworkSession::Organize(detail) => Some(json!({ + session_gateway::LocalCoworkSession::Folder(detail) => Some(json!({ "cowork": { - "scope": "organize-file-folder", + "scope": "intelligent-folder", "execution_context": "desktop", "tool_runtime": "desktop_builtin", "tool_binding_mode": "desktop_only", "local_session_id": detail.base.id.clone(), - "source_root": detail.organize_tree_pair.source_root.clone(), - "result_root": detail.organize_tree_pair.result_root.clone(), + "source_root": detail.folder_tree_pair.source_root.clone(), + "result_root": detail.folder_tree_pair.result_root.clone(), } })), } diff --git a/frontend/src-tauri/src/cowork/chat.rs b/frontend/src-tauri/src/cowork/chat.rs index 5d0256af1..ffd334e47 100644 --- a/frontend/src-tauri/src/cowork/chat.rs +++ b/frontend/src-tauri/src/cowork/chat.rs @@ -8,8 +8,8 @@ pub const COWORK_STREAM_EVENT_NAME: &str = "cowork://stream"; pub enum CoworkChatScope { #[serde(rename = "homepage")] Homepage, - #[serde(rename = "organize-file-folder", alias = "organize_file_folder")] - OrganizeFileFolder, + #[serde(rename = "intelligent-folder", alias = "intelligent_folder")] + IntelligentFolder, } #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] diff --git a/frontend/src-tauri/src/cowork/chat_commands.rs b/frontend/src-tauri/src/cowork/chat_commands.rs index 956b0acf5..9dc0725eb 100644 --- a/frontend/src-tauri/src/cowork/chat_commands.rs +++ b/frontend/src-tauri/src/cowork/chat_commands.rs @@ -22,7 +22,7 @@ pub fn stop_cowork_chat_session( } } - session_gateway::sync_organize_result_tree(&mut local_session)?; + session_gateway::sync_folder_result_tree(&mut local_session)?; local_session = session_gateway::persist_local_session(&app, local_session)?; session_gateway::emit_local_terminal_state(&app, &local_session, false); diff --git a/frontend/src-tauri/src/cowork/organize/capabilities/mod.rs b/frontend/src-tauri/src/cowork/intelligent_folder/capabilities/mod.rs similarity index 100% rename from frontend/src-tauri/src/cowork/organize/capabilities/mod.rs rename to frontend/src-tauri/src/cowork/intelligent_folder/capabilities/mod.rs diff --git a/frontend/src-tauri/src/cowork/organize/chat_prompt.rs b/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs similarity index 78% rename from frontend/src-tauri/src/cowork/organize/chat_prompt.rs rename to frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs index d45394f40..07c37318c 100644 --- a/frontend/src-tauri/src/cowork/organize/chat_prompt.rs +++ b/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs @@ -1,15 +1,15 @@ -use crate::cowork::organize::sessions::CoworkChatSessionDetail; +use crate::cowork::intelligent_folder::sessions::CoworkChatSessionDetail; -pub fn build_organize_prompt_context(session: &CoworkChatSessionDetail) -> String { +pub fn build_folder_prompt_context(session: &CoworkChatSessionDetail) -> String { format!( - "You are assisting in II Cowork organize-file-folder mode.\n\ + "You are assisting in II Cowork intelligent-folder mode.\n\ You are working on the user's real local desktop folder.\n\ -Your job is to inspect, understand, clean up, and reorganize files inside that folder based on the user's request.\n\n\ +Your job is to inspect, understand, clean up, and refolder files inside that folder based on the user's request.\n\n\ [Operating rules]\n\ - Work only inside the selected local folder.\n\ - Understand the current structure before changing it.\n\ - Always read relevant files before modifying them when the decision depends on file content, meaning, or purpose.\n\ -- Do not reorganize semantic content based only on filenames when content inspection is needed.\n\ +- Do not refolder semantic content based only on filenames when content inspection is needed.\n\ - For purely structural tasks such as grouping by extension, renaming obvious folders, or moving generated files, you may act from directory structure alone when that is sufficient.\n\ - Keep changes scoped, intentional, and easy to explain.\n\ - Preserve user content unless the request clearly asks for renaming, regrouping, cleanup, or rewrites.\n\ @@ -32,17 +32,17 @@ Your job is to inspect, understand, clean up, and reorganize files inside that f - `apply_patch` - Apply structured multi-file edits\n\ - `Bash` - Execute shell commands inside the selected folder\n\ - `TodoWrite` - Keep a short task checklist during the run\n\n\ -[Local organize scope and context]\n\ -Mode scope: organize-file-folder\n\ +[Local folder scope and context]\n\ +Mode scope: intelligent-folder\n\ Input folder path: {}\n", - session.organize_tree_pair.source_root, + session.folder_tree_pair.source_root, ) } #[cfg(test)] mod tests { use super::*; - use crate::cowork::organize::file_tree::{FileTreeNode, FileTreeNodeKind}; + use crate::cowork::intelligent_folder::file_tree::{FileTreeNode, FileTreeNodeKind}; fn sample_folder(name: &str) -> FileTreeNode { FileTreeNode { @@ -58,8 +58,8 @@ mod tests { fn sample_session() -> CoworkChatSessionDetail { CoworkChatSessionDetail { base: crate::cowork::chat::CoworkChatSessionDetail { - id: "cowork-organize-1".to_string(), - scope: crate::cowork::chat::CoworkChatScope::OrganizeFileFolder, + id: "cowork-folder-1".to_string(), + scope: crate::cowork::chat::CoworkChatScope::IntelligentFolder, title: "demo".to_string(), preview: "demo".to_string(), updated_at: "2026-04-04T00:00:00.000Z".to_string(), @@ -71,7 +71,7 @@ mod tests { files: Vec::new(), run_status: crate::cowork::chat::CoworkChatRunStatus::Idle, }, - organize_tree_pair: crate::cowork::organize::sessions::CoworkOrganizeTreePair { + folder_tree_pair: crate::cowork::intelligent_folder::sessions::CoworkFolderTreePair { source_root: "C:/demo".to_string(), result_root: "C:/demo".to_string(), source_tree: sample_folder("demo"), @@ -81,8 +81,8 @@ mod tests { } #[test] - fn build_organize_prompt_context_includes_scope_and_tool_guidance() { - let prompt = build_organize_prompt_context(&sample_session()); + fn build_folder_prompt_context_includes_scope_and_tool_guidance() { + let prompt = build_folder_prompt_context(&sample_session()); assert!(prompt.contains("[Operating rules]")); assert!(prompt.contains("[Recommended approach]")); diff --git a/frontend/src-tauri/src/cowork/organize/file_tree.rs b/frontend/src-tauri/src/cowork/intelligent_folder/file_tree.rs similarity index 100% rename from frontend/src-tauri/src/cowork/organize/file_tree.rs rename to frontend/src-tauri/src/cowork/intelligent_folder/file_tree.rs diff --git a/frontend/src-tauri/src/cowork/organize/mod.rs b/frontend/src-tauri/src/cowork/intelligent_folder/mod.rs similarity index 100% rename from frontend/src-tauri/src/cowork/organize/mod.rs rename to frontend/src-tauri/src/cowork/intelligent_folder/mod.rs diff --git a/frontend/src-tauri/src/cowork/organize/sessions.rs b/frontend/src-tauri/src/cowork/intelligent_folder/sessions.rs similarity index 75% rename from frontend/src-tauri/src/cowork/organize/sessions.rs rename to frontend/src-tauri/src/cowork/intelligent_folder/sessions.rs index 8e55e6359..716fe3905 100644 --- a/frontend/src-tauri/src/cowork/organize/sessions.rs +++ b/frontend/src-tauri/src/cowork/intelligent_folder/sessions.rs @@ -1,7 +1,7 @@ pub use crate::cowork::chat::{CoworkChatRunStatus, CoworkChatScope, CoworkChatSessionSummary}; use crate::cowork::chat::CoworkChatSessionDetail as BaseCoworkChatSessionDetail; -use crate::cowork::organize::file_tree::{self, FileTreeNode, FileTreeNodeKind}; +use crate::cowork::intelligent_folder::file_tree::{self, FileTreeNode, FileTreeNodeKind}; use chrono::{SecondsFormat, Utc}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -13,11 +13,11 @@ use std::{ use tauri::{AppHandle, Manager}; const STORE_DIR_NAME: &str = "cowork"; -const SESSION_STORE_DIR_NAME: &str = "organize-sessions"; -const LEGACY_STORE_FILE_NAME: &str = "organize-sessions.json"; +const SESSION_STORE_DIR_NAME: &str = "folder-sessions"; +const LEGACY_STORE_FILE_NAME: &str = "folder-sessions.json"; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct CoworkOrganizeTreePair { +pub struct CoworkFolderTreePair { pub source_root: String, pub result_root: String, pub source_tree: FileTreeNode, @@ -28,7 +28,7 @@ pub struct CoworkOrganizeTreePair { pub struct CoworkChatSessionDetail { #[serde(flatten)] pub base: BaseCoworkChatSessionDetail, - pub organize_tree_pair: CoworkOrganizeTreePair, + pub folder_tree_pair: CoworkFolderTreePair, } impl Deref for CoworkChatSessionDetail { @@ -46,12 +46,12 @@ impl DerefMut for CoworkChatSessionDetail { } #[derive(Debug, Serialize, Deserialize, Default)] -struct OrganizeSessionStore { +struct FolderSessionStore { sessions: Vec, } #[tauri::command] -pub fn list_organize_sessions(app: AppHandle) -> Result, String> { +pub fn list_folder_sessions(app: AppHandle) -> Result, String> { let mut sessions = load_sessions(&app)?; sort_sessions(&mut sessions); @@ -59,7 +59,7 @@ pub fn list_organize_sessions(app: AppHandle) -> Result Result { @@ -67,9 +67,9 @@ pub fn get_organize_session( } #[tauri::command] -pub fn create_organize_session( +pub fn create_folder_session( app: AppHandle, - tree_pair: CoworkOrganizeTreePair, + tree_pair: CoworkFolderTreePair, ) -> Result { validate_tree_pair(&tree_pair)?; @@ -77,7 +77,7 @@ pub fn create_organize_session( let session = CoworkChatSessionDetail { base: BaseCoworkChatSessionDetail { id: generate_session_id(), - scope: CoworkChatScope::OrganizeFileFolder, + scope: CoworkChatScope::IntelligentFolder, title: build_session_title(&tree_pair.source_root), preview: build_session_preview(&tree_pair.source_root), updated_at: now, @@ -89,7 +89,7 @@ pub fn create_organize_session( files: Vec::new(), run_status: CoworkChatRunStatus::Idle, }, - organize_tree_pair: tree_pair, + folder_tree_pair: tree_pair, }; write_session(&app, &session)?; @@ -98,7 +98,7 @@ pub fn create_organize_session( } #[tauri::command] -pub fn update_organize_session( +pub fn update_folder_session( app: AppHandle, session: CoworkChatSessionDetail, ) -> Result { @@ -112,7 +112,7 @@ pub fn update_organize_session( } #[tauri::command] -pub fn rename_organize_session( +pub fn rename_folder_session( app: AppHandle, session_id: String, title: String, @@ -133,18 +133,18 @@ pub fn rename_organize_session( } #[tauri::command] -pub fn delete_organize_session(app: AppHandle, session_id: String) -> Result<(), String> { +pub fn delete_folder_session(app: AppHandle, session_id: String) -> Result<(), String> { delete_session(&app, &session_id) } pub fn sync_result_tree_from_disk(session: &mut CoworkChatSessionDetail) -> Result<(), String> { let latest_tree = - file_tree::read_path_tree(session.organize_tree_pair.source_root.clone(), None)?; - let source_hash = hash_tree(&session.organize_tree_pair.source_tree)?; + file_tree::read_path_tree(session.folder_tree_pair.source_root.clone(), None)?; + let source_hash = hash_tree(&session.folder_tree_pair.source_tree)?; let latest_hash = hash_tree(&latest_tree)?; - session.organize_tree_pair.result_root = session.organize_tree_pair.source_root.clone(); - session.organize_tree_pair.result_tree = if source_hash == latest_hash { + session.folder_tree_pair.result_root = session.folder_tree_pair.source_root.clone(); + session.folder_tree_pair.result_tree = if source_hash == latest_hash { None } else { Some(latest_tree) @@ -154,36 +154,36 @@ pub fn sync_result_tree_from_disk(session: &mut CoworkChatSessionDetail) -> Resu } fn validate_session(session: &CoworkChatSessionDetail) -> Result<(), String> { - if session.scope != CoworkChatScope::OrganizeFileFolder { - return Err("Only organize-file-folder sessions can be persisted locally".to_string()); + if session.scope != CoworkChatScope::IntelligentFolder { + return Err("Only intelligent-folder sessions can be persisted locally".to_string()); } - validate_tree_pair(&session.organize_tree_pair) + validate_tree_pair(&session.folder_tree_pair) } fn hash_tree(tree: &FileTreeNode) -> Result { let serialized = serde_json::to_vec(tree) - .map_err(|error| format!("Failed to serialize organize tree for hashing: {}", error))?; + .map_err(|error| format!("Failed to serialize folder tree for hashing: {}", error))?; let digest = Sha256::digest(serialized); Ok(digest.iter().map(|value| format!("{value:02x}")).collect()) } -fn validate_tree_pair(tree_pair: &CoworkOrganizeTreePair) -> Result<(), String> { +fn validate_tree_pair(tree_pair: &CoworkFolderTreePair) -> Result<(), String> { if tree_pair.source_root.trim().is_empty() { - return Err("Organize session source_root is required".to_string()); + return Err("Folder session source_root is required".to_string()); } if tree_pair.result_root.trim().is_empty() { - return Err("Organize session result_root is required".to_string()); + return Err("Folder session result_root is required".to_string()); } if tree_pair.source_tree.kind != FileTreeNodeKind::Folder { - return Err("Organize session source_tree root must be a folder".to_string()); + return Err("Folder session source_tree root must be a folder".to_string()); } if let Some(result_tree) = &tree_pair.result_tree { if result_tree.kind != FileTreeNodeKind::Folder { - return Err("Organize session result_tree root must be a folder".to_string()); + return Err("Folder session result_tree root must be a folder".to_string()); } } @@ -211,7 +211,7 @@ fn build_session_title(source_root: &str) -> String { .unwrap_or(trimmed); if folder_name.is_empty() { - "Organize session".to_string() + "Folder session".to_string() } else { folder_name.to_string() } @@ -222,7 +222,7 @@ fn build_session_preview(source_root: &str) -> String { } fn generate_session_id() -> String { - format!("cowork-organize-{}", Utc::now().timestamp_millis()) + format!("cowork-folder-{}", Utc::now().timestamp_millis()) } fn now_iso() -> String { @@ -232,7 +232,7 @@ fn now_iso() -> String { fn normalize_session(mut session: CoworkChatSessionDetail) -> CoworkChatSessionDetail { session.base.normalize_runtime_binding(); session.base.message_count = session.base.messages.len(); - session.base.preview = build_session_preview(&session.organize_tree_pair.source_root); + session.base.preview = build_session_preview(&session.folder_tree_pair.source_root); if session.base.messages.is_empty() && session.base.runtime_events.is_empty() && session.base.runtime_session_id.is_none() @@ -259,7 +259,7 @@ fn load_sessions(app: &AppHandle) -> Result, String let store_dir = session_store_dir_path(app)?; let entries = fs::read_dir(&store_dir).map_err(|error| { format!( - "Failed to read organize sessions directory {}: {}", + "Failed to read folder sessions directory {}: {}", store_dir.display(), error ) @@ -269,7 +269,7 @@ fn load_sessions(app: &AppHandle) -> Result, String for entry in entries { let entry = entry.map_err(|error| { format!( - "Failed to read an entry in organize sessions directory {}: {}", + "Failed to read an entry in folder sessions directory {}: {}", store_dir.display(), error ) @@ -291,7 +291,7 @@ fn load_session(app: &AppHandle, session_id: &str) -> Result Result<( let session_file = session_file_path(app, &session.id)?; let contents = serde_json::to_string_pretty(session).map_err(|error| { format!( - "Failed to serialize organize session {}: {}", + "Failed to serialize folder session {}: {}", session.id, error ) })?; fs::write(&session_file, contents).map_err(|error| { format!( - "Failed to write organize session file {}: {}", + "Failed to write folder session file {}: {}", session_file.display(), error ) @@ -322,12 +322,12 @@ fn delete_session(app: &AppHandle, session_id: &str) -> Result<(), String> { let session_file = session_file_path(app, session_id)?; if !session_file.exists() { - return Err(format!("Organize session not found: {}", session_id.trim())); + return Err(format!("Folder session not found: {}", session_id.trim())); } fs::remove_file(&session_file).map_err(|error| { format!( - "Failed to delete organize session file {}: {}", + "Failed to delete folder session file {}: {}", session_file.display(), error ) @@ -337,7 +337,7 @@ fn delete_session(app: &AppHandle, session_id: &str) -> Result<(), String> { fn read_session_file(path: &PathBuf) -> Result { let contents = fs::read_to_string(path).map_err(|error| { format!( - "Failed to read organize session file {}: {}", + "Failed to read folder session file {}: {}", path.display(), error ) @@ -345,14 +345,14 @@ fn read_session_file(path: &PathBuf) -> Result if contents.trim().is_empty() { return Err(format!( - "Organize session file is empty: {}", + "Folder session file is empty: {}", path.display() )); } serde_json::from_str(&contents).map_err(|error| { format!( - "Failed to parse organize session file {}: {}", + "Failed to parse folder session file {}: {}", path.display(), error ) @@ -366,12 +366,12 @@ fn migrate_legacy_store(app: &AppHandle) -> Result<(), String> { } let contents = fs::read_to_string(&legacy_store_file) - .map_err(|error| format!("Failed to read organize session store: {}", error))?; + .map_err(|error| format!("Failed to read folder session store: {}", error))?; if contents.trim().is_empty() { fs::remove_file(&legacy_store_file).map_err(|error| { format!( - "Failed to remove empty legacy organize session store {}: {}", + "Failed to remove empty legacy folder session store {}: {}", legacy_store_file.display(), error ) @@ -379,21 +379,21 @@ fn migrate_legacy_store(app: &AppHandle) -> Result<(), String> { return Ok(()); } - let legacy_store: OrganizeSessionStore = serde_json::from_str(&contents) - .map_err(|error| format!("Failed to parse organize session store: {}", error))?; + let legacy_store: FolderSessionStore = serde_json::from_str(&contents) + .map_err(|error| format!("Failed to parse folder session store: {}", error))?; for session in legacy_store.sessions { let session_file = session_file_path(app, &session.id)?; let session_contents = serde_json::to_string_pretty(&session).map_err(|error| { format!( - "Failed to serialize migrated organize session {}: {}", + "Failed to serialize migrated folder session {}: {}", session.id, error ) })?; fs::write(&session_file, session_contents).map_err(|error| { format!( - "Failed to write migrated organize session file {}: {}", + "Failed to write migrated folder session file {}: {}", session_file.display(), error ) @@ -402,7 +402,7 @@ fn migrate_legacy_store(app: &AppHandle) -> Result<(), String> { fs::remove_file(&legacy_store_file).map_err(|error| { format!( - "Failed to remove legacy organize session store {}: {}", + "Failed to remove legacy folder session store {}: {}", legacy_store_file.display(), error ) @@ -412,11 +412,11 @@ fn migrate_legacy_store(app: &AppHandle) -> Result<(), String> { fn normalize_session_id(session_id: &str) -> Result { let trimmed = session_id.trim(); if trimmed.is_empty() { - return Err("Organize session id is required".to_string()); + return Err("Folder session id is required".to_string()); } if trimmed == "." || trimmed == ".." { - return Err(format!("Invalid organize session id: {}", trimmed)); + return Err(format!("Invalid folder session id: {}", trimmed)); } if trimmed.chars().any(|character| { @@ -426,7 +426,7 @@ fn normalize_session_id(session_id: &str) -> Result { '<' | '>' | ':' | '"' | '/' | '\\' | '|' | '?' | '*' ) }) { - return Err(format!("Invalid organize session id: {}", trimmed)); + return Err(format!("Invalid folder session id: {}", trimmed)); } Ok(trimmed.to_string()) @@ -441,7 +441,7 @@ fn store_root_dir_path(app: &AppHandle) -> Result { data_dir.push(STORE_DIR_NAME); fs::create_dir_all(&data_dir) - .map_err(|error| format!("Failed to create organize session directory: {}", error))?; + .map_err(|error| format!("Failed to create folder session directory: {}", error))?; Ok(data_dir) } @@ -452,7 +452,7 @@ fn session_store_dir_path(app: &AppHandle) -> Result { fs::create_dir_all(&store_root).map_err(|error| { format!( - "Failed to create organize sessions directory {}: {}", + "Failed to create folder sessions directory {}: {}", store_root.display(), error ) diff --git a/frontend/src-tauri/src/cowork/mod.rs b/frontend/src-tauri/src/cowork/mod.rs index 2c39619a0..959954f80 100644 --- a/frontend/src-tauri/src/cowork/mod.rs +++ b/frontend/src-tauri/src/cowork/mod.rs @@ -5,7 +5,7 @@ pub mod chat_commands; pub mod desktop_skills; pub mod desktop_tools; pub mod homepage; -pub mod organize; +pub mod intelligent_folder; pub mod runtime; pub mod session_gateway; pub mod string_utils; diff --git a/frontend/src-tauri/src/cowork/session_gateway.rs b/frontend/src-tauri/src/cowork/session_gateway.rs index 6cbde365e..2d252d7ce 100644 --- a/frontend/src-tauri/src/cowork/session_gateway.rs +++ b/frontend/src-tauri/src/cowork/session_gateway.rs @@ -6,8 +6,8 @@ use crate::cowork::chat::{ CoworkChatSessionSummary, CoworkChatStatusEvent, }; use crate::cowork::homepage::chat_sessions as homepage_sessions; -use crate::cowork::organize::chat_prompt as organize_chat_prompt; -use crate::cowork::organize::sessions as organize_sessions; +use crate::cowork::intelligent_folder::chat_prompt as folder_chat_prompt; +use crate::cowork::intelligent_folder::sessions as folder_sessions; use crate::cowork::runtime::CoworkRuntimeSessionSnapshot; use crate::cowork::time_utils::{generate_message_id, now_iso}; use tauri::AppHandle; @@ -15,21 +15,21 @@ use tauri::AppHandle; #[derive(Clone)] pub enum LocalCoworkSession { Homepage(CoworkChatSessionDetail), - Organize(organize_sessions::CoworkChatSessionDetail), + Folder(folder_sessions::CoworkChatSessionDetail), } impl LocalCoworkSession { pub fn base(&self) -> &CoworkChatSessionDetail { match self { Self::Homepage(session) => session, - Self::Organize(session) => &session.base, + Self::Folder(session) => &session.base, } } pub fn base_mut(&mut self) -> &mut CoworkChatSessionDetail { match self { Self::Homepage(session) => session, - Self::Organize(session) => &mut session.base, + Self::Folder(session) => &mut session.base, } } } @@ -51,10 +51,10 @@ trait FeatureSessionStore { } struct HomepageSessionStore; -struct OrganizeSessionStore; +struct FolderSessionStore; static HOMEPAGE_SESSION_STORE: HomepageSessionStore = HomepageSessionStore; -static ORGANIZE_SESSION_STORE: OrganizeSessionStore = OrganizeSessionStore; +static FOLDER_SESSION_STORE: FolderSessionStore = FolderSessionStore; impl FeatureSessionStore for HomepageSessionStore { fn load(&self, app: &AppHandle, session_id: &str) -> Result { @@ -92,10 +92,10 @@ impl FeatureSessionStore for HomepageSessionStore { } } -impl FeatureSessionStore for OrganizeSessionStore { +impl FeatureSessionStore for FolderSessionStore { fn load(&self, app: &AppHandle, session_id: &str) -> Result { - organize_sessions::get_organize_session(app.clone(), session_id.to_string()) - .map(LocalCoworkSession::Organize) + folder_sessions::get_folder_session(app.clone(), session_id.to_string()) + .map(LocalCoworkSession::Folder) } fn load_or_create( @@ -106,9 +106,9 @@ impl FeatureSessionStore for OrganizeSessionStore { let session_id = request .session_id .as_ref() - .ok_or_else(|| "Organize cowork session_id is required".to_string())?; - let session = organize_sessions::get_organize_session(app.clone(), session_id.clone())?; - Ok((LocalCoworkSession::Organize(session), false)) + .ok_or_else(|| "Folder cowork session_id is required".to_string())?; + let session = folder_sessions::get_folder_session(app.clone(), session_id.clone())?; + Ok((LocalCoworkSession::Folder(session), false)) } fn save( @@ -116,12 +116,12 @@ impl FeatureSessionStore for OrganizeSessionStore { app: &AppHandle, session: LocalCoworkSession, ) -> Result { - let LocalCoworkSession::Organize(detail) = session else { - return Err("Organize session store received a non-organize session".to_string()); + let LocalCoworkSession::Folder(detail) = session else { + return Err("Folder session store received a non-folder session".to_string()); }; - organize_sessions::update_organize_session(app.clone(), detail) - .map(LocalCoworkSession::Organize) + folder_sessions::update_folder_session(app.clone(), detail) + .map(LocalCoworkSession::Folder) } } @@ -197,7 +197,7 @@ pub fn persist_runtime_error( base.message_count = base.messages.len(); base.run_status = CoworkChatRunStatus::Stopped; - sync_organize_result_tree(&mut session)?; + sync_folder_result_tree(&mut session)?; persist_local_session(app, session) } @@ -264,11 +264,11 @@ pub fn clear_runtime_binding(session: &mut LocalCoworkSession) { base.runtime_session_id = None; } -pub fn sync_organize_result_tree(session: &mut LocalCoworkSession) -> Result<(), String> { +pub fn sync_folder_result_tree(session: &mut LocalCoworkSession) -> Result<(), String> { match session { LocalCoworkSession::Homepage(_) => Ok(()), - LocalCoworkSession::Organize(detail) => { - organize_sessions::sync_result_tree_from_disk(detail) + LocalCoworkSession::Folder(detail) => { + folder_sessions::sync_result_tree_from_disk(detail) } } } @@ -279,8 +279,8 @@ pub fn resolve_prompt_context( ) -> Option { explicit_prompt_context.or_else(|| match session { LocalCoworkSession::Homepage(_) => None, - LocalCoworkSession::Organize(detail) => { - Some(organize_chat_prompt::build_organize_prompt_context(detail)) + LocalCoworkSession::Folder(detail) => { + Some(folder_chat_prompt::build_folder_prompt_context(detail)) } }) } @@ -474,14 +474,14 @@ fn is_same_runtime_event(left: &CoworkChatRuntimeEvent, right: &CoworkChatRuntim fn resolve_store_for_scope(scope: CoworkChatScope) -> &'static dyn FeatureSessionStore { match scope { CoworkChatScope::Homepage => &HOMEPAGE_SESSION_STORE, - CoworkChatScope::OrganizeFileFolder => &ORGANIZE_SESSION_STORE, + CoworkChatScope::IntelligentFolder => &FOLDER_SESSION_STORE, } } fn resolve_store_for_session(session: &LocalCoworkSession) -> &'static dyn FeatureSessionStore { match session { LocalCoworkSession::Homepage(_) => &HOMEPAGE_SESSION_STORE, - LocalCoworkSession::Organize(_) => &ORGANIZE_SESSION_STORE, + LocalCoworkSession::Folder(_) => &FOLDER_SESSION_STORE, } } diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index b33ace627..d2ef0858e 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -26,13 +26,13 @@ fn main() { cowork::homepage::chat_sessions::update_homepage_chat_session, cowork::homepage::chat_sessions::rename_homepage_chat_session, cowork::homepage::chat_sessions::delete_homepage_chat_session, - cowork::organize::file_tree::read_path_tree, - cowork::organize::sessions::list_organize_sessions, - cowork::organize::sessions::get_organize_session, - cowork::organize::sessions::create_organize_session, - cowork::organize::sessions::update_organize_session, - cowork::organize::sessions::rename_organize_session, - cowork::organize::sessions::delete_organize_session + cowork::intelligent_folder::file_tree::read_path_tree, + cowork::intelligent_folder::sessions::list_folder_sessions, + cowork::intelligent_folder::sessions::get_folder_session, + cowork::intelligent_folder::sessions::create_folder_session, + cowork::intelligent_folder::sessions::update_folder_session, + cowork::intelligent_folder::sessions::rename_folder_session, + cowork::intelligent_folder::sessions::delete_folder_session ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/frontend/src/components/cowork/chat/cowork-chat-box.tsx b/frontend/src/components/cowork/chat/cowork-chat-box.tsx index 021d2c0ae..c2a099290 100644 --- a/frontend/src/components/cowork/chat/cowork-chat-box.tsx +++ b/frontend/src/components/cowork/chat/cowork-chat-box.tsx @@ -75,8 +75,8 @@ const CoworkChatBox = ({ [activeSession?.id, scope] ) const emptyStateDescription = - scope === 'organize-file-folder' - ? 'Start a Cowork chat session to understand, discuss, and organize your folder.' + scope === 'intelligent-folder' + ? 'Start a Cowork chat session to understand, discuss, and folder your folder.' : 'Start a new Cowork chat to discuss your task.' const responsiveChatBoxWidthClass = 'md:w-[clamp(320px,38vw,600px)]' diff --git a/frontend/src/components/cowork/cowork-main.tsx b/frontend/src/components/cowork/cowork-main.tsx index 72a16b73f..030d0ff14 100644 --- a/frontend/src/components/cowork/cowork-main.tsx +++ b/frontend/src/components/cowork/cowork-main.tsx @@ -6,47 +6,47 @@ import type { ActionStep } from '@/typings/agent' import { Icon } from '@/components/ui/icon' import { cn } from '@/lib/utils' import { COWORK_MODES, type CoworkModeId } from './cowork.constants' -import OrganizeFileFolderMode from './modes/organize-file-folder' +import IntelligentFolderMode from './modes/intelligent-folder' interface CoworkMainProps { activeMode: CoworkModeId | null - organizeSession: CoworkChatSessionDetail | null - organizeLiveSession: CoworkLiveSessionState | null - isOrganizeSessionLoading: boolean - isOrganizeChatSending: boolean + folderSession: CoworkChatSessionDetail | null + folderLiveSession: CoworkLiveSessionState | null + isFolderSessionLoading: boolean + isFolderChatSending: boolean onSelectMode: (mode: CoworkModeId) => void - organizeModeResetVersion: number - onOrganizeWorkflowActiveChange: (active: boolean) => void - onOrganizeSessionCreated: (session: CoworkChatSessionDetail) => void - requestedOrganizeAction?: ActionStep | null - requestedOrganizeActionToken?: number + folderModeResetVersion: number + onFolderWorkflowActiveChange: (active: boolean) => void + onFolderSessionCreated: (session: CoworkChatSessionDetail) => void + requestedFolderAction?: ActionStep | null + requestedFolderActionToken?: number } const CoworkMain = ({ activeMode, - organizeSession, - organizeLiveSession, - isOrganizeSessionLoading, - isOrganizeChatSending, + folderSession, + folderLiveSession, + isFolderSessionLoading, + isFolderChatSending, onSelectMode, - organizeModeResetVersion, - onOrganizeWorkflowActiveChange, - onOrganizeSessionCreated, - requestedOrganizeAction = null, - requestedOrganizeActionToken = 0 + folderModeResetVersion, + onFolderWorkflowActiveChange, + onFolderSessionCreated, + requestedFolderAction = null, + requestedFolderActionToken = 0 }: CoworkMainProps) => { - if (activeMode === 'organize-file-folder') { + if (activeMode === 'intelligent-folder') { return ( - ) } diff --git a/frontend/src/components/cowork/cowork-page.tsx b/frontend/src/components/cowork/cowork-page.tsx index aef63ef38..8ef0df9d9 100644 --- a/frontend/src/components/cowork/cowork-page.tsx +++ b/frontend/src/components/cowork/cowork-page.tsx @@ -48,13 +48,13 @@ import CoworkSidebar from './cowork-sidebar' import CoworkTabs from './cowork-tabs' const HOMEPAGE_SCOPE: CoworkChatScope = 'homepage' -const ORGANIZE_SCOPE: CoworkChatScope = 'organize-file-folder' +const FOLDER_SCOPE: CoworkChatScope = 'intelligent-folder' const createScopeRecord = ( initialValue: T ): Record => ({ homepage: initialValue, - 'organize-file-folder': initialValue + 'intelligent-folder': initialValue }) const upsertChatSessionSummary = ( @@ -99,7 +99,7 @@ const buildSessionDetailFromSummary = ( runtime_events: current?.runtime_events ?? [], files: current?.files ?? [], run_status: current?.run_status ?? 'idle', - organize_tree_pair: current?.organize_tree_pair + folder_tree_pair: current?.folder_tree_pair }) const appendMessageIfMissing = ( @@ -926,9 +926,9 @@ const CoworkPage = () => { const [isSidebarOpen, setIsSidebarOpen] = useState(false) const [isChatSessionsBoardOpen, setIsChatSessionsBoardOpen] = useState(false) - const [organizeModeResetVersion, setOrganizeModeResetVersion] = useState(0) + const [folderModeResetVersion, setFolderModeResetVersion] = useState(0) const [isSessionsBoardOpen, setIsSessionsBoardOpen] = useState(false) - const [isOrganizeWorkflowActive, setIsOrganizeWorkflowActive] = + const [isFolderWorkflowActive, setIsFolderWorkflowActive] = useState(false) const [pendingDeleteSession, setPendingDeleteSession] = useState(null) @@ -956,9 +956,9 @@ const CoworkPage = () => { const [liveSessionsById, setLiveSessionsById] = useState< Record >({}) - const [requestedOrganizeAction, setRequestedOrganizeAction] = + const [requestedFolderAction, setRequestedFolderAction] = useState(null) - const [requestedOrganizeActionToken, setRequestedOrganizeActionToken] = + const [requestedFolderActionToken, setRequestedFolderActionToken] = useState(0) const recoveringSessionIdsRef = useRef>(new Set()) const suppressedStreamingSessionIdsRef = useRef>(new Set()) @@ -970,7 +970,7 @@ const CoworkPage = () => { ) const currentChatScope: CoworkChatScope = - activeMode === 'organize-file-folder' ? ORGANIZE_SCOPE : HOMEPAGE_SCOPE + activeMode === 'intelligent-folder' ? FOLDER_SCOPE : HOMEPAGE_SCOPE const currentActiveChatSession = activeChatSessionsByScope[currentChatScope] const replayedCurrentLiveSession = useMemo( @@ -986,32 +986,32 @@ const CoworkPage = () => { isChatSessionLoadingByScope[currentChatScope] const isCurrentChatSending = isChatSendingByScope[currentChatScope] const isCurrentChatInputLocked = - activeMode === 'organize-file-folder' && !isOrganizeWorkflowActive + activeMode === 'intelligent-folder' && !isFolderWorkflowActive const homepageChatSessions = chatSessionsByScope[HOMEPAGE_SCOPE] const homepageActiveChatSessionId = activeChatSessionIds[HOMEPAGE_SCOPE] - const organizeChatSessions = chatSessionsByScope[ORGANIZE_SCOPE] - const organizeActiveChatSessionId = activeChatSessionIds[ORGANIZE_SCOPE] - const organizeActiveChatSession = activeChatSessionsByScope[ORGANIZE_SCOPE] - const replayedOrganizeLiveSession = useMemo( - () => replayPersistedLiveSession(organizeActiveChatSession), - [organizeActiveChatSession] + const folderChatSessions = chatSessionsByScope[FOLDER_SCOPE] + const folderActiveChatSessionId = activeChatSessionIds[FOLDER_SCOPE] + const folderActiveChatSession = activeChatSessionsByScope[FOLDER_SCOPE] + const replayedFolderLiveSession = useMemo( + () => replayPersistedLiveSession(folderActiveChatSession), + [folderActiveChatSession] ) - const organizeLiveSession = organizeActiveChatSession - ? (liveSessionsById[organizeActiveChatSession.id] ?? - replayedOrganizeLiveSession ?? + const folderLiveSession = folderActiveChatSession + ? (liveSessionsById[folderActiveChatSession.id] ?? + replayedFolderLiveSession ?? null) : null - const isOrganizeSessionLoading = isChatSessionLoadingByScope[ORGANIZE_SCOPE] + const isFolderSessionLoading = isChatSessionLoadingByScope[FOLDER_SCOPE] - const resetOrganizeSessionState = useCallback(() => { + const resetFolderSessionState = useCallback(() => { setActiveChatSessionIds((prev) => ({ ...prev, - [ORGANIZE_SCOPE]: null + [FOLDER_SCOPE]: null })) setActiveChatSessionsByScope((prev) => ({ ...prev, - [ORGANIZE_SCOPE]: null + [FOLDER_SCOPE]: null })) }, []) @@ -1475,7 +1475,7 @@ const CoworkPage = () => { useEffect(() => { void Promise.all([ refreshChatSessions(HOMEPAGE_SCOPE), - refreshChatSessions(ORGANIZE_SCOPE) + refreshChatSessions(FOLDER_SCOPE) ]) }, [refreshChatSessions]) @@ -1484,7 +1484,7 @@ const CoworkPage = () => { setIsSidebarOpen(false) setIsChatSessionsBoardOpen(false) setIsSessionsBoardOpen(false) - setIsOrganizeWorkflowActive(false) + setIsFolderWorkflowActive(false) } const handleSelectMode = (mode: CoworkModeId) => { @@ -1492,12 +1492,12 @@ const CoworkPage = () => { setIsChatSessionsBoardOpen(false) setIsSessionsBoardOpen(false) - if (mode === 'organize-file-folder') { - resetOrganizeSessionState() - setOrganizeModeResetVersion((prev) => prev + 1) - setIsOrganizeWorkflowActive(false) - setRequestedOrganizeAction(null) - setRequestedOrganizeActionToken(0) + if (mode === 'intelligent-folder') { + resetFolderSessionState() + setFolderModeResetVersion((prev) => prev + 1) + setIsFolderWorkflowActive(false) + setRequestedFolderAction(null) + setRequestedFolderActionToken(0) } } @@ -1512,15 +1512,15 @@ const CoworkPage = () => { setIsSessionsBoardOpen(open) } - const handleOrganizeWorkflowActiveChange = (active: boolean) => { - setIsOrganizeWorkflowActive(active) + const handleFolderWorkflowActiveChange = (active: boolean) => { + setIsFolderWorkflowActive(active) } const handleOpenModeFirstPage = () => { - if (activeMode === 'organize-file-folder') { - resetOrganizeSessionState() - setOrganizeModeResetVersion((prev) => prev + 1) - setIsOrganizeWorkflowActive(false) + if (activeMode === 'intelligent-folder') { + resetFolderSessionState() + setFolderModeResetVersion((prev) => prev + 1) + setIsFolderWorkflowActive(false) } setIsChatSessionsBoardOpen(false) @@ -1531,14 +1531,14 @@ const CoworkPage = () => { setIsChatSessionsBoardOpen(false) setIsSessionsBoardOpen(false) - if (sessionId !== activeChatSessionIds[ORGANIZE_SCOPE]) { - await loadChatSession(sessionId, ORGANIZE_SCOPE) + if (sessionId !== activeChatSessionIds[FOLDER_SCOPE]) { + await loadChatSession(sessionId, FOLDER_SCOPE) } } const handleRenameSession = useCallback( async (sessionId: string) => { - const currentSummary = chatSessionsByScope[ORGANIZE_SCOPE].find( + const currentSummary = chatSessionsByScope[FOLDER_SCOPE].find( (session) => session.id === sessionId ) const nextTitle = window.prompt( @@ -1550,24 +1550,24 @@ const CoworkPage = () => { return } - const renamedSession = await coworkService.renameOrganizeSession( + const renamedSession = await coworkService.renameFolderSession( sessionId, nextTitle ) setChatSessionsByScope((prev) => ({ ...prev, - [ORGANIZE_SCOPE]: upsertChatSessionSummary( - prev[ORGANIZE_SCOPE], + [FOLDER_SCOPE]: upsertChatSessionSummary( + prev[FOLDER_SCOPE], buildChatSessionSummary(renamedSession) ) })) setActiveChatSessionsByScope((prev) => ({ ...prev, - [ORGANIZE_SCOPE]: - prev[ORGANIZE_SCOPE]?.id === renamedSession.id + [FOLDER_SCOPE]: + prev[FOLDER_SCOPE]?.id === renamedSession.id ? renamedSession - : prev[ORGANIZE_SCOPE] + : prev[FOLDER_SCOPE] })) }, [chatSessionsByScope] @@ -1613,14 +1613,14 @@ const CoworkPage = () => { const handleDeleteSession = useCallback( (sessionId: string) => { - const currentSummary = chatSessionsByScope[ORGANIZE_SCOPE].find( + const currentSummary = chatSessionsByScope[FOLDER_SCOPE].find( (session) => session.id === sessionId ) setPendingDeleteSession( currentSummary ?? { id: sessionId, - scope: ORGANIZE_SCOPE, + scope: FOLDER_SCOPE, title: sessionId, preview: '', updated_at: '', @@ -1664,7 +1664,7 @@ const CoworkPage = () => { if (sessionScope === HOMEPAGE_SCOPE) { await coworkService.deleteHomepageChatSession(sessionId) } else { - await coworkService.deleteOrganizeSession(sessionId) + await coworkService.deleteFolderSession(sessionId) } setChatSessionsByScope((prev) => ({ @@ -1685,10 +1685,10 @@ const CoworkPage = () => { [HOMEPAGE_SCOPE]: null })) } - } else if (activeChatSessionIds[ORGANIZE_SCOPE] === sessionId) { - resetOrganizeSessionState() - setOrganizeModeResetVersion((prev) => prev + 1) - setIsOrganizeWorkflowActive(false) + } else if (activeChatSessionIds[FOLDER_SCOPE] === sessionId) { + resetFolderSessionState() + setFolderModeResetVersion((prev) => prev + 1) + setIsFolderWorkflowActive(false) } removeLiveSessionState(sessionId) @@ -1701,7 +1701,7 @@ const CoworkPage = () => { isDeletingSession, pendingDeleteSession, removeLiveSessionState, - resetOrganizeSessionState + resetFolderSessionState ]) const handleDeleteDialogOpenChange = useCallback( @@ -1713,22 +1713,22 @@ const CoworkPage = () => { [isDeletingSession] ) - const handleOrganizeSessionCreated = useCallback( + const handleFolderSessionCreated = useCallback( (session: CoworkChatSessionDetail) => { setChatSessionsByScope((prev) => ({ ...prev, - [ORGANIZE_SCOPE]: upsertChatSessionSummary( - prev[ORGANIZE_SCOPE], + [FOLDER_SCOPE]: upsertChatSessionSummary( + prev[FOLDER_SCOPE], buildChatSessionSummary(session) ) })) setActiveChatSessionIds((prev) => ({ ...prev, - [ORGANIZE_SCOPE]: session.id + [FOLDER_SCOPE]: session.id })) setActiveChatSessionsByScope((prev) => ({ ...prev, - [ORGANIZE_SCOPE]: session + [FOLDER_SCOPE]: session })) }, [] @@ -1736,12 +1736,12 @@ const CoworkPage = () => { const handleSelectCoworkAction = useCallback( (action: ActionStep) => { - if (currentChatScope !== ORGANIZE_SCOPE) { + if (currentChatScope !== FOLDER_SCOPE) { return } - setRequestedOrganizeAction(action) - setRequestedOrganizeActionToken((prev) => prev + 1) + setRequestedFolderAction(action) + setRequestedFolderActionToken((prev) => prev + 1) }, [currentChatScope] ) @@ -1786,9 +1786,9 @@ const CoworkPage = () => { return } - if (scope === ORGANIZE_SCOPE) { - setRequestedOrganizeAction(null) - setRequestedOrganizeActionToken((prev) => prev + 1) + if (scope === FOLDER_SCOPE) { + setRequestedFolderAction(null) + setRequestedFolderActionToken((prev) => prev + 1) } setScopeSending(scope, true) @@ -1832,8 +1832,8 @@ const CoworkPage = () => { : hydratedSession if ( - scope === ORGANIZE_SCOPE && - resolvedSession.organize_tree_pair?.source_root + scope === FOLDER_SCOPE && + resolvedSession.folder_tree_pair?.source_root ) { resolvedSession = await coworkService.getChatSession( resolvedSession.id, @@ -1938,13 +1938,13 @@ const CoworkPage = () => { onSessionsBoardOpenChange={ handleSessionsBoardOpenChange } - modeSessions={organizeChatSessions} + modeSessions={folderChatSessions} activeModeSessionId={ - organizeActiveChatSessionId + folderActiveChatSessionId } isModeSessionsLoading={ isChatSessionsLoadingByScope[ - ORGANIZE_SCOPE + FOLDER_SCOPE ] } onSelectSession={handleSelectSession} @@ -1954,33 +1954,33 @@ const CoworkPage = () => {

@@ -2017,7 +2017,7 @@ const CoworkPage = () => { {pendingDeleteSession?.scope === HOMEPAGE_SCOPE ? 'Delete chat session?' - : 'Delete organize session?'} + : 'Delete folder session?'} {`Session "${pendingDeleteSession?.title ?? ''}" will be removed from this device.`} diff --git a/frontend/src/components/cowork/cowork-tabs.tsx b/frontend/src/components/cowork/cowork-tabs.tsx index 84e591bd9..1d4b276ed 100644 --- a/frontend/src/components/cowork/cowork-tabs.tsx +++ b/frontend/src/components/cowork/cowork-tabs.tsx @@ -233,7 +233,7 @@ const CoworkTabs = ({ )} - {activeMode === 'organize-file-folder' && ( + {activeMode === 'intelligent-folder' && (
{ +}: CoworkFolderBuildProps) => { const buildState = useCoworkBuildState({ liveSession, requestedAction, @@ -49,7 +49,7 @@ const CoworkOrganizeBuild = ({ viewport={ { +}: CoworkFolderResultProps) => { if (!tree) { return (
@@ -29,7 +29,7 @@ const CoworkOrganizeResult = ({ } return ( - { +}: CoworkFolderSourceProps) => { if (!tree) { return (
@@ -29,7 +29,7 @@ const CoworkOrganizeSource = ({ } return ( - void +interface CoworkFolderStepsProps { + activeStep: CoworkFolderStep + onSelectStep: (step: CoworkFolderStep) => void } const steps: { - id: CoworkOrganizeStep + id: CoworkFolderStep label: string icon: string }[] = [ @@ -18,10 +18,10 @@ const steps: { { id: 'result', label: 'Result', icon: 'ai-magic' } ] -const CoworkOrganizeSteps = ({ +const CoworkFolderSteps = ({ activeStep, onSelectStep -}: CoworkOrganizeStepsProps) => { +}: CoworkFolderStepsProps) => { return (
{steps.map((step, index) => { @@ -74,4 +74,4 @@ const CoworkOrganizeSteps = ({ ) } -export default CoworkOrganizeSteps +export default CoworkFolderSteps diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-icons.tsx b/frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-icons.tsx similarity index 100% rename from frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-icons.tsx rename to frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-icons.tsx diff --git a/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-view.tsx b/frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-view.tsx similarity index 95% rename from frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-view.tsx rename to frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-view.tsx index f9361c869..170435bbc 100644 --- a/frontend/src/components/cowork/organize-file-folder/cowork-organize-tree-view.tsx +++ b/frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-view.tsx @@ -1,28 +1,28 @@ import { useEffect, useMemo, useState } from 'react' import { ChevronDown, ChevronRight } from 'lucide-react' -import type { CoworkOrganizeTreeNode } from '@/typings/cowork' +import type { CoworkFolderTreeNode } from '@/typings/cowork' import { cn } from '@/lib/utils' -import { getTreeNodeVisual } from './cowork-organize-tree-icons' +import { getTreeNodeVisual } from './cowork-folder-tree-icons' -export type OrganizeTreeNode = CoworkOrganizeTreeNode +export type FolderTreeNode = CoworkFolderTreeNode -interface FlattenedTreeNode extends OrganizeTreeNode { +interface FlattenedTreeNode extends FolderTreeNode { path: string } -interface CoworkOrganizeTreeViewProps { +interface CoworkFolderTreeViewProps { label: string rootPath: string - tree: OrganizeTreeNode + tree: FolderTreeNode } -const getAllFolderIds = (node: OrganizeTreeNode): string[] => [ +const getAllFolderIds = (node: FolderTreeNode): string[] => [ ...(node.kind === 'folder' ? [node.id] : []), ...(node.children?.flatMap((child) => getAllFolderIds(child)) ?? []) ] const flattenTree = ( - node: OrganizeTreeNode, + node: FolderTreeNode, rootPath: string, parentPath = '' ): FlattenedTreeNode[] => { @@ -36,7 +36,7 @@ const flattenTree = ( ] } -const getDirectChildCounts = (node: OrganizeTreeNode) => ({ +const getDirectChildCounts = (node: FolderTreeNode) => ({ folders: node.children?.filter((child) => child.kind === 'folder').length ?? 0, files: node.children?.filter((child) => child.kind === 'file').length ?? 0 @@ -50,7 +50,7 @@ const renderTreeNode = ({ onToggle, onSelect }: { - node: OrganizeTreeNode + node: FolderTreeNode depth: number expandedIds: Set selectedId: string @@ -141,11 +141,11 @@ const renderTreeNode = ({ ) } -const CoworkOrganizeTreeView = ({ +const CoworkFolderTreeView = ({ label, rootPath, tree -}: CoworkOrganizeTreeViewProps) => { +}: CoworkFolderTreeViewProps) => { const [expandedIds, setExpandedIds] = useState>( () => new Set(getAllFolderIds(tree)) ) @@ -296,6 +296,6 @@ const CoworkOrganizeTreeView = ({ ) } -export default CoworkOrganizeTreeView +export default CoworkFolderTreeView diff --git a/frontend/src/components/cowork/organize-file-folder/organize-tree-utils.ts b/frontend/src/components/cowork/intelligent-folder/folder-tree-utils.ts similarity index 63% rename from frontend/src/components/cowork/organize-file-folder/organize-tree-utils.ts rename to frontend/src/components/cowork/intelligent-folder/folder-tree-utils.ts index e5c04921b..a6f2d8d98 100644 --- a/frontend/src/components/cowork/organize-file-folder/organize-tree-utils.ts +++ b/frontend/src/components/cowork/intelligent-folder/folder-tree-utils.ts @@ -1,4 +1,4 @@ -export const ORGANIZE_TREE_READ_OPTIONS = { +export const FOLDER_TREE_READ_OPTIONS = { max_depth: 6, max_entries: 5000, include_hidden: false diff --git a/frontend/src/components/cowork/modes/organize-file-folder.tsx b/frontend/src/components/cowork/modes/intelligent-folder.tsx similarity index 89% rename from frontend/src/components/cowork/modes/organize-file-folder.tsx rename to frontend/src/components/cowork/modes/intelligent-folder.tsx index cf1372619..d6a7f83b3 100644 --- a/frontend/src/components/cowork/modes/organize-file-folder.tsx +++ b/frontend/src/components/cowork/modes/intelligent-folder.tsx @@ -7,26 +7,26 @@ import { coworkService } from '@/services/cowork.service' import type { CoworkChatSessionDetail, CoworkLiveSessionState, - CoworkOrganizeTreeNode, - CoworkOrganizeTreePair + CoworkFolderTreeNode, + CoworkFolderTreePair } from '@/typings/cowork' import type { ActionStep } from '@/typings/agent' import { cn } from '@/lib/utils' import { getCurrentWindow } from '@tauri-apps/api/window' -import CoworkOrganizeBuild from '../organize-file-folder/cowork-organize-build' -import CoworkOrganizeResult from '../organize-file-folder/cowork-organize-result' -import CoworkOrganizeSource from '../organize-file-folder/cowork-organize-source' -import { ORGANIZE_TREE_READ_OPTIONS } from '../organize-file-folder/organize-tree-utils' -import CoworkOrganizeSteps, { - type CoworkOrganizeStep -} from '../organize-file-folder/cowork-organize-steps' - -const organizeStepTransition = { +import CoworkFolderBuild from '../intelligent-folder/cowork-folder-build' +import CoworkFolderResult from '../intelligent-folder/cowork-folder-result' +import CoworkFolderSource from '../intelligent-folder/cowork-folder-source' +import { FOLDER_TREE_READ_OPTIONS } from '../intelligent-folder/folder-tree-utils' +import CoworkFolderSteps, { + type CoworkFolderStep +} from '../intelligent-folder/cowork-folder-steps' + +const folderStepTransition = { duration: 0.14, ease: 'easeOut' } as const -interface OrganizeFileFolderModeProps { +interface IntelligentFolderModeProps { resetVersion?: number session?: CoworkChatSessionDetail | null liveSession?: CoworkLiveSessionState | null @@ -38,7 +38,7 @@ interface OrganizeFileFolderModeProps { requestedBuildActionToken?: number } -const OrganizeFileFolderMode = ({ +const IntelligentFolderMode = ({ resetVersion = 0, session = null, liveSession = null, @@ -48,15 +48,15 @@ const OrganizeFileFolderMode = ({ onSessionCreated, requestedBuildAction = null, requestedBuildActionToken = 0 -}: OrganizeFileFolderModeProps) => { +}: IntelligentFolderModeProps) => { const [sourcePath, setSourcePath] = useState('') const [isLoadingPath, setIsLoadingPath] = useState(false) const [loadError, setLoadError] = useState(null) const [isDragActive, setIsDragActive] = useState(false) const [loadedTreePair, setLoadedTreePair] = - useState(null) + useState(null) const [isProcessing, setIsProcessing] = useState(false) - const [activeStep, setActiveStep] = useState('source') + const [activeStep, setActiveStep] = useState('source') useEffect(() => { onWorkflowActiveChange?.(isProcessing) @@ -74,15 +74,15 @@ const OrganizeFileFolderMode = ({ useLayoutEffect(() => { if (!session) return - setSourcePath(session.organize_tree_pair?.source_root ?? '') + setSourcePath(session.folder_tree_pair?.source_root ?? '') setLoadedTreePair(null) setActiveStep('source') setIsProcessing(true) - }, [session?.id, session?.organize_tree_pair?.source_root]) + }, [session?.id, session?.folder_tree_pair?.source_root]) - const organizeTreePair = loadedTreePair ?? session?.organize_tree_pair - const modeResultTree: CoworkOrganizeTreeNode | null = - organizeTreePair?.result_tree ?? null + const folderTreePair = loadedTreePair ?? session?.folder_tree_pair + const modeResultTree: CoworkFolderTreeNode | null = + folderTreePair?.result_tree ?? null const hasStreamingBuildActivity = Boolean( liveSession?.thinking.trim() || liveSession?.response.trim() || @@ -92,7 +92,7 @@ const OrganizeFileFolderMode = ({ ) const isRunCompleted = session?.run_status === 'completed' const sessionContentKey = useMemo( - () => loadedTreePair?.source_root ?? session?.id ?? 'organize-entry', + () => loadedTreePair?.source_root ?? session?.id ?? 'folder-entry', [loadedTreePair?.source_root, session?.id] ) @@ -202,7 +202,7 @@ const OrganizeFileFolderMode = ({ setSourcePath(resolvedPath) const sourceTree = await coworkService.readPathTree(resolvedPath, { - ...ORGANIZE_TREE_READ_OPTIONS + ...FOLDER_TREE_READ_OPTIONS }) const treePair = { @@ -212,20 +212,20 @@ const OrganizeFileFolderMode = ({ result_tree: null } const persistedSession = - await coworkService.createOrganizeSession(treePair) + await coworkService.createFolderSession(treePair) setLoadedTreePair({ source_root: - persistedSession.organize_tree_pair?.source_root ?? + persistedSession.folder_tree_pair?.source_root ?? resolvedPath, result_root: - persistedSession.organize_tree_pair?.result_root ?? + persistedSession.folder_tree_pair?.result_root ?? resolvedPath, source_tree: - persistedSession.organize_tree_pair?.source_tree ?? + persistedSession.folder_tree_pair?.source_tree ?? sourceTree, result_tree: - persistedSession.organize_tree_pair?.result_tree ?? null + persistedSession.folder_tree_pair?.result_tree ?? null }) onSessionCreated?.(persistedSession) setActiveStep('source') @@ -321,7 +321,7 @@ const OrganizeFileFolderMode = ({ {!isProcessing ? ( */}
setSourcePath(event.target.value) @@ -450,7 +450,7 @@ const OrganizeFileFolderMode = ({ ) : (
- @@ -475,20 +475,20 @@ const OrganizeFileFolderMode = ({ filter: 'blur(0px)' }} exit={{ opacity: 1, filter: 'blur(0px)' }} - transition={organizeStepTransition} + transition={folderStepTransition} className="flex min-h-0 w-full flex-1" > {activeStep === 'source' && ( - )} {activeStep === 'build' && ( - )} {activeStep === 'result' && ( - { - return invoke('read_path_tree', { + ): Promise { + return invoke('read_path_tree', { path, options }) } - async createOrganizeSession( - treePair: CoworkOrganizeTreePair + async createFolderSession( + treePair: CoworkFolderTreePair ): Promise { - return invoke('create_organize_session', { + return invoke('create_folder_session', { treePair }) } @@ -62,10 +62,10 @@ class CoworkService { }) } - async updateOrganizeSession( + async updateFolderSession( session: CoworkChatSessionDetail ): Promise { - return invoke('update_organize_session', { + return invoke('update_folder_session', { session }) } @@ -94,18 +94,18 @@ class CoworkService { }) } - async renameOrganizeSession( + async renameFolderSession( sessionId: string, title: string ): Promise { - return invoke('rename_organize_session', { + return invoke('rename_folder_session', { sessionId, title }) } - async deleteOrganizeSession(sessionId: string): Promise { - return invoke('delete_organize_session', { + async deleteFolderSession(sessionId: string): Promise { + return invoke('delete_folder_session', { sessionId }) } @@ -119,8 +119,8 @@ class CoworkService { ) } - if (scope === ORGANIZE_SCOPE) { - return invoke('list_organize_sessions') + if (scope === FOLDER_SCOPE) { + return invoke('list_folder_sessions') } return [] @@ -139,8 +139,8 @@ class CoworkService { ) } - if (scope === ORGANIZE_SCOPE) { - return invoke('get_organize_session', { + if (scope === FOLDER_SCOPE) { + return invoke('get_folder_session', { sessionId }) } diff --git a/frontend/src/typings/cowork.ts b/frontend/src/typings/cowork.ts index 7dc13f139..0ace57728 100644 --- a/frontend/src/typings/cowork.ts +++ b/frontend/src/typings/cowork.ts @@ -1,7 +1,7 @@ import type { ActionStep, Message } from './agent' export type CoworkChatMessageRole = 'user' | 'assistant' -export type CoworkChatScope = 'homepage' | 'organize-file-folder' +export type CoworkChatScope = 'homepage' | 'intelligent-folder' export interface CoworkGitHubRepositoryContext { owner: string @@ -19,20 +19,20 @@ export interface CoworkChatToolSettings { generate_video?: boolean } -export interface CoworkOrganizeTreeNode { +export interface CoworkFolderTreeNode { id: string name: string kind: 'folder' | 'file' extension?: string size?: string - children?: CoworkOrganizeTreeNode[] + children?: CoworkFolderTreeNode[] } -export interface CoworkOrganizeTreePair { +export interface CoworkFolderTreePair { source_root: string result_root: string - source_tree: CoworkOrganizeTreeNode - result_tree: CoworkOrganizeTreeNode | null + source_tree: CoworkFolderTreeNode + result_tree: CoworkFolderTreeNode | null } export interface CoworkChatMessage { @@ -76,7 +76,7 @@ export interface CoworkChatSessionDetail extends CoworkChatSessionSummary { runtime_events: CoworkRuntimeEventPayload[] files: CoworkChatFile[] run_status: CoworkChatRunStatus - organize_tree_pair?: CoworkOrganizeTreePair + folder_tree_pair?: CoworkFolderTreePair } export type CoworkChatEvent = From 5d1395d92747e317b10d8c30af232d2f3bb97038 Mon Sep 17 00:00:00 2001 From: namtranii Date: Sat, 11 Apr 2026 15:15:55 +0700 Subject: [PATCH 06/12] feat: build ModelSelector button into CoworkChatBox --- .../cowork/chat/cowork-chat-box.tsx | 2 + .../cowork/chat/cowork-model-selector.tsx | 178 ++++++++++++++++++ src/ii_agent/agents/cowork/factory.py | 10 - 3 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/cowork/chat/cowork-model-selector.tsx diff --git a/frontend/src/components/cowork/chat/cowork-chat-box.tsx b/frontend/src/components/cowork/chat/cowork-chat-box.tsx index c2a099290..4baaf3d8b 100644 --- a/frontend/src/components/cowork/chat/cowork-chat-box.tsx +++ b/frontend/src/components/cowork/chat/cowork-chat-box.tsx @@ -13,6 +13,7 @@ import type { CoworkLiveSessionState } from '@/typings/cowork' import { useCoworkChatMessageAdapter } from './use-cowork-chatmessage-adapter' +import CoworkModelSelector from './cowork-model-selector' interface CoworkChatBoxProps { className?: string @@ -125,6 +126,7 @@ const CoworkChatBox = ({ > All files +
= { + openai: '/images/openai-dark.svg', + anthropic: '/images/anthropic-dark.svg' +} +const INVERTS_IN_LIGHT_MODE = new Set(['custom']) + +// Mirror the active-state styling of the Chat / All files tab buttons in +// cowork-chat-box.tsx — but apply it as a :hover (and :data-[state=open]) +// effect so the selector visually "presses in" when the user rolls over or +// opens it, identical to how a tab becomes active. +const TRIGGER_CLASS = clsx( + 'group relative flex h-7 cursor-pointer items-center gap-2 rounded-full', + 'border border-sky-blue px-3 text-xs font-semibold outline-none', + 'transition-colors', + // Idle — same as an inactive tab button + 'border-firefly text-firefly dark:border-sky-blue dark:text-sky-blue', + // Hover / open — same as the active tab button + 'hover:bg-firefly hover:border-firefly hover:text-sky-blue-2', + 'dark:hover:bg-sky-blue dark:hover:border-sky-blue-2 dark:hover:text-black', + 'data-[state=open]:bg-firefly data-[state=open]:border-firefly data-[state=open]:text-sky-blue-2', + 'dark:data-[state=open]:bg-sky-blue dark:data-[state=open]:border-sky-blue-2 dark:data-[state=open]:text-black', + 'disabled:cursor-not-allowed disabled:opacity-50' +) + +interface ProviderIconProps { + providerKey: string + className?: string +} + +const ProviderIcon = ({ providerKey, className }: ProviderIconProps) => { + if (!PROVIDERS_NAME[providerKey]) return null + const defaultSrc = `/images/${providerKey}.svg` + const lightSrc = LIGHT_SRC_OVERRIDE[providerKey] + const invertInLight = INVERTS_IN_LIGHT_MODE.has(providerKey) + + // Case 1: separate light-mode asset exists — render two s toggled + // by tailwind's dark: variant. Keeps each asset pristine. + if (lightSrc) { + return ( + <> + {providerKey} + {providerKey} + + ) + } + + // Case 2: no separate asset and the svg is white-filled — darken it on + // light mode via brightness-0 (white → black); leave it untouched on dark. + if (invertInLight) { + return ( + {providerKey} + ) + } + + // Case 3: asset is a rasterised/pattern image that renders fine on both + // backgrounds (gemini, google, …). Use as-is. + return ( + {providerKey} + ) +} + +const renderTriggerLabel = (model: IModel | null | undefined) => { + if (!model) { + return No model + } + const providerKey = getProviderKey(model) + return ( + <> + + {model.model} + + ) +} + +const CoworkModelSelector = ({ className }: CoworkModelSelectorProps) => { + const dispatch = useAppDispatch() + const availableModels = useAppSelector(selectAvailableModels) + const selectedModelId = useAppSelector(selectSelectedModel) + + const hasModels = availableModels.length > 0 + const effectiveModel = + availableModels.find((model) => model.id === selectedModelId) ?? + availableModels[0] ?? + null + // Pass undefined (not '') to Radix when nothing is selected so the + // trigger remains in uncontrolled-empty state rather than flashing a + // placeholder. Radix Select treats empty string as a real value. + const triggerValue = effectiveModel?.id + + return ( + + ) +} + +export default CoworkModelSelector diff --git a/src/ii_agent/agents/cowork/factory.py b/src/ii_agent/agents/cowork/factory.py index 69341a713..497876247 100644 --- a/src/ii_agent/agents/cowork/factory.py +++ b/src/ii_agent/agents/cowork/factory.py @@ -21,10 +21,6 @@ from ii_agent.agents.skills.prompt_db import generate_skill_tool_description from ii_agent.settings.llm import Provider -# Hardcoded defaults for cowork agents -DEFAULT_MODEL_ID = "gpt-5.2" -DEFAULT_PROVIDER = Provider.OPENAI - class CoworkAgentFactory: """Factory for cowork-specific IIAgent creation with runtime overrides.""" @@ -211,12 +207,6 @@ async def create_agent( ) -> IIAgent: logger.info(f"Creating cowork {agent_type} agent for session {session_id}") - # Override LLM config with hardcoded defaults - llm_config = llm_config.model_copy(update={ - "model": DEFAULT_MODEL_ID, - "provider": DEFAULT_PROVIDER, - }) - tool_args = tool_args or {} has_media = tool_args.get("media_generation", False) has_task_agent = tool_args.get("task_agent", False) From 7847d8b09b963fe68b8ff4240ff7c563a2f70089 Mon Sep 17 00:00:00 2001 From: namtranii Date: Sat, 11 Apr 2026 16:41:38 +0700 Subject: [PATCH 07/12] feat: implement fire-and-forget cancel dispatch for cowork remote runs --- .../src/cowork/agent_remote/cancel.rs | 114 ++++++++++++++++++ .../src-tauri/src/cowork/agent_remote/mod.rs | 1 + .../src-tauri/src/cowork/chat_commands.rs | 53 +++++++- 3 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 frontend/src-tauri/src/cowork/agent_remote/cancel.rs diff --git a/frontend/src-tauri/src/cowork/agent_remote/cancel.rs b/frontend/src-tauri/src/cowork/agent_remote/cancel.rs new file mode 100644 index 000000000..e5f4c7635 --- /dev/null +++ b/frontend/src-tauri/src/cowork/agent_remote/cancel.rs @@ -0,0 +1,114 @@ +//! Fire-and-forget cancel dispatch for cowork remote runs. +//! +//! When the user presses Pause in the cowork chat box, the local +//! `stop_cowork_chat_session` command updates UI state immediately, but the +//! Python backend is still running the agent loop. This module opens a +//! short-lived, independent socket.io connection to Python, emits a single +//! `chat_message` event with `{command: "cancel"}`, and disconnects. +//! +//! Python's `CancelHandler` (see `src/ii_agent/realtime/handlers/cancel.py`) +//! then transitions the running task to ABORTING and sets a Redis cancel +//! flag; the agent's execution loop picks that up on its next poll and +//! throws `RunCancelledException`, which the backend translates into a +//! terminal socket event. The original `run_remote_agent_request` worker +//! (still blocked in `wait_for_remote_run_completion`) receives that event +//! via its OWN long-lived socket and exits cleanly via its existing +//! terminal branches. +//! +//! This is deliberately kept separate from `socket.rs::run_remote_agent_request` +//! because the cancel flow has very different semantics: no event loop, no +//! tool confirmation / continue-run, and errors are best-effort (logged and +//! dropped, never propagated — local stop must not fail because of a network +//! hiccup). +//! +//! ## Why no `join_session`? +//! +//! The Python `chat_message` handler +//! (`src/ii_agent/realtime/manager.py::chat_message`) resolves the session +//! directly from `request.session_uuid` via `_require_session` (DB lookup). +//! It does NOT require the socket to have previously called `join_session`. +//! +//! Emitting `join_session` from this short-lived cancel socket triggered a +//! race: the `join_session` handler performs `enter_room` + +//! `add_sid_to_session` async operations, and if Rust disconnected the +//! socket before those finished, python-socketio's internal sid→session map +//! raised `KeyError: 'Session not found'` during cleanup. Since `chat_message` +//! doesn't need the join, we skip it entirely and avoid the race. +//! +//! We also do NOT pass `session_uuid` in the connect-auth payload — the only +//! required auth field is `token`. Passing `session_uuid` would cause Python +//! to store it on the sid's session data, but since we never emit +//! `join_session`, it goes unused and could mislead future maintainers. + +use rust_socketio::{ClientBuilder as SocketClientBuilder, TransportType}; +use serde_json::json; +use std::thread; +use std::time::Duration; +use tauri::async_runtime::spawn_blocking; + +/// Grace period between emitting the cancel `chat_message` and disconnecting +/// the short-lived cancel socket. Gives Python's async event loop enough time +/// to fully dispatch the incoming event into `CancelHandler` before the socket +/// tears down. 250ms is plenty for an in-process event-loop dispatch and +/// cheap enough that the stop button still feels instant. +const CANCEL_EMIT_GRACE_MS: u64 = 250; + +/// Dispatch a `{command: "cancel"}` event to the Python backend for the given +/// remote session. Fire-and-forget: the returned `Result` is for logging only +/// and should never be used to block the caller's local stop flow. +pub async fn emit_cancel_signal( + api_base_url: String, + access_token: String, + remote_session_id: String, +) -> Result<(), String> { + spawn_blocking(move || -> Result<(), String> { + // Auth payload: ONLY token. See the module-level comment for why we + // deliberately omit session_uuid here. + let auth_payload = json!({ "token": access_token }); + + // Build a standalone socket.io client. We intentionally do NOT + // register any `on("chat_event", …)` listeners — we don't care about + // responses; the original run worker is still subscribed on ITS own + // long-lived socket and will receive the terminal event organically + // once Python marks the run as cancelled. + let socket = SocketClientBuilder::new(api_base_url.as_str()) + .transport_type(TransportType::Websocket) + .auth(auth_payload) + .connect() + .map_err(|error| { + format!("Cowork cancel dispatch: connect failed: {error}") + })?; + + // Emit the cancel command directly. Python's `chat_message` handler + // (`src/ii_agent/realtime/manager.py`) calls `_require_session` on + // `request.session_uuid`, so the session is resolved from the + // payload — no prior `join_session` emit is required. `CancelContent` + // extends `EmptyContent` with `extra="allow"`, so a bare + // `{command: "cancel"}` parses cleanly via the discriminated union. + socket + .emit( + "chat_message", + json!({ + "session_uuid": remote_session_id, + "content": { "command": "cancel" }, + }), + ) + .map_err(|error| { + format!("Cowork cancel dispatch: chat_message emit failed: {error}") + })?; + + // Give Python's event loop a moment to dispatch the cancel into + // CancelHandler before we tear down the socket. Without this sleep, + // a premature disconnect can race the server's async handler and + // cause spurious cleanup errors in python-socketio's internal state. + thread::sleep(Duration::from_millis(CANCEL_EMIT_GRACE_MS)); + + // Best-effort disconnect — ignore errors. We've already delivered the + // cancel signal. + let _ = socket.disconnect(); + + Ok(()) + }) + .await + .map_err(|error| format!("Cowork cancel dispatch worker join failed: {error}"))? +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/mod.rs b/frontend/src-tauri/src/cowork/agent_remote/mod.rs index 6743f52cc..33f7b336f 100644 --- a/frontend/src-tauri/src/cowork/agent_remote/mod.rs +++ b/frontend/src-tauri/src/cowork/agent_remote/mod.rs @@ -1,4 +1,5 @@ pub mod auth; +pub mod cancel; mod desktop_dispatcher; mod mapper; mod payload; diff --git a/frontend/src-tauri/src/cowork/chat_commands.rs b/frontend/src-tauri/src/cowork/chat_commands.rs index 9dc0725eb..16ddfcacf 100644 --- a/frontend/src-tauri/src/cowork/chat_commands.rs +++ b/frontend/src-tauri/src/cowork/chat_commands.rs @@ -1,16 +1,65 @@ +use crate::cowork::agent_remote::auth::{get_auth_context, RemoteAuthState}; +use crate::cowork::agent_remote::cancel::emit_cancel_signal; use crate::cowork::chat::{CoworkChatRunStatus, CoworkChatScope, CoworkChatSessionDetail}; use crate::cowork::session_gateway; use crate::cowork::time_utils::now_iso; -use tauri::AppHandle; +use tauri::{AppHandle, State}; #[tauri::command] -pub fn stop_cowork_chat_session( +pub async fn stop_cowork_chat_session( app: AppHandle, + remote_auth_state: State<'_, RemoteAuthState>, scope: CoworkChatScope, session_id: String, ) -> Result { let mut local_session = session_gateway::load_local_session(&app, scope, &session_id)?; + // Only dispatch a remote cancel if the session is actually mid-run. This + // guarantees the "already completed" / "stopped twice" path never opens a + // pointless socket connection. + let should_dispatch_remote_cancel = matches!( + local_session.base().run_status, + CoworkChatRunStatus::Thinking | CoworkChatRunStatus::WaitingForInput + ); + + if should_dispatch_remote_cancel { + let runtime_session_id = local_session.base().runtime_session_id.clone(); + // Snapshot the auth context into owned values BEFORE any .await so + // the `State<'_, _>` reference never spans an await point (keeps the + // future Send and sidesteps Rust's borrow-across-await diagnostics). + let auth = get_auth_context(&remote_auth_state); + let access_token = auth.access_token; + let api_base_url = auth.api_base_url; + + match (runtime_session_id, access_token, api_base_url) { + (Some(remote_session_id), Some(access_token), Some(api_base_url)) => { + // Fire-and-forget: log any failure and continue with local + // stop. A network hiccup here must not prevent the user from + // seeing the UI transition to Stopped. + if let Err(error) = + emit_cancel_signal(api_base_url, access_token, remote_session_id).await + { + eprintln!("[cowork] remote cancel dispatch failed: {error}"); + } + } + (None, _, _) => { + // Session was never bound to a Python session UUID yet — no + // remote run to cancel. + eprintln!( + "[cowork] stop: no runtime_session_id for local session {session_id}; \ + skipping remote cancel" + ); + } + _ => { + // Auth missing — user may be signed out. Local stop still runs. + eprintln!( + "[cowork] stop: auth context missing; skipping remote cancel for {session_id}" + ); + } + } + } + + // Existing local state update (unchanged). { let base = local_session.base_mut(); if matches!( From 26a4bbc291b1970a269bd6eafb9e045e86b93fd2 Mon Sep 17 00:00:00 2001 From: namtranii Date: Sun, 12 Apr 2026 14:54:51 +0700 Subject: [PATCH 08/12] feat: implement undo/redo functionality for cowork Intelligent Folder --- frontend/src-tauri/Cargo.lock | 49 +- frontend/src-tauri/Cargo.toml | 1 + .../cowork/intelligent_folder/chat_prompt.rs | 1 + .../src/cowork/intelligent_folder/mod.rs | 2 + .../src/cowork/intelligent_folder/sessions.rs | 69 +- .../intelligent_folder/snapshot_store.rs | 1835 +++++++++++++++++ .../intelligent_folder/undo_commands.rs | 142 ++ .../src-tauri/src/cowork/session_gateway.rs | 318 ++- frontend/src-tauri/src/main.rs | 4 +- .../cowork-folder-undo-redo.tsx | 101 + .../cowork/modes/intelligent-folder.tsx | 61 +- frontend/src/services/cowork.service.ts | 12 + frontend/src/typings/cowork.ts | 21 + 13 files changed, 2604 insertions(+), 12 deletions(-) create mode 100644 frontend/src-tauri/src/cowork/intelligent_folder/snapshot_store.rs create mode 100644 frontend/src-tauri/src/cowork/intelligent_folder/undo_commands.rs create mode 100644 frontend/src/components/cowork/intelligent-folder/cowork-folder-undo-redo.tsx diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index e75e87950..a84bc7bbb 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -20,6 +20,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-process", "tauri-plugin-shell", + "zstd", ] [[package]] @@ -357,6 +358,8 @@ version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -675,7 +678,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -812,7 +815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1787,6 +1790,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.2", + "libc", +] + [[package]] name = "js-sys" version = "0.3.93" @@ -3125,7 +3138,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4069,7 +4082,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5547,3 +5560,31 @@ dependencies = [ "quote", "syn 2.0.101", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 58c97db42..8a01ea2d4 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -24,6 +24,7 @@ rust_socketio = "0.6" glob = "0.3" regex = "1" sha2 = "0.10" +zstd = "0.13" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs b/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs index 07c37318c..0e3b35c28 100644 --- a/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs +++ b/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs @@ -77,6 +77,7 @@ mod tests { source_tree: sample_folder("demo"), result_tree: None, }, + undo_slot: crate::cowork::intelligent_folder::sessions::FolderUndoSlotState::None, } } diff --git a/frontend/src-tauri/src/cowork/intelligent_folder/mod.rs b/frontend/src-tauri/src/cowork/intelligent_folder/mod.rs index 26ff8d49b..b4c7aa5ea 100644 --- a/frontend/src-tauri/src/cowork/intelligent_folder/mod.rs +++ b/frontend/src-tauri/src/cowork/intelligent_folder/mod.rs @@ -2,3 +2,5 @@ pub mod capabilities; pub mod chat_prompt; pub mod file_tree; pub mod sessions; +pub mod snapshot_store; +pub mod undo_commands; diff --git a/frontend/src-tauri/src/cowork/intelligent_folder/sessions.rs b/frontend/src-tauri/src/cowork/intelligent_folder/sessions.rs index 716fe3905..03dd54832 100644 --- a/frontend/src-tauri/src/cowork/intelligent_folder/sessions.rs +++ b/frontend/src-tauri/src/cowork/intelligent_folder/sessions.rs @@ -15,6 +15,7 @@ use tauri::{AppHandle, Manager}; const STORE_DIR_NAME: &str = "cowork"; const SESSION_STORE_DIR_NAME: &str = "folder-sessions"; const LEGACY_STORE_FILE_NAME: &str = "folder-sessions.json"; +const FOLDER_SNAPSHOTS_DIR_NAME: &str = "folder-snapshots"; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct CoworkFolderTreePair { @@ -24,11 +25,33 @@ pub struct CoworkFolderTreePair { pub result_tree: Option, } +/// Lightweight UI hint mirroring the on-disk `timeline.json` for a folder +/// session's snapshot history. The authoritative timeline lives in +/// `{app_data}/cowork/folder-snapshots/{session_id}/timeline.json`; this +/// struct caches just enough state on the session JSON for the frontend +/// to render the Undo/Redo pill with a "current/total" counter without +/// having to load the timeline file on every render. +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)] +pub struct FolderUndoState { + /// Is there at least one earlier snapshot to undo to? + pub can_undo: bool, + /// Is there at least one later snapshot to redo into? + pub can_redo: bool, + /// 1-based index of the snapshot currently on disk. `0` when the + /// timeline is empty (no snapshots yet) — in that case `total` is + /// also `0` and the UI hides the pill. + pub current: usize, + /// Total number of snapshots in the timeline. `0` when empty. + pub total: usize, +} + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct CoworkChatSessionDetail { #[serde(flatten)] pub base: BaseCoworkChatSessionDetail, pub folder_tree_pair: CoworkFolderTreePair, + #[serde(default)] + pub undo_state: FolderUndoState, } impl Deref for CoworkChatSessionDetail { @@ -90,6 +113,7 @@ pub fn create_folder_session( run_status: CoworkChatRunStatus::Idle, }, folder_tree_pair: tree_pair, + undo_state: FolderUndoState::default(), }; write_session(&app, &session)?; @@ -134,9 +158,52 @@ pub fn rename_folder_session( #[tauri::command] pub fn delete_folder_session(app: AppHandle, session_id: String) -> Result<(), String> { + // Snapshot dir cleanup is best-effort: if it fails we still delete the + // session file so the user isn't blocked. A dangling snapshot dir only + // wastes disk until the user deletes the app data directory. + if let Ok(snapshot_dir) = folder_snapshot_session_dir(&app, &session_id) { + if snapshot_dir.exists() { + if let Err(error) = fs::remove_dir_all(&snapshot_dir) { + eprintln!( + "[cowork] failed to clean up folder snapshot dir {}: {}", + snapshot_dir.display(), + error + ); + } + } + } + delete_session(&app, &session_id) } +/// Absolute path to `{app_data}/cowork/folder-snapshots/` (ensures it exists). +pub fn folder_snapshots_base_dir(app: &AppHandle) -> Result { + let mut store_root = store_root_dir_path(app)?; + store_root.push(FOLDER_SNAPSHOTS_DIR_NAME); + + fs::create_dir_all(&store_root).map_err(|error| { + format!( + "Failed to create folder snapshots directory {}: {}", + store_root.display(), + error + ) + })?; + + Ok(store_root) +} + +/// Absolute path to the per-session snapshot directory (does NOT create it). +/// The snapshot store is responsible for lazy-creating the subdirs it needs. +pub fn folder_snapshot_session_dir( + app: &AppHandle, + session_id: &str, +) -> Result { + let normalized = normalize_session_id(session_id)?; + let mut dir = folder_snapshots_base_dir(app)?; + dir.push(normalized); + Ok(dir) +} + pub fn sync_result_tree_from_disk(session: &mut CoworkChatSessionDetail) -> Result<(), String> { let latest_tree = file_tree::read_path_tree(session.folder_tree_pair.source_root.clone(), None)?; @@ -161,7 +228,7 @@ fn validate_session(session: &CoworkChatSessionDetail) -> Result<(), String> { validate_tree_pair(&session.folder_tree_pair) } -fn hash_tree(tree: &FileTreeNode) -> Result { +pub fn hash_tree(tree: &FileTreeNode) -> Result { let serialized = serde_json::to_vec(tree) .map_err(|error| format!("Failed to serialize folder tree for hashing: {}", error))?; let digest = Sha256::digest(serialized); diff --git a/frontend/src-tauri/src/cowork/intelligent_folder/snapshot_store.rs b/frontend/src-tauri/src/cowork/intelligent_folder/snapshot_store.rs new file mode 100644 index 000000000..3a61d01a8 --- /dev/null +++ b/frontend/src-tauri/src/cowork/intelligent_folder/snapshot_store.rs @@ -0,0 +1,1835 @@ +//! Content-addressable snapshot store with timeline history for cowork +//! Intelligent Folder sessions. +//! +//! Each folder session gets a **timeline** of snapshots (up to +//! [`MAX_TIMELINE_LEN`] deep) backed by a shared content-addressable +//! blob pool: +//! +//! ```text +//! {app_data}/cowork/folder-snapshots/{session_id}/ +//! ├── blobs/ +//! │ ├── ab/ +//! │ │ └── abc…def # file bytes, filename = full sha256 +//! │ └── 7f/ +//! │ └── 7f9a… +//! ├── snapshots/ +//! │ ├── 001-01HRAB….json # per-snapshot manifest +//! │ ├── 002-01HRAC….json +//! │ └── … +//! ├── timeline.json # ordered list + cursor +//! └── pending.json # pre-run marker (transient) +//! ``` +//! +//! ### Blob pool (content-addressable) +//! +//! Every file is hashed (SHA-256) and its bytes are written to +//! `blobs/{first2}/{full_hash}` exactly once — identical contents dedupe +//! automatically across snapshots. This is what keeps a long timeline +//! cheap: if the agent only modifies 5 files out of 10k between +//! snapshots, only those 5 new blobs are added to the pool. +//! +//! ### Timeline ([`Timeline`]) +//! +//! `timeline.json` is the authoritative ordered history: +//! +//! - `snapshots[i]` holds a [`SnapshotMeta`] pointing at a manifest file +//! and recording the file-tree hash of the disk state it captured. +//! - `cursor` is the index of the snapshot **currently materialised on +//! disk**. `cursor == snapshots.len() - 1` means the user is at the +//! newest checkpoint; `cursor < len - 1` means they've undone one or +//! more steps ("detached"). +//! - Undo/Redo move the cursor by ±1 and re-materialise the corresponding +//! manifest on disk. +//! - Sending a new chat message while detached **truncates** +//! `snapshots[cursor+1..]` (git-style), then appends the fresh +//! post-run state. Old future branches are dropped. +//! - If the timeline grows past [`MAX_TIMELINE_LEN`], the oldest entry is +//! dropped and the cursor shifts to compensate. +//! +//! ### Pending marker ([`PendingSnapshot`]) +//! +//! Captured at run start (pre-run hook in `session_gateway`). Its +//! `pre_run_tree_hash` lets the post-run code cheaply decide "did disk +//! actually change?" without comparing whole manifests. Its `manifest` +//! is used to *seed* the timeline when it's empty (or to *rebase* it +//! when the current cursor entry is out-of-sync with disk, e.g. due to +//! external modification). On every commit/discard the pending file is +//! cleared and orphaned blobs GC'd. +//! +//! ### Crash safety +//! +//! - Blobs, manifest files, `timeline.json`, and `pending.json` are all +//! written via `*.tmp` → `rename`, so readers only ever see fully +//! formed content. +//! - The materialise step (copying bytes from blobs back to +//! `source_root`) uses per-file tmp + rename. A crash mid-apply leaves +//! the user's folder in a mixed state — same risk profile as the +//! agent itself. Documented as a known limitation. +//! - Timeline truncation and retention writes are a single atomic rename +//! of `timeline.json`; manifest files left behind from a half-finished +//! truncation are reclaimed by the next GC pass. +//! +//! Symlinks are stored as-is (target path recorded, not followed). All +//! files including hidden, `.git`, `node_modules`, etc. are included — +//! no ignore list, by the user's explicit choice. + +use super::sessions::folder_snapshot_session_dir; +use chrono::{SecondsFormat, Utc}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::{ + collections::{HashMap, HashSet}, + fs, + io::{self, Read}, + path::{Component, Path, PathBuf}, + time::UNIX_EPOCH, +}; +use tauri::AppHandle; + +const BLOBS_DIR_NAME: &str = "blobs"; +const BLOB_SUFFIX: &str = ".zst"; +const SNAPSHOTS_DIR_NAME: &str = "snapshots"; +const TIMELINE_FILE_NAME: &str = "timeline.json"; +const TIMELINE_TMP_FILE_NAME: &str = "timeline.json.tmp"; +const PENDING_FILE_NAME: &str = "pending.json"; +const PENDING_TMP_FILE_NAME: &str = "pending.json.tmp"; +const STAT_CACHE_FILE_NAME: &str = "stat-cache.json"; +const STAT_CACHE_TMP_FILE_NAME: &str = "stat-cache.json.tmp"; + +/// Zstd compression level. 3 is the default — good compression ratio on +/// text (~60-75% reduction) with minimal CPU overhead (~400 MB/s on +/// modern CPUs). Higher levels plateau quickly for code/text content. +const ZSTD_LEVEL: i32 = 3; + +/// Maximum number of snapshots retained per session. Older entries are +/// dropped from the front of the timeline (with cursor shifted to +/// compensate) when a new snapshot would push the list past this limit. +/// Keeps disk usage bounded — worst case ~20× the size of files changed +/// across the retained runs, thanks to CAS dedup. +pub const MAX_TIMELINE_LEN: usize = 20; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(rename_all = "snake_case", tag = "kind")] +pub enum ManifestEntry { + Dir { + rel_path: String, + }, + File { + rel_path: String, + sha256: String, + }, + Symlink { + rel_path: String, + target: String, + }, +} + +impl ManifestEntry { + fn rel_path(&self) -> &str { + match self { + ManifestEntry::Dir { rel_path } + | ManifestEntry::File { rel_path, .. } + | ManifestEntry::Symlink { rel_path, .. } => rel_path, + } + } +} + +/// A single captured disk state: the sorted list of every file, directory, +/// and symlink under `source_root`, with file content referenced by sha256 +/// into the shared blob pool. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Manifest { + pub entries: Vec, +} + +/// Lightweight descriptor of one snapshot recorded in `timeline.json`. +/// The heavy manifest lives in its own file (`snapshots/{id}.json`) so +/// the timeline file stays small and cheap to read/rewrite. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct SnapshotMeta { + /// Opaque ID used as the manifest filename (`snapshots/{id}.json`). + /// Monotonic-ish so lexicographic sort matches creation order, with a + /// trailing random tag to avoid collisions if two runs finish in the + /// same millisecond. + pub id: String, + /// ISO-8601 timestamp (millis precision, UTC). + pub created_at: String, + /// Hash of the file-tree (via `folder_sessions::hash_tree`) of the + /// disk state this snapshot captures. Used by the post-run hook to + /// cheaply check "does `timeline[cursor]` still match current disk?" + /// without having to re-materialise the manifest. + pub disk_tree_hash: String, +} + +/// Persisted history for one session. `cursor` is always in +/// `0..snapshots.len()` — an empty timeline is valid (`cursor == 0`, +/// no snapshots). +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] +pub struct Timeline { + pub snapshots: Vec, + pub cursor: usize, +} + +impl Timeline { + /// Clamp cursor to a valid index on load, in case the file was + /// corrupted or externally edited. + fn sanitized(mut self) -> Self { + if self.snapshots.is_empty() { + self.cursor = 0; + } else if self.cursor >= self.snapshots.len() { + self.cursor = self.snapshots.len() - 1; + } + self + } + + pub fn can_undo(&self) -> bool { + self.cursor > 0 && !self.snapshots.is_empty() + } + + pub fn can_redo(&self) -> bool { + !self.snapshots.is_empty() && self.cursor + 1 < self.snapshots.len() + } + + /// 1-based index of the current snapshot for UI display + /// (`"{current}/{total}"`). Returns `(0, 0)` when the timeline is + /// empty so the UI can hide itself. + pub fn display_position(&self) -> (usize, usize) { + if self.snapshots.is_empty() { + (0, 0) + } else { + (self.cursor + 1, self.snapshots.len()) + } + } +} + +/// Cache entry for a single file, used to skip re-hashing unchanged +/// files during snapshot scans. Indexed by relative path. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct StatCacheEntry { + /// Modification time in nanoseconds since UNIX epoch. + pub mtime_nanos: u128, + /// File size in bytes. + pub size: u64, + /// SHA-256 of the **uncompressed** file contents. Same as the blob + /// pool key, so we can skip both the `fs::read` and the `sha2` hash + /// computation on a cache hit. + pub sha256: String, +} + +/// Persisted stat cache: `rel_path → StatCacheEntry`. Lives in +/// `{session_dir}/stat-cache.json` and is rewritten atomically after +/// every snapshot scan. Safe to delete externally — a missing cache +/// just means the next scan will re-hash everything. +/// +/// **Correctness note**: mtime can lie on some filesystems (clones, +/// NFS, Docker volumes). If it does, we'll reuse a stale sha256 → +/// create a manifest pointing at the OLD blob → the user's disk state +/// appears unchanged when it isn't. Worst case: a change gets missed, +/// undo can't restore it. Best case: user triggers a subsequent run +/// that touches the same file via a real content edit, the mtime +/// updates, and the cache catches up. +/// +/// This is an acceptable trade-off because: (1) this is a UI undo +/// buffer, not a backup tool; (2) Git uses the same trick and it +/// works fine for 99% of workflows; (3) the CAS layer below us +/// prevents any actual data corruption — the worst outcome is +/// suboptimal undo. +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct StatCache { + pub entries: HashMap, +} + +/// Pre-run marker: a frozen snapshot of disk taken the moment a new agent +/// run started, held on disk so the post-run code can compare against the +/// live disk state (even after process restart) and decide whether to +/// commit the snapshot into the timeline. +/// +/// `pre_run_tree_hash` is the `hash_tree(FileTreeNode)` value of the +/// pre-run disk state. It's stored alongside the manifest so the post-run +/// comparison can be done without having to re-materialize the pre-run +/// state from blobs — we just re-scan disk, hash the result, and compare. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct PendingSnapshot { + pub pre_run_tree_hash: String, + pub manifest: Manifest, +} + +/// Store bound to a single cowork folder session. +pub struct SnapshotStore { + session_dir: PathBuf, +} + +impl SnapshotStore { + /// Resolve the per-session snapshot directory from the app handle. Does + /// not create any files — callers hit [`Self::snapshot`], [`Self::apply`], + /// etc. which lazy-create subdirs as needed. + pub fn for_session(app: &AppHandle, session_id: &str) -> Result { + let session_dir = folder_snapshot_session_dir(app, session_id)?; + Ok(Self { session_dir }) + } + + /// Test-only constructor: bypasses the Tauri app handle and uses the + /// provided directory directly. + #[cfg(test)] + pub fn from_dir(session_dir: PathBuf) -> Self { + Self { session_dir } + } + + fn blobs_dir(&self) -> PathBuf { + self.session_dir.join(BLOBS_DIR_NAME) + } + + fn snapshots_dir(&self) -> PathBuf { + self.session_dir.join(SNAPSHOTS_DIR_NAME) + } + + fn timeline_path(&self) -> PathBuf { + self.session_dir.join(TIMELINE_FILE_NAME) + } + + fn timeline_tmp_path(&self) -> PathBuf { + self.session_dir.join(TIMELINE_TMP_FILE_NAME) + } + + fn pending_path(&self) -> PathBuf { + self.session_dir.join(PENDING_FILE_NAME) + } + + fn pending_tmp_path(&self) -> PathBuf { + self.session_dir.join(PENDING_TMP_FILE_NAME) + } + + fn manifest_path(&self, snapshot_id: &str) -> PathBuf { + self.snapshots_dir().join(format!("{snapshot_id}.json")) + } + + fn manifest_tmp_path(&self, snapshot_id: &str) -> PathBuf { + self.snapshots_dir().join(format!("{snapshot_id}.json.tmp")) + } + + fn stat_cache_path(&self) -> PathBuf { + self.session_dir.join(STAT_CACHE_FILE_NAME) + } + + fn stat_cache_tmp_path(&self) -> PathBuf { + self.session_dir.join(STAT_CACHE_TMP_FILE_NAME) + } + + /// Absolute path to a blob's on-disk location. All blob files are + /// zstd-compressed (`.zst` suffix); the sha256 in the filename + /// always refers to the **uncompressed** contents so dedup keys + /// stay consistent with caller-visible hashes. + fn blob_path_for(&self, hash: &str) -> PathBuf { + // Shard by first 2 hex chars to keep any one directory small. + let mut path = self.blobs_dir(); + path.push(&hash[..2]); + path.push(format!("{hash}{BLOB_SUFFIX}")); + path + } + + /// Load the persisted stat cache for this session. Returns an empty + /// cache when the file is missing or corrupt — the next scan will + /// re-hash everything and rebuild it from scratch, which is slower + /// but always correct. + pub fn read_stat_cache(&self) -> StatCache { + let path = self.stat_cache_path(); + if !path.exists() { + return StatCache::default(); + } + match fs::read_to_string(&path) { + Ok(contents) => serde_json::from_str(&contents).unwrap_or_else(|error| { + eprintln!( + "[cowork] stat-cache: failed to parse {} ({}), resetting", + path.display(), + error + ); + StatCache::default() + }), + Err(error) => { + eprintln!( + "[cowork] stat-cache: failed to read {} ({}), resetting", + path.display(), + error + ); + StatCache::default() + } + } + } + + /// Atomically persist the stat cache. Best-effort — a failure here + /// is logged but not propagated, because losing the cache only + /// degrades snapshot speed, never correctness. + pub fn write_stat_cache(&self, cache: &StatCache) { + if let Err(error) = self.try_write_stat_cache(cache) { + eprintln!( + "[cowork] stat-cache: failed to persist cache: {}", + error + ); + } + } + + fn try_write_stat_cache(&self, cache: &StatCache) -> Result<(), String> { + fs::create_dir_all(&self.session_dir).map_err(|error| { + format!( + "Failed to create session dir {}: {}", + self.session_dir.display(), + error + ) + })?; + let tmp_path = self.stat_cache_tmp_path(); + let serialized = serde_json::to_vec(cache).map_err(|error| { + format!("Failed to serialize stat cache: {}", error) + })?; + fs::write(&tmp_path, serialized).map_err(|error| { + format!( + "Failed to write stat cache tmp file {}: {}", + tmp_path.display(), + error + ) + })?; + fs::rename(&tmp_path, self.stat_cache_path()).map_err(|error| { + format!( + "Failed to commit stat cache to {}: {}", + self.stat_cache_path().display(), + error + ) + }) + } + + /// Walk `source_root` recursively, copy every unique file into the + /// blob pool, and return an in-memory manifest. The returned + /// manifest is not persisted yet — the caller chooses whether to + /// commit it via [`Self::push_snapshot`] or discard it by calling + /// [`Self::gc_unreferenced_blobs`] without referencing its blobs + /// anywhere. + /// + /// Two storage optimizations are applied during the walk: + /// + /// 1. **Stat cache** avoids hashing files whose `(mtime, size)` + /// haven't changed since the last scan, reusing the previously + /// computed sha256. Saves ~50× snapshot time on subsequent runs. + /// 2. **Zstd compression** shrinks text-heavy blobs 60-80% (applied + /// inside [`Self::store_blob`]). + pub fn snapshot(&self, source_root: &Path) -> Result { + if !source_root.exists() { + return Err(format!( + "Snapshot source does not exist: {}", + source_root.display() + )); + } + if !source_root.is_dir() { + return Err(format!( + "Snapshot source is not a directory: {}", + source_root.display() + )); + } + + fs::create_dir_all(self.blobs_dir()).map_err(|error| { + format!( + "Failed to create snapshot blobs dir {}: {}", + self.blobs_dir().display(), + error + ) + })?; + + let previous_cache = self.read_stat_cache(); + let mut next_cache = StatCache::default(); + let mut entries = Vec::new(); + walk_source_tree( + source_root, + source_root, + &mut entries, + self, + &previous_cache, + &mut next_cache, + )?; + // Sort pre-order by rel_path so manifests are deterministic regardless + // of OS `read_dir` ordering. + entries.sort_by(|left, right| left.rel_path().cmp(right.rel_path())); + + // Best-effort persist the refreshed cache for the next scan. + self.write_stat_cache(&next_cache); + + Ok(Manifest { entries }) + } + + /// Read the persisted timeline. Returns an empty timeline (cursor=0, + /// snapshots=[]) if `timeline.json` does not exist. Applies a clamp + /// to `cursor` so a corrupted/hand-edited file can't break callers + /// that assume `cursor` is a valid index. + pub fn read_timeline(&self) -> Result { + let timeline_path = self.timeline_path(); + if !timeline_path.exists() { + return Ok(Timeline::default()); + } + + let contents = fs::read_to_string(&timeline_path).map_err(|error| { + format!( + "Failed to read timeline {}: {}", + timeline_path.display(), + error + ) + })?; + + let timeline: Timeline = serde_json::from_str(&contents).map_err(|error| { + format!( + "Failed to parse timeline {}: {}", + timeline_path.display(), + error + ) + })?; + + Ok(timeline.sanitized()) + } + + /// Atomically replace `timeline.json` on disk. Writes to a sibling + /// `.tmp` file first and then renames into place so readers never + /// see partial content. + pub fn write_timeline(&self, timeline: &Timeline) -> Result<(), String> { + fs::create_dir_all(&self.session_dir).map_err(|error| { + format!( + "Failed to create session snapshot dir {}: {}", + self.session_dir.display(), + error + ) + })?; + + let tmp_path = self.timeline_tmp_path(); + let serialized = serde_json::to_vec_pretty(timeline).map_err(|error| { + format!("Failed to serialize timeline: {}", error) + })?; + fs::write(&tmp_path, serialized).map_err(|error| { + format!( + "Failed to write timeline tmp file {}: {}", + tmp_path.display(), + error + ) + })?; + fs::rename(&tmp_path, self.timeline_path()).map_err(|error| { + format!( + "Failed to commit timeline to {}: {}", + self.timeline_path().display(), + error + ) + })?; + + Ok(()) + } + + /// Read a snapshot manifest by id. Used when Undo/Redo needs to + /// materialize a specific timeline entry on disk. + pub fn read_manifest(&self, snapshot_id: &str) -> Result { + let path = self.manifest_path(snapshot_id); + let contents = fs::read_to_string(&path).map_err(|error| { + format!( + "Failed to read snapshot manifest {}: {}", + path.display(), + error + ) + })?; + serde_json::from_str(&contents).map_err(|error| { + format!( + "Failed to parse snapshot manifest {}: {}", + path.display(), + error + ) + }) + } + + /// Atomically write a snapshot manifest. Caller is responsible for + /// choosing a unique `snapshot_id`. + fn write_manifest( + &self, + snapshot_id: &str, + manifest: &Manifest, + ) -> Result<(), String> { + fs::create_dir_all(self.snapshots_dir()).map_err(|error| { + format!( + "Failed to create snapshots dir {}: {}", + self.snapshots_dir().display(), + error + ) + })?; + + let tmp_path = self.manifest_tmp_path(snapshot_id); + let serialized = serde_json::to_vec_pretty(manifest).map_err(|error| { + format!("Failed to serialize manifest {snapshot_id}: {error}") + })?; + fs::write(&tmp_path, serialized).map_err(|error| { + format!( + "Failed to write manifest tmp file {}: {}", + tmp_path.display(), + error + ) + })?; + fs::rename(&tmp_path, self.manifest_path(snapshot_id)).map_err(|error| { + format!( + "Failed to commit manifest {}: {}", + self.manifest_path(snapshot_id).display(), + error + ) + })?; + + Ok(()) + } + + /// Delete a snapshot manifest file. Idempotent — a missing file is a + /// no-op. Does **not** touch blobs; the caller is expected to follow + /// up with `gc_unreferenced_blobs()` after a batch of deletions so + /// the pool stays in sync with the remaining timeline entries. + pub fn delete_manifest(&self, snapshot_id: &str) -> Result<(), String> { + let path = self.manifest_path(snapshot_id); + if path.exists() { + fs::remove_file(&path).map_err(|error| { + format!( + "Failed to delete manifest {}: {}", + path.display(), + error + ) + })?; + } + Ok(()) + } + + /// Persist a new `Manifest` as a fresh snapshot and return its + /// metadata. The caller is responsible for splicing the returned + /// [`SnapshotMeta`] into the timeline and writing it back via + /// [`Self::write_timeline`]. + /// + /// `disk_tree_hash` should be the `hash_tree` of the file-tree that + /// this manifest represents, so the post-run "does disk still match + /// this snapshot?" check can be a pure string compare. + pub fn push_snapshot( + &self, + manifest: &Manifest, + disk_tree_hash: String, + ) -> Result { + let id = new_snapshot_id(); + self.write_manifest(&id, manifest)?; + Ok(SnapshotMeta { + id, + created_at: Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true), + disk_tree_hash, + }) + } + + /// Atomically write the pre-run pending marker. Called at the moment + /// a new run starts, to capture the disk state that the post-run hook + /// can later compare against and either commit (disk changed) or + /// discard (disk unchanged). + /// + /// Safe to call when a `pending.json` already exists — it overwrites. + /// The caller decides the overwrite policy (session_gateway checks + /// "does pending already exist?" to make the pre-run hook idempotent + /// within a single run while still allowing chained runs to + /// re-snapshot). + pub fn write_pending(&self, pending: &PendingSnapshot) -> Result<(), String> { + fs::create_dir_all(&self.session_dir).map_err(|error| { + format!( + "Failed to create session snapshot dir {}: {}", + self.session_dir.display(), + error + ) + })?; + + let tmp_path = self.pending_tmp_path(); + let serialized = serde_json::to_vec_pretty(pending).map_err(|error| { + format!("Failed to serialize pending snapshot: {}", error) + })?; + fs::write(&tmp_path, serialized).map_err(|error| { + format!( + "Failed to write pending snapshot tmp file {}: {}", + tmp_path.display(), + error + ) + })?; + fs::rename(&tmp_path, self.pending_path()).map_err(|error| { + format!( + "Failed to commit pending snapshot to {}: {}", + self.pending_path().display(), + error + ) + })?; + + Ok(()) + } + + /// Read the pending marker if it exists. Returns `Ok(None)` when + /// there's nothing pending (normal case: no run has started since the + /// last `take_pending` call). + pub fn read_pending(&self) -> Result, String> { + let pending_path = self.pending_path(); + if !pending_path.exists() { + return Ok(None); + } + + let contents = fs::read_to_string(&pending_path).map_err(|error| { + format!( + "Failed to read pending snapshot {}: {}", + pending_path.display(), + error + ) + })?; + + let pending: PendingSnapshot = serde_json::from_str(&contents).map_err(|error| { + format!( + "Failed to parse pending snapshot {}: {}", + pending_path.display(), + error + ) + })?; + + Ok(Some(pending)) + } + + /// Remove the pending marker. Idempotent — does nothing if already + /// absent. Does NOT touch blobs; callers should follow up with + /// `gc_unreferenced_blobs()` to reclaim the pending snapshot's blobs + /// if they're not referenced by `slot.json`. + pub fn clear_pending(&self) -> Result<(), String> { + let pending_path = self.pending_path(); + if pending_path.exists() { + fs::remove_file(&pending_path).map_err(|error| { + format!( + "Failed to remove pending snapshot {}: {}", + pending_path.display(), + error + ) + })?; + } + Ok(()) + } + + /// Materialise the given manifest onto `source_root`: files on disk + /// that are not in the manifest get removed, files missing on disk + /// (or whose sha256 doesn't match) get rewritten from the blob pool, + /// and directories/symlinks are created as needed. + /// + /// This is a pure "make disk match manifest" operation — it does + /// **not** touch the timeline or pending marker. The caller + /// (`undo_commands` / session_gateway) is responsible for updating + /// the timeline cursor after a successful apply. + pub fn apply_manifest( + &self, + manifest: &Manifest, + source_root: &Path, + ) -> Result<(), String> { + if !source_root.exists() { + fs::create_dir_all(source_root).map_err(|error| { + format!( + "Failed to recreate source root {}: {}", + source_root.display(), + error + ) + })?; + } + + // Build set of rel_paths the manifest wants on disk. + let wanted: HashSet<&str> = + manifest.entries.iter().map(|entry| entry.rel_path()).collect(); + + // Walk current disk, delete anything not in `wanted`. Walk files + // first, directories second (bottom-up) so non-empty dir removal + // works. + let mut disk_files = Vec::new(); + let mut disk_dirs = Vec::new(); + let mut disk_symlinks = Vec::new(); + collect_disk_entries( + source_root, + source_root, + &mut disk_files, + &mut disk_dirs, + &mut disk_symlinks, + )?; + + for (rel_path, abs_path) in &disk_symlinks { + if !wanted.contains(rel_path.as_str()) { + let _ = fs::remove_file(abs_path); + } + } + for (rel_path, abs_path) in &disk_files { + if !wanted.contains(rel_path.as_str()) { + fs::remove_file(abs_path).map_err(|error| { + format!( + "Failed to remove {} during snapshot apply: {}", + abs_path.display(), + error + ) + })?; + } + } + // Sort dirs longest-first so we remove children before parents. + disk_dirs.sort_by(|left, right| right.0.len().cmp(&left.0.len())); + for (rel_path, abs_path) in &disk_dirs { + if !wanted.contains(rel_path.as_str()) { + // Only remove if empty after file cleanup. If it's still + // populated (e.g. nested wanted file survived), skip. + let _ = fs::remove_dir(abs_path); + } + } + + // Now materialise the manifest: create dirs, write files, create + // symlinks. We walk `manifest.entries` in pre-order (already + // sorted by rel_path, which is lexicographic and close enough to + // pre-order for parent-before-child — we double-guard by + // `create_dir_all` for files). + for entry in &manifest.entries { + let rel_path = entry.rel_path(); + let abs_path = source_root.join(sanitize_rel_path(rel_path)?); + + match entry { + ManifestEntry::Dir { .. } => { + fs::create_dir_all(&abs_path).map_err(|error| { + format!( + "Failed to create directory {}: {}", + abs_path.display(), + error + ) + })?; + } + ManifestEntry::File { sha256, .. } => { + // Skip rewrite if disk already matches (saves I/O). + if file_matches_hash(&abs_path, sha256).unwrap_or(false) { + continue; + } + + if let Some(parent) = abs_path.parent() { + fs::create_dir_all(parent).map_err(|error| { + format!( + "Failed to create parent dir {}: {}", + parent.display(), + error + ) + })?; + } + + let bytes = self.read_blob(sha256)?; + + // Atomic write via tmp + rename. Use a dotfile tmp name + // alongside the target so it shares the same filesystem. + let tmp_path = tmp_sibling(&abs_path); + fs::write(&tmp_path, bytes).map_err(|error| { + format!( + "Failed to write tmp file {}: {}", + tmp_path.display(), + error + ) + })?; + fs::rename(&tmp_path, &abs_path).map_err(|error| { + format!( + "Failed to commit file {}: {}", + abs_path.display(), + error + ) + })?; + } + ManifestEntry::Symlink { target, .. } => { + // Remove any existing entry at that path first so we + // don't clash with an existing regular file. + let _ = fs::remove_file(&abs_path); + if let Some(parent) = abs_path.parent() { + fs::create_dir_all(parent).map_err(|error| { + format!( + "Failed to create parent dir {}: {}", + parent.display(), + error + ) + })?; + } + create_symlink(target, &abs_path)?; + } + } + } + + Ok(()) + } + + /// Remove any blob files that are not referenced by: + /// + /// - any manifest in the current timeline, OR + /// - the in-flight pending marker (if any). + /// + /// Also cleans up orphaned `snapshots/*.json` files whose IDs are + /// no longer in the timeline (left behind by a half-completed + /// truncation, retention drop, or crash). + /// + /// Idempotent: safe to call more than once. + pub fn gc_unreferenced_blobs(&self) -> Result<(), String> { + // First, collect every sha256 that is reachable from the live + // timeline + pending marker. This is the "keep set". + let mut referenced: HashSet = HashSet::new(); + + let timeline = self.read_timeline()?; + let alive_ids: HashSet = + timeline.snapshots.iter().map(|s| s.id.clone()).collect(); + + for snapshot in &timeline.snapshots { + // Best-effort: if a manifest is unreadable (e.g. corrupted), + // we log and keep GC running — better to leak blobs than to + // wipe the pool outright. + match self.read_manifest(&snapshot.id) { + Ok(manifest) => { + for entry in &manifest.entries { + if let ManifestEntry::File { sha256, .. } = entry { + referenced.insert(sha256.clone()); + } + } + } + Err(error) => { + eprintln!( + "[cowork] gc: failed to read manifest {} during scan: {}", + snapshot.id, error + ); + } + } + } + + if let Some(pending) = self.read_pending()? { + for entry in &pending.manifest.entries { + if let ManifestEntry::File { sha256, .. } = entry { + referenced.insert(sha256.clone()); + } + } + } + + // Clean up orphan manifest files (IDs not in the timeline). + if let Ok(entries) = fs::read_dir(self.snapshots_dir()) { + for entry in entries.flatten() { + let path = entry.path(); + let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + // Strip `.json` / `.json.tmp` and check membership in + // `alive_ids`. `.tmp` leftovers are always orphans. + if file_name.ends_with(".json.tmp") { + let _ = fs::remove_file(&path); + continue; + } + let Some(id) = file_name.strip_suffix(".json") else { + continue; + }; + if !alive_ids.contains(id) { + let _ = fs::remove_file(&path); + } + } + } + + let blobs_dir = self.blobs_dir(); + if !blobs_dir.exists() { + return Ok(()); + } + + let shard_entries = match fs::read_dir(&blobs_dir) { + Ok(iter) => iter, + Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()), + Err(error) => { + return Err(format!( + "Failed to scan blobs dir {}: {}", + blobs_dir.display(), + error + )) + } + }; + + for shard_entry in shard_entries.flatten() { + let shard_path = shard_entry.path(); + if !shard_path.is_dir() { + continue; + } + + let blob_iter = match fs::read_dir(&shard_path) { + Ok(iter) => iter, + Err(_) => continue, + }; + for blob_entry in blob_iter.flatten() { + let blob_path = blob_entry.path(); + let Some(file_name) = blob_path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + // Skip any `.tmp` leftovers from interrupted writes — + // they're orphaned tmp files from a crashed snapshot, not + // valid blobs. Clean them up. + if file_name.ends_with(".tmp") { + let _ = fs::remove_file(&blob_path); + continue; + } + // Strip the compression suffix to recover the canonical + // sha256 key before looking it up in the reference set. + // Legacy (uncompressed) blobs would have no suffix — we + // treat those as orphans and sweep them too, since the + // current code only writes `.zst` files. + let Some(hash) = file_name.strip_suffix(BLOB_SUFFIX) else { + let _ = fs::remove_file(&blob_path); + continue; + }; + if !referenced.contains(hash) { + let _ = fs::remove_file(&blob_path); + } + } + + // Remove now-empty shard dir. + if fs::read_dir(&shard_path) + .map(|mut iter| iter.next().is_none()) + .unwrap_or(false) + { + let _ = fs::remove_dir(&shard_path); + } + } + + Ok(()) + } + + /// Copy a single file's bytes into the blob pool, returning its sha256. + /// If the blob already exists, we skip the write. + /// Hash, compress, and atomically write a file's contents into the + /// CAS pool. Returns the sha256 of the **uncompressed** bytes (the + /// CAS key). If an identical blob already exists, skip the write. + /// + /// Blobs on disk are always zstd-compressed. The compression + /// happens at store time; consumers use [`Self::read_blob`] to get + /// the original bytes back. + fn store_blob(&self, bytes: &[u8]) -> Result { + let hash = sha256_hex(bytes); + let blob_path = self.blob_path_for(&hash); + if blob_path.exists() { + return Ok(hash); + } + + if let Some(parent) = blob_path.parent() { + fs::create_dir_all(parent).map_err(|error| { + format!( + "Failed to create blob shard dir {}: {}", + parent.display(), + error + ) + })?; + } + + let compressed = zstd::encode_all(bytes, ZSTD_LEVEL).map_err(|error| { + format!( + "Failed to zstd-compress blob {}: {}", + hash, error + ) + })?; + + // Append `.tmp` to the full filename (not set_extension, which + // would replace `.zst` and lose the suffix info). + let tmp_path = { + let parent = blob_path.parent().unwrap_or_else(|| Path::new(".")); + let file_name = blob_path + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_else(|| format!("{hash}{BLOB_SUFFIX}")); + parent.join(format!("{file_name}.tmp")) + }; + fs::write(&tmp_path, &compressed).map_err(|error| { + format!( + "Failed to write blob tmp file {}: {}", + tmp_path.display(), + error + ) + })?; + fs::rename(&tmp_path, &blob_path).map_err(|error| { + format!( + "Failed to commit blob {}: {}", + blob_path.display(), + error + ) + })?; + Ok(hash) + } + + /// Load a blob from the CAS pool and decompress it. Returns the + /// original uncompressed bytes. Used by [`Self::apply_manifest`] + /// when materializing a snapshot onto disk. + fn read_blob(&self, hash: &str) -> Result, String> { + let blob_path = self.blob_path_for(hash); + let compressed = fs::read(&blob_path).map_err(|error| { + format!( + "Failed to read blob {}: {}", + blob_path.display(), + error + ) + })?; + zstd::decode_all(compressed.as_slice()).map_err(|error| { + format!( + "Failed to zstd-decompress blob {}: {}", + blob_path.display(), + error + ) + }) + } +} + +// ============================================================================ +// Helpers +// ============================================================================ + +fn walk_source_tree( + root: &Path, + current: &Path, + entries: &mut Vec, + store: &SnapshotStore, + previous_cache: &StatCache, + next_cache: &mut StatCache, +) -> Result<(), String> { + let read_dir = fs::read_dir(current).map_err(|error| { + format!( + "Failed to read directory {} during snapshot: {}", + current.display(), + error + ) + })?; + + for entry_result in read_dir { + let entry = entry_result.map_err(|error| { + format!( + "Failed to iterate {} during snapshot: {}", + current.display(), + error + ) + })?; + let abs_path = entry.path(); + let metadata = fs::symlink_metadata(&abs_path).map_err(|error| { + format!( + "Failed to stat {} during snapshot: {}", + abs_path.display(), + error + ) + })?; + + let rel_path = rel_path_string(root, &abs_path)?; + let file_type = metadata.file_type(); + + if file_type.is_symlink() { + let target = fs::read_link(&abs_path) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default(); + entries.push(ManifestEntry::Symlink { rel_path, target }); + continue; + } + + if file_type.is_dir() { + entries.push(ManifestEntry::Dir { + rel_path: rel_path.clone(), + }); + walk_source_tree(root, &abs_path, entries, store, previous_cache, next_cache)?; + continue; + } + + if file_type.is_file() { + let size = metadata.len(); + let mtime_nanos = metadata + .modified() + .ok() + .and_then(|mtime| mtime.duration_since(UNIX_EPOCH).ok()) + .map(|dur| dur.as_nanos()) + .unwrap_or(0); + + // Stat cache fast path: if the previous scan recorded + // this file with matching (mtime, size), reuse its sha256 + // and skip both the `fs::read` and the SHA-256 computation. + // We still verify the blob is on disk — GC could have + // dropped it — before trusting the cache entry. + let cached = previous_cache.entries.get(&rel_path); + let reuse_hash = match cached { + Some(entry) + if entry.mtime_nanos == mtime_nanos + && entry.size == size + && store.blob_path_for(&entry.sha256).exists() => + { + Some(entry.sha256.clone()) + } + _ => None, + }; + + let sha256 = if let Some(hash) = reuse_hash { + hash + } else { + let bytes = fs::read(&abs_path).map_err(|error| { + format!( + "Failed to read file {} during snapshot: {}", + abs_path.display(), + error + ) + })?; + store.store_blob(&bytes)? + }; + + next_cache.entries.insert( + rel_path.clone(), + StatCacheEntry { + mtime_nanos, + size, + sha256: sha256.clone(), + }, + ); + entries.push(ManifestEntry::File { rel_path, sha256 }); + continue; + } + + // Unknown file type (socket, device, …) — skip and log. We don't + // want to fail the whole snapshot for one weird entry. + eprintln!( + "[cowork] snapshot skipping non-regular entry {} (unknown file type)", + abs_path.display() + ); + } + + Ok(()) +} + +fn collect_disk_entries( + root: &Path, + current: &Path, + files: &mut Vec<(String, PathBuf)>, + dirs: &mut Vec<(String, PathBuf)>, + symlinks: &mut Vec<(String, PathBuf)>, +) -> Result<(), String> { + let read_dir = match fs::read_dir(current) { + Ok(iter) => iter, + Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()), + Err(error) => { + return Err(format!( + "Failed to read directory {}: {}", + current.display(), + error + )); + } + }; + + for entry_result in read_dir { + let entry = entry_result.map_err(|error| { + format!( + "Failed to iterate {}: {}", + current.display(), + error + ) + })?; + let abs_path = entry.path(); + let metadata = match fs::symlink_metadata(&abs_path) { + Ok(m) => m, + Err(_) => continue, + }; + let rel_path = rel_path_string(root, &abs_path)?; + let file_type = metadata.file_type(); + + if file_type.is_symlink() { + symlinks.push((rel_path, abs_path)); + } else if file_type.is_dir() { + dirs.push((rel_path.clone(), abs_path.clone())); + collect_disk_entries(root, &abs_path, files, dirs, symlinks)?; + } else if file_type.is_file() { + files.push((rel_path, abs_path)); + } + } + + Ok(()) +} + +fn rel_path_string(root: &Path, abs_path: &Path) -> Result { + let rel = abs_path.strip_prefix(root).map_err(|error| { + format!( + "Path {} is not within root {}: {}", + abs_path.display(), + root.display(), + error + ) + })?; + Ok(rel.to_string_lossy().replace('\\', "/")) +} + +fn sanitize_rel_path(rel_path: &str) -> Result { + // Reject anything that would escape the root. + let path = PathBuf::from(rel_path); + for component in path.components() { + match component { + Component::Normal(_) | Component::CurDir => {} + _ => { + return Err(format!( + "Manifest rel_path {rel_path:?} contains an unsafe component" + )) + } + } + } + Ok(path) +} + +/// Generate a fresh snapshot ID. Format: `{epoch_millis}-{rand_hex}`. +/// The leading timestamp means lexicographic sort matches creation +/// order, which is the property the [`Timeline::snapshots`] list needs +/// when we do GC by "files in `snapshots/` dir not in timeline". +/// The trailing random hex (8 chars of the current-time nanos hash) +/// prevents collisions if two runs finish in the same millisecond. +fn new_snapshot_id() -> String { + let now = Utc::now(); + let millis = now.timestamp_millis(); + let nanos = now.timestamp_subsec_nanos(); + let rand_tag: String = Sha256::digest(nanos.to_le_bytes()) + .iter() + .take(4) + .map(|b| format!("{b:02x}")) + .collect(); + format!("{millis:013}-{rand_tag}") +} + +fn sha256_hex(bytes: &[u8]) -> String { + let digest = Sha256::digest(bytes); + digest.iter().map(|value| format!("{value:02x}")).collect() +} + +fn file_matches_hash(abs_path: &Path, expected: &str) -> Result { + if !abs_path.exists() { + return Ok(false); + } + let metadata = match fs::symlink_metadata(abs_path) { + Ok(m) => m, + Err(_) => return Ok(false), + }; + if !metadata.is_file() { + return Ok(false); + } + + // Stream-hash so we don't allocate huge Vecs for large files we only + // need to compare. + let mut file = fs::File::open(abs_path).map_err(|error| { + format!("Failed to open {} for hash compare: {}", abs_path.display(), error) + })?; + let mut hasher = Sha256::new(); + let mut buffer = [0u8; 64 * 1024]; + loop { + let n = file.read(&mut buffer).map_err(|error| { + format!( + "Failed to read {} during hash compare: {}", + abs_path.display(), + error + ) + })?; + if n == 0 { + break; + } + hasher.update(&buffer[..n]); + } + let hash_hex: String = hasher + .finalize() + .iter() + .map(|value| format!("{value:02x}")) + .collect(); + Ok(hash_hex == expected) +} + +fn tmp_sibling(abs_path: &Path) -> PathBuf { + let parent = abs_path.parent().unwrap_or_else(|| Path::new(".")); + let file_name = abs_path + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_default(); + parent.join(format!(".{file_name}.cowork-tmp")) +} + +#[cfg(unix)] +fn create_symlink(target: &str, link_path: &Path) -> Result<(), String> { + use std::os::unix::fs::symlink; + symlink(target, link_path).map_err(|error| { + format!( + "Failed to create symlink {} → {}: {}", + link_path.display(), + target, + error + ) + }) +} + +#[cfg(windows)] +fn create_symlink(target: &str, link_path: &Path) -> Result<(), String> { + // On Windows we can't know for sure whether the target was a file or + // directory originally. Best effort: try file first, fall back to dir. + use std::os::windows::fs::{symlink_dir, symlink_file}; + if let Err(file_err) = symlink_file(target, link_path) { + if let Err(dir_err) = symlink_dir(target, link_path) { + return Err(format!( + "Failed to create symlink {} → {} (file: {}, dir: {})", + link_path.display(), + target, + file_err, + dir_err + )); + } + } + Ok(()) +} + +#[cfg(not(any(unix, windows)))] +fn create_symlink(_target: &str, _link_path: &Path) -> Result<(), String> { + Err("Symlinks are not supported on this platform".to_string()) +} + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use std::{ + fs, + time::{SystemTime, UNIX_EPOCH}, + }; + + struct TestDirs { + src: PathBuf, + store: PathBuf, + } + + impl Drop for TestDirs { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.src); + let _ = fs::remove_dir_all(&self.store); + } + } + + fn make_test_dirs(tag: &str) -> TestDirs { + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after unix epoch") + .as_nanos(); + let src = std::env::temp_dir().join(format!("ii-agent-snapshot-src-{tag}-{unique}")); + let store = std::env::temp_dir().join(format!("ii-agent-snapshot-store-{tag}-{unique}")); + fs::create_dir_all(&src).unwrap(); + fs::create_dir_all(&store).unwrap(); + TestDirs { src, store } + } + + fn write(path: &Path, contents: &[u8]) { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).unwrap(); + } + fs::write(path, contents).unwrap(); + } + + fn read_all_files_sorted(root: &Path) -> Vec<(String, Vec)> { + let mut out = Vec::new(); + let mut files = Vec::new(); + let mut dirs = Vec::new(); + let mut syms = Vec::new(); + collect_disk_entries(root, root, &mut files, &mut dirs, &mut syms).unwrap(); + for (rel, abs) in files { + out.push((rel, fs::read(abs).unwrap())); + } + out.sort_by(|a, b| a.0.cmp(&b.0)); + out + } + + /// Helper to snapshot current disk and immediately push it onto the + /// timeline. Mirrors what `session_gateway::push_post_run_snapshot` + /// does in production, simplified for tests. + fn snapshot_and_push(store: &SnapshotStore, src: &Path, tag: &str) { + let manifest = store.snapshot(src).unwrap(); + let meta = store.push_snapshot(&manifest, format!("tree-hash-{tag}")).unwrap(); + let mut timeline = store.read_timeline().unwrap(); + timeline.snapshots.push(meta); + timeline.cursor = timeline.snapshots.len() - 1; + store.write_timeline(&timeline).unwrap(); + } + + #[test] + fn snapshot_then_apply_roundtrip() { + let dirs = make_test_dirs("roundtrip"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("a.txt"), b"hello"); + write(&dirs.src.join("sub/b.txt"), b"world"); + write(&dirs.src.join("sub/nested/c.txt"), b"nested"); + + let manifest = store.snapshot(&dirs.src).unwrap(); + let original = read_all_files_sorted(&dirs.src); + + // Mutate disk in various ways: modify, delete, add. + write(&dirs.src.join("a.txt"), b"hello-modified"); + fs::remove_file(dirs.src.join("sub/b.txt")).unwrap(); + write(&dirs.src.join("sub/added.txt"), b"brand new"); + fs::remove_file(dirs.src.join("sub/nested/c.txt")).unwrap(); + fs::remove_dir(dirs.src.join("sub/nested")).unwrap(); + + // apply_manifest restores from the in-memory manifest. + store.apply_manifest(&manifest, &dirs.src).unwrap(); + + let restored = read_all_files_sorted(&dirs.src); + assert_eq!( + restored, original, + "applying the manifest should restore disk byte-for-byte" + ); + } + + #[test] + fn timeline_push_and_navigate() { + let dirs = make_test_dirs("timeline-nav"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + // Three states, pushed in order: A, B, C. + write(&dirs.src.join("file.txt"), b"state-A"); + snapshot_and_push(&store, &dirs.src, "A"); + let state_a = read_all_files_sorted(&dirs.src); + + write(&dirs.src.join("file.txt"), b"state-B"); + write(&dirs.src.join("new-in-b.txt"), b"only-in-B"); + snapshot_and_push(&store, &dirs.src, "B"); + let state_b = read_all_files_sorted(&dirs.src); + + write(&dirs.src.join("file.txt"), b"state-C"); + fs::remove_file(dirs.src.join("new-in-b.txt")).unwrap(); + write(&dirs.src.join("new-in-c.txt"), b"only-in-C"); + snapshot_and_push(&store, &dirs.src, "C"); + let state_c = read_all_files_sorted(&dirs.src); + + let timeline = store.read_timeline().unwrap(); + assert_eq!(timeline.snapshots.len(), 3); + assert_eq!(timeline.cursor, 2); + assert!(timeline.can_undo()); + assert!(!timeline.can_redo()); + + // Walk back: C -> B -> A via apply_manifest + cursor updates. + let manifest_b = store.read_manifest(&timeline.snapshots[1].id).unwrap(); + store.apply_manifest(&manifest_b, &dirs.src).unwrap(); + assert_eq!(read_all_files_sorted(&dirs.src), state_b); + + let manifest_a = store.read_manifest(&timeline.snapshots[0].id).unwrap(); + store.apply_manifest(&manifest_a, &dirs.src).unwrap(); + assert_eq!(read_all_files_sorted(&dirs.src), state_a); + + // Walk forward: A -> C. + let manifest_c = store.read_manifest(&timeline.snapshots[2].id).unwrap(); + store.apply_manifest(&manifest_c, &dirs.src).unwrap(); + assert_eq!(read_all_files_sorted(&dirs.src), state_c); + } + + #[test] + fn gc_preserves_all_timeline_blobs() { + let dirs = make_test_dirs("gc-timeline"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + // Two distinct states with different file contents. + write(&dirs.src.join("a.txt"), b"v1"); + snapshot_and_push(&store, &dirs.src, "v1"); + + write(&dirs.src.join("a.txt"), b"v2"); + snapshot_and_push(&store, &dirs.src, "v2"); + + store.gc_unreferenced_blobs().unwrap(); + + // Both blobs referenced by the two snapshots must survive GC. + let v1_hash = sha256_hex(b"v1"); + let v2_hash = sha256_hex(b"v2"); + assert!(store.blob_path_for(&v1_hash).exists(), "v1 blob must survive"); + assert!(store.blob_path_for(&v2_hash).exists(), "v2 blob must survive"); + } + + #[test] + fn gc_drops_orphaned_manifest_files() { + let dirs = make_test_dirs("gc-orphan"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("a.txt"), b"only"); + snapshot_and_push(&store, &dirs.src, "only"); + + // Write a stray manifest file that isn't in the timeline. + let orphan_id = "00000000000-abcd"; + let manifest = store.snapshot(&dirs.src).unwrap(); + store.write_manifest(orphan_id, &manifest).unwrap(); + assert!(store.manifest_path(orphan_id).exists()); + + store.gc_unreferenced_blobs().unwrap(); + assert!( + !store.manifest_path(orphan_id).exists(), + "orphaned manifest file should be deleted by GC" + ); + } + + #[test] + fn gc_drops_blobs_no_longer_in_timeline() { + let dirs = make_test_dirs("gc-drop"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("keep.txt"), b"keep"); + write(&dirs.src.join("drop.txt"), b"drop-me"); + + // First snapshot has both files. + snapshot_and_push(&store, &dirs.src, "both"); + + // Remove drop.txt from disk and take a fresh snapshot. If we + // simulate the retention scenario by discarding the first + // snapshot and keeping only the second, drop-me's blob becomes + // orphaned. + fs::remove_file(dirs.src.join("drop.txt")).unwrap(); + let manifest = store.snapshot(&dirs.src).unwrap(); + let meta = store.push_snapshot(&manifest, "after".to_string()).unwrap(); + + // Simulate retention: drop the oldest snapshot, keep only the new one. + let mut timeline = store.read_timeline().unwrap(); + let dropped = timeline.snapshots.remove(0); + store.delete_manifest(&dropped.id).unwrap(); + timeline.snapshots.push(meta); + timeline.cursor = 0; + store.write_timeline(&timeline).unwrap(); + + store.gc_unreferenced_blobs().unwrap(); + + let drop_hash = sha256_hex(b"drop-me"); + assert!( + !store.blob_path_for(&drop_hash).exists(), + "orphaned blob should be GC'd" + ); + let keep_hash = sha256_hex(b"keep"); + assert!( + store.blob_path_for(&keep_hash).exists(), + "blob still referenced must survive" + ); + } + + #[test] + fn cas_dedup_identical_content() { + let dirs = make_test_dirs("dedup"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("one.txt"), b"same"); + write(&dirs.src.join("two.txt"), b"same"); + write(&dirs.src.join("three.txt"), b"same"); + + snapshot_and_push(&store, &dirs.src, "same"); + + let hash = sha256_hex(b"same"); + let blob_path = store.blob_path_for(&hash); + assert!(blob_path.exists(), "single shared blob should exist"); + + // Count blobs in the shard directory for this hash prefix. + let shard_dir = store.blobs_dir().join(&hash[..2]); + let count = fs::read_dir(&shard_dir).unwrap().count(); + assert_eq!( + count, 1, + "CAS should dedupe identical file contents into a single blob" + ); + } + + #[test] + fn read_timeline_returns_empty_when_missing() { + let dirs = make_test_dirs("missing"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + let timeline = store.read_timeline().unwrap(); + assert!(timeline.snapshots.is_empty()); + assert_eq!(timeline.cursor, 0); + assert!(!timeline.can_undo()); + assert!(!timeline.can_redo()); + } + + #[test] + fn timeline_clamps_cursor_on_load() { + let dirs = make_test_dirs("clamp"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + // Write a corrupted timeline with cursor out of bounds. + write(&dirs.src.join("a.txt"), b"a"); + snapshot_and_push(&store, &dirs.src, "a"); + + let corrupted = Timeline { + snapshots: store.read_timeline().unwrap().snapshots, + cursor: 999, + }; + store.write_timeline(&corrupted).unwrap(); + + // Next read should clamp to last valid index. + let loaded = store.read_timeline().unwrap(); + assert_eq!(loaded.cursor, loaded.snapshots.len() - 1); + } + + #[test] + fn pending_write_read_clear_roundtrip() { + let dirs = make_test_dirs("pending"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("a.txt"), b"pending-content"); + let manifest = store.snapshot(&dirs.src).unwrap(); + let pending = PendingSnapshot { + pre_run_tree_hash: "deadbeef".to_string(), + manifest, + }; + + assert!(store.read_pending().unwrap().is_none()); + + store.write_pending(&pending).unwrap(); + let round_tripped = store.read_pending().unwrap().unwrap(); + assert_eq!(round_tripped.pre_run_tree_hash, "deadbeef"); + + store.clear_pending().unwrap(); + assert!(store.read_pending().unwrap().is_none()); + } + + #[test] + fn gc_preserves_pending_blobs() { + let dirs = make_test_dirs("pending-gc"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("live.txt"), b"lives-in-pending"); + let manifest = store.snapshot(&dirs.src).unwrap(); + let pending = PendingSnapshot { + pre_run_tree_hash: "hash".to_string(), + manifest, + }; + store.write_pending(&pending).unwrap(); + + // No timeline entries, just pending. GC must NOT remove the blob. + store.gc_unreferenced_blobs().unwrap(); + + let hash = sha256_hex(b"lives-in-pending"); + let blob_path = store.blob_path_for(&hash); + assert!( + blob_path.exists(), + "pending-referenced blob must survive GC: {}", + blob_path.display() + ); + } + + #[test] + fn sanitize_rel_path_rejects_traversal() { + assert!(sanitize_rel_path("../escape").is_err()); + assert!(sanitize_rel_path("a/../../b").is_err()); + assert!(sanitize_rel_path("/abs/path").is_err()); + assert!(sanitize_rel_path("ok/sub/file.txt").is_ok()); + } + + // ---- Stat cache ---- + + #[test] + fn stat_cache_reused_on_unchanged_files() { + let dirs = make_test_dirs("stat-cache"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("a.txt"), b"original content"); + write(&dirs.src.join("b.txt"), b"other content"); + + // First snapshot populates the cache with both entries. + let _ = store.snapshot(&dirs.src).unwrap(); + let cache_v1 = store.read_stat_cache(); + assert!(cache_v1.entries.contains_key("a.txt")); + assert!(cache_v1.entries.contains_key("b.txt")); + let a_hash_v1 = cache_v1.entries["a.txt"].sha256.clone(); + + // Delete the blob manually to prove the second snapshot would + // detect a cache hit BUT fall through to re-hash because the + // blob is missing. After the call the blob should be back. + let blob_path = store.blob_path_for(&a_hash_v1); + assert!(blob_path.exists()); + fs::remove_file(&blob_path).unwrap(); + assert!(!blob_path.exists()); + + // Re-snapshot without modifying any file. The cache entries + // should be preserved (same sha256), and the missing blob + // should be repopulated by the fall-through path. + let _ = store.snapshot(&dirs.src).unwrap(); + let cache_v2 = store.read_stat_cache(); + assert_eq!(cache_v2.entries["a.txt"].sha256, a_hash_v1); + assert!( + blob_path.exists(), + "missing blob must be recreated on cache fall-through" + ); + } + + #[test] + fn stat_cache_rehashes_when_mtime_or_size_changes() { + let dirs = make_test_dirs("stat-invalidate"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("f.txt"), b"v1"); + let _ = store.snapshot(&dirs.src).unwrap(); + let hash_v1 = store.read_stat_cache().entries["f.txt"].sha256.clone(); + + // Change content (different size and — typically — different + // mtime). The cache key should invalidate and the new blob + // should have a different hash. + // Brief sleep ensures mtime changes on filesystems with 1s + // resolution — not strictly needed if we also change size. + std::thread::sleep(std::time::Duration::from_millis(15)); + write(&dirs.src.join("f.txt"), b"v2-longer-content"); + let _ = store.snapshot(&dirs.src).unwrap(); + let hash_v2 = store.read_stat_cache().entries["f.txt"].sha256.clone(); + + assert_ne!( + hash_v1, hash_v2, + "stat cache failed to invalidate after content change" + ); + } + + // ---- Zstd compression ---- + + #[test] + fn blobs_are_stored_compressed() { + let dirs = make_test_dirs("zstd"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + // Highly compressible content: repeating pattern. + let payload = "hello world ".repeat(1000); + write(&dirs.src.join("compressible.txt"), payload.as_bytes()); + let _ = store.snapshot(&dirs.src).unwrap(); + + let hash = sha256_hex(payload.as_bytes()); + let blob_path = store.blob_path_for(&hash); + assert!(blob_path.exists()); + + let on_disk = fs::metadata(&blob_path).unwrap().len() as usize; + assert!( + on_disk < payload.len() / 2, + "compressed blob ({on_disk} B) should be much smaller than \ + raw ({raw} B)", + raw = payload.len() + ); + + // Decompress and verify round-trip. + let round_tripped = store.read_blob(&hash).unwrap(); + assert_eq!(round_tripped, payload.as_bytes()); + } + + #[test] + fn apply_manifest_round_trip_with_compression() { + // End-to-end: snapshot → mutate disk → apply_manifest → verify + // the original bytes come back even though everything was + // zstd-compressed in the blob pool. + let dirs = make_test_dirs("zstd-roundtrip"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + let original = b"the quick brown fox jumps over the lazy dog".repeat(100); + write(&dirs.src.join("doc.txt"), &original); + let manifest = store.snapshot(&dirs.src).unwrap(); + + // Corrupt the file on disk. + write(&dirs.src.join("doc.txt"), b"corrupted"); + + // Restore. + store.apply_manifest(&manifest, &dirs.src).unwrap(); + + let restored = fs::read(dirs.src.join("doc.txt")).unwrap(); + assert_eq!( + restored, original, + "zstd decompress + restore must produce byte-exact original" + ); + } + + #[test] + fn blob_filename_uses_zst_suffix() { + let dirs = make_test_dirs("suffix"); + let store = SnapshotStore::from_dir(dirs.store.clone()); + + write(&dirs.src.join("x.txt"), b"any content"); + let _ = store.snapshot(&dirs.src).unwrap(); + + let hash = sha256_hex(b"any content"); + let blob_path = store.blob_path_for(&hash); + let file_name = blob_path + .file_name() + .unwrap() + .to_string_lossy() + .to_string(); + assert!( + file_name.ends_with(".zst"), + "blob filename should end with .zst, got {file_name}" + ); + } +} diff --git a/frontend/src-tauri/src/cowork/intelligent_folder/undo_commands.rs b/frontend/src-tauri/src/cowork/intelligent_folder/undo_commands.rs new file mode 100644 index 000000000..26d8fbd79 --- /dev/null +++ b/frontend/src-tauri/src/cowork/intelligent_folder/undo_commands.rs @@ -0,0 +1,142 @@ +//! Tauri commands for cowork Intelligent Folder undo / redo navigation. +//! +//! The cowork folder session keeps a **timeline** of snapshots (see +//! [`crate::cowork::intelligent_folder::snapshot_store`]) with a `cursor` +//! pointing at whichever snapshot is currently materialised on disk. +//! Undo and Redo simply move the cursor by ±1 and re-apply the +//! corresponding manifest: +//! +//! ```text +//! [snap0] [snap1] [snap2] [snap3] +//! ^ cursor here +//! --> Undo --> cursor = 1, disk = snap1 +//! --> Redo --> cursor = 3, disk = snap3 +//! ``` +//! +//! After a successful swap we refresh the session's UI-facing +//! `undo_state` hint and `result_tree` so the frontend can render the +//! button counter and tree view without a follow-up fetch. + +use super::sessions::{ + self, sync_result_tree_from_disk, CoworkChatSessionDetail, FolderUndoState, +}; +use super::snapshot_store::{SnapshotStore, Timeline}; +use crate::cowork::time_utils::now_iso; +use std::path::Path; +use tauri::AppHandle; + +#[tauri::command] +pub fn undo_cowork_folder( + app: AppHandle, + session_id: String, +) -> Result { + navigate_timeline(&app, &session_id, Direction::Backward) +} + +#[tauri::command] +pub fn redo_cowork_folder( + app: AppHandle, + session_id: String, +) -> Result { + navigate_timeline(&app, &session_id, Direction::Forward) +} + +#[derive(Clone, Copy)] +enum Direction { + /// Move cursor back by 1 (Undo). + Backward, + /// Move cursor forward by 1 (Redo). + Forward, +} + +impl Direction { + fn verb(self) -> &'static str { + match self { + Direction::Backward => "undo", + Direction::Forward => "redo", + } + } + + /// Compute the target cursor given the current timeline, or return + /// an error if moving in that direction isn't possible. + fn target_cursor(self, timeline: &Timeline) -> Result { + if timeline.snapshots.is_empty() { + return Err(format!( + "Cannot {}: no snapshots have been captured yet", + self.verb() + )); + } + match self { + Direction::Backward => { + if !timeline.can_undo() { + return Err("Cannot undo: already at the oldest snapshot".to_string()); + } + Ok(timeline.cursor - 1) + } + Direction::Forward => { + if !timeline.can_redo() { + return Err("Cannot redo: already at the newest snapshot".to_string()); + } + Ok(timeline.cursor + 1) + } + } + } +} + +fn navigate_timeline( + app: &AppHandle, + session_id: &str, + direction: Direction, +) -> Result { + let mut session = sessions::get_folder_session(app.clone(), session_id.to_string())?; + + let store = SnapshotStore::for_session(app, session_id)?; + let mut timeline = store.read_timeline()?; + + let target_cursor = direction.target_cursor(&timeline)?; + let target_meta = timeline.snapshots[target_cursor].clone(); + + // Materialise the target manifest onto disk. + let manifest = store.read_manifest(&target_meta.id).map_err(|error| { + format!( + "Cowork {}: failed to read manifest {}: {}", + direction.verb(), + target_meta.id, + error + ) + })?; + let source_root = Path::new(&session.folder_tree_pair.source_root); + store.apply_manifest(&manifest, source_root).map_err(|error| { + format!( + "Cowork {}: failed to apply manifest {}: {}", + direction.verb(), + target_meta.id, + error + ) + })?; + + // Advance cursor and persist the updated timeline. GC is a best-effort + // cleanup — not fatal if it fails. + timeline.cursor = target_cursor; + store.write_timeline(&timeline)?; + if let Err(error) = store.gc_unreferenced_blobs() { + eprintln!( + "[cowork] {}: gc_unreferenced_blobs failed: {}", + direction.verb(), + error + ); + } + + // Refresh UI-facing state on the session hint + the tree viewer. + sync_result_tree_from_disk(&mut session)?; + session.undo_state = FolderUndoState { + can_undo: timeline.can_undo(), + can_redo: timeline.can_redo(), + current: timeline.display_position().0, + total: timeline.display_position().1, + }; + session.base.updated_at = now_iso(); + + let persisted = sessions::update_folder_session(app.clone(), session)?; + Ok(persisted) +} diff --git a/frontend/src-tauri/src/cowork/session_gateway.rs b/frontend/src-tauri/src/cowork/session_gateway.rs index 2d252d7ce..e9af0bd61 100644 --- a/frontend/src-tauri/src/cowork/session_gateway.rs +++ b/frontend/src-tauri/src/cowork/session_gateway.rs @@ -7,7 +7,13 @@ use crate::cowork::chat::{ }; use crate::cowork::homepage::chat_sessions as homepage_sessions; use crate::cowork::intelligent_folder::chat_prompt as folder_chat_prompt; -use crate::cowork::intelligent_folder::sessions as folder_sessions; +use crate::cowork::intelligent_folder::file_tree; +use crate::cowork::intelligent_folder::sessions::{ + self as folder_sessions, FolderUndoState, +}; +use crate::cowork::intelligent_folder::snapshot_store::{ + PendingSnapshot, SnapshotStore, MAX_TIMELINE_LEN, +}; use crate::cowork::runtime::CoworkRuntimeSessionSnapshot; use crate::cowork::time_utils::{generate_message_id, now_iso}; use tauri::AppHandle; @@ -140,6 +146,12 @@ pub fn persist_local_session( mut session: LocalCoworkSession, ) -> Result { normalize_local_session_runtime(&mut session); + // Intelligent Folder snapshot timeline bookkeeping: at run start + // capture a pending pre-run snapshot; at run end push a new timeline + // entry (or discard the pending) based on whether disk actually + // changed. Best-effort — failures are logged and never block the + // persistence path. + maintain_folder_timeline(app, &mut session); let mut persisted = resolve_store_for_session(&session).save(app, session)?; normalize_local_session_runtime(&mut persisted); Ok(persisted) @@ -273,6 +285,310 @@ pub fn sync_folder_result_tree(session: &mut LocalCoworkSession) -> Result<(), S } } +/// Intelligent Folder snapshot timeline bookkeeping. Runs on every +/// [`persist_local_session`] call and decides whether to (a) capture a +/// fresh pre-run snapshot, (b) push an existing pending one into the +/// session's timeline + refresh the UI hint, or (c) do nothing. +/// +/// The decision is driven entirely by `session.run_status` and the +/// presence/absence of `pending.json` on disk — no other code needs to +/// call into the snapshot store for pre/post-run tracking. Intentionally +/// never fails: any error is logged and the session is persisted as-is +/// (a broken snapshot path must not break the user's chat flow). +/// +/// State transitions (only active on [`LocalCoworkSession::Folder`]): +/// +/// | run_status | pending.json? | action | +/// |-------------------------------|---------------|------------------------------| +/// | `Thinking` / `Waiting` | absent | write fresh pending snapshot | +/// | `Thinking` / `Waiting` | present | no-op | +/// | `Completed` / `Stopped` / `Idle` | present | compare, push or discard | +/// | `Completed` / `Stopped` / `Idle` | absent | just refresh undo_state hint | +fn maintain_folder_timeline(app: &AppHandle, session: &mut LocalCoworkSession) { + let folder_detail = match session { + LocalCoworkSession::Folder(detail) => detail, + LocalCoworkSession::Homepage(_) => return, + }; + + let session_id = folder_detail.base.id.clone(); + let source_root = folder_detail.folder_tree_pair.source_root.clone(); + + let store = match SnapshotStore::for_session(app, &session_id) { + Ok(store) => store, + Err(error) => { + eprintln!( + "[cowork] timeline: store init failed for session {}: {}", + session_id, error + ); + return; + } + }; + + let is_run_active = matches!( + folder_detail.base.run_status, + CoworkChatRunStatus::Thinking | CoworkChatRunStatus::WaitingForInput + ); + + if is_run_active { + capture_pre_run_snapshot_if_absent(&store, &session_id, &source_root); + // Run is still in-flight — don't touch the timeline or undo_state + // hint yet. The final post-run persist_local_session call will + // sync everything. + return; + } + + // run_status is terminal (Idle / Completed / Stopped). If there's a + // pending marker, this is the moment to compare it against current + // disk and either push a new timeline entry (disk changed) or drop + // it (disk unchanged). + commit_or_discard_pending(&store, &session_id, &source_root, folder_detail); + + // Regardless of whether anything got committed, refresh the + // UI-facing hint from the on-disk timeline — covers the "no pending, + // just refresh" case too so a session loaded after a restart shows + // the correct Undo/Redo buttons. + refresh_undo_state_hint(&store, &session_id, folder_detail); +} + +fn capture_pre_run_snapshot_if_absent( + store: &SnapshotStore, + session_id: &str, + source_root: &str, +) { + match store.read_pending() { + Ok(Some(_)) => { + // Already captured for this run — do not overwrite, otherwise + // mid-run status updates would keep rewriting pending against + // a disk that the agent is already modifying. + } + Ok(None) => { + let pre_run_tree = match file_tree::read_path_tree(source_root.to_string(), None) { + Ok(tree) => tree, + Err(error) => { + eprintln!( + "[cowork] timeline: pre-run tree scan failed for session {}: {}", + session_id, error + ); + return; + } + }; + let pre_run_tree_hash = match folder_sessions::hash_tree(&pre_run_tree) { + Ok(hash) => hash, + Err(error) => { + eprintln!( + "[cowork] timeline: pre-run hash failed for session {}: {}", + session_id, error + ); + return; + } + }; + let manifest = match store.snapshot(std::path::Path::new(source_root)) { + Ok(manifest) => manifest, + Err(error) => { + eprintln!( + "[cowork] timeline: pre-run snapshot failed for session {}: {}", + session_id, error + ); + return; + } + }; + let pending = PendingSnapshot { + pre_run_tree_hash, + manifest, + }; + if let Err(error) = store.write_pending(&pending) { + eprintln!( + "[cowork] timeline: write_pending failed for session {}: {}", + session_id, error + ); + } + } + Err(error) => { + eprintln!( + "[cowork] timeline: read_pending failed for session {}: {}", + session_id, error + ); + } + } +} + +fn commit_or_discard_pending( + store: &SnapshotStore, + session_id: &str, + source_root: &str, + _folder_detail: &mut folder_sessions::CoworkChatSessionDetail, +) { + let pending = match store.read_pending() { + Ok(Some(pending)) => pending, + Ok(None) => return, + Err(error) => { + eprintln!( + "[cowork] timeline: read_pending failed for session {}: {}", + session_id, error + ); + return; + } + }; + + let post_run_tree = match file_tree::read_path_tree(source_root.to_string(), None) { + Ok(tree) => tree, + Err(error) => { + eprintln!( + "[cowork] timeline: post-run tree scan failed for session {}: {}", + session_id, error + ); + let _ = store.clear_pending(); + let _ = store.gc_unreferenced_blobs(); + return; + } + }; + let post_run_hash = match folder_sessions::hash_tree(&post_run_tree) { + Ok(hash) => hash, + Err(error) => { + eprintln!( + "[cowork] timeline: post-run hash failed for session {}: {}", + session_id, error + ); + let _ = store.clear_pending(); + let _ = store.gc_unreferenced_blobs(); + return; + } + }; + + if pending.pre_run_tree_hash == post_run_hash { + // Disk didn't change → discard pending, leave timeline untouched. + let _ = store.clear_pending(); + let _ = store.gc_unreferenced_blobs(); + return; + } + + // Disk DID change → push a new entry into the timeline. + if let Err(error) = push_post_run_snapshot( + store, + session_id, + std::path::Path::new(source_root), + &pending, + post_run_hash, + ) { + eprintln!( + "[cowork] timeline: push_post_run_snapshot failed for session {}: {}", + session_id, error + ); + } + let _ = store.clear_pending(); + let _ = store.gc_unreferenced_blobs(); +} + +/// Commit a change into the timeline. This is the heart of the +/// `/snapshot history/` design: it handles seeding an empty timeline, +/// rebasing when `timeline[cursor]` drifted out of sync with disk, +/// truncating future branches when the user was detached, pushing the +/// new state, and enforcing the retention cap. +/// +/// **Invariant maintained**: after this function returns Ok, the +/// timeline has a `cursor` pointing at a snapshot whose +/// `disk_tree_hash` equals the `post_run_hash` we just computed. The +/// caller (post-run hook) is responsible for clearing the pending +/// marker and running GC. +fn push_post_run_snapshot( + store: &SnapshotStore, + session_id: &str, + source_root: &std::path::Path, + pending: &PendingSnapshot, + post_run_hash: String, +) -> Result<(), String> { + let mut timeline = store.read_timeline()?; + + // Step 1: if the user is detached (cursor < len-1), any snapshots in + // the future are now orphaned by this new commit — git-style discard. + if timeline.cursor + 1 < timeline.snapshots.len() { + let discarded: Vec = timeline + .snapshots + .drain(timeline.cursor + 1..) + .map(|meta| meta.id) + .collect(); + for id in &discarded { + let _ = store.delete_manifest(id); + } + } + + // Step 2: ensure timeline[cursor] matches the pre-run disk state. If + // the timeline is empty, or the cursor snapshot's hash doesn't match + // pending.pre_run_tree_hash (e.g. timeline was seeded from an older + // run and disk has drifted, or this is the very first commit ever), + // push the pending manifest as a new "pre-run" baseline entry first. + let cursor_matches_pre_run = timeline + .snapshots + .get(timeline.cursor) + .map(|meta| meta.disk_tree_hash == pending.pre_run_tree_hash) + .unwrap_or(false); + + if !cursor_matches_pre_run { + let baseline_meta = + store.push_snapshot(&pending.manifest, pending.pre_run_tree_hash.clone())?; + if timeline.snapshots.is_empty() { + timeline.snapshots.push(baseline_meta); + timeline.cursor = 0; + } else { + // Rebasing: insert the baseline right after the current + // cursor so that subsequent undo still walks older history. + let insert_at = timeline.cursor + 1; + timeline.snapshots.insert(insert_at, baseline_meta); + timeline.cursor = insert_at; + } + } + + // Step 3: snapshot the current (post-run) disk and push as a new + // entry after the baseline. + let post_run_manifest = store.snapshot(source_root)?; + let post_run_meta = store.push_snapshot(&post_run_manifest, post_run_hash)?; + timeline.snapshots.push(post_run_meta); + timeline.cursor = timeline.snapshots.len() - 1; + + // Step 4: enforce retention cap. Drop oldest entries first and + // shift cursor to compensate. + while timeline.snapshots.len() > MAX_TIMELINE_LEN { + let dropped = timeline.snapshots.remove(0); + let _ = store.delete_manifest(&dropped.id); + if timeline.cursor > 0 { + timeline.cursor -= 1; + } + } + + store.write_timeline(&timeline)?; + + let _ = session_id; // reserved for future structured logging + Ok(()) +} + +/// Pull the current timeline from disk and mirror its navigation state +/// onto the session's `undo_state` hint. Best-effort — if the timeline +/// is unreadable, we leave the existing hint alone rather than wiping +/// it to a default (which would hide the button the next render). +fn refresh_undo_state_hint( + store: &SnapshotStore, + session_id: &str, + folder_detail: &mut folder_sessions::CoworkChatSessionDetail, +) { + let timeline = match store.read_timeline() { + Ok(timeline) => timeline, + Err(error) => { + eprintln!( + "[cowork] timeline: read_timeline failed for session {}: {}", + session_id, error + ); + return; + } + }; + let (current, total) = timeline.display_position(); + folder_detail.undo_state = FolderUndoState { + can_undo: timeline.can_undo(), + can_redo: timeline.can_redo(), + current, + total, + }; +} + pub fn resolve_prompt_context( session: &LocalCoworkSession, explicit_prompt_context: Option, diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index d2ef0858e..40f182f91 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -32,7 +32,9 @@ fn main() { cowork::intelligent_folder::sessions::create_folder_session, cowork::intelligent_folder::sessions::update_folder_session, cowork::intelligent_folder::sessions::rename_folder_session, - cowork::intelligent_folder::sessions::delete_folder_session + cowork::intelligent_folder::sessions::delete_folder_session, + cowork::intelligent_folder::undo_commands::undo_cowork_folder, + cowork::intelligent_folder::undo_commands::redo_cowork_folder ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/frontend/src/components/cowork/intelligent-folder/cowork-folder-undo-redo.tsx b/frontend/src/components/cowork/intelligent-folder/cowork-folder-undo-redo.tsx new file mode 100644 index 000000000..e2e5987cf --- /dev/null +++ b/frontend/src/components/cowork/intelligent-folder/cowork-folder-undo-redo.tsx @@ -0,0 +1,101 @@ +import clsx from 'clsx' +import { Icon } from '@/components/ui/icon' +import type { CoworkFolderUndoState } from '@/typings/cowork' + +interface CoworkFolderUndoRedoProps { + state?: CoworkFolderUndoState | null + onUndo: () => void + onRedo: () => void + isBusy?: boolean + className?: string +} + +interface PillButtonProps { + label: string + iconName: string + enabled: boolean + onClick: () => void + ariaLabel: string +} + +/** + * Single Undo or Redo pill — shares the visual language of + * {@link CoworkFolderSteps} (size-7 circle + icon). Disabled pills are + * kept visible (not removed) so the layout doesn't jitter every time + * the user walks to a timeline boundary. + */ +const PillButton = ({ label, iconName, enabled, onClick, ariaLabel }: PillButtonProps) => { + return ( + + ) +} + +/** + * Full Undo / Redo / Counter row rendered to the right of the + * Source / Build / Result stepper in Intelligent Folder mode. + * + * - Rendered only when `state.total > 0` (i.e. the session has + * committed at least one snapshot to the timeline). + * - Counter shows "current/total" — current is 1-based. + * - Both pills are always shown; disabled when navigation in that + * direction isn't possible, so the row doesn't shift around as the + * cursor walks. + */ +const CoworkFolderUndoRedo = ({ + state, + onUndo, + onRedo, + isBusy = false, + className +}: CoworkFolderUndoRedoProps) => { + if (!state || state.total === 0) { + return null + } + + const canUndo = state.can_undo && !isBusy + const canRedo = state.can_redo && !isBusy + + return ( +
+ + + {state.current}/{state.total} + + +
+ ) +} + +export default CoworkFolderUndoRedo diff --git a/frontend/src/components/cowork/modes/intelligent-folder.tsx b/frontend/src/components/cowork/modes/intelligent-folder.tsx index d6a7f83b3..19bcc1807 100644 --- a/frontend/src/components/cowork/modes/intelligent-folder.tsx +++ b/frontend/src/components/cowork/modes/intelligent-folder.tsx @@ -1,6 +1,7 @@ import { AnimatePresence, motion } from 'framer-motion' import { FolderOpen, LoaderCircle } from 'lucide-react' -import { useEffect, useLayoutEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react' +import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { coworkService } from '@/services/cowork.service' @@ -20,6 +21,7 @@ import { FOLDER_TREE_READ_OPTIONS } from '../intelligent-folder/folder-tree-util import CoworkFolderSteps, { type CoworkFolderStep } from '../intelligent-folder/cowork-folder-steps' +import CoworkFolderUndoRedo from '../intelligent-folder/cowork-folder-undo-redo' const folderStepTransition = { duration: 0.14, @@ -57,6 +59,9 @@ const IntelligentFolderMode = ({ useState(null) const [isProcessing, setIsProcessing] = useState(false) const [activeStep, setActiveStep] = useState('source') + // True while an Undo or Redo RPC is in flight — disables the button so + // the user can't double-click and trigger two swaps at once. + const [isUndoRedoBusy, setIsUndoRedoBusy] = useState(false) useEffect(() => { onWorkflowActiveChange?.(isProcessing) @@ -317,6 +322,35 @@ const IntelligentFolderMode = ({ } } + // Session-update callback is stable across Undo/Redo — reuse the same + // channel the rest of the flow uses so the parent page's session cache + // picks up the flipped `undo_slot` and the refreshed `result_tree`. + const handleUndoRedo = useCallback( + async (action: 'undo' | 'redo') => { + if (!session?.id || isUndoRedoBusy) return + setIsUndoRedoBusy(true) + try { + const updated = + action === 'undo' + ? await coworkService.undoFolder(session.id) + : await coworkService.redoFolder(session.id) + onSessionCreated?.(updated) + } catch (error) { + toast.error( + getErrorMessage( + error, + action === 'undo' + ? 'Failed to undo folder changes.' + : 'Failed to redo folder changes.' + ) + ) + } finally { + setIsUndoRedoBusy(false) + } + }, + [session?.id, isUndoRedoBusy, onSessionCreated] + ) + return ( {!isProcessing ? ( @@ -458,10 +492,27 @@ const IntelligentFolderMode = ({ className="h-full w-full overflow-hidden bg-white dark:bg-white/[0.01]" >
- +
+ {/* Left spacer — balances the Undo/Redo slot on the right + so the steps row stays visually centered. */} +
+ +
+ handleUndoRedo('undo')} + onRedo={() => handleUndoRedo('redo')} + isBusy={ + isUndoRedoBusy || + isSending || + isSessionLoading + } + /> +
+
{ + return invoke('undo_cowork_folder', { + sessionId + }) + } + + async redoFolder(sessionId: string): Promise { + return invoke('redo_cowork_folder', { + sessionId + }) + } } export const coworkService = new CoworkService() diff --git a/frontend/src/typings/cowork.ts b/frontend/src/typings/cowork.ts index 0ace57728..9fd86361b 100644 --- a/frontend/src/typings/cowork.ts +++ b/frontend/src/typings/cowork.ts @@ -35,6 +35,26 @@ export interface CoworkFolderTreePair { result_tree: CoworkFolderTreeNode | null } +/** + * Mirrors Rust's `FolderUndoState` struct. Lightweight UI hint derived + * from the session's snapshot timeline on disk: + * + * - `can_undo` / `can_redo` — whether the corresponding pill should + * be enabled. + * - `current` — 1-based index of the snapshot currently materialised + * on disk. `0` when the timeline is empty (no runs have committed + * a change yet). + * - `total` — number of snapshots in the timeline. `0` when empty. + * + * When `total === 0`, the UI hides the Undo/Redo row entirely. + */ +export interface CoworkFolderUndoState { + can_undo: boolean + can_redo: boolean + current: number + total: number +} + export interface CoworkChatMessage { id: string role: CoworkChatMessageRole @@ -77,6 +97,7 @@ export interface CoworkChatSessionDetail extends CoworkChatSessionSummary { files: CoworkChatFile[] run_status: CoworkChatRunStatus folder_tree_pair?: CoworkFolderTreePair + undo_state?: CoworkFolderUndoState } export type CoworkChatEvent = From 08abdd7e55722ebe03a3c4e4217f623916e83c27 Mon Sep 17 00:00:00 2001 From: namtranii Date: Sun, 12 Apr 2026 23:21:43 +0700 Subject: [PATCH 09/12] feat: add desktop skills and WASM sandbox processes. --- frontend/src-tauri/Cargo.lock | 1200 ++++++++++++++++- frontend/src-tauri/Cargo.toml | 5 + .../src/cowork/desktop_runtime/host.rs | 58 + .../src/cowork/desktop_runtime/mod.rs | 61 + .../desktop_runtime/wasm/chunking/docx.rs | 94 ++ .../desktop_runtime/wasm/chunking/mod.rs | 324 +++++ .../desktop_runtime/wasm/chunking/pdf.rs | 345 +++++ .../desktop_runtime/wasm/chunking/pptx.rs | 72 + .../desktop_runtime/wasm/chunking/splitter.rs | 161 +++ .../desktop_runtime/wasm/chunking/xlsx.rs | 72 + .../cowork/desktop_runtime/wasm/freshness.rs | 104 ++ .../desktop_runtime/wasm/guest/.gitignore | 2 + .../wasm/guest/docx_processor/Cargo.toml | 21 + .../wasm/guest/docx_processor/src/main.rs | 166 +++ .../wasm/guest/pdf_processor/Cargo.toml | 21 + .../wasm/guest/pdf_processor/src/main.rs | 199 +++ .../wasm/guest/pptx_processor/Cargo.toml | 22 + .../wasm/guest/pptx_processor/src/main.rs | 262 ++++ .../wasm/guest/xlsx_processor/Cargo.toml | 21 + .../wasm/guest/xlsx_processor/src/main.rs | 204 +++ .../cowork/desktop_runtime/wasm/lifecycle.rs | 249 ++++ .../src/cowork/desktop_runtime/wasm/mod.rs | 1108 +++++++++++++++ .../desktop_runtime/wasm/modules/README.md | 64 + .../wasm/modules/docx_processor.wasm | Bin 0 -> 617045 bytes .../wasm/modules/pdf_processor.wasm | Bin 0 -> 783276 bytes .../wasm/modules/pptx_processor.wasm | Bin 0 -> 231708 bytes .../wasm/modules/xlsx_processor.wasm | Bin 0 -> 634791 bytes .../desktop_skills/desktop_skill_run.rs | 146 ++ .../src/cowork/desktop_skills/docx.skill.md | 83 ++ .../src/cowork/desktop_skills/mod.rs | 364 ++++- .../src/cowork/desktop_skills/pdf.skill.md | 157 +++ .../src/cowork/desktop_skills/pptx.skill.md | 83 ++ .../src/cowork/desktop_skills/xlsx.skill.md | 98 ++ .../src-tauri/src/cowork/desktop_tools/mod.rs | 88 ++ .../src/cowork/desktop_tools/wasm_run.rs | 746 ++++++++++ .../cowork/intelligent_folder/chat_prompt.rs | 39 +- .../cowork/intelligent_folder/file_tree.rs | 32 +- frontend/src-tauri/src/cowork/mod.rs | 1 + frontend/src/components/agent/action.tsx | 22 + .../components/cowork/cowork-action-utils.ts | 216 +++ .../cowork-build/cowork-build.renderers.tsx | 72 +- .../cowork-build/cowork-build.shared.tsx | 60 +- .../cowork/cowork-build/cowork-build.types.ts | 15 +- .../src/components/cowork/cowork-page.tsx | 129 +- .../cowork-folder-build.tsx | 9 +- .../cowork-folder-result.tsx | 6 +- .../cowork-folder-tree-view.tsx | 108 +- .../cowork/modes/intelligent-folder.tsx | 33 +- frontend/src/typings/agent.ts | 14 +- frontend/src/typings/cowork.ts | 1 + 50 files changed, 7162 insertions(+), 195 deletions(-) create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/host.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/mod.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/docx.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/mod.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pptx.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/xlsx.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/freshness.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/.gitignore create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/Cargo.toml create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/src/main.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/Cargo.toml create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/src/main.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/Cargo.toml create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/src/main.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/Cargo.toml create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/src/main.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/README.md create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/docx_processor.wasm create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/pdf_processor.wasm create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/pptx_processor.wasm create mode 100644 frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/xlsx_processor.wasm create mode 100644 frontend/src-tauri/src/cowork/desktop_skills/desktop_skill_run.rs create mode 100644 frontend/src-tauri/src/cowork/desktop_skills/docx.skill.md create mode 100644 frontend/src-tauri/src/cowork/desktop_skills/pdf.skill.md create mode 100644 frontend/src-tauri/src/cowork/desktop_skills/pptx.skill.md create mode 100644 frontend/src-tauri/src/cowork/desktop_skills/xlsx.skill.md create mode 100644 frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs create mode 100644 frontend/src/components/cowork/cowork-action-utils.ts diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index a84bc7bbb..5d83a4cb2 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "chrono", "futures-util", "glob", + "lopdf", "regex", "reqwest 0.12.28", "rust_socketio", @@ -20,6 +21,10 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-process", "tauri-plugin-shell", + "tokio", + "wasmtime", + "wasmtime-wasi", + "wat", "zstd", ] @@ -44,6 +49,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -68,6 +96,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -89,6 +123,21 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object 0.37.3", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "async-stream" version = "0.3.6" @@ -178,7 +227,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.36.7", "rustc-demangle", "windows-targets 0.52.6", ] @@ -219,6 +268,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -264,6 +322,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytemuck" version = "1.23.0" @@ -319,6 +383,84 @@ dependencies = [ "serde", ] +[[package]] +name = "cap-fs-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "cap-net-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" +dependencies = [ + "cap-primitives", + "cap-std", + "rustix 1.1.2", + "smallvec", +] + +[[package]] +name = "cap-primitives" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix 1.1.2", + "rustix-linux-procfs", + "windows-sys 0.59.0", + "winx", +] + +[[package]] +name = "cap-rand" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix 1.1.2", +] + +[[package]] +name = "cap-time-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix 1.1.2", + "winx", +] + [[package]] name = "cargo-platform" version = "0.1.9" @@ -352,12 +494,22 @@ dependencies = [ "toml 0.8.22", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" -version = "1.2.21" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -417,6 +569,25 @@ dependencies = [ "windows-link 0.1.1", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.12", +] + [[package]] name = "combine" version = "4.6.7" @@ -515,6 +686,112 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-bforest" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "540b193ff98b825a1f250a75b3118911af918a734154c69d80bcfcf91e7e9522" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-bitset" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cb269598b9557ab942d687d3c1086d77c4b50dcf35813f3a65ba306fd42279" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-codegen" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46566d7c83a8bff4150748d66020f4c7224091952aa4b4df1ec4959c39d937a1" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.14.5", + "log", + "regalloc2", + "rustc-hash", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df8a86a34236cc75a8a6a271973da779c2aeb36c43b6e14da474cf931317082" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf75340b6a57b7c7c1b74f10d3d90883ee6d43a554be8131a4046c2ebcf5eb65" + +[[package]] +name = "cranelift-control" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e84495bc5d23d86aad8c86f8ade4af765b94882af60d60e271d3153942f1978" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963c17147b80df351965e57c04d20dbedc85bcaf44c3436780a59a3f1ff1b1c2" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-frontend" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727f02acbc4b4cb2ba38a6637101d579db50190df1dd05168c68e762851a3dd5" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b00cc2e03c748f2531eea01c871f502b909d30295fdcad43aec7bf5c5b4667" + +[[package]] +name = "cranelift-native" +version = "0.113.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbeaf978dc7c1a2de8bbb9162510ed218eb156697bc45590b8fbdd69bb08e8de" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -660,13 +937,33 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", ] [[package]] @@ -677,7 +974,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.0", "windows-sys 0.61.2", ] @@ -763,6 +1060,21 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +[[package]] +name = "ecb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" +dependencies = [ + "cipher", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "embed-resource" version = "3.0.2" @@ -783,6 +1095,18 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -818,12 +1142,29 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.1.2", + "windows-sys 0.59.0", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -843,6 +1184,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "flate2" version = "1.1.1" @@ -859,6 +1206,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -910,6 +1263,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix 1.1.2", + "windows-sys 0.59.0", +] + [[package]] name = "futf" version = "0.1.5" @@ -920,6 +1284,20 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -1154,6 +1532,11 @@ name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator", + "indexmap 2.13.0", + "stable_deref_trait", +] [[package]] name = "gio" @@ -1328,6 +1711,25 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -1636,6 +2038,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1695,6 +2103,16 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -1704,6 +2122,22 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + [[package]] name = "ipnet" version = "2.11.0" @@ -1739,6 +2173,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1857,6 +2300,18 @@ dependencies = [ "selectors", ] +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libappindicator" version = "0.9.0" @@ -1897,6 +2352,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.3" @@ -1907,6 +2368,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1935,6 +2402,32 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lopdf" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fa2559e99ba0f26a12458aabc754432c805bbb8cba516c427825a997af1fb7" +dependencies = [ + "aes", + "bitflags 2.11.0", + "cbc", + "ecb", + "encoding_rs", + "flate2", + "indexmap 2.13.0", + "itoa", + "log", + "md-5", + "nom", + "nom_locate", + "rand 0.9.2", + "rangemap", + "sha2", + "stringprep", + "thiserror 2.0.12", + "weezl", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -1947,6 +2440,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "markup5ever" version = "0.14.1" @@ -1978,12 +2480,37 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.2", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -2100,6 +2627,26 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom_locate" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2291,6 +2838,18 @@ name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "memchr", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -2421,6 +2980,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" @@ -2611,6 +3176,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2699,6 +3276,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "pulley-interpreter" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33e7f8a43ccc7f93b330fef4baf271764674926f3f4d40f4a196d54de8af26" +dependencies = [ + "cranelift-bitset", + "log", + "sptr", +] + [[package]] name = "quick-xml" version = "0.32.0" @@ -2888,6 +3486,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -2903,6 +3507,17 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.0" @@ -2914,6 +3529,19 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "regalloc2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12908dbeb234370af84d0579b9f68258a0f67e201412dd9a2814e6f45b2fc0f0" +dependencies = [ + "hashbrown 0.14.5", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + [[package]] name = "regex" version = "1.11.1" @@ -3128,6 +3756,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.2" @@ -3137,10 +3778,20 @@ dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.11.0", "windows-sys 0.61.2", ] +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.1.2", +] + [[package]] name = "rustls" version = "0.23.37" @@ -3488,6 +4139,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs 4.0.0", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3521,11 +4181,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3585,6 +4254,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3616,6 +4291,17 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -3715,6 +4401,22 @@ dependencies = [ "version-compare", ] +[[package]] +name = "system-interface" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" +dependencies = [ + "bitflags 2.11.0", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.59.0", + "winx", +] + [[package]] name = "tao" version = "0.34.8" @@ -3779,7 +4481,7 @@ dependencies = [ "anyhow", "bytes", "cookie", - "dirs", + "dirs 6.0.0", "dunce", "embed_plist", "getrandom 0.3.2", @@ -3829,7 +4531,7 @@ checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 6.0.0", "glob", "heck 0.5.0", "json-patch", @@ -4081,7 +4783,7 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.1.2", "windows-sys 0.61.2", ] @@ -4096,6 +4798,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4408,9 +5119,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "tracing-core" version = "0.1.33" @@ -4427,7 +5150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 6.0.0", "libappindicator", "muda", "objc2 0.6.1", @@ -4521,18 +5244,51 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -4724,6 +5480,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.218.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "491f7e48672d0a1efdeadf897d98ac1f45942c26c3829cb44a6b828f6f26155f" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.246.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" +dependencies = [ + "leb128fmt", + "wasmparser 0.246.2", +] + [[package]] name = "wasm-streams" version = "0.4.2" @@ -4750,6 +5525,304 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.218.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059739c2eac26eea736389a7d6d30b41a8201490bea204d0facde19183359849" +dependencies = [ + "ahash", + "bitflags 2.11.0", + "hashbrown 0.14.5", + "indexmap 2.13.0", + "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.246.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" +dependencies = [ + "bitflags 2.11.0", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "wasmprinter" +version = "0.218.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b30ceafa77646f56747369b0f2a0296016a40b447d32e6907439f2e4bb7695" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.218.1", +] + +[[package]] +name = "wasmtime" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e762e163fd305770c6c341df3290f0cabb3c264e7952943018e9a1ced8d917" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 2.11.0", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "hashbrown 0.14.5", + "indexmap 2.13.0", + "libc", + "libm", + "log", + "mach2", + "memfd", + "object 0.36.7", + "once_cell", + "paste", + "postcard", + "psm", + "pulley-interpreter", + "rustix 0.38.44", + "semver", + "serde", + "serde_derive", + "smallvec", + "sptr", + "target-lexicon", + "wasmparser 0.218.1", + "wasmtime-asm-macros", + "wasmtime-component-macro", + "wasmtime-component-util", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-icache-coherence", + "wasmtime-slab", + "wasmtime-versioned-export-macros", + "wasmtime-winch", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63caa7aebb546374e26257a1900fb93579171e7c02514cde26805b9ece3ef812" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-component-macro" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61a4b5ce2ad9c15655e830f0eac0c38b8def30c74ecac71f452d3901e491b68" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.101", + "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser", +] + +[[package]] +name = "wasmtime-component-util" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e87a1212270dbb84a49af13d82594e00a92769d6952b0ea7fc4366c949f6ad" + +[[package]] +name = "wasmtime-cranelift" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cb40dddf38c6a5eefd5ce7c1baf43b00fe44eada11a319fab22e993a960262f" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools", + "log", + "object 0.36.7", + "smallvec", + "target-lexicon", + "thiserror 1.0.69", + "wasmparser 0.218.1", + "wasmtime-environ", + "wasmtime-versioned-export-macros", +] + +[[package]] +name = "wasmtime-environ" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8613075e89e94a48c05862243c2b718eef1b9c337f51493ebf951e149a10fa19" +dependencies = [ + "anyhow", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "indexmap 2.13.0", + "log", + "object 0.36.7", + "postcard", + "semver", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasm-encoder 0.218.1", + "wasmparser 0.218.1", + "wasmprinter", + "wasmtime-component-util", +] + +[[package]] +name = "wasmtime-fiber" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77acabfbcd89a4d47ad117fb31e340c824e2f49597105402c3127457b6230995" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "rustix 0.38.44", + "wasmtime-asm-macros", + "wasmtime-versioned-export-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da47fba49af72581bc0dc67c8faaf5ee550e6f106e285122a184a675193701a5" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-slab" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770e10cdefb15f2b6304152978e115bd062753c1ebe7221c0b6b104fa0419ff6" + +[[package]] +name = "wasmtime-versioned-export-macros" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8efb877c9e5e67239d4553bb44dd2a34ae5cfb728f3cf2c5e64439c6ca6ee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "wasmtime-wasi" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16c8d87a45168131be6815045e6d46d7f6ddf65897c49444ab210488bce10dc" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 2.11.0", + "bytes", + "cap-fs-ext", + "cap-net-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "futures", + "io-extras", + "io-lifetimes", + "once_cell", + "rustix 0.38.44", + "system-interface", + "thiserror 1.0.69", + "tokio", + "tracing", + "url", + "wasmtime", + "wiggle", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-winch" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7a267367382ceec3e7f7ace63a63b83d86f4a680846743dead644e10f08150" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "object 0.36.7", + "target-lexicon", + "wasmparser 0.218.1", + "wasmtime-cranelift", + "wasmtime-environ", + "winch-codegen", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bef2a726fd8d1ee9b0144655e16c492dc32eb4c7c9f7e3309fcffe637870933" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "wit-parser", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "246.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3fe8e3bf88ad96d031b4181ddbd64634b17cb0d06dfc3de589ef43591a9a62" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.246.2", +] + +[[package]] +name = "wat" +version = "1.246.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd7fda1199b94fff395c2d19a153f05dbe7807630316fa9673367666fd2ad8c" +dependencies = [ + "wast 246.0.2", +] + [[package]] name = "web-sys" version = "0.3.93" @@ -4859,6 +5932,54 @@ dependencies = [ "windows-core", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wiggle" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f25588cf5ea16f56c1af13244486d50c5a2cf67cc0c4e990c665944d741546" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 2.11.0", + "thiserror 1.0.69", + "tracing", + "wasmtime", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28ff23bed568b335dac6a324b8b167318a0c60555199445fcc89745a5eb42452" +dependencies = [ + "anyhow", + "heck 0.5.0", + "proc-macro2", + "quote", + "shellexpand", + "syn 2.0.101", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f13be83541aa0b033ac5ec8a8b59c9a8d8b32305845b8466dd066e722cb0004" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "wiggle-generate", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4890,6 +6011,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winch-codegen" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ab957fc71a36c63834b9b51cc2e087c4260d5ff810a5309ab99f7fbeb19567" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "wasmparser 0.218.1", + "wasmtime-cranelift", + "wasmtime-environ", +] + [[package]] name = "window-vibrancy" version = "0.6.0" @@ -5381,6 +6519,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags 2.11.0", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -5390,6 +6538,36 @@ dependencies = [ "bitflags 2.11.0", ] +[[package]] +name = "wit-parser" +version = "0.218.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f104473e8546f8096f1fa483d337101a98dc9525d67f4275816bcd177fe3e2be" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.218.1", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.69", + "wast 35.0.2", +] + [[package]] name = "write16" version = "1.0.0" @@ -5412,7 +6590,7 @@ dependencies = [ "block2 0.6.1", "cookie", "crossbeam-channel", - "dirs", + "dirs 6.0.0", "dpi", "dunce", "gdkx11", diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 8a01ea2d4..fdab84537 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -25,6 +25,11 @@ glob = "0.3" regex = "1" sha2 = "0.10" zstd = "0.13" +wasmtime = { version = "26", default-features = false, features = ["cranelift", "runtime"] } +wasmtime-wasi = { version = "26", default-features = false, features = ["preview1"] } +wat = "1.218" +lopdf = { version = "0.36", default-features = false } +tokio = { version = "1", features = ["rt", "sync", "time"] } [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/host.rs b/frontend/src-tauri/src/cowork/desktop_runtime/host.rs new file mode 100644 index 000000000..1ae27764c --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/host.rs @@ -0,0 +1,58 @@ +//! Host runtime backend. +//! +//! This backend is a thin policy layer over the existing native tool +//! execution path. The actual tools (bash, read, write, edit, apply_patch, +//! list_dir, glob, grep, todo_write) still own their execution. This module +//! centralises the documentation of what "host execution" means so that +//! tools and skills can reason about it uniformly alongside the WASM +//! backend. +//! +//! The host backend is appropriate for: +//! +//! * file discovery, reading, and structural edits +//! * regex / glob search +//! * shell-based local development tasks +//! * any task that already depends on the user's native environment +//! +//! It is **not** appropriate for isolated processing of user documents +//! with tight resource limits — use [`super::wasm`] for that. + +#![allow(dead_code)] + +use super::{RuntimeKind, RuntimeLimits}; + +/// Marker type for the host runtime. Kept separate from [`super::wasm`] so +/// downstream modules can branch on backend kind without touching tool +/// implementations. +#[derive(Debug, Clone, Copy, Default)] +pub struct HostRuntime; + +impl HostRuntime { + pub const KIND: RuntimeKind = RuntimeKind::Host; + + /// Default resource policy for host execution. The host backend only + /// enforces the wall timeout; memory/fuel limits are advisory and are + /// exposed so that host-side tools which want to match WASM policy can + /// read the same values. + pub fn default_limits() -> RuntimeLimits { + RuntimeLimits::defaults() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn host_runtime_reports_host_kind() { + assert_eq!(HostRuntime::KIND, RuntimeKind::Host); + } + + #[test] + fn host_runtime_exposes_default_limits() { + let limits = HostRuntime::default_limits(); + assert!(limits.wall_timeout.as_secs() > 0); + assert!(limits.max_memory_bytes > 0); + assert!(limits.max_fuel > 0); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/mod.rs b/frontend/src-tauri/src/cowork/desktop_runtime/mod.rs new file mode 100644 index 000000000..41ccfe10d --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/mod.rs @@ -0,0 +1,61 @@ +//! Desktop execution runtimes for cowork desktop tools and skills. +//! +//! The desktop runtime owns execution policy: resource limits, scratch +//! workspace layout, and cleanup semantics for skill-driven processing. +//! Tools plug into a runtime by asking the runtime to execute something — +//! they never spawn isolated work directly. +//! +//! Two backends are currently defined: +//! +//! * [`host`] — the existing native execution backend used by normal local +//! file and shell tools. It is declared here only so that runtime policy +//! lives in one place; the tool modules continue to run on the host +//! directly. +//! * [`wasm`] — the isolated backend used by skill-driven processing tasks +//! that should not run as native host code. Modules are sandboxed by a +//! wasmtime engine with memory and fuel limits. + +pub mod host; +pub mod wasm; + +use std::time::Duration; + +/// Resource limits shared between desktop runtimes. +/// +/// These limits are expressed once so both the host and the WASM backend +/// can enforce a common policy. Individual backends may ignore a limit +/// that does not apply to them (for example, the host backend only enforces +/// `wall_timeout`, while the WASM backend enforces all three). +#[derive(Debug, Clone, Copy)] +pub struct RuntimeLimits { + /// Maximum wall-clock duration of a single runtime call. + pub wall_timeout: Duration, + /// Maximum linear memory (in bytes) a WASM module may allocate. + pub max_memory_bytes: usize, + /// Maximum number of WASM instructions (fuel units) per call. + pub max_fuel: u64, +} + +impl RuntimeLimits { + /// Default limits used when a skill or tool does not override them. + pub const fn defaults() -> Self { + Self { + wall_timeout: Duration::from_secs(30), + max_memory_bytes: 256 * 1024 * 1024, // 256 MiB + max_fuel: 1_000_000_000, + } + } +} + +impl Default for RuntimeLimits { + fn default() -> Self { + Self::defaults() + } +} + +/// Identifier for a desktop runtime backend. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RuntimeKind { + Host, + Wasm, +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/docx.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/docx.rs new file mode 100644 index 000000000..b477f0445 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/docx.rs @@ -0,0 +1,94 @@ +//! Docx-specific chunk planner. +//! +//! OOXML containers (docx) cannot be host-side split like PDF because +//! they share internal resources (styles, relationships, numbering). +//! For files > 100 MB the planner raises the memory ceiling to 1 GB +//! and lets the guest parse the full file in a single call. + +use super::{ChunkPlan, ChunkPlanner}; +use crate::cowork::desktop_runtime::RuntimeLimits; +use serde_json::Value; +use std::path::Path; +use std::time::Duration; + +const SMALL_THRESHOLD: u64 = 20 * 1024 * 1024; +const LARGE_THRESHOLD: u64 = 100 * 1024 * 1024; +const MEDIUM_MEMORY: usize = 768 * 1024 * 1024; +const LARGE_MEMORY: usize = 1024 * 1024 * 1024; // 1 GB + +#[derive(Debug, Default, Clone, Copy)] +pub struct DocxChunker; + +impl ChunkPlanner for DocxChunker { + fn plan( + &self, + file_path: &Path, + op: &str, + _base: &Value, + ) -> Result, String> { + if op != "extract_text" { + return Ok(None); + } + let size = std::fs::metadata(file_path) + .map_err(|e| format!("docx chunker: stat {}: {e}", file_path.display()))? + .len(); + Ok(Some(plan_from_size(size))) + } +} + +pub fn plan_from_size(size: u64) -> ChunkPlan { + if size <= SMALL_THRESHOLD { + return ChunkPlan::single_default(); + } + if size <= LARGE_THRESHOLD { + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = MEDIUM_MEMORY; + return ChunkPlan::Single { limits }; + } + // > 100 MB: single call with raised memory (1 GB) and extended + // timeout. Multi-chunk is pointless because the guest (docx-rs) + // loads the full zip container before slicing by paragraph range. + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = LARGE_MEMORY; + limits.wall_timeout = Duration::from_secs(120); + ChunkPlan::Single { limits } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn small_file_single_default_limits() { + let plan = plan_from_size(5 * 1024 * 1024); + match plan { + ChunkPlan::Single { limits } => { + assert_eq!(limits.max_memory_bytes, RuntimeLimits::defaults().max_memory_bytes); + } + _ => panic!("expected Single"), + } + } + + #[test] + fn medium_file_single_raised_memory() { + let plan = plan_from_size(50 * 1024 * 1024); + match plan { + ChunkPlan::Single { limits } => { + assert_eq!(limits.max_memory_bytes, MEDIUM_MEMORY); + } + _ => panic!("expected Single"), + } + } + + #[test] + fn large_file_single_1gb_memory() { + let plan = plan_from_size(200 * 1024 * 1024); + match plan { + ChunkPlan::Single { limits } => { + assert_eq!(limits.max_memory_bytes, LARGE_MEMORY); + assert_eq!(limits.wall_timeout, Duration::from_secs(120)); + } + _ => panic!("expected Single with 1GB, got Multi"), + } + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/mod.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/mod.rs new file mode 100644 index 000000000..7ad7786ec --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/mod.rs @@ -0,0 +1,324 @@ +//! Host-side chunking policy for desktop WASM calls. +//! +//! Large input documents can exhaust the 256 MB per-Store memory limit +//! if processed in a single `wasm_run` call. This module owns the +//! decision of "does this call need to be split, and if so how?" so +//! the `wasm_run` tool can stay agnostic about individual file formats. +//! +//! ## Concepts +//! +//! * [`ChunkPlan`] — the decision returned by a planner. Either +//! [`ChunkPlan::Single`] (run one WASM call as-is) or +//! [`ChunkPlan::Multi`] (run several WASM calls in sequence and +//! merge the results). +//! * [`ChunkPlanner`] — a format-specific strategy that inspects a +//! host-side file and produces a [`ChunkPlan`]. Each supported format +//! gets its own planner implementation; [`pdf::PdfChunker`] is the +//! first. +//! * [`classify_and_plan`] — the entry point the `wasm_run` tool calls. +//! Picks the right planner for the target module, runs it, and falls +//! back to a single-call plan when no planner exists for the module. +//! +//! ## Why not inside the guest? +//! +//! The obvious alternative is to let each guest module detect its own +//! memory pressure and stream. Two reasons the decision lives in the +//! host instead: +//! +//! 1. Guest modules run with a hard memory ceiling that the host +//! cannot raise mid-call. Splitting the work across Stores is the +//! only way to keep individual peak memory bounded. +//! 2. The host can run chunks in parallel later (each chunk gets its +//! own Store). Inside one guest, parallelism requires WASI threads, +//! which are not part of the `wasm32-wasip1` target we ship. +//! +//! ## Current scope +//! +//! v1 is size-based only: the planner classifies by file size on disk +//! and picks a fixed chunk granularity. It does not parse the document +//! to count pages — that would require pulling `lopdf` into the host +//! binary, which we want to avoid. The granularity heuristic is tuned +//! so the guest, once given a page_range, never tries to load more +//! than ~100 MB of PDF into memory at once. + +pub mod docx; +pub mod pdf; +pub mod pptx; +pub mod splitter; +pub mod xlsx; + +use crate::cowork::desktop_runtime::RuntimeLimits; +use serde_json::Value; +use std::path::Path; + +/// Result of planning a `wasm_run` call against a specific input file. +/// +/// A plan is always safe to ignore: if the caller does not know how to +/// execute multi-chunk plans it can treat [`ChunkPlan::Multi`] as if it +/// were a single call and accept the memory risk. That keeps the plan +/// structure additive — callers adopt chunking at their own pace. +#[derive(Debug, Clone)] +pub enum ChunkPlan { + /// Run the call as-is, optionally with overridden limits (e.g. a + /// larger memory ceiling for borderline inputs that do not need + /// splitting but do need headroom). + Single { limits: RuntimeLimits }, + /// Run several calls in order, each with its own input_json + /// override and limits. The caller merges the structured results + /// back together via [`MergeStrategy`]. + Multi { + chunks: Vec, + merge: MergeStrategy, + /// When `true`, the dispatcher should use host-side file + /// splitting (via [`splitter::split_pdf_pages`]) to produce + /// per-chunk input files instead of passing the full file with + /// a range overlay. This eliminates guest-side full-file memory + /// pressure for formats where host splitting is implemented + /// (currently: PDF only). + use_host_split: bool, + }, +} + +impl ChunkPlan { + pub fn single_default() -> Self { + Self::Single { + limits: RuntimeLimits::defaults(), + } + } + + pub fn is_multi(&self) -> bool { + matches!(self, Self::Multi { .. }) + } + + pub fn uses_host_split(&self) -> bool { + matches!(self, Self::Multi { use_host_split: true, .. }) + } + + pub fn chunk_count(&self) -> usize { + match self { + Self::Single { .. } => 1, + Self::Multi { chunks, .. } => chunks.len(), + } + } +} + +/// One unit of work in a multi-chunk plan. +#[derive(Debug, Clone)] +pub struct Chunk { + /// Zero-based chunk index, used for ordering merged results. + pub index: usize, + /// Human-readable label for logging and error reporting, for + /// example `"pages 1-50"`. + pub label: String, + /// Fields to merge into `input_json` before dispatching the call. + /// The chunker produces this from its own heuristics (for example, + /// a PDF chunker inserts `{"page_range": [1, 50]}`). + pub input_json_overlay: Value, + /// Resource limits for this specific chunk. Usually the same for + /// every chunk in a plan, but kept per-chunk so unusual cases can + /// bump limits on the last chunk if needed. + pub limits: RuntimeLimits, +} + +/// Strategy the caller uses to merge the output of a multi-chunk plan +/// back into a single structured result. +/// +/// Each variant corresponds to a specific `(module, op)` pair. Adding a +/// new chunkable operation means adding a variant here and a matching +/// implementation in [`merge_chunk_outputs`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MergeStrategy { + /// Concatenate `output.json.pages[]` across chunks, ordered by + /// chunk `index`. The merged result uses the first chunk's + /// `total_pages` value. + PdfExtractTextPages, + /// Concatenate `output.json.paragraphs[]` across chunks for the + /// docx `extract_text` op. + DocxExtractTextParagraphs, + /// Concatenate `output.json.rows[]` across chunks for the xlsx + /// `read_sheet` op. + XlsxReadSheetRows, + /// Concatenate `output.json.slides[]` across chunks for the pptx + /// `extract_text` op. + PptxExtractTextSlides, +} + +/// A format-specific strategy for deciding whether a call needs to be +/// chunked and, if so, how. +/// +/// Implementations should be pure functions of `(file_path, op, +/// base_input_json)` so the planner can be unit tested without +/// touching the WASM runtime. +pub trait ChunkPlanner { + /// Plan a call for `file_path` with the given base `input_json`. + /// + /// Returns `Ok(None)` when the planner does not know how to handle + /// the call — the caller should fall through to another planner or + /// the default single-call plan. Returns `Err(_)` only for hard + /// I/O errors that should abort the whole tool call. + fn plan( + &self, + file_path: &Path, + op: &str, + base_input_json: &Value, + ) -> Result, String>; +} + +/// Top-level entry point used by `wasm_run`. +/// +/// The `module` parameter lets the dispatcher pick the right planner +/// without having to know which format the file is. Today only +/// `pdf_processor` has a planner; other modules fall through to the +/// default single-call plan. +pub fn classify_and_plan( + module: &str, + file_path: Option<&Path>, + op: Option<&str>, + base_input_json: &Value, +) -> Result { + let Some(file_path) = file_path else { + return Ok(ChunkPlan::single_default()); + }; + let Some(op) = op else { + return Ok(ChunkPlan::single_default()); + }; + + let planner: Option> = match module { + "pdf_processor" => Some(Box::new(pdf::PdfChunker::default())), + "docx_processor" => Some(Box::new(docx::DocxChunker::default())), + "xlsx_processor" => Some(Box::new(xlsx::XlsxChunker::default())), + "pptx_processor" => Some(Box::new(pptx::PptxChunker::default())), + _ => None, + }; + + let Some(planner) = planner else { + return Ok(ChunkPlan::single_default()); + }; + + match planner.plan(file_path, op, base_input_json)? { + Some(plan) => Ok(plan), + None => Ok(ChunkPlan::single_default()), + } +} + +/// Merge structured outputs of multi-chunk plans back into one +/// `output.json`-style value. +/// +/// The caller hands in the chunk outputs in chunk-index order (the +/// sequential dispatcher in `wasm_run` owns ordering). This function +/// knows the merge shape for each [`MergeStrategy`] variant and +/// produces the final `Value` that gets formatted into the tool +/// result. +pub fn merge_chunk_outputs( + strategy: MergeStrategy, + chunk_outputs: &[Value], +) -> Result { + match strategy { + MergeStrategy::PdfExtractTextPages => { + merge_array_field(chunk_outputs, "pages", "total_pages", "pdf_processor") + } + MergeStrategy::DocxExtractTextParagraphs => merge_array_field( + chunk_outputs, + "paragraphs", + "total_paragraphs", + "docx_processor", + ), + MergeStrategy::XlsxReadSheetRows => { + merge_array_field(chunk_outputs, "rows", "total_rows", "xlsx_processor") + } + MergeStrategy::PptxExtractTextSlides => { + merge_array_field(chunk_outputs, "slides", "total_slides", "pptx_processor") + } + } +} + +/// Generic merge: concatenate `array_field` across all chunk outputs in +/// order, then copy `total_field` from the first chunk. Shared by every +/// merge strategy since they all produce the same shape. +fn merge_array_field( + chunk_outputs: &[Value], + array_field: &str, + total_field: &str, + module_label: &str, +) -> Result { + let mut merged_items: Vec = Vec::new(); + let mut total_value: Option = None; + for (index, chunk) in chunk_outputs.iter().enumerate() { + let items = chunk + .get(array_field) + .and_then(Value::as_array) + .ok_or_else(|| { + format!( + "{module_label} chunk {index}: output.json has no '{array_field}' array" + ) + })?; + merged_items.extend(items.iter().cloned()); + if total_value.is_none() { + total_value = chunk.get(total_field).and_then(Value::as_u64); + } + } + let mut merged = serde_json::Map::new(); + merged.insert(array_field.to_string(), Value::Array(merged_items)); + if let Some(total) = total_value { + merged.insert(total_field.to_string(), Value::from(total)); + } + Ok(Value::Object(merged)) +} + + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn classify_and_plan_falls_back_for_unknown_module() { + let plan = + classify_and_plan("no_such_module", None, Some("extract_text"), &Value::Null).unwrap(); + assert!(matches!(plan, ChunkPlan::Single { .. })); + } + + #[test] + fn classify_and_plan_falls_back_when_no_file_path() { + let plan = + classify_and_plan("pdf_processor", None, Some("extract_text"), &Value::Null).unwrap(); + assert!(matches!(plan, ChunkPlan::Single { .. })); + } + + #[test] + fn merge_pdf_extract_text_concatenates_in_order() { + let outputs = vec![ + json!({ + "pages": ["p1", "p2", "p3"], + "page_offset": 1, + "total_pages": 7, + }), + json!({ + "pages": ["p4", "p5"], + "page_offset": 4, + "total_pages": 7, + }), + json!({ + "pages": ["p6", "p7"], + "page_offset": 6, + "total_pages": 7, + }), + ]; + let merged = merge_chunk_outputs(MergeStrategy::PdfExtractTextPages, &outputs).unwrap(); + let pages = merged + .get("pages") + .and_then(Value::as_array) + .expect("pages"); + assert_eq!(pages.len(), 7); + assert_eq!(pages[0].as_str(), Some("p1")); + assert_eq!(pages[6].as_str(), Some("p7")); + assert_eq!(merged.get("total_pages").and_then(Value::as_u64), Some(7)); + } + + #[test] + fn merge_pdf_extract_text_rejects_missing_pages_array() { + let outputs = vec![json!({ "oops": true })]; + let err = merge_chunk_outputs(MergeStrategy::PdfExtractTextPages, &outputs).unwrap_err(); + assert!(err.contains("no 'pages' array")); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs new file mode 100644 index 000000000..298411a46 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs @@ -0,0 +1,345 @@ +//! PDF-specific chunk planner. +//! +//! Classifies a PDF file by **file size on disk** (no parsing) and +//! picks a page-range chunk granularity that keeps individual guest +//! Store memory usage within the 256 MB ceiling. This is a heuristic — +//! it does not know the actual page count of the document, it only +//! knows the file size. The guest module clamps out-of-range chunk +//! requests gracefully, so overshoot is safe: if we ask for pages +//! `[201, 250]` on a 180-page document, the guest returns an empty +//! `pages` array for that chunk rather than erroring out. +//! +//! ## Heuristic +//! +//! | File size | Plan | Pages per chunk | +//! |---|---|---| +//! | ≤ 20 MB | Single (default limits) | — | +//! | 20–100 MB | Single (larger memory ceiling) | — | +//! | > 100 MB | Multi, sequential | 20 | +//! +//! The chunk count for the "Multi" bucket is hard-capped at +//! [`MAX_CHUNKS`] so a pathologically large file cannot generate +//! thousands of wasm calls. In practice this means the host gives up +//! on documents larger than ~6 GB and falls back to a single +//! best-effort call with raised limits. +//! +//! Only `extract_text` is chunkable in v1. Any other op routes through +//! the default single-call plan. + +use super::splitter; +use super::{Chunk, ChunkPlan, ChunkPlanner, MergeStrategy}; +use crate::cowork::desktop_runtime::RuntimeLimits; +use serde_json::{json, Value}; +use std::path::Path; +use std::time::Duration; + +/// Upper bound on the number of chunks the planner will emit for a +/// single call. Prevents runaway multi-call dispatch on pathologically +/// large files. +pub const MAX_CHUNKS: usize = 256; + +/// Page count per chunk in the "large file" bucket. Tuned so the +/// guest-side `lopdf` parser can process the slice comfortably inside +/// the 256 MB Store limit for typical PDFs. +pub const LARGE_FILE_CHUNK_PAGES: u32 = 20; + +/// Very rough per-page PDF size estimate used to turn "how big is the +/// file" into "how many pages does this cover, roughly". 50 KB per +/// page is pessimistic enough that the last chunk usually overshoots +/// the real document — and the guest clamps overshoot to an empty +/// `pages` array, so we are always safe to ask for more than the +/// document holds. +const BYTES_PER_PAGE_ESTIMATE: u64 = 50 * 1024; + +/// File size threshold at which the plan stops using default limits +/// and starts either bumping memory or splitting the call. +const SMALL_FILE_THRESHOLD: u64 = 20 * 1024 * 1024; + +/// File size threshold at which the plan switches from "single call +/// with larger memory" to "multi chunk". +const LARGE_FILE_THRESHOLD: u64 = 100 * 1024 * 1024; + +/// Memory ceiling for the medium bucket. 768 MB gives lopdf room to +/// parse mid-size PDFs without forcing a multi-call dispatch. +const MEDIUM_MEMORY_BYTES: usize = 768 * 1024 * 1024; + +/// Memory ceiling for chunked calls. Kept at 512 MB because the chunk +/// size is already small (20 pages) so we rarely need more. +const CHUNKED_MEMORY_BYTES: usize = 512 * 1024 * 1024; + +/// Wall-clock deadline for a single chunked call. Chunked calls are +/// each smaller than a full extraction, so they finish faster; 60 s is +/// generous. +const CHUNK_WALL_TIMEOUT_SECS: u64 = 60; + +#[derive(Debug, Default, Clone, Copy)] +pub struct PdfChunker; + +impl ChunkPlanner for PdfChunker { + fn plan( + &self, + file_path: &Path, + op: &str, + _base_input_json: &Value, + ) -> Result, String> { + // Only extract_text benefits from chunking — metadata is a + // constant-time read that already fits in any memory budget. + if op != "extract_text" { + return Ok(None); + } + + let metadata = std::fs::metadata(file_path).map_err(|error| { + format!( + "pdf chunker: cannot stat {} for chunk planning: {}", + file_path.display(), + error + ) + })?; + let size = metadata.len(); + + // For large files, use the real page count from `lopdf` on the + // host side so chunk boundaries are accurate. For small/medium + // files where memory is not at risk, skip the extra parse. + if size > LARGE_FILE_THRESHOLD { + match splitter::count_pdf_pages(file_path) { + Ok(real_page_count) => { + return Ok(Some(plan_from_page_count(real_page_count, size))); + } + Err(_) => { + // If we cannot parse (corrupt file, etc.), fall back + // to size-based heuristic. The guest will fail later + // with a more informative error. + } + } + } + + Ok(Some(plan_from_size(size))) + } +} + +/// Plan using the real page count (available for large PDFs parsed on +/// the host side via [`splitter::count_pdf_pages`]). +pub fn plan_from_page_count(page_count: u32, _file_size: u64) -> ChunkPlan { + if page_count == 0 { + return ChunkPlan::single_default(); + } + + let chunk_pages = LARGE_FILE_CHUNK_PAGES; + let ideal_chunks = page_count.div_ceil(chunk_pages).max(1) as usize; + + if ideal_chunks <= 1 { + // Small number of pages — even though the file is large (heavy + // images), a single call should be fine with raised memory. + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = MEDIUM_MEMORY_BYTES; + limits.wall_timeout = Duration::from_secs(120); + return ChunkPlan::Single { limits }; + } + + if ideal_chunks > MAX_CHUNKS { + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = MEDIUM_MEMORY_BYTES; + limits.wall_timeout = Duration::from_secs(180); + return ChunkPlan::Single { limits }; + } + + let mut chunks = Vec::with_capacity(ideal_chunks); + let mut chunk_limits = RuntimeLimits::defaults(); + chunk_limits.max_memory_bytes = CHUNKED_MEMORY_BYTES; + chunk_limits.wall_timeout = Duration::from_secs(CHUNK_WALL_TIMEOUT_SECS); + + // For accurate chunking, use the `split_source` flag that tells + // the wasm_run dispatcher to call the host-side splitter instead + // of passing a page_range param to the guest with the full file. + // This is handled externally by checking whether the chunk plan + // was built with real page counts; see `plan.uses_host_splitting`. + for index in 0..ideal_chunks { + let start = (index as u32) * chunk_pages + 1; + let end = (start + chunk_pages - 1).min(page_count); + chunks.push(Chunk { + index, + label: format!("pages {start}-{end}"), + input_json_overlay: json!({ + "page_range": [start, end] + }), + limits: chunk_limits, + }); + } + + ChunkPlan::Multi { + chunks, + merge: MergeStrategy::PdfExtractTextPages, + use_host_split: true, // accurate page count → host can split + } +} + +/// Pure decision function — extracted so tests can drive the heuristic +/// with synthetic sizes without touching the filesystem. +pub fn plan_from_size(size: u64) -> ChunkPlan { + if size <= SMALL_FILE_THRESHOLD { + return ChunkPlan::Single { + limits: RuntimeLimits::defaults(), + }; + } + + if size <= LARGE_FILE_THRESHOLD { + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = MEDIUM_MEMORY_BYTES; + return ChunkPlan::Single { limits }; + } + + // Large file: multi-chunk plan. + // + // Estimate the page count from the file size, then split into + // `LARGE_FILE_CHUNK_PAGES` page chunks. Cap at MAX_CHUNKS so a + // 100 GB corrupted input cannot generate 2 million wasm_run calls. + let estimated_pages = ((size + BYTES_PER_PAGE_ESTIMATE - 1) / BYTES_PER_PAGE_ESTIMATE) as u32; + let chunk_pages = LARGE_FILE_CHUNK_PAGES; + let ideal_chunks = estimated_pages.div_ceil(chunk_pages).max(1) as usize; + + if ideal_chunks > MAX_CHUNKS { + // Fall back to a single oversized call rather than spawning + // thousands of wasm invocations. The user will see a memory + // error if this fails — at that point the right answer is + // "too big, split manually", which the skill body documents. + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = MEDIUM_MEMORY_BYTES; + limits.wall_timeout = Duration::from_secs(180); + return ChunkPlan::Single { limits }; + } + + let mut chunks = Vec::with_capacity(ideal_chunks); + let mut chunk_limits = RuntimeLimits::defaults(); + chunk_limits.max_memory_bytes = CHUNKED_MEMORY_BYTES; + chunk_limits.wall_timeout = Duration::from_secs(CHUNK_WALL_TIMEOUT_SECS); + + for index in 0..ideal_chunks { + let start = (index as u32) * chunk_pages + 1; + let end = start + chunk_pages - 1; + chunks.push(Chunk { + index, + label: format!("pages {start}-{end}"), + input_json_overlay: json!({ + "page_range": [start, end] + }), + limits: chunk_limits, + }); + } + + ChunkPlan::Multi { + chunks, + merge: MergeStrategy::PdfExtractTextPages, + use_host_split: false, // size-only heuristic → no real page count + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn small_file_is_single_with_default_limits() { + let plan = plan_from_size(5 * 1024 * 1024); // 5 MB + match plan { + ChunkPlan::Single { limits } => { + assert_eq!(limits.max_memory_bytes, RuntimeLimits::defaults().max_memory_bytes); + } + other => panic!("expected Single, got {:?}", other), + } + } + + #[test] + fn medium_file_is_single_with_raised_memory() { + let plan = plan_from_size(50 * 1024 * 1024); // 50 MB + match plan { + ChunkPlan::Single { limits } => { + assert_eq!(limits.max_memory_bytes, MEDIUM_MEMORY_BYTES); + } + other => panic!("expected Single, got {:?}", other), + } + } + + #[test] + fn large_file_is_multi_with_page_ranges() { + let plan = plan_from_size(200 * 1024 * 1024); // 200 MB + match plan { + ChunkPlan::Multi { chunks, merge, .. } => { + assert_eq!(merge, MergeStrategy::PdfExtractTextPages); + assert!( + !chunks.is_empty() && chunks.len() <= MAX_CHUNKS, + "chunk count {} should be reasonable", + chunks.len() + ); + // Verify chunks form contiguous, non-overlapping ranges starting at 1. + let first = chunks.first().unwrap(); + let overlay = first.input_json_overlay.as_object().expect("overlay obj"); + let range = overlay + .get("page_range") + .and_then(|v| v.as_array()) + .expect("page_range array"); + assert_eq!(range[0].as_u64(), Some(1)); + assert_eq!( + range[1].as_u64(), + Some(LARGE_FILE_CHUNK_PAGES as u64) + ); + // Second chunk starts right after the first. + let second = &chunks[1]; + let overlay2 = second.input_json_overlay.as_object().expect("overlay obj"); + let range2 = overlay2 + .get("page_range") + .and_then(|v| v.as_array()) + .expect("page_range array"); + assert_eq!(range2[0].as_u64(), Some(LARGE_FILE_CHUNK_PAGES as u64 + 1)); + } + other => panic!("expected Multi, got {:?}", other), + } + } + + #[test] + fn pathologically_large_file_falls_back_to_single() { + // 100 GB — far above the MAX_CHUNKS threshold at 20 pages × + // 50 KB per page = 1 MB per chunk. 100 GB / 1 MB = 100_000 + // chunks >> MAX_CHUNKS. The planner should refuse to generate + // that many and fall back to a single oversized call. + let plan = plan_from_size(100 * 1024 * 1024 * 1024); + assert!( + matches!(plan, ChunkPlan::Single { .. }), + "expected Single fallback, got {:?}", + plan + ); + } + + #[test] + fn non_extract_text_op_is_none() { + let chunker = PdfChunker::default(); + // Even if we point at a real file that exists, metadata op + // should bypass chunking. + let tempdir = std::env::temp_dir(); + let result = chunker.plan(&tempdir, "metadata", &Value::Null).unwrap(); + assert!(result.is_none()); + } + + /// End-to-end wiring: hand the real planner the small fixture PDF + /// from `tests/fixtures/`. It is well under 20 MB so the plan must + /// be a Single with default limits. This proves that the I/O path + /// works (stat the file, size bucket, return plan). + #[test] + fn small_fixture_pdf_is_single_plan() { + let fixture = + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hello.pdf"); + assert!(fixture.exists(), "fixture missing: {}", fixture.display()); + let chunker = PdfChunker::default(); + let plan = chunker + .plan(&fixture, "extract_text", &Value::Null) + .expect("plan ok"); + match plan { + Some(ChunkPlan::Single { limits }) => { + assert_eq!( + limits.max_memory_bytes, + RuntimeLimits::defaults().max_memory_bytes + ); + } + other => panic!("expected Single, got {:?}", other), + } + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pptx.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pptx.rs new file mode 100644 index 000000000..2aa84d3f3 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pptx.rs @@ -0,0 +1,72 @@ +//! Pptx-specific chunk planner. +//! +//! Like docx, pptx cannot be host-side split. For files > 100 MB the +//! planner raises memory to 1 GB and runs a single call. + +use super::{ChunkPlan, ChunkPlanner}; +use crate::cowork::desktop_runtime::RuntimeLimits; +use serde_json::Value; +use std::path::Path; +use std::time::Duration; + +const SMALL_THRESHOLD: u64 = 20 * 1024 * 1024; +const LARGE_THRESHOLD: u64 = 100 * 1024 * 1024; +const MEDIUM_MEMORY: usize = 768 * 1024 * 1024; +const LARGE_MEMORY: usize = 1024 * 1024 * 1024; + +#[derive(Debug, Default, Clone, Copy)] +pub struct PptxChunker; + +impl ChunkPlanner for PptxChunker { + fn plan( + &self, + file_path: &Path, + op: &str, + _base: &Value, + ) -> Result, String> { + if op != "extract_text" { + return Ok(None); + } + let size = std::fs::metadata(file_path) + .map_err(|e| format!("pptx chunker: stat {}: {e}", file_path.display()))? + .len(); + Ok(Some(plan_from_size(size))) + } +} + +pub fn plan_from_size(size: u64) -> ChunkPlan { + if size <= SMALL_THRESHOLD { + return ChunkPlan::single_default(); + } + if size <= LARGE_THRESHOLD { + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = MEDIUM_MEMORY; + return ChunkPlan::Single { limits }; + } + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = LARGE_MEMORY; + limits.wall_timeout = Duration::from_secs(120); + ChunkPlan::Single { limits } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn small_file_single() { + let plan = plan_from_size(5 * 1024 * 1024); + assert!(matches!(plan, ChunkPlan::Single { .. })); + } + + #[test] + fn large_file_single_1gb() { + let plan = plan_from_size(200 * 1024 * 1024); + match plan { + ChunkPlan::Single { limits } => { + assert_eq!(limits.max_memory_bytes, LARGE_MEMORY); + } + _ => panic!("expected Single with 1GB"), + } + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs new file mode 100644 index 000000000..aafc6230e --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs @@ -0,0 +1,161 @@ +//! Host-side file splitting for memory-sensitive chunk dispatch. +//! +//! The key problem: guest modules like `pdf_processor` use `lopdf` which +//! calls `Document::load()` on the full file — meaning even if we pass +//! `page_range: [1, 20]`, the guest still loads the full 200 MB into +//! linear memory before extracting the requested pages. This negates +//! the memory benefit of chunking. +//! +//! The fix: split the source file **on the host side** into smaller +//! files (one per chunk), then pass each chunk-file to its own sandbox +//! call. The guest sees a small file and stays within its memory budget. +//! +//! ## Scope +//! +//! v1 supports PDF splitting only. DOCX/XLSX/PPTX are fundamentally +//! different (zipped OOXML containers where you can't extract a subset +//! of pages without rewriting the entire archive), so they skip host-side +//! splitting and continue to pass the full file + range params to the +//! guest. This means large DOCX/XLSX/PPTX may still OOM in the guest, +//! which the skill body documents as a limitation. + +use std::fs; +use std::path::{Path, PathBuf}; + +/// Split a PDF file into multiple smaller PDFs, each containing a +/// contiguous range of pages. Returns a list of `(chunk_file_path, +/// page_start, page_end)` tuples. The chunk files are written to +/// `output_dir` as `chunk_0.pdf`, `chunk_1.pdf`, etc. +/// +/// If the file has fewer pages than `pages_per_chunk`, a single chunk +/// containing the entire document is returned. +/// +/// This function uses `lopdf` on the host side (native, not sandboxed) +/// because it needs to parse the PDF structure to split pages. +pub fn split_pdf_pages( + source: &Path, + output_dir: &Path, + pages_per_chunk: u32, +) -> Result, String> { + // First pass: count pages without keeping the full doc in memory. + let page_numbers = { + let doc = lopdf::Document::load(source) + .map_err(|e| format!("split_pdf: failed to load {}: {e}", source.display()))?; + let pages = doc.get_pages(); + let mut nums: Vec = pages.keys().copied().collect(); + nums.sort_unstable(); + nums + // `doc` drops here — releases memory before we start splitting. + }; + let total = page_numbers.len() as u32; + + if total == 0 { + return Ok(vec![]); + } + + fs::create_dir_all(output_dir) + .map_err(|e| format!("split_pdf: mkdir {}: {e}", output_dir.display()))?; + + let mut chunks = Vec::new(); + let mut chunk_idx = 0u32; + let mut start = 0usize; + + while start < page_numbers.len() { + let end = (start + pages_per_chunk as usize).min(page_numbers.len()); + let chunk_pages: std::collections::HashSet = + page_numbers[start..end].iter().copied().collect(); + + // Reload from disk for each chunk so we never hold more than + // 1 copy of the document in memory at a time. This trades I/O + // (re-read source N times) for memory (peak = 1× doc size + // instead of N× doc size from cloning). + let mut chunk_doc = lopdf::Document::load(source) + .map_err(|e| format!("split_pdf: reload for chunk {chunk_idx}: {e}"))?; + + let pages_to_remove: Vec = chunk_doc + .get_pages() + .keys() + .copied() + .filter(|num| !chunk_pages.contains(num)) + .collect(); + for page_num in pages_to_remove { + chunk_doc.delete_pages(&[page_num]); + } + + let chunk_path = output_dir.join(format!("chunk_{chunk_idx}.pdf")); + chunk_doc + .save(&chunk_path) + .map_err(|e| format!("split_pdf: save chunk_{chunk_idx}: {e}"))?; + + chunks.push(PdfChunkFile { + path: chunk_path, + page_start: (start as u32) + 1, + page_end: end as u32, + total_pages: total, + }); + + start = end; + chunk_idx += 1; + // `chunk_doc` drops here — memory freed before next iteration. + } + + Ok(chunks) +} + +/// Metadata about a host-side chunk file produced by [`split_pdf_pages`]. +#[derive(Debug, Clone)] +pub struct PdfChunkFile { + /// Path to the chunk PDF on the host filesystem. + pub path: PathBuf, + /// 1-based start page (inclusive) relative to the original document. + pub page_start: u32, + /// 1-based end page (inclusive) relative to the original document. + pub page_end: u32, + /// Total pages in the original document. + pub total_pages: u32, +} + + +/// Count pages in a PDF without extracting content. Cheap compared to +/// full extraction — we only parse the xref table and page tree, never +/// decompress streams. Used by the chunker when it needs an accurate +/// page count rather than the size-based heuristic. +pub fn count_pdf_pages(source: &Path) -> Result { + let doc = lopdf::Document::load(source) + .map_err(|e| format!("count_pdf_pages: failed to load {}: {e}", source.display()))?; + Ok(doc.get_pages().len() as u32) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn count_pages_on_fixture() { + let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hello.pdf"); + let count = count_pdf_pages(&fixture).expect("count ok"); + assert_eq!(count, 1); + } + + #[test] + fn split_single_page_produces_one_chunk() { + let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hello.pdf"); + let output_dir = std::env::temp_dir().join(format!( + "split-test-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or_default() + )); + let chunks = split_pdf_pages(&fixture, &output_dir, 10).expect("split ok"); + assert_eq!(chunks.len(), 1); + assert_eq!(chunks[0].page_start, 1); + assert_eq!(chunks[0].page_end, 1); + assert_eq!(chunks[0].total_pages, 1); + assert!(chunks[0].path.exists()); + // Verify chunk is a valid PDF by loading it. + let chunk_doc = lopdf::Document::load(&chunks[0].path).expect("chunk is valid pdf"); + assert_eq!(chunk_doc.get_pages().len(), 1); + fs::remove_dir_all(&output_dir).ok(); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/xlsx.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/xlsx.rs new file mode 100644 index 000000000..a5fb39b3c --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/xlsx.rs @@ -0,0 +1,72 @@ +//! Xlsx-specific chunk planner. +//! +//! Like docx, xlsx cannot be host-side split. For files > 100 MB the +//! planner raises memory to 1 GB and runs a single call. + +use super::{ChunkPlan, ChunkPlanner}; +use crate::cowork::desktop_runtime::RuntimeLimits; +use serde_json::Value; +use std::path::Path; +use std::time::Duration; + +const SMALL_THRESHOLD: u64 = 20 * 1024 * 1024; +const LARGE_THRESHOLD: u64 = 100 * 1024 * 1024; +const MEDIUM_MEMORY: usize = 768 * 1024 * 1024; +const LARGE_MEMORY: usize = 1024 * 1024 * 1024; + +#[derive(Debug, Default, Clone, Copy)] +pub struct XlsxChunker; + +impl ChunkPlanner for XlsxChunker { + fn plan( + &self, + file_path: &Path, + op: &str, + _base: &Value, + ) -> Result, String> { + if op != "read_sheet" { + return Ok(None); + } + let size = std::fs::metadata(file_path) + .map_err(|e| format!("xlsx chunker: stat {}: {e}", file_path.display()))? + .len(); + Ok(Some(plan_from_size(size))) + } +} + +pub fn plan_from_size(size: u64) -> ChunkPlan { + if size <= SMALL_THRESHOLD { + return ChunkPlan::single_default(); + } + if size <= LARGE_THRESHOLD { + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = MEDIUM_MEMORY; + return ChunkPlan::Single { limits }; + } + let mut limits = RuntimeLimits::defaults(); + limits.max_memory_bytes = LARGE_MEMORY; + limits.wall_timeout = Duration::from_secs(120); + ChunkPlan::Single { limits } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn small_file_single() { + let plan = plan_from_size(5 * 1024 * 1024); + assert!(matches!(plan, ChunkPlan::Single { .. })); + } + + #[test] + fn large_file_single_1gb() { + let plan = plan_from_size(200 * 1024 * 1024); + match plan { + ChunkPlan::Single { limits } => { + assert_eq!(limits.max_memory_bytes, LARGE_MEMORY); + } + _ => panic!("expected Single with 1GB"), + } + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/freshness.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/freshness.rs new file mode 100644 index 000000000..44194a476 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/freshness.rs @@ -0,0 +1,104 @@ +//! Module freshness check utilities. +//! +//! At compile time, `include_bytes!` embeds the `.wasm` file bytes into +//! the binary. But there's no mechanism to detect if the `.wasm` file +//! is stale (older than the guest source code). This module provides a +//! runtime check that can log a warning if the compiled module timestamp +//! is older than expected. This is purely a dev-experience helper and +//! does not affect production behavior. +//! +//! ## Usage +//! +//! Call `check_module_freshness()` during runtime startup (e.g., inside +//! `register_builtin_modules`). It compares the mtime of source files +//! against the mtime of the compiled .wasm artifact. If the source is +//! newer, it can print a warning to stderr when explicitly enabled. +//! +//! In release builds (production), this is a no-op because the paths +//! are relative to the source tree which doesn't exist on end-user +//! machines. +//! +//! To avoid noisy logs during normal desktop development, warnings are +//! disabled by default. Set `II_AGENT_WASM_FRESHNESS_WARN=1` (or `true`, +//! `yes`, `on`) to enable them. + +use std::path::Path; + +/// Check whether a compiled `.wasm` module is fresh relative to its +/// guest source. Returns `true` if fresh or if the check cannot be +/// performed (missing files, release build, etc.). +/// +/// Logs a warning to stderr when stale if +/// `II_AGENT_WASM_FRESHNESS_WARN` is enabled. +pub fn check_freshness(module_name: &str, wasm_path: &str, source_dir: &str) -> bool { + let wasm = Path::new(wasm_path); + let source = Path::new(source_dir); + + if !wasm.exists() || !source.exists() { + return true; // can't check — assume fresh + } + + let wasm_mtime = match wasm.metadata().and_then(|m| m.modified()) { + Ok(t) => t, + Err(_) => return true, + }; + + // Walk source dir and find the newest .rs file. + let newest_source = match find_newest_rs(source) { + Some(t) => t, + None => return true, + }; + + if newest_source > wasm_mtime { + if !warnings_enabled() { + return false; + } + eprintln!( + "[warn] desktop_runtime: module '{module_name}' may be stale. \ + Source in {source_dir} is newer than {wasm_path}. \ + Rebuild with: cd {source_dir} && cargo build --target wasm32-wasip1 --release && cp target/wasm32-wasip1/release/{module_name}.wasm {wasm_path}" + ); + return false; + } + true +} + +fn warnings_enabled() -> bool { + std::env::var("II_AGENT_WASM_FRESHNESS_WARN") + .ok() + .map(|value| { + matches!( + value.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) + .unwrap_or(false) +} + +fn find_newest_rs(dir: &Path) -> Option { + let mut newest: Option = None; + let entries = std::fs::read_dir(dir).ok()?; + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + if let Some(t) = find_newest_rs(&path) { + newest = Some(newest.map_or(t, |cur| cur.max(t))); + } + } else if path.extension().is_some_and(|ext| ext == "rs") { + if let Ok(mtime) = path.metadata().and_then(|m| m.modified()) { + newest = Some(newest.map_or(mtime, |cur| cur.max(mtime))); + } + } + } + newest +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_freshness_returns_true_for_missing_paths() { + assert!(check_freshness("foo", "/nonexistent/foo.wasm", "/nonexistent/src")); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/.gitignore b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/.gitignore new file mode 100644 index 000000000..2c96eb1b6 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/Cargo.toml b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/Cargo.toml new file mode 100644 index 000000000..c03205075 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "docx_processor" +version = "0.1.0" +edition = "2021" +description = "WASI preview1 module for the desktop docx skill. Parses Word OOXML containers with docx-rs inside the cowork WASM runtime." +license = "MIT" + +[[bin]] +name = "docx_processor" +path = "src/main.rs" + +[dependencies] +docx-rs = { version = "0.4", default-features = false } +serde_json = { version = "1", default-features = false, features = ["std"] } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +panic = "abort" diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/src/main.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/src/main.rs new file mode 100644 index 000000000..4da5550a0 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/docx_processor/src/main.rs @@ -0,0 +1,166 @@ +//! docx_processor — WASI preview1 module for the desktop `docx` skill. +//! +//! Execution contract (matches `docx.skill.md`): +//! +//! * input: +//! - `/workspace/input.json` — one of: +//! - `{ "op": "extract_text" }` +//! - `{ "op": "extract_text", "paragraph_range": [start, end] }` +//! 1-based inclusive paragraph numbers. Out-of-range requests +//! are clamped to the document length. +//! - `{ "op": "count_paragraphs" }` +//! - `/workspace/inputs/input.docx` — the Word document to process +//! * output: +//! - `/workspace/output.json` — for extract_text: +//! `{ "paragraphs": string[], "paragraph_offset": n, "total_paragraphs": m }` +//! for count_paragraphs: +//! `{ "total_paragraphs": n }` +//! - stdout — short "ok" line for human debugging +//! * exit code 0 on success, 1 on any failure. +//! +//! docx-rs is a pure-Rust DOCX reader/writer; it compiles cleanly to +//! `wasm32-wasip1` with `default-features = false`. The module stays +//! small because it only uses the reader path. + +use std::fs; +use std::io::Write; + +use docx_rs::*; +use serde_json::{json, Value}; + +const INPUT_JSON: &str = "/workspace/input.json"; +const INPUT_DOCX: &str = "/workspace/inputs/input.docx"; +const OUTPUT_JSON: &str = "/workspace/output.json"; + +fn main() { + if let Err(error) = run() { + let _ = writeln!(std::io::stderr(), "docx_processor error: {error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let input = read_input()?; + let bytes = fs::read(INPUT_DOCX) + .map_err(|error| format!("failed to read {INPUT_DOCX}: {error}"))?; + let doc = read_docx(&bytes).map_err(|error| format!("docx parse failed: {error:?}"))?; + + let paragraphs = collect_paragraphs(&doc); + + let output = match input.op.as_str() { + "extract_text" => extract_text(¶graphs, input.paragraph_range.as_ref())?, + "count_paragraphs" => json!({ "total_paragraphs": paragraphs.len() as u64 }), + other => return Err(format!("unknown operation '{other}'")), + }; + + fs::write(OUTPUT_JSON, output.to_string()) + .map_err(|error| format!("failed to write {OUTPUT_JSON}: {error}"))?; + println!("docx_processor: op={} ok", input.op); + Ok(()) +} + +struct InputSpec { + op: String, + paragraph_range: Option<(u32, u32)>, +} + +fn read_input() -> Result { + let bytes = fs::read(INPUT_JSON) + .map_err(|error| format!("failed to read {INPUT_JSON}: {error}"))?; + let value: Value = serde_json::from_slice(&bytes) + .map_err(|error| format!("failed to parse {INPUT_JSON}: {error}"))?; + let op = value + .get("op") + .and_then(Value::as_str) + .map(str::to_string) + .ok_or_else(|| "input.json is missing 'op'".to_string())?; + let paragraph_range = parse_range(&value, "paragraph_range")?; + Ok(InputSpec { + op, + paragraph_range, + }) +} + +fn parse_range(value: &Value, key: &str) -> Result, String> { + match value.get(key) { + Some(Value::Null) | None => Ok(None), + Some(Value::Array(items)) => { + if items.len() != 2 { + return Err(format!( + "{key} must be a 2-element array, got {}", + items.len() + )); + } + let start = items[0] + .as_u64() + .ok_or_else(|| format!("{key}[0] must be a non-negative integer"))?; + let end = items[1] + .as_u64() + .ok_or_else(|| format!("{key}[1] must be a non-negative integer"))?; + Ok(Some((start as u32, end as u32))) + } + Some(other) => Err(format!("{key} must be array or null, got {other:?}")), + } +} + +/// Walk the docx document tree and collect every paragraph's plain +/// text in reading order. We intentionally flatten away run styling, +/// tables, headers, footers — v1 only cares about body text. Future +/// ops can walk the tree more carefully. +fn collect_paragraphs(doc: &Docx) -> Vec { + let mut out: Vec = Vec::new(); + for child in &doc.document.children { + if let DocumentChild::Paragraph(p) = child { + out.push(paragraph_text(p)); + } + } + out +} + +fn paragraph_text(paragraph: &Paragraph) -> String { + let mut text = String::new(); + for child in ¶graph.children { + if let ParagraphChild::Run(run) = child { + for run_child in &run.children { + if let RunChild::Text(t) = run_child { + text.push_str(&t.text); + } + } + } + } + text +} + +fn extract_text(paragraphs: &[String], range: Option<&(u32, u32)>) -> Result { + let total = paragraphs.len() as u32; + let (slice, offset) = if let Some(&(start, end)) = range { + if start == 0 { + return Err("paragraph_range start must be 1 or greater".to_string()); + } + if start > end { + return Ok(json!({ + "paragraphs": Vec::::new(), + "paragraph_offset": start, + "total_paragraphs": total, + })); + } + let start_idx = (start - 1) as usize; + if start_idx >= paragraphs.len() { + return Ok(json!({ + "paragraphs": Vec::::new(), + "paragraph_offset": start, + "total_paragraphs": total, + })); + } + let end_idx = (end as usize).min(paragraphs.len()); + (¶graphs[start_idx..end_idx], start) + } else { + (¶graphs[..], 1u32) + }; + + Ok(json!({ + "paragraphs": slice, + "paragraph_offset": offset, + "total_paragraphs": total, + })) +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/Cargo.toml b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/Cargo.toml new file mode 100644 index 000000000..02a6df79d --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pdf_processor" +version = "0.1.0" +edition = "2021" +description = "WASI preview1 module for desktop pdf skill. Runs inside the wasmtime sandbox owned by cowork desktop runtime." +license = "MIT" + +[[bin]] +name = "pdf_processor" +path = "src/main.rs" + +[dependencies] +lopdf = { version = "0.36", default-features = false } +serde_json = { version = "1", default-features = false, features = ["std"] } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +panic = "abort" diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/src/main.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/src/main.rs new file mode 100644 index 000000000..6dc5fac04 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pdf_processor/src/main.rs @@ -0,0 +1,199 @@ +//! pdf_processor — WASI preview1 module for the desktop `pdf` skill. +//! +//! Execution contract (matches `pdf.skill.md`): +//! +//! * input: +//! - `/workspace/input.json` — one of: +//! - `{ "op": "extract_text" }` +//! - `{ "op": "extract_text", "page_range": [start, end] }` +//! where `start` and `end` are 1-based **inclusive** page +//! numbers. If the range extends past the document, the module +//! clamps to the last page. If `start > end` or the range is +//! empty, the module returns `{"pages": []}` without error. +//! - `{ "op": "metadata" }` +//! - `/workspace/inputs/input.pdf` — the PDF to process +//! * output: +//! - `/workspace/output.json` — +//! - extract_text: `{ "pages": string[], "page_offset": n, "total_pages": m }` +//! `page_offset` is the 1-based page number of the first entry +//! in `pages`. `total_pages` is the document's full length. +//! Host-side chunking relies on both fields to merge chunks back +//! into document order. +//! - metadata: `{ "title": ..., "author": ..., "page_count": n }` +//! - stdout — a short human-readable line so the wasm_run tool result +//! has something useful even when output.json is also present. +//! * exit code 0 on success, 1 on any failure. +//! +//! The module is compiled for `wasm32-wasip1` and loaded by the cowork +//! desktop WASM runtime. It deliberately depends only on `lopdf` + the +//! standard library so the binary stays small. + +use std::fs; +use std::io::Write; + +use lopdf::Document; +use serde_json::{json, Value}; + +const INPUT_JSON: &str = "/workspace/input.json"; +const INPUT_PDF: &str = "/workspace/inputs/input.pdf"; +const OUTPUT_JSON: &str = "/workspace/output.json"; + +fn main() { + if let Err(error) = run() { + let _ = writeln!(std::io::stderr(), "pdf_processor error: {error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let input = read_input()?; + let document = Document::load(INPUT_PDF) + .map_err(|error| format!("failed to load {INPUT_PDF}: {error}"))?; + + let output = match input.op.as_str() { + "extract_text" => extract_text(&document, input.page_range.as_ref())?, + "metadata" => metadata(&document)?, + other => return Err(format!("unknown operation '{other}'")), + }; + + fs::write(OUTPUT_JSON, output.to_string()) + .map_err(|error| format!("failed to write {OUTPUT_JSON}: {error}"))?; + + println!("pdf_processor: op={} ok", input.op); + Ok(()) +} + +struct InputSpec { + op: String, + /// Optional 1-based inclusive page range. `None` means "all pages". + page_range: Option<(u32, u32)>, +} + +fn read_input() -> Result { + let bytes = fs::read(INPUT_JSON) + .map_err(|error| format!("failed to read {INPUT_JSON}: {error}"))?; + let value: Value = serde_json::from_slice(&bytes) + .map_err(|error| format!("failed to parse {INPUT_JSON}: {error}"))?; + let op = value + .get("op") + .and_then(Value::as_str) + .map(str::to_string) + .ok_or_else(|| "input.json is missing the 'op' field".to_string())?; + + let page_range = match value.get("page_range") { + Some(Value::Null) | None => None, + Some(Value::Array(items)) => { + if items.len() != 2 { + return Err(format!( + "page_range must be a 2-element array [start, end], got {} elements", + items.len() + )); + } + let start = items[0] + .as_u64() + .ok_or_else(|| "page_range[0] must be a non-negative integer".to_string())?; + let end = items[1] + .as_u64() + .ok_or_else(|| "page_range[1] must be a non-negative integer".to_string())?; + Some((start as u32, end as u32)) + } + Some(other) => { + return Err(format!( + "page_range must be an array or null, got {}", + kind_of(other) + )); + } + }; + Ok(InputSpec { op, page_range }) +} + +fn kind_of(value: &Value) -> &'static str { + match value { + Value::Null => "null", + Value::Bool(_) => "bool", + Value::Number(_) => "number", + Value::String(_) => "string", + Value::Array(_) => "array", + Value::Object(_) => "object", + } +} + +fn extract_text(document: &Document, page_range: Option<&(u32, u32)>) -> Result { + let pages = document.get_pages(); + let mut ordered: Vec = pages.keys().copied().collect(); + ordered.sort_unstable(); + let total_pages = ordered.len() as u32; + + // Determine the slice of `ordered` to extract. + // + // The range is 1-based inclusive on both ends to match the contract + // in `pdf.skill.md`. We clamp both ends so the host-side chunker + // does not need to know the document length up front: it can fire + // [1, 50], [51, 100], ... and the last chunk will simply return + // fewer pages than requested when it runs off the end. + let (slice, page_offset) = if let Some(&(start, end)) = page_range { + if start == 0 { + return Err("page_range start must be 1 or greater".to_string()); + } + if start > end { + return Ok(json!({ + "pages": Vec::::new(), + "page_offset": start, + "total_pages": total_pages, + })); + } + let start_idx = (start - 1) as usize; + if start_idx >= ordered.len() { + return Ok(json!({ + "pages": Vec::::new(), + "page_offset": start, + "total_pages": total_pages, + })); + } + let end_idx = (end as usize).min(ordered.len()); + (&ordered[start_idx..end_idx], start) + } else { + (&ordered[..], 1u32) + }; + + let mut pages_out: Vec = Vec::with_capacity(slice.len()); + for &page_num in slice { + let text = document + .extract_text(&[page_num]) + .map_err(|error| format!("page {page_num} extraction failed: {error}"))?; + pages_out.push(text); + } + Ok(json!({ + "pages": pages_out, + "page_offset": page_offset, + "total_pages": total_pages, + })) +} + +fn metadata(document: &Document) -> Result { + let page_count = document.get_pages().len(); + let info = document.trailer.get(b"Info").ok(); + let mut title: Option = None; + let mut author: Option = None; + if let Some(info_ref) = info { + if let Ok(object_id) = info_ref.as_reference() { + if let Ok(info_dict) = document.get_object(object_id).and_then(|o| o.as_dict()) { + title = read_text_field(info_dict, b"Title"); + author = read_text_field(info_dict, b"Author"); + } + } + } + Ok(json!({ + "title": title, + "author": author, + "page_count": page_count, + })) +} + +fn read_text_field(dict: &lopdf::Dictionary, key: &[u8]) -> Option { + let object = dict.get(key).ok()?; + if let Ok(bytes) = object.as_str() { + return Some(String::from_utf8_lossy(bytes).into_owned()); + } + None +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/Cargo.toml b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/Cargo.toml new file mode 100644 index 000000000..73cd4c84a --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pptx_processor" +version = "0.1.0" +edition = "2021" +description = "WASI preview1 module for the desktop pptx skill. Extracts slide text from PowerPoint containers using zip + quick-xml." +license = "MIT" + +[[bin]] +name = "pptx_processor" +path = "src/main.rs" + +[dependencies] +zip = { version = "2", default-features = false, features = ["deflate"] } +quick-xml = { version = "0.36", default-features = false } +serde_json = { version = "1", default-features = false, features = ["std"] } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +panic = "abort" diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/src/main.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/src/main.rs new file mode 100644 index 000000000..09aa9218f --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/pptx_processor/src/main.rs @@ -0,0 +1,262 @@ +//! pptx_processor — WASI preview1 module for the desktop `pptx` skill. +//! +//! Execution contract (matches `pptx.skill.md`): +//! +//! * input: +//! - `/workspace/input.json` — one of: +//! - `{ "op": "extract_text" }` +//! - `{ "op": "extract_text", "slide_range": [start, end] }` +//! - `{ "op": "count_slides" }` +//! - `/workspace/inputs/input.pptx` — the presentation to process +//! * output: +//! - `/workspace/output.json`: +//! extract_text: `{ "slides": [{"index": n, "text": string}], "slide_offset": n, "total_slides": m }` +//! count_slides: `{ "total_slides": n }` +//! - stdout — short "ok" line +//! * exit code 0 on success, 1 on any failure. +//! +//! pptx is an OOXML zip container. Slide text lives in +//! `ppt/slides/slideN.xml` where N is a 1-based index. For v1 we: +//! +//! 1. Enumerate every `ppt/slides/slide*.xml` entry in the zip. +//! 2. Sort them by numeric index (slide1, slide2, ... slide10). +//! 3. For each slide in scope, parse the XML and concatenate every +//! `` text run into a single string. +//! +//! This is the minimum viable slide text extraction. It intentionally +//! ignores speaker notes (`ppt/notesSlides/...`), comments, and layout +//! metadata — those are future ops. + +use std::fs; +use std::io::{Cursor, Write}; + +use quick_xml::events::Event; +use quick_xml::reader::Reader; +use serde_json::{json, Value}; +use zip::ZipArchive; + +const INPUT_JSON: &str = "/workspace/input.json"; +const INPUT_PPTX: &str = "/workspace/inputs/input.pptx"; +const OUTPUT_JSON: &str = "/workspace/output.json"; + +fn main() { + if let Err(error) = run() { + let _ = writeln!(std::io::stderr(), "pptx_processor error: {error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let input = read_input()?; + let bytes = fs::read(INPUT_PPTX) + .map_err(|error| format!("failed to read {INPUT_PPTX}: {error}"))?; + let mut archive = ZipArchive::new(Cursor::new(bytes)) + .map_err(|error| format!("pptx zip open failed: {error}"))?; + + let slides = collect_slide_entries(&mut archive)?; + + let output = match input.op.as_str() { + "extract_text" => extract_text(&mut archive, &slides, input.slide_range.as_ref())?, + "count_slides" => json!({ "total_slides": slides.len() as u64 }), + other => return Err(format!("unknown operation '{other}'")), + }; + + fs::write(OUTPUT_JSON, output.to_string()) + .map_err(|error| format!("failed to write {OUTPUT_JSON}: {error}"))?; + println!("pptx_processor: op={} ok", input.op); + Ok(()) +} + +struct InputSpec { + op: String, + slide_range: Option<(u32, u32)>, +} + +fn read_input() -> Result { + let bytes = fs::read(INPUT_JSON) + .map_err(|error| format!("failed to read {INPUT_JSON}: {error}"))?; + let value: Value = serde_json::from_slice(&bytes) + .map_err(|error| format!("failed to parse {INPUT_JSON}: {error}"))?; + let op = value + .get("op") + .and_then(Value::as_str) + .map(str::to_string) + .ok_or_else(|| "input.json is missing 'op'".to_string())?; + let slide_range = parse_range(&value, "slide_range")?; + Ok(InputSpec { op, slide_range }) +} + +fn parse_range(value: &Value, key: &str) -> Result, String> { + match value.get(key) { + Some(Value::Null) | None => Ok(None), + Some(Value::Array(items)) => { + if items.len() != 2 { + return Err(format!( + "{key} must be a 2-element array, got {}", + items.len() + )); + } + let start = items[0] + .as_u64() + .ok_or_else(|| format!("{key}[0] must be a non-negative integer"))?; + let end = items[1] + .as_u64() + .ok_or_else(|| format!("{key}[1] must be a non-negative integer"))?; + Ok(Some((start as u32, end as u32))) + } + Some(other) => Err(format!("{key} must be array or null, got {other:?}")), + } +} + +/// One zip entry representing a slide, pre-sorted by 1-based slide +/// number. +struct SlideEntry { + /// 1-based slide index (`ppt/slides/slide1.xml` → 1). + index: u32, + /// Path inside the archive, used to `by_name` lookup the raw bytes + /// at extraction time. + path: String, +} + +fn collect_slide_entries( + archive: &mut ZipArchive, +) -> Result, String> { + let mut entries: Vec = Vec::new(); + for index in 0..archive.len() { + let file = archive + .by_index(index) + .map_err(|error| format!("pptx archive read failed: {error}"))?; + let name = file.name().to_string(); + if let Some(slide_index) = parse_slide_path(&name) { + entries.push(SlideEntry { + index: slide_index, + path: name, + }); + } + } + entries.sort_by_key(|entry| entry.index); + Ok(entries) +} + +/// Match `ppt/slides/slideN.xml` (case-sensitive, the OOXML spec fixes +/// the casing). Returns the 1-based slide number on match. +fn parse_slide_path(path: &str) -> Option { + let file_name = path.strip_prefix("ppt/slides/")?; + if !file_name.ends_with(".xml") { + return None; + } + let stem = &file_name[..file_name.len() - 4]; + let number = stem.strip_prefix("slide")?; + // Reject `slideLayoutN.xml`, `slideMasterN.xml`, etc. + if !number.chars().all(|c| c.is_ascii_digit()) { + return None; + } + number.parse().ok() +} + +fn extract_text( + archive: &mut ZipArchive, + entries: &[SlideEntry], + range: Option<&(u32, u32)>, +) -> Result { + let total = entries.len() as u32; + let (slice, offset) = if let Some(&(start, end)) = range { + if start == 0 { + return Err("slide_range start must be 1 or greater".to_string()); + } + if start > end { + return Ok(json!({ + "slides": Vec::::new(), + "slide_offset": start, + "total_slides": total, + })); + } + let start_idx = (start - 1) as usize; + if start_idx >= entries.len() { + return Ok(json!({ + "slides": Vec::::new(), + "slide_offset": start, + "total_slides": total, + })); + } + let end_idx = (end as usize).min(entries.len()); + (&entries[start_idx..end_idx], start) + } else { + (entries, 1u32) + }; + + let mut slides_out: Vec = Vec::with_capacity(slice.len()); + for entry in slice { + let mut zip_file = archive + .by_name(&entry.path) + .map_err(|error| format!("pptx: cannot read {}: {}", entry.path, error))?; + let mut xml_bytes = Vec::new(); + std::io::copy(&mut zip_file, &mut xml_bytes) + .map_err(|error| format!("pptx: zip read failed: {error}"))?; + let text = extract_slide_text(&xml_bytes)?; + slides_out.push(json!({ + "index": entry.index, + "text": text, + })); + } + + Ok(json!({ + "slides": slides_out, + "slide_offset": offset, + "total_slides": total, + })) +} + +/// Walk a slide XML document and return every `` run concatenated. +/// Text inside different paragraphs is joined with a single newline so +/// the agent can reconstruct reading order without parsing structure. +fn extract_slide_text(xml_bytes: &[u8]) -> Result { + let mut reader = Reader::from_reader(xml_bytes); + reader.config_mut().trim_text(true); + let mut in_text = false; + let mut in_paragraph = false; + let mut current_paragraph = String::new(); + let mut paragraphs: Vec = Vec::new(); + let mut buf = Vec::new(); + + loop { + match reader + .read_event_into(&mut buf) + .map_err(|error| format!("pptx xml parse error: {error}"))? + { + Event::Start(e) => { + let name = e.name(); + let local = name.as_ref(); + if local.ends_with(b":t") || local == b"t" { + in_text = true; + } else if local.ends_with(b":p") || local == b"p" { + in_paragraph = true; + current_paragraph.clear(); + } + } + Event::End(e) => { + let name = e.name(); + let local = name.as_ref(); + if local.ends_with(b":t") || local == b"t" { + in_text = false; + } else if local.ends_with(b":p") || local == b"p" { + in_paragraph = false; + if !current_paragraph.is_empty() { + paragraphs.push(current_paragraph.clone()); + } + } + } + Event::Text(t) if in_text && in_paragraph => { + let decoded = t + .unescape() + .map_err(|error| format!("pptx xml unescape error: {error}"))?; + current_paragraph.push_str(&decoded); + } + Event::Eof => break, + _ => {} + } + buf.clear(); + } + + Ok(paragraphs.join("\n")) +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/Cargo.toml b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/Cargo.toml new file mode 100644 index 000000000..e7de55316 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "xlsx_processor" +version = "0.1.0" +edition = "2021" +description = "WASI preview1 module for the desktop xlsx skill. Reads spreadsheet workbooks with calamine inside the cowork WASM runtime." +license = "MIT" + +[[bin]] +name = "xlsx_processor" +path = "src/main.rs" + +[dependencies] +calamine = { version = "0.26", default-features = false } +serde_json = { version = "1", default-features = false, features = ["std"] } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +panic = "abort" diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/src/main.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/src/main.rs new file mode 100644 index 000000000..f284cff5c --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/guest/xlsx_processor/src/main.rs @@ -0,0 +1,204 @@ +//! xlsx_processor — WASI preview1 module for the desktop `xlsx` skill. +//! +//! Execution contract (matches `xlsx.skill.md`): +//! +//! * input: +//! - `/workspace/input.json` — one of: +//! - `{ "op": "list_sheets" }` +//! - `{ "op": "read_sheet", "sheet": "" }` +//! - `{ "op": "read_sheet", "sheet": "", "row_range": [start, end] }` +//! 1-based inclusive row numbers. Out-of-range requests clamp +//! to the sheet's used range. +//! - `/workspace/inputs/input.xlsx` — the workbook to process +//! * output: +//! - `/workspace/output.json`: +//! list_sheets: `{ "sheets": [{"name": string, "rows": n, "cols": m}] }` +//! read_sheet: `{ "sheet": string, "rows": Value[][], "row_offset": n, "total_rows": m }` +//! - stdout — short "ok" line +//! * exit code 0 on success, 1 on any failure. +//! +//! calamine is a pure-Rust xlsx/xls/ods reader. It compiles cleanly to +//! `wasm32-wasip1` with `default-features = false`. Writing workbooks +//! back is not supported in v1 because it would pull in a separate +//! writer crate; write-back is a future skill op. + +use std::io::{Cursor, Write}; +use std::{fs, io}; + +use calamine::{open_workbook_auto_from_rs, Data, Reader}; +use serde_json::{json, Value}; + +const INPUT_JSON: &str = "/workspace/input.json"; +const INPUT_XLSX: &str = "/workspace/inputs/input.xlsx"; +const OUTPUT_JSON: &str = "/workspace/output.json"; + +fn main() { + if let Err(error) = run() { + let _ = writeln!(io::stderr(), "xlsx_processor error: {error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let input = read_input()?; + // calamine's `open_workbook_auto_from_rs` requires `Read + Seek + + // Clone`. `Cursor>` satisfies all three (BufReader does + // not — BufReader is not Clone), so we pass the cursor directly. + let bytes = fs::read(INPUT_XLSX) + .map_err(|error| format!("failed to read {INPUT_XLSX}: {error}"))?; + let cursor = Cursor::new(bytes); + let mut workbook = open_workbook_auto_from_rs(cursor) + .map_err(|error| format!("xlsx open failed: {error}"))?; + + let output = match input.op.as_str() { + "list_sheets" => list_sheets(&mut workbook)?, + "read_sheet" => read_sheet( + &mut workbook, + input + .sheet + .as_deref() + .ok_or_else(|| "read_sheet requires a 'sheet' name".to_string())?, + input.row_range.as_ref(), + )?, + other => return Err(format!("unknown operation '{other}'")), + }; + + fs::write(OUTPUT_JSON, output.to_string()) + .map_err(|error| format!("failed to write {OUTPUT_JSON}: {error}"))?; + println!("xlsx_processor: op={} ok", input.op); + Ok(()) +} + +struct InputSpec { + op: String, + sheet: Option, + row_range: Option<(u32, u32)>, +} + +fn read_input() -> Result { + let bytes = fs::read(INPUT_JSON) + .map_err(|error| format!("failed to read {INPUT_JSON}: {error}"))?; + let value: Value = serde_json::from_slice(&bytes) + .map_err(|error| format!("failed to parse {INPUT_JSON}: {error}"))?; + let op = value + .get("op") + .and_then(Value::as_str) + .map(str::to_string) + .ok_or_else(|| "input.json is missing 'op'".to_string())?; + let sheet = value + .get("sheet") + .and_then(Value::as_str) + .map(str::to_string); + let row_range = parse_range(&value, "row_range")?; + Ok(InputSpec { + op, + sheet, + row_range, + }) +} + +fn parse_range(value: &Value, key: &str) -> Result, String> { + match value.get(key) { + Some(Value::Null) | None => Ok(None), + Some(Value::Array(items)) => { + if items.len() != 2 { + return Err(format!( + "{key} must be a 2-element array, got {}", + items.len() + )); + } + let start = items[0] + .as_u64() + .ok_or_else(|| format!("{key}[0] must be a non-negative integer"))?; + let end = items[1] + .as_u64() + .ok_or_else(|| format!("{key}[1] must be a non-negative integer"))?; + Ok(Some((start as u32, end as u32))) + } + Some(other) => Err(format!("{key} must be array or null, got {other:?}")), + } +} + +type Workbook = calamine::Sheets>>; + +fn list_sheets(workbook: &mut Workbook) -> Result { + let names: Vec = workbook.sheet_names().to_vec(); + let mut sheets = Vec::with_capacity(names.len()); + for name in names { + let range = workbook + .worksheet_range(&name) + .map_err(|error| format!("worksheet_range({name}) failed: {error}"))?; + let rows = range.height() as u64; + let cols = range.width() as u64; + sheets.push(json!({ + "name": name, + "rows": rows, + "cols": cols, + })); + } + Ok(json!({ "sheets": sheets })) +} + +fn read_sheet( + workbook: &mut Workbook, + sheet_name: &str, + range_hint: Option<&(u32, u32)>, +) -> Result { + let range = workbook + .worksheet_range(sheet_name) + .map_err(|error| format!("worksheet_range({sheet_name}) failed: {error}"))?; + let total_rows = range.height() as u32; + + // Materialise the sheet into rows-of-cells. + let mut rows: Vec> = Vec::with_capacity(range.height()); + for row in range.rows() { + let mut cells = Vec::with_capacity(row.len()); + for cell in row { + cells.push(cell_to_json(cell)); + } + rows.push(cells); + } + + let (slice, offset): (Vec>, u32) = if let Some(&(start, end)) = range_hint { + if start == 0 { + return Err("row_range start must be 1 or greater".to_string()); + } + if start > end || start as usize > rows.len() { + (Vec::new(), start) + } else { + let start_idx = (start - 1) as usize; + let end_idx = (end as usize).min(rows.len()); + (rows[start_idx..end_idx].to_vec(), start) + } + } else { + (rows, 1u32) + }; + + Ok(json!({ + "sheet": sheet_name, + "rows": slice, + "row_offset": offset, + "total_rows": total_rows, + })) +} + +/// Convert a calamine cell into a JSON value. We avoid using +/// `serde_json::to_value(&Data)` because calamine's `Data` enum uses +/// a serde tag format that is unhelpful for the LLM to read; we flatten +/// to plain JSON primitives instead. +fn cell_to_json(cell: &Data) -> Value { + match cell { + Data::Empty => Value::Null, + Data::String(s) => Value::String(s.clone()), + Data::Float(f) => serde_json::Number::from_f64(*f) + .map(Value::Number) + .unwrap_or(Value::Null), + Data::Int(i) => Value::from(*i), + Data::Bool(b) => Value::Bool(*b), + Data::DateTime(dt) => Value::String(dt.as_f64().to_string()), + Data::Error(e) => Value::String(format!("#ERROR: {e:?}")), + Data::DurationIso(s) => Value::String(s.clone()), + Data::DateTimeIso(s) => Value::String(s.clone()), + } +} + diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs new file mode 100644 index 000000000..f72d71749 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs @@ -0,0 +1,249 @@ +//! Lifecycle management for the shared desktop [`WasmRuntime`]. +//! +//! The engine + registry that back every `wasm_run` call are expensive +//! to set up (compile all built-in modules through cranelift) and +//! reasonably cheap to keep around idle, but we still want them dropped +//! eventually so an unused desktop session does not hold on to +//! ~5–10 MB of wasmtime state forever. This module implements that +//! policy: +//! +//! * **Lazy creation.** The runtime is `None` until the first caller +//! asks for it. At that point we build a fresh [`WasmRuntime`], +//! register the built-in modules, and record `last_used = now`. +//! * **In-flight counting.** Each caller leases the runtime via +//! [`acquire_runtime`] which returns a [`RuntimeLease`] guard. The +//! guard increments an in-flight counter on acquisition and +//! decrements it when dropped. A lease holder keeps the runtime alive +//! no matter how idle the app has been. +//! * **Idle reaper.** A single background thread wakes up on a fixed +//! interval (see [`REAP_INTERVAL`]). When `in_flight == 0` and +//! `now - last_used > IDLE_TIMEOUT`, it drops the runtime. The next +//! caller will rebuild it lazily. +//! +//! The reaper never drops a runtime that has active leases — `Drop` on +//! [`RuntimeLease`] only *refreshes* `last_used`; it does not itself +//! trigger teardown. This means a long-running call (pdf extraction for +//! a large document) cannot have the engine pulled out from under it by +//! the reaper half-way through. + +use super::WasmRuntime; +use std::sync::{Arc, Mutex, OnceLock}; +use std::thread; +use std::time::{Duration, Instant}; + +/// How long the runtime may stay idle before the reaper drops it. +/// +/// Exposed as a module-level constant rather than a config value +/// because it is not something the LLM or the user should tune — it is +/// a direct trade-off between "keep lazy init latency away from the +/// user" and "do not hold RAM forever". Five minutes splits the +/// difference for typical cowork sessions. +pub const IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60); + +/// How often the background reaper wakes up to check whether the +/// runtime should be torn down. Shorter means faster release after +/// idle; longer means less wake-up noise. 30 s is short enough to drop +/// within a minute of the timeout firing and long enough that the +/// reaper thread does not show up in CPU profiles. +pub const REAP_INTERVAL: Duration = Duration::from_secs(30); + +/// Shared mutable state watched by the reaper. Guarded by a single +/// `Mutex` because every mutation (acquire, release, reap) is short +/// and non-blocking. +struct LifecycleState { + runtime: Option>, + last_used: Instant, + in_flight: u32, + /// Timeout applied by the reaper. Exposed as state (rather than a + /// constant) so tests can drive the reaper without waiting five + /// minutes of real time. + idle_timeout: Duration, +} + +impl LifecycleState { + fn new(idle_timeout: Duration) -> Self { + Self { + runtime: None, + last_used: Instant::now(), + in_flight: 0, + idle_timeout, + } + } +} + +/// Lazy container holding the global lifecycle state + the handle to +/// the reaper thread. The first [`acquire_runtime`] call constructs the +/// `Arc>` and spawns the reaper; subsequent calls just clone +/// the `Arc`. +static GLOBAL: OnceLock>> = OnceLock::new(); + +fn global_state() -> Arc> { + GLOBAL + .get_or_init(|| { + let state = Arc::new(Mutex::new(LifecycleState::new(IDLE_TIMEOUT))); + spawn_reaper(Arc::clone(&state), REAP_INTERVAL); + state + }) + .clone() +} + +fn spawn_reaper(state: Arc>, interval: Duration) { + thread::spawn(move || loop { + thread::sleep(interval); + reap_once(&state); + }); +} + +/// Drop the runtime if it has been idle for longer than the configured +/// timeout **and** no leases are outstanding. Factored out so tests can +/// drive it synchronously. +fn reap_once(state: &Arc>) -> bool { + let mut guard = state.lock().expect("wasm lifecycle state poisoned"); + if guard.runtime.is_none() { + return false; + } + if guard.in_flight > 0 { + return false; + } + if guard.last_used.elapsed() < guard.idle_timeout { + return false; + } + guard.runtime = None; + true +} + +/// Acquire a lease on the global desktop WASM runtime. +/// +/// Lazy-creates the runtime on first use. The returned [`RuntimeLease`] +/// keeps the runtime alive while it exists; drop it when the caller is +/// finished to let the idle reaper reclaim the engine eventually. +pub fn acquire_runtime() -> Result { + let state = global_state(); + let runtime_arc = { + let mut guard = state.lock().expect("wasm lifecycle state poisoned"); + if guard.runtime.is_none() { + let new_runtime = WasmRuntime::new()?; + guard.runtime = Some(Arc::new(new_runtime)); + } + guard.in_flight = guard.in_flight.saturating_add(1); + guard.last_used = Instant::now(); + Arc::clone( + guard + .runtime + .as_ref() + .expect("runtime just initialised above"), + ) + }; + Ok(RuntimeLease { + runtime: runtime_arc, + state, + }) +} + +/// RAII guard returned by [`acquire_runtime`]. Holds an `Arc` to the +/// runtime and to the lifecycle state so `Drop` can decrement the +/// in-flight counter and refresh `last_used`. +pub struct RuntimeLease { + runtime: Arc, + state: Arc>, +} + +impl RuntimeLease { + pub fn runtime(&self) -> &Arc { + &self.runtime + } +} + +impl std::ops::Deref for RuntimeLease { + type Target = WasmRuntime; + fn deref(&self) -> &Self::Target { + &self.runtime + } +} + +impl Drop for RuntimeLease { + fn drop(&mut self) { + let mut guard = self + .state + .lock() + .expect("wasm lifecycle state poisoned during drop"); + guard.in_flight = guard.in_flight.saturating_sub(1); + guard.last_used = Instant::now(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test helper — build an isolated lifecycle state with a tunable + /// timeout so we can drive the reaper without waiting five minutes. + fn isolated_state(timeout: Duration) -> Arc> { + Arc::new(Mutex::new(LifecycleState::new(timeout))) + } + + fn lease(state: &Arc>) -> Result + { + let mut guard = state.lock().unwrap(); + if guard.runtime.is_none() { + guard.runtime = Some(Arc::new(WasmRuntime::new()?)); + } + guard.in_flight = guard.in_flight.saturating_add(1); + guard.last_used = Instant::now(); + let runtime = Arc::clone(guard.runtime.as_ref().unwrap()); + drop(guard); + Ok(RuntimeLease { + runtime, + state: Arc::clone(state), + }) + } + + #[test] + fn reaper_drops_idle_runtime() { + let state = isolated_state(Duration::from_millis(50)); + + // First call creates the runtime. + let first_lease = lease(&state).expect("runtime builds"); + assert!(state.lock().unwrap().runtime.is_some()); + drop(first_lease); + + // Still within the timeout window. + assert!(!reap_once(&state), "should not reap while still fresh"); + + // Past the (tiny) idle timeout. + std::thread::sleep(Duration::from_millis(80)); + assert!(reap_once(&state), "should reap after idle timeout"); + assert!(state.lock().unwrap().runtime.is_none()); + } + + #[test] + fn reaper_respects_in_flight_lease() { + let state = isolated_state(Duration::from_millis(10)); + + let _hold = lease(&state).expect("runtime builds"); + std::thread::sleep(Duration::from_millis(50)); + + // In-flight = 1, reaper must leave the runtime alone even though + // the idle window has clearly elapsed. + assert!(!reap_once(&state), "should never reap while leased"); + assert!(state.lock().unwrap().runtime.is_some()); + } + + #[test] + fn lazy_creation_is_observable() { + let state = isolated_state(Duration::from_secs(60)); + assert!(state.lock().unwrap().runtime.is_none()); + + let lease1 = lease(&state).expect("runtime builds"); + assert!(state.lock().unwrap().runtime.is_some()); + assert_eq!(state.lock().unwrap().in_flight, 1); + + let lease2 = lease(&state).expect("runtime reused"); + assert_eq!(state.lock().unwrap().in_flight, 2); + + drop(lease1); + assert_eq!(state.lock().unwrap().in_flight, 1); + drop(lease2); + assert_eq!(state.lock().unwrap().in_flight, 0); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs new file mode 100644 index 000000000..adee3285b --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs @@ -0,0 +1,1108 @@ +//! Isolated WASM runtime backend. +//! +//! This module owns the sandboxed execution path used by skill-driven +//! processing tasks that should not run directly on the host. It is the +//! single place where the wasmtime engine, WASI preview1 linker, resource +//! limits, temporary workspace layout, and the input/output contract with +//! callers are defined. +//! +//! ## Execution contract +//! +//! A caller supplies: +//! +//! 1. A WASM module name. The runtime resolves the module via +//! [`ModuleRegistry`] (either an embedded module shipped with the +//! desktop app or, for tests, a raw byte buffer). +//! 2. A JSON input payload. The runtime serialises this to a temp file +//! inside a per-call scratch directory and exposes the scratch directory +//! to the guest as its preopened `/workspace` directory via WASI. +//! 3. Optional host files to mirror into the scratch `/workspace/inputs` +//! directory. +//! 4. A [`RuntimeLimits`] override. Defaults come from +//! [`RuntimeLimits::defaults`]. +//! +//! The runtime returns a [`WasmRunResult`] containing stdout, stderr, +//! structured JSON output read from `/workspace/output.json` if the guest +//! produced one, a list of output artifact paths copied back from +//! `/workspace/outputs`, and the wall-clock duration. Errors are returned +//! as [`WasmRunError`] variants. +//! +//! ## Sandboxing +//! +//! * A fresh `wasmtime::Store` is created per call (no state bleeding). +//! * Fuel is metered; the store traps when `RuntimeLimits::max_fuel` is +//! consumed. +//! * Epoch-based deadlines enforce `wall_timeout` even when the guest is +//! stuck in non-fuel-accounted code. +//! * Linear memory is capped by [`wasmtime::ResourceLimiter`]. +//! * File access is restricted to the preopened scratch directory via +//! WASI. The guest cannot touch the host filesystem outside of it. +//! +//! ## Temp workspace layout +//! +//! Every call creates a scratch directory like: +//! +//! ```text +//! {root}/wasm-{session_id}/{call_id}/ +//! input.json (caller payload) +//! inputs/ (mirrored host files — optional) +//! outputs/ (populated by the guest) +//! output.json (optional structured guest response) +//! ``` +//! +//! The caller is responsible for providing `root`. The scratch directory +//! is removed when the [`WasmRuntimeCall`] guard is dropped, unless the +//! caller asks the runtime to keep it for debugging. + +#![allow(dead_code)] + +pub mod chunking; +pub mod freshness; +pub mod lifecycle; + +pub use lifecycle::acquire_runtime; + +use super::{RuntimeKind, RuntimeLimits}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; +use std::{io, thread}; +use wasmtime::{Config, Engine, Linker, Module, Store, StoreLimits, StoreLimitsBuilder}; +use wasmtime_wasi::preview1::{self as wasi_preview1, WasiP1Ctx}; +use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxBuilder}; + +/// Registry of WASM modules known to the desktop runtime. +/// +/// Modules can be registered ahead of time (application-owned assets) or +/// at runtime (for tests and experimental skills). Lookup by name is +/// case-sensitive. +#[derive(Default)] +pub struct ModuleRegistry { + entries: Mutex>, +} + +#[derive(Clone)] +struct ModuleEntry { + source: ModuleSource, +} + +#[derive(Clone)] +enum ModuleSource { + Bytes(Arc>), + Wat(Arc), + Path(PathBuf), +} + +impl ModuleRegistry { + pub fn new() -> Self { + Self::default() + } + + /// Register a module by its raw WASM bytes. The byte buffer is stored + /// behind an `Arc` so repeated lookups are cheap. + pub fn register_bytes(&self, name: impl Into, bytes: Vec) { + self.entries.lock().expect("module registry poisoned").insert( + name.into(), + ModuleEntry { + source: ModuleSource::Bytes(Arc::new(bytes)), + }, + ); + } + + /// Register a module by its WAT (text format) source. Useful for + /// bundling small helpers without having to ship pre-compiled bytes. + pub fn register_wat(&self, name: impl Into, wat: impl Into) { + self.entries.lock().expect("module registry poisoned").insert( + name.into(), + ModuleEntry { + source: ModuleSource::Wat(Arc::new(wat.into())), + }, + ); + } + + /// Register a module that lives on disk. The path is resolved lazily, + /// so it is safe to register modules that will only become available + /// at runtime. + pub fn register_path(&self, name: impl Into, path: PathBuf) { + self.entries.lock().expect("module registry poisoned").insert( + name.into(), + ModuleEntry { + source: ModuleSource::Path(path), + }, + ); + } + + pub fn has(&self, name: &str) -> bool { + self.entries + .lock() + .expect("module registry poisoned") + .contains_key(name) + } + + pub fn names(&self) -> Vec { + let mut names: Vec = self + .entries + .lock() + .expect("module registry poisoned") + .keys() + .cloned() + .collect(); + names.sort(); + names + } + + fn lookup(&self, name: &str) -> Option { + self.entries + .lock() + .expect("module registry poisoned") + .get(name) + .cloned() + } + + fn load_module(&self, engine: &Engine, name: &str) -> Result { + let entry = self + .lookup(name) + .ok_or_else(|| WasmRunError::UnknownModule(name.to_string()))?; + match entry.source { + ModuleSource::Bytes(bytes) => Module::new(engine, bytes.as_slice()) + .map_err(|error| WasmRunError::ModuleLoad(error.to_string())), + ModuleSource::Wat(wat) => { + let bytes = wat::parse_str(wat.as_str()) + .map_err(|error| WasmRunError::ModuleLoad(error.to_string()))?; + Module::new(engine, bytes.as_slice()) + .map_err(|error| WasmRunError::ModuleLoad(error.to_string())) + } + ModuleSource::Path(path) => { + let bytes = fs::read(&path).map_err(|error| { + WasmRunError::ModuleLoad(format!( + "failed to read WASM module at {}: {}", + path.display(), + error + )) + })?; + Module::new(engine, bytes.as_slice()) + .map_err(|error| WasmRunError::ModuleLoad(error.to_string())) + } + } + } +} + +/// Input passed to a single [`WasmRuntime::run`] call. +#[derive(Debug, Clone)] +pub struct WasmRunRequest { + pub module_name: String, + /// Optional JSON payload written to `input.json` in the scratch + /// directory before the guest runs. + pub input_json: Option, + /// Optional host files mirrored into `inputs/` (source path → logical + /// filename inside `inputs/`). + pub input_files: Vec<(PathBuf, String)>, + /// Optional resource limit overrides. Missing fields fall back to + /// [`RuntimeLimits::defaults`]. + pub limits: Option, + /// Keep the scratch workspace on disk after the call returns. Useful + /// for debugging; defaults to `false` so artifacts are cleaned up. + pub keep_workspace: bool, + /// Optional entrypoint function to invoke. Defaults to calling the + /// module's `_start` (WASI command) export. + pub entrypoint: WasmEntrypoint, + /// Optional cowork session identifier. When set, the runtime scopes + /// the scratch directory under `{scratch_root}/{session_id}/` so + /// session cleanup can sweep only the directories belonging to a + /// given session. + pub session_id: Option, +} + +impl WasmRunRequest { + pub fn new(module_name: impl Into) -> Self { + Self { + module_name: module_name.into(), + input_json: None, + input_files: Vec::new(), + limits: None, + keep_workspace: false, + entrypoint: WasmEntrypoint::Start, + session_id: None, + } + } + + pub fn with_input_json(mut self, value: Value) -> Self { + self.input_json = Some(value); + self + } + + pub fn with_session_id(mut self, session_id: impl Into) -> Self { + self.session_id = Some(session_id.into()); + self + } +} + +/// Guest entrypoint strategy. +#[derive(Debug, Clone)] +pub enum WasmEntrypoint { + /// Call the module's `_start` export (standard WASI command). + Start, + /// Call a named exported function that takes and returns no + /// parameters. Used by simple utility modules that are not full WASI + /// commands. + NamedVoid(String), +} + +/// Structured result produced by a WASM call. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WasmRunResult { + pub module: String, + pub stdout: String, + pub stderr: String, + pub duration_ms: u128, + pub output_json: Option, + pub output_files: Vec, + /// If `keep_workspace` was set, this is the scratch directory on the + /// host. Otherwise `None`. + pub scratch_dir: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WasmOutputArtifact { + pub name: String, + pub bytes: usize, +} + +/// Typed errors returned by the WASM runtime. +#[derive(Debug)] +pub enum WasmRunError { + /// The module name did not resolve in the registry. + UnknownModule(String), + /// Compilation or bytecode loading failed. + ModuleLoad(String), + /// Host-side I/O error preparing or tearing down the scratch dir. + Io(String), + /// Guest execution trapped or exceeded a limit. + Execution(String), + /// Wall-clock deadline exceeded before the guest returned. + Timeout(Duration), + /// Fuel limit exhausted before the guest returned. + OutOfFuel, + /// Linear memory limit exceeded. + MemoryLimit(usize), +} + +impl std::fmt::Display for WasmRunError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WasmRunError::UnknownModule(name) => { + write!(f, "unknown WASM module '{}'", name) + } + WasmRunError::ModuleLoad(detail) => { + write!(f, "failed to load WASM module: {}", detail) + } + WasmRunError::Io(detail) => write!(f, "WASM runtime I/O error: {}", detail), + WasmRunError::Execution(detail) => { + write!(f, "WASM guest execution failed: {}", detail) + } + WasmRunError::Timeout(duration) => { + write!(f, "WASM call exceeded wall timeout {:?}", duration) + } + WasmRunError::OutOfFuel => write!(f, "WASM call exhausted fuel budget"), + WasmRunError::MemoryLimit(limit) => { + write!(f, "WASM call exceeded memory limit {} bytes", limit) + } + } + } +} + +impl std::error::Error for WasmRunError {} + +impl From for WasmRunError { + fn from(error: io::Error) -> Self { + WasmRunError::Io(error.to_string()) + } +} + +/// The desktop WASM runtime. A single instance owns a shared wasmtime +/// engine and the module registry; it is cheap to clone via `Arc`. +pub struct WasmRuntime { + engine: Engine, + registry: ModuleRegistry, + scratch_root: Mutex>, +} + +impl WasmRuntime { + /// Construct a new runtime with sensible defaults and register the + /// built-in WASM modules that ship with the desktop app. + pub fn new() -> Result { + let mut config = Config::new(); + config.consume_fuel(true); + config.epoch_interruption(true); + config.wasm_backtrace(true); + config.wasm_bulk_memory(true); + config.wasm_multi_value(true); + let engine = Engine::new(&config) + .map_err(|error| WasmRunError::ModuleLoad(error.to_string()))?; + let runtime = Self { + engine, + registry: ModuleRegistry::new(), + scratch_root: Mutex::new(None), + }; + runtime.register_builtin_modules(); + Ok(runtime) + } + + /// Register every WASM module that is bundled into the desktop + /// binary via `include_bytes!`. Called from [`Self::new`]; callers + /// should not need to invoke this directly. + /// + /// Built-in modules are keyed by their skill-contract name (the same + /// string that appears in each skill's frontmatter `wasm_module` + /// field). Skills look up their module by this name when dispatching + /// a call through `desktop_skill_run`. + fn register_builtin_modules(&self) { + const PDF_PROCESSOR: &[u8] = include_bytes!("modules/pdf_processor.wasm"); + const DOCX_PROCESSOR: &[u8] = include_bytes!("modules/docx_processor.wasm"); + const XLSX_PROCESSOR: &[u8] = include_bytes!("modules/xlsx_processor.wasm"); + const PPTX_PROCESSOR: &[u8] = include_bytes!("modules/pptx_processor.wasm"); + self.registry + .register_bytes("pdf_processor", PDF_PROCESSOR.to_vec()); + self.registry + .register_bytes("docx_processor", DOCX_PROCESSOR.to_vec()); + self.registry + .register_bytes("xlsx_processor", XLSX_PROCESSOR.to_vec()); + self.registry + .register_bytes("pptx_processor", PPTX_PROCESSOR.to_vec()); + + // Dev-only freshness checks. Warning output is opt-in via + // II_AGENT_WASM_FRESHNESS_WARN to avoid noisy logs during normal + // desktop development. + #[cfg(debug_assertions)] + { + let base = concat!(env!("CARGO_MANIFEST_DIR"), "/src/cowork/desktop_runtime/wasm"); + freshness::check_freshness( + "pdf_processor", + &format!("{base}/modules/pdf_processor.wasm"), + &format!("{base}/guest/pdf_processor/src"), + ); + freshness::check_freshness( + "docx_processor", + &format!("{base}/modules/docx_processor.wasm"), + &format!("{base}/guest/docx_processor/src"), + ); + freshness::check_freshness( + "xlsx_processor", + &format!("{base}/modules/xlsx_processor.wasm"), + &format!("{base}/guest/xlsx_processor/src"), + ); + freshness::check_freshness( + "pptx_processor", + &format!("{base}/modules/pptx_processor.wasm"), + &format!("{base}/guest/pptx_processor/src"), + ); + } + } + + pub const KIND: RuntimeKind = RuntimeKind::Wasm; + + /// Borrow the module registry so callers can register application + /// assets at startup. + pub fn registry(&self) -> &ModuleRegistry { + &self.registry + } + + /// Configure the root directory used to create per-call scratch + /// workspaces. Typically `{app_data}/cowork/wasm-scratch`. + pub fn set_scratch_root(&self, root: PathBuf) { + *self.scratch_root.lock().expect("scratch root poisoned") = Some(root); + } + + /// Execute a single WASM call and return a structured result. + pub fn run(&self, request: WasmRunRequest) -> Result { + let limits = request.limits.unwrap_or_else(RuntimeLimits::defaults); + + // --- Prepare scratch workspace --- + let scratch_dir = self.build_scratch_dir(request.session_id.as_deref())?; + let inputs_dir = scratch_dir.join("inputs"); + let outputs_dir = scratch_dir.join("outputs"); + fs::create_dir_all(&inputs_dir)?; + fs::create_dir_all(&outputs_dir)?; + + if let Some(input_json) = request.input_json.as_ref() { + let serialised = serde_json::to_vec_pretty(input_json) + .map_err(|error| WasmRunError::Io(error.to_string()))?; + fs::write(scratch_dir.join("input.json"), serialised)?; + } + + for (source_path, logical_name) in &request.input_files { + // Resolve symlinks before copying so a symlink pointing + // outside the desktop scope cannot smuggle host files into + // the sandbox. + let canonical = fs::canonicalize(source_path).map_err(|error| { + WasmRunError::Io(format!( + "failed to resolve input file {}: {}", + source_path.display(), + error + )) + })?; + let destination = inputs_dir.join(logical_name); + if let Some(parent) = destination.parent() { + fs::create_dir_all(parent)?; + } + fs::copy(&canonical, &destination).map_err(|error| { + WasmRunError::Io(format!( + "failed to copy input file {} to scratch: {}", + canonical.display(), + error + )) + })?; + } + + // --- Load module --- + let module = self.registry.load_module(&self.engine, &request.module_name)?; + + // --- Build WASI context --- + let stdout_pipe = wasmtime_wasi::pipe::MemoryOutputPipe::new(256 * 1024); + let stderr_pipe = wasmtime_wasi::pipe::MemoryOutputPipe::new(256 * 1024); + + let wasi = WasiCtxBuilder::new() + .stdout(stdout_pipe.clone()) + .stderr(stderr_pipe.clone()) + .preopened_dir( + &scratch_dir, + "/workspace", + DirPerms::all(), + FilePerms::all(), + ) + .map_err(|error| WasmRunError::Io(error.to_string()))? + .build_p1(); + + let store_limits = StoreLimitsBuilder::new() + .memory_size(limits.max_memory_bytes) + .build(); + + let host_state = HostState { + wasi, + limits: store_limits, + memory_tripped: false, + }; + + let mut store = Store::new(&self.engine, host_state); + store + .set_fuel(limits.max_fuel) + .map_err(|error| WasmRunError::Execution(error.to_string()))?; + store.limiter(|state| &mut state.limits); + store.set_epoch_deadline(1); + + // --- Instantiate + link WASI --- + let mut linker: Linker = Linker::new(&self.engine); + wasi_preview1::add_to_linker_sync(&mut linker, |state| &mut state.wasi) + .map_err(|error| WasmRunError::Execution(error.to_string()))?; + + let start_time = Instant::now(); + let epoch_handle = EpochDeadlineHandle::new(&self.engine, limits.wall_timeout); + + let run_outcome: Result<(), wasmtime::Error> = (|| { + let instance = linker.instantiate(&mut store, &module)?; + match &request.entrypoint { + WasmEntrypoint::Start => { + if let Some(start) = instance.get_func(&mut store, "_start") { + let typed = start.typed::<(), ()>(&store)?; + typed.call(&mut store, ())?; + } else { + return Err(wasmtime::Error::msg( + "module has no '_start' export; set a NamedVoid entrypoint", + )); + } + } + WasmEntrypoint::NamedVoid(name) => { + let func = instance + .get_func(&mut store, name.as_str()) + .ok_or_else(|| { + wasmtime::Error::msg(format!( + "module has no exported function '{}'", + name + )) + })?; + let typed = func.typed::<(), ()>(&store)?; + typed.call(&mut store, ())?; + } + } + Ok(()) + })(); + + drop(epoch_handle); + + let duration = start_time.elapsed(); + + if let Err(error) = run_outcome { + let message = format!("{error:?}"); + // Classify the error into a friendlier variant when possible. + if message.contains("epoch deadline") { + return Err(WasmRunError::Timeout(limits.wall_timeout)); + } + if message.contains("all fuel consumed") { + return Err(WasmRunError::OutOfFuel); + } + if store.data().memory_tripped { + return Err(WasmRunError::MemoryLimit(limits.max_memory_bytes)); + } + // Graceful WASI exit(0) also surfaces as an error here. + if message.contains("Exited with i32 exit status 0") { + // fall through to success path below + } else if !message.contains("Exited with i32 exit status 0") { + return Err(WasmRunError::Execution(message)); + } + } + + // --- Collect outputs --- + let stdout_bytes = stdout_pipe.contents(); + let stderr_bytes = stderr_pipe.contents(); + let stdout_text = String::from_utf8_lossy(&stdout_bytes).into_owned(); + let stderr_text = String::from_utf8_lossy(&stderr_bytes).into_owned(); + + let output_json = { + let output_path = scratch_dir.join("output.json"); + if output_path.exists() { + let bytes = fs::read(&output_path)?; + match serde_json::from_slice::(&bytes) { + Ok(value) => Some(value), + Err(_) => None, + } + } else { + None + } + }; + + let output_files = collect_output_artifacts(&outputs_dir)?; + + let result = WasmRunResult { + module: request.module_name.clone(), + stdout: stdout_text, + stderr: stderr_text, + duration_ms: duration.as_millis(), + output_json, + output_files, + scratch_dir: if request.keep_workspace { + Some(scratch_dir.clone()) + } else { + None + }, + }; + + if !request.keep_workspace { + let _ = fs::remove_dir_all(&scratch_dir); + } + + Ok(result) + } + + fn build_scratch_dir(&self, session_id: Option<&str>) -> Result { + let base = match self.scratch_root.lock().expect("scratch root poisoned").clone() { + Some(root) => root, + None => std::env::temp_dir().join("ii-cowork-wasm"), + }; + let scoped = match session_id { + Some(session_id) => base.join(sanitize_session_segment(session_id)), + None => base, + }; + fs::create_dir_all(&scoped)?; + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| duration.as_nanos()) + .unwrap_or_default(); + // Random suffix prevents collision when two calls happen in + // the same nanosecond (unlikely but possible under load). + let rand_suffix: u32 = (nanos as u32).wrapping_mul(2654435761); + let subdir = scoped.join(format!("call-{nanos}-{rand_suffix:08x}")); + fs::create_dir_all(&subdir)?; + Ok(subdir) + } + + /// Remove every scratch directory belonging to a given session. + /// + /// Called by the cowork session gateway when a session is closed or + /// deleted. Silently succeeds if the session has no scratch state. + pub fn sweep_session(&self, session_id: &str) -> Result<(), WasmRunError> { + let Some(base) = self.scratch_root.lock().expect("scratch root poisoned").clone() else { + return Ok(()); + }; + let scoped = base.join(sanitize_session_segment(session_id)); + if scoped.exists() { + fs::remove_dir_all(&scoped)?; + } + Ok(()) + } + + /// Remove the entire scratch root. Called at desktop app startup so + /// leftover artifacts from previous runs do not accumulate. + pub fn sweep_all(&self) -> Result<(), WasmRunError> { + let Some(base) = self.scratch_root.lock().expect("scratch root poisoned").clone() else { + return Ok(()); + }; + if base.exists() { + fs::remove_dir_all(&base)?; + } + fs::create_dir_all(&base)?; + Ok(()) + } +} + +/// Sanitise a cowork session identifier so it is safe to use as a +/// directory name. Anything that is not alphanumeric, `-`, `_`, or `.` is +/// replaced with `_`. +fn sanitize_session_segment(session_id: &str) -> String { + session_id + .chars() + .map(|c| match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' => c, + _ => '_', + }) + .collect() +} + +/// Shared host state available to wasmtime callbacks. +struct HostState { + wasi: WasiP1Ctx, + limits: StoreLimits, + memory_tripped: bool, +} + +fn collect_output_artifacts(outputs_dir: &Path) -> Result, WasmRunError> { + if !outputs_dir.exists() { + return Ok(Vec::new()); + } + let mut artifacts = Vec::new(); + for entry in fs::read_dir(outputs_dir)? { + let entry = entry?; + let metadata = entry.metadata()?; + if metadata.is_file() { + artifacts.push(WasmOutputArtifact { + name: entry.file_name().to_string_lossy().into_owned(), + bytes: metadata.len() as usize, + }); + } + } + artifacts.sort_by(|a, b| a.name.cmp(&b.name)); + Ok(artifacts) +} + +/// Background thread that bumps the wasmtime engine epoch after the wall +/// deadline so the guest is interrupted even when fuel is not being +/// consumed. +struct EpochDeadlineHandle { + shutdown: Arc>, + thread: Option>, +} + +impl EpochDeadlineHandle { + fn new(engine: &Engine, deadline: Duration) -> Self { + let shutdown = Arc::new(Mutex::new(false)); + let shutdown_clone = Arc::clone(&shutdown); + let engine = engine.clone(); + let thread = thread::spawn(move || { + let start = Instant::now(); + while start.elapsed() < deadline { + if *shutdown_clone.lock().expect("shutdown poisoned") { + return; + } + thread::sleep(Duration::from_millis(25)); + } + engine.increment_epoch(); + }); + Self { + shutdown, + thread: Some(thread), + } + } +} + +impl Drop for EpochDeadlineHandle { + fn drop(&mut self) { + *self.shutdown.lock().expect("shutdown poisoned") = true; + if let Some(handle) = self.thread.take() { + let _ = handle.join(); + } + } +} + +// Global runtime access is provided by [`lifecycle::acquire_runtime`]. +// It replaces an earlier `global()` helper that held the engine alive +// forever. The lifecycle module manages lazy creation, in-flight +// counting, and idle drop. + +#[cfg(test)] +mod tests { + use super::*; + + const HELLO_WAT: &str = r#" + (module + (import "wasi_snapshot_preview1" "fd_write" + (func $fd_write (param i32 i32 i32 i32) (result i32))) + (memory (export "memory") 1) + (data (i32.const 8) "hello wasm\n") + (func $main (export "_start") + (i32.store (i32.const 0) (i32.const 8)) + (i32.store (i32.const 4) (i32.const 11)) + (call $fd_write (i32.const 1) (i32.const 0) (i32.const 1) (i32.const 20)) + drop) + ) + "#; + + #[test] + fn runtime_runs_embedded_wat_module() { + let runtime = WasmRuntime::new().expect("runtime construction"); + runtime.registry().register_wat("hello", HELLO_WAT); + assert!(runtime.registry().has("hello")); + + let request = WasmRunRequest::new("hello"); + let result = runtime.run(request).expect("hello module runs"); + assert_eq!(result.module, "hello"); + assert!( + result.stdout.contains("hello wasm"), + "expected hello wasm in stdout, got {:?}", + result.stdout + ); + assert!(result.scratch_dir.is_none()); + } + + #[test] + fn runtime_reports_unknown_module() { + let runtime = WasmRuntime::new().expect("runtime construction"); + let err = runtime + .run(WasmRunRequest::new("does-not-exist")) + .unwrap_err(); + match err { + WasmRunError::UnknownModule(name) => assert_eq!(name, "does-not-exist"), + other => panic!("expected UnknownModule, got {other:?}"), + } + } + + #[test] + fn runtime_reports_module_list() { + let runtime = WasmRuntime::new().expect("runtime construction"); + runtime.registry().register_wat("test_b", HELLO_WAT); + runtime.registry().register_wat("test_a", HELLO_WAT); + let names = runtime.registry().names(); + assert!(names.contains(&"test_a".to_string())); + assert!(names.contains(&"test_b".to_string())); + // Built-in modules like pdf_processor are registered by + // `WasmRuntime::new()` via `include_bytes!`, so the list + // contains them too. We only assert the test-registered names + // made it in, not the exact shape of the list. + } + + #[test] + fn session_id_scopes_scratch_dir() { + let runtime = WasmRuntime::new().expect("runtime construction"); + runtime.registry().register_wat("hello", HELLO_WAT); + + let root = std::env::temp_dir().join(format!( + "ii-cowork-wasm-lifecycle-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or_default() + )); + runtime.set_scratch_root(root.clone()); + + let request = WasmRunRequest::new("hello") + .with_session_id("session-xyz") + .with_input_json(serde_json::json!({"hello": "world"})); + let result = runtime + .run(WasmRunRequest { + keep_workspace: true, + ..request + }) + .expect("runs"); + + let scratch = result.scratch_dir.expect("kept scratch"); + assert!( + scratch.starts_with(root.join("session-xyz")), + "scratch dir {:?} should live under the session-scoped root", + scratch + ); + + runtime + .sweep_session("session-xyz") + .expect("session sweep succeeds"); + assert!(!root.join("session-xyz").exists()); + + runtime.sweep_all().expect("sweep all succeeds"); + assert!(root.exists(), "sweep_all recreates the empty root"); + + let _ = std::fs::remove_dir_all(&root); + } + + /// End-to-end: boot the real runtime (which auto-registers + /// pdf_processor.wasm via `include_bytes!`), prepare a real PDF input, + /// run the module, and assert the guest produced a structured + /// `output.json` with extracted text. + /// + /// This is the canonical proof that the full sandbox pipeline works: + /// Rust→wasm32-wasip1 module loaded → WASI preopen → guest reads + /// `/workspace/inputs/input.pdf` → lopdf parses → writes + /// `/workspace/output.json` → host reads and returns structured + /// result. + #[test] + fn pdf_processor_extract_text_end_to_end() { + const HELLO_PDF: &[u8] = include_bytes!("../../../../tests/fixtures/hello.pdf"); + + let runtime = WasmRuntime::new().expect("runtime construction"); + assert!( + runtime.registry().has("pdf_processor"), + "pdf_processor module should be auto-registered via include_bytes!" + ); + + // Stage the input PDF on the host side so we can pass it as an + // input file to the runtime (the runtime copies it into the + // sandbox's /workspace/inputs/ dir). + let stage_dir = std::env::temp_dir().join(format!( + "pdf-extract-stage-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or_default() + )); + std::fs::create_dir_all(&stage_dir).unwrap(); + let pdf_path = stage_dir.join("hello.pdf"); + std::fs::write(&pdf_path, HELLO_PDF).unwrap(); + + let request = WasmRunRequest::new("pdf_processor") + .with_session_id("pdf-e2e-test") + .with_input_json(serde_json::json!({ "op": "extract_text" })); + let request = WasmRunRequest { + input_files: vec![(pdf_path.clone(), "input.pdf".to_string())], + ..request + }; + + let result = runtime.run(request).expect("pdf_processor runs"); + + assert_eq!(result.module, "pdf_processor"); + assert!( + result.stdout.contains("pdf_processor: op=extract_text ok"), + "expected stdout success line, got: {:?}", + result.stdout + ); + + let output = result + .output_json + .as_ref() + .expect("pdf_processor should write /workspace/output.json"); + let pages = output + .get("pages") + .and_then(|value| value.as_array()) + .expect("output.json.pages must be an array"); + assert!(!pages.is_empty(), "pages must not be empty"); + let joined: String = pages + .iter() + .filter_map(|page| page.as_str()) + .collect::>() + .join(" "); + assert!( + joined.contains("Hello Desktop Skill"), + "extracted text should contain the fixture phrase, got: {joined:?}" + ); + + std::fs::remove_dir_all(&stage_dir).ok(); + } + + /// End-to-end with `page_range`: we hand the guest a 1-page fixture + /// PDF but ask for `[1, 1]` explicitly, and verify it honours the + /// range plus reports `page_offset`/`total_pages`. This is the + /// smoking gun that our rebuilt `pdf_processor.wasm` picks up the + /// new input contract — the chunking dispatcher relies on this + /// contract for correctness. + #[test] + fn pdf_processor_respects_page_range() { + const HELLO_PDF: &[u8] = include_bytes!("../../../../tests/fixtures/hello.pdf"); + + let runtime = WasmRuntime::new().expect("runtime construction"); + let stage_dir = std::env::temp_dir().join(format!( + "pdf-page-range-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or_default() + )); + std::fs::create_dir_all(&stage_dir).unwrap(); + let pdf_path = stage_dir.join("hello.pdf"); + std::fs::write(&pdf_path, HELLO_PDF).unwrap(); + + // In-range: ask for pages 1-1, expect 1 page with the fixture text. + let in_range = WasmRunRequest { + input_files: vec![(pdf_path.clone(), "input.pdf".to_string())], + ..WasmRunRequest::new("pdf_processor") + .with_session_id("pdf-range-in") + .with_input_json(serde_json::json!({ + "op": "extract_text", + "page_range": [1, 1] + })) + }; + let result = runtime.run(in_range).expect("pdf_processor runs"); + let output = result.output_json.expect("output.json"); + let pages = output + .get("pages") + .and_then(|v| v.as_array()) + .expect("pages array"); + assert_eq!(pages.len(), 1, "expected exactly 1 page in slice"); + assert_eq!(output.get("page_offset").and_then(|v| v.as_u64()), Some(1)); + assert_eq!(output.get("total_pages").and_then(|v| v.as_u64()), Some(1)); + + // Out-of-range: ask for pages 10-20 on a 1-page doc, expect + // empty pages array and no error (guest clamps). + let out_of_range = WasmRunRequest { + input_files: vec![(pdf_path.clone(), "input.pdf".to_string())], + ..WasmRunRequest::new("pdf_processor") + .with_session_id("pdf-range-out") + .with_input_json(serde_json::json!({ + "op": "extract_text", + "page_range": [10, 20] + })) + }; + let result2 = runtime.run(out_of_range).expect("pdf_processor runs out-of-range"); + let output2 = result2.output_json.expect("output.json"); + let pages2 = output2 + .get("pages") + .and_then(|v| v.as_array()) + .expect("pages array"); + assert!( + pages2.is_empty(), + "out-of-range should produce empty pages, got {} pages", + pages2.len() + ); + + std::fs::remove_dir_all(&stage_dir).ok(); + } + + #[test] + fn docx_processor_extract_text_end_to_end() { + const HELLO_DOCX: &[u8] = include_bytes!("../../../../tests/fixtures/hello.docx"); + let runtime = WasmRuntime::new().expect("runtime"); + let stage = stage_file(HELLO_DOCX, "hello.docx"); + let request = WasmRunRequest { + input_files: vec![(stage.join("hello.docx"), "input.docx".to_string())], + ..WasmRunRequest::new("docx_processor") + .with_session_id("docx-e2e") + .with_input_json(serde_json::json!({"op": "extract_text"})) + }; + let result = runtime.run(request).expect("docx_processor runs"); + let output = result.output_json.expect("output.json"); + let paragraphs = output.get("paragraphs").and_then(|v| v.as_array()).expect("paragraphs"); + assert!(!paragraphs.is_empty()); + let joined: String = paragraphs.iter().filter_map(|p| p.as_str()).collect::>().join(" "); + assert!(joined.contains("Hello Desktop Skill Docx"), "got: {joined:?}"); + std::fs::remove_dir_all(&stage).ok(); + } + + #[test] + fn xlsx_processor_list_sheets_end_to_end() { + const HELLO_XLSX: &[u8] = include_bytes!("../../../../tests/fixtures/hello.xlsx"); + let runtime = WasmRuntime::new().expect("runtime"); + let stage = stage_file(HELLO_XLSX, "hello.xlsx"); + let request = WasmRunRequest { + input_files: vec![(stage.join("hello.xlsx"), "input.xlsx".to_string())], + ..WasmRunRequest::new("xlsx_processor") + .with_session_id("xlsx-e2e") + .with_input_json(serde_json::json!({"op": "list_sheets"})) + }; + let result = runtime.run(request).expect("xlsx_processor runs"); + let output = result.output_json.expect("output.json"); + let sheets = output.get("sheets").and_then(|v| v.as_array()).expect("sheets"); + assert!(!sheets.is_empty()); + let first_name = sheets[0].get("name").and_then(|v| v.as_str()).unwrap_or(""); + assert_eq!(first_name, "TestSheet"); + std::fs::remove_dir_all(&stage).ok(); + } + + #[test] + fn xlsx_processor_read_sheet_end_to_end() { + const HELLO_XLSX: &[u8] = include_bytes!("../../../../tests/fixtures/hello.xlsx"); + let runtime = WasmRuntime::new().expect("runtime"); + let stage = stage_file(HELLO_XLSX, "hello.xlsx"); + let request = WasmRunRequest { + input_files: vec![(stage.join("hello.xlsx"), "input.xlsx".to_string())], + ..WasmRunRequest::new("xlsx_processor") + .with_session_id("xlsx-read-e2e") + .with_input_json(serde_json::json!({"op": "read_sheet", "sheet": "TestSheet"})) + }; + let result = runtime.run(request).expect("xlsx_processor runs"); + let output = result.output_json.expect("output.json"); + let rows = output.get("rows").and_then(|v| v.as_array()).expect("rows"); + assert_eq!(rows.len(), 2); + // Row 1: ["Hello", "Desktop"] + let row1 = rows[0].as_array().expect("row1"); + assert_eq!(row1[0].as_str(), Some("Hello")); + assert_eq!(row1[1].as_str(), Some("Desktop")); + std::fs::remove_dir_all(&stage).ok(); + } + + #[test] + fn pptx_processor_extract_text_end_to_end() { + const HELLO_PPTX: &[u8] = include_bytes!("../../../../tests/fixtures/hello.pptx"); + let runtime = WasmRuntime::new().expect("runtime"); + let stage = stage_file(HELLO_PPTX, "hello.pptx"); + let request = WasmRunRequest { + input_files: vec![(stage.join("hello.pptx"), "input.pptx".to_string())], + ..WasmRunRequest::new("pptx_processor") + .with_session_id("pptx-e2e") + .with_input_json(serde_json::json!({"op": "extract_text"})) + }; + let result = runtime.run(request).expect("pptx_processor runs"); + let output = result.output_json.expect("output.json"); + let slides = output.get("slides").and_then(|v| v.as_array()).expect("slides"); + assert!(!slides.is_empty()); + let text = slides[0].get("text").and_then(|v| v.as_str()).unwrap_or(""); + assert!(text.contains("Hello Desktop Skill Pptx"), "got: {text:?}"); + std::fs::remove_dir_all(&stage).ok(); + } + + /// Helper: write fixture bytes to a temp dir so tests can pass them + /// as input_files to the runtime. + fn stage_file(bytes: &[u8], filename: &str) -> PathBuf { + let dir = std::env::temp_dir().join(format!( + "e2e-{}-{}", + filename, + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or_default() + )); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write(dir.join(filename), bytes).unwrap(); + dir + } + + /// Verifies that the skill registry resolves the pdf skill to the + /// pdf_processor module name, and that the runtime registry agrees. + /// This is the "match" step desktop_skill_run performs before + /// calling into the runtime. + #[test] + fn pdf_skill_resolves_to_registered_module() { + use crate::cowork::desktop_skills::find_builtin_skill; + + let skill = find_builtin_skill("pdf").expect("pdf skill registered"); + let module_name = skill.wasm_module().expect("pdf skill declares wasm_module"); + assert_eq!(module_name, "pdf_processor"); + + let runtime = WasmRuntime::new().expect("runtime construction"); + assert!( + runtime.registry().has(module_name), + "pdf skill's wasm_module should be registered in the runtime" + ); + } + + #[test] + fn sanitise_session_segment_is_filesystem_safe() { + // `.`, `-`, `_`, and alphanumerics pass through unchanged. + assert_eq!(sanitize_session_segment("abc_123-x.y"), "abc_123-x.y"); + // Path separators and backslashes collapse to `_`. Parent-dir + // dots are preserved as dots (they are harmless inside a single + // segment). + assert_eq!( + sanitize_session_segment("../../etc/passwd"), + ".._.._etc_passwd" + ); + assert_eq!(sanitize_session_segment("a/b\\c"), "a_b_c"); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/README.md b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/README.md new file mode 100644 index 000000000..bacb983cb --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/README.md @@ -0,0 +1,64 @@ +# Desktop WASM guest modules + +This directory holds the compiled `.wasm` artifacts that are bundled into +the desktop binary at compile time via `include_bytes!` in +[`../mod.rs`](../mod.rs). + +Each file here is produced by building a crate under [`../guest/`](../guest/) +for the `wasm32-wasip1` target. The bytes are then copied into this +directory and registered with the runtime's `ModuleRegistry` when +`WasmRuntime::new()` runs. + +## Rebuilding a module + +After editing any guest crate (for example, `../guest/pdf_processor/`): + +```bash +cd src/cowork/desktop_runtime/wasm/guest/pdf_processor +rustup target add wasm32-wasip1 # only needed once +cargo build --target wasm32-wasip1 --release + +# Copy the compiled artifact into this modules/ directory so +# include_bytes! picks up the new bytes on the next cargo check. +cp target/wasm32-wasip1/release/pdf_processor.wasm ../../modules/pdf_processor.wasm +``` + +Then run `cargo check` on the top-level `src-tauri` crate — it will +recompile the host binary with the new guest bytes embedded. + +## Freshness warnings + +The desktop runtime can detect when a guest module's source is newer than +the compiled `.wasm` artifact, but that warning is disabled by default to +avoid noisy local logs. + +If you want to see the warning while debugging, start the desktop app with +`II_AGENT_WASM_FRESHNESS_WARN=1`. + +## Why separate `guest/` and `modules/`? + +- `guest/` holds *source code* for the WASM programs. Each guest is its + own cargo crate with its own dependency tree and its own target + triple (`wasm32-wasip1`). These crates are **not** part of the main + `src-tauri` workspace — cargo does not walk into them during normal + host builds. +- `modules/` holds the *compiled bytes* that the host embeds at + compile time. These bytes are the only thing the running desktop app + sees; the guest source code is only needed when you want to change + what a skill does inside the sandbox. + +Keeping the source and the artifact separate means the host crate +(`src-tauri`) depends on exactly one file per module — the +`.wasm` artifact — instead of triggering a `wasm32-wasip1` toolchain +build on every `cargo check`. + +## Current modules + +| File | Built from | Used by skill | +|------|------------|----------------| +| `pdf_processor.wasm` | `../guest/pdf_processor/` | `pdf` (extract_text, metadata) | + +`docx`, `xlsx`, and `pptx` are registered as skills but do not yet have +corresponding WASM modules. Calls targeting those modules will return a +clear "module not registered" error until their guest crates are added +here. diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/docx_processor.wasm b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/docx_processor.wasm new file mode 100644 index 0000000000000000000000000000000000000000..eb4f876649efdee49b146f8dd035cc44d67bd63f GIT binary patch literal 617045 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 vw&YsS%&g_b+nd4d2&wHawt#E=|PT2 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�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

2(~QK-wpNhQ;Q0$FPywBoH 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

|jgVV}5x{nT6gxW`t1@m)(~+P|xlMfp`Dj^YQ*f zd{==k%I-vrHn08!?@%km@exYrKr`}UC#^3wl9OqR`OE^PkKW_V6OkNc;y4=^Pyu86 z%J9pOu)tg9cY4w3w@V$U(wTUQ^6#K|ZN@7TlXU1-T8SGm~-orSqjh(o@f*h3!)Z|4f*jEmH63p&qEVX>v z1%CTyHra0~V2NuJp7LWz@To{7lIL7O`EQqbt+Sd`fow8jvEPP$%j8}8Ra+R80ZMOU zkLOzjuOf5>YNV^ZCqj`|ONs^b5(JKwV`W;UZs32Og#|VG%x8FjvUtQ%QT`HB(ojP1 zruK$Y(~82gy8+8QGfAy7lYt#!tVL-8w4zQxvEI(dd-3$q`?=$RJmVeEPmD#L7Y!7P zTakp5MP`%~_}JUx!{~2z5;YbYijW;g14o6h%G;a*ocO$XQy&`>vYZZzcU|Ez>wo#I z6O71I&)mD7`PcD4Nnx&P~SNT&{d+RAxmgCH2uC)hpUY0SWbl^>ogF zh~iMJ`O&m7tNQ4ysTIisw0cz@!vZ}asp7PT_x#X)ajnJHPm0cXp=7}7Gzz58jsP5S z4y9$X9u~;GIIs zBfNHsN))KkDk5zgql$#5(gWj6M;b4(C0+BuUI9cQIr$BVF#?n=@hy*rl97~pH7h$` z%cf$G6|+|!S{k8bLZeR+>!aCKnqt{a%-qpObIm?!$&9r4PK2zj!K%^vp(u2eDS$S^ zwO(p&Xtb*>(W6@NX=S%er9r|*C_7|&u_%+uqBK+Rzxq^ml;7x&GHI<=jLJXn7YRfY zI}nl9AMx$Y^FoWvwA?Y*&GeQeb#_stmM4uC`&cHD1?m)w^?y<CD7C+@OuieKn4xY5gg#tSQ%a4zrP>8K95!Y(7fEjS=;_ zt5qCHkPBhHDFS6w-qk~)fstt=RwJ4#oxtqSkut7&X_^P?&;RWSg!r(VUI5h()-qWS zX4O=b%C1_Y-iY(xo($&fCK-)X*9L1-IB{w{wTz@iMiIpI;xlQ@A=$SKC__3@e+A=1 zwQ7~tD62C$TQ}6Ym|TrdjcW65**1CggXV!WY`$}!E4y#pWmdOv)*g+Cgn>57 zPsTuxO4{a(j%BsBUoOLZJZ+ZDC7Ni&~a(E_=ll27ys z6}{ezZ!J`|^|AkL{@DD>6yWNdB1t>LSFiU ziTH`Isyy2jj@g$5)w!!4wNDw+xiV2e*VX}DkHm%}n>O|oyRLQ3zO~GBU9W0>$hLjl zXtLI5<+W=G%erE;Y(xYilAdUdLRVw;(oY@8gBxQSrP1r1G~Jm_{mYxV`ClJJYP~0E zJ9nCwKy~0h`Ew)0#*)s(X2;^tmA|!JnbVP3lO=1vttpwP)%su`n>8*<@iZ$!0y9Rl z(zRDLIv3j6!JP};iemb1HMZ}b*2k7b>uqICXF_YO%UX0x?TW+VbzJ$^$6BT~sYgVs z-|fmEAi#gtYSmd@Oe$K}c{H0k3%ZYuIm@fs?s*g3^^|3a(9@Y%EbxHlBbD#T|SH+?08BvN*ZMN2! zr1;`FN~=V4&x&i5;xzwih0T9k1!W-w%ND23*=933>snu9dbRVLwL@dddc*RfT)JX4 z`d{Y-+0**vt{^QM)#~G7bE}wf6^Yg!>mge=6uVYVn-v-Kn#v+)-4apkFHAp*Ly%cz_VGV8D-yY zZMAVTQLV76RnssJ|CmJPFDHP**g6Wx!tURXA5RNDBHrk!my1}ti`X>-fGD#wzvS!OGoXuI-P z%Vk9$jUVULWkq$etPy9qRqUn}PzvbU7&KXpS@Y~>t>j(5Sj_gn>C2-$*lcqCRA*U~ ztmVacibb+$+9$3>6(`VQTSLUv$8sGw^yFmQ z=I{UQC-X1cIuhz*|69A9)oA~cG;0>2=}AY~P%Q5Ix?K-iq>`1E-9eBS=~;HHPmCi! z;@S$V^MT1a&uM!PPNPpxhGxU-$vm7c|C zXl=DM%#Ch~*o^_}LGxmL5Z&L7c2~8sX|`vy%y zJ{n8z&WPp1*)k1lo3+};ho=|-^KRonv0AG%kKAaqw(A&Py5vCS&WhQP$tG zrWUw4AeVM5EoIF<)_c0QVzuJxyM(K?{#Sg`kyUG>WyHp)XpMi z&~h2=C0bTT@+#{lWxB4_LdocV*>vk&bcD9<$_sxt3jFUzw|F*QOxOBLR?Mqq)wmX| zIMGMu&qe2SFdC3D+T`teOJ1x_aVk?5yEOE%T7A#57;KzdUO-?D9%RPW+2N$1yju(54&)?yvIQJ^!5jT!T7|2vN+sZ2%BAK8sQLrKCiY3Ebr zLDJDP!{Q0B@nyXr39~AH@@74wv!cy6v!k}z-EeD#W!e6Z8LzA>E}OA7n)P?1=zL1Y z^|bP(V>g!-p=+_--LzS69xQ&vWpP?w#kCkkJIb9YoL}cvX!)^RnU2M5e3xNYk3zt{ zVlZp2$IPqb+{fA(GYZrC;5_jp=C^uas{cpXT}NwKb#24nbV~0{r?hl;cXxMpcXxMp zmvonc2oeg4BB6w!go22$0R;o!-+4do?_=EK8Q*xnKNjm+Yt4DgV;*yz>pZV(Z}z2M z^Ov8n#_?SO8H1nc`dhVc{5;dy6ka9pv`{i~7-T zFTa>L;)(As@jHNXV)(u&zl%84k8B_LUBzX-?4gU_6FkH|_+EtW#zl?$iq}!RHGZu1 z8I>yk=eM)W_#r(p9Bu74h~& z@_Ul<4~y?_^IDH>eG9)!{EnJ$isyUgU&B8RKlmMla`Fkj6MJNCzvoTAT9^I(UAoSr z=jO=1yB(iT*w?bY+4>QFm-%I+Quf2I6ST5oH;ggDXO@i*V)RZL!S@jqa`hrH{{ zdo91-{My0)&Hfl^Ju{x;=`l(@H`D8?`X|@FPq(G;zO%pL!TV1AVu;^H{GxY!FD9HP z^yu#kw6@q^d*KX%msZ~re()XkRrn>t;~Q}|u>aP62);Y$x$Ry;T?g40mEfMz&(|-~ zchZ-E^APXK;;j*{F8)RFKZ8%lcu_+3z#9#3t^Ci+|A}$%d$7^Dey4M-ygrj(DeLj9kHdQ=zQy52q1zklQ`Kp*c)i3+i^n-U`tm;kr-r-^#E2G> zNWCVj|5JY5@fjv=74a{!tLHxxzSyk3_I*9|tEhg9;Eja0LO)(#4DMaH>B4;vb(p`6 zuYPTei{TX;ug7?_!z0%-XGECar8AC&cNG0_{{irkzLoXE_*}A2j?yWy{dbQ4BL1b| z{|N6hxLpJObiaX5Yx?G)|c-A0v<04`Jr_$R`oq1{&9L zy{wV%bNgX6{3>)iNdG(ZpAA3wPR@yNdcc{^?kKxu^xeQNq3b0Lz0!*RoAq7hkC zzN`7&c#jI|A>Li*(-8+{@IEsG{j$?9JG}4ZI}E>~@?9$bLw;LksQC%vbawsqlmAKh z^W=9N-VAu}((gfNl#qS+$Bq&u+s{9n-zRSHzP7sjUeIk9 zywUJ_!yg9!cX}@KM*@GsqqOtuG@L|ujgUumc@#EALOlUm*T%b|0{NA#OY8?;|(|1AOC6{x`x5jC#{|K*#>+*{EeCl{YpIX0D+0ZHQ~)CK2x!aVFuB z+c+uvjn09O*)_7y6T<)0x!%uwXE<5IU7KP42DtUcaE6%gZ@v_}@px6oGdZ3qpZOjI zbvR@^(YT3xN{jP{ebdE$-bdGZbZTuJe0Srg_GJcopTVOC9u4uzM2EcM{7Sc?>QTY^ z=j<};7aOnEUlc#H^~i9Rs828B*7)2q&MuF|;&&APntE(hk2dgQ!T;3!V)K*Kv7tIf z*C*Fk#4{(Jqpa7kp3*pqacpth)3X}hmwj<~H~q_au-WBjHxkb|^hiygO7!_ays6@C zF#nJF0{WMDCKCSxd7puo7XN5?uE9H{`e#)C@71l8IHT!VK4#RAG4N}_?_?a=b^0AX zNAbDDE;qYnbm~T@O8OM~R(L(aYkg|J6Kh{)vM;i`uca3MtoTRqnu6CZIJ4k{;?WCE zV)0Xnf5-fX=8O4ZZ+`q%z$=b#XYum8U!7y0-u$=h4zSBZ?`w1(%D)x=8R8t1@8{yy zbS^fKU+^77*WK5vJO6*;_X)o_bSSD0bL7>{_1z2qQuyDpFVCCb0&fDmI`D(u3Ev{bjtWJj0vb+r>?4zYb>KmwmYSgIy1e?E5qJeOYzs66U-8=$R!6&G4v; zM^58E=@D7~rGAKgP}x3sAAUV?O2JPAe<%By?0bmQQhwW=X9umf+@|jG%Y$E4_t!P_ zDvj5RKkY4kb>&e(9tG(h4Sr_)PSGnJ9ft<>Gd}8EJDl3@bE;Q;_=(^b75@h~-S4;u z^KS?DNj$mY5fzW4{Qi>X6yx5;o%x^OUzXhkcKfYAwZ4L`J?MJOII{Yug@0au$^1I= zG4Q&kUo2i3`ShUI-||nv?qhb1#1Bv6xe?z4>b}xGIOp8?L%su?JN@L>6u*1?$MOFf z?s|Cft$*y?n2Fy2{3fyM$}YLOyonk$q$s^(z^!4ufc5kId-ER(zZv|i_Q5B{F`xPV zo>0Hn3%3m1XXbC4?@IUibnnkEm2ny8L3RAvi~Cv~8XC7TE`|SfaXz5Wefq={_o_M^ zV|UYj>LmX{#+~fbFX3G>A4guX?Ss^GNF@F~=U8F>^Z3_>+YD}QdIsO&o;%#xrp_(t zb5K1$qJJxO>uda(abA8i_;ok_#CWQF`^fhK|8(>%2CpK#LFQYU|IBfL;kjwM zyhq6Ug*Ox3;S{{%`-bJSosRF=-+%DC$}c6m=lV4CI4gc8{TKR8<_GbQBA+n%JTX6= zUW4gS5l%znH|&0(dujZ}!Rrbq_}g$9#Cu@Ay!p=RS35LP$Z$B-;B*uJp!-*E`jw_% zMY=7gdn5Vo;&%`KeE5%m*B;(G`j_I4l;24EvW4|C^e#u2zq)_0;`|^`q>j=)33(>G#nuE}rAmr=$9eH(%HMCjPbgZ>QT!y3LcvX1ezY z>IrW>`!VdNvn#;v=-+&7P!UWM)Lf=!a2MN|LB+LqrXbO;JbZ#=cGTpsPt*654HZc z^=Ilf2fz1>6RXQ_>|59;8|ib2K9S^eQT-Fk<3o0P#k(oqcJcSP|90h9hu;Le?yK_; z{PT#H!Frhe-xdA~`!$#KitfA5T(@2M53-(HzOCq#3;rVeql5i1Mx1PTErB~4ZdrPV z(tDx&F1ZhkXJ4QFYyN%Z9|P_&??-Q%-)g=QoifvDxwysft%2_*@rKK1sC+Vs6Z{{z zR`Bw}o5L?Z{8{4tD$j++?UUGh{DSW!o+Ex6 z>nmI*8|=Tk;?;3(m6Jzyd5puW9$s(wea5e%^+@;(k#BnW_QaIR9Dvw~F_x{d>ztp22s(c86aN{xj3ZlZzZAyB_4n}pN4y*2^}(Yt9>+t1 zpRPs;8E!ozyUp^e#BMA-2YKJWO1xg;ZKhXZdfnoGgnu^U;JZwF=&$Ot75;zV-^)H2Y9FMh(=)jF`8D)@;46JB`>Dh;pDBgW1OIZai?nbK zI9C^{&lB~z$8Rq^&(dchyo2mgc^{fg|A+JFHC<*pM>dLkQQX*YcX__-Cy#RCwSjX4 zP8ahrU8nizP>2rq`FC@^EQH??eo^zq&EK$ZKBY@ux_lVV{nPr-aNe;VW_>1{4)iZ1 z?rZs0!1o_~-@)$&oDp!ZMvE5moGusXl3M&h;ujD1eWvovXFT0FjQ$_deFD7TyN(K5 zZ(}_HzgObtGGE%h*e{O>;yp+y=h(B6hH}q}v zzvDU4{?35kQ~dh)JYtIaePle7o)!4j<~M}>7xqtbyiVfPSYDaz!(;e{Sx+ebB>Qca z{k8fY11q;VN|@!*{`UTl0seHMgzzE!W_ z|4>KLe`S7_>!c;#tBre`AFi&k+@B}9j$616iqbJXetGbnif?UkgYV*;N0(*tE(bp; zoI3J)O_%NLqM086uL8Uhba@~CC~<1)2U!0c&N8@T;T9I}qvyQjD|NYRKYzt9t$M|D-5s#rKptJhuj`yWz%GH$L%PeexI9P5C#Cmu_rwkUANeHn zjm#f&ZX}}D0pp3r3*_GczlQAIWp~~DR`Z?B7dGEpJsO4jyia~D?Zbh7P|?RZJx(6) z;Zs6?ACI--U&iwsoJDZH!S{^yX!80+pEDdznEO8aM8#&_ZS z?-?OY`88pmmHkD0mg6(h{;6UAM3Qeod{gm@9W83e5p^nMpFC!Nl>HTa@;P@}&^rgc z%j(1QtJweJe*BT~Y~$a(&zwrPr0~kN+!4;%kwY! zd?TOv;$E;nx~fk>_x0d!yanIMyV3e&>nr5(QXVzvn*rb7)a@SK-lyyP&a)`;UFQDR zO57asE00$)yo$>s4j#|hf6qP%yub9f&F?TjLfo0+Zow~>eRC0BG&=r8*DbEwbnIh_ zUxR%r_O->&CjKYJ^NeFy{}tX={WNw9#qB8W0DQiq|8n`9wBK%+-)6pu&olC>=VJS6 zEx)PiyTCXGT}G%!Yjr83FXDQ;oWy$wd4DPIIpXFOcOIV4>E4=`zT}}T_>A%i+f&0-$c~!7aQ^JX^-cR)d<^3()Hj0 z-W%>a$jTGRbe0{F1=gWPen)J`L|F_|A8JFTU(dhC5N-4aHwU_cL@a5AQJ@ zN6<5l{S*sMJ2=VNM`HgG`@??xXF^|EeAd#rw|l#{syv;m$Ol zgZ*+i^n09>2%@rt)hgZhoKt zEavx=Um^3i;XkI=hw3=g`apT!mw$fu)12N9W~A?KI!Ce}GCXtN^*${Z`)KSF>L0;> zsXuMJ2X1USZiQQ4yamR;h*O+jeD$o)?p=0yU!!Ova4gI}E8K>MzC6yLut z{`c~Gh))aqs5hK*){oIAyYr=)@jm1HbehF~wz>v?8)digb>lDjW#u>1e(N3Tc@chT z=glH<3(9vUeudaa)9*BHWc~;G53mnLMZ+!Bq!$0ag7hWFg6J6><0&H z*%vi_Xq+EjBz67X^>I<07W^N1Kb(QxBG>CP`(P@aQrKUQ==z#Iqg;pc;SPhl3jdhu z*b-h|b!~qKKX~);%ZJ}=^=Qp+r8wQqSCn5J`Bk?5B|TQr^Bg^s!+*%Gt+>H=jrWj$ zC_X3n&oSRhoO5_of|CSJa{KTj`rq~Z)rkKK@dx<~e z`WF7}@SkA5p7{>OyIp^a#px{0<;!X;uVHwE1@8;lwPKe6pY!lio1a6!IP9L|*I2yg z)`#Ht1Ad*1BdcQ<`4my7)_D9Z&pvQUu?zmwQEvJFD&9xp{mrj8{caj>H|_(svG{G| zQ`B|zoxXxRe`HsX-5UGgwSBO{emm{DX&_EUaVFcJbMc8R&)LzVg?!7e7{8*{V_C09 zulTOpy~bmV`?7o2IXnx$obsxI_dWdf%d;h%zu0}oZcTv4ZY#W->YG;|e7EjgeB0tX z7vCZH*2nLd{2$13FPtvw`f-TQ6V#`n{+___si$v@vS&m3W%360MfkKkX8 ze+ztG=@W;$w%xC0<5dK&(fB5G{>5{>Bn@?K;CT?wH+YV*|9``8sJsiy_fI`{|I^xi zmTsx(eu!>K==WCwKmWvMH@kN|rxd~~4PKMsc7ywy=a4CQHQ={{{UNx&$$z3abzNVl z#GNZ{Z1dCUG2cFVVIO72|AqOh#?9@sU9@xL3WM#XW>5^|3Ua?XLnwl=Jb4z zp3|&1vtHkI*Gm4y<+DIO2jMh?v%$XEN!L_#I8CpA?9XCR-B;=T!1@^RYm49BzJKVt z+{r%%oTlPr!)GtM=KKrGw~hN|NBZXV9NyG?Wb<9v)wPe0h;!073O--s)k6HATo=pX zy};+1e3Hp079OkdD9bJ(-XX>_jb9m`F2YvIa^qLUK0b|CyWm_9f42Ao z#P2Szjp7Z&uPK~Ua4M@$QuXP?zPdctS?_0k0=(|<7P9+?T_W>88;_B9zu@;&#EEIY z9_BxZ{{r<|p{zJnlQkS8j4n@jr)uXZe+3pTK(RXYN&=lRn2Q16}g*3rXUA z)gAXd{aW$*;<1kox9IR4|B?KEeLcPDm<0Kw!@yd%= zZS&>LzrgzhzJvJxxt)|o8r#l*9Lx7I^Ku- zvGH8v%=*9KtrD+?`HlMi`b%_ZOotrCf6%KRUVqAC0=^B5@2PiL`s6o`VmwDat>kkJ z-+l0^!%YRhjy}D~zl+>Y0H3UhAp!r}ZC;Gh3Xy^vLVF+F(4uxP`iZ<9&8V z{x$f&r~aQipH>-9FwT$f#Uy_2g5P8OhKQe^u3f}`j%OU>z4UL-z8d@3`fxg@3im7+ z=H7$PRD9}+mtMRf_Q@yq$t`|a?8~Cg@qFqP3D2!~7H40AeO&f8>2(g?2Dl5Ymx8;G zj^oWAu`d(xOKkm5^OxzgCW+@8^?1kojA`ck^2^6Q7W?7Wt6DE7pYQZTt+%nhK_2DB z{nGjh^MmDi!TY&i?U!3{>&Rn_Jl5hh3a>-#8?j$x|6g$)q|txsI+)GxDSd{ie^vFr z;&Z%C^6W0pg!KB-dPDo@p*$wRyUBZA4qBgJy_R|%Qp38c7{T0{MZu!lYUq{zNA3Bv3zn^oj4gAOI_A9%} zu8T=@ylwwBfRh%^PS@vQ{2Ez50q19RIHGPH=@v=;ec_FOcZ1GF_|-x%v-`^;_95c; zrbj2b3}ZKuU1s{#re8R|$MOA9oF?=-k53i*CL{lc&bj9Nvh(X_{v`dE;q@GTTk|E% zN7m2fw@n=;;ju~{CFxXEf64XO9na!;p28~~zggm}4s@aKZu-{Nr_=YRUkuOvq3Up) z|4@Ea`E|yxAbww{OBDMulK9Qwl!o)t^^gIt^z=Kyeir-F^j%Bey7Fux&w~6~yC1&G z{uui$cs%qRb5-0;;vOYW@V|8i@N3R5ws_ybNh+@oGs-VLzl-AK5buOIG0lHW=X{VO=-$5R!^=FJnk{N2|U_D5&?QzvNd2pEUMoC-?>UkKtdL|4z6ytrt-D?CP){-~Ra45jUN?3d6r|e;0(G8h#=Ca@s$~(z-v(BMY8y%$H&JJG;jA zV;cK$Ivp3$F+QG$<@M71NT0*KcHj6B?*w>F#4DD`h^?DTFvBuYplgcNZe4@ae@48sazc2r5@K3|P0(Y%EMym4-pU-xX-&6ZzAe`H9 zeltH@{r_NhhFuGNcKsH3aXj~as!n<2cR{>N@(A8Y=l8yKEW6t5rsLNJzwhV}MZIc^ zpH}?K^w^+oKAaD^NtYt}_{KR?yMKjvw!dtToBvAuM&d6P?;Cm*rN>5kT&33#{7=y{ zp1kJUS6l6$Gw>I{pGp4~>`LSD4j%Q{|3%-F`cL$saG!Xe@x6S<7Q z&U{((bLsFW9U9`b6R-Bx%dy)f-W&18yB=oY@sa$K$v-Nd3-M_Se~0TjviaufFbJQD z_>?eSXj}r`B6yqN+=cVf^*A}qbBE7A%9&qfUu=R`f?q!S=sG?_@ySEqrtBKZGm|_| z!%M@i9-bfK*+%@LbeclXCiH9rKZkSR2>&Mh>#N5X&ZFhxpB85{ojcJv>N7t>bl%ou zcU%24!P^I~E&QYShRD00`+PQbubdZ8J(qOhpBK*(;*S=uws;N14;4Q%yI+tGKts-HiVO`)vSS&bSZNPwjIg>($sd zWItIyPhUqqMdUNY^_JLqT^EnEcr18gFNZqMjb|ID$A1_8li}}Fm!qzSQ{tEApN0Pn zd|TkVkA7q6H<@2UevQRnCVmg|CCt~x|0Mo3)%l_I*RF@@bZth52Xv?bCpDbJ>eGY% z?})cqKGn>pG`|#1S2%6um(zZKV|>B5o%}wKUv;=Y266GPjDIuy%HmgpUN2o2QOv(% zKHTTn)8rG^IMjHA^-S`KW*pD>5&X*HcaujEc!kvMuKoKlUMc9-8*U@G$MwtgW3JE= zzt-}5s@`kiRfG2(|E>I&vKzy01iKpSy5e0F?;oAdSJ>s2=g;!&%s-lao`@d1<9Y52 z>gVUK71$?XUs>JiiN6KzAh_S)RfOMDxF_KL31?lrm?8PZzaifp>XzJdb`O4o`Sr2y z%G!6o^FPJ^B>%Yld(bVWynBhCLi~^PckH(g;ua7$W4LFsFfsl7yPx|&9sF+LS6G}o z;%sL(j9o=_Sgj6y_@{%jUYx$-9M{j#$Fk2xI_Lhvs~28b={c6pxA5PA{{eWd=y4mb zN*^g`Z#pJ&SpNiro7OxoI?eYGJjy>I9 z%9~GUemC4PaQB)|;<;~_bE}EGKCnL3dVV-J;WW1&s?ue&_+y<@m92+be`P!ppR@c+ z@n2-VgZb?8884q{@_S)_l~j)yevhvP`}q8;szU>HI7zSIZ{g3-x6p@Q_Fh6>2iX^N zo-9n+!JUHm>{zl8ml;$(vpkDlY%)ia;L{3&&5hxgCo7k7@&htt-2 zsP+5ujmxhieiQMS!#}&YiNrnc=nwv?<|ymef^~Y%q33w{-E*$?gLhLtU&HSQzZrd^ zSU-i|Bm1Nsyq@r?!OI74wCksr&$T|3cL<&C@*Br59i5)wnFelXG=CdFyjJ2hGG5}G zNUko=_-_<{7yL;0C&7OLoayu{pdN+Q<707Fv1_3(uOCLY$LYZHaGLe#`jfg;T>mZSFezJ&9*dw8Xqza!@o5CTgBfmeq#O`ydUW;pSJQD zPN&Q2vyG3fh zC+EEA#=fxn91-V_AP)S?{4cQU11~23m2@ghx6X9DDPCLk`bVA@<@wmSukjdpwwLE~ zIDMTT#n}(^+@AKCpQ(6G>BPPe`{H=Li&sv#Kf>K8{vGw63FnFZ{ssSx<~yrb6nJ04 zi9v^E=HJ8bA>F6ozn1@R_T3tJZ^g5({7TF347()!$J@X4?BCq-nIfOm_+^Gy(E9hm z^QU-G@Lom#Ui81J-=Mz;zZd-HxtvS<;=#RXzMkv-NTjGCW$E(BdP(^tmQP&k*R2&*bRnTIaoJ79S=UcZR|$sJLtRO)4+4dLV2u~$94F_^u6)>P`&4y zUunF9-4F6_B%fV)b+NvmUVn<$-FdUX`Znud!`%h<2luxx`E3(_sQ4T39e{5o<1pi* z>c2q!6TrFdIX%5Rel$~qiXKboF_ZlX`|86G_YLpYL-F2%_X_)|oBdQqzEkb%Uht2qPfPtK z{R(`0;5!1p$@p!BKMejJ_yYkr~UJCral3#Xy?eL6)=W6?< zl>O7e_#NZ3*3(%ZAx;f(M(S(o?^_>ly&3-d@J|T;web$~jr1|ueS=34dAAL7j^W=$ zz2e}N1g|}Kj?^*EM>wD=}%Il5&b(5}T)cr&F58yX6zev6}?Ss$Y$H#x0_0{;j zlFwYY{oyWyvkuMyItG7_afVhhy}Kt&fsNS$WJ5_rAE>`45A?%Js4ZuhsPZ!nraQP6s$&$s;?SbBy~L zkFws)KD!OSgL9yi_2>5MOnSA%Bcr^ou#as&Z*yM@_ngvA9!KbKS6@WD=;BqzW3Bzr z0`F+z7J}E(zUycl&-fkZ#<<`dg8NU9C%%2~y~!_=eKp$n8{>9#sY;in_E~54_3%k) zKkvqOI=&0>-7TM|=DXqbwKz@iULfC9;%v1~!`yFH(tRV{f4t(pBHkc&pQ+a^{>8+Z zj(1bMo6BRrJSMQ8&3-xmX#7XPA1!VP{jcs7uJ*0tQM)G)9KIh>qg|iQC z3%o1d@!6em4mwPs!whkMb8a`sV?Q2?`0e7?QT_$tE*0kwdG}SHa_V!&k(SN*@U!dS zTX+|&SI46Q`{e9n(&wr9L+Up#)U_|}-|`vaIcSjQiYMY#aBi1jAIg3(9U7?b4tdsK z_f$T|<+n*5(VP!u?4upxrZ)cz`^NIggntb93E&r!$6R^j#iN$}*OL7Wp9i&OpBv68 z_H)@UfnON@b@`RBkNU&!N2faUcx=5uxcwhS8+=FM|5P3q)c*szBvG$WxKr`SD{et? zC&QUbr|QOKjJw1CEuM4F{6h1^?T1kln(S-hy&3Or;zu_wCeD2N zhp_uno;mTjitiG5``ss#TkmH*3;hnL*Lu2Lr`sMp=iu2Czl-vYcf~be{I%;Wp6jeP zyRx2hTKoTf^Z4bLXA5~2#-o_HtKIJ#!ySx&B%gB(#Cr_hx#2{|BRXA=dOtV~pZs+B z-nf(NZ#kab-yieILX1^ByIIfSp_^zU7A^Jq6 z&olMtrQWrzAIAFwcyHkKgVz+^F?#(-uhI5lEq+g|cM9qVw>;cKc%`uq`_bvVe5#sH zZax$I=lUXel*i+$^Q<$Rk#yg9Wv7)hWRVzZ__b{&!1btJp^}! zIDfkyHn9H^zZCQ=ALji*TJOJH7X_?0whxox9fEgkIwYn;B{-`*M|PHHE&Fah-P+Tw zt@#(8_oL!_+4vWJpYpqdUrF&=vd_c59=i+dRGhx4KM&+HQ6Em%IS|++FUc!C#H(0IvYQsrEq=_=E5qi)RRZCi#7Zc;a93 zT$h`UN$I#+ozAFJDt<4F=ZRN5;B9`f`DJjrz$|g^vB{n8Wy@IPdYxg|} ziT@Ml%Vcq9xo-AaKjS&1C7uuCIoHJfK->f3ju*Fz`LE2sNgG@S-XCA~%uN4$`V{(I z>_)PCfyYoh!sU5Vp6^b=$ZlLYQH`*^4I z#?~{!j|=}#eq)03&%PWP>KW7LB$e&U8SLBP_sD(m8}{X&*?(a^Z_>YXUtS_^7jd`Y z6BVzdaDKB-uhO>weLK+iXZ5M2K9A%*UfdjT#`3Gn?-%(7-)q<&zt5fHABuNfyu$Q| z#=afAozA7?cxAw=iG1(TKY6&H<>7Ha+<4-aq)Qe3OZLO$5e?1?Kj#jE|3I9i{0{JI z%B};u#_}#D@6pz4i$C4<(hSdEjgJ{ew*CmuQ~EXR4!Lf=cir^I^A?`fjB%F8P{2=aF`~8GE)K`Zz_zqUL4A#T(x=*JDbV>#9D|#McSA<<= zI$h+qU*5CjU5#BF*L6vHZpE{g^`!XiwLa7O197{{JEQr}`PIdDE52Fa-GKiyJr5;O zE4mjEw>jRY<v-?|>!vnywRea3DHyTt4s%O@F~%ZAz4;y)0-E4v2lHi#EZyvFj` zEuT;L&Ewa?{dhLq9C%#8qdHyk!-_Z&K@`u z>6Xg6lYp2B>FwI#q=K1peRn zj>ET&xRu2n;d*cHocYPVzDmc&@IG}8%(6bvdIEfti@OQ#mvD>m@2alL#EYulGn`9{ z_${DQ2YS|}PjdQ%;=M1PXKOmuQO6_Vw`2DM+~aT~8*jrW4!lo2e=NYW9iFSyVVZsa z7o3UewG2*CcE`p4N&Nfb91!OioMmuk;qgk|(ae8u9LE2)--n&6@8R>@*!I`;pw94e z;x$1&o#oRM-d=e7#1C^0%;eva-Cg}YeGL3|i@OAVWO-&V{~G?qw85{8MhU4L?kx8{ z|B5(?+!u=Ac}l)bjqkaSf5yL;d|Svrr+xA{9?M+^OT>HaI*!3^9iF$~M}3G5*^)i~TUhe&~bmb$UcMZf%@G zyvTk&TSq@#-O9*&nYixF(NyPs^ykz37g7b;}@IqcgJtuwXxu7+i1J3*M`iykkOUE4i3c0^NHZJA)Dj)l2 z;veT9Re#w2|5%(oaHIM;e_r`TlHYc5CyRTS4hfyJf7-9t=$%cUP@jSS2Xy%j@5O#E zAe-^0#)I*%gnxf{RXukdgfkb;PPilBR={V8=gcwm_?{k>;pK++J|6q<7)ZzRbo`2b zYv~t4zn-q=iTM92pWpa*v0wB1|KUT_Ym2zw(4jD#v~aG8TYx^vj!pc*=N+xhyOSHmFiLnk7DBVrCSI-U*Me@-+0ktgiMfMefhn^zZ1Vi z=6`UXJfY9yI&6*a_s+AZc#WcWEqK4%e~saP3_mp7{VL4;O}%=k({g@)(cwIOmWmUd z{Vw*)@xSM~8z#=z;*?`I);>={@4fcP8RHV-#gg|ke9N2v!u$;Un&bBs-Ye-?Rv%wK zN&KhcUuS=iPI;brUxP<;IO{ym)zi<>&!ST+I$eO*9gjcdF`kZf@rjF1LVmm5-^Sou z2j2_qSFlfR{+#(D=3|;aPrtvNgFE$yVnz%34t{&*_8azJuy3Lc?bV?}YIUMRzB}ge z{DJ>&{*mmPIH8`mjGwbl4JVR)^DUfKaJtdu6kYmSFU!6z|6lmmrT;~Tt7LWJw*lqnM z`}lqMkKxCHf69F&Jsq;qA+qsK<2)HWr&z!5eE3?N!E_vl?@%~Z;p7nSzIye9UmX4p zyockx3D1A$0!DqQ1L4lfcO*{tf&R>sRV;(QgF(4zf$k?|{6HvnvNL5xlni zVjE8vx4F0_>Hn>GyUjN>KZzdo=@EkW7QB1HDJkA-yiT*5YQ34icl9~@zU&7Y=VYG^ z|J(S#4|gbB5A`9j<^TC*I@z~<=@%K!DE0bDU2EZ!2A?PFkF(!nKi#mOUf@3;|6K4M z!fTJmuHZU@vl-4S`aKXg8~@k*wu!&f`8ve73cTI)ibdCB{HwWd95X-N{CV>k&0jEH zW?UOyVR-%3VU+o~uD{mm`#v6D<8d3WUGV?Vf2JRfe=_x3M2~&?26!!S-8G_1M!L*^ zvlz}4d>Z3Z8O~ZbW!2@iy2KLqTR79@|5E<*+4T$T=zo|0XC%w#T)d0dM|gF?_ha>2 z$FCp0x!_HMHk(>=^{ zn0W2P>mg2V&wqX3{qB9yAiBk6HOjBV^!r_X?~7Ya z{_Eu*pB}#(m*N+fUm4@9_*AD$O1kW^RZGh^iMU7Pw+Y^Ax;BNMQ63voJ72A*;Qtf* zrf~Pd{Yf1PIX}{=&mr|Gc83P=uUOB8$7cE05x0Q(FU{X#R}SAR>fX%xG0=XgV85i1 z*Gqh+$Tx$!Y{YAzI7P+(hy5`+r4a9+>+mCZVRTGO$4=(+c@7^2cd+v#7T(*vU+QFB z-TUsj;)L7xKe4OG|7(37{Y!a#jL#;miqCp{y3?nb`qYPe4DM!m43t-0 zxF5Ja{>E>l=Y?$Me}|I{&&}#|8eRwSqQZL|dg{Lk=T!2db>Q}Rk=etED@^grt>(4nvA z@Wb>8rBefZFS(vl$Y&-#)5XhbKeVPtb$X=GzpG!tZj$RZihAsEzMjRSr#wD~lO0YL zxOM3J9zK7H+YsI<`qZ!wf5K-vyCu%i%Fpb7cH_lKCeB~@p0mCVUUhiUtgrQRmS6Dg zpuRh-54XO`dS&bL?SseSowXijeVhI}dGEdKETqpl<7DR3;PC{Hea26$Pg1vS>Q;dN zO#VsW{y?{l#&3+T;WLP?^Ub#vZvnjF@b;_wF>y1)`wd=HIxP0N+IjZf*l%YygIycz zdEi~vuhZ|fUe|h8c!l8=l2`07*DYP7_Ui`m-H%u?he0-l>Y<8pZsENlh{9D*xgN!Q}*U_iZzmG>`I2rWA@hKo)RPmP6 zy@3A@@B#ek@F!cpW4#;QPtpA+^_i$X_tmYO`L5y}Q19*ZNJh_i>^qxp$^Ibw0`iX; z=K7)ASh`*I{-OiEm&! zM;iOFHk_Mq*0KAOUEOfJ@h@S$xAkFgTDp%66{nEfs$nXO+duWbCs z@sI2G)T>1CcUAD5if00P?64o^(CZKT=de5`$s;Eop?DNGj%A!xJzlY2glB0ycZ+w@ z{^<%rij-X zPE7a%?BiJWaZ)%xiu*3Ue^~!myx8*VW&I!LLLcj8#OX+ewsPd!@AU z@;Oc%x~%6{4gPaH2aDGVUO(fM;;pm(T74E6mo%QGFRcHW-C1@&(zycth8wpteyBgK zA1qEwaZd4X;kqxK+PPq#9cQ1C{Ym*gaNW1XGcTTz*v-bH4Z9=kV$va{JZ76eBYqZo z=_I7U!vPHRt99`DByN7xZ1m?~Hg) z<(2apJkOsE*k@!v*LrK~JqVa0udGgPzp8LcdEAB*bw#aj${JVtO@9ZzQ z?@y3tPWT1vn>Bb9#xJ+~{2S}zolirZV-uV|i|Npr4w2=x-2MD1zKw#<{rGL*x7Ybr zM%~Vue_?}T~( zEzi#K%qj0l@=ncv4_%tGdk>#Uc>XBwcIx&9P6_dcIbR#HOD|4lJifKBy31pbJW{}m z3@;(PZt!Y}n^wG({QmI#-v*yta0VD>m&Xy~$;NlZ=|Ycb?3=Ma31>B&@#0Jsr!f3- z@E7V=>Kn7m!Y&)XLh2aK1w>F+EpT_cehy6u$X$hwboQd|= zBmg2Y&okeMlOk5skcxOD7bmN6R_`Bbv){r#lljNa?OAY6!RcxKjQJ(< z>LkDN@_J8Rx6=k6zxX|H`|=mMZKFq0x?K~mg*xZKCk8%08}~6D9AEy<%f-g8==HO> zmF@qY#z%}V^H0xzpU)MC$v2^Te$Rg)-FB$MChLi;KhxJ#?|1PUfmaTElHij9@AG)~ zQ{OV`n_oRvT}5NAM@X;V=#%LuZ{obQP8GM%FGXsz1c;q#nW*koE3HLs{VKl!@+=zex)LW1 zzSr!lDtLwAbx&U7>2v+6XK(s1G2h924&%S*+eu!1)*-$8WiO}8EJ_B)3U*$)-%ht&A~3}>Fc4c-Ik`UPG8kk@+et6RdW2Jex$ zz3FgTU(9pXw$y$O$^WM7a|B+C*%zY2DEUp5-$LU8#zpW;hhIARd}^FUoR{J?xBg>1 z*B87k@=40>ANdx?J0adF@ZILV6ocLy-5>vy@2C8(;vZ^%%ok_5IEmohF~5%gBymRg zJH`$0uXM*Alg|Tq2jNu}KV1B(k?cc!L*& z3B>tMoWywV!F!wWQ23`^PZ`*+v|nP_FXxT38Xtwf5Pm#y4~hF79y{^qrZ1+S$!>-7 zca3o$kqVMj$SCU@m==Cm~ui;dYUw!$N z*eX7%^ zH@nn$4TducPAT|R`9CsW!9MK4zd8R*;huf)jI1xLf3AM@-Is=l_Z8eBbco{o%Vj*- z__^^s`=Wz-)(UlvrS{zCdde$)@<31X4bArvZ@75f@K22YCh;zdml?mD_~jL^nRuD$ zozXe=x4b`x_IbV;`cN%>kvhU^kEpgS}wqI7$`Tam=3Re!lA@4DUDa4mwvV$YV0RhR&^5ben4b9c7=< z{+%T6G<5jPxT4QRySx7W!26(h@x@um{t=x|7$-MADBdLTw#dIO{3-a%H@^(;Zg}U^ z|7|@Sk6zw~=4ST|T`SNb5x(`sndf|oOrKEu@|1BQJZ`WrWk0UQdm_Cb(yOrRHyQtr z`L`9Phd3Xo!y3e`|bl zva2jkG<8c!zfHw7I}@$ba`BlaojJBz*_z-?weoApcjT>SI#f93aXX2SmfkEQth z1#g)1;#c;w*ll7rj$Kx9f5W>2++%Rpx-PfjUGR$Q6weR!GxVeBxrm;R;Z%dOm7bUB z*-l+b`&=Zuy#8Q!)cig3?bRi>&ozFqUdw)H0sjE}sc`ngd4boLcvX_eE_uwg{>XZF zensiO1CK*^#Nj{F_5GpzIvTIy{|o=I#@UTqs>5r#ZD)7K{um&ia>k`xZv~wHk;Hk( zE;jpp^6nz+m>)#|i#R)M1S}^uY5po*DVS z$A2$A**xF9r0W9DEA_2!xBi8=Tjf^(pUC(`7iXyJZo2h**6ZrG>MyX{!tP7)avDDn zzqoTV4!-;Gohz@6^7y#7`@Q-&czxn}FXCLe%Wq5q|L=g^T6P=F zM|bXal;1`9#WX(QT-d2j->B09yz`5*-h5PcvGBdd?=!qM;WbnImEsqs^Vjki2)Cm3 z@9m2{>is>tW9&xpzwMm+h<$YR9w(n+-sg_T?>o4G!& z8s|4IPNz-s{Mvjw^S`qnfma{??eSPbpHZ&APtBKeejhNtVBA}N-^%Yj@g~6OLx<$* z)Cxnv=|2KH=U>BWkQ~5pTH-Qc*T;Gq3iv;iI!oA-L^E(92)f;dwv44+! zdGo#ed?vE`$5sC!@W;Y$M~{4PL!Nog_nc6QE}7}F+x@5%oinieO8(dJO|QPS?5C#i zUhzA}??dsMt3!U{*v94Yo#6gDMLinPy&GPa@k|3ZdZ0I*QoFuBfLp=-Tb{=LrPn}y z=ja=1{*ZW)=r&M3BlN@RS62L4&Xq3kF2UO(uSj@=@eB7k{0h2Nly5rgJFUNKd;zbY z@%jL-Y~gNj{k&B^~O{xS8F^?UId zL;rv981LNcl-l{Lo=fQ#&M&HSCMn+g*^Ohj0PY*OZPoLac+Np~AF|5`?<088_2c#1 z@EwkCWBw%~MGYA$j|}oC2`3JmcERWtv;3ONtCPG2vror93thtLQUR}3 zc%|h3P@XN==ca3MI`*)S&PhL)dbFcQX?m>2GnVI#o9cL29gE*_-HTh5e^UJ^_+8o-k=kQ&EZ+3ko{Wtn8`l|f%^8cFsK=zg8Q;5E8#NF=sY><7?BGfYlJwBnw0CuU_ z6~;SGm}f(L4&jsC{MYtbA-M0rea?S~`gew33w{ayXZf$ymtpsgdfs9e3w|Q_iS#M; z$<*nQ&#e|&??i{S#`ByH!}!H;eSc|wmHBeUpTj$1JlVJ{y-K)`y@SU=_}`h|Zhjb@ z?x|-vx+I`W4fWipo)xW!THg()Hv1O%ZgAawVSa`Azw!PS?|jyeiF2G^QaZk{e$sk* zycT%AxM6)5{~Pc(!~a3QQ=ftk;rtWu>nh$R_o3Z%n_zs)xU70FSMOx(AHZ8_eAf7e zxQF;HV)rY%T68H#mtWW&fx8vYICPz2zPtH8#>esgS^jOD7dy;PF+W-S#`Jyc{JSph zM0O9x#9gSkN)ht+P9ylh2!(NQu0_VkM!&>;@1TJ5AM&?*f$Zsu=P3Cw}d#y z?C<*G6c?wx^}N;}(|0<3Gx1C3+#6y)k5`BN=IfhJ%|06YDsa}qnJ>SA@>{1Lt&fCX zANQkq^k_$q!p7_IJs`gn?zd~i4I)2--_-d~7w&iQyf=^U{Kh{gzo`5UinCChr`BhS zdx-s)bQ*4d-M7D*vCGabzx;2}D~pLS9qt(|q)PX+NE! zTRJR)=B;8#+kaoX%?H1375rC||Ex*FS@b`zv*<|?(pwAq3h6vAz4bc6FJiu`wQ5>d zt(#UuJEZmaxBt+6u9w&SwF+7@&D){>ydL)N>kj&e*MWb47xV#MfD`n!v|v8ygK>Zv z^bwdrA7Dgab@95B*nv$yZK&77wXxdZV63InJSzOBw*~&wyCwdo&-!ogN0k2=I|BUv z`2X+;av7p6G!8H-{%aHH7>on0gV%et!`jAwTQC>A4%W|TC$yv5$J$A4iFQC+uXT*( z^Ls5z@OKxqD*yIp|Lv>&+t-YzmfGjqUTvGU^xuB1K92piSL>^d(e}Od`euUQcMx#V z%4vay6}4HBg8vI5rWR4(>Rtymsj1b{YHJzfUq@e0YoImM8f$H}I@aoHk)1#7jDxy# z)S8-WuC>rQ8+X@wYSqQ;t#4pHj89UnkMRI)pqARWx3xiDXYxAGcAvIi3$#3|1$!{4 zbzmRZMc4%TM%a{$iZ7h@)*I@d>H{vF^sV)6wT@aBt*h2e8!p~TeNSzo*Q@n?wLV%u zt-m%v8>kJ@#%bfVZE~8bpQfeaH(ftN3-;+8{akIH7VMEgv*TU|dn;luCN`hWIHE82 z?>N{$5wC+a$I1U=7woYJp9q^IZ`lN|BW!}#5jFv41Wv^M4RGGJ4aWbsEdj&8Hb2~4 z`v0v3>-qfi->smz|8D8o2F+54#}>bX_y4wtwP5bQu>$)%TCg_?Xa%)GT4Ak-R#baW zOO(OqbNZ}WHm#Qy_yuQR#F_X1$G&z zTt~s{z&E$n{;k)+e0gp6zkRS4*aY|yuY-L*C({4eRPs8wzJhBwq6P7@SP!o0pas`+ zz@^r|Ex@Xy)ztzn5v{)0!FAt23(kkYFL)iyHT$=f(+B*5oCBL6ey|>3ei_ZPvvx`Q zS_^6ste4fAXu&zSPn)l;(Gsdfa27?J8G(Ipjz`>&g4#vA4(b>2I_M*6Rw3R0+~0!Q zMZAu%3-&<7eDFH|zc#_^pvHlH@H)ai*e}62m=9h@%m@1?!Z&ywVH3O#I0p8?>j?V@ z-T$|~5xa;MtOu_n?B0GIG1tU;L<{DE*AZ($eadTrhCvOw{(rPV)*@P97rc(JfBSXB z+z{&#Etm^lN2~>Vsl67|t)~{$B0~H3)bzhwE_46g0RnvU`M z1b*;3B1Z5!B2Mr+B3AG^B3|%1B4+S9B5p)3LENCu!5JH&bGx@*2m3c-KG??*uRFf= zI(Sxym=Dg_h}XgS8u7a8Td#w&D`Gx4|07-p*Gt6f;ChL8-RrH_!TA|6-{-B@ecyWB z@2%JU-+DdZt=9wJdOhf^*Mr}BJ>;#|L*IHGJg)>Cg7dnZ7IC&z^g1}l|8JfZ>#(b< z)zj*04YY<@BdxL4L~E)A_miMDK}{mgl0f@F^FZrB3`o#1FZs$0&N0K0xbd! zg4~0ggIt3ggWQ6gf_?bkXHqe9K?|M*{6sgW)vFC^*;85}t+duuo2bpu z)@Vny(vt6}b=Ssei?nUp9qpwS2(w&!t%VXOg;qo>tF_R&YU8wn+Bxm6b`6oV#QT|e zk%<>u3)Qk{{k1yHJ@ad?wD>JNuWLoLURq!6MoaH8wP)HJtzRp;Xrr|k+8ZrSYtNrr z9j&d_UpuA6RG>mybuG6770@PYi?t2f5$%q4Uwf)}F%>VXmX2oaw4vHQ?YwqbyP?H* zK_t-%Xx$YuyCN3)KkdB({QN$ve{AI);;tbJ1RMKL2OuQu~ z1kp7(kw^H^!YCp>6TVAn>=j9SxNVph_}QSqVp=c z$4Cq!JP98nkT40O{t=pM=~{|#B$ltE_7Y_Ty?Db@*+_efuqFbDU4+vnTHnMmA~l?T zM?>t0pzA&2I1#y-t^p$HZylnTxK0FYr8PiE#?aa!P7%+EY1`=iIKqK&A&Q8X#9KmO zJN@>7s3aZ`F|qVFC6PuX$I`P3$J(2^<4)yz59*+R&Jh97l8_g%A95KB;N5@>Tbj%miJ9Bi*9ZSa?G97ctbj&H!#W_0KiFsyuXe-x7d$~5+ z%(c;Ou8p>HZM2_jqYqpg{ovZ@3)e<}xHkI4wb3uGjlOYh#P=jv|4={HEz+@`Q;D{D zh`}v`b-;YE9+-}G!E~$-rblzqJ*NFi7viM5ankKL={PR1I5@vB9p?_F<6OdYoPU^( zVv4*jBv1YMWu|}~ru_mz=u?DgBu;#GVu*R^qu%>R4RH5T6 zuKQdzlRw4vrkKwumI$50u@+(}&5^FNxX0$Fblvxs{M3nUwC=F>p3s`ZTEiN{+LEN0 z3e*kEGbH9267vkHMqmyx&yaGh5^ILdEhGc;3?16(1$RToxGsHRKJW)vCn&k>nL=+QOi4vleC?m>=+e9UCpLjq#Bpwlu ziE830@tmk7>WCLaJ@JxgBwi7(i8n+wt+kER5%d9d;2P=+fp#DbWl?8*tNoTF>W3fl z0sP^IF+jWFgMQ<>3~8{@M~qKOtBrBOIKYq92Y;l)PHmMKOO%H%bmXDG$VY7S4?6l< zK%m{Q(O2ZLScr`@#6mu7=orKFR-2_WyNJ?J9(E1E;<5Y}l!p03Eavx;bmT*_F+khU zW|oe*fKqW!u^)i}oWOF|eM{4vYz_8xr+gC9npepCXPCD08J%M>^IM z<`iobF<4yKs0Z^0KlB4@7-PZeN4=<@`7s@B*1y|Kg33xvG!31_77XPXbbA(rn5dXKa3M})<(7uxN8l0NZ(JOF2sb5 zbhID+Od?PZ(oq)Uggu1%Fg_@Ue73J}Twr<;>1YFN_@Hkr4{^{BW+M*jL)}P&L>lT~ z^NVprIkXdL@IhQ=BaitqKlmVxwUg<{$NoV~^abmU*@y`pV}QAVjx_i|M_IJ>S*!kl zB#VJO*jQssA_nUZ;$RP@wx)4)^aJJF?y+i0L;I1BILt;Y94~PkLpjvR)*bR-W6q$n zb-~5~lFc>pvCgnRAQ207Vc(%X*o_2>O)Bq{_mA@sB+faIIG;h{d5jf{k6+iC*dXUbZDk7`}8Z+l>N^qx(%b z$KgI0u6MeUokQ0_xP}s&OMmCnwGFPL5Fhn>wo3d40ygS`q@pcyDoJg^q*WTUO5C5c zCn$7_FV{wFeFCu|5t~U|W1tR5)B%ZW54z>lVly4}F&%X>ox1i@M_km8c#s$u)*eXe zdP^SK0U!87GSFt|Xa`G!#2B+Y=q!!-u{5M(4wx@=md1QpS}$puqt<+;BMw^=@MpRS zM@QdTx)13XbM%v|vv_|>Lmik8)Qxd~#5l0FKq4P&4*8JCheTf>(Kbl53ljd*3HU?8 z9}@nM@P~vyvs1_iHYEBWL%<&r>uVw5$N_!SA<#F(!#aVD{ed#bM>&iSE$5bdce=TO{_mi2dEFyg1|Uq zek_Sa1jYriF`non%4%_R99!_W59SY2p;h8|0vpFLNaa?k(kgM>LOPCFkccD8(OFzb z78^Q?51rKkoz(-K)dii^2c6Xkoz;ujtX}BILwlgJdLdc8&{@6ES-sF%z0g^`&{@6E zS-ptI>V?kg#k^vDVBU;`>2DgsE9u94U|r$(G>sT8KxxDX%2P?D^&&{~M{*NM98VZn z56}^>Z5m7Wr97-PCjCTc?g>cz#tG7mSWd8X57HkK_}u`0FL0BnB<>Ma#B-vK7(;R4 zgY<>%ez1fjm*r%02_~D%VzRlcAe&1N*<2FH=8{M@mr$~~gpkc8jBGAz$mX(%Y_PGF zaFSqcE89te$W~HFayd*kmt$me$sn6cHrXJj4drB85lo&Vol73sT*}C1pd56_^fuu4 zYoM@|OC$l(@H@AnRxXePms`0;l8YhP0KbDnJtO`?NBfx%SI6(^SUOinJ6JkbM|)X1 zS4Z1fI#)+qS^8h;-1fKCi>W*p7Hcdg?l_LF$I-d5P;MlFa*!wkDbp%Z4mQd_!hbXY ze@OU4!XFa;kno2zBE}H#heSRk@*$CrJj8)SKGPv#BR29Nkp_u$1p?`iNQXooB=VIA zq(j11Cg2Z=bV&F^B433-IwWk>R;fl3j{+xRAQ766Il|JS55^59J_H4ie=c;SULaNcbZS{*ds8gg+z$ z{?OsCPrx4%{*ds8gbyVAA>nU8z#kI%kjRHbK0rPs;z1%FB&!qtuz@V$O~|!Mpl@W*ps|qoW++pbi`}5TEJt9G$fZKCF+>SsS6F9QF%hK%zV(%0i+X zB+5X-e>4GqNccm-9}@nM@P{-auz%nW34ciVL&6^t{*ds;`h(3~hp_*&?yxqno)GU( z>kH|BT1O*V%OD?ZK>HxkK1h}h9iVlWgfFC6MAmIlIKS=noIe;(xAd$~>NZ2TgJV@C8lVcgm z{7;W*|Eyyf)(ZLniFRXcpdXM(heZ1zxoZeI{2<{62|q~0fyDSh=Z+a{Hb&4f|Hy|# z97yCtvV6n=@MAF{+1f(75rOiMC=ZG9knn~4tMvju?z(_Inm{==|B$fRxd;-rA_0F$ z*nb))w_%C=1EP2*&}m2NH20Q6BO?y?+>o zzc)VIHHEdru3M0fa*!BTNH(s2ipR>aW5fvZpGq(}nsk(7>l`s*BfYK8d{7_LA(=mP zltEeO@ME!AEan3nI@Su)v3^*;P>%H#`Ala%EDqA!>Z~l%0rP>5b%uDLt zahMJtTtBe(aCLT0K--v(IP93u)e)1WBPMi=8)&Pux{%J*5f61Co#{*>4$~o-j(VXZ zA7z=&d=Q_NWpyzh*w|;xW;$$kKH}==7t&e0Kk3W|<>366qY1?4 z>L`nJw3nqrXXz{+e4rzpwGsZ%F*e+E=-4AH4%-*-XX_t2Tf@-VJTjft3mfqv0c$^W z<^#!eww933#u++GXKR4XCtK(6!TFWNVdKyI*!Zw^!jJhw=caSZvhhKD#A5A78Kz@j zuzE3H@Z+`zdEC8;@kAP6;|zaRFLd~zO=Sf7&U757p#!W**yso9#d#7jS)a#~&f>6f zf)DDPLLdgxAyFsdAszWhLo8MvYl5re@2|FPL0a2*NNXDt*k4HlU5g1WOxB zegbq8Q;?WIx;inK&>(tKngoIS&28x^MZYU0=rK2>Lk;kA5Q4(r&zWCPn(6B&!dSwH<9kyU~8M8|_E?{;EwwD1SIH zn!vryal}MoGBJsmO$;T55t@V)f%7=72_az{w@Q;%X-bk^OClfFk?eXBHm)VvH6`q6 ztu`cVi&h(QFoF58Y$L5&ZAi>JSHj=AH6Idd4*7p2;SWZ(VHDY5bQ^FT&IN0W3-$#U z922-;zW~`*unqzKE(O>ZTolRXg8jh-$44$WS98HN7#HjvE;tW!!EuHQjtg9H9N>aI z#)Tbon8fjhivihOFn<7hA7GxiAU5U)Yv4~h=9Z;%b<90W=jtpCdxIVU@vsNj8Xr#j z2!ibwwlA>1us5*3#uKznTb^;?83>+t;28>@o#2@Xo|WJk37(Cx;|TT?_7nCM_N6AF zMQ9Uv4x&rw5&DDyf#+L>gb^{7FedO^%akx9%n3XMv zfCwalh{XhsgG-5J#ByRa9UQ|59B0|F569hR;tdf`>?aNo_+3j1@r1zdqwrgf0s_B_ z*hk=ZA^6Q#GI5YdCDIA}&I7-TI7S>N@H>!GL=N$P>o;-ueFlD~f!_zs>rMkf2oV1X z?+xsu?(8Sh2q^;Jdq^aCfJmVPK{|nKrVSTKL=jtvy_8l&Toa_f@D0gkJwI}rTWhi`GhFN z7)WSQOl{&{z0YY`{X@s3|E>1eQ+phUfA#U@-=RPMtYhWBLtp-x$A0vOt$Uo;*!isO zdF=o19M*O%;XL*C+UZUC*vtRO`RPA#%yGPFdriT{{;#ej{@xt49e+N>|5scqu(@d4 zc0;PKZToQ@^zYPWw$AmaUHZg7`#SD_dk+5Fpa0o)_J8N+|LpGrw&(x5-u^w;o16gs zO-t9dkwSFsPT-q4(Zp6FhS)~nJ7Td!9I=DgN$eta6MG1JV<(<~-+m&2I6x#4NklS% z->9~iroY=rvhQeRl8*0_oglIZe7EWpfp3`Q5cmyjE|Eu^Ag!jxE-NZ)QCX+zi&wZvhfl#nI< z(KfL9QTJcfi{E;q?nnalSrc=KRAL&nBZDOF>EfO{?w#X)IPL-C{x|NO${Qe`*W2?|-k~e`*W2zvye*F-Cv?G|vBw zF=k`>S9AH#r~`W$_uwJv_FK#SE=b&mhg4{lxE~K2_v9gQUmgwX1WeX*X8K=JCFJ3b9DUO$I_>8bVH7gzh#*Z?(Z|*n4{xiIRex1Jb~$Wt^gg+HW44sH!)7A z7ZT%yIPm-5y1vI)qaPSINVE&>fCLyTfIPGf?SwCr@P&;&K*t;{p?*yz@En0XOThQ1 zrqe@}VhYOMHO6;G`q2PASx4hSGJ$NV`SkpfZbHS<%@Pv|x{)5#ZlXy#LiPZ%wdsKf zz8#gak=~c5fmWl*=}8Y)wo-g+N2;7+;2S1Jo9W&-J-orUZSdWM_LPTjGFg(}${@O5 zO%GW*P}yM5AKz=h_XZRwt`F65f+pD{^2a=lrF6^_<_vL}j9rcUpc*m0IsEg?p zi+<@v6VixXo{Jol{FRCqL{iCCWgkAdeaO|%`MCfEU3+frdHHc8!cl)Geb+% zVrZ#rXklz-W^STyXsl;qY-l=_+Gc4uQ_q9~8cs1YH`OxHvobR?HZwIarB;|tHzh%_ z^sGp>)?|vpre@mK`j)030FuD-F^ zbZVKN1xCqS&%(sq&|I&jj+TFZPDP7lHG@2~Obzrb%=9fS;c021Glj<0SkKDPL{Hbu z+RDn-O51XVZA(|IbhOM{YWI@j`}lbINO?Ir`uq6son)jegM9t?9#W3>?(R~KULM}= zd_TUEl&`;|Bj49|zQ4PBkc^g(v%d%5(@)CX#n(^D&&x~7-OJNi#+>it;o|G-;^isj z#P@WeU|LR2K2%D|#Z$`Pm;clB^zxIkUu5s%Ztvj!D|NoThl_iV6f%ANy}iABs9qTh zzOR?Rk0W2o-}7gQu~KNTH{ZwJ4|N52`MWzwIkZ0)E{-a(8f8QV(v!9hY z+Iu?k-Kk-wUQ&(=T&Q9Sj*;Q}{#LoekLGjQr%#&#)IF|2*)2&i~6XuQcg4gjx>KkGFG(yJnTJ#q}=(7`0l>wv2T!vgO|IDBP~!* zH{T!K`ZeWJRGUBl$GUCl=dTDY?j}E01IGd5Q9YaqkUuNMf}~b^^F6UveOtC4mY0M7{P{G>zTWnEOQm&ODGHVvjpWaLXi6K8@9E|5yx>Qj2;I6d zFs8K9ux+sJr2K-s`7-7{UVdJVUhXYBmK&zE$?p95v=DJDXlbZ^>nd!s!L2B)@6Sob z=KnR(Xc1?ou`lg^pIe$Eu7`{+TNN#}@IAc!f@CcH>}g#hGOZX|us$v8_($v?HB(PH z$DB5Y7Zm(?JVJk3%NTmvd(+V_H3AG{Ji{Wg#>uga&W?N_1EzTt)qjJhl?MMmfAjEZnTWNUAWWJ za)AGRwtn{KS8{+4E$fyVrD&nBPSO#>-@Ww^-?CLK=omH5%ae}NEeo!7wXzND%jdiO z>~!mak-IhgTs)|Ibbykv;s^Tu2+?wY)$#H2^&QtbOxUzCdVzj?Pn=68Na@&n+dH_p zyZE`#K5_B%rG4Sz=t74f89g5#Pg;U>NTSWzx>h~tKr)$*1)jY72YT{4(zVSU@op`@ z&%w9Kr8xE(vWIf)B(i&Q?94X)c%OL$Cm%L9zCYQ6Ir)KP59HV@$wvHe0@u+UhzMdc zL6?ubD1w`}rA;2|#|VleL3vzz7{?ymCLgu}CtsRuNsfJ1`1g3Qx$&~wQb6J0JADL^HliiDxf1m8}9Q#QdfBL_l z<_+iM)Bo+tZ+6c%{gWYEhU1U>Mk*W|_l$n)582%I;eHY~A2zqY(qwbv!=A*6?@Bgz z9l;()`9ea%0)j$6|M$!IXKPf&KRe`~;{8jj`IiR&)ti55HUIAh|4$g8|AapLOIOf; z!T|lZ5C8C?{$IM5{-weHLnfIn0sfEvGrM;>?@LC&_r+p4J$62SJ}jf7^2bS~2lu4a zT5YtC%m^5;QOLhb$kvMzLtPtBz28+n%uBcI+LOhf2c+w|jr0|n8gRii#G#~qhkU)L z&S&Q*rsa$DhBga4&(}!0y5*zRPU-bGZC(Zm?t3fN{am2Gg^t@TT^q%XT6)`44hFid z>meG_`IehjW28-(z7FmrrSPW@O@{5nC7wTd0S)eD7&jf#t+Ir)v-gj zA>*3AN9Y=?KE6Ileq?9So2j2ae?QhM_UU8yX&UkooiF!R|FU9p`k-r@o)%Tf-<=O6t(V!w1sMX$a+Ynzj^9e8`bzn*&ha$dmp4C*HVYLz z7we(@rAXKBu8dmW#g4;tb{A?LZbQ~$?5GS(ERdX#_pmG4MPH?ch=pzaxN&P*Bt%168?+UZ;g+- zJg{&5x~ol{l0PU3TR;mz3l_h`ae{%frYd=UnZ1JgxX!8y*hz zm_0V<(nAl+K+QR%=aoB&uH77GG_ZO2)fMp%)n~6>(at(< zZ^N6}US}U%zHM|Yap07?vr`U^f4AYt^e^E9=3N_pcj)W8(dw_Kmz=N*5}&xrwnNsf z4wEh{dbx7waS4f{eV$jGI#1oW$ZEkpk9}45mKAmUx~$Z0d~}Lnr;eR^S=oIvUTUYK zr2hHFqr{rwo2;64ANJ^HByeHL`M7R-ypPnLEbQMgey7%nIHj{U3pYv*b^hcq^|DZz zqd>tY-7gi9i!^r(D7Djey#Fvr*{0bfNur-MLMBXKBOOSdk<-pAj8$WkYiY*-s5} zZJ(m*RTr~)@S;Y$OJ%P#9P?(j>vZ6BW{Rbo|Kj#9#hN`{UogV-0 z{Cse1^*UXj^?s-3up_Hn#V)P9MEZjNNJ+Vi6IKl?TJ2Y{VU?Tm7J0{Ir=1pl z+(Bzn2R_&4olcmP zaz8l4W8lXbyNe9817CF6WZBz3p~F+ZSF7{xSlG;;=KXwD(A5otqTENwYvm7sr`xS4+e8CX6gA?wasC_|>w*U(bdlU%GmWr?I1Nh*e%GhpIl)>V!_TM>LPy1vQLV>%*zcpxcObd&h_oO>wc-sQ$Ll~t331d zJKb?pdRaF|HC%YRVVp>JiC7~^lX1PO`um@F)exv(KKqQF4s<#;J z9xyliiqz!yqt`lwZ3*Amu*zpi=5nh|N0ml5JF6bmIXJ{D&VF~T+@OMYpH~*%zZqTs znAarYwEXR7)rs9FZV;}zu}SlE$o)Cqssd=@)8{745xV!1j^o~2K zhTck7zbuhZdNcXcnp-u&q7?^>YfcZ?BqVy{LyGaB!B>q-_ski?_YN&y<9BnAvkOnE zz+jQOMq$~5o0md;A8PU5-Kihdu{7YKglO}D&9)Z2h20jsE6%q*wW~O&G_zs8=BA#j z7M_VM-|~1++Dch#%_+ByUl)$&6{%K^kl44>)qG~xz3C^_&wg!J_Il?P$<@`bx_7vf zXe67vR$oTQ%qfX!o}@^1`O!GvczZ-sm?%x9Y;zQ5vfsy>8ST zd2a0^*{xO|m%a;sI-_CC^;3cGH%_jKjw`Y4EBnbWXVtS?^0MJQC!7;FJZPe_h3k{} zjT)yjV%NSt7dN7);HmDtnyjQ=V)Nd|oI3ky&V+8MqwXK*UMYU^=;>##PJEbhvzMQZ z*sVy3`43(UPIpqAW!S&_*2}|kO7^&IQLNA7-RY8XrCGhvC4Y_nN$sQtO|xlslbS@` zgpZA~eJVa}boc!SGCSXUn*Ug=-^YklpX|ET&t4g9+3Q=m<>9+yHexP(=fN*$oLp;O zcCyc#hhr@ZCZBUzAnR-G6XI*OZDVtf2^aflYYi-|Gj%c_l6$ymdxy`vqpsJ49B^1R z)IF_I?Tk^D{zttBK1p^ZF$GiCg=B@xZ_M7{X4d`v%B7jRQ=Rzo8sk$u+wHUV*|){jl#Z(WyIie6gnzeZ2sYmYGHxpQmB`nAh_W>FiX^<{gKuu9et)$UWq?oHbNWt#em z>lcI%j1B8G^W*s`Pbc+Q-MFe*`dOu~$@aMScjtDoou9ft`^CeuYx5?bJ9a&*TZ2YS zaq{rk_9CA*$qg#3iLNcp`EKF8!cafp_17Ca#J;|^FW;k(*!Xfo-HC6PR#`6f%)WB* zY=m2_%mJrwOB@&U7j?<~e8jhK|KgIblg3RRJT1|__fFFevlBbV=e0Yz$)!fDsegfk z=-W~=L(eJ0Ul-ip^6By0rsBqe{gvxxW-XfFHoMYeyW~Uvnfjw*KJj~Y3{$XJmp8<= z$Lnp!az3n>vBCLl;+Tf9GPW<5?RE8Ql*rOlol+TnPjY&uX+nPTi>hjK{eI%vJ*w$} zn#tR!`Mn0#RP2p;E-~QJ=WMAX0rRfPH--(J6DcD;;GSR4648^v-w!6IA3vULGqguk z?8+`r=bb4R?>pp)i~GxMAHF~87O`{Nh1ACPo6b4D8`z|=>B_N?uP0*Md^3(N%}D$d zA)C9m`n1zh{cBbCEA6g*IJPvcy6cC$E@SttcT1U|z4+Btw_Q=kWV&Y=r59RWF5B*Y zE=)II-r{nnUAME6m$W}~apCg~MoZmA<~pqLF-&)=kTmE%&ThxW!VeQtN_X6z)TMfR zd643&puo&^HwyAiH+38E-Kc2Z>#0TEltZ)CrrD16c~J80|PU2}5O+WEzjE9Y8h%v`3_u0!&0#nHjv9pgU^FFkTEzx@>p z*>j5XzMsB){FPwUmSgL_I4^yzrK&OZ%i}bin}JekIcHxgj|xfJv8#*m!O*GEQ_JO2 zLf<+@3SQ{`;3j`g&~DLXH~6nkE9rbF?``z`xP-omOoB%57=gN2J=5?r*Gs0Ft8FUX z(Y(^>?5#%&$A{(5sb1?{uxjzXWv`59n&yn~U1VoHJUepZYSS36PutsUfiv7~`#&}&e(|0>`DQZ`gIPda?PU=NDPbAvi zU7V$>BB7b%S<*50;JsaIHqWT(B{HC!tEhJIiMo`0tETA@bh8;Wy>R8!xwShi{l9(`%v{?u^F<%QqRQ?olgqvw7kYHC zZ~X8*`MDOi_F1%Fx?eZ8e6HaueFcwaQx9wj9ryIgiq)0lY&vTuczcDPtU0+S_d%k? zDK|0mtHwn~hYw0>pT6x{Qj&g;1fJCL@qxaVh8E9Rb!lYhjq`ao!_{pzH;r5IVPBKH z*!W=HI-e_Uib0Q z#x1*Yr;Co9-Cm8SFAH2wrEYdEe^YQuLp#>zanpgm9o@3$UD(#|rt{@J zYp&iCn^z=Kd-6oC)a7$IYifB`Jya(f3~?U%a)J8MgSA5hMs<1;)_Kuc<>oec>>!f_*km^8riz+GiP9q;5d9m_#P)2j~azuWeAD%u_1 z|H9p}2IX-Z(lo|PPg<_;^Ee=K&B4%5=@nu*uEhgxcb!$29bIRUQyf;CnZHNepmzI# zTD1cs>Q*EVbvR~Fv3-a}SEse%8P$8r(kJZg+hs+4_`1oxJ~uS%4@ny;EW4o8vhSGi znOO(xHYQYz6Pc)8kUaNl~LZM@~q0`Fdg7l6jLv)W?iI-)(latNGHbWpO7rJ`tW3w|M=j zaP6b%QQ`C621pjZ*6jPz`RI*-GgOYMzOvr0A`?0Hxxj<9^T%JfukY?w`1o|Tx}5!n zUVA60PfZ&#B%r^0%KBw$hx&EsB06VVeX6zeMa!4>F5Vezw0Waa?z0$w|JhbO>m=7& z$=nUlx@Eq+EXvmN2}?j56mciW3=+X%Rcv=-+j@UI`&w}%8C;2 zHPNMY!E#@E&iOc3d*3EOla!#4VeymI|oyI*Q$u0`qz&4>dS6=k~^5I5ZzwS{^9coU8zCWEPne%S#0sE~dmKhDKuaS4Z_M}#B zOnbWlQQ-|yTT1unILOFqxsd2lyk4 z)|z{DEIz&QhMtUW)*6k^-wqBf>h%4^;VBhry$-lUe9S9LyazuuFI+x*vh(>`QIzrr&MNoxj`W1;5)v z-~DC>$87)2mO7pHn?2I4;WzuU;8w1!nWdA>wYwd-`J8Kyein*Hu1@=JdV{X3($3>58t?{a^O;b;3g~uK#5_q+NIsnEulaUM!I{ zyyEsxdvltnd|=p`pLXVSQH|aUQhwU;FN;h1=igxV-q4TI^{VUKh#?EkR9=0WI(AGZ9(+1=eI#B}Nr*J5j~ zn0TyL`^!s8ww-OUyX~1ORXHW6Uw-bZ7W-z{z;|oA95;_$6azb0w`UKz7v_%>Gy3DP&a1f+KQmoKNMuA|Y8IKAyC)gF?t4+E z@JLon4cXU3uI}{knq9Y~M1DCQz3&^sTUEW`ZFRp@XY7tqZR^Jd@2m2Z-5u{RUg;q{ zg-%zI>Qt_GU+}=LNrUMZq>9DT?}qE$+N#r4WYeg_^sCzyV=}yBjF;S;rvK{gUHU&; zoV%#1^F*<$L*I^BbbBrDl54Yz=-LC8yoSaTTe_z57V9k*?ot=NAhu(S5m6JYwalgu&|;cYXco%*Cw>q$}PG-rvP_Okay}9}lbL2)@|pRadlGchR22 zPw#ega4i_--KpkVX1S%oHRU&gdeU(c!$+9y+02g~tL;!BaO+_3uEtl=7vC>Gl~p#u zM0-Q|!_YD5FFqZ;pTu9Y?qONL>dp%4##?rbf3bGYJL_V{^2E~bhJyn0n}@kwT3n0zqF_>*Mw(7AeZqFo#PCZ}9^9-&;V)^OsM|0&%u;^CHGA0N}T zyOiQ&eB)W~6EO>uPgl+}nlPX)Ppo?LRk?5awbtuSSFKG*IMidnajm@E?T)FbZaq`4 zn#9>UJ-$Bf@ zU2d0c7AV-9pdtFclAqcyyu-ziriVdy3!R4tS!meAXIwd%pgN;ZBCD?JI?v~ z^lF&boa4g3(cKSVq_R-+dSkVUvux2RN*DnXG_LSmDsgtr^ZO9E-xJ71}xP6JizXJ zbcmUJ?zIEzJbsrg0ervrK~7JW*Xu@Hk(&EF$MwvD*rI`^r9C#pE`Pr0v8!HV{+Z>8 zg9~oI)Y!eR=Z*`#Q;#(c>{j>Iw6^p@O?|L>gVD9E;zmBU*I&oy3>s;FCB;rRXWPB- zLwcU;M-TV;Jh7*Lue#hV^8G40j(czWtbgKI7n3NPopZg151%+g=Fz^=fP#l7+(QC7 z9~r;FYjNy`0_%6%s|}pCitTLn=v1$v5_f$2gYdLDx$CrYhZ;N+yTE(0eB06KC&t!C z*1xoUWclIjjyaPaynLeKe8PFuc=va)wu5JdZSZSY#VdOll)Gl(*+aFjoey~yMFHKfOF1Pni>$BT^xBQUUv*}{1k5ma4x|Kc_();dqMmE+mQL`XU-!B zOp6y#x}{%uQlOXB*GDUl@(*;>&fjqR<8bYMDNAQc51QLv-(|-1XK}VJM`||(`YWic z+g31sT>QC1A(BoRw=8&}Zp$*41a^EAmoOyPZlm*AdB+lSlNWm19_lN{WM^r9`ra&X zKx)23?)KXS>M1qmC%!M%E=ks~tIsRRT;F3*&He`)YlANvWN%Rs$WV4y@G6veHRMsw zo#d0DT6VeyyiO+v)Vtpq-R;KHnn=H1GtPENjOtN+v0t6e=JAya(p9!!S`*NxDk#3% zDzmwJ!FIj8>`~EktZrW(;QDCst+(H9d5n5DAV+b;Zi~koJI}YBn^+y&r@B@kL+@F? zBM~cPK9<^@onz;uJwPIU@IBu_P49I4y%%LKH}p9qymei&rDTwiVLMls`;}eq>NTZC zEpU|BRk=o3=vGvedBvw&C$uNpg_fo0+4LPhU*m!;Toz6O2E85Es8no!8tG!al$@b3;bD!$2c$Ro0`NAo|gv5>x zJKd#v&yC;Qp@+%>p{rsW7Zq->SUPy!wy}4u3=Q+Y9=YGnEoJmx_uQ;* z2X|DynPeAp>4K%wrHg4b(b21-yf(henUIyN-Sk;vP`28TfTD`P-Q)eY>lqWlhJ3ahoxSzw-fc~@Ge>U?v469~Z)SAW zgSC^?{OtX+t3Nlybzk^7TH?h2z}M*VW5szFe07qPaF*E4;mLXkL#9+n$og zo-TIkTb6MtV%VYEM?GgenY%YOcaQ%4oVSPWgwMU=)u1jMd}~PkdMn#5Q%)_pKdjbB zM5=gfQo8G*ZgENdYz`;vYB%!x$8L9o??wktiyzZJOjj$Za&cj~Fb_AB29=4UrN>_6b1eDB>! z{j=lcC45~*-*Ir-a%RWs$bBQPe7jdFR#8XG%p!zf8VapljiKcXn#J{SkpP zjk}VZnp}IV?J%Kt-^G&ohukh)`+8EZ&%yN_X6@O&-zTo{gWjYO(|22?ua9-qNL(W| zF>Y#D;D^UnY04`mmPVwEEIM12)H}1k>-5k)oeJLGc=PpD`83J+eYzfeoo6!w&u8t9 z8Fnlqp~lE*PfvUAnB#o?(+z{}ciCe%Y0ZqF&R?XzPk&LDWPQEe@y;o?i`G>6fA78J z&DwJ9p|6uVmgLo1S)RF(Ud+ z?vwgKx--iTHdKwDm|8G=QM%;w5#_JLtX<31JKgd-a^v=p#|zin`1w2VMJ}#3G!1pO z8Mo$x`*rc0yh7EaN_)OQ+NY737gtxfcz$Yk-6A_rph8*U(avMT1@&entnyWqu-6wn zTOT^_h<0xull_|o>^@frz8Y~LaQ2NIB~r!jv@$<#5~`OgiBY(uEja4?LW_9+g~HCu zwI)mDy2Jwi7Z-GRx<7EqSqnY;RVqrIWj?Q3|Df0=&O51S zX@++E#nU??Pju2;G%s=Ow@SM$>prLEDo*IfPcJ^&ywx!gKY#K=h{%1* z!AsuG?7iK{d&6Rd#XbeMl51xidEGy6wz15Nvwh6`I`?q$mTcJf()U4-*Q@&u-Jflk zTzLKF>geP#X?iwARqG$gl&G{bN;(y#EKxCL@>Y-7>~j~^NOpg&B3xB`VpG$ly**Af-EvngY3_9I%*1DDova1AnH0J^&TQ&>qp{QA+olHvtR8+em>4*__KAN| z&ov80S1Q&wrSW5qJ(+EJ{Z4WhlX$HpwLUAB?v+$b*6Vh|d~Zkb?o;j$G^vi1S~sRH zL|#}wRrICNmU(t37Py_LPS98_VPld!;M1vhPqf=r4=4$rzbkc~_%P+SfhDyehsuKc z&eL{VI;P^Btb}&;(3$Py2YnI`oAtr?=$D-q0}ja=ULJ8`oAVomhxaGyCMDcix}!N` z|1yC_&F#Gptbg9FgN)WL)3AnlTSpEGH%;HyVSP~K`NtzhpUQrd_SO+ib=Uey6JxJ)W7Kiqp=qJ<&)=TkXd^c9?m)kNo5xku zTwd6xSt4c~e{IxS{^F&Hb`C7zd5H$&L(hmQ{upK{(;FWr2R%) z-&bbWD9*3(CLZY8V6llJx=F{i~pE$ zJC1&Ec75MtmG75PT^&ZL@6?|APT<0u?fW8@WJYGcH4Nzyuuyx^c#R_8xWURP-jYKr zZg#n0+1&M8H-Up8H#QD=+3vt|$DVeVcOTR=k{4E0emAA$@zNQYOA5=c-J2{@sFQJb zW&e9#@%=uDibRKpWkpwwl#8sbN$9p?c!*@*%r{G~_$+(xIH=_2xmWug2icF8{91bN z&BP=Dqc`;%CpxRCJDwJ}A`#X>%X3?g+p8Z6iR6{9I=w5&=(=HUAJe4Ux?$s{?i;gu z+Ah7F^}7dO?|SRmrz$=5w-&2XA8ipju6?;?bIuhB(Id|sdfz>%^Djs}*zab7pf-z>ZQI}vuaA*sv1U$FctcWKyr`D<_eWD>0BtjJ1UIyu|xsK$9&@d;O7 zD9x=Y>t8Z*b!pP#Ia76mkFA_l>UcdkXnw)8zRGi~_YQK9FE}mos6taQaZkZ<*GoxF z-`ma7Jjtx6NptR8%(z$W*p zqo~2dz@FQM&F$(M{LUENVZcS8Mcm zShGFevF7vbCy7G>?o^5Ov5okyJ7@e(=kO_u>XPa@icR;`xTT~XB5?F>kD6)x;Vb)SQ2l7t$;KsikDF*SiTd@z+*#JojX#;k*Sy=55GW zH|NUv2U9b4X3SF69;@0gVC;u4$18SM3GMdLPwD?8Gpe%h?(6y?uKD}gyVHG`|IvT4 zva)ir^0ErDin2*6AvMO>a@+t}{iYiJf$|@=< zsw!%#vZ`{b@~R4|imFPg%Bm`=s;X*g)I>F^UX4PlQ86`2!n-7R4*~ClgsO=1Qi8&ejs=-LVtrJtUqH6;s7|IV6jSbHoec8qtk>yA$FBbkL*K z?n1IFf%hjm6P;Rh*kT0o;EQ(Rynh0@P12w0%`E+O&~7n zfDS+C0CgN#FU~to6cV?I=fo$X(*|+g0Ae(uN|+Ec2qz+l2q$(CsYD)8O-ODO=P41! zge_rDxDiW;C}J;hh{z#Ii3;Kk!P`XRMGPm@34LNV;X?QkD+tUD<^=PBF<|2co8@7? z;Da$hI_oF=kq4VzOKHhNK7c>cS!|3g(*;RqYk;M(`cMbTp>F6Xhdk!b)wyZRkHtYA z%45C|i}|8Hq@(T7S^e-s8p^XYCgG3ruu)IXT|IgIi9y6rVjQ7H=n{s6IblW2B<2wF z2{&R9v65I%Y$kRPyNUfoGLcSX5T}W=#08?1xIx?|?h+4)8lskHAl?#9gy8O;ye>pf zLXsFxj3ML*WkQ|MBJ>GkVj5vX%p>@OE3uFWAXX5Yh-e~?*hB0mQi)9BBypNJN0bt` zi7Mg|@sy||8i|j@S3+P9wSmSB`wIIOdl7qP05P7x`$X7dSbKd4>>n=J?|5g3`LlHj zf5bvr_#-C3zC@XpxgrT4X+o-1M;j1>9YYWsF(CU9@P|ZwhznoDLp#w2v=I{TiOCZv zi+XVkP$Y&DC_kD&9{gEbQ4VnskCnmk1a+|E6xub6=tSeDMc5LqL=X`{q!E=w6VY{V zPhLMlp3oy~2{&Rn5kaIASBTGqU_AK~{fJS-B*L7SPb?)OiG4&Gah510UK8E-_2fwr zh#^l>m#`vyh%h37$Rn;2&j{iDKYS%fjwB3-Im9Aj6Olqx5{-mNLQkGJp-;FHp~N8~ zpSVjrCEgRA>HWts#6-e^m`k8NUL-?_EyMxB^2fXVyg$Cj&C~ty9(l`Kq`cPmLGjk9 z58oT_uY0z=Es}NfAi|{&z#m*NO}6d-dE?feRCFX ztNwgjwxwjt3)8>9eatia@lrg!B}%W1qZ_~8?&opeN&oReYs=RUetFUN5a@FR_CEML z0p~jeRPe`p>v-*z_cyPwQu#l;E6e-CE6O~bU!N_&nEK(}ac@4~hck3PU!DEkx#gPz zyg$6C&-?w`3p~!N!MvZZ*Z&;ozj;BqO%9pAy^75<{Ouil-tVvT^6=_8UL*ebzWL9g zVqYoXX|c~0{PqnAo+bCqeR@UR%e!Ure|!0!_w&{HmY3$Sd|Ey+@ni1L@|G3#>xF;L zTkgC+d;$U`e*2a~i&N`25q_*aKYZf?FF^mQyXDgYDB9*d`d`cDPv3Lk{qb#dp1IH8 zyyVT9d-l-@-f!>FyWra#_zVZ%3C-ZVs?U8*pZB*PY~UHw3)WH#?0xB#b9!6cmp+?7 zZ=F*cTHojYvDGn9tuZM)X8*^F?>ybV^F<1tN!xEH{0jTWH_U%!zs}9_})^9#w9{&8<2A<{5 zZyfymz5z=^t9~W^I$HkW3nrXJ!TG8N78U!-!vcFxCwD$?n!lHyJ$>TCk0@G0Rw0^uSr1$zs>p)ZgM2&sx37FZ=< zCNy4nApO60@8gLJ4HU{3xG&U!mnOm!a1wkiz!y;z+$dNoI8Mk|_=(6K!B4`HA{|6B z1gDDh5)9;72u>7wEmTck4P7Q&%sVI)Br;UcQ@}$=S~yi$m)9(Ch!-L>k5?nmUN~BK rj_??st$><96fa+7hk%ZtzQ6|&p4RE28@x`P1*nZD2Xy0or~m#xH#6AU literal 0 HcmV?d00001 diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/pptx_processor.wasm b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/pptx_processor.wasm new file mode 100644 index 0000000000000000000000000000000000000000..6e84b930c2662a0454a4775f5f1347b57d615e2e GIT binary patch literal 231708 zcmeFaeVkoYeeb^?&ht4lvylLS0Pb_3VTNQfLCAB2tf>SLt%0I1e|_Ei0&nI1WP(aU zF6H*dWRO5d<=#d`jg@-SnwnIJ9W|9)8#Pwi8#T6Li!~~0Y|*9~Wh}AgwkW^%=ezbk z50eRC+xy?o$n1UgUJu{(UEk;RU26xgx%2g55Cq{p(M7j}TZ3Doty{ybx5QiRQs$!n z1@<7gB?xXQ_}>%@ZmHVKEnBRty`XBS`bB%wzVX$z1onJO*2|V#;w@XYgj@7bH(S-V zUY4vfH@?X&w*VHcR zy+5v>{Tr{j^R}Dse8X$*xbt;y+;sCD8*jbqwp)MWvf9Siyy3NPeErS;@2#7H@HAzr zcf4lP>u!GI9k;$AsGO$Az<+-2&Hwqf+i$&d(`z<8Yg=UsZoKt1uMMiFX{kbi-`IHD zrdv;2qJHZe?z(N`8{hD({hW~(y7RW*y!Fm!Evg=W{q{HBd23KR4NU!%0AbHLxHG%T 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⁡Ihl-9wa)1^<@7+Isc8D-nwGpx- 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 Xf)!I69InI`qnFrXZji)`5)%Fkon2@@ literal 0 HcmV?d00001 diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/xlsx_processor.wasm b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/modules/xlsx_processor.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4714d7bd0b0e94010b867af63f8b3e5b1190140a GIT binary patch literal 634791 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

hn!; z&cY-8+cmLp7X?)--17la1CX{DfEJK`i~_(utOuOgGxvv@A}U0bsK;f(3l=1eUtnT* zv$0W3eFOTIEo4SVFkxQ&9Nj97j{g{I`LooZu|R{&p$@I~q!$hKHgWVM{h#5t&14N2 zCnJ|3mWzVg>*17EiZ2Ic*3~yiMtyH-OE1cRN1;Gsgd;i-oIFv%GaG2+fd>9H>zF~) z@3C-|76X>@&AlR0lscajLpEJ-Sn6SSWmG?qa7Mgp4sz8mywrTn5JPLbyXkbgtr-=d z_wccX-VifLOh*vR>BOt%B338zMj5IM0t}Gkcx7;dols*`jXeR>`S*n^3DYK{Dwvauc0FwD? zZa+0J$-W4Z+|e|*Zwj}aN%lwhN>Oay>>Am%jjcRT2d2_M(yZx_Slk{4q6TZH;%rn{ zI7zT9ED1DgVrIuU1_*21>3I9tTq{~D1tUu3rL67&Et_K0P$OIh3w#W;&g%WPggd3xf zaAN?HYK|yxE(Y3oI!~ z;;tu5S-&$T$L8n-ZVM)|rA5zKJL;Q=9OvP_XIH+&EpgZW=%{|DuZdu?rF?enhQErL>zp6jyAt#gaCV?A_->#ESbn>gu& zKsyyEo->=j)M5BqU^y_5FRjVxtZ3eaDwY)~sY%`S%1pjil)K(#nbHt!$QP?byW4HI znD_Yxm&v2OfSztJ6?$XwmYvV;n}{E~2OV>*TRh49U7js6NJGR5Twj775pGnOmfA@@3f~Z7$kCqv}bcoJeYq3K!|BujxG93A5(%%>#_$++(NE zBucgNN*xssDcI`vh6-nJ$i$lXdETcHFeIKU{~4IX|A zOU9hOAJ1nbFVI4__U%{!i`=;JJplM1vC0n{Dm^o#9cQ4VsJcuKfbYY6=>+c?g^Ut! zE}G!dQ{y7&azj2Qifq*=a;!^{R=7)*n|i5oY?5P!)&o}l-Nkkm+N%?&aZ5P%b|X}g zqX>233_@M#HVVXxougmI$rrydDy5?0WF3Ekw%b%usdY>8(B15;r$(`wFyu29*~(}y z>9y&5QSpC2b2Or^bU|4m=b^RFZ{Xx>!EYiw$paOO?8&tD!^D z@YstDucX5vFE&aJDfjf!iSd0$Y+{(0hNHudPe1iBA}Zn{Mre!E^!wwcmTrm85WT}U z^Y+~m{fTZDZI1rbuH>oT6n)LVZS(f4+_DuY;cKpE66OUnh+EG7M)SET=G=P3la67F#G*~Zs`&EL`ctuodjWJ=?66N^(-4x=a){j`F>1%DAQ8Vm~kNBJEk(l zb>h0vt6$hK*g@dMH-8WIFNI~HTO2H)q{EK4tZbJGZgPvC-8FH|F4wpZ|HH@Zi$%24 zEkYq@aSgd$G$>}XLMl|o-86BPRwt5Tu|E{A_`K@ysZV7fJ~^OX$CP_;SH9UxV(0}E zNbFM|>KAL{>uXeX5i6xQM1G7oyq{abmKLF@L#>LY4z=jas&lfID$p z03HXd(hI;3HJ*Oz6B{+(y+)4-j*)-h|Fcf3I~W%G^lL*yb*vMS&08Hs6RRF~ZwlwO z?#Z?QH#tlGCFirCh;1q8#3!AOQN5{k$cc`!U8p>)m1)SFl_wUYzvwUpeSXu;A(=$1PWEkOc}iC zM@O?;AQ$(z8{@2oTh*J#-3D%#;^#>K9_m{&e~a6EFX)E%7cZO?kt{;X)?4;^V9#Q3 z7T5FcNcB!{UTsMIlYNu!G4ebt`^!<6drSIV(>@iBhgNwoX;7COA2M^l+F=42Q5o^*>BcVc zBz&Nr%w$Vl5;FW9L2yq!x-(lVIT9$xnA0bWIYDLM-{B6;U{G63)@vd@#P2OfvbB7n z_iAVft>DXWd(yw|bn6jzE23adWMs;L9Nb#?kFHMiXE-aS z7|~lNqKO+PqV&5T^hC%)+Qe>A&s3amaoZ~B>(M>}%-%K1%%GZ}kM>SAx7EGneZ3QG zkp=zP4D0FGN)DE}IlIfD%3^>_^<%>>cj*>>jr%d#5Ghu8*f! z?#EMn)3YRvNu7XsRG^fIhPasZu-@Wt!z@MP3&N{5M~~VZ-J-I7j{cM8BeF-xBy#FZgQwiTS=SvVsCqb;X?#vzD3It45F=lFD?h-xW19mJMm*J=O!PV~Ncw1C`43(UL zQ6KA*SgzzMw7t-QapO_-3Phd9W!x?z@>J2%q0h&NRj$ey&gY zn8uL7Gz+$Y8>)|aMMAmLJ(@l1D90&v%o6I;5IyBf@mkbHAwvyIP(3cmA?JbTL2gdD zuBy7q|J*{G@k=@8Qj_g89yWv0J{*2VH#yi7PXOZk#A3uMk{q|f>DXCTNP8dQXke{6 zDiC*5@3q(fX-xKawpHZ16mt zmka3E_R=PvDcv)ba0}hymdLMk%a75maVoMBQX-82k81dE*uw%89~|PUosw`|+kGLJ z8&c>P3aMxEi;7oIh{-#=0;r#him&^hMTEtBgIjWB`)I!S*@>g^qxk|257)qgBSo~0 zt3Nk#anZ&lR3=;=Pu|NwpqE|DChy$=<(m175ZlI)Ay2q(_~EMTamLUi4f_;0 zTxiIy!E4*duH)H;AbJy$l_xo|yQT$>*K0XwQSFy2loodV7 zw=#abosH|duZ>BcjK@FImb-dg{L!}Dew=P&&S6b2fn{a9r=9+dG(77F{*pLf5cd%ktIj7Dr3 z&*cI$U8$6k#oaC~H>^6`_dD~AMhR!5Nh|z8<0FU}epBcD`t$B|pMR=UNo(}vzU+EB zrQ`eXp?2Ti>)ogKWH%cH806H0Cqkt=kKcq@=KNepJFBa|0@9ae5D%LGoqa-+}!PoJGR=7T&_z5&^J_F&WI3S8nf7;`N=+gd-r5U15`?G6pbc>8UU^oZF zq@HeKd+=6nVYQn~NCuE$ps`P{d3DymdeU|f7_u?fe?hou>kBLk@;uMKX@J9#zw?tidOw#I{S>bfJp!GI!usk(if z5~|a3q?q=oNR1lzzIu7)!LhMK+@E2q%Hpt9#fc3s#)Im79Sq}QLkN)7!m@gf!DDvp zwG-d{!k52zWdB!x^+L4Q`xaD!R9fS#ko@JJedN1OoqFjvUar3b&dc0_iLbx>vG0E6 zm9IYkihs9Z0+ldw8-TsPGcX-T_0XcsF}qHD?+Y(H_TQfQli&ERzA?v_jHt!Vr5_0h zd5s`pc@50E@=9r3z0~?4J=^HU!xqy@YaMsXdLA)CxZAqq3cjLtDC%Dz? zYbVkt+|s>#jtqW+BDPqgm$UWq>L>V+hpX-7wR(Bu-h8#bT%a$n)l1YCzP#40-nBPh zs_z!)J9~9wc(s&HA>;R!Ur}JMnq6=tGsbAs-SybSRQdJp$AofoX@~8D(m$W{cqjPC zg1};D-I!0MZsSfiW$(alfE3p&9?LO=ac1v>`RHW6sdy%qMIy)DVpt>N;TM#2PG+i{ z%ZOks=w`lF-*UU3+*}Yo=v4Zw;*dpwdqP_a)$+jR3NX_wlow1~rJRK#Qj>p#3TgJb zo5h*qQX&y&v61Drwdu#o0nPOSD9vxiAh0i1g*Z1B@FwENdqATgd^r4Cca7NCH5$BG z^iofkxiyo^fiSKV9j36%NWBi%pFC1D9?92ek-}VR8Z`=tzH>Jo$=AzKwv6trVeigP z)6B!8H3BfW*D@;nPe3|^^NQHZL&gS0IxM-xJBiEWE`U?leJuJJQ2paWP54Fexo5Dh z{N=7u)=+qaBMd`}F}dp*+<5v_2_1R}gwgKH=!=%r-}!DV{kShuc-uktsCUsTz$)I zKaBuFbddHLRgFIokGPx#lPX5L{B41_yx|~c60jZA5b{OhgKf9Sg73+ygxA+N5#in` z6Ob^xG2c|^)*|f(^pYf;`L#%AyuO>y-nl-xp9|K}eOzwYkle!sTk38u3)d&x9CZ__ zHFQn2T?K8zrQebWwwbWLxKIxyV{WW1_Sh0^cU2G!Ax8{D^a!Yn$%2uEF4U{C7$FE+<}BrNaU9DhL9wVUJrq4Ml1 z04TCybBwt=$;{^#k8X}%(Kln8q7PBFV4aBdjP@h)q*AI8N^OdMN;NsS1ao;)WGByU zL@V7CebB$q)5mo?zleWgb3CP6JmbezIc~hey50;|;3}fNr~xC@r}bPh5nL5F{RH3X zdODOlW6v>)^;_zk^trS4a5Kh{yUtxFDnoryxHlctxkXe7j1IQ0@Ya^R5pXK>bjxj` z0N>JYqw1$X~_0*7P#U%ZC z(NCh1vQAki5nW@YeXfQ54f(?srdx=*5K!1JK`cY{4cB6;P*5K3uw(PO z>VNu+`kdn6(f66we4pPnJXbAsi}oO0g#*%mmC*#GAM5KPeQt$xs`suX1AeiZ-b`J- zH~$Px%+r9^(8QOXxa@!{crgMn{bu3e`GAk(eawOSkS{Gv61YAy61s4?w2%;X92s^- zvrO?;y28Fs=?b?yso4evh{Hhx3Lk{X(qA%4xP~=90Q+jYITnZPw~$G8=Z&5%X+$wRzDE{ z`DX3AqSqW85{S`uKnCRN`APYANpqvhnO;35Q3Y)N1DWdUc!=#iS0LtLq9MyVks4td zl;6H<*xbIFnw}$7NSJO76Hi%w3431Ym7{z=_z_1i3kg(~Z z_(CYMC=`*mutut5>0!^eii?mex-XkDx{D_R=9;(&b<1CGOc=_h0t)Ndp>{r|>tQku zm+X&ooy;ye4{6h5-o64~_I`Xw;1QfJD;Un3=!X=Eb74xptSi6IYAShhI?Gi24`sC{ zs_KB>;z9!2GCD6tcl4awmKo4?AbxTfQX3O}j}W_2JyuS_BfhdFjg4@Qvg6)SXzbyT zc09Tv9ns!JDQbQRq66l+af5&;5Bf083>uIQn27<=H~84|B#LYJ2iv*%ot~}%3EpKu zLJ$QY!Lcs|5-h0vwCpAzGTn^99h7!L0vJbNSQ>nmYVd9Xp!_-%KelNp$^)ZD<#ezF zZE?ttYUY3r^?B?8ZhWLAxM4e>y)S^=Rsp8z0mM&@M@#r_6=y~Xr-K;nVNV5xkgKM} z!HN%fSc~t}<*y+?EQJUJHEi_4kEsB#i!I1p0X}Z+v+#8qaIU6xdhwR82wLZg(R@YF zJ6Up&5|lpfR@fS@dzKiFJ;Y5Q&Kr;I*@Mp4+=T(=yQSGp&--dUI2d|x0NI33xnNsQ zN-(95YiAA)`w8aykPAfK^Eo7INS=mt%@UXQh-gnAAkQaK2bi|5$tRq&{W5a3$w&ojq z?Q)b-k?D5Mv=i&mn#*V>OgyauWox{xLyfROJ{~Lj?#fr-x6n7PNNm?#IkOJI8G^!e zxpqpyQP+aXb_k{dMiC|a& zM8JSZ;T=Q}%jG~3R&b=!98jH41FQe2iRmTpD%b@ETGy>iInpefpkB3sqk^n5Y@IZB z+a>Ud=iv1+PH-4~#!qlKTaE32Z*rrt?WA zr^*;J>7A?PXTAzsoco9%xgQCMGiJ;}5P^)u)sq69+Y|}ZLhF7NtU%8F!4mwy6^}Qc z!53TEbqK*R#Z6na)zWtT1DB@HczA2@7urU+)Pi~UN?Xe-)U^_W+v@}0Fz+G{;9yb^ zpo3*BME>U!(`X3S83d&9naWIcBD><}^s-KR)V{#oB2V%Hg)og$ft3jrm~w0jz6=&8 z0!kgKC95RH?O8Q3^NW?o>oiJM9LJKa#gi-*cu$u6=Og?sU6&gL4lQ+=Z0neV!;3ld zk!bZu9}d$~IZ(ofG_7eQnTA=--V3kE>nbx}VHbT2Y z9=4(#=>`z*W5caRyv+9zb(&F(fVJE1s-~BM@7?L6M;W9k5DfY{=K>jN<;X1|gF$gD zG_=|WZ<%@ujd6G_2}So)tlDRKO3qM(meVL=F=CD6pjCtwb7frjw|3cCV=;J=6+R-ix* z%q|Mn+v5BBGN8XWHaIyZB|#hEy=%*cN)=bcvVeW1CObpyQ3K>5{Ag-IC9yDC=SiB>OZ)!j9K$(aHNr ziBxFgusv)zeq;M<_ekH0$Tl5im9%8~82Y%Bmz^TR&0xjvwq(5h2caDJjTH zEC*#Q>)VmFP-rZ*C~XyIdiGm1G}1(skX)uXS$|hR+KicvoTQVuGw&M(2xN-lao^|; z>H`wM!nXHn?_v{|TIVOb`4ZSO+VbSY@C_2$xC*PPrRaU}Pfv+?y>5zL)s0MzxKIl# z14DX1l4kpkHCEq9=O$pv^F%S2FFl1YW0CrzuHF%gc!{kFs$qu`Ropw;`FHg*!@Fr> z7$z-}rNYs<$TdV%WD+W#|IX)pcN@xRJeMc?G+iX-b4n|r@i-qTy+zX!3Oar@=wg{B zh>f!^>x-y!IF1{;fiEFKw8G7+w&5>lEw@ESxilNy{8W^KW{OxeN`u8)GF@NyJNxVu6x7p5u9D9tlGY=@~VwAoS zbQVi8NSKF6Bq&Jm6w+|S#o2h$F9yfOx$0K=HVFG7QWRrD z^?=l=IN4g9w?rMPmxn~2^1#HJi+mIsMK1bW9@aQN!fQ*o!!V2UaiBgIb>0+}-M6ql z4t^K6?sS$_&vAhQ4JEi)pCb)I(PLPX~BhxIJ|*yH-m zlaS=N(VY!nYCvCMSF|#hgt|a^Q%uEJ9owwsxHW21>P+|>w8PS0v77x)A`ekA(1Pa7 zot|zWeQU#P@$|3v&~}50?0c;jlGak&q)1&FX(bgO?*1=LMMj7Eq?P`fR!W(~YAKh) zQ`@-7c4foas4K{~3CD<%jT;);v5rouLcuO=)ba&so+N>bLv+~e)>EH+z<#N-9A=;% z2TPgn$0V?{#sm)mUi?|r)9$!$*E&^iw@MSl#4@HQ$iwsmqbgksb4jiFUXSTo4oOd~ zPM%ibB5;Q#$rx$SYxx`~_nhT(c)6agfs~7VB|du?6@z9Qf8bYsJk81U8DYmG8VoUN z=d&(q{5GArSuSeShwXA8kp~SZ@L{o4n$pCWkL3?cFgp&2vU6PImTIYwA%iy9p)x&? zOhAYjqFK;GbR%y`CPzadS0*hFNs=h#Q%K)o`y5QnA*skw>Ov$6^3g#mba5`#G2wR< zi5QM*WcD?>HC9zY2+##WtB6)c2fj8W+`{p$AINMyXI? zZJhA_%T{T1(FGeGt92Y<;YY;>zE0Cs80V5?NbyFIr$0M`&{L%>ib%MF0&IfMqa4Om zE%#|sE$V8XjA`mbonMjg3d{vDpZLd~+!Q?<=*WDnYBVmrc0xT=VQCLcdH0#D5@T>Y zB?qI-+RuD|R#a@R-z>}Be59K}P~tU^2Sf9EfeGuRVtx{61bFDr{IdJvtde(t7%Gqy zK#wxvwIWK&$C6g|!X%LG43u&4 z?7f1Q0WFV*qx4x7gYEIkShi-KgjtS=;?5=HKjg0SY8&1kIJbxV|U`Wv1fgM%DR!2m8M z2iV#{%?7l}$__83Fp-~8ELoNow zBJ0TKu=e0C$BI_;;iTb{anLVR0Ah8~3^xj;31rF9IwTthmRZy0hFS$r%FHdH>5{uf z)8p1)OW!YP=$MR#(7ti!vybml62n#0Ls1cjl|a=d{A?Z_^5JHNC^GZtYhQ(;)6^PF z8wFB#e7^&u1d|Tyw6Rs%tdh-y`lKu@k+AQrm!7Cf)dr$iBWecb6I<8&ivFE>!}p$E zY65_bEnvF!Xs9`l6_iFhOpIN5yce^BTe6W@#bgDwaI}^h0NRRD3tixwhJxCfe8Pvk zBSKCgFQAWa2#M+ZR=d$h0+fnp)f6HP+;US&5F%ytpHM;K8nb;oSEetTB`uU2_%#V@ zAAwUr(&Tr$i|kTG{D_wTs5$&l)?gUBOSX^tfKlcle0_^gL$xu7On8m-)YGjPeB>xq zZY1l9@j)0+8ocehm;0SC7=v0V~pP zvq8#?lZ3@%6cg8Of+mS}FV7R%MW)B{6M!Sjshvdjo+I)b zpwq`4As04Ql8z4P-^n2_W*`X@{gN8Y4-hGI0~3ZKsPymaMle3ckY4-y5S|$A5p$;{ z5CQ=J9oPjHLL!>$GmkxkMgi()hHYR-%tp8`8L;?5Z_NCFKg12b&^Ip5&8Sf4Gl4jn zToECCVTGKzP(du~&`;%rGz{>(g#3}6`QQ!;wn#1Nk# zK~}whWl#YFP}_3$V6zcp#6$$9ga9@{cUwx00CCywRu${?U!$2)02RodsrO36*VK7D zJ$EF^o@C(mlKLJnA{+!BJ+XV2O9(XuuND`XHHuh_KdN#%p`&z`(`lE~r)HiIgXz^5 z{~;a?7h5X&U#_WFT>%(6*s`u5RgD{X-7~RCiP9%lt1eajsr>3mi*{;NA+Th5`dEC=H>}v9Ti_JzLaX6*%}!<1}|Oueoe8?wm7*|cTo>y z#761&Rfs;Ant{Bthe7gFs;t;GI&Ulm_F-xCUWK(|o}}g$&*)+4Yx%JrK_8EqK4yH8 zu8RgyIt@G@l6_6r09hLshf##t?0V8wDioOME9v~0RWPnvmX`*-QV8;UZTY*{4`+E! zEDpwgpnER`fO1>Pa_o}Q?_KHNNe$|}z1wSa>_+Idv3ezJGo9Dcx3yt){36w|+*R1j zyqyQzh620(rhi*^xpY-Z0L}@y@MMo29UE38Wy}kZU~RHpZI>cSTIUw z#iEZB2N<4`WJ_JD0JO7fDfF~zx}4Yce`k+CBXW?&p_@vVlU9{}3w&9vB2{UY3|5M> z+1@tI_`2KTj3l2a2r%7c%^~BgTlb0%_MIqiEXN7Y^}wa1b__TYnq{vF@LJxGvla1_#sT9&SP;*|z-9_%|Yco{j z$ud5T_l``#N`c&Lye_a`f;NerU5&J0s9byZ910x%*4OGOpO@Il&l6!I* zFc>Vf7K^OMs!_x+n!OTuc_Qx~ze+~iGx*N`)I!APQ4od*{- zEG#eM4r0lbhguec=}486rn7>2ZGz{ttzJtZX4vYnx8*ha(<8!eC#V@NMzKqOTl}_@ z2Tno9y+4v+@IETN2nVr*vypJn4{?**(uvZZdt_v4`b*Ikm7zx6y+nXy*Ka2BBO>Y><|mb zO!nV1OqPAuZwkB;Z66TF(&? z6IBTeI4WmLX$(e**|l@5B;w@L3GzgPFKdKt)Bmj?hwVGs0tNM|Fc91qXKC^B=je!1 z)@DQLuZqC(7P;7`rg5YcF4H>KU8T^XjZbdn8t-5 zl0Ak6I`#VzOA9=bFO+xu?!JulGrZV0WnZDfvu<3Lp8m3x)>1MytFMt~A-<;gYd!!$)!Tc zz5D`A_wbegSI=HJ71(Z7Mv}%u)flttKN@3_%IZRlEd7LcC7E+# z7SDo`SU#?dpU);qb9bwjL^}@E$byu5%Oz;4lB4oouI3 z7ZkJ-NAMB(N2K`5hiNx>UWIkjTcsZRx;jDCIJ$@O<Ih((@WWA zu@~rqG*v9ZU(^@H-&)7v(yGKi`oKYr1x}(zzhw13AI1X9t=Ib%t5>RIRWgj4aups} zQ_&GnzyN{u86mIpg`~T5hQ8YJ2rD=qI58r@ajQA3k2ntqK;#28cm=^2t%wHkqACDW z=L+{1pZn%#>3HmV%1RZ0C)LiuLsF_UOg&XEGO}EX|37u_0%q4$-FcqJeO29Cr6XA~ z0y%K*wbEUhNNvsc5#-ozqT?ohVA7bhzl^)v)8qbznemh~89y`{XFiWz1_25fB8by* zCxSSM6DwdPO2-KbaY#8)5+w)_X$PEOz&#i+(@sW!LljV8e*d-hKKI;u$g(l>K)CL` z=dt(NYp?fSd+l!ZhxR_FQ#AWZt#OlsoGqeLJtaU{3XpI-t~ccKwPAAk4XovGc@20+ z-o};oHmI0{0D;fSdiB&}PBr{kKRMf4&+1n1_-oyt9MF)h00{fYFP#Q%A2i%v5OA%* z8BrNA74P3C#9tfj`C$U4p|WsndI1)>kX1}~$#+ne7RNN}EU$|0`T0kaKmS-i`}?v9 z?pd>$1{MMYaRw)c2Gg$5R-hT}0@Gn-2xp;d4dQ~i5_UVqpUa^@-svo;>F|rARmz!i z)_+lne$BRI2Kmg#V3gi!1IpZ2V9tCzv&{2oOeZ$))xGa?tcTfeOg8yc;9bN~onCa* zuk=tKSfh}Eanf7*f1gcAz|66*nhY7`xb~GaQkz2zCj^S~>lcl$2n>p^9xx7UNE313 zImjJEO7qDtTbnCNiy&79L0bG>kQUzxP&A~)-5*Vu6VOX-Wjhl#2kZs?Avl z5HE*ggG6$CtV(jz-zAdkFCjVQO2Dx%0tNvt$?4j0<(Jryj2%(||2J)iZx4 zvic|0!H@76GeNh|6-IJS)9R_Td_(J)cOAwblGBm*FC0IaR?`YA(M>fy@eFcmm1GSQ zZzGB{=oq;w#c6_VV6g)qOMYyGd+{-9f|E9L=@#+3;dOBD@YK85S4LBq+YB=)e2glDIMihT{;5auX`jjM+ z^q`14EsjS$=s*9|^x(qEn4Jokm1?Uo%T_WWOK1&<1mD*fjTm-Y%W>-OgHy5!R+-!9 z2Z15=MTVdd<7t(yEIwjL`7s#tLXZbxjKjlD+OV3aQ>SEmMhjtLDL+uM+pe?xF6NeK zmUNMwW)3@|K%ezV>W#4Pg?bQepHwp@1kjSjN8@A_A2X0%Vb3sgo;zgnMB*i!_S^ra z)QLcx{AbO=&pmAaUa#Hjb-K{~`0e*~>@SnXkheoeps8l}6d%b`8W@_k4zvh)eh zg&-7oQb0qx8#Q$bhw%!6ruc+mcw~66Kdmw7%q+<9-~B)+t!iT+lsE8rb%Ns=|D&4F zEE4&wL^-zgI3cUCc+7ewgu`QhVLkCqJndO8Y}TK-(8ZT>_I`|MFAb8;k_)|kBJf6p zI=Rc1czC;oAHhd&s5LkoxAqWV^e^YX624SGgbk7{etDP(ld(nO2ERLjq;ub?(UFKG z>z3_V+Sg%dvUgLpSHE3$6s|NDP%la)Q5MV6cf~9MiBAH2E!_tOdxHo9#V0#D4Mkf6 z#4^R_4OcS3Mz+0xZ+6(;Sk_qCw&u#4f9yVkE3K88Rqp3rp~JMxjn zFZ#4WpeCDx0N2Ju5&FjB{)OyHmU-%A*;Q&d84PvmLs}X~iBasXf#Gn8#*GRBnPMkts2st!{aTapez|

4NyA=FzT`dCZ2Thm-fu}1fmfw)p1F(%fD)O-=D6a7fdT4 zU2h{F1G*htvx#WqEwgJaLe%QC!vDIh<+?(BPj0y}XwX=;7N8$NW5w&=h#(ukiPD4E zHtKiUw=4ahf04+&EAg;g_LbQT<|2XviB}>xjIR-vW2wbrtli|22IpiLRAnJ&>C5J%DvO#0@6{kg!OL| zbXzA#TfZhKJLiqgxy!742{b`wIWihD>y<`JZWUOXHUqdQWv##*B+H$^dk49SGDS=0 zyreqv`9D}-w=ttWs8J{V*Fm$Q;LjQoTsMBgO%YS4M*TcOecd8lr@*dg<)$F2CAf=E z>h=CV4U!ZfP#5vO)*)>iwE>gugw`#Oo98wbPut7pgc&9BG}n8@PdDZ8zcV@f!7Obd zte+ENdZ39n!dYVzU@qHDychQR!E0XHOUj)<#5vmX>L( zRlPFiKubXJGngal=6e0PA!4D%l7n{h63|FV)Z=?bczQ1Yv>G0=e?2D~90Jc=fT#bm zMEy?Wx0u>ir{9`$pUcppAk7QbZSmcP09et``6@>OH+0+I7~#KgL+4pe zi*D#P`w5~~GjyJjrE{uDYch2%-aTJ@S%4sFp0}0L7{OW8+V`(0F`kVHJFAF{??pTm zpA7Qgf=K-kYRGB)2V>op+rGz_gUf7{w*QIRHZ>~Be;R_N{!kkEh3e^c0Wb_O^0JIsuY}N?iSUb~c2(ZscIKRm?rK2DdALCh~r&UFYKTG%mn< zRRgwn8C?XvO34EVS=@qhF1G0Tq~@yX`c+)V2cbPz|6;kh?8casl2lJMZlc6^{4P&C zOFa8dGS-=BMgDn42UKTyLV9tgXDVM(^^$8aICrUwj4I7r@Emd z_oa=T?!#q%_)hIE;S_@e<=uL4#F{=_N}O3yHH{m+gL_1-^(~{eR%yAxK)Gc)gzJQ= zN*p-XvZz*g*)qq>z*3lD`IoV$8bcbdsu}U=uptZTqj-O4RzsxaWy4n(ZM!PZw!>So)7bDzFK^-6!P0~J98ICs8q z@Hi7V$ovT2`T*YlZ01DqH3zV;<$EXALom6;jQ!_BseH&p>%xK&^8>e@3(kddOX2*0 z9OrMIx;C&EC^%vAq<>#0S87!qae8u*)T##Gj8}f`Y*Q4r(i9ItBk`Z|O7bKn?4;~d z?k}d}b$*uc63G#oQDzSyQJ9Fzr9A*t0|GscB(kSc8wIP3g38Ks=u8qd#LD=^zE8as zwy!Wu9#xt@AH5;P)GW*uo+ppbpQoWYzrn6(YYhsi=DaQCX$1+W9tawy*jg3c$<|b% z>x_$W1bT!~W&DYkV#0BaRlUbPLvz1zpD>TPV4(yd@3Eci-db2BjpPL192_d%?99Oj z7~w$OAK`>r%XOfF$WB{c^E+xr@96R<+jUrnL#5T@iy#M<&~e>kKJF z2Xn;R)x^o2v|(4aT22Vpq9|aKO{r6`^-jla%YgFOc}MU=c-SunrQXC#XhwHDp-&bxP`YQ z0*3tOHyh%j^{5{nPQ$18Td`$-EZ=%zU&zIYCI~Ps*`bwWSqfMyxT=$0Xutn6sYynE zJGQZn48LXWF=9D4&$!*fNB&M-<4HIq!Y8lv|C=x(9~BVI)&Lk3&j*073u_Y&dB1;m zwrt>IBs3xfn#vd<*k}=gw*J@ApDzFPrrOiwrY(hEy*)>u0NGg7(Sb(PU=k6_QS)M5 z>HkOr1HPP=`U*EG-K=mUQ!|7ryNV+EmJetW)|xPTRT&8LBI_gKvx73PkT`oV%B*1B z(peu=90T=%!WfPzOp+{}`5Fmq_z61o|KSCdSHe0u5=-JAWC5Y8VXZg184pckJ#ts; zI2KtRBYqGp{Zr|H4`Y?>Tggq@3i&5g@XN50f3QX(&4@+Da^vx0%2y9$OHZio!O2idsi}vN(*N_m9 zl?K42`2K_o_QaS6vU7q(e^>^l5gV~O86m4%a`X_rEq|z*AB}NoQ=Rn z|4-ZLn#Lk{=KcWP<*~c(wU+6H&5fr2Ms2B`lp+wMgagmU5&8)gZysbz3F`9y*oITJ zuU4fBNAVSS9v9<~RFAu-7JYCul`_lANJf^(*@%d1^s(lnb%I5G$3;wPTZ|T@tJ~l8H$CjSc z*j5;piZjjXWsPpCoJB#<+bjrC_6hzmh?3g~U43sPj?E#Pme2vy3QA}q&}KRE3!~X9 zW?1?Q4KgumQ0MMKAg$_Y10sD7RHY5{1EeV%>MIBaY}n?e{${ZP%OjE)udFwl*o8i1=}hyzYJNMi#843#4_ zL`eY42(h@PViHPBr;r8xAQc#?zL;-8PhOD=TH;p8hH$Y^qWtlc7qjAXZ?WG5<~#C} z&>!L&{mWHLC$FANc2oM&ejnnzHL6wT#r>uT)C40X-UPzyt0{eIeEex#&4h$t3@9Q4 zdQ9OlenX{Vy5U-@ga^&2tV4%?91CG|i-b9IxN6C%_kzul~wz?3HtvYc&+O(_c~%4Sk? z+Cm1gFbc7}zU^kh{2ROUnKH!!tzBeoKrpu@d~0UByOs8|j%ywPpQWKD%xRoaN0AIP zVwlx-K74V7FW4diJz>oLd9gkxaC*fk--I{CGuwH+)DL#SLwi>AP5HR#mDNVlk03Bu z%$ZQEW(_hd=*8}@2vto{olOw8zbt!6F9-69V_yPQcN2a43)Gbgy+M;@cM}zNN7Ui4 zYdyagaG-5n+5!UjJYyTmvN4jy0b!0)Z6onJ&@Pl)1KQ#}yS0(F=3qh^jE{nUevccG zwaUwgi6xy;`KtL`=uD?QYnKc|^DeHU3#Mq|Sv#V4+-d)JQVf?1EjCge7;iZ@>6}(S z{m^>a;4g)@;3B@KdsQI~swHNcS0Aud8&l_?A7Afdv?SEy#8MK_^twvat@rOe6{sTl zk(P|^t4EWoiwE2S01w5-B_*Vp^n~cNdcXRtpAzvNh+Ag*|GuA-;(6z~2>wYT!);eH zmuo=!-y$s?9(5KFIh{KjSAXPiNSaHlyGygiG`3s+hw2r#G56_SN-d?gpjP$(NN+DD z$X?%EByfa+9&^Jpi^QOZ9DPCScUZ~^JUtD*FQ)oem6{?NxRJ!)S+201l@yc!XO-sE{h zj%+z!LrxjxdSPXnE;sH5`uX8`plt(22r%p&t=~0V%TU&q8zetB$eP{2JLemk%5~tY z6RKXV6Xd$Ubeso0^~(+A`s$?ZN>~t$AqLMMaNS_fs%(Hb72DJ6!2e*LF|l=s+m zYq{jn5+=|AO*soON%1R2wi{vtX4vgzJNw~saCp?c19HQ@6)EjHT-wjK{P=@I&McWO zZO%8$4_{I;2lK;?`A$uK{D+;aMKvVfyV#;Uqs7tnX8X)i7*Jcr>@w#9DCt`wOfT#~ zPDu7cRcSD^Wkd@eEWS0v9>yW#v&Yx#aprpj?0ZM&8;qm#=0`6rt>W~CvUjVS-(tf- z`BI`EyD{8BxT2ex8~9=mfP}k*km8qx3;}mCn|BVxI(exu*elm#NwV}X!z_;<1(?tT z*VO?yUvk&50R0LXxd>SQT|HXIByJ>V@klQoT3FnRFxT*~ByO!g< za;>3I-RNc0cf<3UC#cjNqL&QMH*-;N+%%5}5KoTQ@!{6WhOkQC3%`3yt(VbSgK7hs zD?oL=pyEWcs9iJ=e%DZ`aZT(7(5jbR3xho|*ZJZc&}5r+V4t2G7;!4%0O2Q0Yd(99 zWKFcV$mbgCxc~B+G=f;=fmh!axu*Z&BAty=dpq?non#R1^xD;AIZN>FP*dFkqCjcH zt4&|nMqAN_j0q`a3g{tTW#3CckETQsc(I~kKBkQVh|#KYBxF$hqH!-9!TcK$19{=R z9rd=f4}!c#pXs=U(uI)&qHVj`^cb<);8jl7Z_2PAkez`>ERkeAxvtP&fN$2}qpF0q z_-lc43J-|KH)`2eZi_)asLVf_!StBI9^j|5@bsop0r~RZH5gWF@x!Op@H%OsH6D9+N$xYQ^oGyz1wjiXKMm<1{fZq-Jm@a<7u(Vc^cCA`gvxPniR|lG}X-y5CieL%I zt))Xg3ISeDfo*?{AIyudj_`yqJ#DzY8VTLP3cZnzIpz&#bJtYINf z9ZZMc!sw*vP}t;((d@Vb4Vbaiqt<}=U3WmylE|7E;5vK45inid1TD=n=b8|GSJPMW zyTI0Dx~9M#ld~2QX1Wrau7MP}*FDjc;R>!L?+S!!3fIaj)>pRSUjHQK< zyrCxC4_ucLv9ZXS-XForCdSIOE`Zar*acg3OP~UGm_AJC{Ag;p*0f4mu9Hwd_fFS| zQq}CaA4@k9-RGzvy_s&K%_MiNG-G*-G4d(y0~r?ADvl2gW# zGX6Wkf+|=#Ay^}pHUu@fXVlm9tU4`MjHWi78$$a5n{bC%#F45Ig=tOcUqWm|JguHq zo6i@#Ezx-_AX&zix=o#yLe)864Z|0AFdukcYxRfNhH@bxpCOTK(lo zR`99yX^-RdgE?F74lVI3q&b6N1$mW^WQm)^ed>_Zd-I9?-FX~_XRoKMzJ#?MLdUkk zAseYAMDlt}!M3L15*hUhw0hcVw$lT=q76bmAA0ubPvp1lD%-?;8v$NPXtw2@y(v(B z$1NhJHeHP}mx zz7yZ`7uX0d3bHrAnJS;_#cuUv{i5B2u5?e2bVCdNWLmA;TitA(fHROmcfuX&jl4y; zrN70Kol*?}CK7x2`6UoOAoH*In#LJZgz!v`=r^yf71O2sqXy&O*)zYM>KHVZHW;ol z4w-LRVn}Rq{N9yO&|t88*sbmw_S}IgyNs@bV*{hf#K9<3003oz>#dbQ<$QydwsL-q zq=9@|fKKseEW>9JYMHI+^ytAhgR+=Pqn1w;m-=bcoDw!Sy1Civ|DskP09Yo2r$}1y z0cHIFxu#T*cdQb_5Mk{F0(n@I%zjz}oSHQTDiE{EqLa78-G~7}%yP^%`g1i!8`!W{ z1Ik+G%auB@49Knnxi!F;_diROKSXCx2lsP4R%^4QVUq2P|BT{CZD7*cyzM@-aJ&}j z1(RLWLpYJ*LE#uw+nh{mPWlXuBYJXLEc-MwjTH`LIyf3W8l`clbPlRK-%5+DmiN??2cWod@DrI5&*0b7qbZm6TGHw;1L=_U!WPGE z%>&LzWIWI;^BC2$M+4rFB*5?7!uHP{`R$l+xoM{T#4$~%ghzO2OMZJ@XjP^gZ_2A~ zskGfzI^lwX~-c2CFP%*Y^jk zV}n|1KtXUUEt9thjxy_=rmy$jucHTgzt#uLU&7G1jrw+LG-9TRYX-K~=Tl^S;z0VT0<&nfIzQ zgr{06{&z0Gv78!!JD83@Spe?7bi|$S0JwWos14u_q>5t$_8lzm^ZRy|`^5(&8Uwrr zE%Kn~BQbA%t?7bE-?<|f1p6Rh3s{)^7!Bn8K*7=%qbYeT#v=XVw}my_L9lv2?m1+} zZB#hTLQ+%32OX_qFIHRx%|P=3#poUAotN&KSM7x&5U3^I3hyVbg$YWaxh>gxnb-{}Ct1ue*Eh)CgkwYa z(PMSe$SzhxSkSL(dv#=j7>|BbSHHhHrO%o~BjKJhQVm;zedhSP**lNo#<;aKBh499 zWQm;sw?HyKZw%b-ksdQw=j2>rKq$ zNM%RKoDjYTimZ-8I8D80cxHM=lTM`w#dgTp8P7jVE{&0w7(EvEu%nZxI$FEZs-~v))$c>0chJbgSevRpfOy=@>xwp ztV?dm%h9Z=*V=cE@m^nI37Y>`&el4t8`jXxMavkFyfj}{hZm*AF!rRyT(&6+l1V-~ zhWr0YiK-_4w6M+bq0(NA%gkmRC9J&@fon7htV|~PQ+}mv+ei?0?nv1pf9$43o$$Jh zKF(g^9hT;zE@s6swSn9*gEK{Lz^YkYsvDNDwv9CsUW(3Fnf8A=CAog=8wdAGJLBG! zVVLKtx2Cf%07vIVf&(oOtCR%h80yZPj2vDme$K`rhr;5%vaI=`lb*~>%zPM=ym_1YXcA5xpX5yF=>Rs5e2)aJQ&=8kfLG zjL>hb(jqm_@QkQ=7D}6-_Hs~Dv(xqVAfz#m6&6#gyw_LLNRDpVCvs!#Q}zP&V(!f& zLycYPixh2XQm<)IXD?`1=AZpA+MT3aXIax4s(uF$C{2zcE_;qR z<_U_#DWJE6;qBU>I@A;b)srSc=c@fpS^2o=lipz`cY5UDD#NknCCWX+OS%$y2`|g? zi;YZKUIOizZ7kJ?(z!Ht4!hi=ods5`QY6Z}W%k+j7;F3)7}2MSBww(}(PM<4wBxx% z&4blp>dc_5w$w=!W)IYE)2M8pAwU~w7Kl6xM;};`qq+L<-SBy$grLp~F9vkDqoy78NUaVL2rcDeez3Tp#k5@SVuonprAurWZw8eq z$7Nb0n#NuM3+}&2QQ*Xl`qwZk{Yg)ayi#c9lH}#`U2JzE=h`AmXl+`^SEYF`q=08C zL7PkDj0KRYis>j+hZlkDoH+hl+D;#h!EMAgMo-gin0B{ent>$-eQi@=ALuwU>u{?} zJkBXo?h}9K=8H#Mz6q?$xQ%SPR(82k%K82}Wvp^6nkb*Yue69u@q1AwUXNwMQ`fm4 zJuQczk1=M|m}ul2y2aoN-Ci&EBy@Y75^PrQ_F5bf0Y{PYGT)Z`n-LqRu9&gHQ#SP`7T;*_1HFnX`TeSgdq?B~AwY zD*L$~D57JdFrt90{omNw)=4H^jBWMTAPk10Q~DU#YxrO$c7g#X45qtSj7Yc+igH?@ z_zWyErFkY|rP5!dX`N#$%$Cv0sL$+~mxkYi$ijLq9`&v+N|`=A6zEFx+?Toza}1l} zob-!7(Bq8Wj2BIcxlj$R-)+R&T|&GutnNLI?U^QlNQ5VgYCh+QLUs$;)! z1K7NY#()$UT-hczLagoFTyANw5I&x!(|7p*Rq<3dZC=~7Cc`KuAcA4CQ2K6pA{3Tb zo|fbbLkI~Td1?_!!tBlGiK#LZc2COPh*7GY7@^3YTE{jS(3VWOjMupKal6J$2#YYm zC5hJNFh*EU@Wf4{IoLhTz2Kf1b=`9!jkmC4l!odm&3U^8t^ zBlA708kwEdZOpAN)Wl@8=U`zzJK-l`QwKN6X8` z)$xy%1QUyr@Sly6_^1)?L?&A1*39eTfANz!JnFENgCug=h6hCRCf`plUK^ob)^CNM7gm za{PFbL@*4obD)E2nw1nefO3NNc7RhS0u|cx^f44HCaB;udUS0gCqSOm>aZkyw2Z*QR zDb1_+xU0L~Uq6djR7uYl2G_ILKIb49|Jw;-OGwFpTl7i9WYw@nuey&jt;0fd9oVp|6DsFD@&@QSOf-Vm4KjJ#WN z`=Crr1S+!Z4uz}Wg^PD1iny*%-)EDBk}vTpM;V(MwX@nXB$G4DM2i$n9XF{8s@A4` zsT)0JOJZu(uIGGsyZ?H1NQo$fT|f%Md8_*LMy(SCv~fypiAIehS|tNKe9*sQ^|sSJ z#0DNWCe;`O=k@NSl^uAMA^>IaO}9^;>m?Rnn=c7r4(oIG zw-nNd!RP;G#LsiT`NeGPa%)8~bEYXC6OFGJas4`rpcZlcI&p%ySYQEHtbENhu2P&s zv=D1?h+&=eM65&pI2bWZ>cA)bpq7zTBq(r4tW}XIHHK^_pbgDn)suP)aWtZ;a&(Lu zbzO0kZgJ8~OpB}QI^O%FzlKVy7+;b@o}K?MB>t$^;g(#eBt$_Db``)d|_oaBRMwMv2tggQ)d)Q)2#CjUc{ojvw|_GgG4*fE!lF>|v_B_%p*l z_TQyE339NYYcDI}v$ewFz}L+mSl+BLqAw17WZX2gyIRx#InSoX(ej1_s+}2GI>o1r zoMV3>S`H<CVcI zTpVFG2FD5Y88vgAYfjDP_ZUcQ3NwOs(3%8&y;-J_qj=6}8h9d2<25lni%=3nI=kD1 zy=TLa-*lSqF-~rbZA?L^{Euup%yH7<1rvQ09|*74Ot1@}1l`Wv{*FwnGAY`=gNXQ9 znCgKQg&orxK;rohI^~YnXQd|hCy3VQUS`afU&&d&`CVNc|oz`nfRE`PPFtRP)ka(8L;pdpZPBf^-zPt0W@v@AJkyTLVn~LTT zGHJ6S!$_?DgA!&5oblTd8I_31zDV*LS^Hv*K|7l|I>X44h1f2}a+19rZc2jiewDn| zc6DkmBm-e7LgjinsY7qagRQj+#p}ht_qDZ(EV&|_Z&~@l9fZI$4P40s7RTUq#!rhm zDQB$5@hz(!`P||Hh$%j=WI{t3Cz9Ki@Oteu3YyxK!EAcuFUSn3J}KvG&3yIHXKAE* zM1PCN)lkyE%1Yc^=+@~NnLLkLWcsRB+=Q)oUhZ=w`yBS7E!)zdCnI9XmA;3%%8{>3$;AI(koDSdrm$W_}ZR+BU2N*4# z&>2~@+IjwtH`80TQ}obdYd*7A7s-S|;uKoT-bO#v%U%ZemsW-wsFdG(F7OC<&;MNU z|7jIGetBhhsy1~VghwxAYgA=;jK3oup5uFr>P0FCK=T`F|Bd z&wm_0yvj`f%_`%N)x848xcoS$nIwqt0%meGI8UttXDd)-L(Yvk`yAmcUI1O}R}l+% ze;F%B!a(rYT7cD=%;20@X1$a>)^cx_z+vR2T4X8mjm)r9pz)VcY?EANE8kEHqs{jeL3fS z*O~ile5@gQ`RF6J2{6|+R5BSvUbUMQ}POn_Y|b! zHb^290Jp@f2VkZy)dj@y{M*J+Th@sb9qY#)`qmRYlKDv{u(~i@e~a`Umr{(J$p^Vg zQVp|X{jR4eHfHxqEGoUN0#jkBC2l>kUWnYNO zg_Za(;z{OqH`hTcyHDIYHkxvaG{awlr$^Yh1oT8Z_!(T34c`^x_QqA~Aqk_PjN)KU zRkFyTv^3HL9a0j`gpg8|YMM}AbY07!U+JZNtMTUYC$F%@7H#vtI`@A_Qkk%!B)~=K zELmv8_K+Hx+s_3l8sbTWR&F)o+Hg2Ff0f@7GSL#-2}NQloA-DV!8Dx4Qz7MNYm!vI zf7feOcgpu-p8t{XqYdUZoSWlt*jKcR;V8!dS;XqX-s;++cP4lOvpHs-Xb5bMxW)4tu!XG0@XiQ6FTqk{-3i$wuC#G zYGU~>vtoqSn~*OhG(BI>m+E$DqDQI(!IMU_?mfrbg2u7BXdMTeFR@Hy6-| zM2E+7eX6?Al6WnFFE@q45)%Wa$q-R@Ml4dYl*Ul*e!Qaw3#gv`0ge%HOC$~V&C}^=c9|(6-ztfhf z3H|91>6R}$|V~2d$Lj-<78~2;LHxUU)0Y3tntTFMkCb^ zP*SiYVp=_6$B>iR535zbpkWt&?0PbonSAn#s~ymu4A>RdZ1cY6iRr&G(Wc0ap;kX< zk9dLACBsbyWL&B_Il=<*m+aX=^;~DOKWIJqiRp>b7_FyYKU+^wpL&X?z<lL|fXq+>KbDfOng z`}@$?ii3jVo1lKno~6)@n)^uuF*L_>wH7RszT7A0)dayp)9n70)+;ETCF!Q`WhkRn zoZJVRwb_4L0@oBDeM|Vs_fH7{s_*F>ReVS@()j%+_WS8{f}6T-LWFlTfaUW=K9kaV zX5e{}um^mY(bAR8iTi<#K~|#xK~r?MB{SH-d<;g^dh!9->4C@`9}s3FCepTKx&xx1V?f(}BpyAmw zCc!Fg?7ECz=uz%|5QFvCKzZa^WexhK&AHPLSS4kKCT92w2$i{P+9sOHnGMC7}jbQ z=2eucT?ZW9DAPw4ye;JwZVl>eAMBzY^^5-Kxlil$IkXcTr#lbppCIriR5C7}+~ zCQ1+uOI|m>oe((f>un-I6hAt4z--tZK7+rk-7|j4cZ1+44}7Z#-=^goP9yVhcPtvr zSaBw_n*_&4`>wDbl@5zXGT;LrQ{hV6@EJ0cv$4PNr~<7^|5^Qw*{ND7Kr`@Abmj%G z1r%c-3}DADd#KG3?>*W}X^r3q?QP)(9-mkwtqx*yD&17OiZYlK|6OIg z;o4xDfwYU#_vG@HXlF~dvEH6#dy<&~+kP@}j)GHdUM(tHsMv43om&oyHYgK!GBl~D z8zl}L^6N)(0(9{P_OY8RoGFgWH&z7!TvPAJTcrSe@$)vcy4D090L%T70Ques;IXuo6MxFaF(_=RHgv!iyN`z@Rk+TjS6S^v0pUIZ@SEv#m+?pbmtFZ4$^Ud>VQ6=uG6L6>t8NE#mk#Yffq1 zPCMj|0r2U!eiT{vz8K+G%qPMKp32-Pr~g2Njop}{b%)J_EGJML_Pz`@b9BwJaKdI+ z3L6R4QUEo48mLo>uK=ng(?SW_;w0l12soeQq(G}lozi~wO($cST4EkT8xG|kXKk;G zmEKwJYHl!T9%wf}zXE1g#c5#K;(${LbOKifGES<8Kfb8&pAe^1XQ5Y28-j0q9e^Ma z+lVz}Zj7dk+X)P7PqJzuGZFBW-6e*Fy+%+PzrLITIRtAIKhd}1-7dSWZ|y&@ZnQ9M zJG&q=BP#7%K*Wp%foKhSM>sSEf#8hdWI`zXbvx(K256CSDl89LGWN0&CI{hcex)tA zfN&c7Xl>m-mzef{bYpv`MxDhG-o{n%IYhXNn1{es-L5Q0i}Mh5fV_?zY@gg3N@A(N zD;z^tIy42I>R&DiyHE~$_N5rtfQdcE&$6>EIrNu*u#iwR$j+|Qu1AXlmK2W7E=QU2 z-xOZ8zWOaxli!+R!`zCOyUPIp`c4c+&2-FB_UM#SbM+4?c|S1Bath=4f7Db$9o~3L zbw8&Mwe90ZaT6#T4nd@ zO%oE7sR#nJ69niL6UIPvJ@H~aigiYEt_qQm55SdmpbhyDwQ*^OxTV#7AFx0`@egm} zx&BZxT}s!KhfX~G853oU-u<@z8^;F|Ovr3=HDwL`q-h3|c(VLlteVs{M1p(CDx?kb z9~Td8wj1Wo-%Vw`u>rS0lH;Vs1HxEc09Q~|DyEYHRW(@FEaQk9333~J)>w(* zL0Tvst00cG*gC9^cG|MK8lw1)X)%0)av{mtP0!tS|6hkLm^{nS0mk6t45zxE&c-F1Ao4kzi%>kNw z{#?BClz0b)3PP?5-9!S?>r8BQ{3AT5iVRi-nHdl}Jz+h@x8bHOS>cX4?x=(a&<94d zFpyoEED39uVpUk)Ss3T~^2>=h!tj8)TMENNO&C6%jYazrxGnle(&CVD@#~t6FwwX@ z$>ljmfP*@m7laB*L`U!Pt-2TYM=$)GFvIVM_-$8%=tv|zsj78vz9V@cN9dn-Nm7-nPmxf%K(;J8K`7KGZgU9J>M^5acc(Y(Y=obG$Uz8M59XGw(s!mAK@+-)7B$8r> zmoF^x#gqXei^orR?3bJmdwQM=;SEKzo=n9m4zdD*bBR?CPAbmU07M4^gcw|ceRZJK z%#nP4Ge}r?&KylCuU|IpU|R=-6eden$kH6mpK3L8Tu_5SJJDN*TAz^j|E359XDPAz z*R+ITGRquEi+@l@nn;<$c(UebUzAP<=_ikN`ton5Z@#?sBjVZ1i~SGE9`U!!cJZy1 z-j}0pXSc(IjrUPdl8~z8+A@%kN>f)LA-l*Be0rc_>#?W?u+bS63;JxW$Myj#guZHX zKuZ^;nK`I0Y!H+L34>tF4n{zb4V`%|kqkbrfD=^=ZjR&){3ev-#-?gH%ch^iNd2aG zd5nWM1go<7u32}@3krEow2ov=s#kdQKn}NCiH%p(d$gzG!OWr}KO^@`DZE_T;*b`3 ziff7uJ}-9YL@*tp=WXt*@u-ru<4YinS_ zm;Tdz3%TLb-*KUF8bYwKB2wK)kQ6E5|Dc{!e09GTf~miVK&g4b;L5fo*E&CB7o|Vq z`~X8CMQ9YQAi!F54d0hlCUh=sP7v09X4|K=)8K zYR&f7uFI2FI=f~zZDm3%hTTCk8Sl@k6DJ=wR0}ol&x&8t_W|cGwW{|J`J>EOMP(Gb z((LbYgbH28~H&l1=%9I@I z!&Ey}z*%4K9L+vhnvb|?v}U~MPOo|sRdj=RfcY3vpqKsaHbGkSV>dj+xY0=(t#r4?rLl)Gv@&*tCAd)x5!ERNm{|E{cT4a}}TyVIIzDuxe7aa^y2-(hD zNNdyn8cZ9py=Q78D0FxEKXLcQ+3BQCvw}>mK~#cCjd}*{s!g?I$=E%E4mL_G625z3 zFT(h|iYMv|cgDNhU1~9~=M9H3{R?>!E@sETCCPR<%99YW)V>`QF1gCO`*wwjOx$ia z#ueFY*UW(xsuzlT9{r}c@v(pxzI z_a$<_m0-bHL2{#p)Nvo$AxKVFB5gChEz^Zg2Qpat)wI|&2NmqDaL;coKi0+{DRowp zRH|V;m#5-oVhG?81_Z*Zw9-?~H26+eUk7KZ&=%)GWE^g1%rfeStqFEITOi1QconGh z;nav{@_eE+yIMk(2!Id>TkWdad%c>!l41c;+(zZ&(0<#Vw7FVGaO2P1g4VLM5?)x2 zQNjz0GvkC8@7-nyBzpvy-c@E**%pG;+gAQ9V5rKnpiv*yg8B3zGXa-1`oWTvsRtO` zwZsM1P2_FyQx<@w;V0|0?E4fAP%wJABw)T$=4**8pdEpjvy?|n99HGR_vO7G;6lm; zYzsXRQ!G&P<#G z+!D(h4}v?kadj?xi1@k~f#(mUXe1V)c`{c{GG)lLJUj(^@Q-B`(cS6Q?h>65g)k7dAmkf0Ld#fVC8dtlXm68(Im%ApWBoYlL!<+T zF~odKpa2_>lpgl+Hg!Ri|6b4ooJ-?1Q`tCP^R&flXy|whz|^D~LpEBm|b^^83Y(9DABH#M6JMh}cv5&gYXh?izT< z&Lf1bSXf;x4m4#_>fsOv`i*Ar>S!IjYNA9KU#x>dzjkr1O6+xkt5dkHxtlad*~b{E zb{4F}fVw6H-ux#9d3!e7PSSjuB`ZDPt-#=?&CX*z6m3r-R1D-R`{E?zH?4)?5_;VLpIE@4jv66QOg>nC`!0@;F3+F94ZK3Csk|HA0 zdH^f*7zWu+KZ>d~hY;#kA3k&XD>^-E*@;z&Kh~`t{MfSS&I7$A-05JX96KID&?!UD*r{L9 zZ|zni1wXshKfZD%ugEEsX0~5#satsknhRrrO8^C#1#Hnd;p2gjf`$a4RW(X=R9kD$Ec=@2A?Di*U} zlUIY~859J(&KM0xt2Yv0-^tEo_6h(&&biJgqQ6%|d}*~n4hpHRWBSl;dpvu$D(J zO30@nyGA#PJ-1e;|LQle9=WkCX;uF#pjR)sW(IyQTYIXXT3Bci%~TMC^#j?NysEuM z5Ac;hlO02Ozb@+pwO3(&`lLu1Ci)jiFRv&}RUJ4ah2JZ_X*T&xOoZ8%JSM1TWWyQ> zu@?aUMs+q=Y2AXcFkB-KUs#MO{e?Hr(*#nN%#H1ZQ1-#2*b0QNTzf z;rCbxO8$cPFHKJhbb5Fy9j)1sFX|UpAJHejkMJo*#5D>~lmtNq2VL<+%9jS#T_yg>E#l}Y`ri#aMp4O;tKZdA{~jz8VxxzYL<4t zz%Y_aZP=el<#_3Q+Cqs>!)$e)&i8zZfID5H%+-Vz z!6ei;ksij@+$yQnl@ooDi##IZe&5@VEoe6O>r%s@(1$-!p!lVj2ozQ`RYgz5$ujSgQ`(4ydF~E4($R# z-XKX$NA;^Cf_{8T>`nlrm3;G`8* z{xpd*rU1YblIr=E{Hh&z$vm(ahD` zjMi@PE|G;IV5mVQI0>d_0%Ty}$wu=CM770=vp%jH4T`tOijt7`7CDR)6ZQjA!(2f% z`}N`{o%D>4?7A;-fpP2uaVtXSRbT=j>JbooG~i*NC2$Lj2)7010t1;b&B7w2jHkV5 zfCr9V){n(D#WgfPfYD?Z*#8=gWYV_Avor;CO(OD z)#qO@$I-AFf9ehvN;~RkcprSZxnYd!Cd7{XekM_o0OwoG7K?RYKrkILSqQd!ny=cH z96;})bWkL%YMX*}DzI$Iq53qR60lF{H%qWkypx~7&d#w%7YKBEbW@$unF$LzvrZpV zAW(Zi&BvOhUkS?~4CGfTpju=*;i@1XJiKF&(H=Id?Vag0V*mop@VdYe^==g-9`^?$ z-6M};5#GlRWvi!wwBJBdE-Uk+xGaBq)$ztD(d^&C71L{%5jo)FVnY61MZ*JtED5|C zlI<4cy#kVT;!bhM8M_@FsLcSa9=-z1Iw}k zu_6;$mtC<5;T81K+H$6P`=ba#a&8P9S!-QMw$D)PE+@iM>X8YE8MP8H+Em!6NE5|| z)6jx>W&=Fi_9-d;e!~N!F)@F)bJJ!60xz?^uYoelM6yAKe`4l|L-WEch zA>9J^gRju4uuZPnzFN}3ku5MwDVt`Dq%9#BVkAfq3w(^ZBSQws_{l4h7FOfU*~${R z!QW<=Eak8_(S%qm)n*WceRax(xp7uj?Nw35zhi39d<*v_NJIA|_gSJp5M=e_0{O ze%xhLE!_R4eYbl;YY0Ii_NQ1tjE%xk#&&Ggq2p-v$Gf-`;cjr^%$$vPSvd)Jr77aO zCNeSXaRO}wuCold@EBU*xt^pO)?p`rp#1=ZIV4fCaaiy!8dQ*+9S-13 zA@lGcY-P||Xb%fsmKttiIlD{KNBG1@IHxOwSZxjCBngxyMNi0+JUYqO;4VCrpvr;5T~(14cV}d03AhwFWGmNxDFQ zU4R$H$Hq`V2Xg~;$-o=7s`+~IGV%37XeHvpkRa@i&|<3UlWP$G`-sktk2{m>DOfk3 zAaoAH9KaF#SKtU=5Y_g4Eq@xt%elE6=JNkF%&5smyKq6|p4_+xOQJ^L{Y?ZS2U&83 zM~ZvJwEUDm!iX4spP@`5(u~K|qzwq9gq85fNk6Q63b`U^T7t&q@hc6@k`^0ybsqq{ z*2X`Ay~zeYA}riZ5FTj1?yp{AlgF1M#~5*u2Jxdwo<5_XU|4FYPB(u6OQ~fMW}{v^vr86u^QDnoBjzcfiHxO7 z&VqRv~_8{R0u@i6H15SKnSYyu14x4YG<6w{w`jhyE;UHEIAKLU69^prBvz}5ch`<4< z(T*`}Gef{$8>7QAUj5qSQjuSal12o0pcedZ9COIZ8()9fgg?4hrWRn!*g{0#DyC+tmoF(R(aK)&H62Ox%k#DPNU;XT7&}F8Iw%70J z*;h(Fn;zOOMgDpGe17T98+G9F3BKybjXLQ0xCXw!p^wM(1xzgRY3WHyZW3Db=uM;k ztFnjn1oU=9pD26&3dv9W<)d<(jr76Tsg$%Ev|3sM_eIH9Sg~sB>lPMG-J#_)SDyQt zX*zRnvua;fBTY{}n_5P?&PEnh+vXFj?M$&yhukcOH;%f+`<sC}t{M%5ENXt5 ziq{fk16h({*u4I#UVM!#90+AD+gbf*ji#FNf^1w23GPY-wj_EAu(A@Ofzt+;PN!}F zWJWGWG9SED3xofStRDO$S#$8e8KUV(AkNU69Q?!C^1*xe4`+m~QuTT?8xgF0+_Dv! zjV;>1aE>#1F7xPvi8C1n^Pa6p!gIsisL&*PwV}ZSvcd^6WrPi^$ss{H%j>_H&K^PVsh)v&lCSIds(mJ;(JSLw zS99ezJ4QOU4mX6ah{N;4S4`BG@Kxv?%n!9$K-g>FI(%ukiWGerU)7kTXn25FBeDMK z9$Nq(ZsO}VF{dxccKW|4V@<;F5C~b@l(F~JxeE~TJqU@&^I+R#>;p0Q<>=3gf1 ze3J`hH4$P{`nnm{Z8l32f|NZ8+Akw$?Q2?n&Ayt)vnhH!M(C+t41UkwMnS(V84C^w zl0AqJ`y!8uwT~hXj@oMvXLu96y;5(IEs~{v$7lentAg9A@HNDOGr z^dV{dN3^6$P9y&#??KGLL>9j$35wsD_boamhU!&cw6%d=^>xNE&4VZg5*b`EzzD}1 z-ychvV0(gvDQl7mhO)`h42m?{NxuMVgEVcLRZkkR5UH}9)e&k%@R05x7WQB4P6Za& z_(|GRvV^d(eBd@p?gRuFTY2kv`LTT&7c|mrk*%E!I%270ONvq*Y$Jki(^8yPB09`#a5w2&2G=jFZl)j2>W1i*l5|B3H@#d^V*9lsOh9dx5B; zF$Yrp)(LB64us2OQO3P#>~mArOZ7Co?2}Tx^{>!uuBUw@nvw%q^^byuaj!3Z`qNmq zKy+W`RiZ)*hrdV*x-Ls#4m)7f+Ejrc=_B@F6+aH(qY* zXca&OkGJ~h?=FIpmxIO^ZJ?hS8zfG52LRN)@Ryb^L+~rR!enrneP0e@UtWF>9+huA6`C-YB}qk{i~58*AJNM+B6Cfw z+OW{-`7eH5e9q3*G30=v!0KpJy$^D3r5k#Rv5%_1f%#i}eoaDPu-CphZzqp+}vcN(US01oNm}{in|}yW|@zsBa=;#3foJP=>pw>~3p(S4$A} z-7|I<-zC1Quv~rjo~P7u<8I!(d(iH-$M0%O={nnIYH&T&yg#$nu03McYFN`sSxOT# zX!H&2Cd%9_MK_N**ZJ_o5w~wHNJ8^3f%6tpVrrTLHw1(FT&E{N>+plR*L=gDj z>2Mnq$aqb9(z0mpzDe5iVOP|t2Y3-}O#wCuwg{RJE*MFsir_wYATggY&O?a&!Uy=YgT>LzMeamRnuXV z&RsviCL12l z&?k(2Mvt&nZ%kKOuZ^Uk-8i(vlndQY;xrljyvLqsM~OXJ{JnYIMz-j7G_dpvVrw-s zV@S8IPF>+jFUi)BoguSGfA7tr`a;Q?4WeavvuvdlWjPk_SHHRpk5v0j|HK?Eu#VTQ z;nfdT8!%QrE8(pO2VJlp&R_CjEn=tdvODIo6Y+{$LuT{1twVFi<(Qg>WSaMptoXEz z9!(qo=I-PWEPo5gucWX5kz6;y|4#ujHo>i^zSiP-`#yu>U^@07w@Ngc5}y;P%H!sj zEMm9rOXg@xezW+&(oD45m~vC>t8-SDp^n0F?g)F0b*<)@RQBlOAPpY^x6kZ;oX2Qp z0MrFCJ+wB&uqB2?GUmr)uE~m#yeNroAZHxs2bQ>6ZkTD}<*PozD~?B>rU`O@42&GG z`lIz_o4)z=?Bv`KA!EO&EL1y2VChq`O>L)Otw`{p8pLjtB*0tcD{52Z0D|w2L`>eh z{<YqR@P){5_Xa-6&JD>H;yRh#8vjjHF(O9%o2T z4W`!OTk?el*3LxY#u0)bG$&i~{n+P199AO8y$HoAw@E1TsarzstSIZ$7Hx?U0u4_W zs4+~l*qC~COCJ-IiM7V*%o>Num+_F6p4FPI@8id6nk$kEzz>Qn8sN29K@^Ec)!|XZ z%OO1tUAoV}234*f!eL9w^Mq+dI$a!b7T6x(X8*>mugY$w0le!4!(`18=I~|}_Uh5J z`kZsUf0~O^ov(zt2wM{}?LZGA@2KTe1C_bucAahF=BKMiZCfRDyQfS8By!U62=*YR z95k(oSa^vJYRF7_#X)LCla(-oo%o=$vz|Da&2N?`mGn0zP(v!?Mwah*38Mc4+~5`= zV=YjCUe)1;1Nm0AiwnoKS}rzUPkSub z+M-6-8NLfbhWc9oCIT7M|bb&^`9nwQ{%1yol3G!pQh9hT6fWF*xMRQdS9AEJb& zMnm?x?zBcdt%Acg+G;1k$bVOJ9DmZu+Z3=+l9$l zS7uSNVg@N=NH~g=868dpWI7Q?oRJqF6=5=5DdgKZw@3?+QZ8_5OWN6yW?}AW ze@mL|AV31v)gS89`nyd1i7`Td>aAXq{B7#Yj_X>2n64wn=Cs3vv0^X1+>P4JC$;Pp zpMR6s(Puw53aIXs$I|gl09}nYHQxJejS>jDI^n2SbrcQZ`z#AfUCP@r;rzCO^aH+4 zSxBlRNY2R0o`!10=K0g|9mKM6s8cLQpFiu1-wo|!)>@(koS5wq-dN1nA~V;kqyKRc ze>~bl6AY!T`gi36 z(ew56tVymoGLUWe6hNu_ej@b!jQ6bo1;e~!Xdk!pD8KfgsQI|b`*{ma2jZLto)ZQV)Ss0xQ0Y_USiY?hn z^F`^0S{ea%UW?uU1lx`SB`gElb|I8gm!^ck%l@UQC{J4Mc9Y{QLkaB5!nXODa!v82 zyBUj2xLmQ?selHNJAJIt_TNZQ%RkF0(Ij2;^^vjlN+Sca8ZP5(4iFO+6|#o^rqn3&Lxzw*%yilv zl?szvs^m&a9T;sawurIw5Li49SW$LUBonL;r`4d0h|iRw0j+VaCrA^1P%<5gDCt3M ztHUMjk0_w4tU<x-2sFxFgv7pShbp&vb5zuq=9;HgQCDJnt zoru2un_Tbd`t2rPxbkhk zvL(5FH0z@NUlC@3) z$%I#TYJrC*r%f(G8N(EmioQDhr+rp!QG0{NXdx8PeNe09sm=;ea4aE&BPG2+J1zH}{VmLEMz$VftexpcK)#}J1->2g^YXUX2D9{!$ zfe#qj!5&{Y!Q<5vPw1t<4d2eP?u6gd+AO4k1pU)s?C6!-e(TYy@Ht=c=?uBXqy8D%`=HMHqIo1Jn_t5Z8>axJDNx( zl62CAqDdFX3)e>Twp>|x*E$`Fppfo!LEGC?jmDIZB_Q06HQ<16DJ?O*yZ0nzxd z9*tAf|5iFaTn=Il=&$l|Kmn?lC0iRH_8kb6TaM^*ArnuB3KUftvqCz*2R&xKvhZqrg0DB#By3qB>-qxERmal z5SP(3@}=Tm@ol{?VbU8VvG9Y2Cs9+x4CV}^InVXi>SoG6R;vHb!U~UlC0(OX(+vN$ z`LDx&Yr`kM@2LImBWr5EyG$vZK-4U+4$X=nL0D19LDZ%FUCq!4kyJfAYl_F7ypLT% zax8g+2uzqjQ#cP{)9&v|80eqG_@P;1c_w)k=E$qxq!;6cM8AHQU$#QTFPwAFBLd~z zlPcTa-I092{lDbUtW~eAW`u6_j3!L1^n*oK^Xh{@XcwUKY-hC99_cXV)aqa9=TWNpk@8Ue8B0={gd^6jmI0m`w`(x4+^x?e9e1z} z@!)!-P^)@&{GgNFLmkMj>n>`*(V@<0`l6&${Js-+M2-q%()+@d70#6o|*Se$6AZ;csss^u&AM)^$Di@><|3YvpsMhBPlg9%@sSD z`_%(WM|59vM53m%f^*r~9zYiEhXTzBR%mH~AJsh^i-k*B?^xAu4s~97X(LCtOk8wU z6)PAj$@7Du$Nyw4RY!?3I>3*P37&A^z-!JcvZC-@3N~Psq~i&PyQ%k~*s3 zGiZ8*-7%IMGVV^_ATfE<(9~`7v(xQP+O_{FyxFPVz)@g%r_;$4Mqt$$2b|@AYQZEG zV=n%{iG-cJGv3QSivHNt%pfULQ^wyLk-2MBG5Mx=&1l%>=MR0jHFx)eKa0$;{k$v5 zjFN_5HrS+wj?;FKDeCmdY;nw?^DdBRnmL{_!H;YY1%ip5)HBIm!SPBOjS>``A!5NW zdV-%_0mf1X)`V4YuKeHBy$!fsS6S~n=Ui)lti9IWd*-8QnvZ6#)gXyYXqD2IwqnMn zpHL1}d%Wo7KJq-*`^eqt;h{CBxAz$6f^Hz56ER|rM9evEB*#Wg&xu+eBgErIBLoZ> zuwc+A8wtngjfV&kBgXdr{_i{HTx;#UQ(8W*S3>ukYtAvp$2;Ed@s4-wvnTueo*b|z z2jdfQ?2SHpAm#%O8%tg-gm@Vbm%vW~1zKdcE+OO}Q;yZE-}iwS@JJzD3BXQ}nB@l_N!OO=K{6|p_Fu*$Q8Rca^$n0s)d!d%$Ag}ERBVc-?s z;=Qkq-snfkRlLJlx;{+<75{WR(Z;*bZ2}Q#^=;k z7M~Ru$|Nh&E4qD*k3K(Z53ZoV#YNM5cwvkHvRwu;+Q*>i!}6hU zMkT>=FkYu#(A}l@te$@ze91MwgH$W0!U0{0IGELtPA)?Axu+{V$aRL3gQ%|IKTo2^ zaxD)@fi2S_CPyFVI=QgL<7JC;Lz;S%S~zIJI$x0w2>~AFV3fF4RmX-p%TVm%uQ{AO zCcDx1+YL&O3DuCeV+9OU=AL-ORa`X|(|#nqdOQ|%*^IJyd0C`jxvwFH!iTA)*Fdj& zh17Wu;0Kp4a2cYBpMK}%K8^Zx;E?d8>n;l%ziju)65kQ4Si~b$;K(ApnO``IN6J|g z0ef$bi+D8|f;UI6mg2%x*X2f*gW>{5iyaq5yrRSG>dSRX694cb)H=VgQ-}LH6}CKE zWe2Hjkea(a1>Gs*U|(gis^>`=8ecq4s~&!c1aw;NAOjmGRLM=Y!q65Dx(M2P7WQTK zvVAEbjf5>VMK=^=*s_#7WV)LSGc20_b$VhXO%nbO%M+7|QuwgigFiKp!bHW@!oEda znqAnXeSKY$CMH?nN?GKtwmNFu6}OhL?*Zm3t5sSV3IzB9D~4xyW=ZL#`A14gfF=q( zE(2uiSc5{A2#3VhMY@`dHPda+z?M)O@=p;G1s5{qY2SC|Qqv&IUGSwS*c zacbJ_2bh3pdpLMLGu~OH!YCfFBHQFt2Ja)1)$> zydbe4`S{yw<>F&`b~V-lmlo@eTC8Qcvq^q@8-Tch8R2@9ch>2$vNXgoVi_JBdChf)NoBLTOg=4})#WO?oL3x?vCJm)%3`@P=!^8&Mg{aZ6A5Qo{!zmy z=50Iqcfz_i$Yeuo;MvDd%ZxCgmRQAm#4kIcjH@|X;!`aE&^3^eHf)V^we~WyC4v~H z4vBz=!Hla&ur#CAv}{nbRg=X?rsJQMz&*>|)Tcbu!*5069D_JX#>RoJ`IB#7O!kEu-T=O&Zgx$!T5J5dNy`vSQWt&I<6k!^t4?TBrbh>6bUQqy^w9siO zW7GM2^%6KTZ4O5~T^vyUVLhfJ1#OsQK~);U_)hY9`@kW#UXTOjz+q}zG2L0k_8!H2 zFtX#iGSXx5IwyfF&V!LInXFqDCnQ3pC~&*neVOeQwA&U&h=_atcvABcxzyuF@hjw&3~!(m>jRIZIo`!D2~Dzu@Q`1&*l@=Ginz(MGg%H0RPxie(1c8 z+VOGkrhzB_Yt1qHCSWjTK^L~IqHG{~C6}vSf7a)egm{g$_YpR;cEDT!2kq}6rp65X z$3!e{4=_FXNi}M;Y+e*em(>uM>FrVl;pLJMdJbsqcd>?X;sLE4L4N^aHK~Z3X${zJ zxGsHX#;Hh65*!s97;`#(D^Ki}ttA}-z!cZQ%*d1840{NE&m)K+6O1u@2fI@B^%(BW zhn)Ql;*X-R| zX|JzVmd%y0;1%0D8w?0}AeaDPtYDW70WP`G1}F}%eIQ(tM`_I+ozxxy={8{53 zk38%`gJHOZ-hqiAR{3rvbueKYsG+k4vg=$mEv66HJJ)RQTumh_)cFnhCyb<5s;bVF zg_T-{aPY?^j`6{j#TttH{QGQgFsueuot{C24Zvi*b_x-qMU(3*6ql5}3F%mXLeuoe zu!wEBO+y*2Vy!P$J5GroP}O?JfzF!#I(8{?dxEbbC4diWimPJ@U=x^#<}|X~M6pQK zXm>*_0V1epP;=QctI0JPCOHA}MA!?WVKwj}uU-@0tQY!}KPwFI0Nj?MmobaiX!dQ_ zQ`6qTzK9f~^A@5eUeH0wxHa9aQ;_WJC*wxcC+*X4(L^A1s+*k+GzMXzBa9Ih@p~n| zof+!1+2RYncHzYB4bo@WOgGIlqpUzyLur%ig_^RvE)tzr^6{%f%6@9Ty;v7c)71K6 z8C=WQ=+(tlk}gZoXXokx3MI*;D3n2qLaC2PdcvcbgJnjot0R;*6juu-8Z4Vr@`73z zd|qtu`IVlv0S&1VC9nq@7`oF2#B}JMIDz!JJ{;CHFM~Kv{(+trVXprv<2;WJLK_iM zVk3w0@5Y;0i2QW3%lTc>_uxS(rQ?W{e$Dje;93iB#l)D~?RJ)%}|1N&{AxKIm) zY6OgKIJ-V}1y&S%Zmk&`ST$iki+!Im&T<8jUd7tSnQg_@Y^FvEVhkT3>$tAJ*o4VU z5Q1nL8T%y0<6Ye0eAoCgHcLsmeGQ|T0_KF_?}3p%N6fRE+V+*mSYF+@D@4lG18ia< z%vaCR{~M;L#btIj3F3`s!YSwans01csISHCPEyojFq(9DLZeI%gy);-X`w%}C^Ju) z%pn5#xs3Dc^3s?+VME$Ap1-4{F1nQEyX;r&kpbHlwxV0D&6N7ens*L=s}5PF>%ECX z%_Z2Ynm%G(um=F)a zg0KFx95t+erbfZdv_c$-uO{X2f($aQ=eUY!&*)0y^SaXdXMJf$n`e_B^M@RY8& ztxj@PK*&*DS*3YWSFKoT$>tD0RQUNif1O?%7ErWd~jl8zY=V@eBNsmSP%^ zp3&#tP76NqJsI86Y+E=MRS>9$3bZVHEOBX93-k7jH{qhKlS&7sx`k_{^Mt{>!Yo;0 z3qX9yO$j5-;2Dl~JEUxi)L}ZV=rz3K(=dksQTFQPp0D$-xQ<{RtYN2k6n_q(TY#&k zQ($8mVdJ4!=3s@Hi;6J85KV?vrnpdSxl@<`dWqpwP`;qC@o*P5TlKKZ4?W!5|8RGF zI9om}Zp!w>XNThTK)fF5D|48o8rV`CJ*Z7CQC>5^5jJVB_4Q9z!0o9P=*ytUKF8hS z@n&rTkIW=j9EqHRyW5fxrV;#B$e>DjSc4?9X|w zVfVO{cD1<^;q$A#Ucey?X>={SGNXT`YP9kB$>Wlx^{ zDuAD8(fR*VSveZzP7r;>5ISb%*)91P6Q2Ao+uQL~gtw7rk!Y|m&y^S>4qMg<(`oo^ zi;YUdf95a+#gY7YE*sg-%vs-BXSjI`L`EfU+ycZOv4U$*{{{-R)?BV_11L0oG6TiA-gVr@yW>W?}#7o7!+dAGU>R|_qy0~54`-EzP-BB*19DDL%Cuogv zw^967&naZDStRPOgD5Yn1LrWkCDu-wJv{^2G6lo{3B$lT;V|urxv4p!ZUWRJz1!KB zBsNxZU7mK~-RK`^;!t`iZo7L>8?QXT7^mF(PP_gP7 zet^v|Or{OLV6yokc&cIvQ?%+vXDITzeGM(PW`WP*+0gc$U?Vl8!uuO0|3rt-g&;#q zFkD29wL}ES7&tM^&c_y!oy7U7O@2o$8}-^}OiY>+*ib%^T$*^2=jvQ4VxDoslj%hm zFjg2Mh;|MFox17^hos5$nBXDt0%|a#J80##AzQUb1C)y%L&|pMsoG*p&(iSkz&?RT zvA2COwWdYRHb5Ij$asx2YYVCvQz&UGhPC}Vk%d;jpGuNUUUE`a3btIGdKRXV3SPIwPnngJs{ ztJ$_bN%d1r=X~=`+ey@IOy{4FJDm2ebgfH1?rsAKrLX8z4ab!#X@-l&peLqQ!cASV z8xTg!)z-w*^UpIRE-oCerAOooq<2Vf!Jz!IbJSD<+@%pE8)C@`RHSiL!8ZVf{}H)PFM8 zub_VTl+J30iI;pjbsnzSs;MbnMJ^^=N({_A(hvwV6OMy^$`dSlbUxqg@%iJnlPAnk zh7V5&!$~&Sk0n%B?{J2&-p|!Lq(-5hq3s=!AFayTF_D89kftNu&9Q{m9XrKm1Pkj~ zr(TLfZM0wlY42=*dn$Hx02u5`v?+{005f0*{5xfzl`?)&PavLg$J7(!-u;u~GAc~e zSwD-F`KL5D>gLwu_&6%+gqt^#hF)aSi}VUuHcjFAc-u6vr^a1-rE+8BG-d|vM_Jg@ zS05Qi+f6|Ov)ChA5Bdb4<)QGDMuQTgJ+v)9B1NkZMmtLAVh6bGlAr~T74PJ340q~Q)cFuMel#44_5QE!q46b~pa>Up! zdX|N*)z>SRggH}5;*5kL{>>Pk$0rp!z-#5kmBQRLSyN4->wjC5nt&N*jb0?@_6&-o zSp)TfQf7)Uu-=Z9Mi>V3QSQS&zUOo_zMt*;-ZboeR>OWFJSVNdq&^*)J-A&=x$;!&zycVa(%w;5?>Q5EJ9(jf(DZy-=tfkIL zZe-cYYBiZAOwcb{)~(*}xA%V2aZtd*1?SbED_>LO+IUK6aY$S@E{PeE;C>ML0M;=S zH7$_^Vo3N4w&$lbe05bZTu5Na!jv-_Z62sb0XSwZ1KU}BidQLY%s*pLLynmUy)MyA z$lvrQ5t@Caf*HlClz!8JxiQL?1K9T|oRA9!po0W>W+Bhk)oK898bDXQkf7@u0Qa;g z0H1C$e_ZFAF}Dx*47toaWA(|N^nvueJQ88EO%_;mB&!1g-Qc+cV)RwA|E^2qq`3?r zcoT=c02tp2AQ~Dxzo^i3sU3Bk4`4RG5DOQ;JeyxNQS- z-0p(MWvaJBMg-s~>$}p9x=pQgXc-e2s9+zSAd-w5j%uL zzaN=Fes4pm>)@+YdCs_S$(M(MvP@kXc;@AHi)mClJUP*Za{C9KHG<%EXyBQDd$j)< zBa^_KC_LRpx4Z2z^?CafGsWZ+Q^nh7iYsP1WK^lgP-a}GxOi_%#HNZXrZ#c=sc}3Q zPRL&rfE<2ug9x%0?2t|Kh2-Ze_+8ZIxw^p*FaaMO&L%yVXbLGQEKy7AbO2o%)qY%B zFlPpJnq_=Wu85v4f7}$v6Qx5#>wlCy(r{LCIL_i>X7RKz#b)uz1{0IADzi+Ay+oR2 zb7&}+73!+%S4U_SHX?QPt$i)3_M-z<`;l7IbsVJZaMv1TRvGPwv1X;rrLv$5>QeuD z`W$w)sYnJ!22pyqO5di+p-=VvnrKO;EukfuUjZ!%!FyebmZYT+ zwsInQJba&5qY5e+@F9{s1#4l0x-h1c27@m#_7kOo(qy6GX%|6$!M*$Z*xPU@?7?=_ zOKeDk(Y}VO%}+KIzk~C*!Twe`N*2~4jdSr+d&D>PEp*@fgSdbU?uoLfJO8XcrmG+WJV}Ol|4IPMzlyh@BU-)yOGe`Ao0h4I;aHTY zl`{H*GJ6}aMBzqw3a_-y*TqpkjqlQ^FF?*V9EX@kxHNpX;me$R8l9CudZnNUKy*eY z)gZdmHThEdxVLRttj+S2kTfai3SE<=L!nS{`2*JVmNh88@}}A;sbm;rvh2{)8)5&9 zhz0jEZi5LW@mYr4n8e#K{h~DMOllh0jc+_^(i`A1+Ez{nF)7n*$Vk@Zw-%;^5aE(E zZJ`7D7TMyS)H5CH%Cp^4>wvgvMXt&bCsLx7x*lbc39D!w_&yz`F8^J9DS%1!lZF&$ zLpqy#*ZU`r6=()5uoXfCW@0nHC8NePdu3Y5VGH+jPD_c|rlM4s^h^$XM+{%0;zQeS zX(>JRL+fWk@7GN;-I?M?49_r3>iOT;n$UVs0h^4#6S8!)9keFdCWn}(S38Sr{a^>i_t zE@|vM+*3Z(s_ZU)=;8kUhkN-UO>MWB_L>?jx7=+8omH!3#MGL@*tq=6cpLU%{27K` zu9fYQknCS8Yx*h~u{3d&Y;TmDXqB)6(=zLXu$3#Hyf#ag*M@9ZnZy%(qyHqD_lvCeK(dl$v0P1Dow-GzqZ~q0Z(lq~xt9-am;~KBGsxj9& z1#w~scY9O96tf}&g}Jb*VY&v3K=O^GxH0u&lf57&RDMn&_a%_~QHZ#{J)~$qL(?x# zlFk)V$@kJ!au+Jn+9yyYAKl6Bt7o2>e2gN)a^ZH;CkoPwU-5X6^5HaD{yL8r9R(Py z&A((abuG#%QOt#eDQC$G1<<{ZitUnA_s~Qj%sf2#m}x~?E~C5mj7ujkoexsP$e2c_ zd3U~l(@d3K#Mjaky21&vQr_yEb=ao|a*nK~_6B!FDvWcJ)}K^vPk%XIPv1oT4@z%A z5!{f^O9I1s@HDX~iv!Du<+|jT7{FHcZd)c#TW)2U)ub@XtcFNv5QaG{3J=>-rC7#c ziEJS^4X|blgE%JqCwO8`Pu;_bQ$as&C}Ev>K(93Y&JB1U&^d&@FX2O`jlbCI`RCNp z7D?O*S-(eXrvm_-Bay8`aQ{aJ0K>>OWw%Ek?N~UZx3*r0TisGApkt48CMU9HqI5t8 zAs!9qe^CNPtJR)@)yMRl)}hrZ0J4m<+_L%lMkmZ0HL{z?8)@787;;g232~D%f~}Uc z!AV807=(fN%>^M!FV+00KG=bl^~{dA39aKsY%(sX-(m0;I-=MLDTgMfXFK6q^etWfhLB^~EZ^hySF zW$*ocDu2JP$g;F?rPLh>A;l!dFd0`aeGah9q&_jpCti5f( z%JsX<%<}DiNlzAI1i*d1M;J3>sO&U+DO$Er zYyMZ_iZEj3G?kWR$_!D+UsQyF`o5WvlCM!kd{U+$|B}ipNAZhJzUjDc9{^| zo%Z2%m#54|e1N zadxwJupoXQ9X|L44Z9tvk?kxlGY|9^H2e;jy_C-NXJsEeJJ1hFn+j4J0|gu;|3kIG z4tzTBRGN6SFeXwJP82UHag<9x@<3B1og#gkOu$h6feMgoUmRc2uzc+;y6s(2=;2 zltcAt6Pn=c@s?ZDzfd0#)}Pb09P1_hz}UK=D=xD0y1tYkMqO{dF*~a(1DMx!845#j zlZmO(9S;>ZM$UPLcX2%P)p%?pDWomze->rLG>Jhi7cefZj#wb5t(*N4nNMupYqMm4 z1=FCpK(7V6VQlvcyJ0kqvSMT{y|h5F0QLk?YhgM+FnVnh-$mzy->{@2Fz4f&pZkP zLgz;uD<)PPt1TE-EVV=U!7&xvQ!7U{-)|LZSbI$8+Z%nK9l{hoB;&s&I*?XD-XGl! z^-K%HKvb`KlBG2qb_6m!Xo&L+A!gDjIGf|-xFWL$-bgDkxNS{FT}vxA7ubKM0HCYK z_n6@Lch^pLCKcp|CSiPbQlh2&jfY`UV#R-FaWH)1rL4 z__sfFqZ^|Cc0nH*$1R%qX0n>ttquTi>$o}q0Nu<+Dc`70cQH=^q={Gau3M5%m_zY~ zlD!Uz=;c|RmHbCZd>x$s~nMH1vh(Iguet_Y%Irxihc!dBI{(kKIAvZLr*6A7TbD%+-FM3Qk@ON=QwGcNhDIiqf) zZ0a^fPTeq?>r(1QP?mKsqId=)byyT{l#jt?u2saLIaMcbWkWFUTep}UB#yWgN6?h* zf+B38C7kc42*OD425Y!y8wwggKO|68dvpd;o!6i)ZcUMvkV-s$@;3-!;pOTF@N6_&H# zlqU=Rw`}>)is6+bS0u@*D@Pk+tFLOVZK=rk#6ZDG*2(Kh@^!yY?0a#!H%%Mkn!Ja= z+*E2`T9nH?Z;j|d{6r_+$QLqNjS#!k{SP(tR_9S`}J4#eXi(H5a7b@d^ z^C`{$(zPAKH+05w1>Bt5?YP{FXKIHzExB=Tl)aAYzi>^%MydsAeL0^_Ri3NCvNbJe zJLRE!-kNF=CdAcjb7bROGe`ClvIS$jsJQ_v=QR#!U;bCkd~IS|!x+DoU+h^##0r3i zdjQm6_gYW^(u>91)=h&mB?Mjo6QLd;6>TwHX>{P*acV4)xh*TOrVdtftExXDQ9mNc zR|rZV`mb`$oTb)RjbAM^k@TzJCRvTY%d~QXOzG-Bqp?I+e+i)&^k5OS&lN%&fD75c z3pE2v?r*u87+%TPB5khbX7Ag+IW4!TODDr%$HscHsq1 z*`65k7($?au~DpGnR{%;z^E0@E*tYon# zete*V9K>pphl5z_t?Mt}!7pgCUuqksOCngZga7sGU`guWEFIKJJP;i0;DOk|S#|Kb zB|B)7bV;9uxBq*MuEflPR7$2ND&1wzD@VnlSm{Ak`XVGWQ}8j3(8TmoTu!2uSb+74 zwjYAk86kLhb{kg5={_z&o{iNVr#mjU_I!9?5>CYK99MU?UVe-{&d8efI+4DtrUjgl zK>m_1eX}{^dLVfjo?Ifu0^a=1;*5m$hf@`b?2#_fg$1)Q3F<(Y5Q8EmEo#w?ZT`7N5g4-+cyAVeVM~U=;rDt10tK&uJ ziXBK=*&rvu1h+v>Shda*(Q|bAvHo%z5=*kFZ&T2JYLm}2};_kU{v6}zGR-{r|IB(L>^`a*6 znk6W|=Lsn97SR5#ipI_lR=qolkX?unqp$C0Kkq2}dA0flKhVB8@%~BQmV2e|+GR@< zYXV^-5esJG|AbVqHfIgs_PEYrH$MP78-3U<>mj_WXG6Hq5|jDd=k@BW{<;!pBFXeS zKP&q8`o4OhK%lP{$#rAi_R3GRerw-X#xnJlA6Wg~zE?s=`^rzrzR=eV1Gm2Fnwn`N zjXfk5ZVAKKA@9}w#wCsFfW9;@P;->6e?A=-QPW!XNMZR%g&hg$ihEnPqJ&P{OOYzH#d|6CxO-`vaG_bBo z8U26moq|I-$VCwDx0|4zTho0LNFzKtS8|~dhOs3f7MOj_TC!$L*+bDk)7#V+dhQvY z`N}gxdAg9Wwas7jO4dqEcS-XndSsU1jEIHE8J+dYBV)TnoG-RZ-e8QyE%wl=V6^&VS9Xx`qhlS4;I~wjW+h zr%h^#?|+3gGL+KsG+Uo|Jd zgH$d;+pn8SR7eW#)44U_J^CA^5WZE_66QXnvOTuxgsCRM{O#eSz5bt0%nIyRr{`c3 zVcy<+QEx>3qI~fP@l4d1%+vsNhI~NV6`#!UkqVnl(KsvBrKePKz^K(%a)AY7E;)u} zL>O}iyW|zm$K2dh5XK?pdex|O{1N>e)E@PuqW8a)b)mg>CGZSp>K}|u;4}W($H)xT z#8>`qyJyM}Ln+pAGE&6|uqGuY8Bv7<7C7+L0FDWobWq>i5G=VzPqXhxzw%*=Bqjx_&5UM^9Fd5sg%2r&KUyZobnlJ~lN{h`8Nx5=m0vUrvcNbxdjbHc zv&VG>2>S^1IBBWW__1fKWA@^%7*@245RR`ZB*mb%TX9%A0h5U%Xxw7_Gp)IDY?)Gy zO7_|k`Edxj&pA>zza7ESY5N*GWDvaROXZ_pD1>rBM2j9@E*p^TF#5)8slj>u1fYSa zlQL#{M8}HebmH&y~GkHWIYoAiyjWtwR6tuuz_06-F3^WD9 zV2bejvVUf9pCD8FRKt=yz94qs1)5bcNKwEGL?E88abxV%#yIQ!^=vd0v8{^Wf8V&C zWB@8*)IA$e6;o&Tdx zszgb9T&QZa3ID!>4yWSS{tv$h*& zOHzfb*xuEY%@&&zJF1K5K#hu#&_yDXTHBu}I&W5dypKSS39ibvYctpQtFfW)fKIen zFq~g9^n_}n9;?tBkMcynDxX!6pXlK+OjJ^i6^m)X7P4zdtPedbyB#@gQ=_dcKp~Ay z_?9D^1tvKEXh{~w$J=+>20c^otvkXCRm%i&@kr7>k|e_J4gh>7|0%1Pwlv<(e;NO^ zBED(F@W&F@c8_(YsB$F7C8)GL91;I;DH{EsQFGE5l!YE@V#byHi6l$%6I>kj zX$;*@%wb=gR^bF3?*9eon2INv0;4}o#TZJwu2;L~K^b}RJ=BU9<3-Tb#42jpf|P@z zdR()DYG~dwPPn!NMd>2qwL-SVv&F@j|NnG*F$D{pF^VrqRJ=y=U` zuU*>@@0m%UNzL6!;0Qj{!~f0HEA&uZ1~Z>ym%-Vx%QKVO^;~wjJK15N-^?ywiW{0P z5+y6&l;QxmPAI+7->01UfFraLM{5wddscp1zqV}B9)R(T^I&0 zEu_+rka~B^nsulFuLU(Spu5J^@^FdtxQdNSfcu!>5%3uX0PAC~mkEUYw~v>f0Svs^M? z_>qsvmGhEtUe_w!cZtd84R`lDyJZ9d-4?D3Sdpd!$gjD z#v}}x57;i}DfczmzlGZ^zp8R=)pkQd+pnqor!=b^c~BZafIyj%Jq@+$-evf-d(>4D zA)f`Mn*V}K*EiYu>X`L;0jbF>XRrCK=`a@pxrew6^`*E@ic#(4VAB{I4p~niDw|9n z5BZ77I)&nsCO*?!JT$EEM4e{9-9_+ zta@D+z-_vMUne7DZTD?dfNTWe!REV~pK7G7N3XGJ!+8$Su6YxBd1J;F!zsN& z0lqu(4`vp|wlyJfg+(m~mK2R+V~EMgMz=R-clQqKrhP+vHZwBu;%;mJsIf4@J}pE2 znTvB^ZM8&c+`imi3jF|cqOajX_iIM(YqP5}Xgf%^$iFnhP$&UuJsd_J?2O);{$R$5 zo55Fz#ED3tg7VLxJ(N~yC(LlI$3jxW>goIz0nxGlGLpD6BM(n1vJihRZv3>gALn8c_64UY)sJOuF2p=G zASWN>zF)+F2(gQD@W`Rv4g6B%!AlKYC0 z?QD)95IxL(6=;_>zt(#sa5x-x39IsdiZ29f$`|^x zF@M^g3ba!E@e#zY>S3RKRrPP3_iwfJPc}XHPS@(&b$2hYIcVjbRAR?`qg^;+Z_19D z?XQlJ-Ds(f*^pYXV=`pj!ve2AZUc|$fA z5V2dZY4tHkL-+woN9n5C_E~M^$_pxZxbXLaT)Emn5V*0^@Z&-pW2pg!ViYdY^(XK( zx59Srj({N|Vxmpd%5(BM=_y0Mb ze&y#WNKh>=6i5vh657>Fc)s+zVIP4Nr=wh#dI z2*w9Uv@_I(knpzZ@98v4IhQ^%;sHJk`$R?X!)4W7ZO3V`EN#aeQ{m=x;wFQ~a$y!a z&2sf$xv$807$1#IZ%&h&^hmu_#%3!@wZEr{Dl0{kvBmZ4Qrpdp9|ZVui=8cCaI7m7v~okd!IC*8)2kY@^1fioBz7mf8D?Z z0==Gx*W5@x54agV>RXcA{qFu7HEOwA>tV}uKC4cu1lf^w$*mWla_=qS0$96JhpphK zpJz^4<+iduf(Ik<7-H~&oiNlBm;IzHQwSZzH7DW0=InmJN>Jl)(J)O6F{8RQTtro) zR~0!6wQJkf;=pTQw^UUGGN6TkMH)Z>e#EuAy0oB(6bA=*6BN+X5gBaOJHOXvwPr+X zyXCJ-S&%heBk~;}0v%K8dB=?rM}DlY;)J8#Knb^|J=_WGfQ=+Sfc@H!9(#HX*biH- z19G%sNhq@;S^q%|A)wsnZ40Y5-Sg*8Qj4*l%Zbi#yH3x@(qC)6_3tJGfsf`Tm_{K{OZocrWz(V4^&!YnK9+8It` z(+4ur>F7H&q9gWYWN6a8pj+u)WF#UB59}?~&21f%hak2bv*O&LUX1Yh@m|C3HuCRR zq9XBGE=4n-@TJnJl3>XflJK9UOSrbcQW2P=Kr8cCSHtuE-o|!I@I)Mk@>`0`tQ_qM z-H5Yfg#S1|(g$qr5{;>Uytkpq!ofGgc({lS4XY!Oo!Fg>>jT^&4zAih8oJaXSCo875M1H)$UtN0ZWy z_!^rRv5y_rgW#v^&GM4*6~LEsOJE@%!nCc-3dZ=vTU>T631c<3(OS&^&dgY&ZH!XU zbNupGmi$t73{g3nCzIyrQaV41qnTE6LHCn!%?dawpd!aKsZ5!X)?CG*HYOQ3t)q^4 z%M7Gp*Xa*}xKYt85<;7sv5+OYMod#^-(aCl6uSGenu=5uNHq0R!%ZV$3amHkoAePw z;7F9uWNFqXiAVa$PnboA%>xiJrp2N7;V>TAfd9)OGTlg5v>UEXQd>EY2d$L!i=>@ zh9b{B4Y}@{N%!?cfYPBK@owmN9BsB5r7uX=9sIC}QD%i)BHTsSM2Tf&>65w}+DiRWgy(7rEI02tX(;<{Uffh1B z{S(gv)=_N_DP5vX)AE=(!Mr|aR=hpVQ2y+(;y+=E^?>y*SzZsHwcs6g{qHD-9`a$R zqMCg{rkWk9E#tF?Viu$G1aiq(Vmf#9eNZ3xpr#KfvU~>&?|j0?W-qunG3sr;M&gao zZ68(W>fjcjP1Ap@Pv z(^P;h!a(^5BpZoRAThvxOGn~T4vhoFT}m&pfZLs93VQA&RY~jrZ9oqgD*1wB1k)L) zg~9H&dP@SbL>r0Nj}&0B0I>*V(edMf$PiIt{qDgrN2y;VBKumXwG$Wt5m?g&+A*oG zYWP&bd!E2=EXR=d6mJtGU4PiP8=L^l36pSEKmq@I$NvD%u-LMaM-||mY|npPm_*s0 z^;IOzA7ZQqHu-4%pMKVTJ(4+~Mq9+Sbp)nFZxaO%dd@)*M3ELiG0i_G)ds}n9;7}b zHqiV(;IW2QUOye|iqxo7wpX@I4{|-o>_I=Bi0^=1(TSL<7N`RZovsLmi4e{;|2`dH z<$an|k%Toaz>!HS3s)m>mbPk)mX}w_JA!D;o!7tz(de>=c_WwBUDL637$5WDiMtTf ztA(Nk1-Er->2{AHEK-1x#GPDn>GD!c(v*1DNRSIqURd*nPA#l? zV0-vr!rG(pP>{hq8r0BxDkY|3q09J^252)nbehjaiOsO#k4xbO1kB`fRAS#boHqK1 zwM5*kFoljwA#+@(#?(S#I3rFjC(KZ78-HQV1~$~ULXO206;U8$KI4&B204S9?gY-Y zcu@pLmkOpRN}v*_FbXD*SnVvz8A3^7*)z4X)IV_TfR2J_rCMd+SI3k@WdrZ45O29U zOQmBJ`7eA>t7UaioN_;kGgvXoCLO(fihE7U$qfj?t#^*2(!mEG#H%|Z&oDb@(sl(4 z`#NPl$^FT_HOP)6=uuo1GmyUi<&Y}KsH*8^m@P& zeGKzS2%u3&LO_P>2p=ryg0wjAO{U&ZR~mj@&={`OhRs86ZI{1!%W-~orWjDAEB3)r zJ{YQ}X|VQOqH*C82CVX{eVB9L-9}z~yor>hZ2$O4DIvgDcbIIl3$Vk_1SP z&%+N%o``lVu?i!I(KW0R3TeoAJmPHF#!iHv6eg49T4^cL%@)3A+d)VQ%5sT609MNw zW%UDZ|48*i0dYdso-Y}0Kid4$6fvoZ@PD)}5u63>pi41z$``u2D=E`%x)j*4q9P{8 zZaQfV?53lS+D$hElyJSbxWxQYqk@I4O3F3C7*J4H5K55gyFLmTqAY z+~Rf_m94?8PVSXh;g-d=QMcXRftMzt!aF9o6;ZhNMt0iI)w0^7>CT=SHe8|}JvA4N zPzt8aaH0o-RiPx6PI+HxG9Rl|vR*Y5^`Zu~YNhWHErc{%0h#?A822d>( zF9WXVU@~~q-+XD!VOqXm?y488$wvrB(xMl?+1K%^&lXZW?Er^K2vrUltm1mha}nn{ z=Jfgdq@uAsB7T_!2tAw~RM4+YlIG={)iSja-nIOFQYrxl%bvJfO>3$5Nc?6a8&lc_ zy!(&oD}fkJB?ZoD9$v_qAaV#bHze7%QV*pYfc@Cde2j+D{PQvvnzu=9<#Qq%IqBp5 z)~-}Tw^m96rl?srD?=q}i?6iGtx)7wY7`Xk4REHxx?`V_!SU&3l`#)U>35k?ulW<= z3^qXbX;N!MC^JFfT{yCg3Z=cK(_4}RvGxiHrq9mLb(63~%HUfVlZ`Ts`lji+;zEFd z&-~~(V@#lX>H`w^E6dU0*!*7{;4bxq|9Jp>PC~}mGKl!a02B;@E)O@k6{P6+3X*Pc@ z9WVsz6$di)6o;nqz|pJ?#n6=&K(2Knse6{Ager7-}*EiG}5(r@Avc62Oq z1NmVfFocWI8(KB+X6YODA+Y%M8Jm~Y48DMoKqjDEZzZ(3Z?M;?e_o^p3+==-@o6CF z$oIh70b~ZDn8~0@OQRt%z^YS76}ONG%6AKi2WBC?*&c*9g`FUvc@iRv0nKSj3T-)p z=3^-}iadau+A(~f^{7RF2=4W-F!EmGhbFu1!xCc1h~NlA-NjG@b< znE51U0InKhX^={qe*g{a{6x-T2wcGvM{$E{#!ms7r59gn8M3M*E%;g)N_Zzns?7}r z#ef*2`wGMYgmyfnYo+0(;Y#so6r(X&w^g8Ll=eEPohnd!P=CDh@&V$l1>Z%d*HnIw zEJ7x;PUIJiogoSsnc0i^ub5&ayRw}M`mq7VWqhkQ@3&zGGk*-1?S2ND4o<&BcuR+m z40=;V9wHWjPGkP5@ocoWjyez5iF63%-9S9gX#O7SSv|}tyr=h519`;P?S3AnC0Cr% z_tiWMnp7f8Q{B?4qryp|HZ(D{ZAAmS3+v{_OpAP^T^EMZRQ?NUhjG-mCa($O=9$vM z+G~7`6^+envj{a!jB#myHpr>VR(+PiWHr3-pE$>BJ59Mf(5JLK$*jK5yP&dqzs}R) zbGI7q7rumH1OC`bV^vn=R)(5vRoS-u$~#T`+68ibT3_Wm9tvNYeJ{aJSct~W;MzwR8n54TivvW1x4#|hUtcW zVza7jcOv>&w~ffmhuUF5Nzme8;1ovQLsW+qldtg}Hw0kLD9C=1l#X&eS!P63;G{dp zd8#ToE+O?NrqV0H*6*jc8c4wVr^s|D0}gctiuf>!rcmCLAtQ1+8J!Hncm+5CmxDzpN1%z7XJYiKk8IYMNd6zB3@BSNEkMzv|HtK}lS9iP$x5JEfY9)%%(m%wBvdgm>>x*oM|_k! z?ct8aptw|mBI3mkjq8!pZWWR-_46n3%eOErS@T@W9&}JNEXx|13HndR*!ls($fCyyRkvxs;Rnc zQcc7SIeXk}Mb6TRl6!8dd7!I8Y9p(|;1C3O`>1nIbf-rm0~O~45iip^Q0TQUzWc3f zAC#pRJu1>@ptIV|QoJ~m4IWy)X2VB9S1eK!cEPQc?bL5deoW|pQ|+mhmSmTUOMuMp98D22|35q&ydh(EJ%zITb= zl#}UKRers@E_HiDhxl9t>!MnjK>xP8V0<-;R9#qw%5Um@x&DX4SE{dG|bpvd0He|f*h<(T7D ziQf+0yMb;ESk)gdNdm4}6I;bN68ypmYrV`=Kvf!2p+6g|gbE<7Ip;>{uf91w@C}le z(yL*1T-J5Q!ZrENIFR)K75)yGqL0EdzbUEsNd9gWQGt8q|Iy9ea65&9+fGV5ZbU-e zM}YxE`M9<8oHSS4v>}cNJIh{_RO`s@<)mWB`k8uGG#2OfZs_kueidRX9;%^iI7c z>LVKLGS$2ld#D<=d*}6d#^|bw<+o;QMEmdLlU zbpbxAuE6H)EHMpHzk0YeP#4NroS(>scY)4|4j~Mz#l>}iGCG9pA2z=?!xK#llE4NM z;aoqyhr{|6n&GHDeJ}HmO)0oD6Gj8kF7U_`ny``eDg{dNH$bznM?xLxhH*`uJXX?z zij0e8LW{Q6Qf_2Nx!b5llI9;c=dUc;S-G72U5Nw>IDh7*OvU+SjYZ~9*uiUh%pWJy z>s_;hj_j+RE{FNIIr#x2Q8@(>uyooK-FK(X{(1jy+pvytw;56%6abg+1OV)HLo(o7 z+YL~W{+}sT2ymA6UJDsW4rJlyz}ND%%K;1;(VOIem+jPUN`6{AsU}&N8Q0@Xqn;$d z9=;d6=5k*f|4>OcAh6;9BnhxWxMc@syn@Y{OM?9-2{O>C{D)M?q;T;U!e1yB)XKKV z*SWpjMFM=ZClb^JgjL_?k+FMZoqcTaV7GNxM$vX}bZZw1>~_@jNQ?AQ$eys6y`lUk zj4$#ohUneEesZG&l}G8`?eE3$b~8LhW|04oJe;{BOJ+T<3+@ z2i0xTpDmHiXOarQ(Kuj-rPs*j%Ws6drf%!95nkP6^E_DH84;Uzd^8BGD^hF;XY&+z zt+9D-Ueg&V*$Cw^0&wea`C*I@F0Vr_V)|wp-)p!a0T`VKBmdRG&Xa=$Lp<_xTe)no zs#S6Ha+s-&CvtQ+ka=H*dte^LYe7ZI4Ftm;p*5lGQ{!1VPA#~T$bCrym0Bw22N zX~l4u572)GG4Ptphyjj7FiT>P-#hrU`9;%K@q2>{_$DYCO1(zWH)4BjTQOS1RSEuD zpjRx-XSINh`^Lf#*vkZT%(CHcg=ulJbS{QBh-D;3ULqQgCDFwH@yd>JF&<47cp)5H z#da+`OtNCkBMB+nCo(`@*;ghq7JdnKY?f%F1~GPH3>BbD&aQbyyW8l@e1-U(hytxL zf4>MOX?~zqWBscaQGaqEq8?>Y!R8rnNtudj~ET?{ zNjgwCc4XVk&2pRUuysa0E+1^xNDrL)2nsS4;yD?DI?qY+GFV?x0=P!IKBWh<0sq^HAhH1~l7&B;u>q zBz}W98tAM)<~CZ;d@gQ2IBXfJxp$R2PJuSNadR#24E6Tv3{7dsq6l?>fQYm`Qe*h# zKa@H}6BqTlkZ9=wz`#XDF~)RmrMElmakzTHY(l}&4Q9zrjdq*82V+Ko)~S7JlEy z-^<4l6vk3*Zv)i{Dc#3q74wzBiGGee!2V|W=d~(czb7tB#2%>q5`mll^Cn}DRh;HL z>u)22WG4AeEE9SL+D64AFJa6DvJpdq9~U#|pOsur=6`Qc0a*qrTRudwJ0 z7u`tMlSP^g8xVKG$E}GX%SOU^L~dH7px>cSXhM8PCY6y6+yb0Q2cAA#Vp&s+|2nrKHU9D!SQ)~_$pfT`B#XC@)wQDBI3_z)GO_6 zD9;7*UyIT_Q-|o!=JK5Np_T86oOrU}|(+%%X?!yO`YH2UghZN{Y#!jopyFa=cB!V`K_0MHi&jh@#5@ z4-Op9v}(cBC(ZSiq!VqBabvqX;t3=a;vH9ZW4 zNjo~OYdq45Hb>+vKdEOi$>SXN{5q-_-V=U*SocR&W-p_$;l%{X^qVx(H=Ht8QOeQIDJJAELJFp%pmKaekoU%j;UH3t$8!ub{s1XDO7|L#u@ z3}hxUvTn*yINfH6IZ@`Q5={p`W?=^y+rmz&vuKw57|bfqq9Hm@r*y$!;skgOZJ&&) zR5j`7z6lFIOr=3^DHMpA9lnK-15Ls$F_7BPyf>QWtHw8IG-E!3uRR(?6#lc0MyFXR ze1uTDQv-OwX;wCsM}!BQi*vcDT#B*IQK@-*!->I4kHt#oROz+PuTqx#V^2>HR?4v< z)>D=!QymCXaSWqsY_#AMdv5Tv11T<+7$Z1(4@U|bR}l8jro=d)T48Kq6X(yE#WBL_ z7@Q$&xO03N>dXkMW0^0cqCGr?_7E3RDfeLu7PXZZXQb823q?7mX-wa4|J+C&rTUJ4{4>R2b4=a*=>4NsctUC)Cz4x7DGHVsDf?u+6!vG2K=$} zgiHnEd?87%mAAkkxJtpm)1eSRTrUAP#9E%BPv!r`9H}sfy%{0NFp@RSNd9Awk?e<& z;L*}DJLTRT83{(L)r-a&!^$Fap{<^0-iN3toL?v2D>hVl$9TwjZDFx%$;icI!zWqPu5P@0~D zI`r*r3}XCpc6#QBx^sug8FR9wV<8pMc)*Bq% zuLs?&lPqr4t=x&ha#|{<^_wZT0b}V7&QPhkJYRMB+~7w)>|K7H2kgb+n$63>T=YG) zNcqv~V6M@4FqZ^&t{o4fq0321<|kV;A_X*Hi7=!~gRS1{UA?;;65hTrW(9W-D)aYy z!TWqnBeqE9HSQlQ_*yTBbNpWlp^1>hj20s4Asg5^S9OHH;oLiRV$*W4~=mWN8= z>8j@B+~AyWs)y$OjS$Cr=P{?PK*?i9!aQ1O>LPT|Je)T&Ia403%{I+D##i;&#VNic zU{+ra8GOel+@ZaeX2h4)HeI*0H{||=SVnP>HVjBl_Au;AjR%OY#Ccy$sQ$lD04KQfZD3 z(nr@dPXDpUtWw_(#ZDd6IIp6ppi(Y4xbj7;`>V#U09IrxGZ@ycMsZi z92{*E%E8fTizG+jDmE9|tt_tgxbv+gf#mbmD9V$wa5CXl!zG3&*==@LH9`AhBm0Ck z$WM0=wXW1cz-ZqQ_DNBR+^fWLu2KPdGJ`1zOv%vcPnlsw3U^prpRwL|kvCEyWw2;M zOYKs6mX}iO2fhFbv=5dk!%;KCdWrw^?9vj?PMEH~pxhGA&Y3b*mXo1Q zeFM*QbQT^|LyNN97+MZ0{lGK8AthnpnU1H*!s&tj`M3KA`qIJ#p}ziSP9XxqJ}vo# zYs1l=-lw&F>0_r&C5ha`o*eDxp8jNj`;}>N$Lt!Xf>VFb7 zs)%Ufd2B1i$-#(;3R%n^#OoA9=oHY@8B$=1oqZ&f=|4lt*}&p6q-t6iH*99r%y{Re zfnk)MSFubK)$L)ob=XgnGMEW+`{iQUZ*TlGDOyh;UeSV-#o9I{KSK>vw}|fFuI1JS zc9to>7M-AE2>mKfD;%==O4?Y!vngvXVQDj+abMVgMw{V`E$}^Oh?K`aF^H1g>^H;E zJj%_ug^1_}EgM>`@%SeP=%DV@Djm*pP7{s(mv6yutR>Qsa8mHo*c)1Pw!0PyLQmKj zJ*d+AOh--{Nj_EX4)B)Bgdmu~oEZx4{8>fm3f($*d%qSJ7A|FM`IDIC&bI)NctZ$x;q zw0p^Tp#_G=XSMvR@tGpT>V*I3&ujC4@~4}!``Z@Ba3715WJy&StE-k@C`%9^s&DT0 z-M$1NT9N@|@R0r^lK@$Yc(xV+BS_e5Rtf82zxC;9)f2Q}Xf;y`l1M|$@|1#IaGm$W zK`HLxj6PtckqOq%e~3S`W;l514`9}1R#v_IvcE)*P!EQXU}Sq9`hbC{7@tvqJ)RXr zh#q5H+w|vlqB&THCNAUE58~vkpmST>pmVc9=lZgqv zOqRh=aql{o2T~D_@Mnq!h)xvky$Lc<6&S%EIac({ckb0*$NV+mHnx96DgfcP)`+VB z^ma>oOps6>xOz|^+a^ZSAx)4Cgug@pp8R`F-xoAxdcM(NTM$o>SbU5Ub&SUt9{M|i zDKHkg_EFW&y7q*=UCjYY7LY^K7y%}G`fH9GhZs#USy<^@pRCOPs%-g0S`c<+SbdC1 zvyF3{eV%qE0$X5tsdzyJl%Q38(e?pZ_%+9(zM6~z!v#lK{f-_pTbx)t-LV(i4atk& zj^%4K5K>adYpaG1RGqUvmdr$oZVTP@fa2Z8*>FZ(6OpnkM98KCls3#7-QL4E!>mQn7M~mWwmNF& z2aPw-4Gy%Gx(;%NUHee~L`d~1f~dh0AQblKVSYv(NTkz2Y2x)Z4spHVH{Ddr9nUvf zH=8e~r~c2kZPyb*4)iS}3TyAwUYD&&8m_TBo@q=Ael$;x)#^ZWtlax@{en2Fss+K$ zd|}WtB*f4AEOMU=%~(d8YujY{5V993b|cUx*y^`pCL$obG48Syu54l`;18D&GK640n47!qAYv+&77MP4-5wwGxD>PXRSlZ%S!1sWs7*O5~NmPxcfIy#qS z`OjEzD>86vvZ=HD77fXP0Rs_J6xcX6iZ8?QpE&_~R2->=LdM%spa%oMVxhjtg7his zv*Hl*S*v?|nyvsefv8xHv5e=AUN4{PEU!R>XcTxD!*ZRC?!PqGJ~n-LhrrH7L+1~J zGZtr=_9soSOcaxMJZSKo@cOJUT`bsTUMJzsj|I4<0jo2ymO3Yxj`oCvOMizo^IHuL zUiP6od}`IA({Y5QiRmD)*B=^$Bawx`u^cqr9=BmbEXDX-hlz=p;aD;5qoMeu>Q`3C z>U%@bEp$oq(1!SoZ9cKH!@+@f^JBEg`7wG^8U0jD2tbqJ$54-`aE1CH(U{TC$+hvf z;pl|wf`&MhR2M3m?&O-ld)C4i=;;I%Nz{au;hY#lSe4Jrz#5<$-mZ`_K{AtsTupx7 z6X_*60E^}+AVr5{H^YXl)Sjz}mCy-{U2S4zh>wUwg=E76RH}AGmHsuiTS$UW)rIn2 zf=a3e4Bl$_>I5++m>C=o7vC=a^?u8D_ zzNl659!3d8iMqBmB>~LH|GTEr>37IxiQyp~Opj!OEYqc>Z#8(BHbxT2j|ePE;g||a zk{?Wjqy!>O`W2loI5OaA={s*V>K9DCTL=ReN@WUTOVjYAY?`Fmi=6iGUy`9U&(;RE zXD-+kqfYn!N#Z0EV-i30p<#mJz{*S@Le(I!AU_9V=K92>!K$nBmB7)F?t8L zTFc(5mibRWr?Lt!iln)#G1RoQq3N5eKv2|x%n`?DSu>12!>Y68358tHO~6?U7?QqW z!i3tx?C=%iIyZkC#sJ?=r}n%pB` zqVnR>((VSMMatDU3+Dl@4S+xh{5sTO@?x}%LOMgFM3{Q?ZByO1yme;Rp4qwXqmLn6 zGQ!Uhr$aI2nb?<^BjZ!3vUEokDEwFv%S%;Y!TF@rgLz6%9RGKZ(+g6hbjI@c%`ooH zWc~|w9l|tuJY4l~Czs0V3Em!=esH?5-wmd&zGHiMqnNb_nzI2b^AVx1!)1T87y_a1u^J{w3r>yg<;+B0%?ef<$#^MN%?ePnSuwqKIS48V zJZ5T2BH+g2@qtayvfc#c#j-ehuz#7fC7%MDD8ye;V{$C*+}J=b%4!WatT503`#MbZ z+onc#O_eB|KE~`n^YlOcM^IDQo^pW(SHmTkHQ@WHB!;+ARz8=x(VT#>!}kW|4${O|sB>O0rPewFlRlD&zz} zahdj67{ij6PD{#LevvoQ)V&NOfv#D^P)+wK3upt*NB``zNJU~eW8^*Zbl>#Xze)$c zLO?t*HTGt+NFRp;mZ)avaUEV>2Z!akA%Mk41K~hcC=4LGI;kP@9(~u-{8lAZP7a0U zJL@r{^w$Cu$fcnnAq1u@%_N6}-M(XG;B(t|V&xJFim4k9sX$0PyX8k6cn;73CnQbZ zBU~^$`=JR3K2*U)w)t?_@NYSjaUqaH@(MU5=S`tJ-TkBR1Hq_P!pQR{I^f}_{SDCG zz;tP{bb6CbSK$*5ZP&z0ECW|xwX&(a(uhqZ4lw6S^_n>!Xp1RhYq_{kf3K^V1^ff^ z+yl}gvgQ=YAD9hh%Tu)v zRz!IT1}kKri|xbNz}?ZQ5AFWhUpsUBr{4FWWM?rtb@Jzb=jluT@a4aJH74Fnzs$bR zRzJWs3XSgK?Nq*N>KTOA`3;Xvap`vdqM>)?qceJR{N%`+cVeoLU96v7s5Ee0kz(GLRJtJsj!Jo(-VLPl?VBs-I9=$5blSat3OD63B@rl!{zIN8@}al z9^6_=Ti-; z{a6o&Dfi~-4&~t2ICbS#*_vc+=xLHE_kNQ~g648HXjDogR@H_ znyi$+1a(-WYKDI!Ksn{$5;8CwhPJ}@!ugXdfujp@t?)fGg5}(L<=X9h;z&goXdCB7 zQ(_db_v+@GY&n^UP0v;QgO|;{5pc zGx063L-XGEuto>Ba1uw#AR zRz3Zcy0A~NLKd&?!bE7ov%Hrmfof6`+7_cP{@n*LnagE#HPf1=fV8RlA*M@=kX4I2 z=)vgJ5AK}0{eGGi)8T*l_ditBA2O%k=+vrzts-oyEQa5Y;yF5%e4im+ZO65>Q(yYs zE*`*NXfGFmu!|PN7{!#>5q5ouH`6jblKjh3!L>h;Z@(ASgFBS(USm9Jy#Mxhed<-i ze|WxoyDG~JkAOr*Ovc1;I?VqP4DS@4JB7+7Lu&VMTMI<|5Le!j65ta$!hrXqpwha4w%nYRNKGF z27CMDFct{kOr>N5(`WWle-00KhP75fy_>QlWQv&(LDsWv;m2e096n|Ke|*?ZFs%iC zwpRc~h*(=!kMMIP!~xe9bd>>CblPmm7lx_;&4{5IPb_(SMj|8rsAc|-K%BKqxA~AJ=12_xgfnsZ{ zcty@7J&AKiRdl>qQLw3ALzqNlNl6`P;U|TI$o{SA<>6u2Via2`RlnIsM+@?|Pqtd( zJ&3f_c$_^HOeY1BI;AxfjTx4Eg%P~5-}V)hafw0SBNPE}Qe1&?^yIMjf-Y+92H@8V z8Ez4dKo^c-L(oXo4|;*M&awB@E&*Qywg{$H=-M$|5?csSJWAtIJBA0;j^Tc_V|YOA zFoL5d6ML-9l41yC5>^1AVp!S2ygdd_vl^%C6|dri0D)QG=>Ic+-Oo;pcO(VtOsU}K zy%Pg4c*~ZFx2BsgJUM6R#<*b6KbK6optCaKR6+i4bxy0hxV}NmC@$c19TZt6(okqc zg+f#samTPS;%(EAY++-DIO>dJj~X5aH@Gl{s>52yykdK2QnLteb@GtOAvFlyk)?Cv z@KPi94nW1as{%shR5QzZg&zYXAfVT1()Uu0aUK$Tv(9UMm~DCC0Eu^K^YFz!&@F<$V_C7F@(21q9#VdZ4lyZ zxFJV;Ow0*XSLZeMk8y)%MyA8llYHsly(Eu~J0=;7rJ3?tu%PR-%Dgj$^1VP#Ooz7S z3zKw92c+G%wSEOh6o5Hd8qj@5lAxu)Tj&wE2~D}pTn|qJ*Cs<#ef|Y*GS`-?H2=c1 z!t6~u<3z!>P>bK&A%tR4Ta!b>>YWf=I;N=8PV6C`7SXHLM=LGmepO(wN+WFaBmWb@ z9_5!=2O9;MQpMEAa#!=ek;g#6r)ixKiL%1c`_}^7Hb6z1KQVQ~+l%3u-{~w;H}u+; z^J`(QHN8>VS<<|YWfn#+qZ%idcse+f?g(d+|ChP90h9B(>O9|%s;=sOtGi1dj@xcI ze(SYtx4?>(U9f|b0o`8WU>riiWb!b~>`aDdc890s<%wjW1dyzDtT>v)fQRt|PEdlv z3gmzd2F&6ihDbqxLzG|!GjTQwC`2S8IEes<7!soW{m;GcM^$y}!#2AcM_pC-z3;u} zo_oITx#ym%EowP8&C?zt`Lj$HSgF2T-{xr5Eh#L%&)DLd9um+cf3&^HB}RljBf&7! zf0yAn_B(qfwj|RfK;B^`jffkp6for{4ztP5L>m`T98DsJ83K^=C&U{V z2yFjody_vs?}d}6KqdTH@fp`ZPNo}Zn&`i$~OOl zI>&SsI=ZhTeT)24d76E_Ro{NJOjumo&yAp7PPB=+IeJF|N5wa9gFJLA3actz`!$%7 zATYZKXodAQn>RURQ)OCi>pqN-=9-&Q$StE;AkauP>)0smN%*41`7+$pR?A05C4Qr_ zLVsIn7JE1?Hcp>{v*#$DDC+T$s0+L!e)%NpMFDZr8L;u znj%-g4QvAI>jK~2!l$n4Z9C$$Lh^yOG7kB2ln}^MzEWP$UBA(HFjxKnpstvCrKRDc z7S+l2yUF%IG#@#$yx@Ie;ORPHW{V%16ubHk&ao21DqJojp|Y#sY2()-HsNdGyn<^#;jQRZaqF$b{DJxNA0Y>#s zVD{;RJA1COc6DK2=)#5v>ifRk7`dst012{T*p#!+!?ZWyu$~9(q5_P+_|2B|K8}?) zx^^!X;zX~v2x8QI1n`kGJ9no@PocgLgzynHkeLd?FH+l1#=4L(ruC!keZ#&C*SD3I zcNoeDtI~6+jSU7QU)C4+gCS^LmI3Rtysi>`&G~4B^+ksj&n}C^_C3pp0SJ#vHrkQL z!kxfF&C}6(Yf9j(r>2^WH0}PI{M+RweD(?;1y)uw44DtobWu@<+}Xk}7x?Nd*S3s0 z621}PG@1QMT$2Flu+l&RAiw_``vSdmwMxqqQ2=c!P|MT+dGFa*U?D5^P=8C`t)E@sMTzGouyXYOJ6v zkHeUBmcx$;dCUNXfO7D&sv7ryLIYA5!fV5pkSRelq~^x!y>7wC^9b7tL~A#Q(Gj5* zgLepoMnpQGK)s=yW4|@|9H)R3H=2T9ZcG6z(wu@nlGqWp$EQI0b^u(G$y|<6%>+wl zWri~@=TEq7BFq36V3&zZMGl)1A_W-~N`t0oMH8{w3{>=Xt{Lz{8lQniFo`tD+Pb4;peORMK7gZsRZ(=5E=@L-a|DfVj#FXUmlt@Tq zAnHmJPpLfUA5j(Apgkc`Ur;Qd+NT_pPsYQ8K7Zo4g5*M|_D5qgm1vaRS!lxSr0e6K zOtG^v@wd=PZ`^tOld1hsA|v(=HtE`sdvLPbsRW5OW+!xM_7pT5c*BM$cBy_FM^)^E zCXw)x^tj)0S-d>HSD!SuKLOxn0qnN8hYj#>SCD(+BPz|^k!I=7dee+F0UAQfWs;g@ z%`@o=LmRROzw$NoFeokB@iBipK_M?&jNSnjk2Ov6lp&$dP-{!7Nk@$EC&!uTjuw$VEZI=%V3D)Fw>;c^9Yv(D#kf@7Di*&M-T9e`XNj2yG zFG17)qW-E^PGVZIozp}d-xST}k(=f8+dgVY!1hj^L8DWu4lJ2zVJR_M_gCY!DAK!keONJ$#SW@Wt-hrT&BFp2mEC;7?;Ypzi~~ zlu8o1tblz^+^0%#Y;{Qi=uZD-Lrysq!~J@*Ky$I3ZyB>iK_oa41+n6BNB)8rESOFd zL@sqL9_~4*Rssc;*cW<+US0mo`CbLb_;PG;ylSBMSrTLi1(`_B?Y+VO&`2M)Qix6? zT}U1@@Rj;pzaG#p+gH^&iD+AayuL2y$U`34>BhD$$T2fNFR?772n~1bE%UV>Q51i84@H;Y)>vR#{9X zidFX}k4SyJ!6Pv@Tq`2hh{abjG7@tTrtp*?k>$E2sJ(1iilNr5$woosIVR2{8y3Ql znGyXcxR94+#1V{_NBut*$DjofM8Yzaz2I75rJdK6^ z0btp}kPo|UD%ak(d1iCAz;H?GxkxG9N3(27POxXo z#jwFK1PjKcw=T;-gngZz~iwrWJUFQ+Nb3g8p^>OCR1^=Of%A2M;!` zCZGo4ma0f<51I9RfIoQ<_}-`}j^j=7;5IYeif$tW6)WAp64T+NyXUh<;kSR(k|bOL zz;s#js4H2=L*yXOKh5Kc<}#?f*(?G8$iRlbg_H(zq&b!|a-LXk%UAO93^=oG&;<~=cvV3$0$LbOCZLs2U7CYBB0 zNl)uM2l^pg=bds;HOA$_^~L$*z_7z_*j%1?+06~qL+s+RCPumUns>y)nv4U@$-smv zHGJOA)*G9btlj@7vw(i8#YKPsDIQlG4t1EaLCVNm#)P(I26t0OrrBn=XyQo8NGMht zqaKg`r$Yu0e1atf+jVwvE1P)u2bSDNKX7DOE*kvmGUB$rW?CC_@K%cpyjQA%({92Y zq?MVNeBjmp5;_0I8?@T@QXAK1nzpIbb#L764x*bUmA|*^ewIr4_cK zWZCY1@Kw_sjTTsThp&C>$luk$bTS4czb6-(tA<_O-L*s?;><0a^fQ`pKS39~A|p1z zdLJ9K?isa(Gh$-Z$8t{?f>&<`7~mzz8hUHtFJM*4NX(VZs-Tr)xcKD&9tvB(vmmf* z7o;nxPFYWtm|84cIasA=qKxHAwPviB%3utQu2n^XxRZ9POn&)yq&OnKyhlSpspLeZ ze$KVQY-SPR=eqjV|1Xjiv;}cPUa}OlDl}TJ+tX4!1~fc{f)z`Ob#z6~S>Y+w{r8Wf z?Hx-*+8XAS%q|BNNFrC3RQyyB(?=D0ut)++d z-Iy+jxH&S)*PO2@4lWtxDT_Gf`V>6?=8x9CIjB7j{mS!Xb5hcDVidgsC5!sP@tHH`o6KF-(^Im6n-`%?##Z!;NU{2dDLa zQ^afZk#3H3W$iWE0tai?M{S>M6K51RpN3qURO&EKuswSkYNX!NpdRaWy^2le#RQ8N z709J7Ij08$1d0ae?|;SMPu&$KTno0EU?Ix7U!qK%FBq_s4D!vQd$NQD8i>k}gyg7S(-HZLuIyNqT*sX&?TQx54yp~L5OX*d9kn(w(Ft%|@ z2?09$gBk?;Dg-%4YFVjC!V}=OCx|Hj#7=eUEt|{$*X4Q#x!?!SVr`PMD~k33H?a5A zZQ+&>(^gAFJ0m*4m!4g&XUGCQI=FX*XEK5s^KoZlR9Yf-_|q2qxGrwDh#vOUH5952d5>po3~6c6O8;%U+D#aX5^OEckC0@y_60uG&y=YU83ud@=gYZLTKMU4p`E?K4V z)!wn&$-?~0Yo@q8SoRzp%x2lkK(_ufT;jD#5}jaJf%1ky%d*eMP1S^oXm7N+4b91X zV(?5%x^4SIdHm2F#D(Si4>P9|hw+oDE>}gO*k!8A!V>Gt5&#x$!11D~NLKp(u|48p z!b9`H(nbH*g-m%`ajZ!GB6E>4asR)Vi=A@3S4M5tV%YMMx(S`p4LV3-SNar5AmZ@` zV#8zmPf60k0XVK>pmGFn7_zh)ZfQO^t|v2uC4;GDIm^+v#uMtyzM((tB5@?8&flf% z!S|1dnJ?!%nkVu3V}y3JNe`3QlnXX%|EQ|jq@kmu2vBRp&qupQ~hY`ho^CizFC3CHlvZKo|k z`6FaknVpds>l*}-Tzi@~mdN{bq=!)ceP~B@W;3W~B&oo@7QV5Zqyd^DmFD<`~O=oMG`1f(nNB_qnMjLOtfeY(1D^f@7xJ^M&V?yHZ<|BDw%K)3%dpP z$#N(x!+{|qW&>;P3eZAg8uKRco6fT+?>qg?7NU- z0USSM0&2u)eN-d`_C5FNGVXs|&HG{YKlZlpU8s!v9~M(($C9|Uf?1_m@U{j`A}6}m zHl^GpwK*cuj1;mnbxTcmT`ellCe(+Sd`Ztr)k zJ!o`=76<_miF>y`sdZGxbu6SBN2gZii zi1ps2*+p?4m&m0eGEoHW{_{}*#RO?dM@JL)QCPq2Oi$&fLgf?(P4H=s; zROJoQ`DbyP2%*uYd+q??hhN$>dJO z^>a2jP;JV=-E-KW)S8XKVNB#A#qLrV9eAfeUF%P`|0#_!_#0Ph?rXnr&gSBkniH}3 zw)hBOJ^9n;YDWl4n+SS)vJ%yJBm`6#8oMbx#h7$2&JCdRoUdWaIz|Y05OzRmoj6MGc#{#N9vK zz{u-kl--N~c`oqYGu++(@8Ny)|7CceNr$9EvJrY+hNU65AjZ97XtD}O6rXI4qM5F- z3OPu`EhatcnYIMdl3botm}%{bm4pKMvOphw=b6vIJLh_brTFMO`%azb9c@UCz;@sH z-Z2R~Ou2{7_l~%Gq@PF5_YQnM`p&WQy#w2izVqaH-q9PC+K)3ueKCU=Q%^`3g-)r&e&kL?KUlQ*d)qd%v*VrD$48#Ua(#I;!>T5MOtge% z9c>V^f0VN3A{0bxpq-neqpFKX1Sy7LD$_J^swAgGx~>1~GW*n5Ex*!iEkL_>eMP$z zo5GIB6Rnzrk3_Xea1I)_(HB~u&rEmfdcJuQcN~e9 zO4-+7k0XLTy9}ZWBqKuy*69Y?gB-<35E8ufd$uqcs%(#2q4M2R^^#@F1<|RPsQ%A` z(30FG@0j`Ol>EuubJ|oA%0Q@yS1YwZyv&A(SF`n7ekL@9GGlzc7^TC}1bgD0D^VTk zaM}v3-uG@}iAJk|s4lQoyp(0D@H%{#U*IE5i}5XV~$7vVMN@OLh%RnmsU z1Gv^bBJdn;EpuZvx9+tQioXtS*PotW+py~pyFU~0INVN+>H}@ng*HECZN6xQHsclB zq|}`X?=;(-uBvnJSBFV#d8N|TK=2MqWgbmz6J;E6PkWu#K->o}3UQ_N(IAZDb(1TS zsEE1@pUR``5-&8hys_Z;BkDhUf5V|u+aMbJlN&;-p4>jbO26rpv3)CJ zp`N`Hxn&;@U+$gCU(23ri}rYCd#<=nZi>hY2Ci(+wdWF>Oe(PN%AR}qu74@)yV_f< z@^r*ZFLTC4`>s8#Cfl^N=bWt8b5(0sd#|b;m3yx}T$vu|99xJuJjKn$AdI-N$SV$& zG>o7qyDMI8HS1M`R8qaFr!(&tdCC%7qRMI8{e+Mn(gM~i$tOb<*-j(AB%AjtIwj72 zsa1PT8rn>K7cv^K+{^+)vrsg=iZH9Pxz3i)8@Q0o$Z>c4kO>;Zyfl}@tC5JA9Q^S6 z!L<(4u6NR0SeV2ZZgT(6Kffl;!WwS!@#op3nO8I^HHejsxYsZ!5DNB}ev-s*l_xGJ z5^Se)VFRv|8q6V~5u}*qRCZ^ERs-WF#<~j_f3DpPv3#68-CmJcj6l4%aF{^EyW&t0 z6ND&=f?k*3a=VLRo#;E}hgY5rDHqiH>4P@k4Jmh&_x;im44%jM@oQ6BmE~w=%8-`7 z|9w^C!A3D|^8WL4^uxriXrsj1VQ%WUFo-Q>!U7=MgGvhV+VevlX&jJ5jkSgS`aW_%;m{^5pG z1<}|F3ky54x`2e2N-^uMx_BJoznnar4f-7*(Xkv=C@e2Gk= z^Dbb6_r(w7yfWMtuw$)0`nezb&la%EV`HU#e5}dezo;f{g*pc4(_^h( z%(^KqQ$6ckL<81)PLB@%4=xH;tx%b&kyalVYxVJqYSq@T!$>_g*5rpTs!3C+V@N$Y z*6JT#RI9ecty>MOz3;C^ks3T*7f7jQu#UC**z;^Pcm*ZcC6Ma&u>$1+d8%%I+|_c9 z0jzGum{2tpQAwqHKVP9{Wi@XKxrQxZ^x^uoz@-=Cp0Tw`CB@_HS}9ctB*YQtLkqs1 z{wmIAl|891!9!=~cguPv$7AqTyAN5(#_&$a@8a@(2Obr5otrj{&CF9swai7{c4s^4eS9y!IaP<_&F9Hd;M0*6K$*J6vF^ zfy5+=8?Byto~^D#Mn7H+e|0kQ)=7o3$IkQ4h|a4g(yZ+4z0>E_fvu*-Tm8GAVhjSU zV@wy~SqL$gYomrEw>LdA+h1eaP@&92f_vPZdroawnF{4z1iAO*x*BP9TpJ!gmpmM6 zl}kEkbqLUl)P`eC!ec7{dXd_2tW`4GtxGivH-4E-Fc`?(NtIo7I-ni1|nTM!eE6S$C8cZAqr)S7$eHnD|ePwHj>w3vAV_mKtle_)6UJnQDjuYahL+**B|Y z4Auil9H#Ffs1=n+n5${*QPJYpoN$ zt~7Q7!&s|~7CtIWJ$Jv|0%nE4BYHBgpdt!25|4=&k#e{HlZsG(&M zd5~<^@ikD^N?k1oRrTt z%nYl@Z;^Y;YVEr9m%QlGxfjUcFv_3Y|IAd^OPQc$p0A0;Mbs;_`g|>yORUuw)Nmsk z*9w3>U&C#kZ*{~VxJZY?*i@0ATCk3^dXdhNu~yB-t_dKhq!YDD3TDPR^w0_>k=fW| ztv>TSTQxI#tkwJewo=&7$&i$pJz_{cRyVn_vNR)lY%ZRzUki|2oEa6oe+X-AXLm+5bk`u6eV?hu6olwG2BIBg*}IJq7|9Gb`S4hi=j4a6CXHS~lYv;qMa4xm zIYKNSnX02>8u7rxhU4h3+K`-?8TQCE_NFAn09p3_#YP63ajf7`#l2SX!9nv$k~i88 zi!Rm;T6BFAuY^U{D$%Sfx82zNYP229#zxyeT~Qq!jYZ0?+;-_MdKs?-gamhy8b-4a z*SzwZ4NWIr@>*Z;!4<1jq6hi~|u8WoYyfk8q7PeAS@BNEk6c z7HFe}AMhwsUt8?kM!pdm-9T_7Kw8RJ>ZUQKCp-w}8&hM$EKG>df4Y7-5aT&;zAC00 zsAK-_F;PS1Hn17Yg9bKhg9yA_7;B4qnxXKosby&+D3jYeo-N5bhuFv!!+RWe zyH6+4th4Ync4z*9qW%~6EiXq=b~b4q%Rj?ujZWAfBTzz|+Rc<=u11RvoanlO`P;Qg{Z5gz-t;Q)P&vh$vGX$=4}c6)D#^ z-*O((%fIF05#7afL-M_xO9$`&k&<_dNi8uAz|frHGhWN9`qi~x-@&*4Ue@A!ZlzoK zh6g0XaCFe6dh=zx*>Nv}ir&cQQ{+FNC==Q6n=uNA9L^qm5KKpvJe6T}aDHjq}gbGP!4;L(7}M zh6ZWcUw=4~UL|wFSx%~kkjckRNKutI#Nl{i=`iAnQ~%83iDTv4iD{L4CoC0{&ao?$ z7(2fVCHD10D6zf3K9b^)^{kzz#uz3eFwCd2&?yhnC1rrs);Twl5-FpE!P%W@ zwWC&ydZW^$yGp6(6gW-JK$RIR3dZMwrKGcGYD$g0($LaYYHSTPbx+w){;R|XV~LdR z_6Zd+OLkb20V!UMBo)M~p_&@)D=VZ1Xg#`lavdt$NJ!1&Q?dIgAWl%{@b3TnBPG@X z2EZLqAfm#Nd~P2m6#;Ox5N#WQ3H)BisHvx1I{KmkXekxjWkE)*%6js%Z?ysZNG)n0 zskU!|*BF5^oskGwt7@iJa-Vx5K4pQO%$j6ypAx2_Rf$O`NP7s7s~q3zRVn*>D)3PA zLIvj<)qobXlrbX9J`u-lSUmyi(rzjehT%dKU7TBTclAM?^tvc2Qb*1|HIwN6G9^i| z_nBxy+KxbzTXjEAOX){_06i7=tWa^HtT@}KxSLe|wkJYsx>}oDqe}0#LkG4=3b&Kd zG=E8M>>&G#i&bF=7uh#^A?Br*Niy+D)PH0j)1baEivKCLJOq~wwIGZo6)2kMI9Lp$HF%H(X^VQQTmgVOK5b&>pG7t zBr+6cIg73%TO$t^bVnCMBXy%Fu0sYl#4^cIm6g@K8SUgQ<#I0<=aV}fblW1uoUTDY zsZN;8CZpei#}@6vA1Er&L6#$bsV*_nxuo_4DM@c&4E!+lM*>?greZS8!ZN_Mj1#U)gqwGRX0;q&<$T7rFPLvtcLK!_J*}jGy8bSokQ_7Aujd1erKop11Q0DE zFa7`aT1rS~Ga_AL-}snhri7Ryq^6%}BzDVwcV zBYa2gn-Trnv>HYV5GSd)+E&-vA_|YqR`P8kvlIG;K z?tfhan@@53q~7tIH>LF2laezURxKPgne^Zu*9BhXWDX6; zM8ITi5-F!YqzXjhZ~xxkO6>_;%qAg#$IMtwMN-sIQNt8_j_C^zDVA262oh#C`u00$ zt-=J6cKk0WePQ!euiMVmc%w$>&X?SynX3R8N?*822c$3DNz_AvT=f}E`M6czZ`o$5 z3r22<{#3!1X|b4tM1jajl#&g5q5aQk_UWFk`04rA z%<)<&XYY#ZKDN~Eoe8BxV=UP0NMSJ?%t4MSf4!W1DpuP62?N1y=Tl+-QIj0X`pq!8 z7$~yq;_PMy+L1blZj3GkqRysheRW-22lZl4)Yq1B_44gPjG}P8h69>#%acEcj9CYW zz^7T(;shPDC@lI_a{nCyE&FOh5(i`nQzsOe#cP6|UmVVn#Iv-_vGnF3M!eX1hiSJ1 zTe5-@akbrwk!*F#Z{61goss5Qc}nl(_ie?*?OcnNcXqgCe!qom(fZIKuM29H9(HeM zQU?h@A%dhhF6_lrcO`dBDsmUE66y!|m^EN(Z_C@mFku&jD)grrUab}|VKUmNM`4lO zBI1^Wt#RYxN}I`wN6tjqH$Yn+_q>uy(|}wa@?#qW5^i5a0JXCBrty^Of&!3$ploqJ zkLjqER-cf;^ng5(T+|SZALQINn>wb9b;Nf|pUNWQ5(-Tk(oi3%=FSd_m2;OecZCw( z`8KvtLM>uSX=Kyp@&5D8L^ zQ0&ME<)SJ_$X6ZXmL*!gfpuEFA!Xf}{*`>BmeCN%PS?kV`>hoRpLZl(vF^g7U;$C(xoqhV0gj)@t@L zsFjLZPWgP~;p*7COpwrOVt7K#NXsU6ahWZ2|I^T#tL2-6Fl-?zwCZ4T|5r^#(!V!K zuuLHF{vSwSLfNzqK?fPFoJwE>HyBu}0sQ5(PDld^+My#eT4^_m!yMrc6mlecCNu}i;f=1Aa8v#$itDu9N_cOnE9GXbXW#~RfQR;q88BDybp{nlFl(Lw@N#MSqukL_bQ~?`XmXrdnTC>w3Z0O58sjF-Q#f zjN};@Gzr8CZe;;sInCQ`Q`z3yu7Fk(PpcJ3e$7V1-dFSY3O{i=EQ_z^NswFlY6J+? zkZj`C*ga!4{Z_I2(`L=4uOW}Pld;6TS0g6eS_KO)fP|}j#`stdxl+U0H&ZFNElueC ze0#M)_}Lu)E7=egLOMC0nkR*5yBtQ3Vbtj<8SwvT3?ue=ocw@Q^Uo}iWzVDL<3lwO zi_JC<{S7nY$W|gGTIgAoFC=W^D>zGSe%{*byMMG#7=z)vr_{E4>-}m3Q?G91cjT6W zBQ@9;!>$GctY2Lz#$v?IPodlgw1`4a*iMK zCA#~AIve=duYpweh4jMaD({x%)bY27Y>}Gem*1?#PsewKRghbmp}J>uk4d??@V^gn zWvy?q1Y)KEMY`0#SBlV?a0NMXh0B2uIcyS%6cHKLzeE+9*UN8U9#XeIfLb?fg~fwt zv(N??5=3^az&&kSK(?Eusgv`H8njC%^d*{T>lmPkP-40E)0F^%X*VRB^BPjWzb6j#{Z|m3k%-J1sPq5{wWo2>%XrC zh6yl#3DDq*A(dp_xy4*2oo&Um{GBuTvDjq_=F#sDQ3 zMEV8IAR(h{d3)Yqac}DoKSfBiEKv z%VP%-HX&nX&`q{teaU+WaFlad37EvoYqxmY9gP?S<}j3xS;S+8nXO5}!jDYpd7Fz5 zXo1BbcOlpcRoI=R0D+KqZZEpq@-8buJ(>)6DpNYSEuY*GR;%@*6}1ASEG8KsFhLdu zD_4R-e-p+-9c`E5Kh{VZfah$q+WJGJ4R;*;G$NuzwkCkGdxy$mT@S|t&wauw}Tjfd$Ze$OB< zefmSIw&QNH(FSj|cL00I3l}2OS+}Z!d%&E@-YHBumvZQF*R7fs1Yzo~^M1qry)qC~ zno!FA3v*7Cf;VgUy=8z3^M=mSOcLv!TS_rJUg}0Qji)Sy>9|dy8 z-u0xY3)~|8hcpGI1yXmMXfK77ER?RxHEIU=y-BXiZ_oL@DRPzTl8Ygt;JXY0W+C|| z^F?ZN1cEQG-WmoEaNHB+&Q0k8hS+Pv&FQ-x2Q2vI8+_ zlX_<_57hy<2ql}HN^(@cbwP!us(fBb4*OktRndrCFS@WjXCA%{q5 zIQb)7OSoD zNAq)1Ka}yEWhZWcws*OB`a!j~Z=KdE%n{Nt#mCv`mg%0>dJ*AOF(`5;PDa||m!htU zPRA;OtLmatR)k$p7oD&otgyQ1Q7eM)61SXS9J1o68vU5{Bb%=O8=@WUuBV!O-N$2G z=%~RG1Tfd{+8n+6A=XT~bX^6lU2b-JRkqH`xEYt|-PETnB42( zhPkt6&k|Rp8!NGSYn&VtPBJ_?v6KHt3EE63O8S`%t}nP_WBTN_rf7SSxL{;DPS z51)O#|8?fBm*H>626XrTH5`i3`EL7^F#*nH*~Iyyl@%6;LRNU zi;0v&f#Iqy{PNPwU3Y*xU@;5bDYc7|Ksm@q$so*&sP{3JiCdAoS&@zOiMWGEWxVQr z_lSf?X?$W(M^4^HYw+fIxgxtVw|PAj%FtH87PDu&&x!g}2Oi|)x@h@}w8*N7yU>8q z)_9Di>ke7&-U(P|yIBJ6PE}k2#vOE#K)!xGc*r0+ftZ&y3s%@~Q)9}t7T-?C2~BAf z34okAMH^uDZ|(EBVwQCQs|G_ouqlcZo?5wq6d&Bf#pZgm|t zFET%8)9s)Y^oowhPm!ci+vUqJ1%3L^s+3brfXV(MNrB3VcP z4Wtdpa+wtjOO`9FY*@13>fps;$ue(c!;(dpvAsyf(0L>abw(u%u&yduUQ|n#D~BXY z=B9!x*CO)JYKW0#7Dq^Ag%wWoLF}`fI#n#@fi_WD@f}{sfO{fBv@S(cDkx(!k+OI; zz7e5w47nzM1Mc%>uIVb`c{vi?CA$>%I$?4O*5X;q`(1)dWD;Vu#F&V*JRM9y3al^* z;ACUyJN;^t4r4+ea869wjM+jnvnn_X}(Sy>9oj=N%) zOSjLok|5Q>E{ zD;jzoYDX#xyAgqN#q?o~|LhNMcMtQK?2u`r2IwU!rmV9<#{*Dv{YRwn@WMxoT?6qP zGXZ)63=^Z>yDfQVy!)03u}Qo!$iYU-2_@Pz1mdQVg7E2aE4%M4Qtzpd8v4W*pp3|( z^A9_%`n9dekzQg@GEE?R)tI4xQrTu)Q{MnI%He zm=+_Vn01n*!EzGpI@Iz~5Ze%e`I0A~07!*pL0f%tC_tXQ^>|IFj4!0<%n$Igd{RtB zNaQ>_z@kGbr}g0#y^geDv0?9S-~+E9r^XWao`5VU6J0BJXDT&j04_kvfcs5+oH=3h zxrI{+9LjnHC3hlu%adUWFx2_3@8vy(c9iGD+Vw^Sr3+T)JE+sv;$CMn+juJK!wmw) z|0l5pNPZ)+M$cphWEyy;W5~Hc!Z&}S={*JaUKcf@wiootEofS+7xKg3p;LsOHRiv z@Mp#xb{NEnNKRI0z4-6F>zG3k@s2?xr!~|d1D$;%rq)XA%mnWy{@8@D;NE0`J_wNB zPQAzMmbFsV;=$UTNd7a?_8kRuhzcN@345+zTGU2#8?CKnO@wu3a*gUb>cuij-dKkS zr#~&A=5<+vQ5rdc|0m|M9HxM#K`QHR!P?7W$tKYa#&N_<}r&X&|=4tq=E;joAhe{ zTaYdhoxoc|i&w7!DDB^ZiUD<;p0kKwSq0Fui{0Bli%wv9h3K`{TIfxBXrBOajmF|wr$s~Qd<9{lob4;g72?$|yF zxU+ADJI3g7=iNTI5HAXCqXRiV#^?vo&fU-X5q2T{G`ArLv4S7Ph<0^s?EX=a9cM=|zR?bR?L~y#Anj`oAM9*RM&8SQ>s|X39H_JvDm^x^VONj9RlcWe7wAz_%BoL*05>avoTKsMVKt(l`!Zk4b$ z@M*YLbfA>TmOSdMD`U~05%DmJ)bQbyn@>`$A`T1ZR;Ep!6_e&Lob=)hx3WEF2$Syb z&tAI3$APm~E^X)Y{%3F0&ojTu&)eOaU0P)5lq2FA>B_-KZPaRCP*5puir($e2w>X~ zyz0SfV_XKQ(KWT&BTJwOPhb<(5=bICn1Qx9Y0;DMt75~#QlRXEY4Xb-+npSjGp3+oCEw&L;SPzQ=ItUly2N=+mBzH;r-Xei~o{yZI{1v6}}3MXUz)44X? zY?T^oVWU?Y}DzuWw#D6=N-C0=X{XvG;)FbO(2^pTqhGq-z|3u-HdI2ABTKs8`#OI;*ntw`Qk6BsDNEhH|s>mP(Y z0I-0G^1Y$1n0zWiFgPz0MmSk{W`m*VhvkRT;0?9P(TMOOm{UXkw;0O~O|(2qv;s@C zJWI5OSz=1qpK=y>NX!AY<#8dUa`fRcU{0*PDbsA6(!req9moacrhX8CGQ|y;Te$p* zH4#IQ%F$GcaBap|DF>7d4IRN~N02q;*t_geLcE=uils zUZ%{zacvf7p%5N97#HRak--OPXi1kxgcWE#XW;_XZ|$CMy2Xz1@dhJONJYmj_|Ruo zcUXq-lU^kvz_;MX=JxuvSu7I`slf1Zkr%GXoq^^Ng)O`A-ax{5Rjh-S(BK5Ac@QRK zM@Yw>w5K9Ba9ctGrTrP)?i_R`J*W%5#D%s1bVBz4D!MJwP8mq|N_g9*>a)-7x zR>H`J>$pRD!(9NN$&|gA<#Li4fFw{3XXvPbeD`66`1qRE`Q7+-HB^P zV*z!ry&tBekCBA1B&WW;lJ4}Iv&4J~C)wL3k`{*;{d`tat@vn=Oe6>l*p@8SG`mfl z9S1tYs`e!hT5Ow$PV-1r zS%ZMPi}`+P34Ehty67%S<<7^z#3`=52xVS(t3K&d-TNr+3K;W1pbUyviV3sNE=My~3C@vkn$Wi%1`*{y8+nrB|*Jkh7 zU91tOtr3gyNFC6fI*^Onx|Odf0usw-ZGvgRH^(cwc=?KxAlmlCmPsW%giw84c3Z5QJH892oA{gs}v5Zl78NyUP zBdK5m>gLlL5U3Q!IySBXZJl=vW1SjfoqSrsf!5NL2Gzt^_Z%3TQU~~Xg)v#NU`$qQ zo{VFRsM+&jY`TdtnH4&(GLA7I9M&Mo)e7i9H?uZg?=6R>X?y08JxlL~_V!qNqGwj4=nN7;JH z?V(Ju$lo57y(d{EG)?UcqQF(QinVorUt1#ft?~f;{@ZBXyH&eP>b9fQe9oybEmbBK&p!AUtv7(mwip!zO0c&V zC%=~|@}1L=Zdz|2F4St(pDu03She57JwIeAZZw?N(n$NYa)pJaAKfu1!qK;_X+?w&`C~{lmbH$b-9Kn;CNxJkX`{A+n!`mTd5t0+M1%6Ro zqG|yf-i=HZ`r(WdY0@$O6C@p>A@(fyujB1i2XobXN+c$anNOx2H6`c6t(M8 zL@?^(yAWf1Fx@$cMdW)WEY(7Tu{RYf+;+b#?~)?Yee+tcRULWXb6G`sY=A(XZ~CBX zn!~OI3*(27>wE7%y#rBr`5>W!{d#nr*YGNL6_j%Xxq;7;kYA8c6n zjFn#L?)z|~^nEG5z8cqtZ)i<4!^a+dlP%9C29%b^yPZ2ph#; zboUf`j8jCriu(#9?BK+LI)s%MHb(J zXBgR=+mCq3B4cuPXQ@d0MEcu^Li?U%z}rjxCT#3p$_s7_s>l4@oWV!(p(@zgoU+vj zoApaaC3NycZ9)J?U(M%ao>@=QD&ASf9;S})OQ2@;>%0aTYSn3jPQsucUUAUj;l0BT z^VZ8_Ep#9UO(WzY{CGrVUtO4uwOPJ5xq~QM{>Ao>X~QiR-_a|D(aD)QXHWdw=xom< z{wQm6iu3qg-yYfj&g}huBQR}|Fw1Q-DbNnjo{jD*ym{|`eGk=$u`!}vWFK4q>)cgL zmM?Vo@p63kBGEzm-|Sud{HFbf-Pzgg#ngO}Eyj0Y4|Er!?}1?_U&|arx!QkO{I>mv z{Stl3ee}&s7!Ex|%I$;BVmz3kyRw5kr;WS$Bjv^ay00r{=LgwhvKZ%lnJHg$A9=Go z{$`3ufiNJIsye`?Lb`I)(6)5?^jDgPR$Q!cimh>q$azpq?0!Xm^Qrmp6C-RsPv$Xp z5KaPEKW6hauBDUC3O&LodU^cVbilZIzg5UQ9-HR#A+>iyazx)Bn_e2Mv9w&;PO`_3 zdck9ltsOv_Dyw2(%?dR0YE-X$VDy!;x|bAeh#gmF2*$_tiDpN)xI#2y2BOz7>Vtc8EZZM0x`($Fv)c$Wz0Ey*xV_{)*8e|>Dfh>^ zZ4{)pb`@*gB};>KBKvLlSntTwqi*ka&L16cAtz2VnSnct&ZhK?Sa^}<^X}b?QUC9> zceoh4t@&);xjueYPnTdm_Glg|ChsmTS=_t3coFfkX`czv=N!w`yNcdxSqbyPKk9|S z8fcoc2>TBgvko<@HNdPpvhQCI{l=47xBQ`xUr(Xyx=H=I@2^j!wtaZ1J@Ey82XP{E z6Rx955B)_e)S)5!>1$ZTf*>Yna|nx`VZm(`reXx2^MT3o8VNsee960q)S zcU+&9xIQ8g;WoGvCMrrtgmCMYt*Bgte^*#HTPh_}N?6}+VwcDtFnJhYHbg%(#*TSKj zZ`dyxL5q23NvDYv8~`AlrM$N|b%)k4NEHuEmjdkkUj8D$6HmrKHX=2u?lzSczlP+yR_`U0}$ z;a33(|3|kFPUO{mpgXu+MTfeBIg`CB^We&*I?A-T_ib-bQ+f8S$%A+w9fBxG$NuZt z0!IL_I|e)%i^{{$!VSs8?6JH1ZJpe5kFb1*_~qBi?m#Gha#FIc4h4`AsG;dYVXtm1es?B}lm4N|3sF zwp{e&C=k(SfYMy#T1TiEv^RX)wfsofTklsiMJ%%8~Yqw=;Bk`)2b?Z$U83-m=4(pVhZt-qXmm3^{XK80FI= zvEhEibdSG4q$Nm@A9J#C=WbxC1pS$*LNbbfKyOorV7EEb2nCT0ML!k3L3xOoL@8bf z&3?_A;W(AgV%KTw!k&b`oh9;bW{@9UUrtP~``VvM;NIZwlXt8V!=jW?G@Y;=lKEjD zF$f*6xsnrctQJ%;Z&X393facTCq3DVf#w-SMf zc+Hs4COW?+FrT;%<`ZCwoqKNl2WRBhh`d~Pa4k(U@mDcG{*?plT+9HOx3K}{^Mh+N zz&y;g4bTd0fpHC34ddkPf39(oh#(9!zm_=4!L?alT4hu~P)*je6=j(AIavS5Ee<8=o{ZT*i1qVvlOK-pa>lkpfjrp(EMuPE3VGxWQD@l(><2)!0Mj)AJQr1KIB}!?pD^O zbNSUbPtWLsP0d&G%?#G7%Pl=R%>${qLpjbEtZ%7RbXeIY230G?2}-{Fq@Y_VSxfK8&QR#IEnF~3fmW<3crs= zFB@DT8o5jwkp)@Mh?j}Lt`NapVK(m-2uS|HP-cV3;*i}4NI@By@2cc1G>8`!R2$)} zHD-!nH{D8KYe>>36VQ_M4fEHlSQJ*nH-a`N#Y_^#T`6;IFhN;!Iz2Zh>#EaWh0fCE z2m#BU8P$T0%}24`IEwtztc-n?loH>ka7-X6BPbt;xZfY{(PD_Uyl8O;x#r zI+Z)9?|ngx?((BrUu%RI6KiPravY*&r}Y0oOBva;JrRx~QPybg8;&F1_s%*~kD{*C zT}RL?r_qr57v$hkWn!BT_4veCKd8^y8ozn2SSlyU#J1*K8;$(f~%vKYZ! z_M0zV=N*pT`KVaD*mgi#e75gUWVf7>S?Ppa#zmTjw{b$YyyhwYj(Z+Mq*JT(>zU_q zqVdan9xeQdqzC?gcpf`t6+DlvVb5c$@;r9u@lfJ<r>=P( zv6;>D*uxhw&HR)dgo2PD7G zrgUL&nb>So`m%x}a+}gE)Gg+B7xU^ zmyq8&&}?2pDw&ti>Qr7rt6?HsJ9(v-uxnmItLeRjp_+LK@k`!XyjU%>JT3i!_h^|~ zElmw$_%B`LQwv^rMjyLOqTo#un*l=<8;!jK6qevAx9);y3T{zf#sDj=c(v1qX) zrdZR~9{TFPo-nB?hBw%A2f<%cyP-)M%BS`e>%VWh!#b-E2kVHt0YOxQ+@-)PcWE!5 zL9u7lHYo)Tt*PTZ46MT} z>bXxN2%Fl~tCw%ktpN6U5SOG}zlDaF zy5(dE|3~frwK~VNFFO6L$suh6`+A4cp?X#MN)EsZS^CVV8|@Eiam7Ap8!$)lpRlw6!py(ZFt3u=)CaWWowP6h4(HOz2Ha=?KXa@ z-STJEEj!eq25VDhL4n{=@#4J4#}cQlX3Q5mBTs5KfAKBIz%IU6Nfum!)YeDI zAre>;HR{o4CPD%5qw>XCV4H%B^9;gbd4oj&{HNQjYgxz z08ALQkO9+yDL+%9m>I1SafYzh46EC`E2|t0DCAD^6X`G^FM7bvgre>Ut`O{ru;Z;y zbC=EtM-w2mW$se&yfnZYL1H-1bGR8(tOlULaXYw%f8ZnU3cbcoSPveWwE-Gf?BbxI zxd4yWtOV6*q+Nm4La}(&fkpmx@pQh zHX8$ZW>6nv3;|^}7=oUL8G_U*^0aO8P~<(_!f-}3cu78EK`e}oxENOUCKGtw%LGzu z)G|TB0GS}sU@qe4Booihw zZ<-ORks^vHG=HM#HVl%t8-rXI1m6oXNGveO*f{tl@((l4Z$vcnk8#eKY{4unh1+>& z6M7vV=Q`VD%dcgeqH#Y?71}u2K`F<{)5bXGUdVCcA;O6g#);rzobtEv&nOzH#g^iQ zkFyl&l^EB2&Ox8dr-}UV*N;^XY^)8!rG>^=OJ(+g%xR^t;`}9&IgE9)EPKV|$ghn>EnKwSnnwmvlc$I8M)Qr$#mI4sG#H(b{!b?U}GG)&Pf|vNSqc4#%?pMm# zxPUEh)U!7VfZ-Z5mac(5!G(+iZD%%(rHeKWO5;SUO?DP;p4JlFqu`ohYe0%r|GenI z{Xr@hGeU<$5Hy*unZ7h|Ms-HyYHvbdTwZNVVLU4XS5i7uW@k9@G^Q(;?KC2`@t94@ zkpzr9wBs$)bG=J5xAIoSN<6k&&G9&j)oPAg`K#5e4;qwC^o5G&>#d;iT3XB=vh9?z z*7$oDQX=zsM8<$-I5zx6o=tb{uo4-^!_;d8ci!Sp=RoE|83d{xbAC^vGivh*a6FX>&Au;U>n8cdP#!3~UdSVjmsyF1a?bh2GUE9`>r_P+?-djgn z5Wh?$TJ@BChavRzq`srW=*W;#J`TaG_PFfv%CaibI2Q!ckbF>#XTVwwA+mL=l3Xlj zj>*eS`05dT<~Jmd>GR4P61?9U6Q*$3xKLxu*;3eg=w&}c#@29`3`mF_K8xMk@wX_; z2(FfxyhA{v!15h9;fxySM0p}TLb1)GgVZ*3|L_tzDB+#eN^U8-J!&%sJ z`~YjhYLTvq1{swO6R;c}s!C5*>`zYE(Ldhd_VyLQu;X9@ z?sW{%NS0L4Y>uh}Q=xZYycO37`AYv9Wbk}3tz4lpvtvo~{Fh}Te!9$Y!f_#w%amc8CA zFIKGmQ}{i%Es7a*I+Vgtw0}O7Dj~LHfXsC#d!ugAFycx)amfiL^0HfUyq+$BQc{hf zc9?%1wt+^cZWbS=fIy{Y%Ej!;hz2?K*dh)i0(+5rT-?YZ=FhMt9A)p z1-88!0!Mf$l~)_y(MQ_?A&f4%o1-bm*=chG;9`U&+!5pOlh!iP>|F6cydq3ayaCTg z5CB!{B>}toQ}=U!dK{2esbQ|WMrKLNdlP_G{{U@pea7IT_6k5qkt-8KtQ^VYGE-Hb zX|2174D0~d&iTf*@tnA$!Yqf|G^HF>Q|dOL@Bh);K${UxfVL_@aoUD(14h+ja@?sW z{lNIKet1Te8jd-=DQf4C(M^#K^i}5$qwJlr5esut#M)o9d4WV2hkhYx8Mlh11e0o( zykqUY_xfI;kCEvxgUeTX)-o6w5sg(6p^*)hb9`{n_UxFfl$vhRjh=+aVCw;^=%$@) zEn$MsYRb{z6z@ZFyqbbIoj3*`RV31U%X$Yy+AHrf3bFS`pw-PT)JGTD3tt+FZU)Kk(?E-T|V`({%@SVx!= zzG+GjC1dDgS;}4J3=~vpU@l3i!RE9*v1BHkDlX5bQl2Q zYBmZFoPQ+7*6y8O@I)b%F+vn9W;!)(CE3q6uBNE%IcsKsm@v$o^E|v|#6yMButdu0 z)>p_hU)g@o&H57ioV%YF#>$fbtCC(t+3&ZK$-OO7)L7?QdI#iCqTG!ILS$zVUmqX( zJnVp#tCOK^cYy1y;RBbEG1eg&b$^l4))R+@Zg5ta3}@t3n%%w)Z{DdN8pJ6)!qH&9 zYZHuWTtcLKwI|9#We+>`quQC`3M54(>$LA6&Zp>3#x~&*lkP)b1`hjWx7lNdXj!Ev z95Z5X(l)pRp#J})Dpv=XefmJHVV5WL|6}$v_*(#<_(={;spIq^JI|y2w#06fDFa#n z=}vMjvt@G3{SzXk#5x|z zZlv?l`OGV1o+OM0#M77@b1fk9uCb90jCi%mwNCKAaKOLn#H3?VwI~BVQH$(DVgyE- zXDmFldCUKxAEgCbRx)~Eo-edk4QKmH0QfDGs~2j$g_rfo!Ygrha4*aLu+=ROIQv7- z@g1HZG^si&n0o3aMr^*-Z%C7tlmFz<90at)+q9-2NK5yj z8xW64+pbeRP?Mm9?lc5L0Mr0YBP{P?7`x?f@T1ZVUE&+w1Z5nP24^x4$XCb_XFGuj zxJ-`_rz^hb*PUt(6;!riBx{&

+;&+iWmk(JU~Cm1LTfbPGu-ae}hKqYng$viPby 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⁣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 Phd&-L&5(?a5*z*xJN!%X literal 0 HcmV?d00001 diff --git a/frontend/src-tauri/src/cowork/desktop_skills/desktop_skill_run.rs b/frontend/src-tauri/src/cowork/desktop_skills/desktop_skill_run.rs new file mode 100644 index 000000000..cb7122137 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_skills/desktop_skill_run.rs @@ -0,0 +1,146 @@ +//! `desktop_skill_run` — skill body loader tool. +//! +//! This tool exposes the full markdown body of a desktop skill to the +//! backend agent. The LLM calls it with a skill name; the tool returns +//! the raw `SKILL.md` body that lives bundled inside the desktop binary. +//! The body describes the skill's supported operations, the exact +//! `wasm_run` invocations the agent should use, and any fallback +//! guidance when the backing WASM module is not registered. +//! +//! ## Execution model +//! +//! `desktop_skill_run` **never touches the WASM runtime**. It is a pure +//! text-load tool: input is a skill name, output is the body markdown. +//! After reading the body, the agent decides whether to call `wasm_run` +//! (the executor tool in [`crate::cowork::desktop_tools::wasm_run`]) +//! with the shape the body recommends, or to fall back to host tools, +//! or to tell the user the task is not supported. +//! +//! This separation keeps two concerns independent: +//! +//! * **Guidance** (who decides what to do) — owned by skills, surfaced by +//! this tool. +//! * **Execution** (who runs the isolated code) — owned by `wasm_run` + +//! the desktop WASM runtime. +//! +//! The backend agent is expected to follow a two-step pattern for any +//! skill-driven task: +//! +//! 1. Call `desktop_skill_run(skill_name="pdf")` to load the pdf skill's +//! body into context. +//! 2. Follow the body's instructions — usually that means calling +//! `wasm_run(module=..., input_json=..., input_files=...)` with the +//! exact shape documented there. + +use super::find_builtin_skill; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use crate::cowork::desktop_tools::{DesktopTool, DesktopToolContext}; +use serde_json::{json, Value}; + +pub const TOOL_DESKTOP_SKILL_RUN: &str = "desktop_skill_run"; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_DESKTOP_SKILL_RUN.to_string(), + aliases: Vec::new(), + display_name: "Load a desktop skill body".to_string(), + description: "Load the full markdown body of a desktop skill by name. Use this BEFORE attempting to process a complex document format (pdf, docx, xlsx, pptx) so you can read the skill's decision tree and the exact wasm_run invocation it recommends. This tool does NOT execute anything — it only returns the skill's instructions. After reading the body, call wasm_run yourself with the shape the body documents. If the body reports that the backing WebAssembly module is not yet registered, fall back to filename-level operations and tell the user what is not supported.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "skill_name": { + "type": "string", + "description": "Name of the desktop skill to load. Known names: 'pdf', 'docx', 'xlsx', 'pptx'." + } + }, + "required": ["skill_name"] + }), + }, + execute, + false, + ) +} + +fn execute(_ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let skill_name = tool_input + .get("skill_name") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .ok_or_else(|| "desktop_skill_run requires a 'skill_name' string".to_string())?; + + let skill = find_builtin_skill(skill_name).ok_or_else(|| { + format!( + "desktop_skill_run: unknown skill '{skill_name}'. Known skills: pdf, docx, xlsx, pptx." + ) + })?; + + let runtime_label = skill.runtime().as_str(); + let module_label = skill + .wasm_module() + .map(|name| format!("\nwasm_module: {name}")) + .unwrap_or_default(); + + Ok(format!( + "# Skill: {name}\n\ +description: {description}\n\ +runtime: {runtime}{module_label}\n\ +\n\ +Follow the instructions below. When the body tells you to call wasm_run, use the exact shape documented. Do not invent parameters the body does not mention.\n\ +\n\ +---\n\ +\n\ +{body}", + name = skill.name(), + description = skill.description(), + runtime = runtime_label, + module_label = module_label, + body = skill.body(), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn desktop_tool_descriptor_is_wellformed() { + let tool = desktop_tool(); + assert_eq!(tool.name(), TOOL_DESKTOP_SKILL_RUN); + assert!(!tool.display_name().is_empty()); + let cap = tool.clone().into_capability(); + let schema = cap.input_schema.as_object().expect("schema is object"); + let required = schema + .get("required") + .and_then(Value::as_array) + .expect("required list"); + assert_eq!(required.len(), 1); + assert_eq!(required[0].as_str(), Some("skill_name")); + } + + // The tool's execute() signature needs a DesktopToolContext which + // carries an AppHandle. Rather than mocking the full Tauri surface + // here, we test the core lookup logic directly against the skill + // registry — that exercises the only path execute() actually runs + // for a valid skill name. + #[test] + fn loads_pdf_skill_body() { + let skill = find_builtin_skill("pdf").expect("pdf skill registered"); + assert_eq!(skill.name(), "pdf"); + assert!(!skill.body().is_empty(), "pdf body must not be empty"); + let lower = skill.body().to_ascii_lowercase(); + assert!( + lower.contains("wasm_run") || lower.contains("pdf_processor"), + "pdf body must reference wasm_run or pdf_processor so the agent knows how to invoke it" + ); + } + + #[test] + fn unknown_skill_is_reported_clearly() { + // This mirrors the error branch in execute() without needing a + // DesktopToolContext: if find_builtin_skill returns None, we + // should produce a helpful error listing known skill names. + assert!(find_builtin_skill("no-such-skill").is_none()); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_skills/docx.skill.md b/frontend/src-tauri/src/cowork/desktop_skills/docx.skill.md new file mode 100644 index 000000000..0db11ec86 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_skills/docx.skill.md @@ -0,0 +1,83 @@ +--- +name: docx +description: Desktop Word document handling via an isolated WebAssembly module. Supports text extraction and paragraph counting. +version: 0.1.0 +runtime: wasm +wasm_module: docx_processor +--- + +You are about to work with a Word document inside the selected desktop +folder. Read this body, then pick the operation that matches the task. + +# When to use this skill + +Use when the task requires reading the body text of a `.docx` file. +Do NOT use for plain text/md/txt files (use `Read` directly), or for +tasks that only care about filenames (use `glob`/`list_dir`). + +# Host first, sandbox second + +1. Use `glob` / `list_dir` to find the `.docx` files. +2. If the task only needs filenames or folder structure, answer from + host tools and stop. +3. If you need the document content, call `wasm_run` below. +4. Never try to `Read` or `Edit` a `.docx` file directly — it is a + zipped OOXML container and any byte-level operation will corrupt it + or produce gibberish. + +# Operations + +## extract_text + +Extract all paragraph text in reading order. + +``` +wasm_run({ + "module": "docx_processor", + "input_json": { "op": "extract_text" }, + "input_files": [{ "path": "", "name": "input.docx" }] +}) +``` + +Response `output.json`: +``` +{ "paragraphs": ["...", "..."], "paragraph_offset": 1, "total_paragraphs": N } +``` + +### Optional paragraph_range + +``` +wasm_run({ + "module": "docx_processor", + "input_json": { "op": "extract_text", "paragraph_range": [1, 100] }, + "input_files": [{ "path": "", "name": "input.docx" }] +}) +``` + +Out-of-range requests are clamped. The host automatically chunks large +files and merges results — you always see one combined array. + +## count_paragraphs + +``` +wasm_run({ + "module": "docx_processor", + "input_json": { "op": "count_paragraphs" }, + "input_files": [{ "path": "", "name": "input.docx" }] +}) +``` + +Response: `{ "total_paragraphs": N }` + +# Error handling + +- If `wasm_run` reports a memory limit error, the docx is too large. + Report this to the user. +- If `wasm_run` reports `docx parse failed`, the file is corrupt. +- Never try to "fix" a .docx file with `Edit` — you will corrupt it. + +# What to return to the user + +- Files inspected and operations run. +- Relevant paragraph text or summaries. +- Any errors encountered with file paths. diff --git a/frontend/src-tauri/src/cowork/desktop_skills/mod.rs b/frontend/src-tauri/src/cowork/desktop_skills/mod.rs index 4e36da41c..1a25d2fdb 100644 --- a/frontend/src-tauri/src/cowork/desktop_skills/mod.rs +++ b/frontend/src-tauri/src/cowork/desktop_skills/mod.rs @@ -1,27 +1,381 @@ +//! Desktop skills: first-class local capabilities the agent can reason about. +//! +//! A *skill* is a packaged piece of guidance that tells the agent how to +//! approach a class of tasks (for example, "process a PDF file"). Unlike a +//! tool, a skill is not invoked directly by the model — it surfaces to the +//! agent as a descriptor (name + short description) that the backend +//! agent reads before planning, and the full body is available for the +//! agent to pull in when it decides the skill is relevant. +//! +//! ## Package layout +//! +//! The desktop app owns skill storage. Built-in skills live as Markdown +//! files in `assets/desktop_skills/` and are bundled into the binary via +//! [`include_str!`]. Each skill file has YAML-like frontmatter and a +//! Markdown body: +//! +//! ```markdown +//! --- +//! name: pdf +//! description: Isolated PDF processing on the desktop runtime. +//! version: 0.1.0 +//! triggers: pdf document processing, pdf text extraction +//! runtime: wasm +//! wasm_module: pdf_processor +//! tools: Read, wasm_run +//! --- +//! +//! # PDF Skill +//! +//! ... +//! ``` +//! +//! All frontmatter fields except `name` and `description` are optional. +//! `runtime` picks between `host` (the skill is host-tool driven) and +//! `wasm` (the skill expects an isolated runtime call through +//! `wasm_run`). +//! +//! ## Registry +//! +//! [`common_desktop_skills`] returns the canonical set of built-in +//! skills. Callers that want mode-specific additions merge their own +//! lists, just like the desktop tool layer does. Skill discovery and +//! metadata parsing happen at this layer — downstream code should never +//! parse the raw Markdown directly. +//! +//! ## Tool surface +//! +//! The desktop skill module also owns the `desktop_skill_run` tool +//! (see [`desktop_skill_run`]). That tool lives here — not in the +//! generic `desktop_tools/` folder — because its only job is to load a +//! skill body into the agent's context. Execution of the isolated +//! runtime stays in [`crate::cowork::desktop_tools::wasm_run`]. + +#![allow(dead_code)] + +pub mod desktop_skill_run; + use crate::cowork::agent_presets::shared::DesktopSkillCapability; -#[derive(Clone)] +/// Target runtime for a desktop skill. Guides the agent on when to reach +/// for isolated execution versus normal host tools. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SkillRuntime { + /// The skill expects to drive the host tools (Read/Write/Edit/Bash). + Host, + /// The skill expects to call into the WASM runtime via `wasm_run`. + Wasm, + /// The skill may use either runtime depending on the task. + Mixed, +} + +impl SkillRuntime { + fn parse(value: &str) -> Option { + match value.trim().to_ascii_lowercase().as_str() { + "host" => Some(Self::Host), + "wasm" => Some(Self::Wasm), + "mixed" | "host+wasm" | "wasm+host" => Some(Self::Mixed), + _ => None, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + SkillRuntime::Host => "host", + SkillRuntime::Wasm => "wasm", + SkillRuntime::Mixed => "mixed", + } + } +} + +/// Parsed skill frontmatter. All optional fields are surfaced so tests +/// can inspect them directly. +#[derive(Debug, Clone)] +pub struct SkillFrontmatter { + pub name: String, + pub description: String, + pub version: Option, + pub triggers: Vec, + pub runtime: SkillRuntime, + pub wasm_module: Option, + pub tools: Vec, +} + +/// A fully loaded desktop skill: frontmatter + Markdown body. +#[derive(Debug, Clone)] pub struct DesktopSkill { - capability: DesktopSkillCapability, + pub frontmatter: SkillFrontmatter, + pub body: String, } impl DesktopSkill { pub fn name(&self) -> &str { - self.capability.name.as_str() + self.frontmatter.name.as_str() + } + + pub fn description(&self) -> &str { + self.frontmatter.description.as_str() + } + + pub fn runtime(&self) -> SkillRuntime { + self.frontmatter.runtime } + pub fn wasm_module(&self) -> Option<&str> { + self.frontmatter.wasm_module.as_deref() + } + + pub fn tools(&self) -> &[String] { + &self.frontmatter.tools + } + + pub fn body(&self) -> &str { + self.body.as_str() + } + + /// Consume the skill and return only its capability descriptor (the + /// part that is sent to the backend agent). pub fn into_capability(self) -> DesktopSkillCapability { - self.capability + DesktopSkillCapability { + name: self.frontmatter.name, + description: self.frontmatter.description, + } } } +/// Parse a Markdown document with YAML-like frontmatter into a skill. +/// +/// The parser accepts the minimal subset of frontmatter used by desktop +/// skills: +/// +/// * A document beginning with `---\n`. +/// * `key: value` pairs until the next `---` line. +/// * Comma-separated values for `triggers` and `tools`. +/// * A Markdown body after the closing `---`. +pub fn parse_skill_document(source: &str) -> Result { + let mut lines = source.lines(); + let first = lines + .next() + .ok_or_else(|| "skill document is empty".to_string())?; + if first.trim() != "---" { + return Err("skill document must start with '---' frontmatter".to_string()); + } + + let mut frontmatter_lines: Vec<&str> = Vec::new(); + let mut closed = false; + for line in lines.by_ref() { + if line.trim() == "---" { + closed = true; + break; + } + frontmatter_lines.push(line); + } + if !closed { + return Err("skill document is missing a closing '---'".to_string()); + } + + let mut name: Option = None; + let mut description: Option = None; + let mut version: Option = None; + let mut triggers: Vec = Vec::new(); + let mut runtime: SkillRuntime = SkillRuntime::Host; + let mut wasm_module: Option = None; + let mut tools: Vec = Vec::new(); + + for raw in frontmatter_lines { + let line = raw.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + let (key, value) = line + .split_once(':') + .ok_or_else(|| format!("invalid frontmatter line '{}'", raw))?; + let key = key.trim().to_ascii_lowercase(); + let value = strip_quotes(value.trim()); + match key.as_str() { + "name" => name = Some(value.to_string()), + "description" => description = Some(value.to_string()), + "version" => version = Some(value.to_string()), + "triggers" => triggers = split_list(value), + "runtime" => { + runtime = SkillRuntime::parse(value).ok_or_else(|| { + format!( + "unknown runtime '{}'. Expected host, wasm, or mixed", + value + ) + })?; + } + "wasm_module" => wasm_module = Some(value.to_string()), + "tools" => tools = split_list(value), + "license" => { /* ignored but accepted for backward compatibility */ } + other => { + return Err(format!("unknown frontmatter key '{}'", other)); + } + } + } + + let name = name.ok_or_else(|| "skill frontmatter is missing 'name'".to_string())?; + let description = description + .ok_or_else(|| "skill frontmatter is missing 'description'".to_string())?; + + let body: String = lines.collect::>().join("\n"); + + Ok(DesktopSkill { + frontmatter: SkillFrontmatter { + name, + description, + version, + triggers, + runtime, + wasm_module, + tools, + }, + body: body.trim_start_matches('\n').to_string(), + }) +} + +fn strip_quotes(value: &str) -> &str { + let value = value.trim(); + if value.len() >= 2 { + let bytes = value.as_bytes(); + let first = bytes[0]; + let last = bytes[value.len() - 1]; + if (first == b'"' && last == b'"') || (first == b'\'' && last == b'\'') { + return &value[1..value.len() - 1]; + } + } + value +} + +fn split_list(value: &str) -> Vec { + value + .split(',') + .map(|piece| piece.trim().to_string()) + .filter(|piece| !piece.is_empty()) + .collect() +} + +// ----------------------------------------------------------------------- +// Built-in skill registry +// ----------------------------------------------------------------------- + +/// Raw Markdown source for the built-in skills. Bundled into the binary +/// at compile time so the desktop app does not depend on any runtime +/// filesystem layout. Skill files live in +/// `src-tauri/assets/desktop_skills/` and are edited as plain Markdown. +const BUILTIN_PDF_SKILL: &str = include_str!("pdf.skill.md"); +const BUILTIN_DOCX_SKILL: &str = include_str!("docx.skill.md"); +const BUILTIN_XLSX_SKILL: &str = include_str!("xlsx.skill.md"); +const BUILTIN_PPTX_SKILL: &str = include_str!("pptx.skill.md"); + +/// Canonical set of built-in desktop skills. pub fn common_desktop_skills() -> Vec { - Vec::new() + [ + BUILTIN_PDF_SKILL, + BUILTIN_DOCX_SKILL, + BUILTIN_XLSX_SKILL, + BUILTIN_PPTX_SKILL, + ] + .into_iter() + .map(|source| { + parse_skill_document(source) + .unwrap_or_else(|error| panic!("built-in skill failed to parse: {error}")) + }) + .collect() } +/// Descriptors for all built-in desktop skills (the lightweight shape +/// that gets shipped to the backend agent). pub fn common_desktop_skill_capabilities() -> Vec { common_desktop_skills() .into_iter() .map(DesktopSkill::into_capability) .collect() } + +/// Look up a built-in desktop skill by name. Returns `None` if the name +/// does not match any registered skill. Used when the agent asks for the +/// body of a skill by name. +pub fn find_builtin_skill(name: &str) -> Option { + let normalized = name.trim().to_ascii_lowercase(); + common_desktop_skills() + .into_iter() + .find(|skill| skill.name().eq_ignore_ascii_case(&normalized)) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE: &str = "---\n\ +name: sample\n\ +description: A sample skill used only in tests.\n\ +version: 0.1.0\n\ +triggers: foo, bar, baz\n\ +runtime: wasm\n\ +wasm_module: sample_module\n\ +tools: Read, wasm_run\n\ +---\n\ +\n\ +# Sample\n\ +\n\ +Body text.\n"; + + #[test] + fn parses_frontmatter_and_body() { + let skill = parse_skill_document(SAMPLE).expect("parses"); + assert_eq!(skill.name(), "sample"); + assert_eq!(skill.description(), "A sample skill used only in tests."); + assert_eq!(skill.runtime(), SkillRuntime::Wasm); + assert_eq!(skill.wasm_module(), Some("sample_module")); + assert_eq!(skill.tools(), &["Read".to_string(), "wasm_run".to_string()]); + assert_eq!( + skill.frontmatter.triggers, + vec!["foo".to_string(), "bar".to_string(), "baz".to_string()] + ); + assert_eq!(skill.frontmatter.version.as_deref(), Some("0.1.0")); + assert!(skill.body().contains("Body text.")); + } + + #[test] + fn rejects_missing_name() { + let source = "---\ndescription: nope\n---\nBody\n"; + let error = parse_skill_document(source).unwrap_err(); + assert!(error.contains("missing 'name'"), "got: {}", error); + } + + #[test] + fn rejects_bad_runtime() { + let source = "---\nname: x\ndescription: y\nruntime: lua\n---\nBody\n"; + let error = parse_skill_document(source).unwrap_err(); + assert!(error.contains("unknown runtime"), "got: {}", error); + } + + #[test] + fn builtin_skills_parse() { + let skills = common_desktop_skills(); + let names: Vec<_> = skills.iter().map(|s| s.name().to_string()).collect(); + assert_eq!(names, vec!["pdf", "docx", "xlsx", "pptx"]); + for skill in &skills { + assert!(!skill.description().is_empty()); + assert!(!skill.body().is_empty()); + } + } + + #[test] + fn builtin_skills_expose_capabilities() { + let caps = common_desktop_skill_capabilities(); + assert_eq!(caps.len(), 4); + for cap in &caps { + assert!(!cap.name.is_empty()); + assert!(!cap.description.is_empty()); + } + } + + #[test] + fn find_builtin_skill_is_case_insensitive() { + assert!(find_builtin_skill("PDF").is_some()); + assert!(find_builtin_skill("pdf").is_some()); + assert!(find_builtin_skill("nope").is_none()); + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_skills/pdf.skill.md b/frontend/src-tauri/src/cowork/desktop_skills/pdf.skill.md new file mode 100644 index 000000000..f03464972 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_skills/pdf.skill.md @@ -0,0 +1,157 @@ +--- +name: pdf +description: Desktop PDF processing via an isolated WebAssembly module. Supports plain text extraction and basic metadata. +version: 0.1.0 +runtime: wasm +wasm_module: pdf_processor +--- + +You are about to work with a PDF file inside the selected desktop folder. +Read this entire body, then pick the section that matches the user's +request and copy the `wasm_run` call shape exactly. + +# When to use this skill + +Use it when the task requires reading text, metadata, or structural +information from a `.pdf` file. Do not use it for: + +- plain text, markdown, code, or csv files — use `Read` directly +- moving or renaming pdf files by filename — use `Bash` or `glob` +- tasks where only the filename matters — stay on the host + +# Host first, sandbox second + +Always do cheap host work first and only reach into the sandbox when you +actually need the pdf content: + +1. Use `list_dir` / `glob` to find the pdf files you care about. +2. If the user only cares about filenames or folder structure, answer + from host tools and stop. +3. If you need the pdf's content (to summarise, translate, classify, + extract fields, answer questions about it) call `wasm_run` with one + of the shapes below. +4. Read the result from `output.json` and continue reasoning in the + agent turn. + +The isolated runtime enforces memory, fuel, and wall-clock limits for +you. Trust them — do not try to pre-chunk unless a previous call came +back with an out-of-memory error. + +# Operations + +## extract_text + +Extract one string per page in reading order. Use this for +summarisation, content search, translation, or any task that needs the +raw text of the document. + +Call it with: + +``` +wasm_run({ + "module": "pdf_processor", + "input_json": { "op": "extract_text" }, + "input_files": [ + { "path": "", "name": "input.pdf" } + ] +}) +``` + +The `name` field must be `"input.pdf"` — the pdf_processor module only +reads `/workspace/inputs/input.pdf`. + +On success the tool result contains an `output.json` block of the form: + +``` +{ + "pages": ["page 1 text", "page 2 text", ...], + "page_offset": 1, + "total_pages": +} +``` + +`page_offset` is the 1-based page number of the first entry in `pages`; +when you did not pass a `page_range` it will always be `1`. `total_pages` +is the document's full page count. Join the pages yourself if you need a +single flat string — each page already ends with a newline. + +### Optional page_range + +You may pass `page_range: [start, end]` (1-based, inclusive) inside +`input_json` to extract a subset. Out-of-range requests are clamped: if +a document has 180 pages and you ask for `[201, 250]`, the guest returns +`{"pages": [], "page_offset": 201, "total_pages": 180}` without error. + +Use this only when the user explicitly asks for a specific slice — you +do **not** need to chunk large PDFs by hand. The host automatically +splits very large files into sequential chunks and merges the results +back before returning to you, so you always see a single combined +`pages` array in the tool result (plus a `chunk_summary` section +reporting how many chunks were run). + +``` +wasm_run({ + "module": "pdf_processor", + "input_json": { "op": "extract_text", "page_range": [1, 5] }, + "input_files": [ + { "path": "", "name": "input.pdf" } + ] +}) +``` + +## metadata + +Get the document's title, author, and page count. Use this for +inventory reports, sorting, or to decide whether a document is worth +extracting fully. + +``` +wasm_run({ + "module": "pdf_processor", + "input_json": { "op": "metadata" }, + "input_files": [ + { "path": "", "name": "input.pdf" } + ] +}) +``` + +`output.json` will look like: + +``` +{ "title": "", "author": "", "page_count": } +``` + +# Error handling + +- If `wasm_run` reports `unknown module 'pdf_processor'`, the pdf + runtime has not been shipped with this build. Tell the user that pdf + content extraction is unavailable in this version and offer + filename-level operations instead. +- If `wasm_run` reports a memory limit error, the pdf is too large to + process in a single call. Tell the user the file is oversized and + suggest splitting it manually. Page-range chunking is not yet + supported by the module. +- If `wasm_run` reports an execution error mentioning `load failed` or + `invalid file trailer`, the file is corrupt or not a real pdf. Do not + retry — report the file path and move on. +- Never try to "fix" a pdf by using `Edit` or `Write` on it. pdf is a + binary format and any byte-level edit will corrupt it. + +# Multi-file workflow + +For batch jobs (for example "summarise every pdf in this folder"): + +1. `glob` pattern `**/*.pdf` to list the files. +2. Call `wasm_run` with `op: extract_text` once per file. +3. Between calls, keep per-file results in your reasoning — do not try + to pass multiple pdfs into a single wasm_run call. +4. When you have all the text, compose the final answer. + +# What to return to the user + +Always tell the user: + +- how many files you inspected +- which operation(s) you ran against each +- a short summary of the extracted content, grouped by file path +- any files that failed, with the exact error message diff --git a/frontend/src-tauri/src/cowork/desktop_skills/pptx.skill.md b/frontend/src-tauri/src/cowork/desktop_skills/pptx.skill.md new file mode 100644 index 000000000..2a062d793 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_skills/pptx.skill.md @@ -0,0 +1,83 @@ +--- +name: pptx +description: Desktop presentation handling via an isolated WebAssembly module. Supports slide text extraction and slide counting. +version: 0.1.0 +runtime: wasm +wasm_module: pptx_processor +--- + +You are about to work with PowerPoint files inside the selected desktop +folder. Read this body, then pick the operation that matches the task. + +# When to use this skill + +Use when the task requires reading slide text from a `.pptx` file. +Do NOT use for plain text/md/txt files (use `Read` directly) or for +tasks that only care about filenames (use `glob`/`list_dir`). + +# Host first, sandbox second + +1. Use `glob` / `list_dir` to find `.pptx` files. +2. If the task only needs filenames → answer from host tools. +3. If you need slide content → call `wasm_run` below. +4. Never `Read` or `Edit` a `.pptx` file — it is a zip container. + +# Operations + +## extract_text + +Extract all slide text in slide order. + +``` +wasm_run({ + "module": "pptx_processor", + "input_json": { "op": "extract_text" }, + "input_files": [{ "path": "", "name": "input.pptx" }] +}) +``` + +Response `output.json`: +``` +{ + "slides": [{"index": 1, "text": "..."}, {"index": 2, "text": "..."}, ...], + "slide_offset": 1, + "total_slides": N +} +``` + +### Optional slide_range + +``` +wasm_run({ + "module": "pptx_processor", + "input_json": { "op": "extract_text", "slide_range": [1, 10] }, + "input_files": [{ "path": "", "name": "input.pptx" }] +}) +``` + +Out-of-range requests are clamped. Large files are automatically chunked +by the host. + +## count_slides + +``` +wasm_run({ + "module": "pptx_processor", + "input_json": { "op": "count_slides" }, + "input_files": [{ "path": "", "name": "input.pptx" }] +}) +``` + +Response: `{ "total_slides": N }` + +# Error handling + +- Memory limit error → file too large, report to user. +- `pptx zip open failed` → corrupt file, do not retry. +- Never try to edit a `.pptx` with `Edit`. + +# What to return to the user + +- Files inspected and operations run. +- Per-slide text summaries when applicable. +- Any errors encountered with file paths. diff --git a/frontend/src-tauri/src/cowork/desktop_skills/xlsx.skill.md b/frontend/src-tauri/src/cowork/desktop_skills/xlsx.skill.md new file mode 100644 index 000000000..546375c3c --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_skills/xlsx.skill.md @@ -0,0 +1,98 @@ +--- +name: xlsx +description: Desktop spreadsheet handling. CSV/TSV tasks use host tools directly. XLSX tasks use the isolated xlsx_processor WebAssembly module. +version: 0.1.0 +runtime: mixed +wasm_module: xlsx_processor +--- + +You are about to work with spreadsheet files inside the selected desktop +folder. Read this body carefully — this skill splits cleanly between a +host path (CSV/TSV) and a sandbox path (XLSX) and the two should never +be confused. + +# Decision tree + +1. Is the file `.csv` or `.tsv`? + → Use `Read` and `Edit` directly. These are plain text files. Do + **not** call `wasm_run`. +2. Is the file `.xlsx` or `.xlsm`? + → Content operations need the `xlsx_processor` WebAssembly module. + That module is **not shipped** in this build (see below). +3. Is the task only filename-level? + → Use `glob` / `list_dir` / `Bash` for both csv and xlsx. + +# CSV / TSV — host path (works today) + +Treat these as plain text. Typical tasks: + +- `Read` a csv to show the first few rows +- `Edit` or `Write` to modify cells +- `grep` for a column value across many csv files +- `Bash` for batch rename or move + +Never try to run a csv through `wasm_run` — there is no value in +isolating plain text parsing. + +# XLSX / XLSM — sandbox path (shipped) + +## list_sheets + +Get an inventory of all sheets in the workbook with row/col counts. + +``` +wasm_run({ + "module": "xlsx_processor", + "input_json": { "op": "list_sheets" }, + "input_files": [{ "path": "", "name": "input.xlsx" }] +}) +``` + +Response: `{ "sheets": [{"name": "Sheet1", "rows": 500, "cols": 12}, ...] }` + +## read_sheet + +Read all cell values from a named sheet. + +``` +wasm_run({ + "module": "xlsx_processor", + "input_json": { "op": "read_sheet", "sheet": "" }, + "input_files": [{ "path": "", "name": "input.xlsx" }] +}) +``` + +Response: `{ "sheet": "...", "rows": [[cell, ...], ...], "row_offset": 1, "total_rows": N }` + +### Optional row_range + +``` +wasm_run({ + "module": "xlsx_processor", + "input_json": { "op": "read_sheet", "sheet": "Data", "row_range": [1, 100] }, + "input_files": [{ "path": "", "name": "input.xlsx" }] +}) +``` + +Out-of-range requests are clamped. Large files are automatically chunked +by the host and merged. + +Do not try to open an `.xlsx` file with `Read` — it is a zipped OOXML +container. Do not try to `Edit` it — you will corrupt the zip. + +# Formula safety (when the module ships) + +When writing back cell values via `update_cells`, always: + +1. `read_sheet` first to see whether the target cell already holds a + formula. +2. If it does, ask the user before overwriting it with a plain value. +3. Never silently "fix" `#REF!`, `#N/A`, or `#VALUE!` — surface them in + your summary. + +# What to return to the user + +- The list of spreadsheet files you inspected. +- Which path you took (host-csv vs sandbox-xlsx). +- Any operations that were gated on the missing xlsx module. +- Paths of any files you wrote back. diff --git a/frontend/src-tauri/src/cowork/desktop_tools/mod.rs b/frontend/src-tauri/src/cowork/desktop_tools/mod.rs index a740d67f7..0e6126c47 100644 --- a/frontend/src-tauri/src/cowork/desktop_tools/mod.rs +++ b/frontend/src-tauri/src/cowork/desktop_tools/mod.rs @@ -6,9 +6,11 @@ mod grep; mod list_dir; mod read; mod todo_write; +mod wasm_run; mod write; use crate::cowork::agent_presets::shared::DesktopToolCapability; +use crate::cowork::desktop_runtime::wasm::{WasmRunError, WasmRunResult}; use serde_json::Value; use std::collections::HashMap; use std::fs; @@ -24,6 +26,7 @@ pub const TOOL_TODO_WRITE: &str = "TodoWrite"; pub const TOOL_GLOB: &str = "glob"; pub const TOOL_GREP: &str = "grep"; pub const TOOL_LIST_DIR: &str = "list_dir"; +pub const TOOL_WASM_RUN: &str = "wasm_run"; pub type DesktopToolExecuteFn = for<'a> fn(&mut DesktopToolContext<'a>, &Value) -> Result; @@ -131,6 +134,14 @@ impl<'a> DesktopToolContext<'a> { &self.execution_scope.canonical_root } + pub fn local_session_id(&self) -> &str { + self.local_session_id + } + + pub fn app_handle(&self) -> &AppHandle { + self.app + } + pub fn resolve_scoped_path( &self, file_path: &str, @@ -178,6 +189,12 @@ pub fn common_desktop_tools() -> Vec { edit::desktop_tool(), apply_patch::desktop_tool(), todo_write::desktop_tool(), + // desktop_skill_run lives under desktop_skills/ because its job is + // to surface skill guidance, not to drive the host filesystem. + // It still appears in the common tool list so every cowork mode + // that ships desktop tools automatically advertises it. + crate::cowork::desktop_skills::desktop_skill_run::desktop_tool(), + wasm_run::desktop_tool(), ] } @@ -226,6 +243,77 @@ pub fn resolve_desktop_tool_name(tool_name: &str) -> Option { }) } +/// Shared formatter for desktop tools that invoke the WASM runtime. +/// +/// This stays in the desktop tool layer because it renders the +/// user-facing tool result string, not the runtime's execution contract. +pub(super) fn format_wasm_result(result: &WasmRunResult) -> String { + let mut sections = Vec::new(); + sections.push(format!( + "module: {}\nduration_ms: {}", + result.module, result.duration_ms + )); + if !result.stdout.trim().is_empty() { + sections.push(format!("stdout:\n{}", result.stdout.trim_end())); + } + if !result.stderr.trim().is_empty() { + sections.push(format!("stderr:\n{}", result.stderr.trim_end())); + } + if let Some(output_json) = &result.output_json { + if let Ok(pretty) = serde_json::to_string_pretty(output_json) { + sections.push(format!("output.json:\n{}", pretty)); + } + } + if !result.output_files.is_empty() { + let listing = result + .output_files + .iter() + .map(|file| format!("- {} ({} bytes)", file.name, file.bytes)) + .collect::>() + .join("\n"); + sections.push(format!("output_files:\n{}", listing)); + } + if let Some(scratch) = &result.scratch_dir { + sections.push(format!("scratch_dir: {}", scratch.display())); + } + sections.join("\n\n") +} + +/// Shared formatter for human-readable WASM tool errors. +pub(super) fn format_wasm_error(tool: &str, module: &str, error: WasmRunError) -> String { + let (message, hint) = match &error { + WasmRunError::UnknownModule(name) => ( + format!("{tool}: unknown module '{name}'"), + "Check that the skill body references a registered module name.".to_string(), + ), + WasmRunError::Timeout(duration) => ( + format!("{tool}: module '{module}' timed out after {duration:?}"), + "The file may be too complex. Try a smaller file or a subset (e.g., specific page range).".to_string(), + ), + WasmRunError::OutOfFuel => ( + format!("{tool}: module '{module}' exhausted its instruction budget"), + "The file requires more processing than the budget allows. Try a smaller file.".to_string(), + ), + WasmRunError::MemoryLimit(limit) => ( + format!("{tool}: module '{module}' exceeded memory limit {limit} bytes"), + "The file is too large for the current memory budget. For PDF, the host auto-splits large files. For docx/xlsx/pptx, try a smaller file or extract a subset.".to_string(), + ), + WasmRunError::Io(detail) => ( + format!("{tool}: I/O error preparing sandbox: {detail}"), + "Check that the input file exists and is readable.".to_string(), + ), + WasmRunError::ModuleLoad(detail) => ( + format!("{tool}: failed to load module: {detail}"), + "The WebAssembly module may be corrupt. This is an internal error.".to_string(), + ), + WasmRunError::Execution(detail) => ( + format!("{tool}: module '{module}' failed during execution: {detail}"), + "The file may be corrupt or in an unsupported format. Do not retry with the same file.".to_string(), + ), + }; + format!("{message}\n\nHint: {hint}") +} + fn resolve_scoped_path( execution_scope: &DesktopExecutionScope, file_path: &str, diff --git a/frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs b/frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs new file mode 100644 index 000000000..d38ab21d2 --- /dev/null +++ b/frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs @@ -0,0 +1,746 @@ +//! `wasm_run` desktop tool. +//! +//! This tool exposes the isolated desktop WASM runtime to the agent. It is +//! deliberately scoped as the **isolation path** for processing tasks — +//! not as a replacement for normal file tools. The agent should prefer +//! `Read`, `Write`, `Edit`, `Bash`, etc. for everyday local work and only +//! reach for `wasm_run` when a skill explicitly asks for isolated +//! processing (for example, extracting text from a PDF with tight memory +//! and timeout limits). +//! +//! ## Chunking +//! +//! Before running the guest, the tool asks +//! [`crate::cowork::desktop_runtime::wasm::chunking::classify_and_plan`] +//! whether the call needs to be split. For small inputs the answer is +//! always `ChunkPlan::Single` and the tool runs a single WASM call. +//! For large inputs where a format-specific planner exists, the plan +//! is [`ChunkPlan::Multi`] and the tool dispatches several sequential +//! calls, merges their structured outputs, and returns a combined +//! result. Chunking is transparent to the LLM: it sees one tool call, +//! one tool result. +//! +//! ## Input contract +//! +//! The tool accepts a module name and an optional JSON payload. Input +//! files selected from the current desktop scope are mirrored into the +//! guest's preopened `/workspace/inputs` directory before the module +//! runs. The guest may write outputs to `/workspace/outputs` and/or a +//! structured `/workspace/output.json`; the tool reports back both. +//! +//! The set of modules the agent can invoke is defined by the desktop +//! runtime's [`ModuleRegistry`](crate::cowork::desktop_runtime::wasm::ModuleRegistry). +//! Unknown module names return an error without touching the filesystem. + +use super::{DesktopTool, DesktopToolContext, TOOL_WASM_RUN}; +use crate::cowork::agent_presets::shared::DesktopToolCapability; +use crate::cowork::desktop_runtime::wasm::chunking::{ + self, splitter, Chunk, ChunkPlan, MergeStrategy, +}; +use crate::cowork::desktop_runtime::wasm::{ + acquire_runtime, WasmEntrypoint, WasmRunRequest, WasmRunResult, +}; +use crate::cowork::desktop_runtime::RuntimeLimits; +use serde_json::Value; +use std::path::PathBuf; +use std::time::{Duration, Instant}; +use tauri::{AppHandle, Emitter}; + +pub fn desktop_tool() -> DesktopTool { + DesktopTool::new( + DesktopToolCapability { + name: TOOL_WASM_RUN.to_string(), + aliases: Vec::new(), + display_name: "Run an isolated WASM processing module".to_string(), + description: "Invoke a desktop-owned WebAssembly module inside an isolated sandbox with memory, fuel, and wall-clock limits. Use this when a skill explicitly requires isolated processing (for example, parsing a user document into structured data). Do NOT use it for normal file reading, editing, or shell work — use the Read, Write, Edit, and Bash tools for those. The module must be registered in the desktop WASM runtime. Optional input_files are copied into the guest's /workspace/inputs directory. Optional input_json is written to /workspace/input.json. The guest may return stdout/stderr, a structured /workspace/output.json, and files in /workspace/outputs. Large inputs (for example PDFs over 100 MB) may be transparently split by the host into several sequential sandbox calls whose structured outputs are merged back into one tool result.".to_string(), + input_schema: serde_json::json!({ + "type": "object", + "properties": { + "module": { + "type": "string", + "description": "Name of the WASM module to run. The module must be pre-registered in the desktop runtime." + }, + "entrypoint": { + "type": "string", + "description": "Optional exported function name to call instead of the default `_start`. Leave unset for standard WASI command modules." + }, + "input_json": { + "type": "object", + "description": "Optional JSON payload written to /workspace/input.json before the module runs. Use this to pass structured arguments to the module." + }, + "input_files": { + "type": "array", + "description": "Optional list of host files (relative or absolute within the desktop scope) to mirror into /workspace/inputs inside the sandbox.", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Source path, absolute or relative to the current desktop scope." + }, + "name": { + "type": "string", + "description": "Optional logical filename to expose inside /workspace/inputs. Defaults to the source filename." + } + }, + "required": ["path"] + } + }, + "keep_workspace": { + "type": "boolean", + "description": "Keep the scratch workspace on disk after the call for debugging. Defaults to false." + }, + "timeout_seconds": { + "type": "integer", + "description": "Optional wall-clock timeout in seconds. Defaults to the runtime default. Applied per chunk when the host splits the call." + } + }, + "required": ["module"] + }), + }, + execute, + false, + ) +} + +/// Maximum total wall-clock time a multi-chunk run may spend before +/// aborting mid-sequence. Prevents pathological PDF causing indefinite +/// blocking. +const MAX_MULTI_CHUNK_TOTAL_TIMEOUT: Duration = Duration::from_secs(600); // 10 minutes + +/// Holds everything the tool parsed from the LLM's input block, so the +/// single-call and multi-call dispatch paths can share code. +struct ParsedInput { + module_name: String, + /// Local cowork session id captured at parse time. Used as the + /// scratch-dir scope for every chunk in a multi-chunk plan so the + /// runtime's session sweeper can reclaim them together. + session_id: String, + base_input_json: Option, + input_files: Vec<(PathBuf, String)>, + entrypoint: WasmEntrypoint, + keep_workspace: bool, + /// When set by the user, this is the **total** timeout for the entire + /// call. For multi-chunk plans, each chunk gets `total / chunk_count` + /// as its per-chunk wall deadline (never less than 10 s). + user_timeout_override: Option, + /// Tauri app handle for emitting progress events during multi-chunk + /// dispatch. If absent (unit test context), progress events are skipped. + app_handle: Option, +} + +fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result { + let parsed = parse_input(ctx, tool_input)?; + let module_name = parsed.module_name.clone(); + + // Emit start event so the desktop UI can show a spinner. + emit_lifecycle_event(&parsed, "wasm_run:started"); + + // Lease the shared desktop WASM runtime. This lazily creates the + // wasmtime engine on first use and keeps it alive for the duration + // of the lease so the idle reaper cannot tear it down mid-call. + let lease = acquire_runtime() + .map_err(|error| format!("wasm_run: failed to acquire runtime: {error}"))?; + if !lease.registry().has(&module_name) { + let available = lease.registry().names().join(", "); + return Err(format!( + "wasm_run: module '{}' is not registered. Available modules: [{}]", + module_name, available + )); + } + + // Classify the call. This is the only place that knows about + // chunking policy — the rest of the dispatch path treats plan + // variants uniformly. + let op_name = parsed + .base_input_json + .as_ref() + .and_then(|value| value.get("op")) + .and_then(Value::as_str); + let primary_file = parsed.input_files.first().map(|(path, _)| path.as_path()); + let plan = chunking::classify_and_plan( + &module_name, + primary_file, + op_name, + parsed + .base_input_json + .as_ref() + .unwrap_or(&Value::Null), + )?; + + // Fix #21: multi-file + chunkable plan is ambiguous — which file do + // we chunk? Error early so the LLM knows to split its call. + if plan.is_multi() && parsed.input_files.len() > 1 { + return Err( + "wasm_run: cannot chunk a call with multiple input_files. \ + Split into one call per file so each can be chunked independently." + .to_string(), + ); + } + + let outcome = match plan { + ChunkPlan::Single { limits } => run_single(&lease, &parsed, limits, None), + ChunkPlan::Multi { + chunks, + merge, + use_host_split, + } => { + if use_host_split { + run_multi_with_host_split(&lease, &parsed, chunks, merge) + } else { + run_multi(&lease, &parsed, chunks, merge) + } + } + }; + + drop(lease); + emit_lifecycle_event(&parsed, "wasm_run:completed"); + outcome +} + +fn parse_input( + ctx: &mut DesktopToolContext<'_>, + tool_input: &Value, +) -> Result { + let module_name = tool_input + .get("module") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .ok_or_else(|| "wasm_run requires a 'module' name".to_string())? + .to_string(); + + let base_input_json = tool_input + .get("input_json") + .filter(|v| !v.is_null()) + .cloned(); + + let entrypoint = tool_input + .get("entrypoint") + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(|value| WasmEntrypoint::NamedVoid(value.to_string())) + .unwrap_or(WasmEntrypoint::Start); + + let keep_workspace = tool_input + .get("keep_workspace") + .and_then(Value::as_bool) + .unwrap_or(false); + + let user_timeout_override = tool_input + .get("timeout_seconds") + .and_then(Value::as_u64) + .map(|secs| std::time::Duration::from_secs(secs.min(120))); + + let mut input_files = Vec::new(); + if let Some(entries) = tool_input.get("input_files").and_then(Value::as_array) { + for entry in entries { + let Some(spec) = entry.as_object() else { + return Err("wasm_run: input_files entries must be objects".to_string()); + }; + let raw_path = spec + .get("path") + .and_then(Value::as_str) + .ok_or_else(|| "wasm_run: input_files entry requires a 'path'".to_string())?; + let resolved = ctx.resolve_scoped_path(raw_path, false)?; + let logical_name = spec + .get("name") + .and_then(Value::as_str) + .map(str::to_string) + .unwrap_or_else(|| { + PathBuf::from(raw_path) + .file_name() + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or_else(|| "input.bin".to_string()) + }); + input_files.push((resolved, logical_name)); + } + } + + Ok(ParsedInput { + module_name, + session_id: ctx.local_session_id().to_string(), + base_input_json, + input_files, + entrypoint, + keep_workspace, + user_timeout_override, + app_handle: Some(ctx.app_handle().clone()), + }) +} + +fn build_request( + parsed: &ParsedInput, + session_id: &str, + limits: RuntimeLimits, + input_json_overlay: Option<&Value>, +) -> WasmRunRequest { + let input_json = match (parsed.base_input_json.as_ref(), input_json_overlay) { + (None, None) => None, + (Some(base), None) => Some(base.clone()), + (None, Some(overlay)) => Some(overlay.clone()), + (Some(base), Some(overlay)) => Some(merge_json_objects(base.clone(), overlay)), + }; + + let mut request = WasmRunRequest::new(parsed.module_name.clone()) + .with_session_id(session_id.to_string()); + request.input_json = input_json; + request.entrypoint = parsed.entrypoint.clone(); + request.keep_workspace = parsed.keep_workspace; + request.input_files = parsed.input_files.clone(); + + let mut effective_limits = limits; + if let Some(user_timeout) = parsed.user_timeout_override { + effective_limits.wall_timeout = user_timeout; + } + request.limits = Some(effective_limits); + request +} + +/// Shallow merge two JSON values, preferring keys from `overlay`. Both +/// must be objects; if either is not, `overlay` wins outright. +fn merge_json_objects(base: Value, overlay: &Value) -> Value { + let (Value::Object(mut base_map), Value::Object(overlay_map)) = (base, overlay) else { + return overlay.clone(); + }; + for (key, value) in overlay_map.iter() { + base_map.insert(key.clone(), value.clone()); + } + Value::Object(base_map) +} + +fn run_single( + lease: &crate::cowork::desktop_runtime::wasm::lifecycle::RuntimeLease, + parsed: &ParsedInput, + limits: RuntimeLimits, + overlay: Option<&Value>, +) -> Result { + let request = build_request(parsed, &parsed.session_id, limits, overlay); + let result = lease.run(request); + match result { + Ok(result) => Ok(super::format_wasm_result(&result)), + Err(error) => Err(super::format_wasm_error( + "wasm_run", + &parsed.module_name, + error, + )), + } +} + +fn run_multi( + lease: &crate::cowork::desktop_runtime::wasm::lifecycle::RuntimeLease, + parsed: &ParsedInput, + mut chunks: Vec, + merge: MergeStrategy, +) -> Result { + if chunks.is_empty() { + return Err("wasm_run: chunking planner returned an empty chunk list".to_string()); + } + let chunk_count = chunks.len(); + + // Fix #14 + #23: compute per-chunk timeout from user total override. + // If user set `timeout_seconds`, that's the TOTAL budget across all + // chunks. Each chunk gets total/chunks (min 10 s per chunk). If no + // user override, use planner-assigned limits as-is but enforce + // MAX_MULTI_CHUNK_TOTAL_TIMEOUT as a hard ceiling. + let total_deadline = parsed + .user_timeout_override + .unwrap_or(MAX_MULTI_CHUNK_TOTAL_TIMEOUT); + let per_chunk_timeout = Duration::from_secs( + (total_deadline.as_secs() / chunk_count as u64).max(10), + ); + + // Override per-chunk limits with the computed timeout. + for chunk in &mut chunks { + chunk.limits.wall_timeout = per_chunk_timeout; + } + + let global_start = Instant::now(); + let mut chunk_outputs: Vec = Vec::with_capacity(chunk_count); + let mut combined_stdout = String::new(); + let mut combined_stderr = String::new(); + let mut total_duration_ms: u128 = 0; + let mut chunk_summaries: Vec = Vec::with_capacity(chunk_count); + + for (i, chunk) in chunks.iter().enumerate() { + // Fix #14: check global timeout before starting next chunk. + if global_start.elapsed() > total_deadline { + return Err(format!( + "wasm_run: multi-chunk run exceeded total timeout ({} s) after completing {}/{} chunks", + total_deadline.as_secs(), + i, + chunk_count + )); + } + + let request = build_request( + parsed, + &parsed.session_id, + chunk.limits, + Some(&chunk.input_json_overlay), + ); + let label = chunk.label.clone(); + let result: Result = lease.run(request); + match result { + Ok(chunk_result) => { + total_duration_ms += chunk_result.duration_ms; + if !chunk_result.stdout.trim().is_empty() { + combined_stdout.push_str(&format!( + "[{}]\n{}\n", + label, + chunk_result.stdout.trim_end() + )); + } + if !chunk_result.stderr.trim().is_empty() { + combined_stderr.push_str(&format!( + "[{}]\n{}\n", + label, + chunk_result.stderr.trim_end() + )); + } + let Some(output_json) = chunk_result.output_json else { + return Err(format!( + "wasm_run: chunk {label} produced no output.json; cannot merge" + )); + }; + chunk_summaries.push(format!( + "- [{label}] duration_ms={}, items_in_chunk={}", + chunk_result.duration_ms, + first_array_len(&output_json) + )); + chunk_outputs.push(output_json); + + // Fix #19: emit progress event so the UI can show a + // progress bar between chunks. + emit_chunk_progress(parsed, i + 1, chunk_count, &label); + } + Err(error) => { + let error_msg = super::format_wasm_error( + "wasm_run", + &parsed.module_name, + error, + ); + // Return partial results if we have any, instead of + // losing all completed work. + return format_multi_result( + parsed, + &global_start, + total_duration_ms, + chunk_count, + &chunk_outputs, + &chunk_summaries, + &combined_stdout, + &combined_stderr, + merge, + Some(&error_msg), + ); + } + } + } + + format_multi_result( + parsed, + &global_start, + total_duration_ms, + chunk_count, + &chunk_outputs, + &chunk_summaries, + &combined_stdout, + &combined_stderr, + merge, + None, + ) +} + +/// Format the result of a multi-chunk run, optionally with a partial-failure error. +fn format_multi_result( + parsed: &ParsedInput, + global_start: &Instant, + total_duration_ms: u128, + chunk_count: usize, + chunk_outputs: &[Value], + chunk_summaries: &[String], + combined_stdout: &str, + combined_stderr: &str, + merge: MergeStrategy, + error: Option<&str>, +) -> Result { + let wall_ms = global_start.elapsed().as_millis(); + let completed = chunk_outputs.len(); + let status = if error.is_some() { + format!("PARTIAL ({completed}/{chunk_count} chunks succeeded)") + } else { + format!("{chunk_count} (sequential)") + }; + + let mut sections: Vec = Vec::new(); + sections.push(format!( + "module: {}\nwall_time_ms: {wall_ms}\ncpu_time_ms: {total_duration_ms}\nchunks: {status}", + parsed.module_name + )); + + if !chunk_summaries.is_empty() { + sections.push(format!("chunk_summary:\n{}", chunk_summaries.join("\n"))); + } + if !combined_stdout.trim().is_empty() { + sections.push(format!("stdout (merged):\n{}", combined_stdout.trim_end())); + } + if !combined_stderr.trim().is_empty() { + sections.push(format!("stderr (merged):\n{}", combined_stderr.trim_end())); + } + + if !chunk_outputs.is_empty() { + match chunking::merge_chunk_outputs(merge, chunk_outputs) { + Ok(merged) => { + if let Ok(pretty) = serde_json::to_string_pretty(&merged) { + sections.push(format!("output.json (merged):\n{pretty}")); + } + } + Err(merge_err) => { + sections.push(format!("merge_error: {merge_err}")); + } + } + } + + if let Some(err) = error { + sections.push(format!("error:\n{err}")); + } + + // If we have at least some output, return Ok (partial success) + // even if a chunk failed, so the LLM can use what was extracted. + // Only return Err if zero chunks succeeded. + if chunk_outputs.is_empty() && error.is_some() { + Err(error.unwrap().to_string()) + } else { + Ok(sections.join("\n\n")) + } +} + +/// Multi-chunk dispatch with host-side file splitting (Fix #7). +/// +/// Instead of passing the full source file + `page_range` overlay to +/// each chunk, this path uses [`splitter::split_pdf_pages`] to produce +/// N smaller PDFs on the host. Each guest call then receives a small +/// file that fits within its 256–512 MB memory budget without loading +/// the entire original document. +/// +/// The chunk plan's `input_json_overlay` (which would contain +/// `page_range`) is **ignored** here because the guest file already +/// contains only the relevant pages. The guest is called without a +/// `page_range` param — it extracts the entire (small) file. +fn run_multi_with_host_split( + lease: &crate::cowork::desktop_runtime::wasm::lifecycle::RuntimeLease, + parsed: &ParsedInput, + chunks: Vec, + merge: MergeStrategy, +) -> Result { + if chunks.is_empty() { + return Err("wasm_run: host-split: empty chunk list".to_string()); + } + + // We need the primary input file to split. + let (source_path, _logical_name) = parsed + .input_files + .first() + .ok_or_else(|| "wasm_run: host-split requires at least one input file".to_string())?; + + // Determine pages_per_chunk from the first chunk's overlay. + let pages_per_chunk = chunks + .first() + .and_then(|c| c.input_json_overlay.get("page_range")) + .and_then(|r| r.as_array()) + .and_then(|arr| { + let start = arr.first()?.as_u64()?; + let end = arr.get(1)?.as_u64()?; + Some((end - start + 1) as u32) + }) + .unwrap_or(20); + + // Split the PDF on the host side into chunk files. + let split_dir = std::env::temp_dir().join(format!( + "ii-wasm-split-{}-{}", + parsed.session_id, + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or_default() + )); + let chunk_files = + splitter::split_pdf_pages(source_path, &split_dir, pages_per_chunk) + .map_err(|e| format!("wasm_run: host-split failed: {e}"))?; + + if chunk_files.is_empty() { + let _ = std::fs::remove_dir_all(&split_dir); + return Err("wasm_run: host-split produced 0 chunk files (empty PDF?)".to_string()); + } + + // Compute timeout. + let total_deadline = parsed + .user_timeout_override + .unwrap_or(MAX_MULTI_CHUNK_TOTAL_TIMEOUT); + let per_chunk_timeout = Duration::from_secs( + (total_deadline.as_secs() / chunk_files.len() as u64).max(10), + ); + + let global_start = Instant::now(); + let mut chunk_outputs: Vec = Vec::with_capacity(chunk_files.len()); + let mut combined_stdout = String::new(); + let mut combined_stderr = String::new(); + let mut total_duration_ms: u128 = 0; + let mut chunk_summaries: Vec = Vec::new(); + let actual_chunk_count = chunk_files.len(); + + for (i, chunk_file) in chunk_files.iter().enumerate() { + if global_start.elapsed() > total_deadline { + let _ = std::fs::remove_dir_all(&split_dir); + return Err(format!( + "wasm_run: host-split exceeded total timeout ({} s) after {}/{} chunks", + total_deadline.as_secs(), + i, + actual_chunk_count + )); + } + + // Build a request that uses the chunk file instead of the original. + let mut limits = RuntimeLimits::defaults(); + limits.wall_timeout = per_chunk_timeout; + limits.max_memory_bytes = 512 * 1024 * 1024; + + // base input_json without page_range (guest extracts entire chunk file). + let chunk_input_json = parsed + .base_input_json + .as_ref() + .and_then(|v| v.as_object()) + .map(|obj| { + let mut filtered = obj.clone(); + filtered.remove("page_range"); + Value::Object(filtered) + }) + .or_else(|| parsed.base_input_json.clone()); + + let mut request = WasmRunRequest::new(parsed.module_name.clone()) + .with_session_id(parsed.session_id.clone()); + request.input_json = chunk_input_json; + request.entrypoint = parsed.entrypoint.clone(); + request.keep_workspace = parsed.keep_workspace; + request.input_files = vec![(chunk_file.path.clone(), "input.pdf".to_string())]; + request.limits = Some(limits); + + let label = format!("pages {}-{}", chunk_file.page_start, chunk_file.page_end); + let result = lease.run(request); + + match result { + Ok(chunk_result) => { + total_duration_ms += chunk_result.duration_ms; + if !chunk_result.stdout.trim().is_empty() { + combined_stdout.push_str(&format!("[{label}]\n{}\n", chunk_result.stdout.trim_end())); + } + if !chunk_result.stderr.trim().is_empty() { + combined_stderr.push_str(&format!("[{label}]\n{}\n", chunk_result.stderr.trim_end())); + } + let Some(mut output_json) = chunk_result.output_json else { + let _ = std::fs::remove_dir_all(&split_dir); + return Err(format!("wasm_run: host-split chunk {label} produced no output.json")); + }; + // Patch page_offset to reflect position in the original doc. + if let Some(obj) = output_json.as_object_mut() { + obj.insert( + "page_offset".to_string(), + Value::from(chunk_file.page_start as u64), + ); + obj.insert( + "total_pages".to_string(), + Value::from(chunk_file.total_pages as u64), + ); + } + chunk_summaries.push(format!( + "- [{label}] duration_ms={}, items={}", + chunk_result.duration_ms, + first_array_len(&output_json) + )); + chunk_outputs.push(output_json); + emit_chunk_progress(parsed, i + 1, actual_chunk_count, &label); + } + Err(error) => { + let error_msg = super::format_wasm_error( + "wasm_run", + &parsed.module_name, + error, + ); + let _ = std::fs::remove_dir_all(&split_dir); + return format_multi_result( + parsed, + &global_start, + total_duration_ms, + actual_chunk_count, + &chunk_outputs, + &chunk_summaries, + &combined_stdout, + &combined_stderr, + merge, + Some(&error_msg), + ); + } + } + } + + let _ = std::fs::remove_dir_all(&split_dir); + format_multi_result( + parsed, + &global_start, + total_duration_ms, + actual_chunk_count, + &chunk_outputs, + &chunk_summaries, + &combined_stdout, + &combined_stderr, + merge, + None, + ) +} + +/// Emit a lifecycle event (started/completed) for UI feedback on single calls. +fn emit_lifecycle_event(parsed: &ParsedInput, event_name: &str) { + let Some(app) = &parsed.app_handle else { + return; + }; + let payload = serde_json::json!({ + "module": parsed.module_name, + "session_id": parsed.session_id, + }); + let _ = app.emit(event_name, payload); +} + +/// Emit a progress event after each chunk completes so the desktop +/// UI can render a progress indicator. No-op if app handle is absent +/// (unit tests) or if emit fails (best-effort). +fn emit_chunk_progress(parsed: &ParsedInput, done: usize, total: usize, label: &str) { + let Some(app) = &parsed.app_handle else { + return; + }; + let payload = serde_json::json!({ + "module": parsed.module_name, + "session_id": parsed.session_id, + "chunk_done": done, + "chunk_total": total, + "chunk_label": label, + }); + let _ = app.emit("wasm_run:chunk_progress", payload); +} + +/// Get the length of the first array-valued field in a JSON object. +/// Used for chunk summary (pages, paragraphs, rows, slides — we don't +/// need to know which field it is, just how many items came back). +fn first_array_len(value: &Value) -> usize { + let Some(obj) = value.as_object() else { + return 0; + }; + for val in obj.values() { + if let Some(arr) = val.as_array() { + return arr.len(); + } + } + 0 +} + diff --git a/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs b/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs index 0e3b35c28..6553401e4 100644 --- a/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs +++ b/frontend/src-tauri/src/cowork/intelligent_folder/chat_prompt.rs @@ -31,7 +31,22 @@ Your job is to inspect, understand, clean up, and refolder files inside that fol - `Edit` - Make exact text replacements in local files\n\ - `apply_patch` - Apply structured multi-file edits\n\ - `Bash` - Execute shell commands inside the selected folder\n\ -- `TodoWrite` - Keep a short task checklist during the run\n\n\ +- `TodoWrite` - Keep a short task checklist during the run\n\ +- `desktop_skill_run` - Load the full body of a desktop skill by name. Call this first whenever you plan to process a complex document format (pdf, docx, xlsx, pptx). It returns markdown instructions and the exact `wasm_run` shape you should use next. It does NOT execute anything itself.\n\ +- `wasm_run` - Execute a WebAssembly module inside the isolated desktop runtime. Only call this after you have read the relevant skill body via `desktop_skill_run` and know the exact module name, input_json, and input_files shape to use. You may also call it directly when debugging a specific module.\n\n\ +[Desktop skills available]\n\ +Skills are packaged guidance backed by the desktop WebAssembly runtime. To use a skill, follow the two-step flow:\n\ +1. Call `desktop_skill_run(skill_name=)` to load the skill's body and operation contracts into context.\n\ +2. Follow the body: usually it tells you to call `wasm_run` with a specific module and shape. Copy that shape exactly.\n\ +Built-in skills:\n\ +- `pdf` - PDF text extraction and metadata via the `pdf_processor` isolated runtime.\n\ +- `docx` - Word document text extraction via the `docx_processor` isolated runtime.\n\ +- `xlsx` - Spreadsheet reading via the `xlsx_processor` isolated runtime (csv/tsv use host tools directly).\n\ +- `pptx` - Presentation slide text extraction via the `pptx_processor` isolated runtime.\n\ +Decision rule:\n\ +- Plain text, markdown, code, or csv/tsv files: use `Read`, `Write`, `Edit`, `grep` directly. Do not touch skills.\n\ +- `.pdf`, `.docx`, `.xlsx`, `.pptx`: call `desktop_skill_run` first to read instructions, then follow them.\n\ +- If a skill body reports that its WebAssembly module is not shipped yet, tell the user what is unavailable and offer filename-level operations instead. Never try to edit a binary container (.docx, .xlsx, .pptx are all zipped OOXML) with `Edit` or `Write` — you will corrupt the file.\n\n\ [Local folder scope and context]\n\ Mode scope: intelligent-folder\n\ Input folder path: {}\n", @@ -51,6 +66,7 @@ mod tests { kind: FileTreeNodeKind::Folder, extension: None, size: None, + last_modified: None, children: Some(Vec::new()), } } @@ -77,7 +93,7 @@ mod tests { source_tree: sample_folder("demo"), result_tree: None, }, - undo_slot: crate::cowork::intelligent_folder::sessions::FolderUndoSlotState::None, + undo_state: crate::cowork::intelligent_folder::sessions::FolderUndoState::default(), } } @@ -98,4 +114,23 @@ mod tests { assert!(prompt.contains("- `Edit` - Make exact text replacements in local files")); assert!(prompt.contains("- `Bash` - Execute shell commands inside the selected folder")); } + + #[test] + fn build_folder_prompt_context_advertises_two_step_flow() { + let prompt = build_folder_prompt_context(&sample_session()); + assert!(prompt.contains("- `desktop_skill_run`")); + assert!(prompt.contains("- `wasm_run`")); + assert!(prompt.contains("[Desktop skills available]")); + assert!(prompt.contains("- `pdf`")); + assert!(prompt.contains("- `docx`")); + assert!(prompt.contains("- `xlsx`")); + assert!(prompt.contains("- `pptx`")); + assert!(prompt.contains("Decision rule:")); + // Two-step flow must be spelled out explicitly. + assert!(prompt.contains("two-step flow")); + assert!(prompt.contains("desktop_skill_run(skill_name=")); + assert!(prompt.contains("Follow the body")); + // The prompt must tell the LLM not to edit binary containers. + assert!(prompt.contains("corrupt the file")); + } } diff --git a/frontend/src-tauri/src/cowork/intelligent_folder/file_tree.rs b/frontend/src-tauri/src/cowork/intelligent_folder/file_tree.rs index 04f81aed8..fd9363207 100644 --- a/frontend/src-tauri/src/cowork/intelligent_folder/file_tree.rs +++ b/frontend/src-tauri/src/cowork/intelligent_folder/file_tree.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, SecondsFormat, Utc}; use serde::{Deserialize, Serialize}; use std::{ fs, @@ -31,6 +32,8 @@ pub struct FileTreeNode { #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub last_modified: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub children: Option>, } @@ -115,6 +118,7 @@ fn build_node( kind: FileTreeNodeKind::File, extension: file_extension(path), size: Some(format_bytes(metadata.len())), + last_modified: format_modified_time(&metadata), children: None, }); } @@ -126,6 +130,7 @@ fn build_node( kind: FileTreeNodeKind::Folder, extension: None, size: None, + last_modified: format_modified_time(&metadata), children: Some(Vec::new()), }); } @@ -167,6 +172,7 @@ fn build_node( kind: FileTreeNodeKind::Folder, extension: None, size: None, + last_modified: format_modified_time(&metadata), children: Some(children), }) } @@ -236,9 +242,17 @@ fn format_bytes(bytes: u64) -> String { } } +fn format_modified_time(metadata: &fs::Metadata) -> Option { + let modified = metadata.modified().ok()?; + let date_time = DateTime::::from(modified); + Some(date_time.to_rfc3339_opts(SecondsFormat::Secs, true)) +} + #[cfg(test)] mod tests { - use super::{build_node, format_bytes, FileTreeNodeKind, TraversalState}; + use super::{ + build_node, format_bytes, format_modified_time, FileTreeNodeKind, TraversalState, + }; use std::{ fs, path::PathBuf, @@ -261,6 +275,21 @@ mod tests { assert_eq!(format_bytes(104_857_600), "100 MB"); } + #[test] + fn formats_modified_time_as_rfc3339() { + let root = temp_test_dir(); + fs::write(&root, b"hello world").expect("should create test file"); + + let metadata = fs::symlink_metadata(&root).expect("should read metadata"); + let modified = format_modified_time(&metadata) + .expect("test file metadata should expose modified time"); + + assert!(modified.ends_with('Z')); + assert!(modified.contains('T')); + + fs::remove_file(&root).expect("should clean up test file"); + } + #[test] fn builds_directory_tree() { let root = temp_test_dir(); @@ -290,6 +319,7 @@ mod tests { assert_eq!(nested_children[0].name, "demo.txt"); assert_eq!(nested_children[0].kind, FileTreeNodeKind::File); assert_eq!(nested_children[0].extension.as_deref(), Some("txt")); + assert!(nested_children[0].last_modified.is_some()); fs::remove_dir_all(&root).expect("should clean up test directory"); } diff --git a/frontend/src-tauri/src/cowork/mod.rs b/frontend/src-tauri/src/cowork/mod.rs index 959954f80..ac43fe29b 100644 --- a/frontend/src-tauri/src/cowork/mod.rs +++ b/frontend/src-tauri/src/cowork/mod.rs @@ -2,6 +2,7 @@ pub mod agent_presets; pub mod agent_remote; pub mod chat; pub mod chat_commands; +pub mod desktop_runtime; pub mod desktop_skills; pub mod desktop_tools; pub mod homepage; diff --git a/frontend/src/components/agent/action.tsx b/frontend/src/components/agent/action.tsx index 67c302f1d..fae96eac0 100644 --- a/frontend/src/components/agent/action.tsx +++ b/frontend/src/components/agent/action.tsx @@ -7,6 +7,7 @@ import { FileText, Paperclip, SearchCheck, Share2, Sparkle } from 'lucide-react' import { useTranslation } from 'react-i18next' import { ActionStep, TOOL } from '@/typings/agent' +import { getCoworkActionValue } from '@/components/cowork/cowork-action-utils' import { Icon } from '../ui/icon' import { identifyFilesNeeded, identifySlidesNeeded } from '@/lib/utils' @@ -214,6 +215,20 @@ const Action = ({ workspaceInfo, type, value, onClick }: ActionProps) => { return ( ) + case TOOL.DESKTOP_SKILL_RUN: + return ( + + ) + case TOOL.WASM_RUN: + return ( + + ) case TOOL.GET_DATABASE_CONNECTION: return ( { return t('agent.action.titles.registerStripeWebhook') case TOOL.MOBILE_APP_INIT: return t('agent.action.titles.mobileAppInit') + case TOOL.DESKTOP_SKILL_RUN: + return 'Desktop Skill' + case TOOL.WASM_RUN: + return 'Process' default: // Fallback to tool_display_name if available return value.tool_display_name || type @@ -783,6 +802,9 @@ const Action = ({ workspaceInfo, type, value, onClick }: ActionProps) => { } case TOOL.SKILL: return value.tool_input?.skill + case TOOL.DESKTOP_SKILL_RUN: + case TOOL.WASM_RUN: + return getCoworkActionValue({ type, data: value }) case TOOL.MOBILE_APP_INIT: return value.tool_input?.project_name diff --git a/frontend/src/components/cowork/cowork-action-utils.ts b/frontend/src/components/cowork/cowork-action-utils.ts new file mode 100644 index 000000000..03123bc85 --- /dev/null +++ b/frontend/src/components/cowork/cowork-action-utils.ts @@ -0,0 +1,216 @@ +import { TOOL, type ActionStep } from '@/typings/agent' + +export const readCoworkRecord = ( + value: unknown +): Record | undefined => { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return undefined + } + + return value as Record +} + +export const readCoworkString = ( + record: Record, + key: string +) => { + const value = record[key] + return typeof value === 'string' && value.trim() ? value.trim() : undefined +} + +const lastPathSegment = (value?: string) => { + if (!value) { + return undefined + } + + const parts = value.split(/[\\/]/).filter(Boolean) + return parts.at(-1) +} + +export const getCoworkSkillName = ( + toolInput: unknown +) => { + const input = readCoworkRecord(toolInput) + + if (!input) { + return undefined + } + + return ( + readCoworkString(input, 'skill_name') ?? + readCoworkString(input, 'skill') + ) +} + +export const getCoworkWasmPrimaryInputFileName = ( + toolInput: unknown +) => { + const input = readCoworkRecord(toolInput) + + if (!input) { + return undefined + } + + if (Array.isArray(input.input_files)) { + for (const entry of input.input_files) { + const file = readCoworkRecord(entry) + if (!file) { + continue + } + + const fileName = + lastPathSegment(readCoworkString(file, 'path')) ?? + readCoworkString(file, 'name') + + if (fileName) { + return fileName + } + } + } + + return ( + lastPathSegment(readCoworkString(input, 'file_path')) ?? + lastPathSegment(readCoworkString(input, 'path')) ?? + lastPathSegment(readCoworkString(input, 'file')) ?? + lastPathSegment(readCoworkString(input, 'filename')) + ) +} + +export const getCoworkWasmModuleName = ( + toolInput: unknown +) => { + const input = readCoworkRecord(toolInput) + return input ? readCoworkString(input, 'module') : undefined +} + +export const getCoworkActionValue = (action?: ActionStep) => { + if (!action) { + return undefined + } + + switch (action.type) { + case TOOL.DESKTOP_SKILL_RUN: + return getCoworkSkillName(action.data.tool_input) + case TOOL.WASM_RUN: + return ( + getCoworkWasmPrimaryInputFileName(action.data.tool_input) ?? + getCoworkWasmModuleName(action.data.tool_input) + ) + default: + return undefined + } +} + +export const formatCoworkBuildHeaderLabel = (action?: ActionStep) => { + if (!action) { + return undefined + } + + if (action.type === TOOL.DESKTOP_SKILL_RUN) { + const skillName = getCoworkSkillName(action.data.tool_input) + return skillName ? `Desktop Skill: ${skillName}` : 'Desktop Skill' + } + + if (action.type === TOOL.WASM_RUN) { + const fileName = getCoworkWasmPrimaryInputFileName( + action.data.tool_input + ) + if (fileName) { + return `Process ${fileName}` + } + + const moduleName = getCoworkWasmModuleName(action.data.tool_input) + return moduleName ? `Process ${moduleName}` : 'Process' + } + + return action.data.tool_display_name || action.data.tool_name +} + +export const inferCoworkToolDisplayName = ({ + toolName, + displayName, + toolInput +}: { + toolName?: string + displayName?: string + toolInput?: unknown +}) => { + const normalizedToolName = toolName?.trim().toLowerCase() + const skillName = getCoworkSkillName(toolInput) + + switch (normalizedToolName) { + case TOOL.DESKTOP_SKILL_RUN: + return 'Desktop Skill' + case TOOL.WASM_RUN: + return 'Process' + case TOOL.SKILL.toLowerCase(): + return skillName ? `Skill: ${skillName}` : 'Skill' + default: + return displayName?.trim() || toolName?.trim() || 'Tool' + } +} + +export const normalizeCoworkToolNameForUi = (toolName?: string) => { + const normalized = toolName?.trim() + if (!normalized) { + return undefined + } + + switch (normalized.toLowerCase()) { + case 'ls': + return TOOL.LS + case 'bash': + return TOOL.BASH + case 'bashinit': + case 'bash_init': + return TOOL.BASH_INIT + case 'bashview': + case 'bash_view': + return TOOL.BASH_VIEW + case 'bashstop': + case 'bash_stop': + return TOOL.BASH_STOP + case 'bashkill': + case 'bash_kill': + return TOOL.BASH_KILL + case 'bashlist': + case 'bash_list': + return TOOL.BASH_LIST + case 'bashwritetoprocess': + case 'bash_write_to_process': + return TOOL.BASH_WRITE_TO_PROCESS + case 'read': + case 'read_file': + return TOOL.READ + case 'write': + case 'write_file': + return TOOL.WRITE + case 'edit': + case 'edit_file': + return TOOL.EDIT + case 'apply_patch': + return TOOL.APPLY_PATCH + case 'todowrite': + case 'todo_write': + return TOOL.TODO_WRITE + case 'glob': + return TOOL.GLOB + case 'grep': + case 'astgrep': + return TOOL.GREP + case 'desktop_skill_run': + return TOOL.DESKTOP_SKILL_RUN + case 'wasm_run': + return TOOL.WASM_RUN + case 'multiedit': + case 'multi_edit': + return TOOL.MULTI_EDIT + case 'list_dir': + return TOOL.LS + default: + return normalized + } +} + +export const isCoworkBuildPanelActionVisible = (action?: ActionStep | null) => + Boolean(action && action.type !== TOOL.DESKTOP_SKILL_RUN) diff --git a/frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx b/frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx index ceb10537b..cfa151a34 100644 --- a/frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx +++ b/frontend/src/components/cowork/cowork-build/cowork-build.renderers.tsx @@ -3,12 +3,44 @@ import SearchBrowser from '@/components/agent/search-browser' import CodeEditor from '@/components/code-editor' import Terminal from '@/components/terminal' import { TOOL, type ActionStep } from '@/typings/agent' +import { formatCoworkBuildHeaderLabel } from '../cowork-action-utils' import type { CoworkBuildRendererDefinition, CoworkBuildRendererKey, CoworkBuildRendererContext } from './cowork-build.types' +const escapeRegExp = (value: string) => + value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + +const extractOutputJsonString = (raw?: string) => { + if (!raw) { + return undefined + } + + const sectionKeys = ['output.json (merged)', 'output.json'] + + for (const key of sectionKeys) { + const matcher = new RegExp( + `(?:^|\\n\\n)${escapeRegExp(key)}:\\n([\\s\\S]*?)(?=\\n\\n(?:output\\.json(?: \\(merged\\))?|stdout(?: \\(merged\\))?|stderr(?: \\(merged\\))?|output_files|error|chunk_summary):|$)` + ) + const match = raw.match(matcher) + const sectionBody = match?.[1]?.trim() + + if (!sectionBody) { + continue + } + + try { + return JSON.stringify(JSON.parse(sectionBody), null, 2) + } catch { + return sectionBody + } + } + + return undefined +} + export const coworkBuildEventToolGroups = { terminal: new Set([ TOOL.BASH, @@ -68,9 +100,34 @@ export const coworkBuildEventToolGroups = { TOOL.BROWSER_RESTART, TOOL.BROWSER_ENTER_TEXT, TOOL.BROWSER_ENTER_MULTI_TEXTS - ]) + ]), + desktopTool: new Set([TOOL.WASM_RUN]) } satisfies Record> +const DesktopToolBuildCard = ({ + currentAction, + currentToolCall +}: CoworkBuildRendererContext) => { + const backendResult = + currentToolCall?.result ?? + (typeof currentAction.data.result === 'string' + ? currentAction.data.result + : undefined) + const outputJson = extractOutputJsonString(backendResult) + + return ( +

+ ) +} + export const coworkBuildRendererCatalog: Record< CoworkBuildRendererKey, CoworkBuildRendererDefinition @@ -125,10 +182,7 @@ export const coworkBuildRendererCatalog: Record< key: 'browser', matches: (action: ActionStep) => coworkBuildEventToolGroups.browser.has(action.type), - render: ({ - browserUrl, - browserRaw - }: CoworkBuildRendererContext) => ( + render: ({ browserUrl, browserRaw }: CoworkBuildRendererContext) => ( ) + }, + desktopTool: { + key: 'desktopTool', + matches: (action: ActionStep) => + coworkBuildEventToolGroups.desktopTool.has(action.type), + render: (context: CoworkBuildRendererContext) => ( + + ) } } diff --git a/frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx b/frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx index 73b066e85..c879f5fb6 100644 --- a/frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx +++ b/frontend/src/components/cowork/cowork-build/cowork-build.shared.tsx @@ -5,6 +5,7 @@ import { Icon } from '@/components/ui/icon' import { Slider } from '@/components/ui/slider' import { parseJson } from '@/lib/utils' import { TOOL, type ActionStep } from '@/typings/agent' +import { isCoworkBuildPanelActionVisible } from '../cowork-action-utils' import type { CoworkBuildActionMessage, CoworkBuildRendererContext, @@ -120,9 +121,11 @@ export const useCoworkBuildState = ({ }: UseCoworkBuildStateOptions): CoworkBuildState => { const actionMessages = useMemo( () => - ((liveSession?.event_messages ?? []).filter( - (message) => message.action - ) as CoworkBuildActionMessage[]), + (liveSession?.event_messages ?? []).filter( + (message) => + message.action && + isCoworkBuildPanelActionVisible(message.action) + ) as CoworkBuildActionMessage[], [liveSession?.event_messages] ) const totalSteps = actionMessages.length @@ -172,16 +175,34 @@ export const useCoworkBuildState = ({ Boolean(liveSession?.is_awaiting_turn_action) && isLiveUpdate const selectedAction = step > 0 ? actionMessages[step - 1]?.action : undefined + const liveCurrentAction = isCoworkBuildPanelActionVisible( + liveSession?.current_action + ) + ? liveSession?.current_action + : undefined const currentAction = (!isAwaitingTurnAction ? selectedAction : undefined) ?? - (!isAwaitingTurnAction ? liveSession?.current_action : undefined) + (!isAwaitingTurnAction ? liveCurrentAction : undefined) const currentToolCall = useMemo(() => { + if (!currentAction) { + return undefined + } + const toolCalls = liveSession?.tool_calls ?? [] if (!currentAction?.data.tool_call_id) { - return toolCalls.at(-1) + return ( + [...toolCalls] + .reverse() + .find( + (toolCall) => + toolCall.name === currentAction.data.tool_name || + toolCall.display_name === + currentAction.data.tool_display_name + ) ?? toolCalls.at(-1) + ) } return ( @@ -193,6 +214,25 @@ export const useCoworkBuildState = ({ const previewPath = getPreviewPath(currentAction) const previewContent = getPreviewContent(currentAction) + const currentActivities = useMemo(() => { + if (!currentAction) { + return [] + } + + const toolCallId = + currentAction.data.tool_call_id ?? currentToolCall?.id ?? null + + return (liveSession?.activities ?? []).filter((activity) => { + if (toolCallId && activity.tool_call_id === toolCallId) { + return true + } + + return ( + activity.tool_name === currentAction.data.tool_name && + Boolean(activity.tool_name) + ) + }) + }, [currentAction, currentToolCall?.id, liveSession?.activities]) const searchKeyword = currentAction?.data.tool_input?.query || currentAction?.data.tool_input?.queries?.join(', ') @@ -216,7 +256,8 @@ export const useCoworkBuildState = ({ ? currentAction.data.result : stringifyValue(currentAction.data.result) : undefined - const fallbackContent = currentToolCall?.result || currentToolCall?.input || '' + const fallbackContent = + currentToolCall?.result || currentToolCall?.input || '' return { actionMessages, @@ -227,6 +268,7 @@ export const useCoworkBuildState = ({ isAwaitingTurnAction, currentAction, currentToolCall, + currentActivities, fallbackContent, previewPath, previewContent, @@ -340,6 +382,7 @@ export const CoworkBuildViewport = ({ fallbackContent, previewPath, previewContent, + currentActivities, browserUrl, browserRaw, searchKeyword, @@ -363,9 +406,7 @@ export const CoworkBuildViewport = ({

{emptyLabel}

-

- {emptyTitle} -

+

{emptyTitle}

{ } } -const readString = (record: Record, key: string) => { - const value = record[key] - return typeof value === 'string' && value.trim() ? value : undefined -} +const readString = (record: Record, key: string) => + readCoworkString(record, key) const readEventText = (record: Record) => readString(record, 'text') ?? @@ -174,71 +179,8 @@ const mergeStreamingText = (current: string, incoming: string) => { return `${current}${incoming}` } -const normalizeCoworkToolNameForUi = (toolName?: string) => { - const normalized = toolName?.trim() - if (!normalized) { - return undefined - } - - switch (normalized.toLowerCase()) { - case 'ls': - return TOOL.LS - case 'bash': - return TOOL.BASH - case 'bashinit': - case 'bash_init': - return TOOL.BASH_INIT - case 'bashview': - case 'bash_view': - return TOOL.BASH_VIEW - case 'bashstop': - case 'bash_stop': - return TOOL.BASH_STOP - case 'bashkill': - case 'bash_kill': - return TOOL.BASH_KILL - case 'bashlist': - case 'bash_list': - return TOOL.BASH_LIST - case 'bashwritetoprocess': - case 'bash_write_to_process': - return TOOL.BASH_WRITE_TO_PROCESS - case 'read': - case 'read_file': - return TOOL.READ - case 'write': - case 'write_file': - return TOOL.WRITE - case 'edit': - case 'edit_file': - return TOOL.EDIT - case 'apply_patch': - return TOOL.APPLY_PATCH - case 'todowrite': - case 'todo_write': - return TOOL.TODO_WRITE - case 'glob': - return TOOL.GLOB - case 'grep': - case 'astgrep': - return TOOL.GREP - case 'multiedit': - case 'multi_edit': - return TOOL.MULTI_EDIT - case 'list_dir': - return TOOL.LS - default: - return normalized - } -} - -const readRecord = (value: unknown): Record | undefined => { - if (!value || typeof value !== 'object' || Array.isArray(value)) { - return undefined - } - - return value as Record -} +const readRecord = (value: unknown): Record | undefined => + readCoworkRecord(value) const toTimestamp = (value?: string) => { if (!value) { @@ -249,26 +191,14 @@ const toTimestamp = (value?: string) => { return Number.isNaN(timestamp) ? Date.now() : timestamp } -const inferToolDisplayName = (content: Record) => { - const skillName = - typeof content.tool_input === 'object' && - content.tool_input !== null && - 'skill' in content.tool_input && - typeof content.tool_input.skill === 'string' - ? content.tool_input.skill - : undefined - const toolName = - readString(content, 'display_name') ?? - readString(content, 'tool_display_name') ?? - readString(content, 'tool_name') ?? - 'Tool' - - if (toolName === 'Skill' && skillName) { - return `Skill: ${skillName}` - } - - return toolName -} +const inferToolDisplayName = (content: Record) => + inferCoworkToolDisplayName({ + toolName: readString(content, 'tool_name'), + displayName: + readString(content, 'display_name') ?? + readString(content, 'tool_display_name'), + toolInput: readRecord(content.tool_input) + }) const HIDDEN_TOOL_MESSAGE_TYPES = new Set([ TOOL.SEQUENTIAL_THINKING, @@ -826,7 +756,7 @@ const reduceCoworkLiveEvent = ( event.runtime_event_type === 'complete' || event.runtime_event_type === 'stream_complete' || event.runtime_event_type === 'sub_agent_complete' - ? readEventText(content) ?? nextState.response + ? (readEventText(content) ?? nextState.response) : nextState.response const flushedTranscriptMessages = flushTranscriptBuffer({ messages: flushedThinkingMessages, @@ -928,8 +858,7 @@ const CoworkPage = () => { useState(false) const [folderModeResetVersion, setFolderModeResetVersion] = useState(0) const [isSessionsBoardOpen, setIsSessionsBoardOpen] = useState(false) - const [isFolderWorkflowActive, setIsFolderWorkflowActive] = - useState(false) + const [isFolderWorkflowActive, setIsFolderWorkflowActive] = useState(false) const [pendingDeleteSession, setPendingDeleteSession] = useState(null) const [isDeletingSession, setIsDeletingSession] = useState(false) @@ -1013,6 +942,8 @@ const CoworkPage = () => { ...prev, [FOLDER_SCOPE]: null })) + setRequestedFolderAction(null) + setRequestedFolderActionToken(0) }, []) const setScopeLoading = useCallback( @@ -1715,6 +1646,8 @@ const CoworkPage = () => { const handleFolderSessionCreated = useCallback( (session: CoworkChatSessionDetail) => { + setRequestedFolderAction(null) + setRequestedFolderActionToken((prev) => prev + 1) setChatSessionsByScope((prev) => ({ ...prev, [FOLDER_SCOPE]: upsertChatSessionSummary( @@ -1740,6 +1673,10 @@ const CoworkPage = () => { return } + if (!isCoworkBuildPanelActionVisible(action)) { + return + } + setRequestedFolderAction(action) setRequestedFolderActionToken((prev) => prev + 1) }, @@ -1954,12 +1891,8 @@ const CoworkPage = () => {
-
+

Result{' '} - + {rootResult}

- No mode-specific result is available yet. + No changes from the source folder yet.

diff --git a/frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-view.tsx b/frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-view.tsx index 170435bbc..25f558e67 100644 --- a/frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-view.tsx +++ b/frontend/src/components/cowork/intelligent-folder/cowork-folder-tree-view.tsx @@ -42,6 +42,19 @@ const getDirectChildCounts = (node: FolderTreeNode) => ({ files: node.children?.filter((child) => child.kind === 'file').length ?? 0 }) +const formatLastModified = (value?: string) => { + if (!value) { + return '-' + } + + const parsed = new Date(value) + if (Number.isNaN(parsed.getTime())) { + return value + } + + return parsed.toLocaleString() +} + const renderTreeNode = ({ node, depth, @@ -165,21 +178,54 @@ const CoworkFolderTreeView = ({ () => getDirectChildCounts(selectedNode), [selectedNode] ) + const selectedDetails = useMemo( + () => + selectedNode.kind === 'folder' + ? [ + { + label: 'Node type', + value: 'Folder' + }, + { + label: 'Subfolders', + value: selectedChildCounts.folders + }, + { + label: 'Files', + value: selectedChildCounts.files + } + ] + : [ + { + label: 'Node type', + value: selectedNode.extension?.toUpperCase() ?? 'File' + }, + { + label: 'Size', + value: selectedNode.size ?? '-' + }, + { + label: 'Last modified', + value: formatLastModified(selectedNode.last_modified) + } + ], + [selectedChildCounts.files, selectedChildCounts.folders, selectedNode] + ) return (
-
+

{label}{' '} - + {rootPath}

-
-
+
+
{renderTreeNode({ node: tree, @@ -198,8 +244,8 @@ const CoworkFolderTreeView = ({
-
-
+
+

Selected

@@ -236,54 +282,16 @@ const CoworkFolderTreeView = ({

-
- -
-

- File details -

-
- {( - selectedNode.kind === 'folder' - ? [ - { - label: 'Node type', - value: 'Folder' - }, - { - label: 'Subfolders', - value: selectedChildCounts.folders - }, - { - label: 'Files', - value: selectedChildCounts.files - } - ] - : [ - { - label: 'Node type', - value: - selectedNode.extension?.toUpperCase() ?? - 'File' - }, - { - label: 'Size', - value: selectedNode.size ?? '-' - }, - { - label: 'Children', - value: 0 - } - ] - ).map((item) => ( +
+ {selectedDetails.map((item) => (
- + {item.label} - + {item.value}
@@ -297,5 +305,3 @@ const CoworkFolderTreeView = ({ } export default CoworkFolderTreeView - - diff --git a/frontend/src/components/cowork/modes/intelligent-folder.tsx b/frontend/src/components/cowork/modes/intelligent-folder.tsx index 19bcc1807..5d389f495 100644 --- a/frontend/src/components/cowork/modes/intelligent-folder.tsx +++ b/frontend/src/components/cowork/modes/intelligent-folder.tsx @@ -1,6 +1,12 @@ import { AnimatePresence, motion } from 'framer-motion' import { FolderOpen, LoaderCircle } from 'lucide-react' -import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react' +import { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useState +} from 'react' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -14,6 +20,7 @@ import type { import type { ActionStep } from '@/typings/agent' import { cn } from '@/lib/utils' import { getCurrentWindow } from '@tauri-apps/api/window' +import { isCoworkBuildPanelActionVisible } from '../cowork-action-utils' import CoworkFolderBuild from '../intelligent-folder/cowork-folder-build' import CoworkFolderResult from '../intelligent-folder/cowork-folder-result' import CoworkFolderSource from '../intelligent-folder/cowork-folder-source' @@ -86,13 +93,23 @@ const IntelligentFolderMode = ({ }, [session?.id, session?.folder_tree_pair?.source_root]) const folderTreePair = loadedTreePair ?? session?.folder_tree_pair + const isFreshLoadedFolderAwaitingSessionSync = Boolean( + loadedTreePair && + loadedTreePair.source_root !== + session?.folder_tree_pair?.source_root + ) const modeResultTree: CoworkFolderTreeNode | null = folderTreePair?.result_tree ?? null + const hasVisibleBuildAction = Boolean( + isCoworkBuildPanelActionVisible(liveSession?.current_action) || + liveSession?.event_messages?.some((message) => + isCoworkBuildPanelActionVisible(message.action) + ) + ) const hasStreamingBuildActivity = Boolean( liveSession?.thinking.trim() || liveSession?.response.trim() || - liveSession?.current_action || - liveSession?.tool_calls.length || + hasVisibleBuildAction || liveSession?.is_awaiting_turn_action ) const isRunCompleted = session?.run_status === 'completed' @@ -106,8 +123,8 @@ const IntelligentFolderMode = ({ return } - if (isRunCompleted) { - setActiveStep('result') + if (isFreshLoadedFolderAwaitingSessionSync) { + setActiveStep('source') return } @@ -116,6 +133,11 @@ const IntelligentFolderMode = ({ return } + if (isRunCompleted) { + setActiveStep('result') + return + } + if ( isSending || hasStreamingBuildActivity || @@ -126,6 +148,7 @@ const IntelligentFolderMode = ({ } }, [ hasStreamingBuildActivity, + isFreshLoadedFolderAwaitingSessionSync, isProcessing, isRunCompleted, isSending, diff --git a/frontend/src/typings/agent.ts b/frontend/src/typings/agent.ts index 11b067887..0e3fc2c86 100644 --- a/frontend/src/typings/agent.ts +++ b/frontend/src/typings/agent.ts @@ -133,7 +133,7 @@ export enum ErrorCode { UNKNOWN_FORK_TYPE = 'unknown_fork_type', // Design mode DESIGN_SYNC_STATE_ERROR = 'design_sync_state_error', - SLIDE_DECK_SYNC_STATE_ERROR = 'slide_deck_sync_state_error', + SLIDE_DECK_SYNC_STATE_ERROR = 'slide_deck_sync_state_error' } export enum AgentEvent { @@ -336,6 +336,8 @@ export enum TOOL { ASK_USER_ENV = 'ask_user_env', ASK_USER_SELECT = 'ask_user_select', SKILL = 'Skill', + DESKTOP_SKILL_RUN = 'desktop_skill_run', + WASM_RUN = 'wasm_run', MOBILE_APP_INIT = 'mobile_app_init', RESTART_MOBILE_SERVER = 'restart_mobile_server' } @@ -473,6 +475,16 @@ export type ActionStep = { secrets?: Array<{ key: string; value: string }> attachments?: string[] skill?: string + skill_name?: string + module?: string + entrypoint?: string + keep_workspace?: boolean + timeout_seconds?: number + input_json?: Record + input_files?: Array<{ + path: string + name?: string + }> } result?: | string diff --git a/frontend/src/typings/cowork.ts b/frontend/src/typings/cowork.ts index 9fd86361b..59f674093 100644 --- a/frontend/src/typings/cowork.ts +++ b/frontend/src/typings/cowork.ts @@ -25,6 +25,7 @@ export interface CoworkFolderTreeNode { kind: 'folder' | 'file' extension?: string size?: string + last_modified?: string children?: CoworkFolderTreeNode[] } From c59c0bb14c0ead2b973ab2e9be69d4241f16fd0b Mon Sep 17 00:00:00 2001 From: namtranii Date: Wed, 15 Apr 2026 09:26:53 +0700 Subject: [PATCH 10/12] fix: enable headless shell execution on Windows --- frontend/src-tauri/src/cowork/desktop_tools/bash.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src-tauri/src/cowork/desktop_tools/bash.rs b/frontend/src-tauri/src/cowork/desktop_tools/bash.rs index 2e97031ac..dcc9fc311 100644 --- a/frontend/src-tauri/src/cowork/desktop_tools/bash.rs +++ b/frontend/src-tauri/src/cowork/desktop_tools/bash.rs @@ -6,6 +6,12 @@ use std::process::{Command, Stdio}; use std::thread; use std::time::{Duration, Instant}; +#[cfg(target_os = "windows")] +use std::os::windows::process::CommandExt; + +#[cfg(target_os = "windows")] +const CREATE_NO_WINDOW: u32 = 0x0800_0000; + const DEFAULT_BASH_TIMEOUT_SECS: u64 = 60; const MAX_BASH_TIMEOUT_SECS: u64 = 180; @@ -268,6 +274,12 @@ fn build_command_process( }; process.current_dir(working_directory); + #[cfg(target_os = "windows")] + { + // Keep shell execution headless in desktop mode to avoid flashing terminal windows. + process.creation_flags(CREATE_NO_WINDOW); + } + Ok(process) } From 07e696990bce11660d0356ab1a2a2f5e265d31b0 Mon Sep 17 00:00:00 2001 From: namtranii Date: Wed, 15 Apr 2026 15:36:58 +0700 Subject: [PATCH 11/12] fix: wasm process large file and enhance error handling --- .../src/cowork/agent_remote/socket.rs | 264 +++--- frontend/src-tauri/src/cowork/bootstrap.rs | 44 + .../desktop_runtime/wasm/chunking/pdf.rs | 53 +- .../desktop_runtime/wasm/chunking/splitter.rs | 785 ++++++++++++++++-- .../cowork/desktop_runtime/wasm/lifecycle.rs | 29 +- .../src/cowork/desktop_runtime/wasm/mod.rs | 112 ++- .../src/cowork/desktop_tools/wasm_run.rs | 102 ++- frontend/src-tauri/src/cowork/mod.rs | 1 + frontend/src-tauri/src/cowork/runtime.rs | 43 +- frontend/src-tauri/src/main.rs | 6 + 10 files changed, 1147 insertions(+), 292 deletions(-) create mode 100644 frontend/src-tauri/src/cowork/bootstrap.rs diff --git a/frontend/src-tauri/src/cowork/agent_remote/socket.rs b/frontend/src-tauri/src/cowork/agent_remote/socket.rs index aba5fd871..7b74a24c5 100644 --- a/frontend/src-tauri/src/cowork/agent_remote/socket.rs +++ b/frontend/src-tauri/src/cowork/agent_remote/socket.rs @@ -17,6 +17,7 @@ use crate::cowork::string_utils::normalize_optional_string; use crate::cowork::time_utils::now_iso; use rust_socketio::{ClientBuilder as SocketClientBuilder, Payload, TransportType}; use serde_json::{json, Value}; +use std::any::Any; use std::sync::mpsc; use std::time::{Duration, Instant}; use tauri::async_runtime::spawn_blocking; @@ -42,121 +43,163 @@ pub async fn run_remote_agent_request( }; spawn_blocking(move || { - let (tx, rx) = mpsc::channel::(); - let tx_chat = tx.clone(); - let tx_error = tx.clone(); - let mut last_status = Some(CoworkChatRunStatus::Thinking); - let mut desktop_runtime = DesktopToolRuntime::default(); - - let auth_payload = if let Some(session_id) = remote_session_id_for_auth.clone() { - json!({ - "token": access_token, - "session_uuid": session_id, - }) - } else { - json!({ - "token": access_token, - }) - }; - - let socket = SocketClientBuilder::new(api_base_url.as_str()) - .transport_type(TransportType::Websocket) - .auth(auth_payload) - .on("chat_event", move |payload, _socket| { - if let Some(event) = parse_socket_payload(payload).and_then(parse_socket_chat_event) - { - let _ = tx_chat.send(RemoteSocketSignal::ChatEvent(event)); - } - }) - .on("error", move |payload, _socket| { - let message = parse_socket_payload(payload) - .and_then(|value| { - let extracted = extract_error_message(&value); - if extracted.is_empty() { - None - } else { - Some(extracted) + std::panic::catch_unwind(std::panic::AssertUnwindSafe( + || -> Result { + let (tx, rx) = mpsc::channel::(); + let tx_chat = tx.clone(); + let tx_error = tx.clone(); + let mut last_status = Some(CoworkChatRunStatus::Thinking); + let mut desktop_runtime = DesktopToolRuntime::default(); + + let auth_payload = if let Some(session_id) = remote_session_id_for_auth.clone() { + json!({ + "token": access_token, + "session_uuid": session_id, + }) + } else { + json!({ + "token": access_token, + }) + }; + + let socket = SocketClientBuilder::new(api_base_url.as_str()) + .transport_type(TransportType::Websocket) + .auth(auth_payload) + .on("chat_event", move |payload, _socket| { + if let Err(payload) = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + if let Some(event) = + parse_socket_payload(payload).and_then(parse_socket_chat_event) + { + let _ = tx_chat.send(RemoteSocketSignal::ChatEvent(event)); + } + })) + { + eprintln!( + "[cowork] chat_event callback panicked: {}", + panic_payload_message(payload.as_ref()) + ); } }) - .unwrap_or_else(|| "Cowork agent socket returned an unknown error".to_string()); - let _ = tx_error.send(RemoteSocketSignal::ClientError(message)); - }) - .connect() - .map_err(|error| format!("Failed to connect Cowork agent socket: {error}"))?; - - let join_payload = if let Some(session_id) = remote_session_id.clone() { - json!({ "session_uuid": session_id }) - } else { - json!({}) - }; + .on("error", move |payload, _socket| { + if let Err(payload) = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let message = parse_socket_payload(payload) + .and_then(|value| { + let extracted = extract_error_message(&value); + if extracted.is_empty() { + None + } else { + Some(extracted) + } + }) + .unwrap_or_else(|| { + "Cowork agent socket returned an unknown error".to_string() + }); + let _ = tx_error.send(RemoteSocketSignal::ClientError(message)); + })) + { + eprintln!( + "[cowork] socket error callback panicked: {}", + panic_payload_message(payload.as_ref()) + ); + } + }) + .connect() + .map_err(|error| format!("Failed to connect Cowork agent socket: {error}"))?; - socket - .emit("join_session", join_payload) - .map_err(|error| format!("Failed to join Cowork agent session: {error}"))?; - - let active_session_id = wait_for_socket_session( - &rx, - remote_session_id.clone(), - &stream_context, - &mut last_status, - )?; - - // Inject "command" into content so the backend discriminated union can resolve it. - let mut enriched_content = command_payload.clone(); - if let Some(obj) = enriched_content.as_object_mut() { - obj.insert( - "command".to_string(), - Value::String(REMOTE_SOCKET_MESSAGE_TYPE.to_string()), - ); - } + let join_payload = if let Some(session_id) = remote_session_id.clone() { + json!({ "session_uuid": session_id }) + } else { + json!({}) + }; - socket - .emit( - "chat_message", - json!({ - "session_uuid": active_session_id, - "content": enriched_content, - }), - ) - .map_err(|error| format!("Failed to send Cowork agent message: {error}"))?; + socket + .emit("join_session", join_payload) + .map_err(|error| format!("Failed to join Cowork agent session: {error}"))?; + + let active_session_id = wait_for_socket_session( + &rx, + remote_session_id.clone(), + &stream_context, + &mut last_status, + )?; + + // Inject "command" into content so the backend discriminated union can resolve it. + let mut enriched_content = command_payload.clone(); + if let Some(obj) = enriched_content.as_object_mut() { + obj.insert( + "command".to_string(), + Value::String(REMOTE_SOCKET_MESSAGE_TYPE.to_string()), + ); + } - let continue_session_id = active_session_id.clone(); - let mut continue_run = - |run_id: &str, external_tool_results: Vec| -> Result<(), String> { socket .emit( "chat_message", json!({ - "session_uuid": continue_session_id, - "content": { - "command": "cowork_continue_run", - "run_id": run_id, - "confirmed": true, - "external_tool_results": external_tool_results, - } + "session_uuid": active_session_id, + "content": enriched_content, }), ) - .map_err(|error| format!("Failed to continue Cowork agent run: {error}")) - }; - - wait_for_remote_run_completion( - &rx, - &stream_context, - &mut last_status, - desktop_runtime_preset.as_ref(), - &mut desktop_runtime, - &mut continue_run, - )?; - let _ = socket.disconnect(); - - Ok(RemoteAgentRunOutcome { - runtime_session_id: active_session_id, - }) + .map_err(|error| format!("Failed to send Cowork agent message: {error}"))?; + + let continue_session_id = active_session_id.clone(); + let mut continue_run = |run_id: &str, + external_tool_results: Vec| + -> Result<(), String> { + socket + .emit( + "chat_message", + json!({ + "session_uuid": continue_session_id, + "content": { + "command": "cowork_continue_run", + "run_id": run_id, + "confirmed": true, + "external_tool_results": external_tool_results, + } + }), + ) + .map_err(|error| format!("Failed to continue Cowork agent run: {error}")) + }; + + wait_for_remote_run_completion( + &rx, + &stream_context, + &mut last_status, + desktop_runtime_preset.as_ref(), + &mut desktop_runtime, + &mut continue_run, + )?; + let _ = socket.disconnect(); + + Ok(RemoteAgentRunOutcome { + runtime_session_id: active_session_id, + }) + }, + )) + .map_err(|payload| { + format!( + "Cowork agent worker panicked: {}", + panic_payload_message(payload.as_ref()) + ) + })? }) .await .map_err(|error| format!("Cowork agent worker failed: {error}"))? } +fn panic_payload_message(payload: &(dyn Any + Send)) -> String { + if let Some(message) = payload.downcast_ref::<&str>() { + (*message).to_string() + } else if let Some(message) = payload.downcast_ref::() { + message.clone() + } else { + "non-string panic payload".to_string() + } +} + fn wait_for_socket_session( receiver: &mpsc::Receiver, fallback_session_id: Option, @@ -243,10 +286,8 @@ fn wait_for_remote_run_completion( } } - if should_ignore_terminal_after_continue( - &event, - pending_continue_run_id.as_deref(), - ) { + if should_ignore_terminal_after_continue(&event, pending_continue_run_id.as_deref()) + { continue; } if should_clear_pending_continue(&event, pending_continue_run_id.as_deref()) { @@ -382,11 +423,11 @@ pub fn normalize_event_name(raw: &str) -> String { #[cfg(test)] mod tests { + use super::super::types::RemoteSocketChatEvent; use super::{ normalize_event_name, parse_socket_chat_event, should_clear_pending_continue, should_ignore_terminal_after_continue, }; - use super::super::types::RemoteSocketChatEvent; use serde_json::json; #[test] @@ -401,8 +442,14 @@ mod tests { #[test] fn normalizes_reasoning_events_to_thinking_events() { - assert_eq!(normalize_event_name("agent.reasoning.start"), "agent_thinking_start"); - assert_eq!(normalize_event_name("agent.reasoning.delta"), "agent_thinking_delta"); + assert_eq!( + normalize_event_name("agent.reasoning.start"), + "agent_thinking_start" + ); + assert_eq!( + normalize_event_name("agent.reasoning.delta"), + "agent_thinking_delta" + ); assert_eq!(normalize_event_name("agent.reasoning"), "agent_thinking"); } @@ -418,7 +465,10 @@ mod tests { }; assert!(should_ignore_terminal_after_continue(&event, Some("run-1"))); - assert!(!should_ignore_terminal_after_continue(&event, Some("run-2"))); + assert!(!should_ignore_terminal_after_continue( + &event, + Some("run-2") + )); } #[test] diff --git a/frontend/src-tauri/src/cowork/bootstrap.rs b/frontend/src-tauri/src/cowork/bootstrap.rs new file mode 100644 index 000000000..a74db5c91 --- /dev/null +++ b/frontend/src-tauri/src/cowork/bootstrap.rs @@ -0,0 +1,44 @@ +use std::any::Any; +use std::backtrace::Backtrace; +use std::io::Write; + +pub fn install_panic_diagnostics() { + std::panic::set_hook(Box::new(|panic_info| { + let location = panic_info + .location() + .map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column())) + .unwrap_or_else(|| "".to_string()); + let thread = std::thread::current(); + let thread_name = thread.name().unwrap_or(""); + let payload = panic_payload_message(panic_info.payload()); + let backtrace = Backtrace::force_capture(); + let message = format!( + "[ii-agent panic] thread={thread_name} location={location}\nmessage: {payload}\nbacktrace:\n{backtrace}\n" + ); + + eprintln!("{message}"); + + let log_path = std::env::temp_dir().join("ii-agent-panic.log"); + if let Ok(mut file) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(&log_path) + { + let _ = writeln!(file, "{message}"); + } + })); +} + +pub fn maybe_startup_exit_code_from_env_args() -> Option { + crate::cowork::desktop_runtime::wasm::chunking::splitter::maybe_run_pdf_helper_from_env_args() +} + +fn panic_payload_message(payload: &(dyn Any + Send)) -> String { + if let Some(message) = payload.downcast_ref::<&str>() { + (*message).to_string() + } else if let Some(message) = payload.downcast_ref::() { + message.clone() + } else { + "non-string panic payload".to_string() + } +} diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs index 298411a46..c903ebc6c 100644 --- a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/pdf.rs @@ -101,22 +101,31 @@ impl ChunkPlanner for PdfChunker { // host side so chunk boundaries are accurate. For small/medium // files where memory is not at risk, skip the extra parse. if size > LARGE_FILE_THRESHOLD { - match splitter::count_pdf_pages(file_path) { - Ok(real_page_count) => { - return Ok(Some(plan_from_page_count(real_page_count, size))); - } - Err(_) => { - // If we cannot parse (corrupt file, etc.), fall back - // to size-based heuristic. The guest will fail later - // with a more informative error. - } - } + return Ok(Some(plan_large_file( + file_path, + size, + splitter::count_pdf_pages(file_path), + )?)); } Ok(Some(plan_from_size(size))) } } +fn plan_large_file( + file_path: &Path, + size: u64, + page_count: Result, +) -> Result { + let real_page_count = page_count.map_err(|error| { + format!( + "pdf chunker: failed to count pages for large PDF {}: {error}", + file_path.display() + ) + })?; + Ok(plan_from_page_count(real_page_count, size)) +} + /// Plan using the real page count (available for large PDFs parsed on /// the host side via [`splitter::count_pdf_pages`]). pub fn plan_from_page_count(page_count: u32, _file_size: u64) -> ChunkPlan { @@ -236,13 +245,17 @@ pub fn plan_from_size(size: u64) -> ChunkPlan { #[cfg(test)] mod tests { use super::*; + use std::path::Path; #[test] fn small_file_is_single_with_default_limits() { let plan = plan_from_size(5 * 1024 * 1024); // 5 MB match plan { ChunkPlan::Single { limits } => { - assert_eq!(limits.max_memory_bytes, RuntimeLimits::defaults().max_memory_bytes); + assert_eq!( + limits.max_memory_bytes, + RuntimeLimits::defaults().max_memory_bytes + ); } other => panic!("expected Single, got {:?}", other), } @@ -278,10 +291,7 @@ mod tests { .and_then(|v| v.as_array()) .expect("page_range array"); assert_eq!(range[0].as_u64(), Some(1)); - assert_eq!( - range[1].as_u64(), - Some(LARGE_FILE_CHUNK_PAGES as u64) - ); + assert_eq!(range[1].as_u64(), Some(LARGE_FILE_CHUNK_PAGES as u64)); // Second chunk starts right after the first. let second = &chunks[1]; let overlay2 = second.input_json_overlay.as_object().expect("overlay obj"); @@ -309,6 +319,19 @@ mod tests { ); } + #[test] + fn large_file_page_count_failure_does_not_fall_back_to_heuristic() { + let err = plan_large_file( + Path::new("/tmp/heavy.pdf"), + LARGE_FILE_THRESHOLD + 1, + Err("helper crashed".to_string()), + ) + .expect_err("large-file helper failure should be surfaced"); + + assert!(err.contains("failed to count pages for large PDF /tmp/heavy.pdf")); + assert!(err.contains("helper crashed")); + } + #[test] fn non_extract_text_op_is_none() { let chunker = PdfChunker::default(); diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs index aafc6230e..ebcbb0dc6 100644 --- a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/chunking/splitter.rs @@ -10,6 +10,15 @@ //! files (one per chunk), then pass each chunk-file to its own sandbox //! call. The guest sees a small file and stays within its memory budget. //! +//! One subtlety: parsing/splitting a huge PDF with `lopdf` can itself +//! consume a large amount of native heap. If we do that work inside the +//! Tauri process, the OS can kill the entire desktop app before Wasmtime +//! gets a chance to report a sandboxed memory error. To keep the UI +//! alive, production builds run the expensive host-side PDF work in a +//! short-lived helper process spawned from the current executable. If the +//! helper panics, aborts, or is OOM-killed, the parent sees a normal tool +//! error instead of losing the whole app. +//! //! ## Scope //! //! v1 supports PDF splitting only. DOCX/XLSX/PPTX are fundamentally @@ -19,8 +28,20 @@ //! guest. This means large DOCX/XLSX/PPTX may still OOM in the guest, //! which the skill body documents as a limitation. +use crate::cowork::desktop_runtime::wasm::WasmRunResult; +use lopdf::Document; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::any::Any; +use std::ffi::{OsStr, OsString}; use std::fs; use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +const PDF_HELPER_FLAG: &str = "--ii-pdf-helper"; +const PDF_HELPER_COUNT_PAGES: &str = "count-pages"; +const PDF_HELPER_SPLIT_PAGES: &str = "split-pages"; +const PDF_HELPER_RUN_PROCESSOR: &str = "run-pdf-processor"; /// Split a PDF file into multiple smaller PDFs, each containing a /// contiguous range of pages. Returns a list of `(chunk_file_path, @@ -37,73 +58,112 @@ pub fn split_pdf_pages( output_dir: &Path, pages_per_chunk: u32, ) -> Result, String> { - // First pass: count pages without keeping the full doc in memory. - let page_numbers = { - let doc = lopdf::Document::load(source) - .map_err(|e| format!("split_pdf: failed to load {}: {e}", source.display()))?; - let pages = doc.get_pages(); - let mut nums: Vec = pages.keys().copied().collect(); - nums.sort_unstable(); - nums - // `doc` drops here — releases memory before we start splitting. - }; - let total = page_numbers.len() as u32; - - if total == 0 { - return Ok(vec![]); + if cfg!(test) { + return split_pdf_pages_in_process(source, output_dir, pages_per_chunk); } - fs::create_dir_all(output_dir) - .map_err(|e| format!("split_pdf: mkdir {}: {e}", output_dir.display()))?; + let result_path = helper_result_path("split"); + let output = run_helper_process(&[ + OsString::from(PDF_HELPER_FLAG), + OsString::from(PDF_HELPER_SPLIT_PAGES), + source.as_os_str().to_os_string(), + output_dir.as_os_str().to_os_string(), + OsString::from(pages_per_chunk.to_string()), + result_path.as_os_str().to_os_string(), + ])?; - let mut chunks = Vec::new(); - let mut chunk_idx = 0u32; - let mut start = 0usize; + if !output.status.success() { + let _ = fs::remove_file(&result_path); + return Err(format!( + "split_pdf: helper process failed{}{}", + format_exit_status(&output.status), + format_stderr_suffix(&output.stderr) + )); + } - while start < page_numbers.len() { - let end = (start + pages_per_chunk as usize).min(page_numbers.len()); - let chunk_pages: std::collections::HashSet = - page_numbers[start..end].iter().copied().collect(); + let parsed: SplitPdfPagesOutput = read_helper_result(&result_path)?; + let _ = fs::remove_file(&result_path); + Ok(parsed.chunks) +} + +fn split_pdf_pages_in_process( + source: &Path, + output_dir: &Path, + pages_per_chunk: u32, +) -> Result, String> { + run_pdf_host_operation("split_pdf", || { + if pages_per_chunk == 0 { + return Err("split_pdf: pages_per_chunk must be greater than 0".to_string()); + } - // Reload from disk for each chunk so we never hold more than - // 1 copy of the document in memory at a time. This trades I/O - // (re-read source N times) for memory (peak = 1× doc size - // instead of N× doc size from cloning). - let mut chunk_doc = lopdf::Document::load(source) - .map_err(|e| format!("split_pdf: reload for chunk {chunk_idx}: {e}"))?; + // First pass: count pages without keeping the full doc in memory. + let page_numbers = { + let doc = lopdf::Document::load(source) + .map_err(|e| format!("split_pdf: failed to load {}: {e}", source.display()))?; + let pages = doc.get_pages(); + let mut nums: Vec = pages.keys().copied().collect(); + nums.sort_unstable(); + nums + // `doc` drops here — releases memory before we start splitting. + }; + let total = page_numbers.len() as u32; - let pages_to_remove: Vec = chunk_doc - .get_pages() - .keys() - .copied() - .filter(|num| !chunk_pages.contains(num)) - .collect(); - for page_num in pages_to_remove { - chunk_doc.delete_pages(&[page_num]); + if total == 0 { + return Ok(vec![]); } - let chunk_path = output_dir.join(format!("chunk_{chunk_idx}.pdf")); - chunk_doc - .save(&chunk_path) - .map_err(|e| format!("split_pdf: save chunk_{chunk_idx}: {e}"))?; + fs::create_dir_all(output_dir) + .map_err(|e| format!("split_pdf: mkdir {}: {e}", output_dir.display()))?; - chunks.push(PdfChunkFile { - path: chunk_path, - page_start: (start as u32) + 1, - page_end: end as u32, - total_pages: total, - }); + let mut chunks = Vec::new(); + let mut chunk_idx = 0u32; + let mut start = 0usize; - start = end; - chunk_idx += 1; - // `chunk_doc` drops here — memory freed before next iteration. - } + while start < page_numbers.len() { + let end = (start + pages_per_chunk as usize).min(page_numbers.len()); + let chunk_pages: std::collections::HashSet = + page_numbers[start..end].iter().copied().collect(); - Ok(chunks) + // Reload from disk for each chunk so we never hold more than + // 1 copy of the document in memory at a time. This trades I/O + // (re-read source N times) for memory (peak = 1× doc size + // instead of N× doc size from cloning). + let mut chunk_doc = lopdf::Document::load(source) + .map_err(|e| format!("split_pdf: reload for chunk {chunk_idx}: {e}"))?; + + let pages_to_remove: Vec = chunk_doc + .get_pages() + .keys() + .copied() + .filter(|num| !chunk_pages.contains(num)) + .collect(); + for page_num in pages_to_remove { + chunk_doc.delete_pages(&[page_num]); + } + + let chunk_path = output_dir.join(format!("chunk_{chunk_idx}.pdf")); + chunk_doc + .save(&chunk_path) + .map_err(|e| format!("split_pdf: save chunk_{chunk_idx}: {e}"))?; + + chunks.push(PdfChunkFile { + path: chunk_path, + page_start: (start as u32) + 1, + page_end: end as u32, + total_pages: total, + }); + + start = end; + chunk_idx += 1; + // `chunk_doc` drops here — memory freed before next iteration. + } + + Ok(chunks) + }) } /// Metadata about a host-side chunk file produced by [`split_pdf_pages`]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PdfChunkFile { /// Path to the chunk PDF on the host filesystem. pub path: PathBuf, @@ -115,15 +175,498 @@ pub struct PdfChunkFile { pub total_pages: u32, } - /// Count pages in a PDF without extracting content. Cheap compared to /// full extraction — we only parse the xref table and page tree, never /// decompress streams. Used by the chunker when it needs an accurate /// page count rather than the size-based heuristic. pub fn count_pdf_pages(source: &Path) -> Result { - let doc = lopdf::Document::load(source) - .map_err(|e| format!("count_pdf_pages: failed to load {}: {e}", source.display()))?; - Ok(doc.get_pages().len() as u32) + if cfg!(test) { + return count_pdf_pages_in_process(source); + } + + let result_path = helper_result_path("count"); + let output = run_helper_process(&[ + OsString::from(PDF_HELPER_FLAG), + OsString::from(PDF_HELPER_COUNT_PAGES), + source.as_os_str().to_os_string(), + result_path.as_os_str().to_os_string(), + ])?; + + if !output.status.success() { + let _ = fs::remove_file(&result_path); + return Err(format!( + "count_pdf_pages: helper process failed{}{}", + format_exit_status(&output.status), + format_stderr_suffix(&output.stderr) + )); + } + + let parsed: CountPdfPagesOutput = read_helper_result(&result_path)?; + let _ = fs::remove_file(&result_path); + Ok(parsed.page_count) +} + +pub fn run_pdf_processor_in_helper( + input_file: &Path, + input_json: Option, +) -> Result { + let request_path = helper_result_path("run-pdf-processor-request"); + let result_path = helper_result_path("run-pdf-processor-result"); + + let request = PdfProcessorRunRequest { + input_file: input_file.to_path_buf(), + input_json, + }; + write_helper_result(&request_path, &request)?; + + let output = run_helper_process(&[ + OsString::from(PDF_HELPER_FLAG), + OsString::from(PDF_HELPER_RUN_PROCESSOR), + request_path.as_os_str().to_os_string(), + result_path.as_os_str().to_os_string(), + ])?; + + let _ = fs::remove_file(&request_path); + + if !output.status.success() { + let _ = fs::remove_file(&result_path); + return Err(format!( + "run_pdf_processor: helper process failed{}{}", + format_exit_status(&output.status), + format_stderr_suffix(&output.stderr) + )); + } + + let parsed: PdfProcessorRunOutput = read_helper_result(&result_path)?; + let _ = fs::remove_file(&result_path); + Ok(parsed.result) +} + +fn count_pdf_pages_in_process(source: &Path) -> Result { + run_pdf_host_operation("count_pdf_pages", || { + let doc = lopdf::Document::load(source) + .map_err(|e| format!("count_pdf_pages: failed to load {}: {e}", source.display()))?; + Ok(doc.get_pages().len() as u32) + }) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct CountPdfPagesOutput { + page_count: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SplitPdfPagesOutput { + chunks: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct PdfProcessorRunRequest { + input_file: PathBuf, + input_json: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct PdfProcessorRunOutput { + result: WasmRunResult, +} + +#[derive(Debug)] +struct PdfProcessorInput { + op: String, + page_range: Option<(u32, u32)>, +} + +#[derive(Debug)] +enum PdfHelperCommand { + CountPages { + source: PathBuf, + result_path: PathBuf, + }, + SplitPages { + source: PathBuf, + output_dir: PathBuf, + pages_per_chunk: u32, + result_path: PathBuf, + }, + RunPdfProcessor { + request_path: PathBuf, + result_path: PathBuf, + }, +} + +pub fn maybe_run_pdf_helper_from_env_args() -> Option { + let args: Vec = std::env::args_os().skip(1).collect(); + let command = match parse_pdf_helper_args(args) { + Ok(Some(command)) => command, + Ok(None) => return None, + Err(error) => { + eprintln!("ii pdf helper: {error}"); + return Some(2); + } + }; + + let outcome = match command { + PdfHelperCommand::CountPages { + source, + result_path, + } => count_pdf_pages_in_process(&source).and_then(|page_count| { + write_helper_result(&result_path, &CountPdfPagesOutput { page_count }) + }), + PdfHelperCommand::SplitPages { + source, + output_dir, + pages_per_chunk, + result_path, + } => split_pdf_pages_in_process(&source, &output_dir, pages_per_chunk) + .and_then(|chunks| write_helper_result(&result_path, &SplitPdfPagesOutput { chunks })), + PdfHelperCommand::RunPdfProcessor { + request_path, + result_path, + } => read_helper_result::(&request_path) + .and_then(run_pdf_processor_in_process) + .and_then(|result| { + write_helper_result(&result_path, &PdfProcessorRunOutput { result }) + }), + }; + + match outcome { + Ok(()) => Some(0), + Err(error) => { + eprintln!("ii pdf helper: {error}"); + Some(1) + } + } +} + +fn parse_pdf_helper_args( + args: impl IntoIterator, +) -> Result, String> { + let mut args = args.into_iter(); + loop { + let Some(flag) = args.next() else { + return Ok(None); + }; + if flag == OsStr::new(PDF_HELPER_FLAG) { + break; + } + } + + let Some(command) = args.next() else { + return Err("missing helper command".to_string()); + }; + + match command.to_string_lossy().as_ref() { + PDF_HELPER_COUNT_PAGES => { + let source = args + .next() + .map(PathBuf::from) + .ok_or_else(|| "count-pages requires ".to_string())?; + let result_path = args + .next() + .map(PathBuf::from) + .ok_or_else(|| "count-pages requires ".to_string())?; + if args.next().is_some() { + return Err("count-pages received unexpected extra arguments".to_string()); + } + Ok(Some(PdfHelperCommand::CountPages { + source, + result_path, + })) + } + PDF_HELPER_SPLIT_PAGES => { + let source = args + .next() + .map(PathBuf::from) + .ok_or_else(|| "split-pages requires ".to_string())?; + let output_dir = args + .next() + .map(PathBuf::from) + .ok_or_else(|| "split-pages requires ".to_string())?; + let pages_per_chunk = args + .next() + .ok_or_else(|| "split-pages requires ".to_string())? + .to_string_lossy() + .parse::() + .map_err(|error| format!("split-pages invalid pages_per_chunk: {error}"))?; + if pages_per_chunk == 0 { + return Err("split-pages requires pages_per_chunk > 0".to_string()); + } + let result_path = args + .next() + .map(PathBuf::from) + .ok_or_else(|| "split-pages requires ".to_string())?; + if args.next().is_some() { + return Err("split-pages received unexpected extra arguments".to_string()); + } + Ok(Some(PdfHelperCommand::SplitPages { + source, + output_dir, + pages_per_chunk, + result_path, + })) + } + PDF_HELPER_RUN_PROCESSOR => { + let request_path = args + .next() + .map(PathBuf::from) + .ok_or_else(|| "run-pdf-processor requires ".to_string())?; + let result_path = args + .next() + .map(PathBuf::from) + .ok_or_else(|| "run-pdf-processor requires ".to_string())?; + if args.next().is_some() { + return Err("run-pdf-processor received unexpected extra arguments".to_string()); + } + Ok(Some(PdfHelperCommand::RunPdfProcessor { + request_path, + result_path, + })) + } + other => Err(format!("unknown helper command '{other}'")), + } +} + +fn helper_result_path(label: &str) -> PathBuf { + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| duration.as_nanos()) + .unwrap_or_default(); + std::env::temp_dir().join(format!("ii-pdf-helper-{label}-{nanos}.json")) +} + +fn run_helper_process(args: &[OsString]) -> Result { + let current_exe = std::env::current_exe() + .map_err(|error| format!("cannot locate current executable: {error}"))?; + Command::new(current_exe) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .output() + .map_err(|error| format!("failed to spawn pdf helper process: {error}")) +} + +fn write_helper_result(result_path: &Path, value: &T) -> Result<(), String> { + if let Some(parent) = result_path.parent() { + fs::create_dir_all(parent) + .map_err(|error| format!("helper: mkdir {}: {error}", parent.display()))?; + } + let bytes = + serde_json::to_vec(value).map_err(|error| format!("helper: serialize result: {error}"))?; + fs::write(result_path, bytes) + .map_err(|error| format!("helper: write {}: {error}", result_path.display())) +} + +fn read_helper_result Deserialize<'de>>(result_path: &Path) -> Result { + let bytes = fs::read(result_path).map_err(|error| { + format!( + "helper result missing at {}: {error}", + result_path.display() + ) + })?; + serde_json::from_slice(&bytes).map_err(|error| { + format!( + "helper result parse failed at {}: {error}", + result_path.display() + ) + }) +} + +fn format_exit_status(status: &std::process::ExitStatus) -> String { + match status.code() { + Some(code) => format!(" (exit code {code})"), + None => " (terminated by signal)".to_string(), + } +} + +fn format_stderr_suffix(stderr: &[u8]) -> String { + let text = String::from_utf8_lossy(stderr); + let trimmed = text.trim(); + if trimmed.is_empty() { + String::new() + } else { + format!(": {trimmed}") + } +} + +fn run_pdf_host_operation(operation: &str, work: F) -> Result +where + F: FnOnce() -> Result, +{ + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(work)) { + Ok(result) => result, + Err(payload) => Err(format!( + "{operation}: host PDF processing panicked: {}", + panic_payload_message(payload.as_ref()) + )), + } +} + +fn panic_payload_message(payload: &(dyn Any + Send)) -> String { + if let Some(message) = payload.downcast_ref::<&str>() { + (*message).to_string() + } else if let Some(message) = payload.downcast_ref::() { + message.clone() + } else { + "non-string panic payload".to_string() + } +} + +fn run_pdf_processor_in_process(request: PdfProcessorRunRequest) -> Result { + run_pdf_host_operation("run_pdf_processor", || { + let started_at = std::time::Instant::now(); + let input = parse_pdf_processor_input(request.input_json.as_ref())?; + let document = Document::load(&request.input_file) + .map_err(|error| format!("failed to load {}: {error}", request.input_file.display()))?; + + let output_json = match input.op.as_str() { + "extract_text" => pdf_processor_extract_text(&document, input.page_range.as_ref())?, + "metadata" => pdf_processor_metadata(&document)?, + other => return Err(format!("unknown operation '{other}'")), + }; + + Ok(WasmRunResult { + module: "pdf_processor".to_string(), + stdout: format!("pdf_processor: op={} ok\n", input.op), + stderr: String::new(), + duration_ms: started_at.elapsed().as_millis(), + output_json: Some(output_json), + output_files: Vec::new(), + scratch_dir: None, + }) + }) +} + +fn parse_pdf_processor_input(input_json: Option<&Value>) -> Result { + let value = input_json + .cloned() + .ok_or_else(|| "failed to read /workspace/input.json: not provided".to_string())?; + let op = value + .get("op") + .and_then(Value::as_str) + .map(str::to_string) + .ok_or_else(|| "input.json is missing the 'op' field".to_string())?; + + let page_range = match value.get("page_range") { + Some(Value::Null) | None => None, + Some(Value::Array(items)) => { + if items.len() != 2 { + return Err(format!( + "page_range must be a 2-element array [start, end], got {} elements", + items.len() + )); + } + let start = items[0] + .as_u64() + .ok_or_else(|| "page_range[0] must be a non-negative integer".to_string())?; + let end = items[1] + .as_u64() + .ok_or_else(|| "page_range[1] must be a non-negative integer".to_string())?; + Some((start as u32, end as u32)) + } + Some(other) => { + return Err(format!( + "page_range must be an array or null, got {}", + json_kind(other) + )); + } + }; + + Ok(PdfProcessorInput { op, page_range }) +} + +fn json_kind(value: &Value) -> &'static str { + match value { + Value::Null => "null", + Value::Bool(_) => "bool", + Value::Number(_) => "number", + Value::String(_) => "string", + Value::Array(_) => "array", + Value::Object(_) => "object", + } +} + +fn pdf_processor_extract_text( + document: &Document, + page_range: Option<&(u32, u32)>, +) -> Result { + let pages = document.get_pages(); + let mut ordered: Vec = pages.keys().copied().collect(); + ordered.sort_unstable(); + let total_pages = ordered.len() as u32; + + let (slice, page_offset) = if let Some(&(start, end)) = page_range { + if start == 0 { + return Err("page_range start must be 1 or greater".to_string()); + } + if start > end { + return Ok(serde_json::json!({ + "pages": Vec::::new(), + "page_offset": start, + "total_pages": total_pages, + })); + } + + let start_idx = (start - 1) as usize; + if start_idx >= ordered.len() { + return Ok(serde_json::json!({ + "pages": Vec::::new(), + "page_offset": start, + "total_pages": total_pages, + })); + } + + let end_idx = (end as usize).min(ordered.len()); + (&ordered[start_idx..end_idx], start) + } else { + (&ordered[..], 1u32) + }; + + let mut pages_out = Vec::with_capacity(slice.len()); + for &page_num in slice { + let text = document + .extract_text(&[page_num]) + .map_err(|error| format!("page {page_num} extraction failed: {error}"))?; + pages_out.push(text); + } + + Ok(serde_json::json!({ + "pages": pages_out, + "page_offset": page_offset, + "total_pages": total_pages, + })) +} + +fn pdf_processor_metadata(document: &Document) -> Result { + let page_count = document.get_pages().len(); + let info = document.trailer.get(b"Info").ok(); + let mut title: Option = None; + let mut author: Option = None; + + if let Some(info_ref) = info { + if let Ok(object_id) = info_ref.as_reference() { + if let Ok(info_dict) = document + .get_object(object_id) + .and_then(|object| object.as_dict()) + { + title = read_pdf_text_field(info_dict, b"Title"); + author = read_pdf_text_field(info_dict, b"Author"); + } + } + } + + Ok(serde_json::json!({ + "title": title, + "author": author, + "page_count": page_count, + })) +} + +fn read_pdf_text_field(dict: &lopdf::Dictionary, key: &[u8]) -> Option { + let object = dict.get(key).ok()?; + let bytes = object.as_str().ok()?; + Some(String::from_utf8_lossy(bytes).into_owned()) } #[cfg(test)] @@ -158,4 +701,132 @@ mod tests { assert_eq!(chunk_doc.get_pages().len(), 1); fs::remove_dir_all(&output_dir).ok(); } + + #[test] + fn helper_args_ignore_normal_app_launch() { + let parsed = parse_pdf_helper_args(Vec::::new()).expect("parse ok"); + assert!(parsed.is_none()); + } + + #[test] + fn helper_args_parse_count_pages() { + let parsed = parse_pdf_helper_args([ + OsString::from(PDF_HELPER_FLAG), + OsString::from(PDF_HELPER_COUNT_PAGES), + OsString::from("/tmp/input.pdf"), + OsString::from("/tmp/result.json"), + ]) + .expect("parse ok"); + + match parsed { + Some(PdfHelperCommand::CountPages { + source, + result_path, + }) => { + assert_eq!(source, PathBuf::from("/tmp/input.pdf")); + assert_eq!(result_path, PathBuf::from("/tmp/result.json")); + } + other => panic!("expected CountPages, got {other:?}"), + } + } + + #[test] + fn helper_args_find_flag_after_prefix_args() { + let parsed = parse_pdf_helper_args([ + OsString::from("--tauri-dev"), + OsString::from(PDF_HELPER_FLAG), + OsString::from(PDF_HELPER_COUNT_PAGES), + OsString::from("/tmp/input.pdf"), + OsString::from("/tmp/result.json"), + ]) + .expect("parse ok"); + + match parsed { + Some(PdfHelperCommand::CountPages { + source, + result_path, + }) => { + assert_eq!(source, PathBuf::from("/tmp/input.pdf")); + assert_eq!(result_path, PathBuf::from("/tmp/result.json")); + } + other => panic!("expected CountPages, got {other:?}"), + } + } + + #[test] + fn helper_args_parse_split_pages() { + let parsed = parse_pdf_helper_args([ + OsString::from(PDF_HELPER_FLAG), + OsString::from(PDF_HELPER_SPLIT_PAGES), + OsString::from("/tmp/input.pdf"), + OsString::from("/tmp/chunks"), + OsString::from("20"), + OsString::from("/tmp/result.json"), + ]) + .expect("parse ok"); + + match parsed { + Some(PdfHelperCommand::SplitPages { + source, + output_dir, + pages_per_chunk, + result_path, + }) => { + assert_eq!(source, PathBuf::from("/tmp/input.pdf")); + assert_eq!(output_dir, PathBuf::from("/tmp/chunks")); + assert_eq!(pages_per_chunk, 20); + assert_eq!(result_path, PathBuf::from("/tmp/result.json")); + } + other => panic!("expected SplitPages, got {other:?}"), + } + } + + #[test] + fn pdf_processor_in_process_extracts_text_without_wasm() { + let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hello.pdf"); + let result = run_pdf_processor_in_process(PdfWasmRunRequest { + input_file: fixture, + input_json: Some(serde_json::json!({ "op": "extract_text" })), + session_id: "test-session".to_string(), + wall_timeout_secs: 30, + max_memory_bytes: 256 * 1024 * 1024, + max_fuel: 1_000_000, + }) + .expect("native pdf processor runs"); + + assert_eq!(result.module, "pdf_processor"); + assert!(result.stdout.contains("pdf_processor: op=extract_text ok")); + let output = result.output_json.expect("output json"); + let pages = output + .get("pages") + .and_then(Value::as_array) + .expect("pages array"); + assert_eq!(pages.len(), 1); + } + + #[test] + fn pdf_processor_in_process_handles_page_range_contract() { + let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hello.pdf"); + let result = run_pdf_processor_in_process(PdfWasmRunRequest { + input_file: fixture, + input_json: Some(serde_json::json!({ + "op": "extract_text", + "page_range": [10, 20] + })), + session_id: "test-session".to_string(), + wall_timeout_secs: 30, + max_memory_bytes: 256 * 1024 * 1024, + max_fuel: 1_000_000, + }) + .expect("native pdf processor runs"); + + let output = result.output_json.expect("output json"); + let pages = output + .get("pages") + .and_then(Value::as_array) + .expect("pages array"); + assert!(pages.is_empty()); + assert_eq!(output.get("page_offset").and_then(Value::as_u64), Some(10)); + assert_eq!(output.get("total_pages").and_then(Value::as_u64), Some(1)); + } } diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs index f72d71749..3833e12cd 100644 --- a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/lifecycle.rs @@ -27,7 +27,7 @@ //! the reaper half-way through. use super::WasmRuntime; -use std::sync::{Arc, Mutex, OnceLock}; +use std::sync::{Arc, Mutex, MutexGuard, OnceLock}; use std::thread; use std::time::{Duration, Instant}; @@ -98,7 +98,7 @@ fn spawn_reaper(state: Arc>, interval: Duration) { /// timeout **and** no leases are outstanding. Factored out so tests can /// drive it synchronously. fn reap_once(state: &Arc>) -> bool { - let mut guard = state.lock().expect("wasm lifecycle state poisoned"); + let mut guard = lock_or_recover(state); if guard.runtime.is_none() { return false; } @@ -120,19 +120,14 @@ fn reap_once(state: &Arc>) -> bool { pub fn acquire_runtime() -> Result { let state = global_state(); let runtime_arc = { - let mut guard = state.lock().expect("wasm lifecycle state poisoned"); + let mut guard = lock_or_recover(&state); if guard.runtime.is_none() { let new_runtime = WasmRuntime::new()?; guard.runtime = Some(Arc::new(new_runtime)); } guard.in_flight = guard.in_flight.saturating_add(1); guard.last_used = Instant::now(); - Arc::clone( - guard - .runtime - .as_ref() - .expect("runtime just initialised above"), - ) + Arc::clone(guard.runtime.as_ref().unwrap_or_else(|| unreachable!())) }; Ok(RuntimeLease { runtime: runtime_arc, @@ -163,15 +158,18 @@ impl std::ops::Deref for RuntimeLease { impl Drop for RuntimeLease { fn drop(&mut self) { - let mut guard = self - .state - .lock() - .expect("wasm lifecycle state poisoned during drop"); + let mut guard = lock_or_recover(&self.state); guard.in_flight = guard.in_flight.saturating_sub(1); guard.last_used = Instant::now(); } } +fn lock_or_recover(mutex: &Mutex) -> MutexGuard<'_, T> { + mutex + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) +} + #[cfg(test)] mod tests { use super::*; @@ -182,8 +180,9 @@ mod tests { Arc::new(Mutex::new(LifecycleState::new(timeout))) } - fn lease(state: &Arc>) -> Result - { + fn lease( + state: &Arc>, + ) -> Result { let mut guard = state.lock().unwrap(); if guard.runtime.is_none() { guard.runtime = Some(Arc::new(WasmRuntime::new()?)); diff --git a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs index adee3285b..1d5da8caa 100644 --- a/frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs +++ b/frontend/src-tauri/src/cowork/desktop_runtime/wasm/mod.rs @@ -65,10 +65,11 @@ pub use lifecycle::acquire_runtime; use super::{RuntimeKind, RuntimeLimits}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::any::Any; use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::time::{Duration, Instant}; use std::{io, thread}; use wasmtime::{Config, Engine, Linker, Module, Store, StoreLimits, StoreLimitsBuilder}; @@ -105,7 +106,7 @@ impl ModuleRegistry { /// Register a module by its raw WASM bytes. The byte buffer is stored /// behind an `Arc` so repeated lookups are cheap. pub fn register_bytes(&self, name: impl Into, bytes: Vec) { - self.entries.lock().expect("module registry poisoned").insert( + lock_or_recover(&self.entries).insert( name.into(), ModuleEntry { source: ModuleSource::Bytes(Arc::new(bytes)), @@ -116,7 +117,7 @@ impl ModuleRegistry { /// Register a module by its WAT (text format) source. Useful for /// bundling small helpers without having to ship pre-compiled bytes. pub fn register_wat(&self, name: impl Into, wat: impl Into) { - self.entries.lock().expect("module registry poisoned").insert( + lock_or_recover(&self.entries).insert( name.into(), ModuleEntry { source: ModuleSource::Wat(Arc::new(wat.into())), @@ -128,7 +129,7 @@ impl ModuleRegistry { /// so it is safe to register modules that will only become available /// at runtime. pub fn register_path(&self, name: impl Into, path: PathBuf) { - self.entries.lock().expect("module registry poisoned").insert( + lock_or_recover(&self.entries).insert( name.into(), ModuleEntry { source: ModuleSource::Path(path), @@ -137,31 +138,18 @@ impl ModuleRegistry { } pub fn has(&self, name: &str) -> bool { - self.entries - .lock() - .expect("module registry poisoned") - .contains_key(name) + lock_or_recover(&self.entries).contains_key(name) } pub fn names(&self) -> Vec { - let mut names: Vec = self - .entries - .lock() - .expect("module registry poisoned") - .keys() - .cloned() - .collect(); + let mut names: Vec = lock_or_recover(&self.entries).keys().cloned().collect(); names.sort(); names } fn lookup(&self, name: &str) -> Option { - self.entries - .lock() - .expect("module registry poisoned") - .get(name) - .cloned() - } + lock_or_recover(&self.entries).get(name).cloned() + } fn load_module(&self, engine: &Engine, name: &str) -> Result { let entry = self @@ -341,8 +329,8 @@ impl WasmRuntime { config.wasm_backtrace(true); config.wasm_bulk_memory(true); config.wasm_multi_value(true); - let engine = Engine::new(&config) - .map_err(|error| WasmRunError::ModuleLoad(error.to_string()))?; + let engine = + Engine::new(&config).map_err(|error| WasmRunError::ModuleLoad(error.to_string()))?; let runtime = Self { engine, registry: ModuleRegistry::new(), @@ -379,7 +367,10 @@ impl WasmRuntime { // desktop development. #[cfg(debug_assertions)] { - let base = concat!(env!("CARGO_MANIFEST_DIR"), "/src/cowork/desktop_runtime/wasm"); + let base = concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/cowork/desktop_runtime/wasm" + ); freshness::check_freshness( "pdf_processor", &format!("{base}/modules/pdf_processor.wasm"), @@ -414,11 +405,22 @@ impl WasmRuntime { /// Configure the root directory used to create per-call scratch /// workspaces. Typically `{app_data}/cowork/wasm-scratch`. pub fn set_scratch_root(&self, root: PathBuf) { - *self.scratch_root.lock().expect("scratch root poisoned") = Some(root); + *lock_or_recover(&self.scratch_root) = Some(root); } /// Execute a single WASM call and return a structured result. pub fn run(&self, request: WasmRunRequest) -> Result { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| self.run_inner(request))).map_err( + |payload| { + WasmRunError::Execution(format!( + "WASM runtime panicked: {}", + panic_payload_message(payload.as_ref()) + )) + }, + )? + } + + fn run_inner(&self, request: WasmRunRequest) -> Result { let limits = request.limits.unwrap_or_else(RuntimeLimits::defaults); // --- Prepare scratch workspace --- @@ -459,7 +461,9 @@ impl WasmRuntime { } // --- Load module --- - let module = self.registry.load_module(&self.engine, &request.module_name)?; + let module = self + .registry + .load_module(&self.engine, &request.module_name)?; // --- Build WASI context --- let stdout_pipe = wasmtime_wasi::pipe::MemoryOutputPipe::new(256 * 1024); @@ -598,7 +602,7 @@ impl WasmRuntime { } fn build_scratch_dir(&self, session_id: Option<&str>) -> Result { - let base = match self.scratch_root.lock().expect("scratch root poisoned").clone() { + let base = match lock_or_recover(&self.scratch_root).clone() { Some(root) => root, None => std::env::temp_dir().join("ii-cowork-wasm"), }; @@ -624,7 +628,7 @@ impl WasmRuntime { /// Called by the cowork session gateway when a session is closed or /// deleted. Silently succeeds if the session has no scratch state. pub fn sweep_session(&self, session_id: &str) -> Result<(), WasmRunError> { - let Some(base) = self.scratch_root.lock().expect("scratch root poisoned").clone() else { + let Some(base) = lock_or_recover(&self.scratch_root).clone() else { return Ok(()); }; let scoped = base.join(sanitize_session_segment(session_id)); @@ -637,7 +641,7 @@ impl WasmRuntime { /// Remove the entire scratch root. Called at desktop app startup so /// leftover artifacts from previous runs do not accumulate. pub fn sweep_all(&self) -> Result<(), WasmRunError> { - let Some(base) = self.scratch_root.lock().expect("scratch root poisoned").clone() else { + let Some(base) = lock_or_recover(&self.scratch_root).clone() else { return Ok(()); }; if base.exists() { @@ -703,7 +707,7 @@ impl EpochDeadlineHandle { let thread = thread::spawn(move || { let start = Instant::now(); while start.elapsed() < deadline { - if *shutdown_clone.lock().expect("shutdown poisoned") { + if *lock_or_recover(&shutdown_clone) { return; } thread::sleep(Duration::from_millis(25)); @@ -719,13 +723,29 @@ impl EpochDeadlineHandle { impl Drop for EpochDeadlineHandle { fn drop(&mut self) { - *self.shutdown.lock().expect("shutdown poisoned") = true; + *lock_or_recover(&self.shutdown) = true; if let Some(handle) = self.thread.take() { let _ = handle.join(); } } } +fn lock_or_recover(mutex: &Mutex) -> MutexGuard<'_, T> { + mutex + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) +} + +fn panic_payload_message(payload: &(dyn Any + Send)) -> String { + if let Some(message) = payload.downcast_ref::<&str>() { + (*message).to_string() + } else if let Some(message) = payload.downcast_ref::() { + message.clone() + } else { + "non-string panic payload".to_string() + } +} + // Global runtime access is provided by [`lifecycle::acquire_runtime`]. // It replaces an earlier `global()` helper that held the engine alive // forever. The lifecycle module manages lazy creation, in-flight @@ -960,7 +980,9 @@ mod tests { "page_range": [10, 20] })) }; - let result2 = runtime.run(out_of_range).expect("pdf_processor runs out-of-range"); + let result2 = runtime + .run(out_of_range) + .expect("pdf_processor runs out-of-range"); let output2 = result2.output_json.expect("output.json"); let pages2 = output2 .get("pages") @@ -988,10 +1010,20 @@ mod tests { }; let result = runtime.run(request).expect("docx_processor runs"); let output = result.output_json.expect("output.json"); - let paragraphs = output.get("paragraphs").and_then(|v| v.as_array()).expect("paragraphs"); + let paragraphs = output + .get("paragraphs") + .and_then(|v| v.as_array()) + .expect("paragraphs"); assert!(!paragraphs.is_empty()); - let joined: String = paragraphs.iter().filter_map(|p| p.as_str()).collect::>().join(" "); - assert!(joined.contains("Hello Desktop Skill Docx"), "got: {joined:?}"); + let joined: String = paragraphs + .iter() + .filter_map(|p| p.as_str()) + .collect::>() + .join(" "); + assert!( + joined.contains("Hello Desktop Skill Docx"), + "got: {joined:?}" + ); std::fs::remove_dir_all(&stage).ok(); } @@ -1008,7 +1040,10 @@ mod tests { }; let result = runtime.run(request).expect("xlsx_processor runs"); let output = result.output_json.expect("output.json"); - let sheets = output.get("sheets").and_then(|v| v.as_array()).expect("sheets"); + let sheets = output + .get("sheets") + .and_then(|v| v.as_array()) + .expect("sheets"); assert!(!sheets.is_empty()); let first_name = sheets[0].get("name").and_then(|v| v.as_str()).unwrap_or(""); assert_eq!(first_name, "TestSheet"); @@ -1050,7 +1085,10 @@ mod tests { }; let result = runtime.run(request).expect("pptx_processor runs"); let output = result.output_json.expect("output.json"); - let slides = output.get("slides").and_then(|v| v.as_array()).expect("slides"); + let slides = output + .get("slides") + .and_then(|v| v.as_array()) + .expect("slides"); assert!(!slides.is_empty()); let text = slides[0].get("text").and_then(|v| v.as_str()).unwrap_or(""); assert!(text.contains("Hello Desktop Skill Pptx"), "got: {text:?}"); diff --git a/frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs b/frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs index d38ab21d2..ee1a53273 100644 --- a/frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs +++ b/frontend/src-tauri/src/cowork/desktop_tools/wasm_run.rs @@ -162,20 +162,15 @@ fn execute(ctx: &mut DesktopToolContext<'_>, tool_input: &Value) -> Result 1 { - return Err( - "wasm_run: cannot chunk a call with multiple input_files. \ + return Err("wasm_run: cannot chunk a call with multiple input_files. \ Split into one call per file so each can be chunked independently." - .to_string(), - ); + .to_string()); } let outcome = match plan { @@ -276,15 +271,10 @@ fn build_request( limits: RuntimeLimits, input_json_overlay: Option<&Value>, ) -> WasmRunRequest { - let input_json = match (parsed.base_input_json.as_ref(), input_json_overlay) { - (None, None) => None, - (Some(base), None) => Some(base.clone()), - (None, Some(overlay)) => Some(overlay.clone()), - (Some(base), Some(overlay)) => Some(merge_json_objects(base.clone(), overlay)), - }; + let input_json = build_effective_input_json(parsed, input_json_overlay); - let mut request = WasmRunRequest::new(parsed.module_name.clone()) - .with_session_id(session_id.to_string()); + let mut request = + WasmRunRequest::new(parsed.module_name.clone()).with_session_id(session_id.to_string()); request.input_json = input_json; request.entrypoint = parsed.entrypoint.clone(); request.keep_workspace = parsed.keep_workspace; @@ -298,6 +288,18 @@ fn build_request( request } +fn build_effective_input_json( + parsed: &ParsedInput, + input_json_overlay: Option<&Value>, +) -> Option { + match (parsed.base_input_json.as_ref(), input_json_overlay) { + (None, None) => None, + (Some(base), None) => Some(base.clone()), + (None, Some(overlay)) => Some(overlay.clone()), + (Some(base), Some(overlay)) => Some(merge_json_objects(base.clone(), overlay)), + } +} + /// Shallow merge two JSON values, preferring keys from `overlay`. Both /// must be objects; if either is not, `overlay` wins outright. fn merge_json_objects(base: Value, overlay: &Value) -> Value { @@ -316,6 +318,18 @@ fn run_single( limits: RuntimeLimits, overlay: Option<&Value>, ) -> Result { + if parsed.module_name == "pdf_processor" { + let (input_file, _logical_name) = parsed + .input_files + .first() + .ok_or_else(|| "wasm_run: pdf_processor requires one input file".to_string())?; + let result = splitter::run_pdf_processor_in_helper( + input_file, + build_effective_input_json(parsed, overlay), + )?; + return Ok(super::format_wasm_result(&result)); + } + let request = build_request(parsed, &parsed.session_id, limits, overlay); let result = lease.run(request); match result { @@ -347,9 +361,8 @@ fn run_multi( let total_deadline = parsed .user_timeout_override .unwrap_or(MAX_MULTI_CHUNK_TOTAL_TIMEOUT); - let per_chunk_timeout = Duration::from_secs( - (total_deadline.as_secs() / chunk_count as u64).max(10), - ); + let per_chunk_timeout = + Duration::from_secs((total_deadline.as_secs() / chunk_count as u64).max(10)); // Override per-chunk limits with the computed timeout. for chunk in &mut chunks { @@ -416,11 +429,7 @@ fn run_multi( emit_chunk_progress(parsed, i + 1, chunk_count, &label); } Err(error) => { - let error_msg = super::format_wasm_error( - "wasm_run", - &parsed.module_name, - error, - ); + let error_msg = super::format_wasm_error("wasm_run", &parsed.module_name, error); // Return partial results if we have any, instead of // losing all completed work. return format_multi_result( @@ -530,7 +539,7 @@ fn format_multi_result( /// contains only the relevant pages. The guest is called without a /// `page_range` param — it extracts the entire (small) file. fn run_multi_with_host_split( - lease: &crate::cowork::desktop_runtime::wasm::lifecycle::RuntimeLease, + _lease: &crate::cowork::desktop_runtime::wasm::lifecycle::RuntimeLease, parsed: &ParsedInput, chunks: Vec, merge: MergeStrategy, @@ -566,9 +575,8 @@ fn run_multi_with_host_split( .map(|d| d.as_nanos()) .unwrap_or_default() )); - let chunk_files = - splitter::split_pdf_pages(source_path, &split_dir, pages_per_chunk) - .map_err(|e| format!("wasm_run: host-split failed: {e}"))?; + let chunk_files = splitter::split_pdf_pages(source_path, &split_dir, pages_per_chunk) + .map_err(|e| format!("wasm_run: host-split failed: {e}"))?; if chunk_files.is_empty() { let _ = std::fs::remove_dir_all(&split_dir); @@ -579,9 +587,6 @@ fn run_multi_with_host_split( let total_deadline = parsed .user_timeout_override .unwrap_or(MAX_MULTI_CHUNK_TOTAL_TIMEOUT); - let per_chunk_timeout = Duration::from_secs( - (total_deadline.as_secs() / chunk_files.len() as u64).max(10), - ); let global_start = Instant::now(); let mut chunk_outputs: Vec = Vec::with_capacity(chunk_files.len()); @@ -602,12 +607,7 @@ fn run_multi_with_host_split( )); } - // Build a request that uses the chunk file instead of the original. - let mut limits = RuntimeLimits::defaults(); - limits.wall_timeout = per_chunk_timeout; - limits.max_memory_bytes = 512 * 1024 * 1024; - - // base input_json without page_range (guest extracts entire chunk file). + // Remove page_range because the helper receives a pre-split PDF chunk. let chunk_input_json = parsed .base_input_json .as_ref() @@ -619,29 +619,25 @@ fn run_multi_with_host_split( }) .or_else(|| parsed.base_input_json.clone()); - let mut request = WasmRunRequest::new(parsed.module_name.clone()) - .with_session_id(parsed.session_id.clone()); - request.input_json = chunk_input_json; - request.entrypoint = parsed.entrypoint.clone(); - request.keep_workspace = parsed.keep_workspace; - request.input_files = vec![(chunk_file.path.clone(), "input.pdf".to_string())]; - request.limits = Some(limits); - let label = format!("pages {}-{}", chunk_file.page_start, chunk_file.page_end); - let result = lease.run(request); + let result = splitter::run_pdf_processor_in_helper(&chunk_file.path, chunk_input_json); match result { Ok(chunk_result) => { total_duration_ms += chunk_result.duration_ms; if !chunk_result.stdout.trim().is_empty() { - combined_stdout.push_str(&format!("[{label}]\n{}\n", chunk_result.stdout.trim_end())); + combined_stdout + .push_str(&format!("[{label}]\n{}\n", chunk_result.stdout.trim_end())); } if !chunk_result.stderr.trim().is_empty() { - combined_stderr.push_str(&format!("[{label}]\n{}\n", chunk_result.stderr.trim_end())); + combined_stderr + .push_str(&format!("[{label}]\n{}\n", chunk_result.stderr.trim_end())); } let Some(mut output_json) = chunk_result.output_json else { let _ = std::fs::remove_dir_all(&split_dir); - return Err(format!("wasm_run: host-split chunk {label} produced no output.json")); + return Err(format!( + "wasm_run: host-split chunk {label} produced no output.json" + )); }; // Patch page_offset to reflect position in the original doc. if let Some(obj) = output_json.as_object_mut() { @@ -663,10 +659,9 @@ fn run_multi_with_host_split( emit_chunk_progress(parsed, i + 1, actual_chunk_count, &label); } Err(error) => { - let error_msg = super::format_wasm_error( - "wasm_run", - &parsed.module_name, - error, + let error_msg = format!( + "wasm_run: module '{}' failed: {}", + parsed.module_name, error ); let _ = std::fs::remove_dir_all(&split_dir); return format_multi_result( @@ -743,4 +738,3 @@ fn first_array_len(value: &Value) -> usize { } 0 } - diff --git a/frontend/src-tauri/src/cowork/mod.rs b/frontend/src-tauri/src/cowork/mod.rs index ac43fe29b..f85000a1c 100644 --- a/frontend/src-tauri/src/cowork/mod.rs +++ b/frontend/src-tauri/src/cowork/mod.rs @@ -1,5 +1,6 @@ pub mod agent_presets; pub mod agent_remote; +pub mod bootstrap; pub mod chat; pub mod chat_commands; pub mod desktop_runtime; diff --git a/frontend/src-tauri/src/cowork/runtime.rs b/frontend/src-tauri/src/cowork/runtime.rs index fc0550e01..dd645303d 100644 --- a/frontend/src-tauri/src/cowork/runtime.rs +++ b/frontend/src-tauri/src/cowork/runtime.rs @@ -3,6 +3,9 @@ use crate::cowork::chat::{ CoworkAgentRuntimeKind, CoworkChatFile, CoworkChatMessage, CoworkChatRunStatus, CoworkChatRuntimeEvent, CoworkChatSendMessageRequest, CoworkChatSendMessageResponse, }; +use futures_util::FutureExt; +use std::any::Any; +use std::future::Future; use tauri::{AppHandle, State}; pub struct CoworkRuntimeSessionSnapshot { @@ -15,18 +18,44 @@ pub struct CoworkRuntimeSessionSnapshot { pub runtime_events: Vec, } +pub(crate) async fn guard_async_command(label: &str, future: F) -> Result +where + F: Future>, +{ + match std::panic::AssertUnwindSafe(future).catch_unwind().await { + Ok(result) => result, + Err(payload) => Err(format!( + "{label} panicked: {}", + panic_payload_message(payload.as_ref()) + )), + } +} + +fn panic_payload_message(payload: &(dyn Any + Send)) -> String { + if let Some(message) = payload.downcast_ref::<&str>() { + (*message).to_string() + } else if let Some(message) = payload.downcast_ref::() { + message.clone() + } else { + "non-string panic payload".to_string() + } +} + #[tauri::command] pub async fn send_cowork_chat_message( app: AppHandle, remote_auth_state: State<'_, RemoteAuthState>, request: CoworkChatSendMessageRequest, ) -> Result { - match request.requested_runtime_kind() { - CoworkAgentRuntimeKind::Remote => { - remote_service::send_remote_chat_message(app, remote_auth_state, request).await + guard_async_command("send_cowork_chat_message", async move { + match request.requested_runtime_kind() { + CoworkAgentRuntimeKind::Remote => { + remote_service::send_remote_chat_message(app, remote_auth_state, request).await + } + CoworkAgentRuntimeKind::Local => { + Err("Cowork local runtime is not implemented yet.".to_string()) + } } - CoworkAgentRuntimeKind::Local => { - Err("Cowork local runtime is not implemented yet.".to_string()) - } - } + }) + .await } diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 40f182f91..5de100fa7 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -10,6 +10,12 @@ fn greet(name: &str) -> String { } fn main() { + cowork::bootstrap::install_panic_diagnostics(); + + if let Some(exit_code) = cowork::bootstrap::maybe_startup_exit_code_from_env_args() { + std::process::exit(exit_code); + } + tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) From c88d588f1a9b74dc2bc056d8276247225fd3470a Mon Sep 17 00:00:00 2001 From: namtranii Date: Tue, 21 Apr 2026 21:12:22 +0700 Subject: [PATCH 12/12] refactor: new schemas and related handlers --- .../src/cowork/agent_remote/payload.rs | 284 ++++++++++- .../src/cowork/agent_remote/types.rs | 18 +- src/ii_agent/agents/cowork/__init__.py | 1 - .../agents/cowork/desktop_proxy_tools.py | 160 ------- src/ii_agent/agents/cowork/factory.py | 341 -------------- src/ii_agent/agents/cowork/models.py | 59 --- src/ii_agent/agents/factory/agent.py | 52 +- src/ii_agent/agents/types.py | 1 + src/ii_agent/clients/__init__.py | 0 src/ii_agent/clients/cowork/__init__.py | 3 + src/ii_agent/clients/cowork/config.py | 27 ++ src/ii_agent/clients/cowork/factory.py | 154 ++++++ src/ii_agent/clients/proxy_capabilities.py | 443 ++++++++++++++++++ .../realtime/handlers/cowork_continue_run.py | 102 ++-- .../realtime/handlers/cowork_query.py | 156 +++--- src/ii_agent/realtime/handlers/factory.py | 5 +- src/ii_agent/realtime/schemas.py | 49 +- 17 files changed, 1069 insertions(+), 786 deletions(-) delete mode 100644 src/ii_agent/agents/cowork/__init__.py delete mode 100644 src/ii_agent/agents/cowork/desktop_proxy_tools.py delete mode 100644 src/ii_agent/agents/cowork/factory.py delete mode 100644 src/ii_agent/agents/cowork/models.py create mode 100644 src/ii_agent/clients/__init__.py create mode 100644 src/ii_agent/clients/cowork/__init__.py create mode 100644 src/ii_agent/clients/cowork/config.py create mode 100644 src/ii_agent/clients/cowork/factory.py create mode 100644 src/ii_agent/clients/proxy_capabilities.py diff --git a/frontend/src-tauri/src/cowork/agent_remote/payload.rs b/frontend/src-tauri/src/cowork/agent_remote/payload.rs index 17b37eb80..a4769c81e 100644 --- a/frontend/src-tauri/src/cowork/agent_remote/payload.rs +++ b/frontend/src-tauri/src/cowork/agent_remote/payload.rs @@ -1,8 +1,10 @@ use super::types::{ - RemoteAgentCommandContent, RemoteAgentToolArgs, RemoteModelSelection, REMOTE_AGENT_TYPE, - REMOTE_BUILD_MODE, + RemoteAgentCommandContent, RemoteAgentToolArgs, RemoteModelSelection, + RemoteRequestedCapabilities, REMOTE_AGENT_TYPE, REMOTE_BUILD_MODE, +}; +use crate::cowork::agent_presets::shared::{ + DesktopCapabilities, DesktopSkillCapability, DesktopToolCapability, }; -use crate::cowork::agent_presets::shared::DesktopCapabilities; use crate::cowork::chat::{ CoworkAgentOverrides, CoworkChatToolSettings, CoworkGitHubRepositoryContext, }; @@ -20,11 +22,6 @@ pub fn build_remote_command( github_repository: Option, agent_overrides: Option<&CoworkAgentOverrides>, ) -> Result { - let skill_names = - sanitize_name_list(agent_overrides.and_then(|overrides| overrides.skill_names.as_ref())); - let tool_names = - sanitize_name_list(agent_overrides.and_then(|overrides| overrides.tool_names.as_ref())); - serde_json::to_value(RemoteAgentCommandContent { model_id, provider: model_selection.provider, @@ -36,13 +33,10 @@ pub fn build_remote_command( resume, files: Vec::new(), metadata, - desktop_capabilities, + requested_capabilities: build_requested_capabilities(agent_overrides, desktop_capabilities), github_repository, build_mode: REMOTE_BUILD_MODE, system_prompt: build_system_prompt(agent_overrides), - tool_names, - skill_names, - agent_config: agent_overrides.and_then(|overrides| overrides.runtime_options.clone()), }) .map_err(|error| format!("Failed to encode Cowork agent command: {error}")) } @@ -75,6 +69,37 @@ fn build_system_prompt(agent_overrides: Option<&CoworkAgentOverrides>) -> Option .and_then(normalize_optional_string) } +fn build_requested_capabilities( + agent_overrides: Option<&CoworkAgentOverrides>, + desktop_capabilities: Option, +) -> Option { + let skill_names = + sanitize_name_list(agent_overrides.and_then(|overrides| overrides.skill_names.as_ref())); + let tool_names = + sanitize_name_list(agent_overrides.and_then(|overrides| overrides.tool_names.as_ref())); + + let (client_tools, core_tools) = + split_client_and_core_tools(desktop_capabilities.as_ref(), tool_names.as_ref()); + let (client_skills, core_skills) = + split_client_and_core_skills(desktop_capabilities.as_ref(), skill_names.as_ref()); + + if client_tools.is_none() + && client_skills.is_none() + && core_tools.is_none() + && core_skills.is_none() + { + return None; + } + + Some(RemoteRequestedCapabilities { + client_tools, + client_skills, + core_tools, + core_skills, + connector: None, + }) +} + fn sanitize_name_list(values: Option<&Vec>) -> Option> { let mut normalized = Vec::new(); if let Some(items) = values { @@ -93,3 +118,238 @@ fn sanitize_name_list(values: Option<&Vec>) -> Option> { Some(normalized) } } + +fn split_client_and_core_tools( + desktop_capabilities: Option<&DesktopCapabilities>, + selected_tool_names: Option<&Vec>, +) -> (Option>, Option>) { + let Some(desktop_capabilities) = desktop_capabilities else { + return (None, selected_tool_names.cloned()); + }; + + let mut client_tools = Vec::new(); + let mut core_tools = Vec::new(); + + match selected_tool_names { + Some(selected_names) => { + for selected_name in selected_names { + if let Some(capability) = + find_matching_client_tool(&desktop_capabilities.tools, selected_name) + { + if !client_tools + .iter() + .any(|existing: &DesktopToolCapability| existing.name == capability.name) + { + client_tools.push(capability.clone()); + } + } else if !core_tools.iter().any(|existing| existing == selected_name) { + core_tools.push(selected_name.clone()); + } + } + } + None => client_tools.extend(desktop_capabilities.tools.iter().cloned()), + } + + ( + if client_tools.is_empty() { + None + } else { + Some(client_tools) + }, + if core_tools.is_empty() { + None + } else { + Some(core_tools) + }, + ) +} + +fn split_client_and_core_skills( + desktop_capabilities: Option<&DesktopCapabilities>, + selected_skill_names: Option<&Vec>, +) -> (Option>, Option>) { + let Some(desktop_capabilities) = desktop_capabilities else { + return (None, selected_skill_names.cloned()); + }; + + let mut client_skills = Vec::new(); + let mut core_skills = Vec::new(); + + match selected_skill_names { + Some(selected_names) => { + for selected_name in selected_names { + if let Some(capability) = + find_matching_client_skill(&desktop_capabilities.skills, selected_name) + { + if !client_skills + .iter() + .any(|existing: &DesktopSkillCapability| existing.name == capability.name) + { + client_skills.push(capability.clone()); + } + } else if !core_skills.iter().any(|existing| existing == selected_name) { + core_skills.push(selected_name.clone()); + } + } + } + None => client_skills.extend(desktop_capabilities.skills.iter().cloned()), + } + + ( + if client_skills.is_empty() { + None + } else { + Some(client_skills) + }, + if core_skills.is_empty() { + None + } else { + Some(core_skills) + }, + ) +} + +fn find_matching_client_tool<'a>( + tools: &'a [DesktopToolCapability], + selected_name: &str, +) -> Option<&'a DesktopToolCapability> { + let normalized = selected_name.trim(); + if normalized.is_empty() { + return None; + } + + tools.iter().find(|tool| { + tool.name.eq_ignore_ascii_case(normalized) + || tool + .aliases + .iter() + .any(|alias| alias.eq_ignore_ascii_case(normalized)) + }) +} + +fn find_matching_client_skill<'a>( + skills: &'a [DesktopSkillCapability], + selected_name: &str, +) -> Option<&'a DesktopSkillCapability> { + let normalized = selected_name.trim(); + if normalized.is_empty() { + return None; + } + + skills + .iter() + .find(|skill| skill.name.eq_ignore_ascii_case(normalized)) +} + +#[cfg(test)] +mod tests { + use super::{ + build_requested_capabilities, DesktopCapabilities, DesktopSkillCapability, + DesktopToolCapability, + }; + use crate::cowork::chat::CoworkAgentOverrides; + use serde_json::json; + + fn sample_desktop_capabilities() -> DesktopCapabilities { + DesktopCapabilities { + tools: vec![DesktopToolCapability { + name: "Read".to_string(), + aliases: vec!["read_file".to_string()], + display_name: "Read".to_string(), + description: "Read a file from desktop scope.".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "path": { "type": "string" } + }, + "required": ["path"] + }), + }], + skills: vec![DesktopSkillCapability { + name: "pdf".to_string(), + description: "Process PDFs on the desktop runtime.".to_string(), + }], + } + } + + #[test] + fn builds_client_capabilities_from_desktop_capability_catalog() { + let requested = build_requested_capabilities( + Some(&CoworkAgentOverrides { + system_prompt: None, + tool_names: Some(vec!["Read".to_string()]), + skill_names: Some(vec!["pdf".to_string()]), + runtime_options: None, + }), + Some(sample_desktop_capabilities()), + ) + .expect("requested capabilities"); + + assert_eq!(requested.core_tools, None); + assert_eq!(requested.core_skills, None); + assert_eq!( + requested + .client_tools + .expect("client tools") + .into_iter() + .map(|tool| tool.name) + .collect::>(), + vec!["Read".to_string()] + ); + assert_eq!( + requested + .client_skills + .expect("client skills") + .into_iter() + .map(|skill| skill.name) + .collect::>(), + vec!["pdf".to_string()] + ); + } + + #[test] + fn keeps_non_desktop_overrides_as_core_capabilities() { + let requested = build_requested_capabilities( + Some(&CoworkAgentOverrides { + system_prompt: None, + tool_names: Some(vec!["web_search".to_string()]), + skill_names: Some(vec!["writer".to_string()]), + runtime_options: None, + }), + Some(sample_desktop_capabilities()), + ) + .expect("requested capabilities"); + + assert_eq!(requested.client_tools, None); + assert_eq!(requested.client_skills, None); + assert_eq!(requested.core_tools, Some(vec!["web_search".to_string()])); + assert_eq!(requested.core_skills, Some(vec!["writer".to_string()])); + } + + #[test] + fn includes_all_desktop_capabilities_when_no_subset_is_requested() { + let requested = build_requested_capabilities(None, Some(sample_desktop_capabilities())) + .expect("requested capabilities"); + + assert_eq!( + requested + .client_tools + .expect("client tools") + .into_iter() + .map(|tool| tool.name) + .collect::>(), + vec!["Read".to_string()] + ); + assert_eq!( + requested + .client_skills + .expect("client skills") + .into_iter() + .map(|skill| skill.name) + .collect::>(), + vec!["pdf".to_string()] + ); + assert_eq!(requested.core_tools, None); + assert_eq!(requested.core_skills, None); + } +} diff --git a/frontend/src-tauri/src/cowork/agent_remote/types.rs b/frontend/src-tauri/src/cowork/agent_remote/types.rs index 2aaf3eb80..f440d7fe5 100644 --- a/frontend/src-tauri/src/cowork/agent_remote/types.rs +++ b/frontend/src-tauri/src/cowork/agent_remote/types.rs @@ -1,4 +1,4 @@ -use crate::cowork::agent_presets::shared::DesktopCapabilities; +use crate::cowork::agent_presets::shared::{DesktopSkillCapability, DesktopToolCapability}; use crate::cowork::chat::CoworkGitHubRepositoryContext; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -26,18 +26,26 @@ pub(super) struct RemoteAgentCommandContent { #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub desktop_capabilities: Option, + pub requested_capabilities: Option, #[serde(skip_serializing_if = "Option::is_none")] pub github_repository: Option, pub build_mode: &'static str, #[serde(skip_serializing_if = "Option::is_none")] pub system_prompt: Option, +} + +#[derive(Debug, Serialize)] +pub(super) struct RemoteRequestedCapabilities { + #[serde(skip_serializing_if = "Option::is_none")] + pub client_tools: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub client_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub tool_names: Option>, + pub core_tools: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub skill_names: Option>, + pub core_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub agent_config: Option, + pub connector: Option, } #[derive(Debug, Serialize)] diff --git a/src/ii_agent/agents/cowork/__init__.py b/src/ii_agent/agents/cowork/__init__.py deleted file mode 100644 index 72132b582..000000000 --- a/src/ii_agent/agents/cowork/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Cowork-specific agent creation and tools.""" diff --git a/src/ii_agent/agents/cowork/desktop_proxy_tools.py b/src/ii_agent/agents/cowork/desktop_proxy_tools.py deleted file mode 100644 index da9697c08..000000000 --- a/src/ii_agent/agents/cowork/desktop_proxy_tools.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Desktop proxy tools for Cowork desktop execution modes.""" - -from __future__ import annotations - -from typing import Any, Dict, Iterable, List, Optional - -from ii_agent.core.logger import logger -from ii_agent.agents.tools.function import Function - - -def _placeholder_tool_entrypoint(**_: Any) -> str: - return "This tool is executed by the II Agent desktop runtime." - - -def _build_external_function( - *, - name: str, - display_name: str, - description: str, - parameters: dict[str, Any], -) -> Function: - return Function( - name=name, - description=description, - parameters=parameters, - display_name=display_name, - entrypoint=_placeholder_tool_entrypoint, - skip_entrypoint_processing=True, - external_execution=True, - show_result=True, - ) - - -def _normalize_aliases(raw_aliases: Any) -> List[str]: - if not isinstance(raw_aliases, list): - return [] - - aliases: List[str] = [] - for alias in raw_aliases: - if not isinstance(alias, str): - continue - normalized = alias.strip() - if not normalized: - continue - aliases.append(normalized) - return aliases - - -def _normalize_requested_names( - requested_tool_names: Optional[Iterable[str]], -) -> Optional[set[str]]: - if not requested_tool_names: - return None - - normalized_names = { - tool_name.strip().lower() - for tool_name in requested_tool_names - if isinstance(tool_name, str) and tool_name.strip() - } - return normalized_names or None - - -def _iter_desktop_tool_descriptors( - desktop_capabilities: Optional[Dict[str, Any]], -) -> List[Dict[str, Any]]: - if not isinstance(desktop_capabilities, dict): - return [] - tools = desktop_capabilities.get("tools") - if not isinstance(tools, list): - return [] - return [tool for tool in tools if isinstance(tool, dict)] - - -def build_desktop_proxy_tools( - desktop_capabilities: Optional[Dict[str, Any]] = None, - requested_tool_names: Optional[Iterable[str]] = None, -) -> List[Function]: - requested_names = _normalize_requested_names(requested_tool_names) - tool_specs = _iter_desktop_tool_descriptors(desktop_capabilities) - if not tool_specs: - logger.warning( - "Cowork desktop tool runtime requested but no desktop capability descriptors were provided" - ) - return [] - - proxy_tools = [] - for tool_spec in tool_specs: - name = tool_spec.get("name") - description = tool_spec.get("description") - if not isinstance(name, str) or not name.strip(): - continue - if not isinstance(description, str) or not description.strip(): - continue - - display_name = tool_spec.get("display_name") - if not isinstance(display_name, str) or not display_name.strip(): - display_name = name - parameters = tool_spec.get("input_schema") - if not isinstance(parameters, dict): - parameters = {"type": "object", "properties": {}, "required": []} - aliases = _normalize_aliases(tool_spec.get("aliases")) - - names_to_publish: List[str] - if requested_names is None: - names_to_publish = [name] - else: - candidate_names = [name, *aliases] - names_to_publish = [] - for candidate_name in candidate_names: - if candidate_name.strip().lower() not in requested_names: - continue - if candidate_name in names_to_publish: - continue - names_to_publish.append(candidate_name) - if not names_to_publish: - continue - - for published_name in names_to_publish: - proxy_tools.append( - _build_external_function( - name=published_name, - display_name=display_name, - description=description, - parameters=parameters, - ) - ) - - return proxy_tools - - -def build_desktop_skill_context( - desktop_capabilities: Optional[Dict[str, Any]] = None, - requested_skill_names: Optional[Iterable[str]] = None, -) -> Optional[str]: - if not isinstance(desktop_capabilities, dict): - return None - - skills = desktop_capabilities.get("skills") - if not isinstance(skills, list) or not skills: - return None - - requested_names = _normalize_requested_names(requested_skill_names) - skill_lines: List[str] = [] - for skill in skills: - if not isinstance(skill, dict): - continue - name = skill.get("name") - description = skill.get("description") - if not isinstance(name, str) or not name.strip(): - continue - if requested_names is not None and name.strip().lower() not in requested_names: - continue - if not isinstance(description, str) or not description.strip(): - continue - skill_lines.append(f"- {name}: {description}") - - if not skill_lines: - return None - - return "Desktop skills available in this runtime:\n" + "\n".join(skill_lines) diff --git a/src/ii_agent/agents/cowork/factory.py b/src/ii_agent/agents/cowork/factory.py deleted file mode 100644 index 497876247..000000000 --- a/src/ii_agent/agents/cowork/factory.py +++ /dev/null @@ -1,341 +0,0 @@ -"""Cowork-specific agent creation helpers.""" - -from typing import Any, Dict, List, Optional - -from ii_agent.core.config.llm_config import LLMConfig -from ii_agent.core.logger import logger -from ii_agent.agents.prompts.agent_prompts import get_system_prompt_for_agent_type -from ii_server.core.workspace import WorkspaceManager -from ii_agent.agents.agent import IIAgent -from ii_agent.agents.connector.base import BaseConnectorTool -from ii_agent.agents.cowork.desktop_proxy_tools import ( - build_desktop_proxy_tools, - build_desktop_skill_context, -) -from ii_agent.agents.factory.agent import AgentFactory -from ii_agent.agents.factory.tool_manager import AgentToolManager -from ii_agent.agents.factory.tools import AgentType, TOOL_CLASS_MAP -from ii_agent.agents.models.utils import get_model -from ii_agent.agents.sessions.base import SessionStore -from ii_agent.agents.skills.base import SkillCreator -from ii_agent.agents.skills.prompt_db import generate_skill_tool_description -from ii_agent.settings.llm import Provider - -class CoworkAgentFactory: - """Factory for cowork-specific IIAgent creation with runtime overrides.""" - - def __init__(self, factory: AgentFactory): - self.factory = factory - - @staticmethod - def _normalize_name_list(values: Optional[List[str]]) -> Optional[set[str]]: - if not values: - return None - normalized = { - value.strip().lower() - for value in values - if isinstance(value, str) and value.strip() - } - return normalized or None - - def _apply_skill_name_overrides(self, skill_tool, skill_names: Optional[List[str]]): - requested_skill_names = self._normalize_name_list(skill_names) - if skill_tool is None or not requested_skill_names: - return skill_tool - - filtered_registry = { - name: skill - for name, skill in skill_tool._skills_registry.items() - if name.strip().lower() in requested_skill_names - } - missing_skill_names = requested_skill_names - { - name.strip().lower() for name in skill_tool._skills_registry.keys() - } - if missing_skill_names: - logger.warning( - f"Requested cowork skills were not found: {sorted(missing_skill_names)}" - ) - - if not filtered_registry: - logger.warning("Cowork skill override removed all available skills") - return None - - skill_tool._skills_registry = filtered_registry - skill_tool.description = generate_skill_tool_description(list(filtered_registry.values())) - return skill_tool - - def _add_requested_tools( - self, - agent_tools: List[Any], - requested_tool_names: Optional[List[str]], - ) -> List[Any]: - normalized_requested = self._normalize_name_list(requested_tool_names) - if not normalized_requested: - return agent_tools - - existing_names = { - tool.name.strip().lower() for tool in agent_tools if hasattr(tool, "name") - } - missing_tool_names = normalized_requested - existing_names - for tool_name in missing_tool_names: - requested_tool = None - for registered_tool_name in TOOL_CLASS_MAP.keys(): - if registered_tool_name.strip().lower() == tool_name: - requested_tool = AgentToolManager.convert_tool(registered_tool_name) - break - if requested_tool is None: - logger.warning(f"Requested cowork tool `{tool_name}` is not registered") - continue - agent_tools.append(requested_tool) - - return agent_tools - - def _filter_tools_by_name( - self, - agent_tools: List[Any], - requested_tool_names: Optional[List[str]], - ) -> List[Any]: - normalized_requested = self._normalize_name_list(requested_tool_names) - if not normalized_requested: - return agent_tools - - filtered_tools = [ - tool - for tool in agent_tools - if getattr(tool, "name", "").strip().lower() in normalized_requested - ] - found_tool_names = { - getattr(tool, "name", "").strip().lower() - for tool in filtered_tools - if hasattr(tool, "name") - } - missing_tool_names = normalized_requested - found_tool_names - if missing_tool_names: - logger.warning( - f"Requested cowork tools were not created: {sorted(missing_tool_names)}" - ) - - return filtered_tools - - @staticmethod - def _dedupe_tools_by_name(agent_tools: List[Any]) -> List[Any]: - unique_tools: List[Any] = [] - seen_names: set[str] = set() - for tool in agent_tools: - tool_name = getattr(tool, "name", None) - if not tool_name: - unique_tools.append(tool) - continue - normalized_name = tool_name.strip().lower() - if normalized_name in seen_names: - continue - seen_names.add(normalized_name) - unique_tools.append(tool) - return unique_tools - - @staticmethod - def _build_agent_runtime_config( - agent_type: AgentType, - agent_config: Optional[Dict[str, Any]], - ) -> Dict[str, Any]: - config_overrides = agent_config or {} - return { - "name": config_overrides.get("name", f"{agent_type.value}_agent"), - "description": config_overrides.get("description"), - "additional_context": config_overrides.get("additional_context"), - "tool_call_limit": config_overrides.get("tool_call_limit"), - "tool_choice": config_overrides.get("tool_choice"), - "retries": config_overrides.get("retries", 0), - "delay_between_retries": config_overrides.get("delay_between_retries", 1), - "exponential_backoff": config_overrides.get("exponential_backoff", False), - "stream": config_overrides.get("stream", True), - "stream_events": config_overrides.get("stream_events", True), - "store_events": config_overrides.get("store_events", True), - "delegate_to_all_members": config_overrides.get("delegate_to_all_members", False), - "stream_member_events": config_overrides.get("stream_member_events", True), - "store_member_responses": config_overrides.get("store_member_responses", False), - "role": config_overrides.get("role"), - } - - @staticmethod - def _get_cowork_metadata(metadata: Optional[Dict[str, Any]]) -> Dict[str, Any]: - if not isinstance(metadata, dict): - return {} - cowork_metadata = metadata.get("cowork") - return cowork_metadata if isinstance(cowork_metadata, dict) else {} - - @classmethod - def _is_desktop_execution_mode(cls, metadata: Optional[Dict[str, Any]]) -> bool: - cowork_metadata = cls._get_cowork_metadata(metadata) - return cowork_metadata.get("execution_context") == "desktop" - - @classmethod - def _uses_desktop_proxy_tools(cls, metadata: Optional[Dict[str, Any]]) -> bool: - cowork_metadata = cls._get_cowork_metadata(metadata) - return cls._is_desktop_execution_mode(metadata) and cowork_metadata.get( - "tool_runtime" - ) in { - "desktop_builtin", - "desktop_proxy", - } - - @classmethod - def _uses_desktop_tools_exclusively(cls, metadata: Optional[Dict[str, Any]]) -> bool: - cowork_metadata = cls._get_cowork_metadata(metadata) - binding_mode = cowork_metadata.get("tool_binding_mode") - if binding_mode is None: - binding_mode = "desktop_only" if cls._uses_desktop_proxy_tools(metadata) else None - return cls._uses_desktop_proxy_tools(metadata) and binding_mode == "desktop_only" - - async def create_agent( - self, - user_id: str, - session_id: str, - llm_config: LLMConfig, - agent_type: AgentType = AgentType.GENERAL, - workspace_manager: Optional[WorkspaceManager] = None, - session_store: Optional[SessionStore] = None, - tool_args: Optional[Dict[str, Any]] = None, - metadata: Optional[Dict[str, Any]] = None, - system_prompt: Optional[str] = None, - skill_creator: Optional[SkillCreator] = None, - connector_tool: Optional[BaseConnectorTool] = None, - tool_names: Optional[List[str]] = None, - skill_names: Optional[List[str]] = None, - desktop_capabilities: Optional[Dict[str, Any]] = None, - agent_config: Optional[Dict[str, Any]] = None, - ) -> IIAgent: - logger.info(f"Creating cowork {agent_type} agent for session {session_id}") - - tool_args = tool_args or {} - has_media = tool_args.get("media_generation", False) - has_task_agent = tool_args.get("task_agent", False) - has_researcher = tool_args.get("deep_research", False) - has_design_doc = tool_args.get("design_document", False) - - provider = llm_config.provider - model = get_model(provider, llm_config=llm_config) - - uses_desktop_proxy_tools = self._uses_desktop_proxy_tools(metadata) - uses_desktop_tools_exclusively = self._uses_desktop_tools_exclusively(metadata) - - if uses_desktop_tools_exclusively: - agent_tools = build_desktop_proxy_tools( - desktop_capabilities=desktop_capabilities, - requested_tool_names=tool_names, - ) - logger.info("Cowork desktop tool runtime enabled; using desktop proxy tools only") - else: - agent_tools = AgentToolManager.resolve_tools( - agent_type=agent_type, - model_name=model.id, - tool_args=tool_args, - ) - agent_tools = self._add_requested_tools(agent_tools, tool_names) - if uses_desktop_proxy_tools: - agent_tools.extend( - build_desktop_proxy_tools( - desktop_capabilities=desktop_capabilities, - requested_tool_names=tool_names, - ) - ) - logger.info("Cowork desktop tool runtime enabled; merging desktop proxy tools") - - if skill_creator is not None and not uses_desktop_tools_exclusively: - skill_tool = await skill_creator.create_skill_tool() - skill_tool = self._apply_skill_name_overrides(skill_tool, skill_names) - if skill_tool: - agent_tools.append(skill_tool) - logger.info(f"Added SkillTool with {len(skill_tool._skills_registry)} skills") - - if connector_tool is not None and not uses_desktop_tools_exclusively: - try: - connector_tools = await connector_tool.create_connector_tools( - workspace_manager=workspace_manager, - ) - if connector_tools: - logger.info( - f"[Cowork Factory] Received {len(connector_tools)} connector tools from loader" - ) - logger.debug( - f"[Cowork Factory] Connector tool names: {[t.name for t in connector_tools]}" - ) - agent_tools.extend(connector_tools) - except Exception as e: - logger.error( - f"[Cowork Factory] Failed to load connector tools: {e}", exc_info=True - ) - - agent_tools = self._filter_tools_by_name(agent_tools, tool_names) - agent_tools = self._dedupe_tools_by_name(agent_tools) - AgentToolManager.log_tool_summary(agent_tools, f"Cowork agent {agent_type.value}") - - if system_prompt is None: - workspace_path = ( - workspace_manager.workspace_path.as_posix() - if workspace_manager - else "/workspace" - ) - system_prompt = await get_system_prompt_for_agent_type( - agent_type=agent_type, - workspace_path=workspace_path, - design_document=has_design_doc, - researcher=has_researcher, - media=has_media, - a2a_agents=False, - task_agent=has_task_agent, - metadata=metadata, - provider=llm_config.provider if llm_config else None, - ) - - desktop_skill_context = build_desktop_skill_context( - desktop_capabilities=desktop_capabilities, - requested_skill_names=skill_names, - ) - if desktop_skill_context: - system_prompt = f"{system_prompt}\n\n{desktop_skill_context}" - - sub_agents = [] - if has_task_agent: - task_agent = await self.factory.create_task_agent_tool( - user_id=user_id, - session_id=session_id, - llm_config=llm_config, - tool_args=tool_args, - ) - sub_agents.append(task_agent) - - runtime_config = self._build_agent_runtime_config( - agent_type=agent_type, - agent_config=agent_config, - ) - - agent = IIAgent( - user_id=user_id, - session_id=session_id, - model=model, - name=runtime_config["name"], - description=runtime_config["description"], - additional_context=runtime_config["additional_context"], - tools=agent_tools, - tool_call_limit=runtime_config["tool_call_limit"], - tool_choice=runtime_config["tool_choice"], - system_message=system_prompt, - session_store=session_store, - metadata=metadata, - sub_agents=sub_agents, - retries=runtime_config["retries"], - delay_between_retries=runtime_config["delay_between_retries"], - exponential_backoff=runtime_config["exponential_backoff"], - stream=runtime_config["stream"], - stream_events=runtime_config["stream_events"], - store_events=runtime_config["store_events"], - delegate_to_all_members=runtime_config["delegate_to_all_members"], - stream_member_events=runtime_config["stream_member_events"], - store_member_responses=runtime_config["store_member_responses"], - role=runtime_config["role"], - ) - agent.set_id() - - logger.info(f"Created cowork {agent_type.value} agent with {len(agent_tools)} tools") - return agent diff --git a/src/ii_agent/agents/cowork/models.py b/src/ii_agent/agents/cowork/models.py deleted file mode 100644 index c15b2041b..000000000 --- a/src/ii_agent/agents/cowork/models.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, Dict, List, Optional - -from pydantic import BaseModel - -from ii_agent.realtime.schemas import QueryCommandContent - - -class CoworkAgentConfig(BaseModel): - """Runtime overrides for cowork-specific IIAgent creation.""" - - name: Optional[str] = None - description: Optional[str] = None - additional_context: Optional[str] = None - retries: Optional[int] = None - delay_between_retries: Optional[int] = None - exponential_backoff: Optional[bool] = None - stream: Optional[bool] = None - stream_events: Optional[bool] = None - store_events: Optional[bool] = None - tool_call_limit: Optional[int] = None - tool_choice: Optional[Any] = None - delegate_to_all_members: Optional[bool] = None - stream_member_events: Optional[bool] = None - store_member_responses: Optional[bool] = None - role: Optional[str] = None - - -class DesktopCapabilityToolDescriptor(BaseModel): - """Desktop tool descriptor sent by the desktop runtime.""" - - name: str - aliases: List[str] = [] - display_name: Optional[str] = None - description: str - input_schema: Dict[str, Any] = {} - - -class DesktopCapabilitySkillDescriptor(BaseModel): - """Desktop skill descriptor sent by the desktop runtime.""" - - name: str - description: str - - -class DesktopCapabilitiesContent(BaseModel): - """Desktop tool and skill catalog sent with cowork requests.""" - - tools: List[DesktopCapabilityToolDescriptor] = [] - skills: List[DesktopCapabilitySkillDescriptor] = [] - - -class CoworkQueryCommandContent(QueryCommandContent): - """Extended query contract for cowork-specific agent overrides.""" - - system_prompt: Optional[str] = None - tool_names: Optional[List[str]] = None - skill_names: Optional[List[str]] = None - agent_config: Optional[CoworkAgentConfig] = None - desktop_capabilities: Optional[DesktopCapabilitiesContent] = None diff --git a/src/ii_agent/agents/factory/agent.py b/src/ii_agent/agents/factory/agent.py index 694325e6f..99d952b47 100644 --- a/src/ii_agent/agents/factory/agent.py +++ b/src/ii_agent/agents/factory/agent.py @@ -1,6 +1,6 @@ """Agent factory for creating configured agent instances.""" -from typing import Any, Dict, List, Optional +from typing import Any, Dict, Optional from uuid import UUID from ii_agent.core.config.settings import Settings, get_settings @@ -17,9 +17,7 @@ from ii_agent.agents.models.utils import get_model from ii_agent.agents.sessions import SessionStore from ii_agent.agents.tools.task import SYSTEM_PROMPT, TaskAgentTool, DESCRIPTION -from ii_agent.core.db import get_session_factory from ii_agent.core.logger import logger -from ii_agent.sessions.schemas import SessionInfo def _append_prompt_section(base_prompt: Optional[str], section: Optional[str]) -> Optional[str]: @@ -493,52 +491,4 @@ async def create_codex_agent_tool( return None - async def create_cowork_agent( - self, - session_info: SessionInfo, - llm_config: LLMConfig, - workspace_manager: WorkspaceManager, - agent_type: AgentType = AgentType.GENERAL, - tool_args: Optional[Dict[str, Any]] = None, - metadata: Optional[Dict[str, Any]] = None, - default_repository: Optional[Dict[str, str]] = None, - system_prompt: Optional[str] = None, - tool_names: Optional[List[str]] = None, - skill_names: Optional[List[str]] = None, - desktop_capabilities: Optional[Dict[str, Any]] = None, - agent_config: Optional[Dict[str, Any]] = None, - ) -> IIAgent: - from ii_agent.agents.skills.db_creator import DbSkillCreator - from ii_agent.agents.connector.connector_tool import ConnectorTool - from ii_agent.agents.cowork.factory import CoworkAgentFactory - from ii_agent.agents.sessions.store import AgentSessionStore - - logger.info( - f"[Agent Service] Creating Cowork V1 agent for user {session_info.user_id}" - ) - - skill_creator = DbSkillCreator(user_id=str(session_info.user_id)) - connector_tool = ConnectorTool( - user_id=str(session_info.user_id), default_repository=default_repository - ) - - cowork_factory = CoworkAgentFactory(factory=self) - return await cowork_factory.create_agent( - user_id=str(session_info.user_id), - session_id=str(session_info.id), - llm_config=llm_config, - agent_type=agent_type, - workspace_manager=workspace_manager, - session_store=AgentSessionStore(session_maker=get_session_factory()), - tool_args=tool_args, - metadata=metadata, - skill_creator=skill_creator, - connector_tool=connector_tool, - system_prompt=system_prompt, - tool_names=tool_names, - skill_names=skill_names, - desktop_capabilities=desktop_capabilities, - agent_config=agent_config, - ) - agent_factory = AgentFactory(config=get_settings()) diff --git a/src/ii_agent/agents/types.py b/src/ii_agent/agents/types.py index 5b876addd..9778778ea 100644 --- a/src/ii_agent/agents/types.py +++ b/src/ii_agent/agents/types.py @@ -30,6 +30,7 @@ class AgentType(StrEnum): FAST_RESEARCH = "fast_research" RESEARCH_TO_WEBSITE = "research_to_website" MOBILE_APP = "mobile_app" + COWORK = "cowork" __all__ = ["AgentType"] diff --git a/src/ii_agent/clients/__init__.py b/src/ii_agent/clients/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/ii_agent/clients/cowork/__init__.py b/src/ii_agent/clients/cowork/__init__.py new file mode 100644 index 000000000..e35c60e9a --- /dev/null +++ b/src/ii_agent/clients/cowork/__init__.py @@ -0,0 +1,3 @@ +from ii_agent.clients.cowork.factory import cowork_agent_factory + +__all__ = ["cowork_agent_factory"] \ No newline at end of file diff --git a/src/ii_agent/clients/cowork/config.py b/src/ii_agent/clients/cowork/config.py new file mode 100644 index 000000000..5a15a0f80 --- /dev/null +++ b/src/ii_agent/clients/cowork/config.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +#: Fallback for ``requested_capabilities.core_tools`` (names from +#: :data:`TOOL_CLASS_MAP`) when the request doesn't specify any. +COWORK_DEFAULT_CORE_TOOLS: set[str] = set() + +#: Fallback for ``requested_capabilities.core_skills`` (names from the +#: user's persisted ``SkillTool`` registry) when the request doesn't +#: specify any. +COWORK_DEFAULT_CORE_SKILLS: set[str] = set() + +#: Fallback for ``requested_capabilities.connector`` (e.g. ``"github"``, +#: ``"google_drive"``) when the request doesn't specify one. The +#: connector still requires a wired ``connector_tool`` to instantiate. +COWORK_DEFAULT_CONNECTORS: set[str] = set() + +#: Fallback system prompt — used only when the request doesn't ship its own. +DEFAULT_SYSTEM_PROMPT = ( + "You are the Cowork agent inside II Agent desktop. " + "Help the user reason over local cowork context, use tools deliberately, and keep responses concise and actionable." +) + +#: Heading for the client-defined skill catalog appended to the system prompt. +CLIENT_SKILL_HEADING = "Skills available in the cowork mode:" + +#: Tag used by the shared capability helpers when emitting warnings. +LOG_PREFIX = "cowork" \ No newline at end of file diff --git a/src/ii_agent/clients/cowork/factory.py b/src/ii_agent/clients/cowork/factory.py new file mode 100644 index 000000000..637335c74 --- /dev/null +++ b/src/ii_agent/clients/cowork/factory.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +from typing import Any, Dict, List, Optional + +from ii_agent.agents.agent import IIAgent +from ii_agent.agents.connector.base import BaseConnectorTool +from ii_agent.agents.factory.agent import AgentFactory, agent_factory as default_agent_factory +from ii_agent.agents.factory.tool_manager import AgentToolManager +from ii_agent.agents.models.utils import get_model +from ii_agent.agents.sessions.base import SessionStore +from ii_agent.agents.skills.base import SkillCreator +from ii_agent.agents.types import AgentType +from ii_agent.clients.cowork.config import ( + COWORK_DEFAULT_CONNECTORS, + COWORK_DEFAULT_CORE_SKILLS, + COWORK_DEFAULT_CORE_TOOLS, + CLIENT_SKILL_HEADING, + DEFAULT_SYSTEM_PROMPT, + LOG_PREFIX, +) +from ii_agent.clients.proxy_capabilities import ( + RequestedCapabilities, + build_client_skill_prompt, + build_client_tools, + dedupe_tools, + include_connector_tools, + include_core_skills, + include_core_tools, +) +from ii_agent.core.config.llm_config import LLMConfig +from ii_agent.core.logger import logger +from ii_server.core.workspace import WorkspaceManager + + +class CoworkAgentFactory: + """Build a runtime-configured ``IIAgent`` for the cowork mode.""" + + def __init__(self, factory: AgentFactory): + self._factory = factory + + async def create_agent( + self, + user_id: str, + session_id: str, + llm_config: LLMConfig, + agent_type: AgentType = AgentType.COWORK, + workspace_manager: Optional[WorkspaceManager] = None, + session_store: Optional[SessionStore] = None, + tool_args: Optional[Dict[str, Any]] = None, + metadata: Optional[Dict[str, Any]] = None, + system_prompt: Optional[str] = None, + skill_creator: Optional[SkillCreator] = None, + connector_tool: Optional[BaseConnectorTool] = None, + requested_capabilities: Optional[Any] = None, + ) -> IIAgent: + logger.info( + "Creating cowork %s agent for session %s", + agent_type, + session_id, + ) + + capabilities = RequestedCapabilities.parse(requested_capabilities) + agent_tools: List[Any] = [] + + # Client-defined + client_tools = build_client_tools( + capabilities.client_tools, log_prefix=LOG_PREFIX + ) + if client_tools: + agent_tools.extend(client_tools) + logger.info( + "[cowork] Added %d client-defined tools", + len(client_tools), + ) + client_skill_prompt = build_client_skill_prompt( + capabilities.client_skills, heading=CLIENT_SKILL_HEADING + ) + + # Core tools — user's request wins; default kicks in if request is empty. + core_tools = include_core_tools( + capabilities.core_tools, + default_core_tools=COWORK_DEFAULT_CORE_TOOLS, + log_prefix=LOG_PREFIX, + ) + if core_tools: + agent_tools.extend(core_tools) + logger.info( + "[cowork] Added %d core tools", len(core_tools) + ) + + skill_tool, core_skill_prompt = await include_core_skills( + capabilities.core_skills, + skill_creator=skill_creator, + default_core_skills=COWORK_DEFAULT_CORE_SKILLS, + log_prefix=LOG_PREFIX, + ) + if skill_tool is not None: + agent_tools.append(skill_tool) + logger.info( + "[cowork] Added SkillTool with %d skills", + len(skill_tool._skills_registry), + ) + + # Connector — user's choice unless missing, then first default. + connector_tools = await include_connector_tools( + capabilities.connector, + connector_tool=connector_tool, + workspace_manager=workspace_manager, + default_connectors=COWORK_DEFAULT_CONNECTORS, + log_prefix=LOG_PREFIX, + ) + if connector_tools: + agent_tools.extend(connector_tools) + + # Final assembly + agent_tools = dedupe_tools(agent_tools) + AgentToolManager.log_tool_summary( + agent_tools, f"cowork agent {agent_type.value}" + ) + + model = get_model(llm_config.provider, llm_config=llm_config) + + if not system_prompt: + system_prompt = DEFAULT_SYSTEM_PROMPT + for prompt_section in (client_skill_prompt, core_skill_prompt): + if prompt_section: + system_prompt = f"{system_prompt}\n\n{prompt_section}" + + agent = IIAgent( + user_id=user_id, + session_id=session_id, + model=model, + name=f"{agent_type.value}_agent", + tools=agent_tools, + system_message=system_prompt, + session_store=session_store, + metadata=metadata, + sub_agents=[], + retries=0, + stream=True, + stream_events=True, + store_events=True, + ) + agent.set_id() + + logger.info( + "[cowork] Created %s agent with %d tools", + agent_type.value, + len(agent_tools), + ) + return agent + + +cowork_agent_factory = CoworkAgentFactory(default_agent_factory) diff --git a/src/ii_agent/clients/proxy_capabilities.py b/src/ii_agent/clients/proxy_capabilities.py new file mode 100644 index 000000000..1fac80632 --- /dev/null +++ b/src/ii_agent/clients/proxy_capabilities.py @@ -0,0 +1,443 @@ +"""Proxy capabilities — what each client exposes to the agent loop. + +A "capability" is anything the LLM can invoke during an agent turn: +either a tool (callable with structured input) or a skill (advisory recipe +appended to the system prompt). Each request from a ``ii_agent.clients.*`` +subpackage carries a single ``requested_capabilities`` payload that may +mix four sources: + +* ``client_tools`` — JSON descriptors shipped over the wire by the client. + Become :class:`Function` stubs flagged + ``external_execution=True``: the agent loop pauses on + call and waits for the client to ship results back via + ``external_tool_results``. +* ``client_skills`` — JSON descriptors rendered into the system prompt as a + short advisory catalog. No Python execution. +* ``core_tools`` — names from ii-agent's :data:`TOOL_CLASS_MAP` that the + client wants to opt in to. Each client subpackage + decides which core tools it allows via an + ``allowed_core_tools`` whitelist. +* ``core_skills`` — names from the user's persisted ``SkillTool`` registry + to keep, gated by an ``allowed_core_skills`` whitelist. + +By default each client subpackage loads nothing from the core catalog — +it only exposes what the request explicitly asks for AND what the client +has whitelisted. + +Helpers are pure (no I/O, no DB) and parameterised by ``log_prefix`` / +``heading`` so multi-client deployments stay greppable. +""" + +from __future__ import annotations + +from typing import Any, Iterable, List, Optional + +from ii_agent.agents.factory.tool_manager import AgentToolManager +from ii_agent.agents.factory.tools import TOOL_CLASS_MAP +from ii_agent.agents.skills.prompt_db import generate_skill_tool_description +from ii_agent.agents.tools.function import Function +from ii_agent.core.logger import logger + + +# --------------------------------------------------------------------------- +# Shared helpers +# --------------------------------------------------------------------------- + + +def _normalize_name_set(values: Optional[Iterable[str]]) -> Optional[set[str]]: + if not values: + return None + normalized = { + value.strip().lower() + for value in values + if isinstance(value, str) and value.strip() + } + return normalized or None + + +def _normalize_name_list(values: Optional[Iterable[str]]) -> List[str]: + if not values: + return [] + out: List[str] = [] + seen: set[str] = set() + for value in values: + if not isinstance(value, str): + continue + cleaned = value.strip().lower() + if not cleaned or cleaned in seen: + continue + seen.add(cleaned) + out.append(cleaned) + return out + + +def _tool_name(tool: Any) -> str: + name = getattr(tool, "name", "") + return name.strip().lower() if isinstance(name, str) else "" + + +def _coerce_to_dict(value: Optional[Any]) -> Optional[dict[str, Any]]: + """Accept either a Pydantic model (``model_dump``) or a plain ``dict``.""" + if value is None: + return None + if hasattr(value, "model_dump"): + return value.model_dump() + if isinstance(value, dict): + return value + return None + + +def _pick_requested( + requested: Optional[Iterable[str]], + default: Optional[Iterable[str]], +) -> set[str]: + """Union of the client default and the user's request. + + Both inputs are normalized (lowercased, stripped, de-duped). For + example, ``default = {A, B}`` + ``requested = {A, C}`` → ``{A, B, C}``. + Returns an empty set when neither side specifies anything. + """ + return (_normalize_name_set(requested) or set()) | ( + _normalize_name_set(default) or set() + ) + + +# --------------------------------------------------------------------------- +# Requested capabilities (parsed view of the wire payload) +# --------------------------------------------------------------------------- + + +class RequestedCapabilities: + """Normalized view of a client's ``requested_capabilities`` payload. + + Attributes are always lists (possibly empty), so callers don't have to + juggle ``None`` checks at the use site. + """ + + __slots__ = ( + "client_tools", + "client_skills", + "core_tools", + "core_skills", + "connector", + "raw", + ) + + def __init__( + self, + *, + client_tools: List[dict[str, Any]], + client_skills: List[dict[str, Any]], + core_tools: List[str], + core_skills: List[str], + connector: Optional[str], + raw: dict[str, Any], + ) -> None: + self.client_tools = client_tools + self.client_skills = client_skills + self.core_tools = core_tools + self.core_skills = core_skills + self.connector = connector + self.raw = raw + + @classmethod + def parse(cls, payload: Optional[Any]) -> "RequestedCapabilities": + as_dict = _coerce_to_dict(payload) or {} + + def _list_of_dicts(key: str) -> List[dict[str, Any]]: + value = as_dict.get(key) or [] + if not isinstance(value, list): + return [] + return [item for item in value if isinstance(item, dict)] + + connector = as_dict.get("connector") + if not isinstance(connector, str) or not connector.strip(): + connector = None + else: + connector = connector.strip().lower() + + return cls( + client_tools=_list_of_dicts("client_tools"), + client_skills=_list_of_dicts("client_skills"), + core_tools=_normalize_name_list(as_dict.get("core_tools")), + core_skills=_normalize_name_list(as_dict.get("core_skills")), + connector=connector, + raw=as_dict, + ) + + +# --------------------------------------------------------------------------- +# Core agent capabilities (per-client defaults unioned with the user request) +# --------------------------------------------------------------------------- + + +def include_core_tools( + requested: Optional[Iterable[str]], + *, + default_core_tools: Optional[Iterable[str]] = None, + log_prefix: str = "client", +) -> List[Any]: + """Instantiate core tools from the union of the user request and + ``default_core_tools``. + + No allow-list gating: both the request and the defaults are trusted. + Names not registered in :data:`TOOL_CLASS_MAP` are dropped with a + warning. + """ + selected = _pick_requested(requested, default_core_tools) + if not selected: + return [] + + instances: List[Any] = [] + for tool_name in sorted(selected): + target_name = next( + ( + registered + for registered in TOOL_CLASS_MAP + if registered.strip().lower() == tool_name + ), + None, + ) + if target_name is None: + logger.warning( + "[%s] Core tool '%s' is not registered", log_prefix, tool_name + ) + continue + instance = AgentToolManager.convert_tool(target_name) + if instance is None: + logger.warning( + "[%s] Failed to instantiate core tool '%s'", log_prefix, tool_name + ) + continue + instances.append(instance) + return instances + + +async def include_core_skills( + requested: Optional[Iterable[str]], + *, + skill_creator: Optional[Any], + default_core_skills: Optional[Iterable[str]] = None, + log_prefix: str = "client", +) -> tuple[Optional[Any], Optional[str]]: + """Build a ``SkillTool`` for skills from the union of the user request + and ``default_core_skills``. + + Returns ``(skill_tool, prompt_section)``. Either may be ``None`` — + callers should treat ``None`` as "drop the skill tool entirely / don't + append a prompt section". + """ + if skill_creator is None: + return None, None + + selected = _pick_requested(requested, default_core_skills) + if not selected: + return None, None + + skill_tool = await skill_creator.create_skill_tool() + if skill_tool is None: + return None, None + + registry = getattr(skill_tool, "_skills_registry", None) + if not isinstance(registry, dict): + return None, None + + filtered = { + name: skill + for name, skill in registry.items() + if name.strip().lower() in selected + } + missing = selected - {name.strip().lower() for name in registry.keys()} + for name in sorted(missing): + logger.warning( + "[%s] Core skill '%s' not found in user registry", log_prefix, name + ) + + if not filtered: + return None, None + + skill_tool._skills_registry = filtered + skill_tool.description = generate_skill_tool_description(list(filtered.values())) + return skill_tool, skill_tool.description + + +async def include_connector_tools( + requested: Optional[str], + *, + connector_tool: Optional[Any], + workspace_manager: Optional[Any] = None, + default_connectors: Optional[Iterable[str]] = None, + log_prefix: str = "client", +) -> List[Any]: + """Instantiate connector tools for the user-requested connector, + falling back to the first entry of ``default_connectors``. + + Connector is a single-slot capability (one connector per agent run), + so unlike tools/skills there's no union — the user's choice wins, and + we only consult ``default_connectors`` when the request is silent. + Returns ``[]`` when nothing is selected, no ``connector_tool`` is + wired, or instantiation fails. + """ + selected = (requested or "").strip().lower() or next( + iter(sorted(_normalize_name_set(default_connectors) or set())), None + ) + if not selected or connector_tool is None: + return [] + + try: + connector_tools = await connector_tool.create_connector_tools( + workspace_manager=workspace_manager, + ) + except Exception as exc: + logger.error( + "[%s] Failed to load connector '%s': %s", + log_prefix, + selected, + exc, + exc_info=True, + ) + return [] + + if connector_tools: + logger.info( + "[%s] Added %d connector tools (%s)", + log_prefix, + len(connector_tools), + selected, + ) + return connector_tools or [] + + +def dedupe_tools(agent_tools: List[Any]) -> List[Any]: + """Drop duplicate tools by case-insensitive name, preserving order.""" + seen: set[str] = set() + out: List[Any] = [] + for tool in agent_tools: + name = _tool_name(tool) + if not name: + out.append(tool) + continue + if name in seen: + continue + seen.add(name) + out.append(tool) + return out + + +# --------------------------------------------------------------------------- +# Client-defined capabilities +# --------------------------------------------------------------------------- + + +def _placeholder_tool_entrypoint(**_: Any) -> str: + """Body of every external-execution Function — never actually invoked.""" + return "This tool is executed by the client runtime." + + +def _build_external_function( + *, + name: str, + display_name: str, + description: str, + parameters: dict[str, Any], +) -> Function: + return Function( + name=name, + description=description, + parameters=parameters, + display_name=display_name, + entrypoint=_placeholder_tool_entrypoint, + skip_entrypoint_processing=True, + external_execution=True, + show_result=True, + ) + + +def build_client_tools( + client_tool_specs: Optional[Iterable[dict[str, Any]]], + *, + log_prefix: str = "client", +) -> List[Function]: + """Build external-execution Functions from ``client_tools`` descriptors. + + Each descriptor must carry ``name`` and ``description``; ``input_schema`` + falls back to an open object schema. ``aliases`` and ``display_name`` are + optional. + """ + if not client_tool_specs: + return [] + + client_tools: List[Function] = [] + for spec in client_tool_specs: + if not isinstance(spec, dict): + continue + name = spec.get("name") + description = spec.get("description") + if not isinstance(name, str) or not name.strip(): + continue + if not isinstance(description, str) or not description.strip(): + continue + + display_name = spec.get("display_name") + if not isinstance(display_name, str) or not display_name.strip(): + display_name = name + + parameters = spec.get("input_schema") + if not isinstance(parameters, dict) or not parameters: + parameters = {"type": "object", "properties": {}, "required": []} + + aliases = spec.get("aliases") or [] + if not isinstance(aliases, list): + aliases = [] + published_names = [name] + [ + a.strip() for a in aliases if isinstance(a, str) and a.strip() + ] + + seen: set[str] = set() + for published_name in published_names: + if published_name in seen: + continue + seen.add(published_name) + client_tools.append( + _build_external_function( + name=published_name, + display_name=display_name, + description=description, + parameters=parameters, + ) + ) + + if not client_tools and any( + isinstance(spec, dict) for spec in client_tool_specs + ): + logger.warning( + "[%s] client_tools provided but no valid descriptors were published", + log_prefix, + ) + return client_tools + + +def build_client_skill_prompt( + client_skill_specs: Optional[Iterable[dict[str, Any]]], + *, + heading: str = "Skills available in the client runtime:", +) -> Optional[str]: + """Render a short skill catalog for inclusion in the system prompt.""" + if not client_skill_specs: + return None + + lines: List[str] = [] + for skill in client_skill_specs: + if not isinstance(skill, dict): + continue + name = skill.get("name") + description = skill.get("description") + if not isinstance(name, str) or not name.strip(): + continue + if not isinstance(description, str) or not description.strip(): + continue + lines.append(f"- {name}: {description}") + + if not lines: + return None + return f"{heading}\n" + "\n".join(lines) diff --git a/src/ii_agent/realtime/handlers/cowork_continue_run.py b/src/ii_agent/realtime/handlers/cowork_continue_run.py index cdf083ce6..366b55b46 100644 --- a/src/ii_agent/realtime/handlers/cowork_continue_run.py +++ b/src/ii_agent/realtime/handlers/cowork_continue_run.py @@ -1,17 +1,13 @@ -"""Handler for cowork_continue_run command. - -Adapted from the legacy ``server.socket.command.cowork_continue_run`` -to use the new ``BaseCommandHandler`` / pubsub / container pattern. -""" +"""Handler for cowork_continue_run command.""" from __future__ import annotations from typing import Any from uuid import UUID -from ii_agent.agents.factory.agent import agent_factory from ii_agent.agents.sessions import AgentSessionStore from ii_agent.agents.types import AgentType +from ii_agent.clients.cowork.factory import cowork_agent_factory from ii_agent.core.db import get_db_session_local, get_session_factory from ii_agent.core.logger import logger from ii_agent.realtime.events.app_events import ( @@ -26,19 +22,23 @@ class CoworkContinueRunHandler(ContinueRunHandler): - """Cowork-only continue handler with desktop external execution support.""" + """Handle ``cowork_continue_run`` commands.""" _content_type = CoworkContinueRunContent def get_command_type(self) -> CommandType: return CommandType.COWORK_CONTINUE_RUN - async def handle(self, content: CoworkContinueRunContent, session_info: SessionInfo) -> None: + async def handle( + self, + content: CoworkContinueRunContent, + session_info: SessionInfo, + ) -> None: if session_info.api_version != "v1": await self._send_error_event( session_info.id, error_code=ErrorCode.UNSUPPORTED_API_VERSION, - message="Continue run is only supported for v1 API version", + message="continue_run is only supported for v1 API version", ) return @@ -47,7 +47,6 @@ async def handle(self, content: CoworkContinueRunContent, session_info: SessionI user_input = content.user_input external_tool_results = content.external_tool_results or [] - # Send AGENT_CONTINUE event immediately await self.send_event( AgentContinueEvent( session_id=UUID(str(session_info.id)), @@ -64,7 +63,6 @@ async def handle(self, content: CoworkContinueRunContent, session_info: SessionI run_response = await session_store.get_by_run_id( run_id=run_id, session_id=str(session_info.id) ) - if not run_response: await self._send_error_event( session_info.id, @@ -73,12 +71,12 @@ async def handle(self, content: CoworkContinueRunContent, session_info: SessionI ) return - run_task_data = await self._load_cowork_run_task_data(run_id) + run_task_data = await self._load_run_task_data(run_id) for tool in run_response.tools_requiring_confirmation: tool.confirmed = bool(confirmed) logger.info( - "Cowork continue confirmation for run %s tool_call_id=%s confirmed=%s", + "[cowork] continue confirmation run=%s tool_call_id=%s confirmed=%s", run_id, tool.tool_call_id, confirmed, @@ -92,12 +90,9 @@ async def handle(self, content: CoworkContinueRunContent, session_info: SessionI tool.answered = False self._apply_external_tool_results( - run_response.tools, - external_tool_results, - run_id, + run_response.tools, external_tool_results, run_id ) - # Get model config — fall back to hardcoded default for cowork llm_config = None if session_info.model_setting_id: try: @@ -107,25 +102,24 @@ async def handle(self, content: CoworkContinueRunContent, session_info: SessionI db, setting_id=session_info.model_setting_id ) ) - except (ValueError, Exception) as e: + except Exception as exc: logger.warning( - "Cowork continue_run model resolution failed: %s, using default", e + "[cowork] continue_run model resolution failed: %s", + exc, ) - # Create cowork agent for continuation - agent = await agent_factory.create_cowork_agent( - session_info=session_info, + agent = await cowork_agent_factory.create_agent( + user_id=str(session_info.user_id), + session_id=str(session_info.id), llm_config=llm_config, - workspace_manager=None, agent_type=AgentType(session_info.agent_type) if session_info.agent_type - else AgentType.GENERAL, + else AgentType.COWORK, + session_store=session_store, metadata=run_task_data.get("metadata"), system_prompt=run_task_data.get("system_prompt"), - tool_names=run_task_data.get("tool_names"), - skill_names=run_task_data.get("skill_names"), - desktop_capabilities=run_task_data.get("desktop_capabilities"), - agent_config=run_task_data.get("agent_config"), + requested_capabilities=run_task_data.get("requested_capabilities"), + skill_creator=self._create_skill_creator(session_info.user_id), ) await self.send_event( @@ -150,29 +144,29 @@ async def handle(self, content: CoworkContinueRunContent, session_info: SessionI event_stream, session_info, run_id=UUID(run_response.run_id), - is_user_key=llm_config.is_user_model(), + is_user_key=llm_config.is_user_model() if llm_config else False, llm_config=llm_config, ) - except ValueError as error: - logger.error(f"ValueError in cowork_continue_run: {str(error)}") + except ValueError as exc: + logger.error("[cowork] continue_run ValueError: %s", exc) await self._send_error_event( session_info.id, error_code=ErrorCode.VALIDATION_ERROR, - message=str(error), + message=str(exc), ) - except Exception as error: + except Exception as exc: logger.error( - f"Error in cowork_continue_run handler: {str(error)}", - exc_info=True, + "[cowork] continue_run failed: %s", exc, exc_info=True ) await self._send_error_event( session_info.id, error_code=ErrorCode.EXECUTION_ERROR, - message=f"Failed to continue run: {str(error)}", + message=f"Failed to continue run: {exc}", ) - async def _load_cowork_run_task_data(self, run_id: str) -> dict[str, Any]: + async def _load_run_task_data(self, run_id: str) -> dict[str, Any]: + """Reload the original ``RunTask.data`` so overrides survive resume.""" try: run_task_id = UUID(str(run_id)) except ValueError: @@ -180,13 +174,11 @@ async def _load_cowork_run_task_data(self, run_id: str) -> dict[str, Any]: async with get_db_session_local() as db: run_task = await self._container.run_task_service.get_task_by_id( - db, - task_id=run_task_id, + db, task_id=run_task_id ) if not run_task or not isinstance(run_task.data, dict): return {} - return run_task.data @staticmethod @@ -195,39 +187,41 @@ def _apply_external_tool_results( external_tool_results: list[dict[str, Any]], run_id: str, ) -> None: + """Inject extension-side tool execution results back into paused tools.""" if not external_tool_results: return - tool_results_by_id = { - str(result.get("tool_call_id")): result - for result in external_tool_results - if isinstance(result, dict) and result.get("tool_call_id") + by_id = { + str(item.get("tool_call_id")): item + for item in external_tool_results + if isinstance(item, dict) and item.get("tool_call_id") } for tool in tools: tool_call_id = getattr(tool, "tool_call_id", None) if not tool_call_id: continue - - external_result = tool_results_by_id.get(str(tool_call_id)) - if not external_result: + external = by_id.get(str(tool_call_id)) + if not external: continue - llm_content = external_result.get("llm_content") - user_display_content = external_result.get("user_display_content") - tool.result = llm_content if llm_content is not None else user_display_content - tool.tool_call_error = bool(external_result.get("is_error")) + llm_content = external.get("llm_content") + user_display_content = external.get("user_display_content") + tool.result = ( + llm_content if llm_content is not None else user_display_content + ) + tool.tool_call_error = bool(external.get("is_error")) - tool_input = external_result.get("tool_input") + tool_input = external.get("tool_input") if isinstance(tool_input, dict): tool.tool_args = tool_input - tool_name = external_result.get("tool_name") + tool_name = external.get("tool_name") if isinstance(tool_name, str) and tool_name.strip(): tool.tool_name = tool_name logger.info( - "Cowork continue applied external tool result for run %s tool_call_id=%s error=%s", + "[cowork] applied external tool result run=%s tool_call_id=%s error=%s", run_id, tool_call_id, tool.tool_call_error, diff --git a/src/ii_agent/realtime/handlers/cowork_query.py b/src/ii_agent/realtime/handlers/cowork_query.py index 74c636a33..1b5f4d5c7 100644 --- a/src/ii_agent/realtime/handlers/cowork_query.py +++ b/src/ii_agent/realtime/handlers/cowork_query.py @@ -1,19 +1,18 @@ -"""Handler for cowork_query command with agent overrides. - -Adapted from the legacy ``server.socket.command.cowork_query`` -to use the new ``BaseCommandHandler`` / pubsub / container pattern. -""" +"""Handler for cowork_query command with agent overrides.""" from __future__ import annotations -from ii_agent.agents.factory.agent import agent_factory +from ii_agent.agents.sandboxes import upload_media_to_sandbox +from ii_agent.agents.sessions import AgentSessionStore from ii_agent.agents.types import AgentType -from ii_agent.core.db import get_db_session_local +from ii_agent.clients.cowork.factory import cowork_agent_factory +from ii_agent.core.db import get_db_session_local, get_session_factory from ii_agent.core.logger import logger +from ii_agent.files.media import File as UrlFile, Image from ii_agent.realtime.events.app_events import ErrorCode from ii_agent.realtime.handlers.base import CommandType from ii_agent.realtime.handlers.query import UserQueryHandler -from ii_agent.realtime.schemas import QueryCommandContent +from ii_agent.realtime.schemas import CoworkQueryCommandContent from ii_agent.sessions.schemas import SessionInfo from ii_agent.sessions.types import AppKind from ii_agent.settings.llm.schemas import ModelConfig @@ -21,48 +20,36 @@ class CoworkQueryHandler(UserQueryHandler): - """Handler for cowork-specific query command with agent overrides.""" + """Handle ``cowork_query`` commands from the cowork mode.""" - COWORK_SESSION_AGENT_TYPE = "cowork" + _content_type = CoworkQueryCommandContent def get_command_type(self) -> CommandType: return CommandType.COWORK_QUERY - async def handle(self, content: QueryCommandContent, existing_session: SessionInfo) -> None: - query_command = content - - # Stamp the session as a cowork app_kind so it is excluded from the - # standard Project sidebar listing. Cowork sessions are managed by - # the desktop runtime and must never appear there. + async def handle( + self, + content: CoworkQueryCommandContent, + existing_session: SessionInfo, + ) -> None: await self._ensure_cowork_app_kind(existing_session.id) is_valid, session_info, llm_config = await self.validate_and_update_session( - existing_session, query_command + existing_session, content ) + if not is_valid or not session_info or not llm_config: + return - # For cowork mode, fall back to hardcoded model if resolution fails - if not is_valid or not llm_config: - if not session_info: - return - - await self._handle_cowork_query(query_command, session_info, llm_config) + await self._handle_cowork_query(content, session_info, llm_config) async def _handle_cowork_query( self, - query_command: QueryCommandContent, + query_command: CoworkQueryCommandContent, session_info: SessionInfo, llm_config: ModelConfig, ) -> None: - """Handle cowork query by delegating to the cowork agent factory.""" - plan_service = self._container.plan_service run_service = self._container.run_task_service - - milestone_context = None - if query_command.milestone_ids and query_command.plan_context: - milestone_context = plan_service.get_milestone_context( - plan_context=query_command.plan_context, - milestone_ids=query_command.milestone_ids, - ) + file_service = self._container.file_service run_task = None try: @@ -77,87 +64,98 @@ async def _handle_cowork_query( session_info, query_command, db, run_id=run_task.id ) await db.commit() - await self.send_event(user_event) - except Exception as e: - logger.error(f"Failed to claim task: {e}", exc_info=True) + except Exception as exc: + logger.error( + "[cowork] Failed to claim task: %s", exc, exc_info=True + ) await self._send_error_event( session_id=session_info.id, error_code=ErrorCode.INTERNAL_ERROR, - message=str(e), + message=str(exc), user_id=session_info.user_id, ) return - final_status = RunStatus.FAILED try: - agent = await agent_factory.create_cowork_agent( - session_info=session_info, + session_store = AgentSessionStore(session_maker=get_session_factory()) + agent = await cowork_agent_factory.create_agent( + user_id=str(session_info.user_id), + session_id=str(session_info.id), llm_config=llm_config, - workspace_manager=None, agent_type=AgentType(session_info.agent_type) if session_info.agent_type - else AgentType.GENERAL, + else AgentType.COWORK, + session_store=session_store, tool_args=query_command.tool_args, metadata=query_command.metadata, - system_prompt=getattr(query_command, "system_prompt", None), - tool_names=getattr(query_command, "tool_names", None), - skill_names=getattr(query_command, "skill_names", None), - desktop_capabilities=getattr(query_command, "desktop_capabilities", None), - agent_config=getattr(query_command, "agent_config", None), + system_prompt=query_command.system_prompt, + requested_capabilities=query_command.requested_capabilities, + skill_creator=self._create_skill_creator(session_info.user_id), ) - instruction_text = query_command.text - if milestone_context: - instruction_text = f"{milestone_context}\n\nUser instruction: {query_command.text}" + images: list[Image] = [] + files: list[UrlFile] = [] + if query_command.files: + async with get_db_session_local() as db: + images, files = await file_service.prepare_agent_files( + db, + file_ids=query_command.files, + user_id=session_info.user_id, + session_id=session_info.id, + ) + + if images or files: + sandbox_service = self._container.sandbox_service + async with get_db_session_local() as db: + sandbox = await sandbox_service.init_sandbox( + db, + session_id=session_info.id, + user_id=session_info.user_id, + ) + agent.sandbox = sandbox + await sandbox.create_directory(sandbox.upload_path, exist_ok=True) + sandbox_files, sandbox_images = await upload_media_to_sandbox( + sandbox=sandbox, + files=files or [], + images=images or [], + upload_path=sandbox.upload_path, + ) + if sandbox_files: + files = sandbox_files + if sandbox_images: + images = sandbox_images event_stream = await agent.arun( - instruction_text, + query_command.text, stream=True, stream_events=True, run_id=str(run_task.id), + images=images or None, + files=files or None, yield_run_output=False, ) - final_status = await self.process_agent_event_stream( + await self.process_agent_event_stream( event_stream, session_info, run_id=run_task.id, is_user_key=llm_config.is_user_model(), llm_config=llm_config, ) - - async with get_db_session_local() as db: - await plan_service.update_milestones_after_run( - db, - session_id=session_info.id, - milestone_ids=query_command.milestone_ids, - status=final_status, - ) - - except Exception as e: - logger.opt(exception=True).error("Error processing cowork query: {}", str(e)) + except Exception as exc: + logger.opt(exception=True).error( + "[cowork] Error processing query: %s", exc + ) async with get_db_session_local() as db: await run_service.transition_status( db, task_id=run_task.id, to_status=RunStatus.FAILED ) await db.commit() - if query_command.milestone_ids: - async with get_db_session_local() as db: - await plan_service.reset_milestones_to_pending( - db, - session_id=session_info.id, - milestone_ids=query_command.milestone_ids, - ) raise - - async def _ensure_cowork_app_kind(self, session_id) -> None: - """Persist ``app_kind = cowork`` on the session row if not already set. - Cowork sessions must be invisible to the standard Project sidebar - listing. Stamping ``app_kind`` here is the canonical discriminator - used by ``SessionRepository.get_user_sessions``. - """ + async def _ensure_cowork_app_kind(self, session_id) -> None: + """Stamp ``app_kind = cowork`` on the session if not set.""" try: async with get_db_session_local() as db: session = await self._container.session_service._session_repo.get_by_id( @@ -170,5 +168,7 @@ async def _ensure_cowork_app_kind(self, session_id) -> None: await db.commit() except Exception as exc: logger.warning( - "Failed to stamp cowork app_kind on session %s: %s", session_id, exc + "[cowork] Failed to stamp app_kind on session %s: %s", + session_id, + exc, ) diff --git a/src/ii_agent/realtime/handlers/factory.py b/src/ii_agent/realtime/handlers/factory.py index eb971de48..2146ba906 100644 --- a/src/ii_agent/realtime/handlers/factory.py +++ b/src/ii_agent/realtime/handlers/factory.py @@ -64,7 +64,6 @@ def _initialize_handlers(self) -> None: self._handlers = { CommandType.QUERY: query_handler, - CommandType.COWORK_QUERY: CoworkQueryHandler(pubsub=ps, container=ct), CommandType.PLAN: PlanHandler(pubsub=ps, container=ct), CommandType.SANDBOX_STATUS: SandboxStatusHandler(pubsub=ps, container=ct), CommandType.AWAKE_SANDBOX: AwakeSandboxHandler(pubsub=ps, container=ct), @@ -72,7 +71,6 @@ def _initialize_handlers(self) -> None: CommandType.PING: PingHandler(pubsub=ps, container=ct), CommandType.CANCEL: CancelHandler(pubsub=ps, container=ct), CommandType.CONTINUE_RUN: ContinueRunHandler(pubsub=ps, container=ct), - CommandType.COWORK_CONTINUE_RUN: CoworkContinueRunHandler(pubsub=ps, container=ct), CommandType.ENHANCE_PROMPT: EnhancePromptHandler(pubsub=ps, container=ct), CommandType.PUBLISH_PROJECT: PublishProjectHandler(pubsub=ps, container=ct), CommandType.PUBLISH_CLOUD_RUN: CloudRunPublishHandler(pubsub=ps, container=ct), @@ -96,6 +94,9 @@ def _initialize_handlers(self) -> None: CommandType.DESIGN_SAVE_STATE: DesignSaveStateHandler(pubsub=ps, container=ct), CommandType.DESIGN_SYNC_STATE: DesignSyncStateHandler(pubsub=ps, container=ct), CommandType.SLIDE_DECK_SYNC_STATE: SlideDeckSyncStateHandler(pubsub=ps, container=ct), + CommandType.COWORK_QUERY: CoworkQueryHandler(pubsub=ps, container=ct), + CommandType.COWORK_CONTINUE_RUN: CoworkContinueRunHandler(pubsub=ps, container=ct), + } def get_handler(self, command_type: CommandType) -> BaseCommandHandler | None: diff --git a/src/ii_agent/realtime/schemas.py b/src/ii_agent/realtime/schemas.py index 253d34643..2b9eff1a5 100644 --- a/src/ii_agent/realtime/schemas.py +++ b/src/ii_agent/realtime/schemas.py @@ -25,7 +25,6 @@ class CommandType(StrEnum): INIT_AGENT = "init_agent" QUERY = "query" - COWORK_QUERY = "cowork_query" PLAN = "plan" WORKSPACE_INFO = "workspace_info" AWAKE_SANDBOX = "awake_sandbox" @@ -33,7 +32,6 @@ class CommandType(StrEnum): PING = "ping" CANCEL = "cancel" CONTINUE_RUN = "continue_run" - COWORK_CONTINUE_RUN = "cowork_continue_run" ENHANCE_PROMPT = "enhance_prompt" PUBLISH_PROJECT = "publish" PUBLISH_CLOUD_RUN = "publish_cloud_run" @@ -60,6 +58,10 @@ class CommandType(StrEnum): APPLE_CHECK_AUTH = "apple_check_auth" SAVE_EXPO_TOKEN = "save_expo_token" + # Cowork mode + COWORK_QUERY = "cowork_query" + COWORK_CONTINUE_RUN = "cowork_continue_run" + # --------------------------------------------------------------------------- # Base empty content (shared fields for no-payload commands) @@ -256,27 +258,6 @@ class ContinueRunContent(BaseModel): user_input: dict[str, str] = {} -class CoworkQueryCommandContent(BaseCommandQuery): - """Payload for the ``cowork_query`` command.""" - - command: Literal[CommandType.COWORK_QUERY] = CommandType.COWORK_QUERY - system_prompt: str | None = None - tool_names: list[str] | None = None - skill_names: list[str] | None = None - agent_config: dict[str, Any] | None = None - desktop_capabilities: dict[str, Any] | None = None - - -class CoworkContinueRunContent(BaseModel): - """Payload for cowork_continue_run command.""" - - command: Literal[CommandType.COWORK_CONTINUE_RUN] = CommandType.COWORK_CONTINUE_RUN - run_id: str - confirmed: bool - user_input: dict[str, str] = {} - external_tool_results: list[dict[str, Any]] | None = None - - # --------------------------------------------------------------------------- # Publish / deploy content models # --------------------------------------------------------------------------- @@ -441,6 +422,28 @@ class SlideDeckSyncStateContent(BaseModel): model_config = ConfigDict(extra="allow") +# --------------------------------------------------------------------------- +# Cowork mode content models +# --------------------------------------------------------------------------- + +class CoworkQueryCommandContent(BaseCommandQuery): + """Payload for the ``cowork_query`` command.""" + + command: Literal[CommandType.COWORK_QUERY] = CommandType.COWORK_QUERY + system_prompt: str | None = None + requested_capabilities: dict[str, Any] | None = None + + +class CoworkContinueRunContent(BaseModel): + """Payload for cowork_continue_run command.""" + + command: Literal[CommandType.COWORK_CONTINUE_RUN] = CommandType.COWORK_CONTINUE_RUN + run_id: str + confirmed: bool + user_input: dict[str, str] = {} + external_tool_results: list[dict[str, Any]] | None = None + + # --------------------------------------------------------------------------- # Discriminated union of all command content types # ---------------------------------------------------------------------------