gflow-cli 0.30.0__tar.gz → 0.31.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/CHANGELOG.md +10 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/KNOWN_ISSUES.md +102 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/PKG-INFO +1 -1
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/INDEX.md +1 -1
- gflow_cli-0.31.0/docs/LIVE_VERIFICATION_v0.31.0.md +83 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/pyproject.toml +1 -1
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/__init__.py +1 -1
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/drivers/agentic.py +61 -8
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/ui_automation_video.py +63 -8
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_image.py +20 -2
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/recorder.py +129 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/errors.py +34 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/image_batch.py +100 -22
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/worker/daemon.py +42 -16
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/drivers/test_agentic.py +119 -4
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_ui_automation_video.py +275 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_image.py +177 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_recorder.py +237 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_video_agent_ui_steps.py +3 -2
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/image_batch/test_image_manifest.py +325 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/image_batch/test_observability_events.py +26 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_errors.py +25 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/worker/test_daemon.py +183 -1
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/README.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/active.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/branch-review.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/changelog.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/check.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/doc-review.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/issue-assessment.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/issue-resolve.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/known-issues.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/next.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/plan.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/pr-council-review.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/predict.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/release.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/scenario.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/sonar.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.claude/commands/gflow/status.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.env.template +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.gitattributes +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/CODEOWNERS +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/copilot-instructions.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/dependabot.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/workflows/ci.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/workflows/external-pr-triage.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/workflows/governance-advisory.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/workflows/governance-benchmark.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/workflows/main-base-guard.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.github/workflows/release.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.gitignore +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.gitleaks.toml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.pre-commit-config.yaml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/.secrets.baseline +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/AGENTS.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/CLAUDE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/CONFIGURATION.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/CONTRIBUTING.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/DISCLAIMER.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/GEMINI.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/LICENSE +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/README.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/RELEASE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/ROADMAP.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/conftest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docker-compose.yml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/AGENT_GUIDE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/AGENT_UI_E2E.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/AGENT_UI_RECON.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/ARCHITECTURE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/AUTHENTICATION.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/CHARACTER.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/CHARACTER_RECON.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/CONFIGURATION.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/DATA_LAYER.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/DEBUGGING.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/DEMOS.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/DEVELOPMENT.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/E2E_TESTING.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/EXTERNAL_STORAGE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/GITHUB.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/GOVERNANCE_BENCHMARK.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/IMAGE_UPSCALE_RECON.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/INSTRUCTIONS.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_data_layer.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_image_batch.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.10.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.11.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.12.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.13.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.16.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.17.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.18.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.19.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.20.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.20.1.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.21.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.22.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.23.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.24.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.25.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.26.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.27.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.27.1.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.28.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.29.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.30.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.7.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.8.1.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.9.0.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_v0.9.1.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/LIVE_VERIFICATION_video_download.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/MCP.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/MOVIE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/PROJECT_STATUS.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/PROMPT_EXPANSION.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/SECURITY.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/TOOLS.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/USAGE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/USER_GUIDE.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/assets/demo-split-pf.gif +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/assets/example-run.gif +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/assets/examples.webp +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/medium_tutorial.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/schemas/movie-handoff.schema.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/sonar-cleanup-tracker.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-12-issue-174-library-ui-attach/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-22-mcp-server/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-22-mcp-server/PREDICT.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-22-mcp-server/SCENARIO.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-22-prompt-expansion/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-22-prompt-expansion/PREDICT.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-22-prompt-expansion/SCENARIO.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-24-gflow-studio-scaffold/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-24-rest-api-layer/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-06-26-mcp-e2e-test/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/plans/2026-07-09-camoufox-adoption/PLAN.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/research/2026-06-27-tool-abstraction-evaluation.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/specs/2026-06-29-issue-assessment-workflow-design.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/docs/superpowers/spikes/2026-07-09-camoufox-waf-403.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/examples/README.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/examples/batch_from_config.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/examples/multi_prompt_t2i.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/examples/sample_config.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/examples/sample_prompts.txt +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/examples/single_image_t2i.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/examples/workflow_chain.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/llms.txt +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/README.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/01_upload_image.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/02_batchAsyncGenerateVideoText.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/03_batchCheckAsyncVideoGenerationStatus.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/04_archive_workflow.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/05_createProject.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/06_batchGenerateImages.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/07_batchGenerateImages_seeded.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/08_batchAsyncGenerateVideoStartAndEndImage.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/09_batchAsyncGenerateVideoReferenceImages.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/10_batchCheckAsyncVideoGenerationStatus_successful.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/11_batchCheckAsyncVideoGenerationStatus_failed.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/12_create_scene.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/13_sceneWorkflows_update.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/14_get_scene_workflows.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/samples/captured/15_commit_flowWorkflow.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/ci/check_doc_links.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/ci/check_materiality.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/ci/check_repo_hygiene.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/debug_editor.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/debug_gen_settings.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/debug_settings.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/_recording_client.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/_spike_common.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/active_plan.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/analyze_agent_ui_capture.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/capture_i2v_frame_slots_dom.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/capture_i2v_intercept_submit.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/capture_i2v_model_select_repro.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/capture_i2v_post_bind_state.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/capture_image_add_media_dom.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/capture_locale_invariants.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/cdp_drive_and_probe.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/character_create_spike.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/character_create_spike_v2.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/dump_character_selectors.js +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/make_project.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/materiality_backtest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/monitor_pr_38.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/patch_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/skillopt/README.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/skillopt/harness.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/skillopt/tasks.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_char_editor_dom.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_char_gen_capture.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_image_upscale_capture.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_image_upscale_drive.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_image_upscale_recaptcha_action.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_image_upscale_rest_probe.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_issue170_picker_locale_recon.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_issue174_library_ui_recon.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_movie_attach_payload.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_movie_entity_recon.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_movie_gen_capture.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_movie_picker_select.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_movie_voice_list.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_patch_entity.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/dev/spike_patchright.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/diag/README.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/diag/capture_flow_traffic.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/diag/memory_profile.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/diag/recaptcha_mint.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/e2e/agentic_image_e2e.ps1 +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/e2e/capture_agent_toggle.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/record_demo.ps1 +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/smoke_image.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/smoke_real_chrome_image.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/smoke_video_editor.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/smoke_worker_style.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/spike_waf_camoufox.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/scripts/verify_chrome_auth_viability.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/README.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/gflow-cli/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/issue-assessment/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/issue-resolve/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/plan/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/pr-council-review/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/predict/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/scenario/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/skills/status/SKILL.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/sonar-project.properties +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/__main__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/_cli_helpers.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/_engine.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/_retry.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/_sapisidhash.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/client.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/dto.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/image.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/image_upscale.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/recaptcha.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/routes.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/scene.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/_common.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/_fingerprint.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/base.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/drivers/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/drivers/base.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/drivers/classic.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/drivers/factory.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/experimental/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/experimental/bearer.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/experimental/evaluate_fetch.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/experimental/sapisidhash.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/transports/ui_automation.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/api/video.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/base.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/cookies.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/factory.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/internal_chromium.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/real_chrome.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/strategies.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/auth/verification.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/browser_manager.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/chain.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/chain_manifest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_data.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_instructions.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_models.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_movie.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_run.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_scene.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_tools.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/cli_video.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/composition.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/config.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/chain_repo.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0001_initial.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0002_add_cloud_storage.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0003_add_scene_tables.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0004_add_scene_output_path.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0005_add_chain_links.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0006_add_operations_metadata.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0007_queue.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/0008_add_operation_expanded_prompt.sql +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/migrations/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/models.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/queries.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/redaction.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/repository.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/data/store.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/exceptions.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/json_output.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/manifest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/mcp/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/mcp/prompts.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/mcp/resources.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/mcp/server.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/mcp/tools.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/media.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/movie_manifest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/observability.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/paths.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/profile_store.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/services/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/services/character_create.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/storage.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/banned.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/builtin/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/builtin/creative-director.toml +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/expander.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/invocation.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/loader.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/registry.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/runtime.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/tools/spec.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/ui/app.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/ui/server.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/src/gflow_cli/worker/queue.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tasks/lessons.md +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/test_assets/sample_batch.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/test_assets/sample_batch.tsv +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/test_assets/sample_batch_invalid.tsv +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/fixtures/character_gen_response.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/fixtures/patch_entity_response.json +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_aisandbox_auth_error.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_aisandbox_auth_headers.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_bearer_redaction.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_delete_characters.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_generate_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_image.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_launch_kwargs.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_patch_entity.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_scene.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_client_upscale.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_concurrency.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_dto.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_engine.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_image.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_image_dto.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_image_upscale.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_post_json_aisandbox_auth.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_recaptcha.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_retry.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_routes.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_routes_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_routes_scene.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_sapisidhash_helper.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_scene_models.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_video.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/test_video_request.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/conftest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/drivers/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/drivers/test_factory.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_base.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_bearer.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_common.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_evaluate_fetch.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_factory.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_fingerprint.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_sapisidhash.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_selector_symmetry.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_transport_timeout.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_ui_automation.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_ui_automation_batch.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_ui_automation_image_mode.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/api/transports/test_ui_character_editor.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/auth/strategies/test_factory.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/auth/strategies/test_strategies.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/auth/test_cookies.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/auth/test_verification.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_auth_list.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_character_create.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_data.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_image_seed_removed.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_image_selector_drift.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_image_upscale.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_instructions.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_models.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_movie.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_run.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_scene.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_tools.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_video.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_cli_video_chain.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_error_handling.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_helpers.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_movie_manifest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_movie_manifest_style_variants.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_settings_validation.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/cli/test_t2i_multi_prompt.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/composition/test_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/composition/test_compose_prompt.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/composition/test_handoff.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/composition/test_style_variants.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/composition/test_style_variants_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/conftest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_chain_repo.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_find_incomplete_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_models.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_packaging.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_recorder_character.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_redaction.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_repository.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_scene_persistence.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_settings_and_errors.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/data/test_store_migrations.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/dev/test_recording_client.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/conftest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_aisandbox_auth_live.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_auth_verification_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_chain_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_character_create_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_daemon_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_data_layer_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_i2v_flags_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_image_batch_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_image_i2i_ref_cap_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_json_output_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_live_agentic_instructions.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_locale_selectors_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_scene_compose_live.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_tools_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_transports_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_video_r2v_ref_cap_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/e2e/test_video_t2v_e2e.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/auth.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/auth_login.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/character_create.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/character_read.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/conftest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/image.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/image_upscale.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/instructions.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/locale_picker_include.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_auth_login_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_auth_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_character_create_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_character_read_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_image_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_image_upscale_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_instructions_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_locale_picker_include_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_step_collision_guard.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/test_video_chain_steps.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/video_agent_ui.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/features/video_chain.feature +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/fixtures/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/fixtures/seeded_catalog.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/image_batch/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/integration/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/integration/conftest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/integration/constants.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/integration/test_storage_gcs.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/integration/test_storage_s3.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/conftest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/test_cli_parity.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/test_server.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/test_stdio_transport.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/test_tools_helpers.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/test_tools_instructions.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/mcp/test_tools_wired.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/scripts/test_analyze_agent_ui_capture.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/scripts/test_capture_locale_invariants.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/scripts/test_check_materiality.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/scripts/test_check_repo_hygiene.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/scripts/test_materiality_backtest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/services/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/services/test_character_create_redaction.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/services/test_character_create_saga.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/services/test_character_gen_no_direct_post.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/smoke/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/smoke/test_profile_account_smoke.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/smoke/test_real_flow.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_auth.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_browser_manager.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_chain.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_chain_manifest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_cli_data.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_config.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_conftest_isolation.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_data_queries.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_documentation_gate.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_errors_403.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_json_output.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_manifest.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_marker_registry.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_media.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_observability.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_paths.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_profile_store.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/test_smoke.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/__init__.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/test_banned.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/test_expander.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/test_invocation.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/test_loader.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/test_registry.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/test_runtime.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/tools/test_spec.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/tests/ui/test_app.py +0 -0
- {gflow_cli-0.30.0 → gflow_cli-0.31.0}/uv.lock +0 -0
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.31.0] — 2026-07-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Agentic image generation no longer risks attributing a pre-existing project asset to the current request.** `await_images` settled its new-media baseline with a single DOM scrape, which could miss a lazily-rendered pre-existing tile and let `_build_generated_images` slice an arbitrary UUID out of an unordered set as "the" generated image — the 2026-07-10 production incident: an old project logo was silently downloaded and reported as a fresh generation. The baseline is now the **union of two `_scrape_img_srcs` passes** one poll interval apart, absorbing lazy-render stragglers before they're mistaken for new media, and if more new UUIDs still appear than were requested, `await_images` now fails fast with the new `MediaAttributionError` (**exit code 26**, RFC 9457 type `media-attribution`) naming every candidate UUID and the expected count instead of guessing (#281).
|
|
15
|
+
- **A pre-download attribution guard now runs ahead of every image download, with `DataIntegrityError` escalated instead of warned.** Even a driver that never hits the agentic ambiguity check above can still hand back a `flow_media_id` already recorded for the profile. `OperationRecorder.verify_media_attribution()` (called from `cli_image.py`, `image_batch.py`'s manifest batch path, and `FlowWorker.process_task`) checks `is_media_recorded()` before `_download_images` and raises `MediaAttributionError` rather than downloading — it also now rejects an intra-batch duplicate `flow_media_id` (the classic transport can return the same media twice in one submission) with no DB lookup needed. Separately, a `DataIntegrityError` from the recorder's `UNIQUE(profile_name, flow_media_id)` constraint — previously caught by the generic `DataStoreError` path and reported as a warn-only "Generated media was saved, but local history was not updated." — now escalates to the same `MediaAttributionError` when (and only when) the failing write is that exact constraint (`route == "data.upsert_asset"`); any other `DataIntegrityError` (e.g. an unrelated `insert_operation`/`link_operation_asset` failure) falls through to the ordinary warn-and-continue path instead of being mislabeled as a media collision. The escalation message now names the full candidate set of `flow_media_id`s / saved paths ("one of ...") rather than always fingering the first image, since the colliding index can't be recovered from sqlite's bare constraint violation (#281).
|
|
16
|
+
- **The pre-download attribution guard and the collision-escalation logic are each now a single shared implementation instead of three near-identical copies.** Both originally shipped duplicated across `cli_image.py`, `image_batch.py`, and `worker/daemon.py` (each call site already held a recorder instance), which tripped SonarCloud's duplicated-lines gate. The guard is consolidated onto `OperationRecorder.verify_media_attribution()`; the escalation is consolidated onto the new `gflow_cli.data.recorder.escalate_asset_collision()` — both with no behavior change beyond the route-scoping and intra-batch-duplicate fixes above (#281, #282, #283).
|
|
17
|
+
- **`gflow image batch` (the manifest path) now honors `--continue-on-error` for a media-attribution collision.** A collision on one row — from either the pre-download guard or the post-download collision escalation — used to propagate out of the whole batch run regardless of `--continue-on-error`, discarding every other row's already-completed outcome. With `--continue-on-error` the colliding row is now marked as a normal "fail" outcome (nothing was downloaded/recorded for that row) and the batch continues; without it, the collision still aborts the run as before (#281, #282).
|
|
18
|
+
- **`--ref <uuid>` picker selection now resolves every ref, not just the first.** `_select_existing_asset` gave up as soon as a tile wasn't in the initial viewport or surfaced by the display-name search, so any `--ref` after the first raised `TransportTimeoutError` once the virtualised (react-virtuoso) media grid hadn't rendered it yet. It now falls back to scrolling and re-checking between scrolls, mirroring the entity picker's `_find_picker_entity_tile` strategy, and `_attach_image_uuid_refs` clears the picker search input at the start of every ref iteration so a search typed while resolving one ref can't shadow the next ref's tile lookup. `_select_existing_asset` itself now also clears a failed display-name search before falling back to scrolling, so it doesn't scroll a grid still filtered by the failed term (#282).
|
|
19
|
+
|
|
10
20
|
## [0.30.0] — 2026-07-09
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -663,6 +663,108 @@ key — surfaces as a `RuntimeError` that `auth/cookies.py` normalizes to
|
|
|
663
663
|
|
|
664
664
|
## Resolved
|
|
665
665
|
|
|
666
|
+
### Agentic image generation could silently attribute a pre-existing project asset as the "generated" image
|
|
667
|
+
|
|
668
|
+
- **Status:** Resolved · **Severity:** Was-High (wrong file downloaded and reported as success, silently) · **Was-affecting:** the agentic driver through v0.30.0 (and, for the pre-download guard below, every transport) · **Fixed in:** unreleased (0.31.0) · **Tracked:** [#281](https://github.com/ffroliva/gflow-cli/issues/281); related picker fix [#282](https://github.com/ffroliva/gflow-cli/issues/282)
|
|
669
|
+
|
|
670
|
+
Discovered 2026-07-10 in a live production run: `gflow image t2i` under the
|
|
671
|
+
agentic driver downloaded an **old project logo** and reported it as a freshly
|
|
672
|
+
generated "portrait" — no error, just the pre-existing warn-only line
|
|
673
|
+
`"Generated media was saved, but local history was not updated."` — and 11
|
|
674
|
+
downstream `i2i` scenes were then anchored to the wrong file.
|
|
675
|
+
|
|
676
|
+
**Root cause — two defects in `await_images` (agentic driver):**
|
|
677
|
+
1. The new-media baseline was a **single** DOM scrape (`_scrape_img_srcs`); a
|
|
678
|
+
pre-existing tile that rendered lazily (after generation had already
|
|
679
|
+
started) was missed by that scrape and then counted as "new" once it
|
|
680
|
+
appeared.
|
|
681
|
+
2. `_build_generated_images` sliced an **unordered** UUID set down to
|
|
682
|
+
`expected_count` — an arbitrary pick when more "new" UUIDs were present
|
|
683
|
+
than were actually requested, with no signal for which UUID(s) belonged to
|
|
684
|
+
this generation.
|
|
685
|
+
|
|
686
|
+
**A third, independent gap** existed downstream of the driver: even a transport
|
|
687
|
+
that never hits the agentic DOM-scrape path above could still hand back a
|
|
688
|
+
`flow_media_id` already present in local history for the profile. That case
|
|
689
|
+
was caught by the generic `DataStoreError` path, which only warned and
|
|
690
|
+
continued — a silently duplicated/misattributed history row, not a hard
|
|
691
|
+
failure.
|
|
692
|
+
|
|
693
|
+
**Fix — three defense layers, all raising the new `MediaAttributionError`
|
|
694
|
+
(exit code 26, RFC 9457 type `media-attribution`) instead of a silent or
|
|
695
|
+
warn-only outcome:**
|
|
696
|
+
1. **Baseline settle.** The baseline is now the **union of two
|
|
697
|
+
`_scrape_img_srcs` passes** one poll interval apart, absorbing lazy-render
|
|
698
|
+
stragglers before they can be mistaken for new media.
|
|
699
|
+
2. **Ambiguity fail-fast.** If more new UUIDs still appear than were
|
|
700
|
+
requested, `await_images` raises `MediaAttributionError` naming every
|
|
701
|
+
candidate UUID and the expected count, rather than guessing via
|
|
702
|
+
`_build_generated_images`'s old arbitrary slice.
|
|
703
|
+
3. **Pre-download attribution guard + collision escalation.**
|
|
704
|
+
`OperationRecorder.verify_media_attribution()` (called from `cli_image.py`,
|
|
705
|
+
`image_batch.py`'s manifest batch path, and the worker daemon's
|
|
706
|
+
`FlowWorker.process_task`; consolidated onto the recorder in #283 after
|
|
707
|
+
shipping as three near-identical module-level copies) checks
|
|
708
|
+
`is_media_recorded()` and raises before any download if the driver
|
|
709
|
+
returned a `flow_media_id` already recorded for the profile.
|
|
710
|
+
Separately, a `DataIntegrityError` from the recorder's
|
|
711
|
+
`UNIQUE(profile_name, flow_media_id)` constraint now escalates to
|
|
712
|
+
`MediaAttributionError` (naming the suspect file) instead of being caught
|
|
713
|
+
by the generic `DataStoreError` warn-only path.
|
|
714
|
+
|
|
715
|
+
Precedent for this class of fix: the `--model` silent no-op entry below (same
|
|
716
|
+
section) — an error the user sees beats a wrong artifact reported as success.
|
|
717
|
+
|
|
718
|
+
**Related, but distinct.** [#282](https://github.com/ffroliva/gflow-cli/issues/282)
|
|
719
|
+
fixed a separate defect in the same media-picker surface (`--ref <uuid>`
|
|
720
|
+
selection failing on any ref after the first once the virtualised
|
|
721
|
+
(react-virtuoso) grid needed to scroll to render it) — a picker-navigation
|
|
722
|
+
bug, not a data-integrity one. [#174](https://github.com/ffroliva/gflow-cli/issues/174)
|
|
723
|
+
(open, in the "Open" section above) is a Flow-side full-page media-library
|
|
724
|
+
UI rollout that breaks entity attach on affected accounts — a different
|
|
725
|
+
code path, but the same general theme that the media-picker surface needs
|
|
726
|
+
ongoing hardening.
|
|
727
|
+
|
|
728
|
+
**Residual risk (post-fix, not fully closed).** The two-pass baseline settle
|
|
729
|
+
narrows the window, it doesn't remove it: the agentic poll loop still stops
|
|
730
|
+
scraping the moment the new-UUID count on a single scrape equals the
|
|
731
|
+
requested count. A pre-existing tile that lazy-renders *after* the 0.5s
|
|
732
|
+
baseline settle but *before* the real generation actually completes
|
|
733
|
+
(generations take 30–60s) lands inside that window at exactly the expected
|
|
734
|
+
count — the ambiguity fail-fast in layer 2 only triggers on an *excess* of
|
|
735
|
+
new UUIDs, so it never sees this case. Layer 3's pre-download guard and
|
|
736
|
+
collision escalation then only catch it if the misattributed asset happens
|
|
737
|
+
to already be in **local** history for the profile — an asset created in the
|
|
738
|
+
Flow web UI, generated on another machine, or recorded under a different
|
|
739
|
+
profile DB has no local history row to collide with, so it can still slip
|
|
740
|
+
through all three layers: wrong file on disk, exit 0. Mitigations until this
|
|
741
|
+
is closed: run generations in a dedicated, low-asset project (fewer
|
|
742
|
+
pre-existing tiles means a smaller lazy-render population to collide with),
|
|
743
|
+
and visually verify anchor/canonical images before referencing them
|
|
744
|
+
downstream in `i2i`. Follow-up hardening (a break condition stable across
|
|
745
|
+
two consecutive scrapes, not one) is tracked upstream against this issue.
|
|
746
|
+
|
|
747
|
+
**Known gap: the shell multi-prompt path records no history at all.**
|
|
748
|
+
`gflow run --config <file>` and `gflow image t2i` with multiple prompts
|
|
749
|
+
(positional, `--prompts-file`, or `--stdin`) both go through
|
|
750
|
+
`image_batch.run_image_batch` / `run_one_image_prompt`, which never opens an
|
|
751
|
+
`OperationRecorder` or calls `record_generated_images` — no local history row
|
|
752
|
+
is written for these runs at all. Layer 1 (the agentic driver's two-pass
|
|
753
|
+
baseline settle + ambiguity fail-fast in `await_images`) still applies since
|
|
754
|
+
it lives in the transport, independent of recording. Layers 2 and 3
|
|
755
|
+
(`verify_media_attribution`'s pre-download guard and the post-download
|
|
756
|
+
collision escalation) both depend on local history via
|
|
757
|
+
`OperationRecorder.is_media_recorded()` / `record_generated_images()`, so
|
|
758
|
+
neither guard exists on this path — this is a pre-existing gap, not a
|
|
759
|
+
regression from this fix. `gflow image batch` (the manifest path, via
|
|
760
|
+
`run_manifest_image_batch`) already threads a recorder through and is covered
|
|
761
|
+
by all three layers. Follow-up tracked in #283.
|
|
762
|
+
|
|
763
|
+
**Workaround (pre-fix):** none — on an affected version, manually verify the
|
|
764
|
+
downloaded file matches the prompt before trusting a `t2i`/`i2i` result.
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
666
768
|
### Profile named `default` is opaque — no Google account identity
|
|
667
769
|
|
|
668
770
|
- **Status:** Resolved · **Severity:** Was-Low (UX confusion, no data loss) · **Was-affecting:** all versions through v0.9.x · **Fixed in:** v0.10.0 via PR #110 (2026-05-28) · **Tracked:** issue #92
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gflow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.0
|
|
4
4
|
Summary: Unofficial CLI for Google Flow — drive Veo image-to-video generations from the terminal.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ffroliva/gflow-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/ffroliva/gflow-cli/issues
|
|
@@ -122,7 +122,7 @@ Slash commands for Claude Code, stored in `.claude/commands/gflow/`. All prefixe
|
|
|
122
122
|
**"A gflow command hangs / fails — where do I start?"** → [DEBUGGING § Quick reference](DEBUGGING.md#quick-reference)
|
|
123
123
|
**"Flow's UI broke a selector — how do I diagnose it?"** → [DEBUGGING § Inspecting Flow's live UI](DEBUGGING.md#inspecting-flows-live-ui)
|
|
124
124
|
**"What does each `ui_automation.*` log event mean?"** → [DEBUGGING § Listener & HTTP-layer debugging](DEBUGGING.md#listener--http-layer-debugging)
|
|
125
|
-
**"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.30.0](LIVE_VERIFICATION_v0.30.0.md) (**agentic-cohort image path + both fixes live-verified credit-free** — native 768×1376 stills, i2i-chain + canonical-ref identity hold; the character-create `DataIntegrityError` and the `prefer_classic` WARNING were observed live then fixed [recorder idempotent + INFO log]; MCP video-param feature deferred-with-reason on Veo credits, per the stills-only goal). Prior: [v0.29.0](LIVE_VERIFICATION_v0.29.0.md) (**persistent `gflow instructions` CRUD proven live e2e GREEN, credit-free** — 11-command sequence on a fresh project: add incl. `--ref <image-UUID>` attach, list/enable/disable, master toggle both ways, TOML `apply` destructive full-sync with `enabled=false` honored, `rm` with server card-id preserved across mutations; movie brief-sync code-path-verified via the same primitives, deferred-with-reason on credits; MCP `gflow_instructions_*` tools are thin adapters over the live-verified primitives). Prior: [v0.28.0](LIVE_VERIFICATION_v0.28.0.md) (**agent instructions `-i` now steer agentic generation — crayon e2e GREEN**: a style-neutral prompt + an enabled "crayon" card produced an unmistakable crayon drawing through the real transport; root causes were conversational phrasing + the `project_brief.enabled` master switch). Prior: [v0.27.1](LIVE_VERIFICATION_v0.27.1.md) (**v0.27.0 release follow-up fixes and documentation sync**). Prior: [v0.27.0](LIVE_VERIFICATION_v0.27.0.md) (**`[style]` block with named variants + prompt-aware resume — credit-free CLI verification**: real `movie run --dry-run` 3-way ledger proving resolved-style display, `skip (done)` on matching `style_hash`, and `re-run (style changed)` after a style edit; composition is local, wire path untouched, so no Veo credits spent — first credited styled run will confirm visually). Prior: [v0.26.0](LIVE_VERIFICATION_v0.26.0.md) (**image i2i by generated-image UUID proven live e2e GREEN** — the existing asset is *selected in place* by its media UUID, no duplicate upload, producing an i2i output that references it; plus generated-image `display_name` capture, credited @C1ph3r404). Prior: [v0.25.0](LIVE_VERIFICATION_v0.25.0.md) (**#237 remote-UUID i2v proven live e2e GREEN** — a real 8s 720×1280 interpolation from a generated image's UUID, after the picker-search attach was reworked to local upload; #240 home-`.env` matrix live; plus two silent-failure guards root-caused live: video-as-image download rejection and rejected-upload fail-loud). Prior: [v0.24.0](LIVE_VERIFICATION_v0.24.0.md) (--project parity across CLI + MCP — automated coverage; live gen auth-gated). Prior: [v0.23.0](LIVE_VERIFICATION_v0.23.0.md) (MCP generation wiring proven live end-to-end — tool→worker→real Flow REST→structured-error translation; final image-write blocked by an expired session, environmental. #222 macOS fix reporter-verified e2e on Apple Silicon). Prior: [v0.22.0](LIVE_VERIFICATION_v0.22.0.md) (Tools framework — automated/CI coverage complete; credit/key-gated live feature run is a pending owner gate, see the doc) · [v0.21.0](LIVE_VERIFICATION_v0.21.0.md) (MCP server over stdio + HTTP/SSE — credit-free protocol handshake; 4 tools / 2 prompts / 3 resources confirmed on stdout) · [v0.20.1](LIVE_VERIFICATION_v0.20.1.md) · [v0.20.0](LIVE_VERIFICATION_v0.20.0.md) · [v0.19.0](LIVE_VERIFICATION_v0.19.0.md) · [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md)
|
|
125
|
+
**"What was actually live-verified for the latest release?"** → latest: [LIVE_VERIFICATION_v0.31.0](LIVE_VERIFICATION_v0.31.0.md) (**#281 wrong-media defenses live-verified in BOTH directions, credit-free** — correct attribution downloads with real wire metadata [seed, 768×1376]; the incident-class lazy-render ambiguity reproduced live and hard-stopped with `MediaAttributionError` exit 26 naming every candidate UUID, no file written; **#282 two-UUID-ref i2i proven e2e** — both freshly generated portrait UUIDs picker-selected into one scene with both canonical identities visually transferred). Prior: [v0.30.0](LIVE_VERIFICATION_v0.30.0.md) (**agentic-cohort image path + both fixes live-verified credit-free** — native 768×1376 stills, i2i-chain + canonical-ref identity hold; the character-create `DataIntegrityError` and the `prefer_classic` WARNING were observed live then fixed [recorder idempotent + INFO log]; MCP video-param feature deferred-with-reason on Veo credits, per the stills-only goal). Prior: [v0.29.0](LIVE_VERIFICATION_v0.29.0.md) (**persistent `gflow instructions` CRUD proven live e2e GREEN, credit-free** — 11-command sequence on a fresh project: add incl. `--ref <image-UUID>` attach, list/enable/disable, master toggle both ways, TOML `apply` destructive full-sync with `enabled=false` honored, `rm` with server card-id preserved across mutations; movie brief-sync code-path-verified via the same primitives, deferred-with-reason on credits; MCP `gflow_instructions_*` tools are thin adapters over the live-verified primitives). Prior: [v0.28.0](LIVE_VERIFICATION_v0.28.0.md) (**agent instructions `-i` now steer agentic generation — crayon e2e GREEN**: a style-neutral prompt + an enabled "crayon" card produced an unmistakable crayon drawing through the real transport; root causes were conversational phrasing + the `project_brief.enabled` master switch). Prior: [v0.27.1](LIVE_VERIFICATION_v0.27.1.md) (**v0.27.0 release follow-up fixes and documentation sync**). Prior: [v0.27.0](LIVE_VERIFICATION_v0.27.0.md) (**`[style]` block with named variants + prompt-aware resume — credit-free CLI verification**: real `movie run --dry-run` 3-way ledger proving resolved-style display, `skip (done)` on matching `style_hash`, and `re-run (style changed)` after a style edit; composition is local, wire path untouched, so no Veo credits spent — first credited styled run will confirm visually). Prior: [v0.26.0](LIVE_VERIFICATION_v0.26.0.md) (**image i2i by generated-image UUID proven live e2e GREEN** — the existing asset is *selected in place* by its media UUID, no duplicate upload, producing an i2i output that references it; plus generated-image `display_name` capture, credited @C1ph3r404). Prior: [v0.25.0](LIVE_VERIFICATION_v0.25.0.md) (**#237 remote-UUID i2v proven live e2e GREEN** — a real 8s 720×1280 interpolation from a generated image's UUID, after the picker-search attach was reworked to local upload; #240 home-`.env` matrix live; plus two silent-failure guards root-caused live: video-as-image download rejection and rejected-upload fail-loud). Prior: [v0.24.0](LIVE_VERIFICATION_v0.24.0.md) (--project parity across CLI + MCP — automated coverage; live gen auth-gated). Prior: [v0.23.0](LIVE_VERIFICATION_v0.23.0.md) (MCP generation wiring proven live end-to-end — tool→worker→real Flow REST→structured-error translation; final image-write blocked by an expired session, environmental. #222 macOS fix reporter-verified e2e on Apple Silicon). Prior: [v0.22.0](LIVE_VERIFICATION_v0.22.0.md) (Tools framework — automated/CI coverage complete; credit/key-gated live feature run is a pending owner gate, see the doc) · [v0.21.0](LIVE_VERIFICATION_v0.21.0.md) (MCP server over stdio + HTTP/SSE — credit-free protocol handshake; 4 tools / 2 prompts / 3 resources confirmed on stdout) · [v0.20.1](LIVE_VERIFICATION_v0.20.1.md) · [v0.20.0](LIVE_VERIFICATION_v0.20.0.md) · [v0.19.0](LIVE_VERIFICATION_v0.19.0.md) · [v0.18.0](LIVE_VERIFICATION_v0.18.0.md) · [v0.17.0](LIVE_VERIFICATION_v0.17.0.md) · [v0.16.0](LIVE_VERIFICATION_v0.16.0.md) · [v0.13.0](LIVE_VERIFICATION_v0.13.0.md) · [v0.12.0](LIVE_VERIFICATION_v0.12.0.md) · [v0.11.0](LIVE_VERIFICATION_v0.11.0.md) · [v0.10.0](LIVE_VERIFICATION_v0.10.0.md) · [v0.9.1](LIVE_VERIFICATION_v0.9.1.md) · [v0.9.0](LIVE_VERIFICATION_v0.9.0.md) · [v0.8.1](LIVE_VERIFICATION_v0.8.1.md) · [v0.7.0](LIVE_VERIFICATION_v0.7.0.md)
|
|
126
126
|
**"Where is the reverse-engineered wire protocol for a feature?"** → the `*_RECON.md` design docs: [CHARACTER_RECON](CHARACTER_RECON.md) (Flow character entity protocol), [IMAGE_UPSCALE_RECON](IMAGE_UPSCALE_RECON.md) (`/v1/flow/upsampleImage` wire). Naming convention: one `<FEATURE>_RECON.md` per reverse-engineered surface, kept as the durable spec after the feature ships.
|
|
127
127
|
**"What was live-verified for the data layer (PR #58)?"** → [LIVE_VERIFICATION_data_layer](LIVE_VERIFICATION_data_layer.md) — 1 Imagen + 1 Veo credit on denon82, 6-layer ledger (file + magic + Pillow + DB rows + CLI round-trip + structlog)
|
|
128
128
|
**"What was live-verified for the video-download feature (#29)?"** → [LIVE_VERIFICATION_video_download](LIVE_VERIFICATION_video_download.md)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Live Verification — v0.31.0
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-10 · **Profile:** `denon82` · **Project:** `f6caf027-ad68-49e3-aac9-ee32f1582bf3`
|
|
4
|
+
(the standing production project — deliberately asset-heavy, i.e. the exact environment of the
|
|
5
|
+
2026-07-10 wrong-media incident this release fixes) · **Build:** develop @ `ecfb5ed`
|
|
6
|
+
(merge of PR #285) run via `uv run gflow` · **Model:** nano-pro · **Aspect:** 9:16 ·
|
|
7
|
+
**Credit cost:** 0 Veo credits (image generation only).
|
|
8
|
+
|
|
9
|
+
Features under verification (CHANGELOG `[Unreleased]` → 0.31.0):
|
|
10
|
+
1. **#281 layer 1** — agentic ambiguity fail-fast + two-pass baseline settle (`MediaAttributionError`, exit 26)
|
|
11
|
+
2. **#281 layers 2/3** — pre-download attribution guard + route-scoped collision escalation
|
|
12
|
+
3. **#282** — UUID `--ref` picker: virtualised-grid scroll + search-state hygiene
|
|
13
|
+
|
|
14
|
+
## Run 1 — t2i portrait (musician): correct attribution end-to-end ✅
|
|
15
|
+
|
|
16
|
+
`gflow image t2i "<musician portrait prompt>" --model nano-pro --aspect 9:16 --project f6caf027-… --out <dir>`
|
|
17
|
+
|
|
18
|
+
5-layer ledger:
|
|
19
|
+
1. **File count:** exactly 1 file produced: `98a404e7-766d-443c-9385-a16fdb37c799_1.jpg` (886,886 bytes; sha256 `e25e96be454f0215…`).
|
|
20
|
+
2. **Magic bytes:** `ff d8 ff` (JPEG).
|
|
21
|
+
3. **Dimensions:** 768×1376 (native 9:16, no crop).
|
|
22
|
+
4. **Structlog invariants:** real wire metadata surfaced — `seed=648895`, `dimensions=768x1376`
|
|
23
|
+
(not the agentic scrape sentinels seed=0/0x0); **no** `data.persistence_failed_after_success`
|
|
24
|
+
warning (the 0.30.0 incident's tell); recorder wrote history cleanly.
|
|
25
|
+
5. **User-confirmable artifact:** image visually inspected — weathered street musician, late 50s,
|
|
26
|
+
grey beard, black beanie, worn dark jacket, guitar, plain neutral background, front view.
|
|
27
|
+
Exactly the prompt; NOT a pre-existing project asset. (Under 0.30.0 in this same project, this
|
|
28
|
+
exact call once returned an old brand logo.)
|
|
29
|
+
|
|
30
|
+
## Run 2 — t2i portrait (mentor), attempt 1: ambiguity fail-fast fires ✅ (negative-path proof)
|
|
31
|
+
|
|
32
|
+
Same command shape, mentor prompt. The generation hit the incident-class condition live: the
|
|
33
|
+
asset-heavy project lazy-rendered multiple prior assets after the baseline settle, and the run
|
|
34
|
+
**failed loudly instead of guessing**:
|
|
35
|
+
|
|
36
|
+
- `MediaAttributionError` raised naming **every** candidate UUID (the list included
|
|
37
|
+
`6a0b44d5-…` and `64fc2c31-…` — assets generated earlier that day, i.e. genuinely pre-existing
|
|
38
|
+
media that 0.30.0 would have been free to silently download) plus the remediation text
|
|
39
|
+
("Re-run the generation; a dedicated project with fewer pre-existing assets avoids lazy-render
|
|
40
|
+
ambiguity (issue #281)").
|
|
41
|
+
- **No file was written** for this attempt (output dir unchanged).
|
|
42
|
+
|
|
43
|
+
This is the strongest possible live evidence for #281: the exact failure environment reproduced,
|
|
44
|
+
and the new behavior is a actionable hard stop, not a wrong artifact with exit 0.
|
|
45
|
+
|
|
46
|
+
## Run 2 — t2i portrait (mentor), attempt 2: clean ✅
|
|
47
|
+
|
|
48
|
+
Retry of the same command: 1 file `dacdc6ee-2725-4e5c-b08f-c7143c36d9b6_1.jpg` (591,180 bytes;
|
|
49
|
+
sha256 `e82cc96b3bba60cf…`; JPEG magic `ff d8 ff`; 768×1376; real `seed=744814`; no persistence
|
|
50
|
+
warning). Visually inspected: elegant silver-haired man, 60s, tailored black suit, pocket square,
|
|
51
|
+
neutral background — exactly the prompt.
|
|
52
|
+
|
|
53
|
+
## Run 3 — i2i with two UUID `--ref`s (#282 picker) ✅
|
|
54
|
+
|
|
55
|
+
`gflow image i2i "<street scene prompt>" --ref dacdc6ee-… --ref 98a404e7-… --model nano-pro
|
|
56
|
+
--aspect 9:16 --project f6caf027-… --out <dir>` — both refs are the freshly generated portrait
|
|
57
|
+
UUIDs from Runs 1–2, attached by picker selection (no local file passed). Under v0.30.0 this exact
|
|
58
|
+
call failed structurally: only the first UUID was picker-selectable (#282).
|
|
59
|
+
|
|
60
|
+
Two attempts noted for completeness: attempt 1 failed pre-submission with
|
|
61
|
+
`UiSelectorDriftError probe=mode_switch_trigger` whose debug screenshot was an entirely black
|
|
62
|
+
viewport — a page-that-never-rendered transient, not selector drift; zero generations spent.
|
|
63
|
+
Attempt 2 succeeded.
|
|
64
|
+
|
|
65
|
+
5-layer ledger:
|
|
66
|
+
1. **File count:** exactly 1 file: `d6f1927a-3eae-4626-bc90-9a6ea7637bab_1.jpg` (670,708 bytes;
|
|
67
|
+
sha256 `fd828df1a33e37c0…`).
|
|
68
|
+
2. **Magic bytes:** `ff d8 ff` (JPEG).
|
|
69
|
+
3. **Dimensions:** 768×1376 (native 9:16).
|
|
70
|
+
4. **Structlog invariants:** real `seed=244712`; both refs attached (no
|
|
71
|
+
"could not be selected in the picker" error, no upload fallback); no persistence warning.
|
|
72
|
+
5. **User-confirmable artifact:** visually inspected — BOTH canonical identities transferred:
|
|
73
|
+
the mentor is the same silver-haired man from Run 2's portrait (same face, suit, and paisley
|
|
74
|
+
pocket square), the musician the same man from Run 1's (same beanie, beard, work jacket).
|
|
75
|
+
Monochrome street scene with the iridescent coin accent confined to the guitar case. No
|
|
76
|
+
pre-existing project asset leaked into the frame.
|
|
77
|
+
|
|
78
|
+
## Verdict
|
|
79
|
+
|
|
80
|
+
All three release features live-verified: #281's defense layers in both directions (correct
|
|
81
|
+
attribution succeeds with ground-truth metadata; incident-class ambiguity hard-stops with
|
|
82
|
+
actionable output and no file written), and #282's multi-UUID-ref picker selection end-to-end —
|
|
83
|
+
which simultaneously validates the canonical-reference identity workflow the fixes exist to serve.
|
|
@@ -27,7 +27,12 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
27
27
|
|
|
28
28
|
import structlog
|
|
29
29
|
|
|
30
|
-
from gflow_cli.errors import
|
|
30
|
+
from gflow_cli.errors import (
|
|
31
|
+
ContentPolicyError,
|
|
32
|
+
FlowAgentUiError,
|
|
33
|
+
MediaAttributionError,
|
|
34
|
+
TransportTimeoutError,
|
|
35
|
+
)
|
|
31
36
|
|
|
32
37
|
if TYPE_CHECKING:
|
|
33
38
|
from pathlib import Path
|
|
@@ -462,6 +467,25 @@ class AgenticFlowUiDriver:
|
|
|
462
467
|
default). On partial completion the error detail includes the
|
|
463
468
|
produced-vs-requested mismatch count.
|
|
464
469
|
|
|
470
|
+
**Baseline settle (issue #281):** a single baseline scrape can miss
|
|
471
|
+
pre-existing project tiles that lazily render a moment later — those
|
|
472
|
+
would then be miscounted as "new" media and silently attributed to
|
|
473
|
+
this generation (the 2026-07-10 production incident: a pre-existing
|
|
474
|
+
logo was downloaded and reported as a fresh portrait). The baseline is
|
|
475
|
+
therefore the UNION of two scrapes separated by one
|
|
476
|
+
``_POLL_INTERVAL_S`` sleep, so a lazy tile rendering between the two
|
|
477
|
+
passes still counts as pre-existing, not new. This adds one poll
|
|
478
|
+
interval of wall time up front; the total poll timeout budget
|
|
479
|
+
(``_AWAIT_TIMEOUT_S``) is unchanged.
|
|
480
|
+
|
|
481
|
+
**Ambiguity fail-fast (issue #281):** if more new UUIDs appear than
|
|
482
|
+
were requested, there is no reliable way to tell which ones belong to
|
|
483
|
+
this generation. Rather than arbitrarily slice the unordered set (the
|
|
484
|
+
prior, buggy behaviour), this raises ``MediaAttributionError`` naming
|
|
485
|
+
every candidate UUID and the expected count. The caller should re-run
|
|
486
|
+
the generation — ideally in a dedicated project with fewer
|
|
487
|
+
pre-existing assets, which avoids the ambiguity entirely.
|
|
488
|
+
|
|
465
489
|
**Content-policy fail-fast (conservative):** scans for explicit text
|
|
466
490
|
(``"content policy"`` / ``"can't create"`` / ``"violat"`` / ``"not able
|
|
467
491
|
to generate"``) and the ``warning``/``error``/``block`` Material Symbol
|
|
@@ -469,10 +493,13 @@ class AgenticFlowUiDriver:
|
|
|
469
493
|
Because no positive block-refusal sample was captured, we prefer a false
|
|
470
494
|
*miss* (timeout) over a false *positive* (spurious ContentPolicyError).
|
|
471
495
|
"""
|
|
472
|
-
#
|
|
473
|
-
#
|
|
474
|
-
|
|
475
|
-
|
|
496
|
+
# Baseline settle (issue #281): union two scrapes separated by one
|
|
497
|
+
# poll interval so a lazily-rendered pre-existing tile is captured as
|
|
498
|
+
# baseline, not miscounted as "new" media.
|
|
499
|
+
first_baseline_srcs = await _scrape_img_srcs(page)
|
|
500
|
+
await asyncio.sleep(_POLL_INTERVAL_S)
|
|
501
|
+
second_baseline_srcs = await _scrape_img_srcs(page)
|
|
502
|
+
baseline_uuids = _extract_uuids(first_baseline_srcs) | _extract_uuids(second_baseline_srcs)
|
|
476
503
|
|
|
477
504
|
deadline = asyncio.get_event_loop().time() + _AWAIT_TIMEOUT_S
|
|
478
505
|
new_uuids: set[str] = set()
|
|
@@ -511,6 +538,18 @@ class AgenticFlowUiDriver:
|
|
|
511
538
|
)
|
|
512
539
|
raise TransportTimeoutError(detail=detail, route="agentic:await_images")
|
|
513
540
|
|
|
541
|
+
if len(new_uuids) > expected_count:
|
|
542
|
+
candidates = sorted(new_uuids)
|
|
543
|
+
raise MediaAttributionError(
|
|
544
|
+
detail=(
|
|
545
|
+
f"Cannot attribute the generation among {len(candidates)} candidate "
|
|
546
|
+
f"media UUIDs (expected {expected_count}): {candidates}. Re-run the "
|
|
547
|
+
"generation; a dedicated project with fewer pre-existing assets "
|
|
548
|
+
"avoids lazy-render ambiguity."
|
|
549
|
+
),
|
|
550
|
+
route="agentic:await_images",
|
|
551
|
+
)
|
|
552
|
+
|
|
514
553
|
return _build_generated_images(
|
|
515
554
|
uuids=new_uuids,
|
|
516
555
|
expected_count=expected_count,
|
|
@@ -544,7 +583,23 @@ def _build_generated_images(
|
|
|
544
583
|
- ``dimensions`` — ``(0, 0)`` placeholder (naturalWidth/Height
|
|
545
584
|
are unreliable for tRPC redirect URLs until
|
|
546
585
|
the image has fully decoded; scraping deferred)
|
|
586
|
+
|
|
587
|
+
``uuids`` MUST contain exactly ``expected_count`` entries — the caller
|
|
588
|
+
(``await_images``) raises ``TransportTimeoutError`` for fewer and
|
|
589
|
+
``MediaAttributionError`` for more, so this is only ever reached with an
|
|
590
|
+
unambiguous, exact match (issue #281: a set is unordered and previously
|
|
591
|
+
was silently truncated to ``expected_count``, which could attribute the
|
|
592
|
+
wrong media to the request).
|
|
547
593
|
"""
|
|
594
|
+
if len(uuids) != expected_count:
|
|
595
|
+
msg = (
|
|
596
|
+
f"_build_generated_images invariant violated: got {len(uuids)} uuids, "
|
|
597
|
+
f"expected exactly {expected_count}. Callers must raise "
|
|
598
|
+
"TransportTimeoutError (too few) or MediaAttributionError (too many) "
|
|
599
|
+
"before reaching this helper."
|
|
600
|
+
)
|
|
601
|
+
raise AssertionError(msg)
|
|
602
|
+
|
|
548
603
|
# Late import — avoid module-load-time import of dto.
|
|
549
604
|
from gflow_cli.api.dto import GeneratedImage # noqa: PLC0415
|
|
550
605
|
|
|
@@ -555,9 +610,7 @@ def _build_generated_images(
|
|
|
555
610
|
aspect_wire = _label_to_wire.get(pending_aspect, aspect_wire)
|
|
556
611
|
|
|
557
612
|
images: list[GeneratedImage] = []
|
|
558
|
-
|
|
559
|
-
# caller verified len >= expected_count before calling this helper).
|
|
560
|
-
for uuid in list(uuids)[:expected_count]:
|
|
613
|
+
for uuid in uuids:
|
|
561
614
|
fife_url = _MEDIA_REDIRECT_BASE.format(uuid=uuid)
|
|
562
615
|
images.append(
|
|
563
616
|
GeneratedImage(
|
|
@@ -1502,25 +1502,63 @@ class VideoGenerationMixin:
|
|
|
1502
1502
|
"""In the OPEN reference picker, select the already-existing Flow asset
|
|
1503
1503
|
identified by ``media_id`` (preferred over uploading a duplicate).
|
|
1504
1504
|
|
|
1505
|
-
Locates the tile by the media UUID in its thumbnail URL
|
|
1506
|
-
already visible, searches ``display_name`` to surface it
|
|
1507
|
-
|
|
1508
|
-
|
|
1505
|
+
Locates the tile by the media UUID in its thumbnail URL. If it isn't
|
|
1506
|
+
already visible, searches ``display_name`` to surface it (when given);
|
|
1507
|
+
if it's STILL not visible, scrolls the virtualised picker grid
|
|
1508
|
+
(react-virtuoso renders off-viewport tiles lazily — #282) and
|
|
1509
|
+
re-checks between scrolls, the same strategy `_find_picker_entity_tile`
|
|
1510
|
+
uses for the entity picker. Returns ``True`` once attached, ``False``
|
|
1511
|
+
when the asset can't be located by any of the above (the caller then
|
|
1512
|
+
falls back to a local upload).
|
|
1509
1513
|
"""
|
|
1510
1514
|
tile = VideoGenerationMixin._existing_asset_tile(page, media_id)
|
|
1515
|
+
visible = True
|
|
1511
1516
|
try:
|
|
1512
1517
|
await tile.wait_for(state="visible", timeout=4000)
|
|
1513
1518
|
except Exception: # noqa: BLE001 - not-visible is an expected branch
|
|
1514
|
-
|
|
1515
|
-
|
|
1519
|
+
visible = False
|
|
1520
|
+
|
|
1521
|
+
attempted_search = False
|
|
1522
|
+
if not visible and display_name:
|
|
1516
1523
|
search = page.locator(PICKER_SEARCH_INPUT)
|
|
1517
1524
|
# Human-like typing jitter to dodge WAF heuristics — not security.
|
|
1518
1525
|
await search.press_sequentially(display_name, delay=random.randint(10, 50)) # NOSONAR
|
|
1519
1526
|
await page.wait_for_timeout(800)
|
|
1527
|
+
attempted_search = True
|
|
1520
1528
|
try:
|
|
1521
1529
|
await tile.wait_for(state="visible", timeout=6000)
|
|
1522
|
-
|
|
1523
|
-
|
|
1530
|
+
visible = True
|
|
1531
|
+
except Exception: # noqa: BLE001 - still not found -> try scrolling
|
|
1532
|
+
pass
|
|
1533
|
+
|
|
1534
|
+
if not visible:
|
|
1535
|
+
# A failed display-name search leaves the grid filtered on that
|
|
1536
|
+
# term — scrolling a still-filtered grid would only shuffle
|
|
1537
|
+
# through the (possibly empty) filtered results rather than the
|
|
1538
|
+
# full asset list, so the search box must be cleared first.
|
|
1539
|
+
# Presence-guarded like `_attach_image_uuid_refs`'s per-ref clear.
|
|
1540
|
+
if attempted_search:
|
|
1541
|
+
clear_search = page.locator(PICKER_SEARCH_INPUT).first
|
|
1542
|
+
if await clear_search.count():
|
|
1543
|
+
await clear_search.fill("")
|
|
1544
|
+
await page.wait_for_timeout(400)
|
|
1545
|
+
# Neither the initial viewport nor the display-name search
|
|
1546
|
+
# surfaced the tile — the Tudo grid is virtualised, so an
|
|
1547
|
+
# off-screen (but existing) asset is simply absent from the DOM
|
|
1548
|
+
# until scrolled into range. Scroll and re-check before giving up.
|
|
1549
|
+
for _ in range(PICKER_GRID_SCROLL_ATTEMPTS):
|
|
1550
|
+
if await tile.count():
|
|
1551
|
+
visible = True
|
|
1552
|
+
break
|
|
1553
|
+
await VideoGenerationMixin._scroll_picker_grid(page)
|
|
1554
|
+
if visible:
|
|
1555
|
+
try:
|
|
1556
|
+
await tile.wait_for(state="visible", timeout=4000)
|
|
1557
|
+
except Exception: # noqa: BLE001 - rendered but never became visible
|
|
1558
|
+
visible = False
|
|
1559
|
+
|
|
1560
|
+
if not visible:
|
|
1561
|
+
return False
|
|
1524
1562
|
|
|
1525
1563
|
await tile.click()
|
|
1526
1564
|
await page.wait_for_timeout(400)
|
|
@@ -1577,6 +1615,23 @@ class VideoGenerationMixin:
|
|
|
1577
1615
|
await add.click()
|
|
1578
1616
|
await page.wait_for_timeout(800)
|
|
1579
1617
|
|
|
1618
|
+
# Fresh per-ref state (#282): a display-name search typed while
|
|
1619
|
+
# locating a previous ref must not leak into this ref's lookup —
|
|
1620
|
+
# every ref after the first was failing because the picker was
|
|
1621
|
+
# still filtered on the prior ref's search term. (The dialog and
|
|
1622
|
+
# tile locators are already re-resolved fresh each iteration below,
|
|
1623
|
+
# since each is a brand new `page.locator(...)` call rather than a
|
|
1624
|
+
# handle carried over from a prior loop pass.)
|
|
1625
|
+
#
|
|
1626
|
+
# Presence-guarded (#281 final review): a picker variant with no
|
|
1627
|
+
# search box at all (#174's full-page media-library drift) must
|
|
1628
|
+
# not become a hard dependency for every ref — an unconditional
|
|
1629
|
+
# `.fill("")` would wait out a full actionability timeout against
|
|
1630
|
+
# an element that's simply absent from the DOM.
|
|
1631
|
+
search = page.locator(PICKER_SEARCH_INPUT).first
|
|
1632
|
+
if await search.count():
|
|
1633
|
+
await search.fill("")
|
|
1634
|
+
|
|
1580
1635
|
if await VideoGenerationMixin._select_existing_asset(
|
|
1581
1636
|
page, media_id, display_name, out_dir=out_dir
|
|
1582
1637
|
):
|
|
@@ -51,10 +51,14 @@ from gflow_cli.api.image import (
|
|
|
51
51
|
from gflow_cli.api.image_upscale import TargetResolution, UpsampleImageRequest
|
|
52
52
|
from gflow_cli.api.transports import transport_choices
|
|
53
53
|
from gflow_cli.config import get_settings
|
|
54
|
-
from gflow_cli.data.recorder import OperationRecorder
|
|
54
|
+
from gflow_cli.data.recorder import OperationRecorder, escalate_asset_collision
|
|
55
55
|
from gflow_cli.data.repository import DataRepository
|
|
56
56
|
from gflow_cli.data.store import DataStore
|
|
57
|
-
from gflow_cli.errors import
|
|
57
|
+
from gflow_cli.errors import (
|
|
58
|
+
ConfigurationError,
|
|
59
|
+
DataIntegrityError,
|
|
60
|
+
DataStoreError,
|
|
61
|
+
)
|
|
58
62
|
from gflow_cli.image_batch import (
|
|
59
63
|
ALLOWED_ASPECT_RATIOS as _ALLOWED_ASPECT_RATIOS,
|
|
60
64
|
)
|
|
@@ -939,6 +943,16 @@ def _record_generated_images_safe(
|
|
|
939
943
|
|
|
940
944
|
Tool provenance (``original_prompt`` / ``tool``) travels on ``request``, so
|
|
941
945
|
the recorder reads it directly — no separate kwarg to drift out of sync.
|
|
946
|
+
|
|
947
|
+
Collision escalation (issue #281, #282 review): a ``DataIntegrityError``
|
|
948
|
+
whose ``route`` is the asset-collision constraint means the write itself
|
|
949
|
+
violated a local DB constraint — most likely the per-profile uniqueness of
|
|
950
|
+
``flow_media_id`` — i.e. the just-downloaded file may be a pre-existing
|
|
951
|
+
asset rather than genuinely new media. That is NOT a warn-and-continue
|
|
952
|
+
case like a generic ``DataStoreError``; ``escalate_asset_collision``
|
|
953
|
+
raises ``MediaAttributionError`` for that route and returns normally for
|
|
954
|
+
any other (unrelated) ``DataIntegrityError``, in which case this falls
|
|
955
|
+
through to the same warn-and-continue path as a plain ``DataStoreError``.
|
|
942
956
|
"""
|
|
943
957
|
try:
|
|
944
958
|
recorder.record_generated_images(
|
|
@@ -954,6 +968,8 @@ def _record_generated_images_safe(
|
|
|
954
968
|
operation_kind=operation_kind,
|
|
955
969
|
)
|
|
956
970
|
except DataStoreError as exc:
|
|
971
|
+
if isinstance(exc, DataIntegrityError):
|
|
972
|
+
escalate_asset_collision(exc, images=images, saved_paths=saved_paths)
|
|
957
973
|
first_image = images[0] if images else None
|
|
958
974
|
first_path = saved_paths[0] if saved_paths else None
|
|
959
975
|
_warn_persistence_failed_after_success(
|
|
@@ -1004,6 +1020,7 @@ async def _run_t2i(
|
|
|
1004
1020
|
count=count,
|
|
1005
1021
|
)
|
|
1006
1022
|
|
|
1023
|
+
recorder.verify_media_attribution(profile_name=profile_name, images=images)
|
|
1007
1024
|
saved_paths = await _download_images(client, images, out, output_root)
|
|
1008
1025
|
|
|
1009
1026
|
if as_json:
|
|
@@ -1450,6 +1467,7 @@ async def _run_i2i(
|
|
|
1450
1467
|
count=count,
|
|
1451
1468
|
)
|
|
1452
1469
|
|
|
1470
|
+
recorder.verify_media_attribution(profile_name=profile_name, images=images)
|
|
1453
1471
|
saved_paths = await _download_images(client, images, out, output_root)
|
|
1454
1472
|
|
|
1455
1473
|
if as_json:
|