From ee824a7057c50e09471764b8422a884fc0841082 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:30:38 +0000 Subject: [PATCH 1/7] feat: add file browser and download capability to UI - Added backend endpoints in `api_server.py` for listing, reading, and downloading files. - Added `ArtifactsBrowser` component in frontend to navigate and view local files. - Integrated "Files" button in `LabNotebook` to access the file browser. - Verified backend with Python tests and frontend with Playwright. --- __pycache__/api_server.cpython-312.pyc | Bin 0 -> 36911 bytes __pycache__/insights.cpython-312.pyc | Bin 0 -> 6745 bytes api_server.py | 130 +++++++++- backend.log | 15 ++ frontend.log | 11 + frontend/package-lock.json | 18 +- .../components/Artifacts/ArtifactsBrowser.tsx | 229 ++++++++++++++++++ frontend/src/components/LabNotebook.tsx | 43 +++- frontend/src/lib/api.ts | 37 +++ 9 files changed, 471 insertions(+), 12 deletions(-) create mode 100644 __pycache__/api_server.cpython-312.pyc create mode 100644 __pycache__/insights.cpython-312.pyc create mode 100644 backend.log create mode 100644 frontend.log create mode 100644 frontend/src/components/Artifacts/ArtifactsBrowser.tsx diff --git a/__pycache__/api_server.cpython-312.pyc b/__pycache__/api_server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99e1add305b790fa04271b9556257a4a87cc39f8 GIT binary patch literal 36911 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 literal 0 HcmV?d00001 diff --git a/__pycache__/insights.cpython-312.pyc b/__pycache__/insights.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5e73844bf6f42ec896f6797ca140c1e2099e1d1 GIT binary patch literal 6745 zcma)AYit`=cD}>m@J&jhsP}6mSvDz4l%I*LhZ8%pEH#dl+DUdTSD{Co5j8Y9=rIiplScqD0TrE*&n3nK+LQb4Y28t{9|Jk*v3E7 zbB7#KR=i2a7Vo*QbMHOp+;h)&K5{y32tJAK75`9=&>u-f|L7t?J^g1O*3sLDM>B}0 zcteDW8fFX>q3H-cLu;jR#;BFd3^ZC@rz` zqQt{MTRf5gjuIP-D`HIIB5ZJhlR-#v0X73?g2PFQ080m9kPL(%`)!{SmG~&t&Ph^C z;mAxpE8pfj+AoW@_EIeSHeb}s+3`Kh1Mya(v2#OQV81>Yq`gu_xzAj z?0Re1p;MJIg&xrQSZ?2bt{|T=`N}0ODH274Nhtz|9D}GMg08RP`B<+k^e3pI>!doSwdT=|bS#o0EapFI-JFJzryW5!0eJb%D8e>;5%=rt^5tePYG@Nmc!>yFJq} zlyeVf9K*ZrhV{O+zRkra?tvBauHC&n^%OXuVCiBL8WAmN`t5?&breErDy2D^W;BLo z69}aZDMNVw3~d^?fwC5EQJ6R+W0=^=f$x5)o~Hi{7e?1*iHj-B9AE*}G8dy#wyKm- zpUInVj3FdjQbyjuo0}1DNy418F-3*T_Eq7@TT}E;5N}Hve}aBi=0Tx5i$=uT!)4p6 z%t)237HrIsJTo4Xqa5*50;_q7d10~WD)f6Ej78&I5Jm{MlmxliZ`A& zR3o{D@C!|AMg;WF&q;mAETPVZ-V<{@EHLl1V^%&2K~2=6=S~Ivu;_lY}Dtf z+3o7y&GDV8pq!tpI$M4W_PVD{%B<5 z(6+rjU*EPNY$&Tz=E#|B{h5`Ed5iM{$8E>z_clBmrj6j{k!!+rz7goV_h$Y?I;1M8UEPf(p5Vnkl~nk_1;(Hrk2;HDmq;GsWcnH1PCK4OhUR z)3j1H#)=7AQOX#G6%KwcG&bg&of&UZv>1T!3cC(+gf^n_17FJUz^t2oh~xIwjx!|BnS16^B)=`*v=Hog4I>%hf4!${Oy{c~{CL z_}{eo4*T59n#xQ}-SO}53m>Y?)hXWn-1u>@46@n_MurdTb1G%nSD|%^ujM@t>U52k z_9z>H?>9PEqPu>bN7%1Zl`>Vfn6!;=PuXr#xI;Ok_or;(QJt=oe7kOC3VC?6Pv04~ zrpzU)XiEV{EiUW;zDOqZ4q^*GXxzU-u}v8&F+ycd3~wMa2#0Umhuoo@)aR2`$twCD zc~*M_TzWg)Viul)Ch`=Jwj%#4s2eH7j zA#Hl!v`8(YTl7`5NFCs4q|jBgO5J(m&-8abx8l(Q%R!Z|e}2WQr4{cy{g##gA#LAx zmg6tr+yI>IDKp5Xu^H`?W!XCTMoo$azDc5Z7m?k4W04O$@P@R5Z%&y($8Yj2T6qcV z-5fssf|Z(ey+HrVFX(U4`}tO~N;673ztx7F`kb^wsyZNbnn+zT>M~C`j{Yb5IYIbK@sOgfEuxREz-b=xqhvm z^%8CXY(fNX5Ln~$mpPQ){UMfs05X*aNPyWzQCVOiWQhm_*<8O)0+8$1HDGi7dMN^d zI)t)-y2wtR`=$wWBGJxiKsPYI1e{=paB#pdvcQsPx^$$XZ1$b%y{;nQTk0AjSg()m z(vS|+$~c7VayG54oNW0HCjw9kY1puM;#fk7Clm;brhEp~T;irO8XN8uJbzbX2{8j+c>YNuk)!zAgxH zg5>Z*3_zN9Wc2unW9(u~=CQAjjmyG3XlgDIi3ker1Ar^fb3uW`eE>uNhe(O&oFGFF zfs#QEb_qw)L@GfL2rz^IIe@A2>=K-W06r{evjiESDMV!jmh3gO4pr{g|5vj$i{)sECoDN3wqQwIZNh0K`1RzSpIQ zN<=8z0NDdny2$!nsV-Q)3oIkT%?S|;qJR^50BG6c~805=HOH65yUXn^RP1OFhXHBs(X zplG}SC3XX{0Wgcvk_M`!bnUQe0%~3iCB3DjLQ&F?9%1J>F#;%6^B!7iqz4qygJlwa zX&aPD=%?C6iAXWPa|)*#$#0TLDD(Y;s!0q^YeO#(rnIC*5e$P@ArV0mCdMU?Wj zHIf&~+X!fvaEJ}#n8flz5E2|;+u3LjW?=D*ZW$ z$&MoWhltw@S;#RE}ZUqG`NurvFHzklt6Ow8M?9C~PtlHov z@*)USz#43l`^avHWymBBkx!9@O+}HY=0#4H;4T0N13>}uig7$v8Ay30LNZH68i_km z8CffABBUlDrzHge3KIWU1a~x;lFy}D^rt}X*48KiGZN#0y=Yk))dGeLw-pO2B|rtl zt{vA}-n42YcB`6TTp|J~A+oOk98tAkE^-rA;8j<7a9}PHo5KWxJm>pM0I~6aH3W3e zDE}4AAacMXpfyNNv?5o_#>+Wp_wx9s_L>hyZjanKn{OF>bShglk+Dn^Ocv|Q1sX98 zdyUA|^rPA3@q)32aph~9)<@SycWRGkYmevM-p{J*)}3q4d{f763;Xk-yF)uI1KE~= zhwo%s268PUndZ?whOTiJXr|t^=RlsOomzjk)}QY_op0^PcOBj{8$GT<4f33Liku$X ziluM}RlAob3WjQCn)6%2G!XTJ5& zJ>!mdFzX%M@s4J_qdVTQtat3ucQS9!X1rrL@4MO7ckfOV?5OP&fM8GS`q{O!x!P_x zlC7S(LhaVntv9bVZ%pKBdR9z@W5`moVal4@e;IyQ|1Zt|)coOO?)doc=qHCS!Aje$ zqZ#k$ifLsjYiY`xU8{$)=EjY8HZR@{WZDLItC}_rZ#LW=cyQ`T)vLf@b*ZJ~?-P@g``PQz@Ga!z8{5_m$J^4_`whot801H@t z<4=2w6mrykfeegm*Ww}=KU4S0qsFb{Pb_Z$=zmsVpy%0M7h-*1AgZZTZDk8Il+X4| z+B^z0Mkcy$yD|;0KJspz+p$b#EmO~66m&fU{S)>cB8(#cyyHsV;UWK>b*ua;UU~Nc zeQ)Bv^Fw;uJA60s$eneL?O7SCt>8kQ#uZE6?ppQUeq{r1F5LZozO83-;aAaLL?6DE zJ3RQ|;J=^!*RxyQx#6kIUtZ1*U(U2$S(yUOIqP;?I*4U%O1YNPYnDIl*0#d-opqm7 zdthVEx-b75w(t3=ZFBI!TTdEKGVN`lIH$*N+)K8lVV$ z%=-EW^U)atp@%6#k8}e4yKV+1K->jxgao}w2l!u3EG3P7M$JKe=^^pvwJ~sG-AP*hjW5GZ`wFLt63G$K#0y20P zBu|hZcznU#mjYnb1c3y2UBJlDMY1(ANzKS4O{kheu^423i+NW^vEtXZUwkvH;OH+E zUB4v|An^q(be>Gt(z#WEz`is)bdh(c_$(2x_iLo3po$qZ%LqJAiHLAk7NLv0au^a5 z2n>OuzA%_5dT?N|3Zy_K<%Gen^ziktj$>~IHjER(6R}v yr2ELSIb-d}p~Fv2R;u=Cql0QMwBMj8civS~pb7D*v$Mbu0*0GBduBpvJNiF AgentSummaryResponse: return AgentSummaryResponse(**result) +@app.get("/api/files/list", summary="List files in a directory") +def list_files(path: str = ".") -> List[Dict[str, Any]]: + """ + List files and directories in the given path relative to the project root. + """ + # Security check: ensure path resolves to inside BASE_DIR + # We strip any leading slashes/dots to prevent traversing up from root initially + clean_path = path.strip("/") + if clean_path == "." or clean_path == "": + target_path = BASE_DIR + else: + target_path = (BASE_DIR / clean_path).resolve() + + if not str(target_path).startswith(str(BASE_DIR)): + raise HTTPException(status_code=403, detail="Access denied: Cannot traverse outside project root.") + + if not target_path.exists(): + raise HTTPException(status_code=404, detail="Path not found.") + + if not target_path.is_dir(): + raise HTTPException(status_code=400, detail="Path is not a directory.") + + results = [] + try: + for item in target_path.iterdir(): + # Skip hidden files/dirs and .git, and __pycache__ + if item.name.startswith(".") or item.name == "__pycache__": + continue + + stats = item.stat() + results.append({ + "name": item.name, + "path": str(item.relative_to(BASE_DIR)).replace("\\", "/"), + "type": "directory" if item.is_dir() else "file", + "size": stats.st_size, + "modified": datetime.fromtimestamp(stats.st_mtime, timezone.utc).isoformat(), + }) + except PermissionError: + raise HTTPException(status_code=403, detail="Permission denied.") + + # Sort: directories first, then files + results.sort(key=lambda x: (x["type"] != "directory", x["name"].lower())) + return results + + +@app.get("/api/files/read", summary="Read a file content") +def read_file(path: str) -> Dict[str, str]: + """ + Read the content of a text file. + """ + clean_path = path.strip("/") + target_path = (BASE_DIR / clean_path).resolve() + + if not str(target_path).startswith(str(BASE_DIR)): + raise HTTPException(status_code=403, detail="Access denied.") + + if not target_path.exists() or not target_path.is_file(): + raise HTTPException(status_code=404, detail="File not found.") + + try: + content = target_path.read_text(encoding="utf-8") + return {"content": content} + except UnicodeDecodeError: + raise HTTPException(status_code=400, detail="Binary file not supported for text view.") + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/api/files/download", summary="Download a file") +def download_file(path: str): + """ + Download a single file. + """ + clean_path = path.strip("/") + target_path = (BASE_DIR / clean_path).resolve() + + if not str(target_path).startswith(str(BASE_DIR)): + raise HTTPException(status_code=403, detail="Access denied.") + + if not target_path.exists() or not target_path.is_file(): + raise HTTPException(status_code=404, detail="File not found.") + + return FileResponse(target_path, filename=target_path.name) + + +@app.get("/api/files/download_zip", summary="Download directory as zip") +def download_zip(path: str = "."): + """ + Download a directory as a zip file. + """ + clean_path = path.strip("/") + if clean_path == "." or clean_path == "": + target_path = BASE_DIR + else: + target_path = (BASE_DIR / clean_path).resolve() + + if not str(target_path).startswith(str(BASE_DIR)): + raise HTTPException(status_code=403, detail="Access denied.") + + if not target_path.exists() or not target_path.is_dir(): + raise HTTPException(status_code=404, detail="Directory not found.") + + # Create a zip file in memory + import io + import zipfile + + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file: + for root, dirs, files in os.walk(target_path): + # exclude hidden folders + dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__" and d != "node_modules"] + + for file in files: + if file.startswith("."): + continue + file_path = Path(root) / file + arcname = file_path.relative_to(target_path) + zip_file.write(file_path, arcname) + + zip_buffer.seek(0) + filename = target_path.name if (path != "." and path != "") else "project" + return StreamingResponse( + zip_buffer, + media_type="application/zip", + headers={"Content-Disposition": f"attachment; filename={filename}.zip"} + ) + + # --------------------------------------------------------------------------- # Static file serving for production (Railway, etc.) # --------------------------------------------------------------------------- diff --git a/backend.log b/backend.log new file mode 100644 index 0000000..afb100b --- /dev/null +++ b/backend.log @@ -0,0 +1,15 @@ +INFO: Will watch for changes in these directories: ['/app'] +INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) +INFO: Started reloader process [7049] using StatReload +INFO: Started server process [7196] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: 127.0.0.1:35952 - "GET /api/health HTTP/1.1" 200 OK +INFO: 127.0.0.1:35954 - "GET /api/credentials/status HTTP/1.1" 200 OK +INFO: 127.0.0.1:35964 - "GET /api/credentials/status HTTP/1.1" 200 OK +INFO: 127.0.0.1:35964 - "GET /api/files/list?path=. HTTP/1.1" 200 OK +INFO: Shutting down +INFO: Waiting for application shutdown. +INFO: Application shutdown complete. +INFO: Finished server process [7196] +INFO: Stopping reloader process [7049] diff --git a/frontend.log b/frontend.log new file mode 100644 index 0000000..ee1435a --- /dev/null +++ b/frontend.log @@ -0,0 +1,11 @@ + +> frontend@0.0.0 dev +> vite + +Port 5173 is in use, trying another one... +Port 5174 is in use, trying another one... + + VITE v7.2.4 ready in 262 ms + + ➜ Local: http://localhost:5175/ + ➜ Network: use --host to expose diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8ecd8e7..cbea246 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -82,6 +82,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1777,6 +1778,7 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1786,6 +1788,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1852,6 +1855,7 @@ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", @@ -2110,6 +2114,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2276,6 +2281,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -2646,6 +2652,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4815,6 +4822,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4901,6 +4909,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4910,6 +4919,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5239,7 +5249,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -5296,6 +5307,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5374,6 +5386,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5582,6 +5595,7 @@ "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -5675,6 +5689,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5734,6 +5749,7 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/src/components/Artifacts/ArtifactsBrowser.tsx b/frontend/src/components/Artifacts/ArtifactsBrowser.tsx new file mode 100644 index 0000000..977cc02 --- /dev/null +++ b/frontend/src/components/Artifacts/ArtifactsBrowser.tsx @@ -0,0 +1,229 @@ +import { useState, useEffect } from "react"; +import { X, Loader2, FileText, Folder, Image, Download, ChevronRight, Home, ArrowUp } from "lucide-react"; +import { FileItem, listFiles, readFile, getDownloadUrl, getZipDownloadUrl } from "@/lib/api"; +import { cn } from "@/lib/utils"; + +interface ArtifactsBrowserProps { + isOpen: boolean; + onClose: () => void; +} + +export function ArtifactsBrowser({ isOpen, onClose }: ArtifactsBrowserProps) { + const [currentPath, setCurrentPath] = useState("."); + const [files, setFiles] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedFile, setSelectedFile] = useState(null); + const [fileContent, setFileContent] = useState(null); + const [loadingContent, setLoadingContent] = useState(false); + + useEffect(() => { + if (isOpen) { + loadFiles(currentPath); + } + }, [isOpen, currentPath]); + + const loadFiles = async (path: string) => { + setLoading(true); + setError(null); + try { + const data = await listFiles(path); + setFiles(data); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load files"); + } finally { + setLoading(false); + } + }; + + const handleNavigate = (path: string) => { + setCurrentPath(path); + setSelectedFile(null); + setFileContent(null); + }; + + const handleUp = () => { + if (currentPath === "." || currentPath === "") return; + const parts = currentPath.split("/"); + parts.pop(); + const newPath = parts.length === 0 || (parts.length === 1 && parts[0] === "") ? "." : parts.join("/"); + handleNavigate(newPath); + }; + + const handleFileClick = async (file: FileItem) => { + if (file.type === "directory") { + handleNavigate(file.path); + } else { + setSelectedFile(file); + setLoadingContent(true); + setFileContent(null); + + // Check if text or image + const isImage = /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file.name); + + if (!isImage) { + try { + const { content } = await readFile(file.path); + setFileContent(content); + } catch (err) { + // Binary file or error + setFileContent(null); + } + } + setLoadingContent(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+ + {/* Header */} +
+
+

