diff --git a/python-tools/app/discovery.py b/python-tools/app/discovery.py index c4e3d14..d8e5acf 100644 --- a/python-tools/app/discovery.py +++ b/python-tools/app/discovery.py @@ -144,8 +144,14 @@ class TaskCatalog: group_path, {path.name for path in task_paths}, ) + paths_by_filename = {path.name: path for path in task_paths} + ordered_filenames = [ + *declarations_by_filename, + *(path.name for path in task_paths if path.name not in declarations_by_filename), + ] tasks = [] - for path in task_paths: + for filename in ordered_filenames: + path = paths_by_filename[filename] declaration = declarations_by_filename.get(path.name) tasks.append( Task( @@ -289,11 +295,10 @@ def parse_task_declaration( ) -> TaskDeclaration: if ( not isinstance(declaration, dict) - or not {"inputs"} <= set(declaration) or set(declaration) - {"inputs", "wait_for_result", "download_artifacts"} ): raise ValueError(f"{manifest_path} task {filename} has an invalid declaration.") - raw_inputs = declaration["inputs"] + raw_inputs = declaration.get("inputs", []) if not isinstance(raw_inputs, list): raise ValueError(f"{manifest_path} task {filename} inputs must be a list.") fields = tuple(parse_input_field(manifest_path, filename, raw) for raw in raw_inputs) diff --git a/python-tools/app/runner.py b/python-tools/app/runner.py index a2d60bb..8c076b2 100644 --- a/python-tools/app/runner.py +++ b/python-tools/app/runner.py @@ -121,8 +121,13 @@ class RunManager: run = self.store.get(int(run_id_text)) if kind == "uploads" and (run is None or run.status not in {"queued", "running"}): shutil.rmtree(path, ignore_errors=True) - elif kind == "artifacts" and path.stat().st_mtime < cutoff: - shutil.rmtree(path, ignore_errors=True) + elif kind == "artifacts": + try: + expired = path.stat().st_mtime < cutoff + except FileNotFoundError: + continue + if expired: + shutil.rmtree(path, ignore_errors=True) def run_background_task(self, task: BackgroundTask) -> None: """Run a declared internal task without creating high-volume run history.""" diff --git a/python-tools/static/app.js b/python-tools/static/app.js index 512b2e1..407cc66 100644 --- a/python-tools/static/app.js +++ b/python-tools/static/app.js @@ -126,12 +126,14 @@ for (const form of document.querySelectorAll("form[data-wait-for-result]")) { return; } event.preventDefault(); + // Disabled fields are omitted from FormData, so collect values first. + const formData = new FormData(form); setWaiting(true); showResult("Running...", "running"); try { const response = await fetch(form.action, { method: "POST", - body: new FormData(form), + body: formData, headers: { Accept: "application/json" }, }); const data = await response.json();