Skip to content

Commit 0d937cc

Browse files
committed
feat: unify the three install paths, replace the frozen agent
1 parent a2f61e1 commit 0d937cc

64 files changed

Lines changed: 7680 additions & 2188 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,26 @@ install.bat
2323
OmniParser_CraftOS
2424
debug_images
2525
workspace
26+
27+
# ── Per-machine runtime state — never belongs in an image ──────────────────
28+
# `COPY . .` takes whatever is in the build context, so without these a local
29+
# `docker build` bakes in the BUILDER's data and publishes it to every user of
30+
# the image. CI escapes it only by checking out clean, which makes this a trap
31+
# that fires exactly once, on someone's laptop.
32+
#
33+
# Same failure as packaging/CraftBotAgent.spec's blanket app/data entry, which
34+
# shipped 1.1 GB of one machine's memory index and databases.
35+
app/data/.file_index
36+
app/data/.usage
37+
agent_file_system
38+
chroma_db_memory
39+
logs
40+
runtime
41+
*.db
42+
.craftbot-managed
43+
config.json
44+
wheelhouse
45+
downloads-cache
46+
npm-cache
47+
playwright-browsers
48+
hf-cache

.github/workflows/parity.yml

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Three guarantees. This workflow turns "the install paths have drifted" from
2+
# something discovered by reading code into a number that moves.
3+
#
4+
# 1. locks — the hash-pinned lock matches requirements.txt everywhere
5+
# 2. parity — pip and conda installs produce the same environment
6+
# 3. installer-e2e — an install produces the same thing as a checkout
7+
#
8+
# A lock can only be generated on the platform it describes, so they are
9+
# generated by hand and committed. CI only verifies they are current.
10+
#
11+
# There is no frozen-agent job: the agent is no longer a PyInstaller bundle,
12+
# which is what removed the whole class of divergence these jobs guard.
13+
14+
name: Install parity
15+
16+
on:
17+
# No push trigger: nothing here writes to the repo, so there is nothing a
18+
# push would accomplish that a PR does not.
19+
pull_request:
20+
paths:
21+
- "requirements.txt"
22+
- "environment.yml"
23+
- "requirements/**"
24+
- "scripts/generate_lock.py"
25+
- "scripts/parity_check.py"
26+
- "scripts/package_source.py"
27+
- "scripts/test_install_e2e.py"
28+
- "app/provision/**"
29+
- "app/paths.py"
30+
- ".github/workflows/parity.yml"
31+
workflow_dispatch:
32+
33+
jobs:
34+
# ──────────────────────────────────────────────
35+
# Every committed lock came from the current
36+
# requirements.txt.
37+
# ──────────────────────────────────────────────
38+
locks:
39+
name: Locks match requirements.txt
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v4
43+
44+
- uses: actions/setup-python@v5
45+
with:
46+
python-version: "3.10"
47+
48+
# Checks all platforms' locks from one runner, and needs no pip resolve:
49+
# it compares the source digest recorded in each lock's header. See
50+
# scripts/generate_lock.py for why re-resolving here would be wrong.
51+
#
52+
# This only CHECKS. Locks are generated by hand — `python
53+
# scripts/generate_lock.py` — on the platform each one describes, and
54+
# committed. release.yml refuses to build a payload if one is missing.
55+
- name: Check locks
56+
run: python scripts/generate_lock.py --check
57+
58+
# ──────────────────────────────────────────────
59+
# pip vs conda must produce the same environment.
60+
# environment.yml used to carry its own package
61+
# list, which drifted by 15 packages. It now
62+
# provides only the runtime, and BOTH paths
63+
# install the same lock — so this job checks
64+
# that the two runtimes agree, not two lists.
65+
# ──────────────────────────────────────────────
66+
parity:
67+
name: pip vs conda (${{ matrix.os_label }})
68+
runs-on: ${{ matrix.os }}
69+
strategy:
70+
fail-fast: false
71+
matrix:
72+
include:
73+
- os: ubuntu-latest
74+
os_label: linux
75+
- os: windows-latest
76+
os_label: windows
77+
steps:
78+
- uses: actions/checkout@v4
79+
80+
- uses: actions/setup-python@v5
81+
with:
82+
python-version: "3.10"
83+
84+
- name: Install via pip (from the lock)
85+
shell: bash
86+
run: |
87+
python -m venv .venv-pip
88+
if [ -f .venv-pip/bin/python ]; then PY=.venv-pip/bin/python;
89+
else PY=.venv-pip/Scripts/python.exe; fi
90+
echo "PIP_PY=$PY" >> "$GITHUB_ENV"
91+
"$PY" -m pip install --upgrade pip
92+
# `ls | head -1` would pick another platform's lock once Linux and
93+
# macOS locks exist. app.provision.deps.find_lock resolves the exact
94+
# (platform, python) tag and refuses to fall back — a Linux lock
95+
# pins CUDA-flavoured torch wheels that do not exist on Windows.
96+
LOCK=$("$PY" -c "from app.provision.deps import find_lock; print(find_lock('.') or '')")
97+
if [ -z "$LOCK" ]; then
98+
echo "::error::no lock for this platform — run scripts/generate_lock.py"
99+
exit 1
100+
fi
101+
echo "using $LOCK"
102+
"$PY" -m pip install --require-hashes -r "$LOCK"
103+
104+
- name: Fingerprint pip install
105+
shell: bash
106+
run: |
107+
"$PIP_PY" scripts/parity_check.py --label pip > fp-pip.json
108+
109+
- uses: conda-incubator/setup-miniconda@v3
110+
with:
111+
activate-environment: craftbot
112+
environment-file: environment.yml
113+
auto-activate-base: false
114+
115+
# environment.yml deliberately lists no Python packages — they come from
116+
# the shared lock. Without this step the conda env holds only the
117+
# interpreter and system binaries, and the comparison below would report
118+
# a 200-package "divergence" that is really just a missing install.
119+
- name: Install the lock into the conda env
120+
shell: bash -el {0}
121+
run: |
122+
LOCK=$(python -c "from app.provision.deps import find_lock; print(find_lock('.') or '')")
123+
if [ -z "$LOCK" ]; then
124+
echo "::error::no lock for this platform — run scripts/generate_lock.py"
125+
exit 1
126+
fi
127+
echo "using $LOCK"
128+
python -m pip install --require-hashes -r "$LOCK"
129+
130+
- name: Fingerprint conda install
131+
shell: bash -el {0}
132+
run: python scripts/parity_check.py --label conda > fp-conda.json
133+
134+
- name: Compare
135+
shell: bash
136+
run: python scripts/parity_check.py --compare fp-pip.json fp-conda.json
137+
138+
- name: Upload fingerprints
139+
if: always()
140+
uses: actions/upload-artifact@v4
141+
with:
142+
name: fingerprints-${{ matrix.os_label }}
143+
path: fp-*.json
144+
145+
# ──────────────────────────────────────────────
146+
# The installer path must produce the same thing
147+
# as a source checkout. This is the acceptance
148+
# test for the whole architecture, so it runs on
149+
# every platform we ship.
150+
# ──────────────────────────────────────────────
151+
installer-e2e:
152+
name: Installer E2E (${{ matrix.os_label }})
153+
runs-on: ${{ matrix.os }}
154+
strategy:
155+
fail-fast: false
156+
matrix:
157+
include:
158+
- os: ubuntu-latest
159+
os_label: linux
160+
- os: windows-latest
161+
os_label: windows
162+
- os: macos-latest
163+
os_label: macos
164+
steps:
165+
- uses: actions/checkout@v4
166+
167+
- uses: actions/setup-python@v5
168+
with:
169+
python-version: "3.10"
170+
171+
- uses: actions/setup-node@v4
172+
with:
173+
node-version: "20"
174+
175+
- name: Build frontend
176+
shell: bash
177+
env:
178+
VITE_BACKEND_PORT: "7926"
179+
run: |
180+
cd app/ui_layer/browser/frontend
181+
npm install
182+
npx vite build
183+
184+
- name: Build the source payload
185+
shell: bash
186+
run: |
187+
python scripts/package_source.py
188+
189+
# Structural only: verifies path resolution, the managed-install marker
190+
# and payload completeness. The full run installs 239 packages and is
191+
# too slow for every PR — the `parity` job covers dependency equality.
192+
- name: Install E2E (structural)
193+
shell: bash
194+
run: |
195+
python scripts/test_install_e2e.py --skip-deps
196+
197+
- name: Upload payload
198+
if: always()
199+
uses: actions/upload-artifact@v4
200+
with:
201+
name: payload-${{ matrix.os_label }}
202+
path: dist/CraftBot-src.zip

0 commit comments

Comments
 (0)