Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bee381a
Fix macOS Codex packaging and pin runtime 0.154.0 for build6
10-X-eng Sep 17, 2026
ed7ffc7
Include macOS QuickLook bundles in binary relocation
10-X-eng Sep 17, 2026
bc80dc8
Add a three-workflow click harness on the existing loopback UI path.
cursoragent Sep 17, 2026
4ac9e40
Keep the optional Jev mock usable without a TypeSafe key.
cursoragent Sep 17, 2026
36baf68
Merge pull request #241 from 10-X-eng/fix/macos-codex-relocation
10-X-eng Sep 17, 2026
6fd5587
Merge branch 'main' into cursor/workflow-harness-7f69
jkbennitt Sep 18, 2026
a2e551b
Land New after focus moves and drive sketch from Part Design commands.
cursoragent Sep 18, 2026
b6965be
Find command QActions with the same search findChildren already uses.
cursoragent Sep 18, 2026
8ef8d83
Do not hold HTTP inside Choose Orientation; the next click presses OK.
cursoragent Sep 18, 2026
26d4f38
Leave the sketch before the Model extrude command.
cursoragent Sep 18, 2026
42605c0
Do not expect DesignExtrude after an empty sketch.
cursoragent Sep 18, 2026
4834906
Place a closed circle through /v1/run, then click Extrude and export.
cursoragent Sep 18, 2026
324ed2e
Treat run-only harness steps as having no click text.
cursoragent Sep 18, 2026
efbb84e
Enable Extrude with InternalFace1 and dismiss Document Recovery via C…
cursoragent Sep 18, 2026
49d37d3
Prefer the enabled Extrude QAction when findChildren also sees a hidd…
cursoragent Sep 18, 2026
9f7225e
Follow canInvoke when every Extrude QAction is disabled.
cursoragent Sep 18, 2026
6714cd6
Keep one document and leave the exact edit sketch.
cursoragent Sep 18, 2026
fda2934
Write a real STEP from the DesignExtrude Shape.
cursoragent Sep 18, 2026
522bd2c
Accept the Extrude task and export the publication solid.
cursoragent Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/vibecad-workflow-harness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: VibeCAD Workflow Harness

on:
pull_request:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read

concurrency:
group: vibecad-workflow-harness-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
harness:
name: Three-workflow click harness
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out source
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.11"

- name: Run harness self-test without a TypeSafe key
working-directory: tools
run: python vibecad_workflow_harness_selftest.py

- name: Run the three workflows against the fake click channel
working-directory: tools
run: python vibecad_workflow_harness.py --fake
93 changes: 90 additions & 3 deletions docs/vibecad-agent-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ owner's network controls.
| POST | `/v1/close` | optional `document`, explicit `discard_unsaved` | Close without silently discarding a modified document |
| GET | `/v1/ui/menus` | | Live top-level menu names, indices, visibility, and screen geometry |
| GET | `/v1/ui/ribbon` | | Live ribbon names, workbenches, indices, selection, and screen geometry |
| POST | `/v1/ui/click` | `{"kind":"menu|ribbon","text":"..."}`, optional exact PID/index | Activate one semantic Qt target without moving or clicking the OS cursor |
| POST | `/v1/ui/click` | `{"kind":"menu|ribbon|action|dialog","text":"..."}`, optional exact PID/index | Activate one semantic Qt target without moving or clicking the OS cursor |
| POST | `/v1/run` | `{"python":"..."}` or `{"script":"..."}` plus optional `path`, `recompute` | Exec against the active doc |
| GET | `/v1/operations/{operation_id}` | | Read the in-memory state/result of a client-identified operation without entering the document thread |
| GET/POST | `/v1/aero` | operation payload for POST | Bounded Aero context and operations |
Expand Down Expand Up @@ -388,8 +388,95 @@ tracking explicitly by supplying an `operation_id` through the HTTP API.

### Semantic UI activation and the independent cursor