Project Artifacts

+
+
+ + {currentPath !== "." && ( + <> + + {currentPath} + + )} +
+
+ +
+ + {/* Content */} +
+ + {/* File List */} +
+ {/* Toolbar */} + + + {loading ? ( +
+ +
+ ) : error ? ( +
+ {error} +
+ ) : files.length === 0 ? ( +
+ Empty directory +
+ ) : ( +
+ {files.map((file) => ( + + ))} +
+ )} +
+ + {/* Preview Pane */} + {selectedFile && ( +
+
+
+ {/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(selectedFile.name) ? ( + + ) : ( + + )} + {selectedFile.name} +
+ + + Download + +
+ +
+ {loadingContent ? ( +
+ +
+ ) : /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(selectedFile.name) ? ( +
+ {selectedFile.name} +
+ ) : fileContent !== null ? ( +
+                                {fileContent}
+                            
+ ) : ( +
+

Preview not available for this file type.

+
+ )} +
+
+ )} +
+
+
+ ); +} + +function formatBytes(bytes: number, decimals = 2) { + if (!+bytes) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; +} diff --git a/frontend/src/components/LabNotebook.tsx b/frontend/src/components/LabNotebook.tsx index 64c0fb0..fdc89b0 100644 --- a/frontend/src/components/LabNotebook.tsx +++ b/frontend/src/components/LabNotebook.tsx @@ -8,6 +8,7 @@ import { ResearchPaper } from "./Notebook/ResearchPaper"; import { cn } from "@/lib/utils"; import { StreamingMarkdown } from "./StreamingMarkdown"; import { CredentialPrompt, CredentialFormState } from "./CredentialPrompt"; +import { ArtifactsBrowser } from "./Artifacts/ArtifactsBrowser"; import { CredentialStatus, fetchCredentialStatus, saveCredentials } from "@/lib/api"; type PendingRun = { @@ -39,6 +40,7 @@ export function LabNotebook() { }); const [selectedModel, setSelectedModel] = useState<"gemini-3-pro-preview" | "claude-opus-4-5">("gemini-3-pro-preview"); const [showCredentialPrompt, setShowCredentialPrompt] = useState(false); + const [showArtifacts, setShowArtifacts] = useState(false); const [pendingRun, setPendingRun] = useState(null); const [isCheckingCredentials, setIsCheckingCredentials] = useState(false); const [isSavingCredentials, setIsSavingCredentials] = useState(false); @@ -169,16 +171,32 @@ export function LabNotebook() { return (
- {/* Fixed API Keys Button - Top Right */} - + {/* Fixed Header Buttons - Top Right */} +
+ + + +
@@ -446,6 +464,11 @@ export function LabNotebook() { isSaving={isSavingCredentials} error={credentialPromptError} /> + + setShowArtifacts(false)} + />
); } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 3057e7c..bcde6d9 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -225,3 +225,40 @@ export async function saveCredentials(payload: CredentialUpdatePayload): Promise // Return the new status based on what we just stored + what was already there return fetchCredentialStatus(); } + +// --------------------------------------------------------------------------- +// File API +// --------------------------------------------------------------------------- + +export interface FileItem { + name: string; + path: string; + type: "directory" | "file"; + size: number; + modified: string; +} + +export async function listFiles(path: string = "."): Promise { + const response = await fetch(`${API_BASE_URL}/api/files/list?path=${encodeURIComponent(path)}`); + if (!response.ok) { + throw new Error(`Failed to list files: ${response.statusText}`); + } + return response.json(); +} + +export async function readFile(path: string): Promise<{ content: string }> { + const response = await fetch(`${API_BASE_URL}/api/files/read?path=${encodeURIComponent(path)}`); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || `Failed to read file: ${response.statusText}`); + } + return response.json(); +} + +export function getDownloadUrl(path: string): string { + return `${API_BASE_URL}/api/files/download?path=${encodeURIComponent(path)}`; +} + +export function getZipDownloadUrl(path: string): string { + return `${API_BASE_URL}/api/files/download_zip?path=${encodeURIComponent(path)}`; +} From d75573212a88786e805c54cecb46cbcaa80d41ed Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:35:07 +0000 Subject: [PATCH 2/7] feat: add file browser and download capability to UI - Added backend endpoints in `api_server.py` for listing, reading, and downloading files. - Added `ArtifactsBrowser` component in frontend to navigate and view local files. - Integrated "Files" button in `LabNotebook` to access the file browser. - Verified backend with Python tests and frontend with Playwright. From 99d9328b6fe05f095602df3dd36d2b75c0247dbe Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:55:40 +0000 Subject: [PATCH 3/7] fix: ensure files button is always visible and add refresh - Increased z-index of header buttons in `LabNotebook.tsx` to z-[60] to prevent them from being covered by the run header. - Added refresh buttons to `ArtifactsBrowser.tsx` for both the file list and file preview. - Verified fixes with Playwright. --- __pycache__/api_server.cpython-312.pyc | Bin 36911 -> 36911 bytes backend.log | 18 ++-- frontend.log | 5 +- .../components/Artifacts/ArtifactsBrowser.tsx | 87 +++++++++++------- frontend/src/components/LabNotebook.tsx | 2 +- 5 files changed, 69 insertions(+), 43 deletions(-) diff --git a/__pycache__/api_server.cpython-312.pyc b/__pycache__/api_server.cpython-312.pyc index 99e1add305b790fa04271b9556257a4a87cc39f8..942c8b5c696817660232a746d754344844457cd1 100644 GIT binary patch delta 21 bcmZ3#fNA{#Ca%-Gyj%=Gz%I3sOLYPOL+=GG delta 21 bcmZ3#fNA{#Ca%-Gyj%=GAi=hgOLYPOL-7SF diff --git a/backend.log b/backend.log index afb100b..d810b66 100644 --- a/backend.log +++ b/backend.log @@ -1,15 +1,17 @@ INFO: Will watch for changes in these directories: ['/app'] INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) -INFO: Started reloader process [7049] using StatReload -INFO: Started server process [7196] +INFO: Started reloader process [8444] using StatReload +INFO: Started server process [8592] INFO: Waiting for application startup. INFO: Application startup complete. -INFO: 127.0.0.1:35952 - "GET /api/health HTTP/1.1" 200 OK -INFO: 127.0.0.1:35954 - "GET /api/credentials/status HTTP/1.1" 200 OK -INFO: 127.0.0.1:35964 - "GET /api/credentials/status HTTP/1.1" 200 OK -INFO: 127.0.0.1:35964 - "GET /api/files/list?path=. HTTP/1.1" 200 OK +INFO: 127.0.0.1:41720 - "GET /api/health HTTP/1.1" 200 OK +INFO: 127.0.0.1:41734 - "GET /api/credentials/status HTTP/1.1" 200 OK +INFO: 127.0.0.1:41736 - "GET /api/credentials/status HTTP/1.1" 200 OK +INFO: 127.0.0.1:41736 - "GET /api/files/list?path=. HTTP/1.1" 200 OK +INFO: 127.0.0.1:41736 - "GET /api/files/list?path=frontend HTTP/1.1" 200 OK +INFO: 127.0.0.1:41736 - "GET /api/files/read?path=frontend%2Fpackage.json HTTP/1.1" 200 OK INFO: Shutting down INFO: Waiting for application shutdown. INFO: Application shutdown complete. -INFO: Finished server process [7196] -INFO: Stopping reloader process [7049] +INFO: Finished server process [8592] +INFO: Stopping reloader process [8444] diff --git a/frontend.log b/frontend.log index ee1435a..8ed84e3 100644 --- a/frontend.log +++ b/frontend.log @@ -4,8 +4,9 @@ Port 5173 is in use, trying another one... Port 5174 is in use, trying another one... +Port 5175 is in use, trying another one... - VITE v7.2.4 ready in 262 ms + VITE v7.2.4 ready in 1266 ms - ➜ Local: http://localhost:5175/ + ➜ Local: http://localhost:5176/ ➜ Network: use --host to expose diff --git a/frontend/src/components/Artifacts/ArtifactsBrowser.tsx b/frontend/src/components/Artifacts/ArtifactsBrowser.tsx index 977cc02..eaa9b6d 100644 --- a/frontend/src/components/Artifacts/ArtifactsBrowser.tsx +++ b/frontend/src/components/Artifacts/ArtifactsBrowser.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { X, Loader2, FileText, Folder, Image, Download, ChevronRight, Home, ArrowUp } from "lucide-react"; +import { X, Loader2, FileText, Folder, Image, Download, ChevronRight, Home, ArrowUp, RefreshCw } from "lucide-react"; import { FileItem, listFiles, readFile, getDownloadUrl, getZipDownloadUrl } from "@/lib/api"; import { cn } from "@/lib/utils"; @@ -50,27 +50,31 @@ export function ArtifactsBrowser({ isOpen, onClose }: ArtifactsBrowserProps) { handleNavigate(newPath); }; + const fetchFileContent = async (file: FileItem) => { + setLoadingContent(true); + setFileContent(null); + + // Check if text or image + const isImage = /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file.name); + + if (!isImage) { + try { + const { content } = await readFile(file.path); + setFileContent(content); + } catch (err) { + // Binary file or error + setFileContent(null); + } + } + setLoadingContent(false); + }; + const handleFileClick = async (file: FileItem) => { if (file.type === "directory") { handleNavigate(file.path); } else { setSelectedFile(file); - setLoadingContent(true); - setFileContent(null); - - // Check if text or image - const isImage = /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file.name); - - if (!isImage) { - try { - const { content } = await readFile(file.path); - setFileContent(content); - } catch (err) { - // Binary file or error - setFileContent(null); - } - } - setLoadingContent(false); + fetchFileContent(file); } }; @@ -109,13 +113,23 @@ export function ArtifactsBrowser({ isOpen, onClose }: ArtifactsBrowserProps) {
{/* Toolbar */}
- +
+ + +
{selectedFile.name}
- - - Download - +
+ + + + Download + +
diff --git a/frontend/src/components/LabNotebook.tsx b/frontend/src/components/LabNotebook.tsx index fdc89b0..7784d53 100644 --- a/frontend/src/components/LabNotebook.tsx +++ b/frontend/src/components/LabNotebook.tsx @@ -172,7 +172,7 @@ export function LabNotebook() { return (
{/* Fixed Header Buttons - Top Right */} -
+
+ {currentPath !== "." && ( + <> + + {currentPath} + + )} +
+
+ +
+ + {/* Content */} +
+ + {/* File List */} +
+ {/* Toolbar */} + + + {loading ? ( +
+ +
+ ) : error ? ( +
+ {error} +
+ ) : files.length === 0 ? ( +
+ Empty directory +
+ ) : ( +
+ {files.map((file) => ( + + ))} +
+ )} +
+ + {/* Preview Pane */} + {selectedFile && ( +
+
+
+ {/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(selectedFile.name) ? ( + + ) : ( + + )} + {selectedFile.name} +
+
+ + + + Download + +
+
+ +
+ {loadingContent ? ( +
+ +
+ ) : /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(selectedFile.name) ? ( +
+ {selectedFile.name} +
+ ) : fileContent !== null ? ( +
+                                {fileContent}
+                            
+ ) : ( +
+

Preview not available for this file type.

+
+ )} +
+
+ )} +
+
+
+ ); +} + +function formatBytes(bytes: number, decimals = 2) { + if (!+bytes) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; +} diff --git a/frontend/src/components/LabNotebook.tsx b/frontend/src/components/LabNotebook.tsx index 64c0fb0..7784d53 100644 --- a/frontend/src/components/LabNotebook.tsx +++ b/frontend/src/components/LabNotebook.tsx @@ -8,6 +8,7 @@ import { ResearchPaper } from "./Notebook/ResearchPaper"; import { cn } from "@/lib/utils"; import { StreamingMarkdown } from "./StreamingMarkdown"; import { CredentialPrompt, CredentialFormState } from "./CredentialPrompt"; +import { ArtifactsBrowser } from "./Artifacts/ArtifactsBrowser"; import { CredentialStatus, fetchCredentialStatus, saveCredentials } from "@/lib/api"; type PendingRun = { @@ -39,6 +40,7 @@ export function LabNotebook() { }); const [selectedModel, setSelectedModel] = useState<"gemini-3-pro-preview" | "claude-opus-4-5">("gemini-3-pro-preview"); const [showCredentialPrompt, setShowCredentialPrompt] = useState(false); + const [showArtifacts, setShowArtifacts] = useState(false); const [pendingRun, setPendingRun] = useState(null); const [isCheckingCredentials, setIsCheckingCredentials] = useState(false); const [isSavingCredentials, setIsSavingCredentials] = useState(false); @@ -169,16 +171,32 @@ export function LabNotebook() { return (
- {/* Fixed API Keys Button - Top Right */} - + {/* Fixed Header Buttons - Top Right */} +
+ + + +
@@ -446,6 +464,11 @@ export function LabNotebook() { isSaving={isSavingCredentials} error={credentialPromptError} /> + + setShowArtifacts(false)} + />
); } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 3057e7c..bcde6d9 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -225,3 +225,40 @@ export async function saveCredentials(payload: CredentialUpdatePayload): Promise // Return the new status based on what we just stored + what was already there return fetchCredentialStatus(); } + +// --------------------------------------------------------------------------- +// File API +// --------------------------------------------------------------------------- + +export interface FileItem { + name: string; + path: string; + type: "directory" | "file"; + size: number; + modified: string; +} + +export async function listFiles(path: string = "."): Promise { + const response = await fetch(`${API_BASE_URL}/api/files/list?path=${encodeURIComponent(path)}`); + if (!response.ok) { + throw new Error(`Failed to list files: ${response.statusText}`); + } + return response.json(); +} + +export async function readFile(path: string): Promise<{ content: string }> { + const response = await fetch(`${API_BASE_URL}/api/files/read?path=${encodeURIComponent(path)}`); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.detail || `Failed to read file: ${response.statusText}`); + } + return response.json(); +} + +export function getDownloadUrl(path: string): string { + return `${API_BASE_URL}/api/files/download?path=${encodeURIComponent(path)}`; +} + +export function getZipDownloadUrl(path: string): string { + return `${API_BASE_URL}/api/files/download_zip?path=${encodeURIComponent(path)}`; +} diff --git a/logger.py b/logger.py index be1f86a..1756784 100644 --- a/logger.py +++ b/logger.py @@ -36,6 +36,13 @@ def setup_logging(): # Global file logger instance logger = setup_logging() +def set_log_file(path): + """Adds a file handler to the global logger.""" + file_handler = logging.FileHandler(path) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) + logger.addHandler(file_handler) + def log_step(step_name, status="INFO"): """Logs a step to the file.""" logger.info(f"[{step_name}] {status}") diff --git a/main.py b/main.py index 8c62c91..a33501f 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,13 @@ import os import sys import argparse +import json +from datetime import datetime +from pathlib import Path from dotenv import load_dotenv from agent import run_experiment_loop -from logger import print_status +from logger import print_status, set_log_file def main(): @@ -32,6 +35,12 @@ def main(): "In 'orchestrator' mode: the high-level research task to investigate." ), ) + parser.add_argument( + "--run-dir", + type=str, + default=None, + help="Directory to store run artifacts (agent.log, final paper, etc.).", + ) parser.add_argument( "--gpu", type=str, @@ -88,6 +97,30 @@ def main(): args = parser.parse_args() + # Determine/Create Run Directory + if args.run_dir: + run_dir = Path(args.run_dir) + # If passed by parent process, assume created. If not, create it. + run_dir.mkdir(parents=True, exist_ok=True) + else: + # Generate new run directory for top-level process + task_slug = "".join(x for x in args.task if x.isalnum() or x in " -_").strip().replace(" ", "_")[:50] + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + # Change naming convention to _ as requested + run_dir = Path("runs") / f"{task_slug}_{timestamp}" + run_dir.mkdir(parents=True, exist_ok=True) + + # Configure Logger to write to this run directory + set_log_file(run_dir / "agent.log") + + # Save metadata for top-level runs + if not args.run_dir: + try: + with open(run_dir / "metadata.json", "w") as f: + json.dump(vars(args), f, indent=2, default=str) + except Exception as e: + print(f"[WARNING] Failed to save metadata: {e}", file=sys.stderr) + # Preserve existing behavior by default: single agent mode. if args.mode == "single": print_status("Initializing Single Researcher Agent...", "bold cyan") @@ -122,6 +155,7 @@ def main(): max_parallel_experiments=args.max_parallel, test_mode=args.test_mode, model=args.model, + run_dir=run_dir, ) except KeyboardInterrupt: print_status("\nOrchestrated experiment interrupted by user.", "bold red") diff --git a/orchestrator.py b/orchestrator.py index 1f2e2dd..c8cfe54 100644 --- a/orchestrator.py +++ b/orchestrator.py @@ -4,6 +4,7 @@ import subprocess import threading import re +from pathlib import Path from concurrent.futures import ThreadPoolExecutor from typing import List, Optional, Dict, Any @@ -18,6 +19,7 @@ # Global orchestrator state _default_gpu: Optional[str] = None _default_model: str = "gemini-3-pro-preview" +_run_dir: Optional[Path] = None _experiment_counter: int = 0 # Regex for stripping ANSI escape sequences (Rich colour codes, etc.). @@ -287,6 +289,10 @@ def run_researcher(hypothesis: str, gpu: Optional[str] = None, test_mode: bool = if test_mode: cmd.append("--test-mode") + # Pass the run directory to the subprocess so it logs to the same place + if _run_dir: + cmd.extend(["--run-dir", str(_run_dir)]) + print_status( f"[Experiment {experiment_id}] Spawning single-agent process with command: " f"{' '.join(cmd)}", @@ -366,6 +372,16 @@ def _reader(stream, chunks: List[str], is_err: bool) -> None: # UI-only event lines, while leaving CLI / UI behavior untouched. llm_transcript = _clean_transcript_for_llm(transcript) + # Save transcript to file if run_dir is set + if _run_dir: + try: + agents_dir = _run_dir / "agents" + agents_dir.mkdir(parents=True, exist_ok=True) + with open(agents_dir / f"agent_{experiment_id}.txt", "w", encoding="utf-8") as f: + f.write(transcript) + except Exception as e: + logger.error(f"Failed to save transcript for agent {experiment_id}: {e}") + result: Dict[str, Any] = { "experiment_id": experiment_id, "hypothesis": hypothesis, @@ -406,6 +422,7 @@ def run_orchestrator_loop( max_parallel_experiments: int = 2, test_mode: bool = False, model: str = "gemini-3-pro-preview", + run_dir: Optional[Path] = None, ) -> None: """ Main orchestrator loop using Gemini 3 Pro or Claude Opus 4.5 with thinking + manual tool calling. @@ -419,10 +436,12 @@ def run_orchestrator_loop( in a single wave of tool calls. test_mode: If True, runs in test mode with mock data. model: LLM model to use ("gemini-3-pro-preview" or "claude-opus-4-5"). + run_dir: Directory to save artifacts. """ - global _default_gpu, _default_model + global _default_gpu, _default_model, _run_dir _default_gpu = default_gpu _default_model = model + _run_dir = run_dir print_panel( f"Research Task:\n{research_task}", @@ -514,6 +533,14 @@ def run_orchestrator_loop( "Method X is recommended.\n\n" "[DONE]" ) + + if _run_dir: + try: + with open(_run_dir / "final_paper.md", "w", encoding="utf-8") as f: + f.write(final_paper) + except Exception as e: + logger.error(f"Failed to save final paper (test mode): {e}") + print_panel(final_paper, "Final Paper", "bold green") log_step("ORCH_FINAL", "Final paper generated.") emit_event("ORCH_PAPER", {"content": final_paper}) @@ -876,6 +903,14 @@ def _execute_single_call(tool_block): final_text[-1] += delta.text final_paper = "\n\n".join(t for t in final_text if t) + + if _run_dir: + try: + with open(_run_dir / "final_paper.md", "w", encoding="utf-8") as f: + f.write(final_paper) + except Exception as e: + logger.error(f"Failed to save final paper: {e}") + print_panel(final_paper, "Final Paper", "bold green") log_step("ORCH_FINAL", "Final paper generated.") emit_event("ORCH_PAPER", {"content": final_paper}) @@ -1236,6 +1271,13 @@ def _execute_single_call(fn_call) -> Dict[str, Any]: if part.text and not getattr(part, "thought", False): final_text += part.text + if _run_dir: + try: + with open(_run_dir / "final_paper.md", "w", encoding="utf-8") as f: + f.write(final_text) + except Exception as e: + logger.error(f"Failed to save final paper: {e}") + print_panel(final_text, "Final Paper", "bold green") log_step("ORCH_FINAL", "Final paper generated.") emit_event("ORCH_PAPER", {"content": final_text}) From 4081f5304654a5e84c7eb2960ed617faa9b1f564 Mon Sep 17 00:00:00 2001 From: peter milford Date: Fri, 28 Nov 2025 18:39:40 -0800 Subject: [PATCH 5/7] Add .gitignore with common files. Manually remove the files we wanted ignored. --- .gitignore | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9326a35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# ignored files +.env +__pycache__/ + +# === Logs === +*.log + +# === Runtime artifacts === +run/ + +# === Python cache === +__pycache__/ +*.py[cod] # for compiled files + +# === Environment variables === +.env +.env.local + +# === Virtual environments (optional) === +venv/ +env/ +.venv/ + +# === IDE / editor files (optional but common) === +.idea/ +.vscode/ +*.swp +*.swo \ No newline at end of file From 99bb7d2f9196b9a212d75d0f50c03b589bda52ec Mon Sep 17 00:00:00 2001 From: peter milford Date: Fri, 28 Nov 2025 18:44:16 -0800 Subject: [PATCH 6/7] Remove files that shouldn't have been tracked. --- __pycache__/agent.cpython-312.pyc | Bin 27700 -> 0 bytes __pycache__/api_server.cpython-312.pyc | Bin 36911 -> 0 bytes __pycache__/insights.cpython-312.pyc | Bin 6745 -> 0 bytes __pycache__/logger.cpython-312.pyc | Bin 2557 -> 0 bytes __pycache__/orchestrator.cpython-312.pyc | Bin 43229 -> 0 bytes agent.log | 44 ----------------------- backend.log | 17 --------- frontend.log | 12 ------- 8 files changed, 73 deletions(-) delete mode 100644 __pycache__/agent.cpython-312.pyc delete mode 100644 __pycache__/api_server.cpython-312.pyc delete mode 100644 __pycache__/insights.cpython-312.pyc delete mode 100644 __pycache__/logger.cpython-312.pyc delete mode 100644 __pycache__/orchestrator.cpython-312.pyc delete mode 100644 agent.log delete mode 100644 backend.log delete mode 100644 frontend.log diff --git a/__pycache__/agent.cpython-312.pyc b/__pycache__/agent.cpython-312.pyc deleted file mode 100644 index f72fe4f2f96d52b787e5db0ae73f6cdadad88573..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27700 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 diff --git a/__pycache__/api_server.cpython-312.pyc b/__pycache__/api_server.cpython-312.pyc deleted file mode 100644 index 942c8b5c696817660232a746d754344844457cd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36911 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 diff --git a/__pycache__/insights.cpython-312.pyc b/__pycache__/insights.cpython-312.pyc deleted file mode 100644 index a5e73844bf6f42ec896f6797ca140c1e2099e1d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6745 zcma)AYit`=cD}>m@J&jhsP}6mSvDz4l%I*LhZ8%pEH#dl+DUdTSD{Co5j8Y9=rIiplScqD0TrE*&n3nK+LQb4Y28t{9|Jk*v3E7 zbB7#KR=i2a7Vo*QbMHOp+;h)&K5{y32tJAK75`9=&>u-f|L7t?J^g1O*3sLDM>B}0 zcteDW8fFX>q3H-cLu;jR#;BFd3^ZC@rz` zqQt{MTRf5gjuIP-D`HIIB5ZJhlR-#v0X73?g2PFQ080m9kPL(%`)!{SmG~&t&Ph^C z;mAxpE8pfj+AoW@_EIeSHeb}s+3`Kh1Mya(v2#OQV81>Yq`gu_xzAj z?0Re1p;MJIg&xrQSZ?2bt{|T=`N}0ODH274Nhtz|9D}GMg08RP`B<+k^e3pI>!doSwdT=|bS#o0EapFI-JFJzryW5!0eJb%D8e>;5%=rt^5tePYG@Nmc!>yFJq} zlyeVf9K*ZrhV{O+zRkra?tvBauHC&n^%OXuVCiBL8WAmN`t5?&breErDy2D^W;BLo z69}aZDMNVw3~d^?fwC5EQJ6R+W0=^=f$x5)o~Hi{7e?1*iHj-B9AE*}G8dy#wyKm- zpUInVj3FdjQbyjuo0}1DNy418F-3*T_Eq7@TT}E;5N}Hve}aBi=0Tx5i$=uT!)4p6 z%t)237HrIsJTo4Xqa5*50;_q7d10~WD)f6Ej78&I5Jm{MlmxliZ`A& zR3o{D@C!|AMg;WF&q;mAETPVZ-V<{@EHLl1V^%&2K~2=6=S~Ivu;_lY}Dtf z+3o7y&GDV8pq!tpI$M4W_PVD{%B<5 z(6+rjU*EPNY$&Tz=E#|B{h5`Ed5iM{$8E>z_clBmrj6j{k!!+rz7goV_h$Y?I;1M8UEPf(p5Vnkl~nk_1;(Hrk2;HDmq;GsWcnH1PCK4OhUR z)3j1H#)=7AQOX#G6%KwcG&bg&of&UZv>1T!3cC(+gf^n_17FJUz^t2oh~xIwjx!|BnS16^B)=`*v=Hog4I>%hf4!${Oy{c~{CL z_}{eo4*T59n#xQ}-SO}53m>Y?)hXWn-1u>@46@n_MurdTb1G%nSD|%^ujM@t>U52k z_9z>H?>9PEqPu>bN7%1Zl`>Vfn6!;=PuXr#xI;Ok_or;(QJt=oe7kOC3VC?6Pv04~ zrpzU)XiEV{EiUW;zDOqZ4q^*GXxzU-u}v8&F+ycd3~wMa2#0Umhuoo@)aR2`$twCD zc~*M_TzWg)Viul)Ch`=Jwj%#4s2eH7j zA#Hl!v`8(YTl7`5NFCs4q|jBgO5J(m&-8abx8l(Q%R!Z|e}2WQr4{cy{g##gA#LAx zmg6tr+yI>IDKp5Xu^H`?W!XCTMoo$azDc5Z7m?k4W04O$@P@R5Z%&y($8Yj2T6qcV z-5fssf|Z(ey+HrVFX(U4`}tO~N;673ztx7F`kb^wsyZNbnn+zT>M~C`j{Yb5IYIbK@sOgfEuxREz-b=xqhvm z^%8CXY(fNX5Ln~$mpPQ){UMfs05X*aNPyWzQCVOiWQhm_*<8O)0+8$1HDGi7dMN^d zI)t)-y2wtR`=$wWBGJxiKsPYI1e{=paB#pdvcQsPx^$$XZ1$b%y{;nQTk0AjSg()m z(vS|+$~c7VayG54oNW0HCjw9kY1puM;#fk7Clm;brhEp~T;irO8XN8uJbzbX2{8j+c>YNuk)!zAgxH zg5>Z*3_zN9Wc2unW9(u~=CQAjjmyG3XlgDIi3ker1Ar^fb3uW`eE>uNhe(O&oFGFF zfs#QEb_qw)L@GfL2rz^IIe@A2>=K-W06r{evjiESDMV!jmh3gO4pr{g|5vj$i{)sECoDN3wqQwIZNh0K`1RzSpIQ zN<=8z0NDdny2$!nsV-Q)3oIkT%?S|;qJR^50BG6c~805=HOH65yUXn^RP1OFhXHBs(X zplG}SC3XX{0Wgcvk_M`!bnUQe0%~3iCB3DjLQ&F?9%1J>F#;%6^B!7iqz4qygJlwa zX&aPD=%?C6iAXWPa|)*#$#0TLDD(Y;s!0q^YeO#(rnIC*5e$P@ArV0mCdMU?Wj zHIf&~+X!fvaEJ}#n8flz5E2|;+u3LjW?=D*ZW$ z$&MoWhltw@S;#RE}ZUqG`NurvFHzklt6Ow8M?9C~PtlHov z@*)USz#43l`^avHWymBBkx!9@O+}HY=0#4H;4T0N13>}uig7$v8Ay30LNZH68i_km z8CffABBUlDrzHge3KIWU1a~x;lFy}D^rt}X*48KiGZN#0y=Yk))dGeLw-pO2B|rtl zt{vA}-n42YcB`6TTp|J~A+oOk98tAkE^-rA;8j<7a9}PHo5KWxJm>pM0I~6aH3W3e zDE}4AAacMXpfyNNv?5o_#>+Wp_wx9s_L>hyZjanKn{OF>bShglk+Dn^Ocv|Q1sX98 zdyUA|^rPA3@q)32aph~9)<@SycWRGkYmevM-p{J*)}3q4d{f763;Xk-yF)uI1KE~= zhwo%s268PUndZ?whOTiJXr|t^=RlsOomzjk)}QY_op0^PcOBj{8$GT<4f33Liku$X ziluM}RlAob3WjQCn)6%2G!XTJ5& zJ>!mdFzX%M@s4J_qdVTQtat3ucQS9!X1rrL@4MO7ckfOV?5OP&fM8GS`q{O!x!P_x zlC7S(LhaVntv9bVZ%pKBdR9z@W5`moVal4@e;IyQ|1Zt|)coOO?)doc=qHCS!Aje$ zqZ#k$ifLsjYiY`xU8{$)=EjY8HZR@{WZDLItC}_rZ#LW=cyQ`T)vLf@b*ZJ~?-P@g``PQz@Ga!z8{5_m$J^4_`whot801H@t z<4=2w6mrykfeegm*Ww}=KU4S0qsFb{Pb_Z$=zmsVpy%0M7h-*1AgZZTZDk8Il+X4| z+B^z0Mkcy$yD|;0KJspz+p$b#EmO~66m&fU{S)>cB8(#cyyHsV;UWK>b*ua;UU~Nc zeQ)Bv^Fw;uJA60s$eneL?O7SCt>8kQ#uZE6?ppQUeq{r1F5LZozO83-;aAaLL?6DE zJ3RQ|;J=^!*RxyQx#6kIUtZ1*U(U2$S(yUOIqP;?I*4U%O1YNPYnDIl*0#d-opqm7 zdthVEx-b75w(t3=ZFBI!TTdEKGVN`lIH$*N+)K8lVV$ z%=-EW^U)atp@%6#k8}e4yKV+1K->jxgao}w2l!u3EG3P7M$JKe=^^pvwJ~sG-AP*hjW5GZ`wFLt63G$K#0y20P zBu|hZcznU#mjYnb1c3y2UBJlDMY1(ANzKS4O{kheu^423i+NW^vEtXZUwkvH;OH+E zUB4v|An^q(be>Gt(z#WEz`is)bdh(c_$(2x_iLo3po$qZ%LqJAiHLAk7NLv0au^a5 z2n>OuzA%_5dT?N|3Zy_K<%Gen^ziktj$>~IHjER(6R}v yr2ELSIb-d}p~Fv2R;u=Cql0QMwBMj8civS~pb7D*v$Mbu0*0GBduBpvJNiFy=!|D2Pe)45VAA`SV$c{`XND83EHM0LP$$1R8tgfJR955+Uw4) zOJfBoNJyyap*d6{h#+#L3g@bSLZx0}B~h#?^w4tYjSza_)HkzUW1tc%&6_uG-h90G z`_0?m+uC9XzF#H|6_N@!{X<5$Tx0u>zqfkM!>{{nFfd5u&dG$V`omxR|G&ETfmREX5FI?^;Z6(R>|&q)tL zEBsXWq}DT4-_|M#4pv=5b7&^sI_K*xx6)pp4OUCA`XVyIRpX-Nxq79n+fFVA>C-(& zpK`psK3%Xa9bnV5KBnpC#?V32&3c8RWw`p=`p`k!`qr{bW^-JGbOV@%Ta5}#)3B!z zVqxLOBGV2nnjTa0J{epV6JC@2B!E$A+nE z3~p;trn;c=Td?rQpPc^kQ_eTO-6Y@3qAtfzf8IV~mdhi)DP5jrZLq3R7CsO$)xoI? zntKVvJbH}do4BWjdmgr@ezd>0>xmPOwa!g#pr#Eh$JeysCn21S{i~?q$g?igxpOl) zSW6BrkF6z-&Y!E}#3t^o;oha5d;Pcj*YSaRyn7XQZ^PCo+oTf=!q5cO{siSsRGUKq zaMz?cX@;+T z9G)MmZN7g!C4xHx%A11onp~a(ZqCU>dLXYf%IOQX ziBJb8XrTnf%G4?RQuH$a!2}@(MQW7S8?WHm7SpC2N-PS|%)%&QeFqE3ILs>zXBIB= z4JkjZVfNrpM32Cjg~c+L$Ds#wi3|TpaWH)!W{yE~IWa}mxOx8i`JaXsmF3E>@BWrr zPhQx-6R$(haS+)#4a%Fc1JDEL06ix_$k*>S|KLCY3?;m`nHL{Wy-|nL^e`O3q$^eV z@(Aa_aTwBfKmZSvh7)}c#xGGo^P}|lq9FfN z{cl#2M>p^>5wIbN=L(%*-45}VnZ{vDHZ>{Dg8C8b16n^UBP*(eM%fYRcaY z)3O!UbBdF`Nd%>X-KRN!#Dcp5AaVVlk+_uqx7q(4ym zBcwkxtfaTTeZ)tDe|h*ZS)1sjYWBQ&pVb)k52QCp7< o%_~oFyVSGw7K-)!Ffbo^io?>r?RHr@ysgU8XVNo}$Hi6t0|6yN!2kdN diff --git a/__pycache__/orchestrator.cpython-312.pyc b/__pycache__/orchestrator.cpython-312.pyc deleted file mode 100644 index 3561c01bf9ffc6362f5d598a27e16b2c28ab7c16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43229 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 diff --git a/agent.log b/agent.log deleted file mode 100644 index 1c4a513..0000000 --- a/agent.log +++ /dev/null @@ -1,44 +0,0 @@ -2025-11-29 01:47:38,531 - INFO - [ORCH_START] Research Task: Test Artifacts -2025-11-29 01:47:38,535 - INFO - [ORCH_THOUGHT] I need to decompose the research task into testable hypotheses. - -Based on the request, I will investigate three main directions: -1. Method X applicability -2. Method Y optimization -3. Hyperparameter Z sensitivity -I will launch agents to test these concurrently. -2025-11-29 01:47:40,538 - INFO - [ORCH_EXPERIMENT] Scheduled experiment 1 with GPU=None -2025-11-29 01:47:40,540 - INFO - [ORCH_EXPERIMENT] Scheduled experiment 2 with GPU=None -2025-11-29 01:47:43,289 - INFO - [START] Hypothesis: Hypothesis A: Test Artifacts can be solved by method X. -2025-11-29 01:47:43,292 - INFO - [THOUGHT] I need to verify this hypothesis using a Python script. -I will create a synthetic dataset and run a simple regression model. -Then I will analyze the coefficients to check the relationship. -2025-11-29 01:47:43,317 - INFO - [START] Hypothesis: Hypothesis B: Test Artifacts requires method Y optimization. -2025-11-29 01:47:43,319 - INFO - [THOUGHT] I need to verify this hypothesis using a Python script. -I will create a synthetic dataset and run a simple regression model. -Then I will analyze the coefficients to check the relationship. -2025-11-29 01:47:44,794 - INFO - [TOOL_CALL] execute_in_sandbox({'code': "import numpy as np\nimport pandas as pd\nprint('Generating synthetic data...')\ndata = pd.DataFrame({'x': np.random.rand(100), 'y': np.random.rand(100)})\nprint('Data shape:', data.shape)\nprint('Correlation:', data.corr().iloc[0,1])"}) -2025-11-29 01:47:44,821 - INFO - [TOOL_CALL] execute_in_sandbox({'code': "import numpy as np\nimport pandas as pd\nprint('Generating synthetic data...')\ndata = pd.DataFrame({'x': np.random.rand(100), 'y': np.random.rand(100)})\nprint('Data shape:', data.shape)\nprint('Correlation:', data.corr().iloc[0,1])"}) -2025-11-29 01:47:45,796 - INFO - [TOOL_RESULT] Executed -2025-11-29 01:47:45,823 - INFO - [TOOL_RESULT] Executed -2025-11-29 01:47:47,298 - INFO - [MODEL] The correlation is very low, which suggests no strong linear relationship. -However, since this is mock data, I will conclude based on the hypothesis. -2025-11-29 01:47:47,325 - INFO - [MODEL] The correlation is very low, which suggests no strong linear relationship. -However, since this is mock data, I will conclude based on the hypothesis. -2025-11-29 01:47:48,693 - INFO - [ORCH_EXPERIMENT] Scheduled experiment 3 with GPU=None -2025-11-29 01:47:48,709 - INFO - [ORCH_TOOL_RESULT] run_researcher completed -2025-11-29 01:47:48,744 - INFO - [ORCH_TOOL_RESULT] run_researcher completed -2025-11-29 01:47:51,408 - INFO - [START] Hypothesis: Hypothesis C: Test Artifacts is sensitive to hyperparameter Z. -2025-11-29 01:47:51,411 - INFO - [THOUGHT] I need to verify this hypothesis using a Python script. -I will create a synthetic dataset and run a simple regression model. -Then I will analyze the coefficients to check the relationship. -2025-11-29 01:47:52,913 - INFO - [TOOL_CALL] execute_in_sandbox({'code': "import numpy as np\nimport pandas as pd\nprint('Generating synthetic data...')\ndata = pd.DataFrame({'x': np.random.rand(100), 'y': np.random.rand(100)})\nprint('Data shape:', data.shape)\nprint('Correlation:', data.corr().iloc[0,1])"}) -2025-11-29 01:47:53,916 - INFO - [TOOL_RESULT] Executed -2025-11-29 01:47:55,417 - INFO - [MODEL] The correlation is very low, which suggests no strong linear relationship. -However, since this is mock data, I will conclude based on the hypothesis. -2025-11-29 01:47:56,829 - INFO - [ORCH_TOOL_RESULT] run_researcher completed -2025-11-29 01:47:56,831 - INFO - [ORCH_THOUGHT] The experiments have completed. -Agent A reported success with Method X. -Agent B found Method Y to be unstable. -Agent C confirmed sensitivity to Z. -I have sufficient evidence to write the final paper. -2025-11-29 01:47:58,834 - INFO - [ORCH_FINAL] Final paper generated. diff --git a/backend.log b/backend.log deleted file mode 100644 index d810b66..0000000 --- a/backend.log +++ /dev/null @@ -1,17 +0,0 @@ -INFO: Will watch for changes in these directories: ['/app'] -INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) -INFO: Started reloader process [8444] using StatReload -INFO: Started server process [8592] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: 127.0.0.1:41720 - "GET /api/health HTTP/1.1" 200 OK -INFO: 127.0.0.1:41734 - "GET /api/credentials/status HTTP/1.1" 200 OK -INFO: 127.0.0.1:41736 - "GET /api/credentials/status HTTP/1.1" 200 OK -INFO: 127.0.0.1:41736 - "GET /api/files/list?path=. HTTP/1.1" 200 OK -INFO: 127.0.0.1:41736 - "GET /api/files/list?path=frontend HTTP/1.1" 200 OK -INFO: 127.0.0.1:41736 - "GET /api/files/read?path=frontend%2Fpackage.json HTTP/1.1" 200 OK -INFO: Shutting down -INFO: Waiting for application shutdown. -INFO: Application shutdown complete. -INFO: Finished server process [8592] -INFO: Stopping reloader process [8444] diff --git a/frontend.log b/frontend.log deleted file mode 100644 index 8ed84e3..0000000 --- a/frontend.log +++ /dev/null @@ -1,12 +0,0 @@ - -> frontend@0.0.0 dev -> vite - -Port 5173 is in use, trying another one... -Port 5174 is in use, trying another one... -Port 5175 is in use, trying another one... - - VITE v7.2.4 ready in 1266 ms - - ➜ Local: http://localhost:5176/ - ➜ Network: use --host to expose From 78e0adea13d0c64157061bc9984242d90d1455df Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 29 Nov 2025 20:54:37 +0000 Subject: [PATCH 7/7] Fix: Ensure final paper is saved upon completion - Refactor file saving logic into `_save_final_paper` helper. - Ensure `final_paper.md` is saved when the orchestrator loop terminates successfully with `[DONE]` in both Gemini and Claude loops. - Previously, the file was only saved in the fallback path, causing missing output for successful runs. --- orchestrator.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/orchestrator.py b/orchestrator.py index c8cfe54..6b67884 100644 --- a/orchestrator.py +++ b/orchestrator.py @@ -51,6 +51,16 @@ def _clean_transcript_for_llm(transcript: str) -> str: return "\n".join(cleaned_lines) +def _save_final_paper(content: str) -> None: + """Save the final paper to the run directory.""" + if _run_dir: + try: + with open(_run_dir / "final_paper.md", "w", encoding="utf-8") as f: + f.write(content) + except Exception as e: + logger.error(f"Failed to save final paper: {e}") + + def emit_event(event_type: str, data: Dict[str, Any]) -> None: """Emit a structured event for the frontend.""" # Only emit structured events when explicitly enabled (e.g. via the web API). @@ -715,6 +725,7 @@ def _run_claude_orchestrator_loop( final_content = "\n\n".join(t for t in text_content if t) display_content = final_content.replace("[DONE]", "").strip() if display_content: + _save_final_paper(display_content) print_panel(display_content, "Final Paper", "bold green") log_step("ORCH_FINAL", "Final paper generated (in loop).") emit_event("ORCH_PAPER", {"content": display_content}) @@ -904,12 +915,7 @@ def _execute_single_call(tool_block): final_paper = "\n\n".join(t for t in final_text if t) - if _run_dir: - try: - with open(_run_dir / "final_paper.md", "w", encoding="utf-8") as f: - f.write(final_paper) - except Exception as e: - logger.error(f"Failed to save final paper: {e}") + _save_final_paper(final_paper) print_panel(final_paper, "Final Paper", "bold green") log_step("ORCH_FINAL", "Final paper generated.") @@ -1113,6 +1119,7 @@ def _run_gemini_orchestrator_loop( display_content = final_content.replace("[DONE]", "").strip() if display_content: + _save_final_paper(display_content) print_panel(display_content, "Final Paper", "bold green") log_step("ORCH_FINAL", "Final paper generated (in loop).") emit_event("ORCH_PAPER", {"content": display_content}) @@ -1271,12 +1278,7 @@ def _execute_single_call(fn_call) -> Dict[str, Any]: if part.text and not getattr(part, "thought", False): final_text += part.text - if _run_dir: - try: - with open(_run_dir / "final_paper.md", "w", encoding="utf-8") as f: - f.write(final_text) - except Exception as e: - logger.error(f"Failed to save final paper: {e}") + _save_final_paper(final_text) print_panel(final_text, "Final Paper", "bold green") log_step("ORCH_FINAL", "Final paper generated.")