From c19a35d8c9b018d17a84c788eaa56f4afaa8630b Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 24 Sep 2026 23:34:29 -0500 Subject: [PATCH] fix(desktop): keep pip downloads and cache files out of Artifacts 021950ac81 stopped indexing arbitrary dotted paths, but terminal output is still scanned for bare URLs and looksLikeArtifact accepts any http(s) value. Installing from a PyPI mirror logs every download with its full URL (the .whl, its .whl.metadata, sdists under /packages/), so each one landed in the Artifacts menu; sdists pip reports under its cache dir (AppData\Local\pip\Cache, ~/.cache/pip, Library/Caches/pip) were indexed too. Heuristic candidates that are wheels, package-index dist downloads, or files under pip's cache are now dropped; explicit MEDIA deliveries are unaffected. Co-authored-by: yungchentang <46495124+yungchentang@users.noreply.github.com> --- .../src/app/artifacts/artifact-utils.ts | 33 +++++++++++++++++++ apps/desktop/src/app/artifacts/index.test.ts | 30 +++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/apps/desktop/src/app/artifacts/artifact-utils.ts b/apps/desktop/src/app/artifacts/artifact-utils.ts index db2d0a9f26..4609150adf 100644 --- a/apps/desktop/src/app/artifacts/artifact-utils.ts +++ b/apps/desktop/src/app/artifacts/artifact-utils.ts @@ -56,7 +56,40 @@ const SCREENSHOT_PATH_RE = /Screenshot path:\s*([^\r\n<>]+)/gi // scraped heuristically out of prose or a tool payload. type PushValue = (value: string, explicit?: boolean) => void +// pip's own traffic, not session output (#52972): terminal output logs every +// download (`Downloading https:///packages/…/pkg-1.0-py3-none-any.whl`, +// plus its `.whl.metadata`) and cached files live under pip's cache dir. +const PIP_CACHE_DIR_RE = /\/(?:\.cache\/pip|library\/caches\/pip|appdata\/local\/pip\/cache)\// +const WHEEL_RE = /\.whl(?:\.metadata)?$/ +const PACKAGE_INDEX_DIST_RE = /\/packages\/.+\.(?:tar\.gz|tar\.bz2|zip|egg)(?:\.metadata)?$/ + +function artifactPathForFiltering(value: string): string { + if (/^(?:https?|file):\/\//i.test(value)) { + try { + return decodeURIComponent(new URL(value).pathname).toLowerCase() + } catch { + // Malformed URL: fall through to plain string normalization. + } + } + + return value.replace(/\\/g, '/').toLowerCase() +} + +function isPythonPackageDownload(value: string): boolean { + const path = artifactPathForFiltering(value) + + return ( + PIP_CACHE_DIR_RE.test(path) || + WHEEL_RE.test(path) || + (/^https?:\/\//i.test(value) && PACKAGE_INDEX_DIST_RE.test(path)) + ) +} + function looksLikeArtifact(value: string, explicit = false): boolean { + if (!explicit && isPythonPackageDownload(value)) { + return false + } + if (/^(?:https?:\/\/|data:image\/)/.test(value)) { return true } diff --git a/apps/desktop/src/app/artifacts/index.test.ts b/apps/desktop/src/app/artifacts/index.test.ts index 943b5ccf74..f6ef646c50 100644 --- a/apps/desktop/src/app/artifacts/index.test.ts +++ b/apps/desktop/src/app/artifacts/index.test.ts @@ -251,6 +251,36 @@ ${payload} ]) }) + // #52972: pip logs every download with its full URL when the index is not + // files.pythonhosted.org (a mirror), and on Windows reports sdists under + // its cache dir. None of that is something the session produced. + it('does not index pip downloads or cache files from terminal output', () => { + const mirror = 'https://mirror.example.com/pypi/packages/7a/1b/0f3c' + const report = '/home/example/project/report.pdf' + const release = 'https://github.com/example/tool/archive/refs/tags/v1.0.tar.gz' + + const artifacts = collectArtifactsForSession(makeSession({ id: 'pip-session' }), [ + { + content: JSON.stringify({ + output: [ + 'Collecting anthropic', + ` Downloading ${mirror}/anthropic-0.46.0-py3-none-any.whl.metadata (23 kB)`, + ` Downloading ${mirror}/anthropic-0.46.0-py3-none-any.whl (223 kB)`, + ` Downloading ${mirror}/jiter-0.8.2.tar.gz (163 kB)`, + ' Saved C:\\Users\\Alice\\AppData\\Local\\pip\\Cache\\http-v2\\a\\b\\docstring_parser-0.16.tar.gz', + `Wrote ${report}; upstream release: ${release}` + ].join('\n'), + exit_code: 0 + }), + role: 'tool', + timestamp: 1_781_774_001, + tool_name: 'terminal' + } + ]) + + expect(artifacts.map(artifact => artifact.value).sort()).toEqual([report, release].sort()) + }) + it('does not treat an arbitrary dotted absolute path as an artifact', () => { const artifacts = collectArtifactsForSession(makeSession(), [ {