`/v1/ui/click` targets an exact live Qt menu action or
`VibeCADRibbonTabs` entry by visible text. Optional `expected_process_id` and
`/v1/ui/click` targets an exact live Qt menu action,
`VibeCADRibbonTabs` entry, or named command `QAction` by visible text
or object name. Action search uses the same `findChildren(QAction)`
set the main window already exposes, not only toolbar or menu-bar
`actions()`. Ribbon and menu kinds stay unchanged. The additive
`action` kind queues `QAction.trigger()` onto the next Qt event-loop
turn so a modal `QDialog.exec()` cannot hold the HTTP request.
`findChildren(QAction)` can see a hidden or disabled command copy
before the enabled Model-tab action. The click path now prefers the
enabled visible match, or the single enabled command action when the
standard toolbar copy is hidden. When every matching `QAction` is
disabled, `QAction.trigger()` does not fire; the click then follows
`Gui.isCommandActive` (`Command.canInvoke()`) and queues
`Gui.Command.get(name).run()`. `UI_TARGET_DISABLED` is returned only
when that command is also not invokable, and the payload then includes
`action_pick`, `action_match_count`, and `command_active`. The
additive `dialog` kind presses OK on the visible Choose Orientation
dialog (XY-plane already selected) in-process. That opens the sketch
editor: domain tabs including Model are disabled, and
`PartDesign_Pad` is not a `findChildren(QAction)` match because the
command is never `addTo()`'d on the Model or sketch.edit ribbon. The
Finish-group command the window does expose is `Sketcher_LeaveSketch`.
`CmdSketcherLeaveSketch::activated()` passes
`getActiveGuiDocument()` (`Application::activeDocument()`) into
`SketcherGui.leaveActiveSketch`. `requireExactEditState` then throws
`The exact Sketch document is no longer active in edit mode` when
`activeDocument()` is not `editDocument()`. A queued LeaveSketch
click plus a new `Std_New` per workflow creates that split (active
`Unnamed2`, editor still on an earlier document). The harness
therefore keeps one document through new, sketch, Extrude, and
export, and finishes the editor with
`SketcherGui.leaveActiveSketch` on `Gui.editDocument()`, not the
active tab. After leave, the rebuilt Model ribbon surfaces
`PartDesign_DesignExtrude`. An empty sketch makes
`ProfileBased` throw `Linked shape object is empty` and
`startConfiguredDesignProfileOperation` then raises
`The Design operation task panel did not open` and aborts the
transaction. The smallest closed-profile control on the
sketch.edit Geometry group is `Sketcher_CreateRectangle` (child of
`Sketcher_CompCreateRectangles`). Its `activated()` only starts
`DrawSketchHandlerRectangle`; it does not add geometry. Placing
the two corners needs viewport clicks, which `/v1/ui/click` kinds
do not do. The harness therefore does not click that button to
draw. After `Sketcher_LeaveSketch`, it posts the existing
`/v1/run` route (already used for tree inspect) with the same
`SketchObject.addGeometry(Part.Circle(...), False)` call
`TestConsolidatedPartTools._circle_sketch` and
`TestSketcherSolver.CreateCircleSketch` already use. Extrude is
enabled by `designProfileOperationActive()`: a reusable sketch or
one or more `InternalFace*` regions, not an edge
(`ReferenceSelection.cpp`, `TestDesignProfileRegionsGui`). The
harness therefore selects `InternalFace1` on the sketch object
and calls `Gui.Command.update()` so the `QAction` enabled flag
matches `isActive()`, then clicks the Extrude button.

Before any workflow, the harness dismisses a leftover
`Document Recovery` dialog with its real `Cancel` button
(`DocumentRecovery.ui` / `DocumentRecovery.cpp`). It does not
press `Start Recovery` (that is the relabeled Ok button). A dirty
kill leaves that modal under the next Choose Orientation; kind
`dialog` text `OK` then matches every visible `QDialog` and never
lands. A pass
leaves `PartDesign::Body`, a `Sketcher::SketchObject` with
`GeometryCount >= 1`, `PartDesign::DesignExtrude`, and
`PartDesign::DesignBodyPublication` (`BodyResult`) in the
tree. Kind `dialog` text `OK` looks for a `QDialog` titled OK
and does not see the Extrude OK in the Tasks dock. The harness
accepts that task the same way
`TestDesignProfileRegionsGui._close_task` does: a visible
`QDialogButtonBox` OK through `/v1/run`, then waits until
`Gui.Control.activeDialog` is gone. After that OK,
`DesignExtrude.Shape` stays null; the solid with Faces is the
publication. Export does not click `Std_Export`: that command's
`activated()` opens `FileDialog::getSaveFileName`, which would
hold the HTTP request. `Import.export` is the exporter that
dialog would call (`AppImportPy.cpp`). `WriterStep::write` only
throws on OCCT `RetError` / `RetFail` / `RetStop`, so it can
return without creating the file. Live 6714cd65 then failed in
`os.path.getsize` (`WinError 2`) on
`%LOCALAPPDATA%\Temp\vibecad-workflow-harness.step`. The
harness writes the object that actually has Faces, preferring
`PartDesign::DesignBodyPublication`, tries `Import.export`, and
if that path is still missing or empty writes
`solid.Shape.exportStep` (`TopoShapePy` / `TopoShape.cpp`). It
does not report success unless that file exists and is
non-empty.
A successful trigger
counts as applied even when creating a document moves Qt focus;
restoration fields stay on the payload for evidence. Optional `expected_process_id` and
`expected_index` values make stale geometry fail closed. Ribbon clicks use an
in-process Qt mouse event; top-level menus use a non-blocking in-process Qt
popup. A menu popup is displayed for one bounded preview, then closed before
Expand Down
19 changes: 18 additions & 1 deletion package/rattler-build/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,24 @@ source:
use_gitignore: true

