@capagents/forgeqe 1.3.2 → 2.0.0

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.
Files changed (115) hide show
  1. package/HOW_TO_USE.md +373 -9
  2. package/README.md +55 -4
  3. package/docs/ARCHITECTURE.md +62 -1
  4. package/docs/CONTAINERS.md +227 -0
  5. package/docs/COST_ARCHITECTURE.md +32 -0
  6. package/docs/DESKTOP-CI.md +87 -0
  7. package/docs/DESKTOP.md +387 -0
  8. package/docs/MOBILE.md +228 -0
  9. package/docs/feature-test-prompts.md +25 -0
  10. package/docs/security-data-flow.html +440 -3
  11. package/docs/user-guide.html +797 -415
  12. package/env.example.yaml +67 -0
  13. package/forgeqe.example.yaml +56 -1
  14. package/llms.example.json +7 -0
  15. package/package.json +4 -1
  16. package/prompts/brain.md +31 -0
  17. package/prompts/find-tests.md +1 -0
  18. package/py/forgeqe_desktop/__init__.py +1 -0
  19. package/py/forgeqe_desktop/__main__.py +3 -0
  20. package/py/forgeqe_desktop/apps.py +80 -0
  21. package/py/forgeqe_desktop/checks.py +168 -0
  22. package/py/forgeqe_desktop/cli.py +90 -0
  23. package/py/forgeqe_desktop/handhooks.py +293 -0
  24. package/py/forgeqe_desktop/handrecord.py +239 -0
  25. package/py/forgeqe_desktop/heal.py +107 -0
  26. package/py/forgeqe_desktop/models.py +208 -0
  27. package/py/forgeqe_desktop/pywinauto_driver.py +217 -0
  28. package/py/forgeqe_desktop/record.py +150 -0
  29. package/py/forgeqe_desktop/replay.py +345 -0
  30. package/py/forgeqe_desktop/service.py +356 -0
  31. package/py/forgeqe_desktop/session.py +60 -0
  32. package/py/forgeqe_desktop/ufo_bridge.py +86 -0
  33. package/py/forgeqe_desktop/ufo_runner.py +305 -0
  34. package/py/forgeqe_desktop/visual.py +112 -0
  35. package/py/forgeqe_desktop/workflow.py +338 -0
  36. package/py/forgeqe_engine/llm.py +54 -4
  37. package/py/forgeqe_engine/model_params.py +169 -0
  38. package/py/forgeqe_mobile/__init__.py +1 -0
  39. package/py/forgeqe_mobile/__main__.py +3 -0
  40. package/py/forgeqe_mobile/appium_driver.py +342 -0
  41. package/py/forgeqe_mobile/apps.py +84 -0
  42. package/py/forgeqe_mobile/cli.py +70 -0
  43. package/py/forgeqe_mobile/explore.py +203 -0
  44. package/py/forgeqe_mobile/hyperexecute.py +261 -0
  45. package/py/forgeqe_mobile/maestro.py +321 -0
  46. package/py/forgeqe_mobile/service.py +332 -0
  47. package/skills/testcontainers/SKILL.md +42 -0
  48. package/src/agents.ts +11 -2
  49. package/src/api/plan.ts +2 -0
  50. package/src/api/write.ts +2 -0
  51. package/src/auth/copilot.ts +130 -0
  52. package/src/auth/signin.ts +54 -1
  53. package/src/brain/index.ts +26 -4
  54. package/src/capabilities/guide.ts +2 -0
  55. package/src/capabilities/registry.ts +110 -2
  56. package/src/capabilities/status.ts +15 -0
  57. package/src/cases/rca.ts +2 -0
  58. package/src/childEnv.ts +4 -1
  59. package/src/cli/args.ts +3 -2
  60. package/src/cli/help.ts +24 -3
  61. package/src/cli/index.ts +47 -3
  62. package/src/code/find.ts +26 -0
  63. package/src/code/loop.ts +4 -1
  64. package/src/code/prompt.ts +2 -1
  65. package/src/code/prreview.ts +2 -0
  66. package/src/code/review.ts +6 -2
  67. package/src/code/runtime.ts +34 -3
  68. package/src/commands.ts +6 -1
  69. package/src/config.ts +159 -3
  70. package/src/containers/command.ts +68 -0
  71. package/src/containers/compose.ts +173 -0
  72. package/src/containers/config.ts +283 -0
  73. package/src/containers/mock.ts +156 -0
  74. package/src/containers/run.ts +362 -0
  75. package/src/containers/tests.ts +80 -0
  76. package/src/context.ts +33 -6
  77. package/src/copilot.ts +155 -0
  78. package/src/desktop/bridge.ts +229 -0
  79. package/src/desktop/command.ts +350 -0
  80. package/src/desktop/edit.ts +217 -0
  81. package/src/desktop/junit.ts +35 -0
  82. package/src/desktop/report.ts +155 -0
  83. package/src/desktop/run.ts +252 -0
  84. package/src/desktop/setup.ts +134 -0
  85. package/src/engine/bridge.ts +12 -5
  86. package/src/engine/setup.ts +3 -3
  87. package/src/environments.ts +566 -0
  88. package/src/evals/domain.ts +1 -0
  89. package/src/explore/charters.ts +2 -0
  90. package/src/guardrails.ts +145 -0
  91. package/src/heal/loop.ts +4 -1
  92. package/src/heal/prompt.ts +2 -1
  93. package/src/init.ts +5 -2
  94. package/src/llm.ts +23 -2
  95. package/src/mcp/attachtools.ts +16 -4
  96. package/src/mcp/captools.ts +27 -0
  97. package/src/mcp/containertools.ts +212 -0
  98. package/src/mcp/desktoptools.ts +334 -0
  99. package/src/mcp/mobiletools.ts +132 -0
  100. package/src/mcp/reporttools.ts +17 -2
  101. package/src/mcp/repotests.ts +22 -3
  102. package/src/mcp/routing.ts +11 -5
  103. package/src/mcp/scope.ts +210 -0
  104. package/src/mcp/server.ts +80 -5
  105. package/src/mcp/tools.ts +38 -1
  106. package/src/mobile/command.ts +245 -0
  107. package/src/mobile/run.ts +226 -0
  108. package/src/pack/pipeline.ts +44 -2
  109. package/src/refine.ts +3 -0
  110. package/src/report/html.ts +3 -0
  111. package/src/report/template.html +48 -48
  112. package/src/run.ts +7 -3
  113. package/src/tokenomics/ledger.ts +3 -1
  114. package/src/tui/App.tsx +23 -0
  115. package/src/usage.ts +1 -1
