diff --git a/package-lock.json b/package-lock.json index 5a48d6563..4b1f72104 100644 --- a/package-lock.json +++ b/package-lock.json @@ -148,7 +148,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1530,7 +1529,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -5147,6 +5145,7 @@ "integrity": "sha512-6rqTdFt67AAAzln3NOKsXRmv5ZzPkgbfaebKBqUbts7vK1GZudqnrun5a8d3M/h955cam9RHZ6Jb4Y1XhnmFPg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", @@ -5171,6 +5170,7 @@ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -5185,6 +5185,7 @@ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -5206,6 +5207,7 @@ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -5219,6 +5221,7 @@ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=6.6.0" } @@ -5229,6 +5232,7 @@ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -5272,6 +5276,7 @@ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -5290,6 +5295,7 @@ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -5300,6 +5306,7 @@ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -5313,6 +5320,7 @@ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 0.8" } @@ -5323,6 +5331,7 @@ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=18" }, @@ -5336,6 +5345,7 @@ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -5349,6 +5359,7 @@ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -5359,6 +5370,7 @@ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "optional": true, + "peer": true, "dependencies": { "side-channel": "^1.1.0" }, @@ -5375,6 +5387,7 @@ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", @@ -5398,6 +5411,7 @@ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -5414,6 +5428,7 @@ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "optional": true, + "peer": true, "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", @@ -6583,7 +6598,6 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.18.tgz", "integrity": "sha512-DZgd6860dxcq3YX7UzIXeBr6m3UgXvo9acxp5jiJyIZXdR00Br9JwVkO7e0bUeTA2d3Z8dsmtAR84Y86NnH64Q==", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/core": "^7.12.4", "escape-string-regexp": "^4.0.0", @@ -7273,7 +7287,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz", "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -7308,7 +7321,6 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -7430,7 +7442,6 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -8119,7 +8130,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8180,7 +8190,6 @@ "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.78.tgz", "integrity": "sha512-ec77fmQwJGLduswMrW4AAUGSOiu8dZaIwMmWHHGKsrMUFFS6ugfkTyx0srtuKYHNRRLRC2dT7cPirnUl98VnxA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@ai-sdk/gateway": "2.0.1", "@ai-sdk/provider": "2.0.0", @@ -9069,7 +9078,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -10554,7 +10562,6 @@ "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -11119,7 +11126,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -11316,7 +11322,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -11687,7 +11692,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.20.tgz", "integrity": "sha512-mWHky+H63W60P5Oo+VbtqzF2sLvdaoSSwG57H9rlq1DrgIla++QJZuwJkXXo55lYPymVmkVhwG6FjWYKKylwpw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.13", @@ -11860,7 +11864,6 @@ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.10.tgz", "integrity": "sha512-Rhtv+X974k0Cahmvx6p7ER5+pNhBC0XbP1lRviL2J1Xl4sT2FBaIuIxF/0I0CbhOsySf0ksqc5caFweAy9Ewiw==", "license": "MIT", - "peer": true, "dependencies": { "@expo/config": "~12.0.10", "@expo/env": "~2.0.7" @@ -11941,7 +11944,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.9.tgz", "integrity": "sha512-xCoQbR/36qqB6tew/LQ6GWICpaBmHLhg/Loix5Rku/0ZtNaXMJv08M9o1AcrdiGTn/Xf/BnLu6DgS45cWQEHZg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -12019,7 +12021,6 @@ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.8.tgz", "integrity": "sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg==", "license": "MIT", - "peer": true, "dependencies": { "expo-constants": "~18.0.8", "invariant": "^2.2.4" @@ -12164,6 +12165,15 @@ "node": ">=20.16.0" } }, + "node_modules/expo-sharing": { + "version": "14.0.8", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-14.0.8.tgz", + "integrity": "sha512-A1pPr2iBrxypFDCWVAESk532HK+db7MFXbvO2sCV9ienaFXAk7lIBm6bkqgE6vzRd9O3RGdEGzYx80cYlc089Q==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-splash-screen": { "version": "31.0.10", "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-31.0.10.tgz", @@ -13325,7 +13335,6 @@ "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -14849,7 +14858,6 @@ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", "license": "MPL-2.0", - "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -17315,7 +17323,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17356,7 +17363,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -17393,7 +17399,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -17483,7 +17488,6 @@ "resolved": "https://registry.npmjs.org/react-native-edge-to-edge/-/react-native-edge-to-edge-1.7.0.tgz", "integrity": "sha512-ERegbsq28yoMndn/Uq49i4h6aAhMvTEjOfkFh50yX9H/dMjjCr/Tix/es/9JcPRvC+q7VzCMWfxWDUb6Jrq1OQ==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -17503,7 +17507,6 @@ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", "license": "MIT", - "peer": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -17560,7 +17563,6 @@ "integrity": "sha512-+/uVS7FQwOiKYZQERMIvBRv5/X3CVHrFG6Nr/kIhVfVxGeUimHnBz7cgA97lJKIn7AKDRWL+UjLedW8pGOt0dg==", "hasInstallScript": true, "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -17587,7 +17589,6 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz", "integrity": "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==", "license": "MIT", - "peer": true, "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" @@ -17616,7 +17617,6 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz", "integrity": "sha512-/wJE58HLEAkATzhhX1xSr+fostLsK8Q97EfpfMDKo8jlOc1QKESSX/FQrhk7HhQH/2uSaox4Y86sNaI02kteiA==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -17627,7 +17627,6 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", "license": "MIT", - "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", @@ -17643,7 +17642,6 @@ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.14.0.tgz", "integrity": "sha512-B3gYc7WztcOT4N54AtUutbe0Nuqqh/nkresY0fAXzUHYLsWuIu/yGiCCD3DKfAs6GLv5LFtWTu7N333Q+e3bkg==", "license": "MIT", - "peer": true, "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", @@ -17686,7 +17684,6 @@ "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", @@ -17719,7 +17716,6 @@ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", @@ -17835,7 +17831,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -19874,6 +19869,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -20032,7 +20028,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20389,7 +20384,6 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -21106,7 +21100,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -21177,6 +21170,7 @@ "expo-keep-awake": "^15.0.7", "expo-linking": "~8.0.8", "expo-router": "~6.0.13", + "expo-sharing": "^14.0.8", "expo-splash-screen": "~31.0.10", "expo-symbols": "~1.0.7", "expo-system-ui": "~6.0.7", diff --git a/packages/app/package.json b/packages/app/package.json index 48358ef24..d32b6ba22 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -39,6 +39,7 @@ "expo-keep-awake": "^15.0.7", "expo-linking": "~8.0.8", "expo-router": "~6.0.13", + "expo-sharing": "^14.0.8", "expo-splash-screen": "~31.0.10", "expo-symbols": "~1.0.7", "expo-system-ui": "~6.0.7", diff --git a/packages/app/src/app/file-explorer.tsx b/packages/app/src/app/file-explorer.tsx index bb9b5533e..a897825de 100644 --- a/packages/app/src/app/file-explorer.tsx +++ b/packages/app/src/app/file-explorer.tsx @@ -1,12 +1,16 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ActivityIndicator, + Alert, FlatList, Image as RNImage, + LayoutChangeEvent, ListRenderItemInfo, ViewToken, NativeScrollEvent, NativeSyntheticEvent, + Modal, + Platform, Pressable, ScrollView, Text, @@ -17,21 +21,23 @@ import { import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import * as Clipboard from "expo-clipboard"; +import * as FileSystem from "expo-file-system"; +import * as Sharing from "expo-sharing"; import { - Copy, - Check, File, FileText, Folder, Image as ImageIcon, LayoutGrid, List as ListIcon, + MoreVertical, X, } from "lucide-react-native"; import { BackHeader } from "@/components/headers/back-header"; import type { ExplorerEntry } from "@/stores/session-store"; import type { ConnectionStatus } from "@/contexts/daemon-connections-context"; import { useDaemonConnections } from "@/contexts/daemon-connections-context"; +import type { DaemonProfile } from "@/contexts/daemon-registry-context"; import { formatConnectionStatus } from "@/utils/daemons"; import { useSessionStore } from "@/stores/session-store"; @@ -109,6 +115,8 @@ function FileExplorerContent({ fileParamRaw, }: FileExplorerContentProps) { const { theme } = useUnistyles(); + const { connectionStates } = useDaemonConnections(); + const daemonProfile = connectionStates.get(serverId)?.daemon; const agent = useSessionStore((state) => agentId && state.sessions[serverId] @@ -125,18 +133,17 @@ function FileExplorerContent({ const methods = useSessionStore((state) => state.sessions[serverId]?.methods); const requestDirectoryListing = methods?.requestDirectoryListing; const requestFilePreview = methods?.requestFilePreview; + const requestFileDownloadToken = methods?.requestFileDownloadToken; const navigateExplorerBack = methods?.navigateExplorerBack; const [viewMode, setViewMode] = useState<"list" | "grid">("list"); const [selectedEntryPath, setSelectedEntryPath] = useState(null); const pendingPathParamRef = useRef(null); const pendingFileParamRef = useRef(null); - const [copiedPath, setCopiedPath] = useState(null); - const copyTimeoutRef = useRef | null>(null); const listScrollRef = useRef | null>(null); const listScrollOffsetRef = useRef(0); const scrollOffsetsByPathRef = useRef>(new Map()); const pendingScrollRestoreRef = useRef(null); - const { width: windowWidth } = useWindowDimensions(); + const { width: windowWidth, height: windowHeight } = useWindowDimensions(); const normalizedPathParam = normalizePathParam(getFirstParam(pathParamRaw)); const normalizedFileParam = normalizeFileParam(getFirstParam(fileParamRaw)); @@ -196,6 +203,60 @@ function FileExplorerContent({ }, [windowWidth]); const listColumns = viewMode === "grid" ? gridColumnCount : 1; const listKey = viewMode === "grid" ? `grid-${gridColumnCount}` : "list"; + const [menuEntry, setMenuEntry] = useState(null); + const [menuAnchor, setMenuAnchor] = useState({ top: 0, left: 0 }); + const [menuHeight, setMenuHeight] = useState(0); + const agentIdRef = useRef(agentId); + const viewModeRef = useRef(viewMode); + const requestFilePreviewRef = useRef(requestFilePreview); + const explorerFilesRef = useRef(explorerState?.files); + + useEffect(() => { + agentIdRef.current = agentId; + }, [agentId]); + + useEffect(() => { + viewModeRef.current = viewMode; + }, [viewMode]); + + useEffect(() => { + requestFilePreviewRef.current = requestFilePreview; + }, [requestFilePreview]); + + useEffect(() => { + explorerFilesRef.current = explorerState?.files; + }, [explorerState?.files]); + + const handleViewableItemsChangedRef = useRef( + ({ viewableItems }: { viewableItems: Array }) => { + const currentAgentId = agentIdRef.current; + const currentViewMode = viewModeRef.current; + const currentRequestFilePreview = requestFilePreviewRef.current; + if (!currentAgentId || currentViewMode !== "grid" || !currentRequestFilePreview) { + return; + } + + viewableItems.forEach((token) => { + const item = token.item as ExplorerEntry | undefined; + if (!item) { + return; + } + + if (getEntryDisplayKind(item) !== "image") { + return; + } + + const hasPreview = explorerFilesRef.current?.get(item.path); + if (hasPreview || pendingThumbnailPathsRef.current.has(item.path)) { + return; + } + + pendingThumbnailPathsRef.current.add(item.path); + setThumbnailLoadingMap((prev) => ({ ...prev, [item.path]: true })); + currentRequestFilePreview(currentAgentId, item.path); + }); + } + ); const restoreQueuedScrollOffset = useCallback(() => { if (pendingScrollRestoreRef.current === null) { @@ -236,10 +297,6 @@ function FileExplorerContent({ queueScrollRestore(savedOffset); }, [activePath, queueScrollRestore]); - useEffect(() => { - setCopiedPath(null); - }, [activePath]); - useEffect(() => { if (!agentId || !initialTargetDirectory || !requestDirectoryListing) { pendingPathParamRef.current = null; @@ -303,24 +360,103 @@ function FileExplorerContent({ const handleCopyPath = useCallback(async (path: string) => { await Clipboard.setStringAsync(path); - setCopiedPath(path); - if (copyTimeoutRef.current) { - clearTimeout(copyTimeoutRef.current); - } - copyTimeoutRef.current = setTimeout(() => { - setCopiedPath(null); - copyTimeoutRef.current = null; - }, 1500); }, []); - useEffect(() => { - return () => { - if (copyTimeoutRef.current) { - clearTimeout(copyTimeoutRef.current); - } - }; + const handleOpenMenu = useCallback((entry: ExplorerEntry, event: any) => { + event.stopPropagation(); + const { pageX, pageY } = event.nativeEvent ?? {}; + setMenuAnchor({ + left: typeof pageX === "number" ? pageX : 0, + top: typeof pageY === "number" ? pageY : 0, + }); + setMenuEntry(entry); }, []); + const handleCloseMenu = useCallback(() => { + setMenuEntry(null); + setMenuHeight(0); + }, []); + + const handleMenuLayout = useCallback((event: LayoutChangeEvent) => { + const { height } = event.nativeEvent.layout; + setMenuHeight((current) => (current === height ? current : height)); + }, []); + + const handleDownloadEntry = useCallback( + async (entry: ExplorerEntry) => { + if (!agentId || !requestFileDownloadToken || entry.kind !== "file") { + return; + } + + try { + const tokenResponse = await requestFileDownloadToken(agentId, entry.path); + if (tokenResponse.error || !tokenResponse.token) { + throw new Error(tokenResponse.error ?? "Failed to request download token."); + } + + const downloadTarget = resolveDaemonDownloadTarget(daemonProfile); + if (!downloadTarget.baseUrl) { + throw new Error("Download host is unavailable."); + } + + const fileName = tokenResponse.fileName ?? entry.name; + const downloadUrl = buildDownloadUrl( + downloadTarget.baseUrl, + tokenResponse.token, + Platform.OS === "web" ? downloadTarget.authCredentials : null + ); + + if (Platform.OS === "web") { + triggerBrowserDownload(downloadUrl, fileName); + return; + } + + const targetUri = await resolveDownloadTargetUri(fileName); + const downloadResult = await FileSystem.downloadAsync( + downloadUrl, + targetUri, + downloadTarget.authHeader + ? { headers: { Authorization: downloadTarget.authHeader } } + : undefined + ); + if (await Sharing.isAvailableAsync()) { + await Sharing.shareAsync(downloadResult.uri, { + mimeType: tokenResponse.mimeType ?? undefined, + dialogTitle: fileName ? `Share ${fileName}` : "Share file", + }); + } else { + Alert.alert("Download complete", `Saved to ${downloadResult.uri}`); + } + } catch (error) { + const message = + error instanceof Error ? error.message : "Failed to download file."; + if (Platform.OS === "web") { + console.warn("[FileExplorer] Download failed:", message); + return; + } + Alert.alert("Download failed", message); + } + }, + [agentId, daemonProfile, requestFileDownloadToken] + ); + + const menuPosition = useMemo(() => { + if (!menuEntry) { + return null; + } + const menuWidth = 180; + const horizontalPadding = theme.spacing[2]; + const verticalPadding = theme.spacing[2]; + const maxLeft = Math.max(horizontalPadding, windowWidth - menuWidth - horizontalPadding); + const maxTop = Math.max(verticalPadding, windowHeight - menuHeight - verticalPadding); + const left = Math.min( + Math.max(menuAnchor.left - menuWidth + horizontalPadding, horizontalPadding), + maxLeft + ); + const top = Math.min(Math.max(menuAnchor.top + verticalPadding, verticalPadding), maxTop); + return { top, left, width: menuWidth }; + }, [menuEntry, menuAnchor.left, menuAnchor.top, menuHeight, theme.spacing, windowHeight, windowWidth]); + const handleListScroll = useCallback( (event: NativeSyntheticEvent) => { const offset = event.nativeEvent.contentOffset.y; @@ -342,6 +478,13 @@ function FileExplorerContent({ router.back(); }, [agentId, serverId]); + const handleRetryDirectory = useCallback(() => { + if (!agentId || !requestDirectoryListing) { + return; + } + requestDirectoryListing(agentId, activePath); + }, [agentId, requestDirectoryListing, activePath]); + const handleBackNavigation = useCallback(() => { if (!agentId) { router.back(); @@ -429,27 +572,19 @@ function FileExplorerContent({ { - event.stopPropagation(); - handleCopyPath(item.path); - }} + onPress={(event) => handleOpenMenu(item, event)} hitSlop={8} - style={styles.copyButton} + style={styles.menuButton} > - {copiedPath === item.path ? ( - - ) : ( - - )} + ); }, [ - copiedPath, explorerState?.files, - handleCopyPath, handleEntryPress, + handleOpenMenu, theme.colors, thumbnailLoadingMap, viewMode, @@ -474,35 +609,6 @@ function FileExplorerContent({ ); }, [activePath, showListLoadingBanner, viewMode]); - const handleViewableItemsChanged = useCallback( - ({ viewableItems }: { viewableItems: Array }) => { - if (!agentId || viewMode !== "grid" || !requestFilePreview) { - return; - } - - viewableItems.forEach((token) => { - const item = token.item as ExplorerEntry | undefined; - if (!item) { - return; - } - - if (getEntryDisplayKind(item) !== "image") { - return; - } - - const hasPreview = explorerState?.files.get(item.path); - if (hasPreview || pendingThumbnailPathsRef.current.has(item.path)) { - return; - } - - pendingThumbnailPathsRef.current.add(item.path); - setThumbnailLoadingMap((prev) => ({ ...prev, [item.path]: true })); - requestFilePreview(agentId, item.path); - }); - }, - [agentId, explorerState?.files, requestFilePreview, viewMode] - ); - useEffect(() => { if (!explorerState) { return; @@ -607,6 +713,9 @@ function FileExplorerContent({ {error ? ( {error} + + Retry + ) : showInitialListLoading ? ( @@ -638,17 +747,63 @@ function FileExplorerContent({ onLayout={restoreQueuedScrollOffset} onContentSizeChange={restoreQueuedScrollOffset} ListHeaderComponent={listHeaderComponent} - extraData={{ copiedPath, viewMode, thumbnailLoadingMap }} + extraData={{ viewMode, thumbnailLoadingMap }} initialNumToRender={20} maxToRenderPerBatch={30} windowSize={10} - onViewableItemsChanged={handleViewableItemsChanged} + onViewableItemsChanged={handleViewableItemsChangedRef.current} viewabilityConfig={viewabilityConfigRef.current} /> )} )} + + + + + {menuEntry && menuPosition ? ( + + { + handleCopyPath(menuEntry.path); + handleCloseMenu(); + }} + > + Copy Path + + {menuEntry.kind === "file" ? ( + { + handleCloseMenu(); + await handleDownloadEntry(menuEntry); + }} + > + Download + + ) : null} + + ) : null} + + ); } @@ -898,6 +1053,120 @@ function getExtension(name: string): string | null { return name.slice(index + 1).toLowerCase(); } +type DownloadTarget = { + baseUrl: string | null; + authHeader: string | null; + authCredentials: { username: string; password: string } | null; +}; + +function resolveDaemonDownloadTarget(daemon?: DaemonProfile): DownloadTarget { + const rawUrl = daemon?.restUrl ?? daemon?.wsUrl; + if (!rawUrl) { + return { baseUrl: null, authHeader: null, authCredentials: null }; + } + + let parsed: URL; + try { + parsed = new URL(rawUrl); + } catch { + return { baseUrl: null, authHeader: null, authCredentials: null }; + } + + if (parsed.protocol === "ws:") { + parsed.protocol = "http:"; + } else if (parsed.protocol === "wss:") { + parsed.protocol = "https:"; + } + + let authCredentials: { username: string; password: string } | null = null; + if (parsed.username || parsed.password) { + authCredentials = { + username: decodeURIComponent(parsed.username), + password: decodeURIComponent(parsed.password), + }; + parsed.username = ""; + parsed.password = ""; + } + + parsed.pathname = parsed.pathname.replace(/\/ws\/?$/, "/"); + + const baseUrl = parsed.origin; + const authHeader = authCredentials + ? `Basic ${btoa(`${authCredentials.username}:${authCredentials.password}`)}` + : null; + + return { baseUrl, authHeader, authCredentials }; +} + +function buildDownloadUrl( + baseUrl: string, + token: string, + authCredentials: { username: string; password: string } | null +): string { + const url = new URL("/api/files/download", baseUrl); + url.searchParams.set("token", token); + if (authCredentials) { + url.username = authCredentials.username; + url.password = authCredentials.password; + } + return url.toString(); +} + +function triggerBrowserDownload(url: string, fileName: string) { + if (typeof document === "undefined") { + if (typeof window !== "undefined") { + window.open(url, "_blank", "noopener"); + } + return; + } + + const link = document.createElement("a"); + link.href = url; + link.download = fileName; + link.rel = "noopener"; + document.body.appendChild(link); + link.click(); + link.remove(); +} + +async function resolveDownloadTargetUri(fileName: string): Promise { + const directory = FileSystem.Paths.cache?.uri ?? FileSystem.Paths.document?.uri; + if (!directory) { + throw new Error("No download directory available."); + } + + const safeName = sanitizeDownloadFileName(fileName); + const split = splitFileName(safeName); + let targetUri = `${directory}${safeName}`; + let suffix = 1; + + while ((await FileSystem.getInfoAsync(targetUri)).exists) { + targetUri = `${directory}${split.base} (${suffix})${split.ext}`; + suffix += 1; + } + + return targetUri; +} + +function sanitizeDownloadFileName(fileName: string): string { + const trimmed = fileName.trim(); + if (!trimmed) { + return "download"; + } + return trimmed.replace(/[\\/:*?"<>|]+/g, "_"); +} + +function splitFileName(fileName: string): { base: string; ext: string } { + const lastDot = fileName.lastIndexOf("."); + if (lastDot <= 0) { + return { base: fileName, ext: "" }; + } + return { + base: fileName.slice(0, lastDot), + ext: fileName.slice(lastDot), + }; +} + const styles = StyleSheet.create((theme) => ({ container: { flex: 1, @@ -962,6 +1231,18 @@ const styles = StyleSheet.create((theme) => ({ fontSize: theme.fontSize.base, textAlign: "center", }, + retryButton: { + borderRadius: theme.borderRadius.full, + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.primary, + paddingHorizontal: theme.spacing[3], + paddingVertical: theme.spacing[1], + }, + retryButtonText: { + color: theme.colors.primary, + fontSize: theme.fontSize.sm, + fontWeight: theme.fontWeight.semibold, + }, statusText: { color: theme.colors.mutedForeground, fontSize: theme.fontSize.sm, @@ -1037,13 +1318,37 @@ const styles = StyleSheet.create((theme) => ({ fontSize: theme.fontSize.xs, marginTop: theme.spacing[1], }, - copyButton: { + menuButton: { width: 36, height: 36, borderRadius: theme.borderRadius.full, alignItems: "center", justifyContent: "center", }, + menuOverlay: { + flex: 1, + }, + menuBackdrop: { + ...StyleSheet.absoluteFillObject, + backgroundColor: "rgba(0, 0, 0, 0.2)", + }, + entryMenu: { + borderRadius: theme.borderRadius.md, + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.border, + backgroundColor: theme.colors.card, + padding: theme.spacing[1], + }, + entryMenuItem: { + paddingVertical: theme.spacing[2], + paddingHorizontal: theme.spacing[3], + borderRadius: theme.borderRadius.md, + }, + entryMenuText: { + color: theme.colors.foreground, + fontSize: theme.fontSize.sm, + fontWeight: theme.fontWeight.semibold, + }, viewToggleContainer: { flexDirection: "row", borderRadius: theme.borderRadius.full, diff --git a/packages/app/src/contexts/session-context.tsx b/packages/app/src/contexts/session-context.tsx index d678b161d..b9368de95 100644 --- a/packages/app/src/contexts/session-context.tsx +++ b/packages/app/src/contexts/session-context.tsx @@ -60,6 +60,11 @@ type FileExplorerResponseMessage = Extract< { type: "file_explorer_response" } >; +type FileDownloadTokenResponseMessage = Extract< + SessionOutboundMessage, + { type: "file_download_token_response" } +>; + type StatusMessage = Extract; const SESSION_SNAPSHOT_STORAGE_PREFIX = "@paseo:session-snapshot:"; @@ -173,6 +178,7 @@ export interface SessionContextValue { requestGitDiff: (agentId: string) => void; requestDirectoryListing: (agentId: string, path: string, options?: { recordHistory?: boolean }) => void; requestFilePreview: (agentId: string, path: string) => void; + requestFileDownloadToken: (agentId: string, path: string) => Promise; navigateExplorerBack: (agentId: string) => string | null; requestProviderModels: (provider: any, options?: { cwd?: string }) => void; restartServer: (reason?: string) => void; @@ -503,6 +509,40 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid keepPreviousData: false, }); + const fileDownloadTokenRequest = useDaemonRequest< + { agentId: string; path: string }, + FileDownloadTokenResponseMessage["payload"], + FileDownloadTokenResponseMessage + >({ + ws, + responseType: "file_download_token_response", + buildRequest: ({ params, requestId }) => ({ + type: "session", + message: { + type: "file_download_token_request", + agentId: params?.agentId ?? "", + path: params?.path ?? "", + requestId, + }, + }), + matchResponse: (message, context) => { + if (message.payload.requestId) { + return message.payload.requestId === context.requestId; + } + return ( + message.payload.agentId === context.params?.agentId && + message.payload.path === context.params?.path + ); + }, + getRequestKey: (params) => + params ? `${params.agentId}:download:${params.path}` : "default", + selectData: (message) => message.payload, + extractError: (message) => + message.payload.error ? new Error(message.payload.error) : null, + timeoutMs: 10000, + keepPreviousData: false, + }); + const initializeAgentRpc = useSessionRpc({ ws, requestType: "initialize_agent_request", @@ -1635,6 +1675,13 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid }); }, [filePreviewRequest, updateExplorerState]); + const requestFileDownloadToken = useCallback( + (agentId: string, path: string) => { + return fileDownloadTokenRequest.execute({ agentId, path }); + }, + [fileDownloadTokenRequest] + ); + const navigateExplorerBack = useCallback((agentId: string) => { let targetPath: string | null = null; @@ -1721,6 +1768,7 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid requestGitDiff, requestDirectoryListing, requestFilePreview, + requestFileDownloadToken, navigateExplorerBack, requestProviderModels, restartServer, @@ -1744,6 +1792,7 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid requestGitDiff, requestDirectoryListing, requestFilePreview, + requestFileDownloadToken, navigateExplorerBack, requestProviderModels, restartServer, @@ -1769,6 +1818,7 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid requestGitDiff, requestDirectoryListing, requestFilePreview, + requestFileDownloadToken, navigateExplorerBack, requestProviderModels, restartServer, @@ -1788,6 +1838,7 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid requestGitDiff, requestDirectoryListing, requestFilePreview, + requestFileDownloadToken, navigateExplorerBack, requestProviderModels, restartServer, diff --git a/packages/app/src/stores/session-store.ts b/packages/app/src/stores/session-store.ts index 964ac6a35..bdc67a589 100644 --- a/packages/app/src/stores/session-store.ts +++ b/packages/app/src/stores/session-store.ts @@ -18,7 +18,7 @@ import type { AgentUsage, AgentPersistenceHandle, } from "@server/server/agent/agent-sdk-types"; -import type { GitSetupOptions } from "@server/server/messages"; +import type { FileDownloadTokenResponse, GitSetupOptions } from "@server/server/messages"; import { isPerfLoggingEnabled, measurePayload, perfLog } from "@/utils/perf"; // Re-export types that were in session-context @@ -177,6 +177,7 @@ export interface SessionState { requestGitDiff: (agentId: string) => void; requestDirectoryListing: (agentId: string, path: string, options?: { recordHistory?: boolean }) => void; requestFilePreview: (agentId: string, path: string) => void; + requestFileDownloadToken: (agentId: string, path: string) => Promise; navigateExplorerBack: (agentId: string) => string | null; requestProviderModels: (provider: any, options?: { cwd?: string }) => void; restartServer: (reason?: string) => void; diff --git a/packages/server/src/server/bootstrap.ts b/packages/server/src/server/bootstrap.ts index 951e7dc65..5595dda5d 100644 --- a/packages/server/src/server/bootstrap.ts +++ b/packages/server/src/server/bootstrap.ts @@ -89,6 +89,9 @@ export async function createPaseoDaemon( realm: authRealm, }); app.use((req, res, next) => { + if (req.path === "/api/files/download") { + return next(); + } if (agentMcpBearerToken && req.path.startsWith(agentMcpRoute)) { const authHeader = req.header("authorization") ?? ""; if (authHeader.startsWith("Bearer ")) { diff --git a/packages/server/src/server/file-explorer/service.ts b/packages/server/src/server/file-explorer/service.ts index dfbd26b1c..9d067fec9 100644 --- a/packages/server/src/server/file-explorer/service.ts +++ b/packages/server/src/server/file-explorer/service.ts @@ -56,6 +56,12 @@ const TEXT_EXTENSIONS = new Set([ ".sh", ]); +const TEXT_MIME_TYPES: Record = { + ".json": "application/json", +}; + +const DEFAULT_TEXT_MIME_TYPE = "text/plain"; + const IMAGE_MIME_TYPES: Record = { ".png": "image/png", ".jpg": "image/jpeg", @@ -140,7 +146,7 @@ export async function readExplorerFile({ kind: "text", encoding: "utf-8", content, - mimeType: "text/plain", + mimeType: TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE, }; } @@ -182,7 +188,7 @@ export async function getDownloadableFileInfo({ const ext = path.extname(filePath).toLowerCase(); const mimeType = TEXT_EXTENSIONS.has(ext) - ? "text/plain" + ? TEXT_MIME_TYPES[ext] ?? DEFAULT_TEXT_MIME_TYPE : ext in IMAGE_MIME_TYPES ? IMAGE_MIME_TYPES[ext] : "application/octet-stream"; diff --git a/packages/server/src/server/messages.ts b/packages/server/src/server/messages.ts index 9b99e1280..7fc66cc7d 100644 --- a/packages/server/src/server/messages.ts +++ b/packages/server/src/server/messages.ts @@ -487,6 +487,7 @@ export const FileDownloadTokenRequestSchema = z.object({ type: z.literal("file_download_token_request"), agentId: z.string(), path: z.string(), + requestId: z.string().optional(), }); export const ClearAgentAttentionMessageSchema = z.object({ @@ -761,6 +762,7 @@ export const FileDownloadTokenResponseSchema = z.object({ mimeType: z.string().nullable(), size: z.number().nullable(), error: z.string().nullable(), + requestId: z.string().optional(), }), }); diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 1dfc3e669..fc169063a 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -2098,7 +2098,7 @@ export class Session { private async handleFileDownloadTokenRequest( request: FileDownloadTokenRequest ): Promise { - const { agentId, path: requestedPath } = request; + const { agentId, path: requestedPath, requestId } = request; console.log( `[Session ${this.clientId}] Handling file download token request for agent ${agentId} (${requestedPath})` @@ -2119,6 +2119,7 @@ export class Session { mimeType: null, size: null, error: `Agent not found: ${agentId}`, + ...(requestId ? { requestId } : {}), }, }); return; @@ -2148,6 +2149,7 @@ export class Session { mimeType: entry.mimeType, size: entry.size, error: null, + ...(requestId ? { requestId } : {}), }, }); } catch (error: any) { @@ -2165,6 +2167,7 @@ export class Session { mimeType: null, size: null, error: error.message, + ...(requestId ? { requestId } : {}), }, }); }