build:
number: 5
number: 6
# Upstream Codex executables are self-contained, with system-only Mach-O
# dependencies. Adding conda RPATHs overflows their unpadded headers.
# Relocate our bin/lib installs, app plug-ins and native modules, not prebuilt
# executables under Mod. The final recursive bundle audit remains mandatory.
dynamic_linking:
binary_relocation:
- if: osx
then:
- bin/**
- lib/**
- Library/**
- PlugIns/**
- '**/*.so'
- '**/*.dylib'
- '**/*.bundle'
else:
- '**'
script:
env:
# rattler-build runs the build in a sandbox that does not inherit shell
Expand Down
14 changes: 7 additions & 7 deletions package/rattler-build/scripts/install_vibecad_codex_runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ download_cache="${VIBECAD_DOWNLOAD_CACHE:-${repository_root}/package/rattler-bui
runtime_root="${module_directory}/codex_runtime"
stamp="${runtime_root}/runtime-spec.sha256"

codex_version="0.153.4"
codex_version="0.154.0"
release_tag="rust-v${codex_version}"
release_root="https://github.com/openai/codex/releases/download/${release_tag}"
license_url="https://raw.githubusercontent.com/openai/codex/${release_tag}/LICENSE"
Expand All @@ -31,32 +31,32 @@ machine="$(${python_executable} -c 'import platform; print(platform.machine().lo
case "${platform}:${machine}" in
linux:x86_64|linux:amd64)
archive="codex-app-server-package-x86_64-unknown-linux-musl.tar.gz"
archive_sha256="a5d37ff1fa6953ee6d317b7e69bfafd39f5f53350b631d790fa7531159f22420"
archive_sha256="b2450aaa4004d06790dd8a69d0246f4503ff1258400cc20de7d7a42ffe81b253"
executable="${runtime_root}/bin/codex-app-server"
;;
linux:aarch64|linux:arm64)
archive="codex-app-server-package-aarch64-unknown-linux-musl.tar.gz"
archive_sha256="5673c5a8935ff2f85ca67b489e560fdd5e08fb0f0e2f7426f048ec7449aa4fdc"
archive_sha256="295bb1b94a8b964b2d2461db9736b9907a9e4daa6ceb8e9bbb820b304fa897ed"
executable="${runtime_root}/bin/codex-app-server"
;;
win32:amd64|win32:x86_64)
archive="codex-app-server-package-x86_64-pc-windows-msvc.tar.gz"
archive_sha256="69441ca4c8f6197923dc1b70a8aa870ff912b5367347287d021eaca1f3add971"
archive_sha256="5f8b43e030c0aeeb7bdb3d5e03fff4c68ba94fa2df0ae437c490811f54660d74"
executable="${runtime_root}/bin/codex-app-server.exe"
;;
win32:arm64|win32:aarch64)
archive="codex-app-server-package-aarch64-pc-windows-msvc.tar.gz"
archive_sha256="d5f0ef33223912a1559a7e97012afa18eef3369f1d07dde199edfada062503ee"
archive_sha256="7406aa3745acd5bd639b8921c0ee5e241861763606465cac643683944a849457"
executable="${runtime_root}/bin/codex-app-server.exe"
;;
darwin:arm64|darwin:aarch64)
archive="codex-app-server-package-aarch64-apple-darwin.tar.gz"
archive_sha256="90f0467fd03294896204e8856bf969a0691590e8bef78dc2563a264b186f3265"
archive_sha256="7bf20c1843bdcff086c89a294299833f20146ebbdba03e7f49f020b7adbfff7b"
executable="${runtime_root}/bin/codex-app-server"
;;
darwin:x86_64|darwin:amd64)
archive="codex-app-server-package-x86_64-apple-darwin.tar.gz"
archive_sha256="ee286ca326a0df4a2b81dddb213d61e610d7b9c4f3173cc16f6023683a94ca82"
archive_sha256="4fddde3689d2aa0058c06138a84b05f87bdbff8cd556fba5816a97ac0469a4d4"
executable="${runtime_root}/bin/codex-app-server"
;;
*)
Expand Down
1 change: 1 addition & 0 deletions src/Mod/VibeCAD/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,7 @@ if(BUILD_TEST AND BUILD_GUI)
vibecad_tests/test_agent_control_grok_bot.py
vibecad_tests/test_dev_launcher_contract.py
vibecad_tests/test_visible_operator_contract.py
vibecad_tests/test_workflow_harness_contract.py
vibecad_tests/test_reference_image_downscale.py
vibecad_tests/test_ollama_inspect.py
vibecad_tests/test_live_acceptance_oracle.py
Expand Down
4 changes: 2 additions & 2 deletions src/Mod/VibeCAD/VibeCADAgentCli.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ def build_parser() -> argparse.ArgumentParser:

ui_click_parser = sub.add_parser(
"ui-click",
help="Activate one semantic ribbon or menu target without moving the OS cursor.",
help="Activate one semantic ribbon, menu, action, or dialog target without moving the OS cursor.",
)
ui_click_parser.add_argument(
"--kind",
required=True,
choices=("ribbon", "menu"),
choices=("ribbon", "menu", "action", "dialog"),
help="Target family to activate.",
)
ui_click_parser.add_argument("--text", required=True, help="Exact visible target text.")
Expand Down
Loading
Loading