@@ -0,0 +1,387 @@
1
+ # ForgeQE desktop: thick-client automation
2
+
3
+ Status: runs on Windows. The CI job `.github/workflows/forgeqe-desktop-windows.yml` (GitHub's `windows-2022`) installs the
4
+ engine and the pinned UFO², replays Notepad live with the pywinauto driver and through `forgeqe desktop replay`, heals a
5
+ moved control from the live window, and runs a whole explore: UFO² drives Notepad with a scripted model behind an
6
+ OpenAI-compatible server, the action is recorded with the control's AutomationId, and the recording replays. Not yet
7
+ run: explore with a real model (the job has no key), and your own apps. See
8
+ [Before first use on Windows](#before-first-use-on-windows).
9
+
10
+ ForgeQE drives web apps through Chrome. The desktop engine does the same for Windows thick clients (Win32, WinForms,
11
+ WPF, and the native parts of Electron and WebView2 apps): say what you want in plain language, UFO² does it on the live
12
+ app, and the run is saved as a YAML workflow that replays later without a model.
13
+
14
+ ## Use
15
+
16
+ ```bash
17
+ forgeqe desktop setup --ufo # once, on Windows: the engine, pywinauto, and the pinned UFO²
18
+ forgeqe desktop apps # what forgeqe.yaml allows
19
+ forgeqe desktop explore "Create an invoice for Contoso, net 30" --app billing
20
+ forgeqe desktop record --app billing # or do the steps yourself: no model, no UFO
21
+ forgeqe desktop replay .forgeqe/reports/desktop-explore-…/workflow.yaml customer=Fabrikam
22
+ forgeqe desktop replay workflow.yaml --data customers.csv # once per row; the header names the variables
23
+ forgeqe desktop replay desktop-tests/ --junit results.xml # every workflow in a folder, results for CI
24
+ forgeqe desktop replay workflow.yaml --update-baselines # save new baselines for its expect_image steps
25
+ forgeqe desktop doctor # can this machine run replay unattended?
26
+ forgeqe desktop validate workflow.yaml # any OS
27
+ ```
28
+
29
+ Or ask ForgeQE's agent in the shell or `forgeqe run`: "create an invoice for Contoso in Billing and save it as a test",
30
+ or "I'll show you the steps in Billing, record them". Its tools are `test_apps`, `desktop_explore`, `desktop_record`,
31
+ `desktop_replay` and `desktop_edit_workflow`. It asks before it starts the app or takes over the desktop, and before it
32
+ changes a workflow. Where it cannot ask (an MCP client), the call answers `needs_confirmation` until the user agrees.
33
+
34
+ Desktop apps come from the active environment in `env.yaml` (see HOW_TO_USE.md, "Environments and apps"), or from
35
+ `desktop.apps` in `forgeqe.yaml`, which works without environments:
36
+
37
+ ```yaml
38
+ desktop:
39
+ apps:
40
+ billing:
41
+ path: C:\Program Files\Contoso\Billing\Billing.exe
42
+ args: ["--profile", "test"]
43
+ # window: "Billing.*" # for a launcher that starts the real app and exits: connect by title
44
+ secrets: [BILLING_PASSWORD] # the only environment variables {{secret:…}} may read
45
+ heal: true # replay asks the model to re-find a control that moved (default on)
46
+ screenshots: true # one screenshot per replayed step (default on)
47
+ ```
48
+
49
+ Each run writes a folder under the reports folder (`desktop-explore-…` or `desktop-replay-…`): `run.json`,
50
+ `report.html`, `steps/NNN.png`, and for explore `workflow.yaml`, `trace.json` and UFO's own logs in `ufo/`. A replay
51
+ whose steps were healed also writes `heals.json` and asks whether to write the new locators into the workflow
52
+ (`--accept-heals` writes them without asking; with no terminal they are never written).
53
+
54
+ ## Decisions
55
+
56
+ | Question | Decision | Why |
57
+ |---|---|---|
58
+ | Agent that explores an app | Microsoft UFO² (`microsoft/UFO`, MIT), pinned to commit `eae3c0a8d168` and never edited, behind one adapter (`ufo_runner.py`) | It already solves UI Automation (UIA) control detection, merging it with vision, and the per-app agent loop. Behind the adapter it can be swapped for our own engine without touching the CLI, the YAML or replay. |
59
+ | Saved scripts | ForgeQE's own YAML (`forgeqe-desktop-v1`), replayed on pywinauto (UIA backend) | UFO calls a model at every step. Replay must be fast, free, repeatable and fit for CI, so no model runs unless a step needs healing. |
60
+ | Models | `llms.json`, the same profiles as the browser engine. UFO never gets its own model config | One place to configure models, keys and Microsoft Entra ID. |
61
+ | Python environment | Its own (`~/.forgeqe/desktop/.venv`, Python 3.10), not the browser engine's | UFO pins `openai==1.66.2` and `pydantic`, which the browser engine's library does not run with, and `pandas==1.4.3`, which has Windows wheels up to Python 3.10. Replay's packages use UFO's pins, so one environment runs both. |
62
+ | Not chosen: `windowsagent` | Reference only | Very early (v0.1.0), replay not built, and it exposes a model-driven `/shell` endpoint. |
63
+
64
+ ## How a run flows
65
+
66
+ ```
67
+ forgeqe desktop explore ──► src/desktop/bridge.ts ──JSON lines──► python -m forgeqe_desktop serve
68
+ │ start the app from desktop.apps (no shell)
69
+ │ UFO² in-process: model = llms.json profile,
70
+ │ UI tools only, every action traced with its
71
+ ▼ control's UIA properties
72
+ record.py ──► workflow.yaml
73
+ forgeqe desktop replay ───► bridge ──► serve (replay.py on pywinauto, no model)
74
+ │ a control is not found
75
+ ▼
76
+ heal.py: the model picks one of the window's real controls, by number
77
+ ▼
78
+ proposed locator → heals.json, written into the workflow only when accepted
79
+ ```
80
+
81
+ ## One model config: `llms.json` → UFO
82
+
83
+ UFO picks its model client by `API_TYPE` through `ufo.llm.base.BaseService.get_service`, which every model call goes
84
+ through (`ufo/llm/llm_call.py`). The adapter adds the type `forgeqe`:
85
+
86
+ - `write_ufo_config` writes UFO's `config/ufo/agents.yaml` from its template, with `API_TYPE: forgeqe`, the model's name,
87
+ and no endpoint and no key, for `HOST_AGENT`, `APP_AGENT`, `BACKUP_AGENT` and `EVALUATION_AGENT`.
88
+ - `ufo_bridge.install` makes `get_service("forgeqe", …)` return `ForgeQEService`, which calls the profile through the
89
+ `openai` SDK (`models.py`): `azure` (key or `auth: entra`), `openai`, `openai_compatible` (key or Entra), and `ollama`
90
+ through its OpenAI-compatible `/v1`. Keys, Azure endpoints and Entra use the browser engine's own helpers
91
+ (`forgeqe_engine.llm`), so a profile means the same thing in both. Keys stay in environment variables.
92
+ - Each call's time and tokens go to ForgeQE's usage ledger as the "desktop agent" (counts only, never the prompt).
93
+
94
+ Explore sends screenshots, so its profile must take images: `"vision": true` or `false` on the profile says so; without
95
+ it, `ollama` profiles are refused and others accepted. Healing sends a screenshot only to a profile that takes images.
96
+
97
+ The request parameters suit the model by themselves: a reasoning model (o1, o3, o4, gpt-5, by the profile's `model` or
98
+ `deployment` name, or `"reasoning": true` on the profile) gets `max_completion_tokens` and no temperature from the first
99
+ call, and when the API refuses a parameter (`max_tokens`, `temperature`, `frequency_penalty`, `response_format`, …) the
100
+ call is sent again without it, and later calls to that model leave it out (`py/forgeqe_engine/model_params.py`). The
101
+ browser agent does the same.
102
+
103
+ ## Workflow YAML (`forgeqe-desktop-v1`)
104
+
105
+ ```yaml
106
+ format: forgeqe-desktop-v1
107
+ name: Create an invoice
108
+ app: billing # a name from forgeqe.yaml desktop.apps, never a path or a command
109
+ variables:
110
+ customer: Contoso
111
+ steps:
112
+ - op: launch
113
+ - op: click
114
+ target: {automation_id: btnNewInvoice, control_type: Button, name: New invoice}
115
+ - op: type
116
+ target: {automation_id: txtCustomer, control_type: Edit}
117
+ text: "{{customer}}"
118
+ - op: type
119
+ target: {automation_id: txtPassword, control_type: Edit}
120
+ text: "{{secret:BILLING_PASSWORD}}"
121
+ - op: select
122
+ target: {automation_id: cmbTerms, control_type: ComboBox}
123
+ value: Net 30
124
+ - op: keys
125
+ keys: ctrl+s
126
+ - op: expect
127
+ target: {automation_id: lblStatus}
128
+ text: Saved
129
+ - op: close
130
+ ```
131
+
132
+ - **Ops:** `launch`, `click`, `double_click`, `type`, `select`, `keys`, `expect`, `expect_image`, `wait` (at most
133
+ 60 s), `wait_for`, `close`.
134
+ - **Locators** use UIA properties, never screen coordinates: `automation_id`, `name`, `control_type`, `class_name`,
135
+ `index` (when several match), and `path` (the ancestors' `control_type` and `name`). A target needs an
136
+ `automation_id` or a `name`. Replay looks in the launched app's own windows only.
137
+ - **Variables:** `{{name}}` from the workflow's `variables` or `name=value` on the command line; `{{secret:NAME}}` from
138
+ the environment, only for names in `desktop.secrets`. A secret ForgeQE itself uses (a model key, a tracker token) is
139
+ refused there. Values are substituted once and never expanded again. Targets take `{{name}}` too (a CSV row's
140
+ `{{customer}}` as the control to click), never a secret.
141
+ - **Keys:** `ctrl`, `shift` and `alt` with one named key or character (`ctrl+s`, `alt+f4`, `enter`, `f5`). The Windows
142
+ and context-menu keys are refused: Win+R followed by typing runs any command.
143
+ - **Typing** is literal: pywinauto's key syntax (`^`, `%`, `+`, `~`, `{ENTER}`) in the text is escaped.
144
+
145
+ ## Recording by hand
146
+
147
+ When you know the steps, do them yourself instead of describing a goal: no model and no UFO are involved, so it costs
148
+ nothing and does exactly what you did.
149
+
150
+ ```bash
151
+ forgeqe desktop record --app billing --name "Create an invoice"
152
+ ```
153
+
154
+ ForgeQE starts the app and watches the mouse and keyboard (low-level Windows hooks), asking UI Automation which control
155
+ each click and key went to. Do the steps, then:
156
+
157
+ - **Ctrl+Shift+F11** with the mouse over a control adds a check: an `expect` step with the text it shows now (a
158
+ status message, a saved total). Add one at the end, so the replay tests the outcome, not only the steps.
159
+ - **Ctrl+Shift+F12**, or closing the app, finishes. The console shows each step as it is recorded.
160
+
161
+ What becomes a step:
162
+
163
+ | You do | Step |
164
+ |---|---|
165
+ | Click a control (two quick clicks: `double_click`) | `click` with its AutomationId, name and control type |
166
+ | Pick an item in a drop-down | `select` on the drop-down, with the item's name |
167
+ | Type into a field (corrections and pastes included) | one `type` with the field's text when you move on, read from the field |
168
+ | A key chord outside a text field (`ctrl+s`, `enter`, `f5`) | `keys` |
169
+ | Ctrl+Shift+F11 over a control | `expect` with its text |
170
+
171
+ - **Passwords:** a password field is never read; its step types `{{secret:NAME}}` (the one name in `desktop.secrets`,
172
+ or `<APP>_PASSWORD`, which the result tells you to set). Text equal to a `desktop.secrets` value is written as its
173
+ placeholder. The typed characters themselves are never kept, only the field's text when you move on.
174
+ - **Only the app:** clicks and keys outside the app's own process are not recorded; nor are the Windows and menu keys,
175
+ or controls with neither an AutomationId nor a name. The result lists what was left out.
176
+ - The run folder has `workflow.yaml`, `run.json` and `report.html`, like explore. Replay it with
177
+ `forgeqe desktop replay`.
178
+
179
+ ## Fixing a workflow from chat
180
+
181
+ When a replayed step fails because the app was slow or a dialog was in the way, ForgeQE's agent fixes the workflow
182
+ with `desktop_edit_workflow`: it proposes the change (a longer `timeout` or `retries` on the step, a `wait_for` before
183
+ it, a dialog button in `dismiss`, a healed locator, a corrected text), shows you the diff, and writes the file only
184
+ when you agree. For example, after "step 4 timed out while the invoice list loaded":
185
+
186
+ ```text
187
+ Change invoice.yaml?
188
+ • new wait_for step before step 4
189
+ • step 4: retries 2
190
+ - op: click
191
+ + target: {automation_id: btnPost}
192
+ + retries: 2
193
+ ```
194
+
195
+ - Each change has a fixed shape (`set`, `unset`, `set_step`, `add_dismiss`, `insert_step`, `remove_step`,
196
+ `set_variable`); step numbers are the replay report's. Anything else is refused.
197
+ - The changed workflow is checked by the engine's own validator before it is shown; one that would not run is not
198
+ offered. Comments and layout are kept.
199
+ - The old text is kept in the reports folder (`desktop-edits/`), and a file that changed since the edit was proposed is
200
+ not overwritten.
201
+ - The workflow must be a `.yaml` inside the working folder or the reports folder.
202
+
203
+ ## Waits, retries and dialogs
204
+
205
+ Real apps are slow in places: a splash screen, a form that loads its data, a "Tip of the day" or "Save changes?"
206
+ dialog. A workflow says how to wait for them, without fixed `wait` steps:
207
+
208
+ ```yaml
209
+ timeout: 20 # seconds a step waits for its control (default 10, at most 300)
210
+ retries: 1 # tries a failed action again, after a short pause (default 0, at most 5)
211
+ dismiss: # clicked whenever one of them is in the way
212
+ - {name: OK, control_type: Button, automation_id: btnTipClose}
213
+ - {name: "Don't Save", control_type: Button}
214
+ steps:
215
+ - op: launch
216
+ timeout: 90 # a slow start: how long to wait for the app's window
217
+ - op: wait_for # the splash screen goes away
218
+ target: {automation_id: splashForm}
219
+ gone: true
220
+ timeout: 120
221
+ - op: wait_for # the form has loaded its data
222
+ target: {automation_id: lblStatus}
223
+ text: Ready
224
+ - op: click
225
+ target: {automation_id: btnPost}
226
+ timeout: 60 # per step, over the workflow's timeout
227
+ retries: 3
228
+ ```
229
+
230
+ - **Each step waits** for its control up to its `timeout`. `click`, `type` and `select` also wait until the control is
231
+ enabled and visible (a form that is still loading greys its buttons out).
232
+ - **`wait_for`** waits until a control is there, shows a `text`, or with `gone: true` has gone.
233
+ - **`retries`** applies to `click`, `double_click`, `type`, `select`, `keys`, `expect` and `expect_image`. Between
234
+ tries, replay pauses (1 s, 2 s, … up to 5 s) and closes any `dismiss` dialog. An error in the workflow itself (a
235
+ missing variable) is not retried.
236
+ - **`dismiss`** lists at most 10 targets, checked like any target. When a control is not found, or something waits to
237
+ go away, replay clicks the `dismiss` controls that are on screen and looks again, up to 3 times per lookup. The
238
+ report and the console name every dialog closed, and how many tries a step took.
239
+
240
+ ## Visual checks (`expect_image`)
241
+
242
+ `expect` compares text. When what matters is drawn (a chart, a signature, a colour, a layout), `expect_image`
243
+ compares pixels with a baseline image:
244
+
245
+ ```yaml
246
+ - op: expect_image
247
+ target: {automation_id: chartRevenue} # without a target: the app's top window
248
+ image: revenue-chart.png # a file name in <workflow name>.baselines/ next to the workflow
249
+ tolerance: 0.01 # share of pixels that may differ (default 0.01, i.e. 1%)
250
+ ignore: [[0, 0, 120, 24]] # regions [x, y, width, height] that change every run (a date, a clock)
251
+ ```
252
+
253
+ - **The first run** with no baseline saves what it captured and passes; the report says so. Commit the baselines
254
+ folder with the workflow. `--update-baselines` replaces them after an intended change.
255
+ - **A pixel counts as different** when a colour channel differs by more than 32 of 255, so anti-aliasing and ClearType
256
+ do not fail a check. A different size (another screen scale or window size) fails with both sizes named.
257
+ - **The report** shows the baseline, what was captured, and the differences in red, with the share of pixels that
258
+ differ (`images/NNN-baseline|actual|diff.png` in the run folder).
259
+ - **Image names** are file names only (letters, digits, `_`, `-`, ending `.png`). A path, `..`, or a link that leads
260
+ out of the baselines folder is refused.
261
+ - Run visual checks on one machine with a fixed resolution and scale (see [DESKTOP-CI.md](DESKTOP-CI.md)).
262
+
263
+ ## Unattended runs
264
+
265
+ Replay needs a signed-in, unlocked Windows session. Before it starts the app, it checks the session and fails at once
266
+ with the reason if it is a Windows service (session 0), a locked screen, or a disconnected Remote Desktop session.
267
+ `forgeqe desktop doctor` runs the same check. To run the suite every night on a machine of your own, see
268
+ [DESKTOP-CI.md](DESKTOP-CI.md): a runner set up in the user's session (`ci/desktop-runner/prepare-runner.ps1`), a
269
+ nightly workflow (`ci/desktop-runner/desktop-nightly.example.yml`), and `--junit` results.
270
+
271
+ ## Updating UFO²
272
+
273
+ UFO² is pinned to one commit. Moving it is checked before it is pinned:
274
+
275
+ 1. `.github/workflows/forgeqe-desktop-ufo-update.yml` runs weekly, or by hand with a commit. It sets up that UFO on
276
+ Windows (`FORGEQE_UFO_COMMIT`, which takes a full 40-character commit id only), runs the adapter tests against its
277
+ real code and a scripted explore of Notepad, and says in the job summary whether it passed.
278
+ 2. When it passed: `bun scripts/ufo-bump.ts <commit>` writes the new pin into `src/desktop/setup.ts`,
279
+ `py/forgeqe_desktop/ufo_runner.py` and this page. Run the tests and push; the Windows CI job tests the pin again.
280
+
281
+ When it fails, the failing step names what changed in UFO (usually the action executor or the config layout that the
282
+ adapter wraps). Keep the pin until the adapter is updated for it.
283
+
284
+ ## Checks of the outcome
285
+
286
+ A recording of actions only proves the steps run. When UFO has finished, and before the app closes, explore shows the
287
+ model the goal and the app's controls with the text each shows now, and the model picks up to three whose text proves
288
+ the goal (a status message, a saved value), by number, with the part of the text to expect. A check is kept only when
289
+ that text really is in that control, so the model can choose among what is on screen but never invent it; text holding
290
+ a `desktop.secrets` value is never offered. The checks are saved as `expect` steps at the end of the workflow, and the
291
+ report says how many there are. With none, the report and the agent say so: add `expect` steps by hand.
292
+
293
+ ## Recording
294
+
295
+ UFO's own step log names the control and a per-step id, not its AutomationId, so the adapter wraps UFO's
296
+ `ActionExecutor.execute` and records each action with the UIA properties of the control it acted on. `record.py` turns
297
+ that trace into steps: `click_input` → `click`/`double_click`, `set_edit_text` → `type`, `keyboard_input` → `keys` (one
298
+ allowed chord) or `type`, `select` → `select`. Coordinate clicks, scrolls and anything else are listed as not recorded
299
+ in the report. A typed value equal to a `desktop.secrets` value is written as its placeholder; typed text that only
300
+ looks like `{{…}}` is kept literal, so a model told to type `{{secret:X}}` cannot make replay type the secret.
301
+
302
+ ## Security
303
+
304
+ The YAML, the trace, the app's screen text and the model's replies can all be written by someone else. The rules in the
305
+ repository's `CLAUDE.md` apply; for this engine:
306
+
307
+ - **No command in the workflow.** `app:` names a `desktop.apps` entry: an absolute `.exe` (a `.bat` or `.cmd` would run
308
+ through `cmd.exe`, which reparses its arguments) and an argument list, started with `subprocess.Popen([path, *args])`.
309
+ - **UFO gets UI tools only.** Each explore writes UFO's `mcp.yaml` with `UICollector`, `HostUIExecutor` and
310
+ `AppUIExecutor`. UFO's default `CommandLineExecutor`, which runs shell commands, and its Word, Excel, PowerPoint and
311
+ PDF executors are left out. The adapter starts the app, so UFO never launches a program.
312
+ - **Keys:** during explore the adapter refuses UFO's Windows and menu keys too, and the report lists what it refused.
313
+ - **No one at the console:** UFO's safe guard asks before sensitive actions; explore answers no, since nobody is there.
314
+ - **Environment:** the engine process gets system settings, the profile's key variable (or Entra's) and
315
+ `desktop.secrets`, nothing else (`desktopEnvNames`).
316
+ - **Explore controls the real mouse and keyboard.** It asks before starting (`--yes` to skip; required with no
317
+ terminal). Use a test machine or VM and a test account.
318
+ - **Agent tools:** a workflow path the agent gives must be a `.yaml` inside the working folder or the reports folder (real
319
+ path, so a link out is refused); variables are names and text only; heals are reported, never written.
320
+ - **Healing:** the model picks one of the window's real controls by number; control text is passed as data, and any
321
+ other answer heals nothing. Heals are written into the workflow only when accepted.
322
+ - **Report:** every value is escaped; images are only the engine's own `steps/NNN.png` and
323
+ `images/NNN-baseline|actual|diff.png`.
324
+ - **Baselines:** `expect_image` names are file names, and their real path must be inside the workflow's baselines
325
+ folder, so a workflow cannot read or overwrite another file.
326
+ - **Unattended machines:** `prepare-runner.ps1` never handles a password; automatic sign-in uses Sysinternals
327
+ Autologon, which stores it encrypted.
328
+ - **Install:** UFO is cloned and checked out at the pinned commit (or `FORGEQE_UFO_COMMIT`, a full commit id only),
329
+ and the checkout's `HEAD` is verified; replay's packages are pinned to exact versions.
330
+
331
+ ## Code
332
+
333
+ | File | Contents |
334
+ |---|---|
335
+ | `src/desktop/command.ts` | `forgeqe desktop`: setup, apps, doctor, validate, explore, record, replay (folders, `--data`, `--junit`, `--update-baselines`); heal acceptance |
336
+ | `src/desktop/junit.ts` | JUnit XML for a suite |
337
+ | `src/desktop/edit.ts` | Changes to a workflow from chat: shapes, applied with comments kept, validated, diff, backup |
338
+ | `src/desktop/run.ts` | Explore and replay as the CLI and the agent's tools run them; variables, CSV rows, workflow paths |
339
+ | `src/mcp/desktoptools.ts` | ForgeQE's agent's `test_apps`, `desktop_explore`, `desktop_record`, `desktop_replay`, `desktop_edit_workflow` |
340
+ | `src/desktop/bridge.ts` | Starts `python -m forgeqe_desktop serve`, passes events on, records model calls in the usage ledger |
341
+ | `src/desktop/setup.ts` | The desktop environment, the pinned UFO checkout (`ufoCommit`), `desktopDoctor` |
342
+ | `scripts/ufo-bump.ts` | Pins a new UFO commit in every place that names it |
343
+ | `ci/desktop-runner/` | `prepare-runner.ps1` and the nightly workflow example for unattended runs |
344
+ | `src/desktop/report.ts` | `report.html` for a run |
345
+ | `py/forgeqe_desktop/service.py` | `serve`: the JSON-lines protocol, replay and explore runs, `run.json` |
346
+ | `py/forgeqe_desktop/workflow.py` | Load, validate and save workflows; variables and secrets; the key-chord grammar |
347
+ | `py/forgeqe_desktop/apps.py` | `desktop.apps`: the app allowlist and the argument array a launch uses |
348
+ | `py/forgeqe_desktop/models.py` | The profile through the `openai` SDK; image checks |
349
+ | `py/forgeqe_desktop/ufo_bridge.py` | UFO's model blocks, and the `forgeqe` model service |
350
+ | `py/forgeqe_desktop/ufo_runner.py` | UFO's config, the action trace and key guard, running UFO in-process |
351
+ | `py/forgeqe_desktop/record.py` | Trace to workflow steps |
352
+ | `py/forgeqe_desktop/replay.py` | Runs a workflow on a `Driver`: timeouts, retries, `wait_for`, dismissing dialogs, heal and stop |
353
+ | `py/forgeqe_desktop/handrecord.py` | Recording by hand: clicks, typing, chords and checks as steps; secrets |
354
+ | `py/forgeqe_desktop/handhooks.py` | The Windows hooks and UI Automation lookups that feed it |
355
+ | `py/forgeqe_desktop/visual.py` | `expect_image`: baselines, pixel comparison, diff images |
356
+ | `py/forgeqe_desktop/session.py` | Whether this Windows session can be driven (service, locked, no screen) |
357
+ | `py/forgeqe_desktop/heal.py` | One model call to re-find a moved control |
358
+ | `py/forgeqe_desktop/checks.py` | One model call for checks of the outcome, kept only when on screen |
359
+ | `py/forgeqe_desktop/pywinauto_driver.py` | The Windows `Driver` |
360
+
361
+ Tests: `bun test tests/desktop.test.ts`, and `<engine-python> -m pytest tests/desktop -q` (needs `openai==1.66.2`
362
+ and PyYAML; they use a fake driver and a fake UFO, so they run on any OS).
363
+
364
+ ## Before first use on Windows
365
+
366
+ Verified in CI: setup with and without `--ufo`, replay, healing, the CLI, and UFO's own code with the adapter (its
367
+ config, its model calls routed to the profile, the action trace, UI tools only). Still to check, on a Windows test
368
+ machine with your apps:
369
+
370
+ 1. **UIA coverage:** `Inspect.exe` shows the controls that matter with stable `AutomationId`s. Custom grids, Java
371
+ (needs Java Access Bridge), Delphi, SAP GUI and Citrix (vision only) are where UIA falls short.
372
+ 2. **Explore with your model:** one goal with an Azure profile and one with `auth: entra`; `trace.json` has the
373
+ controls' AutomationIds, and `ufo/` has UFO's logs.
374
+ 3. **Replay:** the recorded workflow passes 10 runs out of 10; a renamed control is healed.
375
+ 4. **Cost and time** of one explore, from `forgeqe` usage.
376
+
377
+ Found by the Windows runs and fixed: UFO's pins need Python 3.10 (pandas 1.4.3); UFO prints emoji panels to stdout, which
378
+ is the engine's event stream and crashed on the cp1252 console (now sent to stderr, characters replaced); an error in
379
+ UFO's own evaluation lost the recorded actions (the trace is now kept and saved).
380
+
381
+ ## Next steps
382
+
383
+ 1. The checks above.
384
+ 2. `forgeqe suite` and test cases from Azure DevOps, Jira and Xray for desktop workflows, with results written back.
385
+ 3. Scribe: export a workflow as pywinauto code (or C# with FlaUI) in the user's repo.
386
+ 4. App adapters where UIA falls short: Java Access Bridge, SAP GUI scripting, vision-only for Citrix.
387
+ 5. A pipeline template for a self-hosted Windows runner signed in to a desktop session.
package/docs/MOBILE.md ADDED
@@ -0,0 +1,228 @@
1
+ # ForgeQE mobile: Android and iOS apps
2
+
3
+ Status: the engine and its tests run on any OS, against a fake Appium server and a stand-in for Maestro. The CI job
4
+ `.github/workflows/forgeqe-mobile-android.yml` runs it on an Android emulator: Appium explores and replays the
5
+ emulator's Settings app, and Maestro replays the same workflow. iOS runs the same code with the XCUITest driver, but
6
+ no CI job tests it yet (it needs a macOS runner with a simulator).
7
+
8
+ ForgeQE drives mobile apps two ways, and one workflow format runs on both:
9
+
10
+ | | Appium | Maestro |
11
+ |---|---|---|
12
+ | Runs | step by step, through an Appium server (yours or a device cloud's) | the whole workflow as one Maestro flow |
13
+ | Explore a goal with the model | yes | no |
14
+ | Heal a control that moved | yes, one model call, reported for review | no |
15
+ | Visual checks (`expect_image`) | yes | no |
16
+ | Your team's own flows | n/a | `forgeqe mobile maestro`, checked against an allowlist |
17
+ | Needs | `appium` with UiAutomator2 (Android) or XCUITest (iOS), a device or emulator | `maestro` and a device or emulator |
18
+
19
+ ## Use
20
+
21
+ ```bash
22
+ forgeqe mobile doctor # does the Appium server answer, is maestro installed?
23
+ forgeqe mobile apps # the mobile apps of the active environment
24
+ forgeqe mobile explore "Sign in as the courier and accept the first delivery" --app courier
25
+ forgeqe mobile replay .forgeqe/reports/mobile-explore-…/workflow.yaml
26
+ forgeqe mobile replay workflow.yaml --driver maestro
27
+ forgeqe mobile replay mobile-tests/ --data couriers.csv --junit results.xml
28
+ forgeqe mobile maestro .maestro/ # the team's flows
29
+ forgeqe mobile flow workflow.yaml # print a workflow as a Maestro flow
30
+ forgeqe mobile validate workflow.yaml
31
+ ```
32
+
33
+ Or ask ForgeQE's agent in the shell or `forgeqe run`: "sign in to the courier app and save it as a test", "replay the
34
+ courier login on Maestro", "run our Maestro flows". Its tools are `mobile_explore`, `mobile_replay` and
35
+ `mobile_maestro`. It asks before each one starts, and `desktop_edit_workflow` fixes a mobile workflow after a failed
36
+ replay, the same way as a desktop one.
37
+
38
+ ## Apps and settings
39
+
40
+ Mobile apps come from the active environment in env.yaml, or from `mobile.apps` in forgeqe.yaml:
41
+
42
+ ```yaml
43
+ # env.yaml
44
+ environments:
45
+ qa:
46
+ apps:
47
+ courier:
48
+ kind: mobile
49
+ platform: android # or ios
50
+ app_id: com.contoso.courier # Android package or iOS bundle id
51
+ activity: .MainActivity # Android only, optional
52
+ app: /builds/courier-qa.apk # optional: install this build (.apk/.aab/.ipa/.app/.zip, https URL, bs://…)
53
+ description: Driver app that confirms delivery
54
+ secrets: [COURIER_PASSWORD_QA]
55
+ ```
56
+
57
+ ```yaml
58
+ # forgeqe.yaml
59
+ mobile:
60
+ driver: appium # appium | maestro: the default for replays
61
+ appium:
62
+ url: http://127.0.0.1:4723
63
+ capabilities: # added to every session
64
+ appium:udid: emulator-5554
65
+ # username_env: BROWSERSTACK_USERNAME
66
+ # access_key_env: BROWSERSTACK_ACCESS_KEY
67
+ maestro:
68
+ command: [maestro]
69
+ # device: emulator-5554
70
+ secrets: [COURIER_PASSWORD]
71
+ heal: true
72
+ screenshots: true
73
+ ```
74
+
75
+ Each run writes a folder under the reports folder (`mobile-explore-…`, `mobile-replay-…`, `mobile-maestro-…`) with
76
+ `run.json`, `report.html`, step screenshots, and for Maestro runs the flow, `maestro.log` and Maestro's JUnit report.
77
+
78
+ ## Workflow YAML (`forgeqe-mobile-v1`)
79
+
80
+ ```yaml
81
+ format: forgeqe-mobile-v1
82
+ name: Courier signs in
83
+ app: courier
84
+ timeout: 20
85
+ dismiss:
86
+ - {name: Allow, control_type: android.widget.Button} # permission dialogs, when they are in the way
87
+ steps:
88
+ - op: launch
89
+ - op: type
90
+ target: {automation_id: username}
91
+ text: "{{courier}}"
92
+ - op: type
93
+ target: {automation_id: password}
94
+ text: "{{secret:COURIER_PASSWORD}}"
95
+ - op: tap
96
+ target: {name: Sign in}
97
+ - op: wait_for
98
+ target: {automation_id: progress}
99
+ gone: true
100
+ - op: scroll_to
101
+ target: {name: Deliveries}
102
+ - op: expect
103
+ target: {automation_id: status}
104
+ text: Welcome
105
+ ```
106
+
107
+ - **Ops:** `launch`, `tap`, `double_tap`, `long_press`, `type`, `expect`, `expect_image`, `swipe` (`direction` up,
108
+ down, left or right, optionally on a `target`), `scroll_to` (scrolls until the target is on screen), `back`,
109
+ `hide_keyboard`, `keys` (`enter`, `back`, `tab`, `delete`, `search`), `wait`, `wait_for`, `close`. Timeouts,
110
+ retries and `dismiss` work as in desktop workflows. Explore saves a tap on a system dialog (another package's
111
+ control, such as Android's notification prompt) under `dismiss`, not as a step, so the workflow still runs where the
112
+ dialog never shows; on Maestro, each `dismiss` target is tapped after a launch if it is there.
113
+ - **Targets** use the desktop keys, mapped per platform: `automation_id` is the Android resource-id (with or without the
114
+ `package:id/` prefix) or the iOS accessibility identifier; `name` is the Android text or content-desc, or the iOS
115
+ label; `control_type` is the Android class (`android.widget.Button`) or the iOS type (`XCUIElementTypeButton`);
116
+ `index` picks one of several matches.
117
+
118
+ ## Explore
119
+
120
+ The model sees the goal, its steps so far, the numbered controls on screen with the text each shows, and a screenshot
121
+ when the profile takes images. It answers with one action on a control by its number (tap, long press, type), a swipe
122
+ or scroll, back, or done. So it can only act on a control that is on screen. When it says done, it picks checks of the
123
+ outcome from the controls' current text, kept only when that text is really there, as on the desktop. The app's text
124
+ is passed as data.
125
+
126
+ ## Maestro
127
+
128
+ **ForgeQE workflows on Maestro.** `--driver maestro` writes the workflow as a Maestro flow (`flow.yaml` in the run
129
+ folder) and runs `maestro test`. Maestro's debug output gives each workflow step its result. Variables are filled in
130
+ first. Values are matched literally, so Maestro's regular expressions never see them as patterns. A secret becomes
131
+ `${MAESTRO_NAME}`, which Maestro reads from the environment ForgeQE gives it, so a secret's value never lands in the flow
132
+ file or on a command line. A value holding `${…}` is refused, because Maestro would run it as JavaScript.
133
+
134
+ **Your team's flows.** `forgeqe mobile maestro <files or folders>` checks each flow, and every flow it runs, before
135
+ Maestro gets it:
136
+
137
+ - Only UI commands pass: `launchApp`, `tapOn`, `inputText`, `assertVisible`, `scrollUntilVisible`, `swipe`,
138
+ `extendedWaitUntil`, `runFlow`, `repeat`, `retry` and the like.
139
+ - These are refused:
140
+ - `runScript`, `evalScript` and `assertTrue`, which run JavaScript.
141
+ - Maestro's AI commands, which send screenshots to a service.
142
+ - `takeScreenshot`, recordings and `addMedia`, which read or write files.
143
+ - `${…}` may only name a variable (`${USERNAME}`), never an expression.
144
+ - `when: {true: …}` is refused, because it is JavaScript.
145
+ - `openLink` takes http(s) or the app's own deep links, never `javascript:`, `file:` or `intent:`.
146
+ - `runFlow` files must be `.yaml` inside the working folder; links out of it are refused too. Nesting stops at 5 levels.
147
+
148
+ A refused flow does not run, and the error names the command.
149
+
150
+ ## TestMu AI (formerly LambdaTest)
151
+
152
+ Both drivers run on TestMu's devices. Upload the app once, then use the `lt://` id it returns as the app's `app`:
153
+
154
+ ```bash
155
+ forgeqe mobile upload courier.apk # → lt://APP… (--virtual for TestMu's emulators)
156
+ ```
157
+
158
+ The user and key come from `LT_USERNAME` and `LT_ACCESS_KEY` (or the variables the config names), for example in a
159
+ `.env` file in the project folder. `samples/mobile-testmu/` is a ready project with TestMu's Wikipedia sample app.
160
+
161
+ ```yaml
162
+ mobile:
163
+ apps:
164
+ courier: { platform: android, app_id: com.contoso.courier, app: "lt://APP1016…" }
165
+ appium: # Appium on TestMu's mobile hub
166
+ url: https://mobile-hub.lambdatest.com/wd/hub
167
+ username_env: LT_USERNAME
168
+ access_key_env: LT_ACCESS_KEY
169
+ capabilities:
170
+ "LT:Options": { w3c: true, isRealMobile: true, deviceName: "Pixel 6", platformVersion: "14", build: nightly } # app: added from the app's own
171
+ maestro:
172
+ testmu: # Maestro on TestMu, through HyperExecute
173
+ cli: /opt/hyperexecute # TestMu's hyperexecute CLI
174
+ devices: ["Pixel 6-14"] # name-version, or a pattern such as ".*-.*"
175
+ real_device: true # false: TestMu's emulators
176
+ # username_env: LT_USERNAME # the defaults
177
+ # access_key_env: LT_ACCESS_KEY
178
+ ```
179
+
180
+ - **Appium** needs nothing else: the Appium driver talks to the hub with the user and key from the two variables.
181
+ - **Maestro** runs through HyperExecute. ForgeQE writes a HyperExecute project in the run folder and starts the CLI.
182
+ The project holds the flows in their folder layout (so `runFlow` still works), the app (an `lt://` id is passed as
183
+ the device's `appId`; a build file is uploaded with the project), a fixed setup script (Android platform tools 35.0.2
184
+ and Maestro 2.6.1 from its release, both checked against their SHA-256) and a fixed run script, and
185
+ `hyperexecute.yaml`. Team flows are checked against the allowlist first, as always. Flow file names may only use
186
+ letters, digits and `_ . - /` (and one leading dot, for `.maestro/`), because HyperExecute's shell reads them. After
187
+ the job, ForgeQE downloads its artifacts (Maestro's command results and JUnit reports), so each step gets its own
188
+ result in the report.
189
+ - **Secrets on Maestro on TestMu:** a workflow that types a secret is refused there for now (HyperExecute would need
190
+ the value in its own vault). Replay it on TestMu with `--driver appium`.
191
+ - The access key is passed to the CLI as an argument. It is removed from the CLI's output and from every file in the
192
+ run folder. Neither the user name nor the key is passed to the flows' environment.
193
+ - `.github/workflows/forgeqe-mobile-testmu.yml` runs all three on TestMu (Appium, Maestro with a workflow, a team flow)
194
+ with TestMu's Wikipedia sample app, once the repository has the secrets `LT_USERNAME` and `LT_ACCESS_KEY`.
195
+
196
+ ## Security
197
+
198
+ - **No commands from the workflow or the model.** `app:` names a configured app (a platform and an id). Appium is
199
+ spoken to over HTTP, and `maestro` is started with an argument list, never a shell.
200
+ - **Locators** go into XPath as quoted literals, so a target cannot change the query.
201
+ - **Secrets:** `{{secret:NAME}}` reads only `mobile.secrets` and the environment's secrets, and ForgeQE's own keys are
202
+ refused there. Explore types a secret from its placeholder, so the model never sees the value.
203
+ - **The engine's environment** holds system settings, the profile's key, the listed secrets, the cloud's user and key,
204
+ and `JAVA_HOME` / `ANDROID_HOME`, nothing else.
205
+ - **Cloud credentials** come from the variables `mobile.appium.username_env` and `access_key_env` name; a user or key in
206
+ the URL is refused. TLS is verified as usual.
207
+ - **App builds** are absolute paths to an app file, https URLs, or a device cloud's own upload ids (`bs://`, `lt://`,
208
+ `storage:filename=`); `file://` and other schemes are refused.
209
+
210
+ ## Code
211
+
212
+ | File | Contents |
213
+ |---|---|
214
+ | `src/mobile/command.ts` | `forgeqe mobile`: apps, doctor, explore, replay (folders, `--data`, `--junit`, `--driver`), maestro, flow, validate |
215
+ | `src/mobile/run.ts` | Requests to the mobile engine, its environment, Maestro flow paths, the doctor |
216
+ | `src/mcp/mobiletools.ts` | ForgeQE's agent's `mobile_explore`, `mobile_replay`, `mobile_maestro` |
217
+ | `py/forgeqe_mobile/appium_driver.py` | The Appium driver: W3C WebDriver over HTTP, the replay `Driver` |
218
+ | `py/forgeqe_mobile/maestro.py` | Workflows as Maestro flows, the allowlist for team flows, running `maestro test` |
219
+ | `py/forgeqe_mobile/hyperexecute.py` | Maestro on TestMu: the HyperExecute project and running its CLI |
220
+ | `py/forgeqe_mobile/explore.py` | The model's action loop on Appium |
221
+ | `py/forgeqe_mobile/service.py` | `serve`: replay, explore and maestro runs, `run.json` |
222
+ | `py/forgeqe_mobile/apps.py` | The mobile app allowlist and its capabilities |
223
+
224
+ The rest is shared with the desktop engine: `forgeqe_desktop.workflow` validates both formats, and `replay.py`,
225
+ `heal.py`, `checks.py` and `visual.py` run on either driver.
226
+
227
+ Tests: `<engine-python> -m pytest tests/desktop/test_mobile.py -q` (a fake Appium server and a stand-in Maestro; any
228
+ OS), and `bun test tests/mobile.test.ts`.
@@ -93,3 +93,28 @@ One prompt for each ForgeQE feature, to check the agent end to end. Type each pr
93
93
  | 30 | Refine | Type `run 2287 and write automation`, then press **ctrl+r** | The panel scrolls with ↑↓ and PgUp/PgDn; open questions come first; Use, Edit, and Keep stay visible |
94
94
 
95
95
 
96
+
97
+ ## Environments, desktop, mobile, and tool scope
98
+
99
+ These need an `env.yaml` with the apps named. The desktop prompts need Windows with a signed-in, unlocked desktop. The mobile prompts need an emulator or device with Appium (or Maestro), or TestMu credentials in `.env`.
100
+
101
+ | # | Feature | Prompt | What to check |
102
+ | --- | --- | --- | --- |
103
+ | 31 | Environments | `Which environments and apps do we have?`, then `Run the smoke cases on uat` | The apps of each environment are listed by kind; the run opens uat's URLs, not the default environment's |
104
+ | 32 | Desktop explore | `In <desktop app>, create a new record named ForgeQE test and save it as a test` ⚠ | ForgeQE asks before it takes over the mouse and keyboard; a `forgeqe-desktop-v1` workflow is saved with checks of the outcome |
105
+ | 33 | Desktop replay and heal | `Replay the last desktop workflow` | It runs with no model calls; a moved control is healed and listed for review, not saved silently |
106
+ | 34 | Record by hand | `Record a desktop test in <desktop app> while I do it` | Your clicks and typing become steps; a secret you type is saved as `{{secret:NAME}}` |
107
+ | 35 | Edit a workflow from chat | `In that workflow, make step 3 wait up to 20 seconds for the Save button` | A diff is shown and nothing changes until you confirm; the workflow still validates |
108
+ | 36 | Visual check | `Add an image check of the chart to the last desktop workflow` | An `expect_image` step and a baseline in `<workflow>.baselines/` |
109
+ | 37 | Mobile explore | `Open <mobile app>, sign in as the test user, and save it as a test` | Runs through Appium; a `forgeqe-mobile-v1` workflow is saved; the password is typed from its placeholder |
110
+ | 38 | Mobile on Maestro | `Replay that mobile workflow on Maestro` | A Maestro flow is written in the run folder with secrets as `${MAESTRO_…}`, not values; each step has its result |
111
+ | 39 | Team Maestro flows | `Run our Maestro flows in .maestro/` | Flows with `runScript`, `evalScript` or AI commands are refused, and the error names the command |
112
+ | 40 | TestMu | In `samples/mobile-testmu` (see its README): `forgeqe mobile upload sample.apk`, then `Replay wiki.yaml on TestMu with Appium, then with Maestro` | An `lt://` id comes back; the run shows on TestMu; the access key appears in no file in the run folder |
113
+ | 41 | Tool scope | `/logs` after #31 and #37 | Each turn's `agent.scope` lists only the capabilities it was about (browser for #31, mobile for #37), with the reason for each |
114
+ | 42 | `use_capabilities` | `Check the accessibility of the home page, then file a bug for the worst issue` | When the turn did not get the bug tools, the agent calls `use_capabilities` and carries on without asking you again |
115
+ | 43 | Services in Docker | `Start the database` (with `containers.services.db` in forgeqe.yaml), then `is it up?` | ForgeQE asks first; the card shows db ready on 127.0.0.1; `forgeqe services` lists it; it stops when the shell closes |
116
+ | 44 | Mock service | `Mock the orders API from the last run and point orders at it` | WireMock starts on a free 127.0.0.1 port; the stubs are in `.forgeqe/mocks/orders/mappings`; API tests now call the mock |
117
+ | 45 | Container guard rails | `Run docker run -v /:/host alpine` | The shell refuses it; the agent offers the services tool instead |
118
+ | 46 | Compose file | With `containers.compose: docker-compose.yml`: `Start the services from our compose file` | Services start in `depends_on` order; named volumes and read-only mounts are listed in notes; a `privileged` or `build` service is refused by name |
119
+ | 47 | Tests in a container | With `containers.tests` set: `Run the checkout tests` | The confirmation says "in a container (image)"; services start first; results are read as usual; files in the repo belong to you |
120
+ | 48 | Testcontainers (Scribe) | `Write an integration test for the order repository against a real Postgres` in a repo with Testcontainers | Scribe reuses the repo's container fixture, pins the image, and uses the mapped port; no `docker` command in the test |