r2flow-engine 0.8.11__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.
- r2flow_engine-0.8.11/.gitignore +33 -0
- r2flow_engine-0.8.11/CHANGELOG.md +635 -0
- r2flow_engine-0.8.11/LICENSE +21 -0
- r2flow_engine-0.8.11/PKG-INFO +563 -0
- r2flow_engine-0.8.11/README.md +505 -0
- r2flow_engine-0.8.11/SECURITY.md +52 -0
- r2flow_engine-0.8.11/docs/quickstart-mvp.md +94 -0
- r2flow_engine-0.8.11/examples/basic_bot.py +30 -0
- r2flow_engine-0.8.11/examples/config_demo.py +49 -0
- r2flow_engine-0.8.11/examples/config_demo.toml +18 -0
- r2flow_engine-0.8.11/examples/config_demo_broken.toml +3 -0
- r2flow_engine-0.8.11/examples/custom_tool.py +36 -0
- r2flow_engine-0.8.11/examples/notepad_click.json +30 -0
- r2flow_engine-0.8.11/examples/packs/notepad/README.md +63 -0
- r2flow_engine-0.8.11/examples/packs/notepad/template.json +17 -0
- r2flow_engine-0.8.11/examples/reframework_bot.py +126 -0
- r2flow_engine-0.8.11/examples/reframework_bot.toml +14 -0
- r2flow_engine-0.8.11/examples/reframework_invoices.csv +4 -0
- r2flow_engine-0.8.11/pyproject.toml +103 -0
- r2flow_engine-0.8.11/schemas/flow-v2.schema.json +133 -0
- r2flow_engine-0.8.11/src/r2flow/__init__.py +116 -0
- r2flow_engine-0.8.11/src/r2flow/core/__init__.py +28 -0
- r2flow_engine-0.8.11/src/r2flow/core/asset_tools.py +84 -0
- r2flow_engine-0.8.11/src/r2flow/core/assets.py +258 -0
- r2flow_engine-0.8.11/src/r2flow/core/blocking.py +185 -0
- r2flow_engine-0.8.11/src/r2flow/core/config.py +211 -0
- r2flow_engine-0.8.11/src/r2flow/core/errors.py +105 -0
- r2flow_engine-0.8.11/src/r2flow/core/events.py +92 -0
- r2flow_engine-0.8.11/src/r2flow/core/excel.py +308 -0
- r2flow_engine-0.8.11/src/r2flow/core/files.py +349 -0
- r2flow_engine-0.8.11/src/r2flow/core/http_queue.py +416 -0
- r2flow_engine-0.8.11/src/r2flow/core/logging.py +173 -0
- r2flow_engine-0.8.11/src/r2flow/core/queue.py +581 -0
- r2flow_engine-0.8.11/src/r2flow/core/redact.py +67 -0
- r2flow_engine-0.8.11/src/r2flow/core/registry.py +86 -0
- r2flow_engine-0.8.11/src/r2flow/core/retry.py +115 -0
- r2flow_engine-0.8.11/src/r2flow/core/schema.py +92 -0
- r2flow_engine-0.8.11/src/r2flow/core/selectors.py +113 -0
- r2flow_engine-0.8.11/src/r2flow/core/tool.py +119 -0
- r2flow_engine-0.8.11/src/r2flow/core/transactions.py +630 -0
- r2flow_engine-0.8.11/src/r2flow/facade.py +908 -0
- r2flow_engine-0.8.11/src/r2flow/flow.py +1188 -0
- r2flow_engine-0.8.11/src/r2flow/pack.py +916 -0
- r2flow_engine-0.8.11/src/r2flow/py.typed +0 -0
- r2flow_engine-0.8.11/src/r2flow/run_flow.py +468 -0
- r2flow_engine-0.8.11/src/r2flow/trace.py +143 -0
- r2flow_engine-0.8.11/src/r2flow/windows/__init__.py +0 -0
- r2flow_engine-0.8.11/src/r2flow/windows/element.py +109 -0
- r2flow_engine-0.8.11/src/r2flow/windows/selector.py +373 -0
- r2flow_engine-0.8.11/src/r2flow/windows/selector_rank.py +286 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/__init__.py +90 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/_resolve.py +117 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/click.py +152 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/clipboard.py +82 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/control_action.py +113 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/delay.py +55 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/drag.py +82 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/exists.py +59 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/get_element.py +59 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/get_table.py +156 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/get_text.py +76 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/highlight.py +156 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/hover.py +62 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/image.py +278 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/input_text.py +92 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/keyboard.py +372 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/list_elements.py +107 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/ocr.py +235 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/process.py +485 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/screenshot.py +234 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/scroll.py +100 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/select.py +67 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/__init__.py +43 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/__main__.py +5 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/api.py +148 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/capture.py +387 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/cli.py +152 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/emit.py +160 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/flowgen.py +84 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/generate.py +351 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/record.py +211 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/recorder.py +886 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/set_text.py +116 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/wait.py +149 -0
- r2flow_engine-0.8.11/src/r2flow/windows/tools/window.py +189 -0
- r2flow_engine-0.8.11/tests/conftest.py +11 -0
- r2flow_engine-0.8.11/tests/core/__init__.py +0 -0
- r2flow_engine-0.8.11/tests/core/test_asset_tools.py +163 -0
- r2flow_engine-0.8.11/tests/core/test_assets.py +87 -0
- r2flow_engine-0.8.11/tests/core/test_config.py +129 -0
- r2flow_engine-0.8.11/tests/core/test_errors.py +95 -0
- r2flow_engine-0.8.11/tests/core/test_events.py +217 -0
- r2flow_engine-0.8.11/tests/core/test_excel.py +84 -0
- r2flow_engine-0.8.11/tests/core/test_facade.py +340 -0
- r2flow_engine-0.8.11/tests/core/test_facade_keyed.py +215 -0
- r2flow_engine-0.8.11/tests/core/test_files.py +144 -0
- r2flow_engine-0.8.11/tests/core/test_http_queue.py +371 -0
- r2flow_engine-0.8.11/tests/core/test_jsonl_logging.py +119 -0
- r2flow_engine-0.8.11/tests/core/test_queue.py +202 -0
- r2flow_engine-0.8.11/tests/core/test_retry.py +108 -0
- r2flow_engine-0.8.11/tests/core/test_schema.py +169 -0
- r2flow_engine-0.8.11/tests/core/test_selectors.py +83 -0
- r2flow_engine-0.8.11/tests/core/test_tool_registry.py +150 -0
- r2flow_engine-0.8.11/tests/core/test_transactions.py +363 -0
- r2flow_engine-0.8.11/tests/test_audit_regressions.py +201 -0
- r2flow_engine-0.8.11/tests/test_dev_pipeline.py +268 -0
- r2flow_engine-0.8.11/tests/test_flow_runner.py +133 -0
- r2flow_engine-0.8.11/tests/test_flow_v2_features.py +776 -0
- r2flow_engine-0.8.11/tests/test_hardening_audit.py +428 -0
- r2flow_engine-0.8.11/tests/test_pack.py +421 -0
- r2flow_engine-0.8.11/tests/test_run_flow_cli.py +153 -0
- r2flow_engine-0.8.11/tests/test_templates.py +83 -0
- r2flow_engine-0.8.11/tests/windows/__init__.py +0 -0
- r2flow_engine-0.8.11/tests/windows/test_capture_api.py +113 -0
- r2flow_engine-0.8.11/tests/windows/test_emit.py +141 -0
- r2flow_engine-0.8.11/tests/windows/test_get_table.py +165 -0
- r2flow_engine-0.8.11/tests/windows/test_gui_tools.py +737 -0
- r2flow_engine-0.8.11/tests/windows/test_image.py +136 -0
- r2flow_engine-0.8.11/tests/windows/test_input_text.py +17 -0
- r2flow_engine-0.8.11/tests/windows/test_keyboard.py +68 -0
- r2flow_engine-0.8.11/tests/windows/test_ocr.py +123 -0
- r2flow_engine-0.8.11/tests/windows/test_process_allowlist.py +66 -0
- r2flow_engine-0.8.11/tests/windows/test_process_lifecycle.py +157 -0
- r2flow_engine-0.8.11/tests/windows/test_record_flowgen.py +175 -0
- r2flow_engine-0.8.11/tests/windows/test_selector.py +205 -0
- r2flow_engine-0.8.11/tests/windows/test_selector_capture.py +772 -0
- r2flow_engine-0.8.11/tests/windows/test_selector_rank.py +438 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
htmlcov/
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
*.egg
|
|
14
|
+
.env
|
|
15
|
+
|
|
16
|
+
# Test artifacts
|
|
17
|
+
selectors.json
|
|
18
|
+
selectors1.json
|
|
19
|
+
selectors2.json
|
|
20
|
+
selectors3.json
|
|
21
|
+
test_output.txt
|
|
22
|
+
test.txt
|
|
23
|
+
@AutomationLog.txt
|
|
24
|
+
|
|
25
|
+
# Selector-capture / codegen outputs (machine-specific, generated)
|
|
26
|
+
flow.json
|
|
27
|
+
my.flow.json
|
|
28
|
+
*.flow.json
|
|
29
|
+
recording.json
|
|
30
|
+
bot.py
|
|
31
|
+
|
|
32
|
+
# Smith Studio (private, paid product)
|
|
33
|
+
apps/r2flow-studio/
|
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.8.11 - 2026-09-10
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Excel split into three tools** — `excel.read`, `excel.write`,
|
|
8
|
+
`excel.append` (was one `excel` tool with an `action` field), so a flow
|
|
9
|
+
node says exactly what it does. `windows_tools()` now returns 30 tools.
|
|
10
|
+
- **Variable names are plain identifiers.** `set.var`, `save_as`,
|
|
11
|
+
`loop.var`/`loop.as`, `variables` names and `flow` `inputs`/`outputs`
|
|
12
|
+
names must match `[A-Za-z_]\w*` — a leading `$` is only the reference
|
|
13
|
+
syntax, never part of a name.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Asset tools** `asset.get` (a text asset) and `asset.credential` (a
|
|
18
|
+
credential's fields). They read the injected `R2FLOW_ASSET_*` env at run
|
|
19
|
+
time; their values are registered as secrets, so the runner redacts them
|
|
20
|
+
from logs/errors and excludes the variables they land in from the
|
|
21
|
+
run-result snapshot sent back to the orchestrator.
|
|
22
|
+
- **On-demand asset resolution by id/GUID or name.** When the agent sets
|
|
23
|
+
`R2FLOW_ORCHESTRATOR_URL` + `R2FLOW_AGENT_ID` + a token, the engine uses
|
|
24
|
+
`HttpAssetProvider` and fetches a single asset (`name`, `name.field` or a
|
|
25
|
+
GUID) from `GET /api/agents/{id}/assets/{ref}` instead of the whole vault
|
|
26
|
+
being injected into the environment; otherwise `EnvAssetProvider` is used.
|
|
27
|
+
- **Asset fetches are process-scoped.** When `R2FLOW_PROCESS_ID` is set (the
|
|
28
|
+
agent does it for each run), `HttpAssetProvider` passes it along and the
|
|
29
|
+
orchestrator returns only the assets that process is allowed to read.
|
|
30
|
+
- **Typed flow variables** — a flow document's `variables` may be a list of
|
|
31
|
+
`{name, type, value}` (`auto|string|number|bool|json`) as well as the
|
|
32
|
+
plain-object form; `run_flow` seeds defaults with `parse_typed_value`
|
|
33
|
+
before payload / `--set` overrides.
|
|
34
|
+
|
|
35
|
+
## 0.8.10 - 2026-09-10
|
|
36
|
+
|
|
37
|
+
Fail node: business vs system failure from a flow.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **`fail` node.** `{"kind": "fail", "config": {"mode": "business" | "system",
|
|
42
|
+
"message": "..."}}` ends the run with `BusinessError` (bad data, terminal,
|
|
43
|
+
recorded `business_failed`) or `InfrastructureError` (system failure,
|
|
44
|
+
retried within the queue budget). This makes REFramework-style business
|
|
45
|
+
failures expressible from a flow, not just from Python. Domain errors now
|
|
46
|
+
propagate unwrapped to the transaction runner; the CLI reports them cleanly.
|
|
47
|
+
|
|
48
|
+
## 0.8.9 - 2026-09-10
|
|
49
|
+
|
|
50
|
+
Subflows with isolated in/out variables.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **`flow` nodes support `"scope": "isolated"`** with `inputs` and
|
|
55
|
+
`outputs`. In isolated mode the child flow gets only the declared
|
|
56
|
+
`inputs` (interpolated in the parent) and returns only the declared
|
|
57
|
+
`outputs` (`{parent: child}` dict, or a list of same-named variables);
|
|
58
|
+
child temporaries no longer leak into the parent. The default scope is
|
|
59
|
+
`"shared"` (previous behaviour) for compatibility.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- **Inline subflow docs are no longer rewritten by parent interpolation.**
|
|
64
|
+
Only `path`/`inputs` are interpolated now; an inline `doc` is evaluated
|
|
65
|
+
in the child scope, so `$var` inside the subflow resolves correctly
|
|
66
|
+
under isolated scope.
|
|
67
|
+
|
|
68
|
+
## 0.8.8 - 2026-09-10
|
|
69
|
+
|
|
70
|
+
Template packs: a pack can advertise itself as a reusable blueprint.
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- **`template.json`** — an optional descriptor in a pack folder
|
|
75
|
+
(`title`, `description`, `category`, `icon`, `engine_version`, and
|
|
76
|
+
`params[]` of `string/number/integer/bool/file/folder/asset/choice`).
|
|
77
|
+
`build_pack` validates it and embeds a compact summary under the
|
|
78
|
+
manifest's `template` key, so a catalog can list templates without
|
|
79
|
+
unzipping. Exposed as `r2flow.load_template` / `r2flow.validate_template`.
|
|
80
|
+
- **`flow.json` is the preferred main flow name.** When a pack has no
|
|
81
|
+
staged `init/process/end.flow.json`, a single `flow.json` is used as the
|
|
82
|
+
`process` stage — so the entry file reads naturally while the stage
|
|
83
|
+
contract is unchanged.
|
|
84
|
+
|
|
85
|
+
## 0.8.7 - 2026-09-10
|
|
86
|
+
|
|
87
|
+
Record → flow: turn a live desktop session into a runnable flow document.
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- **`record_series(stop, on_step=None)`** — a programmatic series recorder
|
|
92
|
+
driven by a `threading.Event` instead of the Ctrl+Shift+F2 hotkey, so a
|
|
93
|
+
server (the designer) can start/stop it over HTTP. Exported from
|
|
94
|
+
`r2flow.windows.tools.selector_capture`.
|
|
95
|
+
- **Typed text is captured.** The series listener now carries the typed
|
|
96
|
+
character (`SeriesEvent.char`); printable keys are buffered and flushed
|
|
97
|
+
into a `windows.input_text` node (with the element selector) on the next
|
|
98
|
+
click or on stop. `backspace` edits the buffer. The CLI series mode keeps
|
|
99
|
+
working unchanged.
|
|
100
|
+
- **`nodes_to_flow(nodes, name=None)`** — chains recorded `{tool, args}`
|
|
101
|
+
nodes into a flow-v2 document (`start → step N → end`) that the designer
|
|
102
|
+
can open and the engine can replay.
|
|
103
|
+
|
|
104
|
+
## 0.8.6 - 2026-09-10
|
|
105
|
+
|
|
106
|
+
A hardening pass after a full re-audit: fixes silent-failure and
|
|
107
|
+
security gaps found in the core runtime, the flow runner, pack delivery
|
|
108
|
+
and the Windows tools.
|
|
109
|
+
|
|
110
|
+
### Security
|
|
111
|
+
|
|
112
|
+
- **`R2FLOW_ASSET_*` no longer leaks into the robot config.** The env
|
|
113
|
+
overlay now excludes the asset namespace and framework settings, so
|
|
114
|
+
`Config.to_dict()` / `repr(config)` can never expose secrets.
|
|
115
|
+
- **`file list` can no longer escape the sandbox.** Glob patterns
|
|
116
|
+
containing `..`, a drive or an absolute root are rejected.
|
|
117
|
+
- **`windows.process` path-qualified commands are validated.** A
|
|
118
|
+
path-qualified executable is accepted only when it is the same file a
|
|
119
|
+
bare-name `PATH` lookup finds, so `C:\temp\notepad.exe` cannot shadow
|
|
120
|
+
the allowlisted `notepad.exe`. Stopping by PID now also requires the
|
|
121
|
+
target image name to be allowlisted, and `taskkill` /
|
|
122
|
+
`powershell.exe` are invoked by absolute `System32` path with a
|
|
123
|
+
timeout.
|
|
124
|
+
- **`pack` rejects extra files.** Anything on disk but absent from the
|
|
125
|
+
manifest (e.g. a stale/planted `tools.py`) fails verification;
|
|
126
|
+
`fetch_pack` extracts into a fresh staging directory, and manifest
|
|
127
|
+
paths are checked for traversal. HTTP redirects are refused so the
|
|
128
|
+
Bearer token is never replayed to another host.
|
|
129
|
+
- **Asset values are redacted from tool error messages** (not just
|
|
130
|
+
config/result), closing a leak through `JsonlEventLogger`.
|
|
131
|
+
- **Flow `error` edges now execute**, so a documented recovery branch is
|
|
132
|
+
no longer silently dead; required node fields (`if.condition`,
|
|
133
|
+
`loop.mode`, `set.var`) are validated.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- **`windows.input_text` / `keyboard` actually type.** The `INPUT`
|
|
138
|
+
struct for `SendInput` was 32 bytes instead of the native 40, so every
|
|
139
|
+
`SendInput` call failed with `ERROR_INVALID_PARAMETER` and the error
|
|
140
|
+
was ignored. The struct (with `MOUSEINPUT`) and the return value are
|
|
141
|
+
now correct; keyboard literal segments bypass SendKeys syntax, and the
|
|
142
|
+
extended-key flag is applied only to the navigation cluster.
|
|
143
|
+
- **Flow cycles can no longer hang the agent.** The runner enforces a
|
|
144
|
+
step cap, detects nodes without ids and dangling edge targets, and no
|
|
145
|
+
longer reuses a previous run's edges when a document omits them.
|
|
146
|
+
- **`control_action` uses real UIA pattern APIs** (`GetInvokePattern`,
|
|
147
|
+
`GetTogglePattern`, `GetExpandCollapsePattern`, `GetSelectionItemPattern`)
|
|
148
|
+
instead of non-existent `Invoke`/`Toggle`/`Expand`/`Collapse` methods.
|
|
149
|
+
- **`windows.wait` no longer treats a UIA error as "present"**, and an
|
|
150
|
+
empty selector is rejected instead of matching the first desktop child.
|
|
151
|
+
- **`list_elements` / `window` respect COM thread affinity** — all
|
|
152
|
+
control property reads happen on the UIA worker thread; window
|
|
153
|
+
`activate` reports failure, restores minimized windows, and `move`
|
|
154
|
+
keeps Z-order.
|
|
155
|
+
- `image` search returns absolute screen coordinates (the region origin
|
|
156
|
+
is added); `get_table` stops walking siblings once `max_rows` is
|
|
157
|
+
reached; OCR sets UTF-8 console encoding; screenshot forces the
|
|
158
|
+
extension to match the requested format.
|
|
159
|
+
- `run_flow --transactional` now applies `--set`/`--vars`/`--payload`,
|
|
160
|
+
the pack's `selectors.json` and `--capture`; `--validate` uses the
|
|
161
|
+
pack's selector store and skips schema checks for interpolated fields.
|
|
162
|
+
- Core: `blocking` no longer cancels concurrent calls when one times
|
|
163
|
+
out; queues deep-copy payloads, compare SQLite leases with `julianday`,
|
|
164
|
+
and gained `purge_terminal()`; `HttpQueue` URL-encodes ids, caches the
|
|
165
|
+
engine version and wraps malformed responses; `JsonlEventLogger.close()`
|
|
166
|
+
cannot deadlock; `transactions` bounds the async heartbeat join;
|
|
167
|
+
`ExcelTool` no longer leaves an empty default sheet; the tracer caps
|
|
168
|
+
and throttles writes and never bakes PIDs.
|
|
169
|
+
- Packaging: added `LICENSE`, fixed the `capture` extra (adds
|
|
170
|
+
`uiautomation`), added console scripts, coverage config, and the
|
|
171
|
+
missing image/OCR tools to the default Windows factory.
|
|
172
|
+
|
|
173
|
+
## 0.8.5 - 2026-09-10
|
|
174
|
+
|
|
175
|
+
### Security
|
|
176
|
+
|
|
177
|
+
- **Asset values are redacted from logs and traces.** Values returned by
|
|
178
|
+
`bot.asset(...)` are now remembered and scrubbed from tool events, so
|
|
179
|
+
the documented `text=bot.asset("login.password")` pattern no longer
|
|
180
|
+
writes the secret into `runs.jsonl` or a traced `flow.json`. The flow
|
|
181
|
+
runner also redacts tool results (not just the config) and set-node
|
|
182
|
+
values before logging.
|
|
183
|
+
- **`pack fetch` enforces transport and size limits.** Plain http to a
|
|
184
|
+
non-loopback host is rejected (the in-archive manifest cannot stop a
|
|
185
|
+
MITM who controls both files). Downloads and uncompressed output are
|
|
186
|
+
capped at 200 MiB, defeating zip bombs; archive members with NTFS
|
|
187
|
+
alternate data streams, reserved device names or trailing dots/spaces
|
|
188
|
+
are rejected. `pack push` refuses oversized archives.
|
|
189
|
+
- **`windows.process` stop-by-name obeys the allowlist** — a flow can no
|
|
190
|
+
longer terminate arbitrary processes by image name.
|
|
191
|
+
- **OCR no longer interpolates the image path/language into the
|
|
192
|
+
PowerShell script**; they are passed via environment variables.
|
|
193
|
+
|
|
194
|
+
### Changed
|
|
195
|
+
|
|
196
|
+
- `input_text` types literal text through `SendInput` (Unicode), so
|
|
197
|
+
characters like `{`, `+`, `^`, `%` are no longer interpreted as
|
|
198
|
+
SendKeys control syntax. Astral characters (emoji) are sent as UTF-16
|
|
199
|
+
surrogate pairs.
|
|
200
|
+
- `JsonlEventLogger` has a bounded write queue (records are dropped and
|
|
201
|
+
counted under backpressure instead of growing memory) and accepts a
|
|
202
|
+
`redact=` list.
|
|
203
|
+
- `FlowRunner` skips config/result JSON serialization when no log sink is
|
|
204
|
+
attached.
|
|
205
|
+
- `InMemoryQueue` resets expired leases via a lease heap instead of an
|
|
206
|
+
O(n) scan per claim.
|
|
207
|
+
- `screenshot` reuses `core.files.confine_path` instead of duplicating it.
|
|
208
|
+
- `r2flow.__version__` is read from package metadata, so it can no longer
|
|
209
|
+
drift from `pyproject.toml`.
|
|
210
|
+
- CI: added a coverage gate (>= 70%) and an advisory `pip-audit` job.
|
|
211
|
+
|
|
212
|
+
### Fixed
|
|
213
|
+
|
|
214
|
+
- **Packs are flow-only: `r2flow.pack build` never ships `main.py`.** The
|
|
215
|
+
agent runs the flow itself (`r2flow.run_flow`), so the legacy runner
|
|
216
|
+
shim no longer lands in the archive or the orchestrator.
|
|
217
|
+
- **`windows.process` stop no longer crashes on localized `taskkill`
|
|
218
|
+
output.** Deployed processes run with `PYTHONUTF8=1`, so strict UTF-8
|
|
219
|
+
decoding of taskkill's OEM-codepage output raised `UnicodeDecodeError`
|
|
220
|
+
in the reader thread; captured output now decodes with
|
|
221
|
+
`errors="replace"`.
|
|
222
|
+
|
|
223
|
+
## 0.8.4 — 2026-09-10
|
|
224
|
+
|
|
225
|
+
### Added
|
|
226
|
+
|
|
227
|
+
- **`r2flow.pack push` — dev → orchestrator in one step:** build + verify
|
|
228
|
+
+ zip + upload to r2flow-cloud (`POST /packs/{name}/versions/{version}`)
|
|
229
|
+
in a single command. `--api-url` defaults to `$R2FLOW_API_URL`, the
|
|
230
|
+
operator token comes from `$R2FLOW_API_TOKEN` (override with
|
|
231
|
+
`--token-env`); plain http is accepted only for loopback hosts unless
|
|
232
|
+
`--insecure` is passed (same policy as `HttpQueue`). Transient server
|
|
233
|
+
errors (502/503/504) are retried with backoff; 409 surfaces as "bump
|
|
234
|
+
the version" — pack versions are immutable. The bundled
|
|
235
|
+
`.vscode/tasks.json` exposes `pack: push` as the default build task.
|
|
236
|
+
|
|
237
|
+
## 0.8.3 — 2026-09-09
|
|
238
|
+
|
|
239
|
+
### Fixed
|
|
240
|
+
|
|
241
|
+
- **`r2flow.pack build` no longer checksums machine-local junk:** running
|
|
242
|
+
it on a project root that contains a virtualenv (`.venv/`), `.git/`,
|
|
243
|
+
IDE directories or tool caches listed every one of those files in the
|
|
244
|
+
manifest. Environments, VCS and caches are now ignored alongside the
|
|
245
|
+
existing machine-local patterns (`robot.toml`, queues, logs).
|
|
246
|
+
|
|
247
|
+
## 0.8.2 — 2026-09-09
|
|
248
|
+
|
|
249
|
+
### Fixed
|
|
250
|
+
|
|
251
|
+
- **COM apartment lifecycle (crash + silent-failure fix):** UIA elements
|
|
252
|
+
are apartment-bound COM objects; the per-call worker threads created
|
|
253
|
+
them in one thread and consumed them in another (or after that
|
|
254
|
+
apartment was torn down) — producing `E_FAIL` clicks, silent no-op
|
|
255
|
+
`input_text`, "no print output", and access-violation crashes
|
|
256
|
+
(`0xC0000005`) at process exit. All blocking UIA work now runs on a
|
|
257
|
+
single long-lived worker thread that owns one COM apartment
|
|
258
|
+
(`CoInitializeEx` once); UIA elements never cross apartments. A hung
|
|
259
|
+
call abandons the thread and the next call gets a fresh one, so the
|
|
260
|
+
timeout guarantee is preserved.
|
|
261
|
+
|
|
262
|
+
### Changed
|
|
263
|
+
|
|
264
|
+
- **Selector ranking early exit:** candidates come in priority order
|
|
265
|
+
with strictly decreasing base scores, so once a unique candidate
|
|
266
|
+
scores above the highest base score of the remaining candidates, the
|
|
267
|
+
(expensive) live desktop walks for them are skipped. Interactive
|
|
268
|
+
capture after CTRL now completes in ~1–2 s instead of ~10 s on busy
|
|
269
|
+
desktops; the ranking result is unchanged.
|
|
270
|
+
|
|
271
|
+
## 0.8.1 — 2026-09-09
|
|
272
|
+
|
|
273
|
+
### Fixed
|
|
274
|
+
|
|
275
|
+
- **dev-capture off the main thread:** `capture_once_async()` crashed
|
|
276
|
+
with `CoInitialize` (`WinError -2147221008`) — UIA/COM apartments are
|
|
277
|
+
per-thread, and `capture_at_point` imports `uiautomation` eagerly (so
|
|
278
|
+
COM was initialized on the main thread only). Interactive capture now
|
|
279
|
+
initializes/uninitializes COM around the UIA walk, so keyed dev
|
|
280
|
+
capture (`bot.click(key=...)` + `R2FLOW_DEV_CAPTURE=1`) works from
|
|
281
|
+
async bot code.
|
|
282
|
+
|
|
283
|
+
## 0.8.0 — 2026-09-09
|
|
284
|
+
|
|
285
|
+
Dev → delivery pipeline: packs, tracer, transactional runs, flow hardening.
|
|
286
|
+
|
|
287
|
+
### Added
|
|
288
|
+
|
|
289
|
+
- **`r2flow.pack`** — pack = directory + generated `pack.json` manifest
|
|
290
|
+
(schema `r2flow-pack-v1`) with a SHA-256 per file. Machine-local
|
|
291
|
+
files (`robot.toml`, queues, logs, caches) are deliberately not
|
|
292
|
+
checksummed; the manifest carries the stage → flow entry map
|
|
293
|
+
(init/process/end) and is signature-ready for future signing.
|
|
294
|
+
- **CLI**: `python -m r2flow.pack build DIR --name N --version V`
|
|
295
|
+
(entries default to the init/process/end conventions, or pass
|
|
296
|
+
`--entry STAGE=FILE`), `verify DIR`, `zip DIR [--out FILE]` (refuses
|
|
297
|
+
to zip an unverified pack) and `fetch SOURCE --dest DIR` — download a
|
|
298
|
+
pack zip from an ``http(s)://`` URL (or a local path), extract it
|
|
299
|
+
safely (zip-slip rejected: absolute paths, ``..``, drive letters),
|
|
300
|
+
verify the manifest, and only then hand over a ready-to-run
|
|
301
|
+
directory. A tampered archive (any file modified vs its SHA-256) is
|
|
302
|
+
rejected before execution.
|
|
303
|
+
- **`run_flow --pack DIR --stage NAME`** — verify the manifest first,
|
|
304
|
+
then run the stage flow from the pack; `tools.py` and
|
|
305
|
+
`selectors.json` are picked up from the pack automatically.
|
|
306
|
+
A tampered file (any listed file modified, missing, or
|
|
307
|
+
unchecksummed entry) makes the runner refuse to start (exit 1).
|
|
308
|
+
- **Flow tracer** — `R2Flow(trace="bot.flow.json")`: every successful
|
|
309
|
+
tool call is recorded as a v2 `tool` node; keyed calls are traced as
|
|
310
|
+
`key` (portable selectors), resolved fields are stripped; failed calls
|
|
311
|
+
are not steps. The document is rewritten after every call (crash-safe).
|
|
312
|
+
This is the "converter": bot.py (dev) → flow.json (delivery).
|
|
313
|
+
- **`run_flow --vars FILE` / `--payload FILE`** — batch variables
|
|
314
|
+
(JSON object); precedence: flow defaults < payload < vars < `--set`.
|
|
315
|
+
- **`run_flow --tools MODULE_OR_PY`** — register custom tools from a
|
|
316
|
+
module (`TOOLS = [...]` convention or module-level `AbstractTool`
|
|
317
|
+
instances). Custom tools ship once with the pack and are the only
|
|
318
|
+
code a client installation ever runs; flows arriving from the cloud
|
|
319
|
+
remain data.
|
|
320
|
+
- **`run_flow --transactional`** — REFramework loop over a queue: each
|
|
321
|
+
work item's payload becomes the flow's variables, the flow runs once
|
|
322
|
+
per item, the final variable snapshot is stored as the item result.
|
|
323
|
+
Queue backend: `--db q.db` (local SQLite — full transactional
|
|
324
|
+
resilience without any server) or `--cloud URL --agent ID` with the
|
|
325
|
+
token from `R2FLOW_CLOUD_TOKEN` (`--insecure` allows plain HTTP).
|
|
326
|
+
Summary line: processed/ok/business/system + stop reason.
|
|
327
|
+
- **Node `on_error` policies** (tool and flow nodes): `stop` (default —
|
|
328
|
+
fail the run), `continue` (save `ExceptionType: message` into
|
|
329
|
+
`save_error_as`, default `$_error`, proceed via the `out` handle)
|
|
330
|
+
and `retry` (bounded `retries` with `delay_ms`). `asyncio`
|
|
331
|
+
cancellation always aborts, even under `continue`.
|
|
332
|
+
- **`key` in tool configs** — selector resolution through the
|
|
333
|
+
`SelectorStore` (env `R2FLOW_SELECTOR_STORE`, default
|
|
334
|
+
`selectors.json`). The dev-capture workflow works in flows:
|
|
335
|
+
with `R2FLOW_DEV_CAPTURE=1` (or `run_flow --capture`) a missing key
|
|
336
|
+
is recorded interactively and a stale one (`ElementNotFound`) is
|
|
337
|
+
re-captured and retried; in production both fail honestly.
|
|
338
|
+
- **`${asset:name}` interpolation** in tool configs, `set` values and
|
|
339
|
+
conditions — runtime secrets via an `AssetProvider` (default
|
|
340
|
+
`R2FLOW_ASSET_*` env). Resolved asset values are redacted from the
|
|
341
|
+
runner's log output.
|
|
342
|
+
- **`flow` nodes** (subflows): run a nested document from `config.doc`
|
|
343
|
+
(inline) or `config.path` (file), sharing the variable scope;
|
|
344
|
+
optional `inputs` mapping; recursion capped at 8 levels.
|
|
345
|
+
- **`run_flow --validate`** — dry-run: version, start node, unique ids,
|
|
346
|
+
edge endpoints, node shapes, `on_error` specs, tool registration,
|
|
347
|
+
config vs tool schemas, selector-key existence. Nothing executes.
|
|
348
|
+
- **Service-grade exit codes** for `run_flow`: `0` finished, `1`
|
|
349
|
+
validation/node failure, `2` stopped (SIGTERM/SIGINT/Ctrl+C cancel
|
|
350
|
+
the run cleanly) — a supervising service can now distinguish a crash
|
|
351
|
+
from a requested stop.
|
|
352
|
+
- **`HttpQueue.claim` version stamping** — the claim body carries the
|
|
353
|
+
engine version and the agent version (`R2FLOW_AGENT_VERSION` env);
|
|
354
|
+
both fields are optional and ignored by servers without the feature.
|
|
355
|
+
- Full client-side chain: `fetch → verify → run_flow --pack --stage
|
|
356
|
+
--transactional`. SHA-256 covers integrity (transit + storage);
|
|
357
|
+
authenticity (who signed the pack) is signature-ready in the manifest
|
|
358
|
+
schema and deferred until the cloud launch.
|
|
359
|
+
|
|
360
|
+
### Deferred
|
|
361
|
+
|
|
362
|
+
- Manifest signatures (ed25519) — the format already reserves room.
|
|
363
|
+
- `TransactionBot` lifecycle sugar (Initialize/Get/Process/Status/End
|
|
364
|
+
hooks), queue `priority` ordering, custom `get_transaction` hooks —
|
|
365
|
+
designed, not built yet.
|
|
366
|
+
|
|
367
|
+
## 0.7.0 — 2026-09-08
|
|
368
|
+
|
|
369
|
+
Dev-capture workflow, production toolset, audit hardening, flow-v2
|
|
370
|
+
executor and the runner CLI.
|
|
371
|
+
|
|
372
|
+
### Added
|
|
373
|
+
|
|
374
|
+
- flow-v2 executor in the engine core (`r2flow.flow.FlowRunner`) and a
|
|
375
|
+
standalone runner CLI: `python -m r2flow.run_flow flow.json [--set NAME=VALUE]`
|
|
376
|
+
— flows from the designer now run as plain programs (e.g. inside
|
|
377
|
+
r2flow-cloud process bundles)
|
|
378
|
+
- Programmatic capture API (`r2flow[capture]`):
|
|
379
|
+
`capture_once()` / `capture_once_async()` — block the script, hover
|
|
380
|
+
an element, press CTRL (ESC cancels via `CaptureCancelled`), get a
|
|
381
|
+
ranked `CapturedSelector` (selector + full_path + confidence +
|
|
382
|
+
warnings) back into your code.
|
|
383
|
+
- `SelectorStore` (`r2flow.core.selectors`) — key → selector registry
|
|
384
|
+
persisted as JSON (atomic writes, survives corrupt files).
|
|
385
|
+
- Facade keyed selectors + dev capture:
|
|
386
|
+
`R2Flow(selector_store=..., dev_capture=True)` (or env
|
|
387
|
+
`R2FLOW_DEV_CAPTURE=1`) and `key=` on `click`, `wait`, `input_text`,
|
|
388
|
+
`set_text`, `get_element`, `hover`, `exists`, `get_text`,
|
|
389
|
+
`highlight`, `get_table`, `control_action`.
|
|
390
|
+
Workflow: a stored key runs silently (no re-prompting); a missing key
|
|
391
|
+
or a stale one (`ElementNotFound` mid-run) triggers one interactive
|
|
392
|
+
capture, persists it, and retries. In production (dev capture off) a
|
|
393
|
+
missing key is a hard error and a stale selector fails honestly.
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
bot = R2Flow(tools=windows_tools(), dev_capture=True)
|
|
397
|
+
await bot.click(key="login.submit") # first run: capture; then: silent
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
- `windows.get_table` — extract DataGrid/ListView/TreeView rows as JSON
|
|
401
|
+
(columns from a header control + row arrays); works through wrapper
|
|
402
|
+
containers via bounded descent.
|
|
403
|
+
- `windows.control_action` — native UIA pattern actions (`invoke`,
|
|
404
|
+
`toggle`, `expand`, `collapse`, `select`, `focus`) that keep working
|
|
405
|
+
when a window is covered or unfocused (no coordinate clicks).
|
|
406
|
+
- `file` tool — `read`/`write`/`append`/`copy`/`move`/`delete`/
|
|
407
|
+
`exists`/`wait_for`/`list`; optional `R2FLOW_FILE_ROOT` sandbox
|
|
408
|
+
confines every path (flow configs then cannot touch anything outside).
|
|
409
|
+
- `excel` tool — `read`/`write`/`append` for xlsx via `openpyxl`
|
|
410
|
+
(new ``excel`` extra), honors the same file sandbox.
|
|
411
|
+
- `windows.process` lifecycle: `wait` (bounded wait for exit + exit
|
|
412
|
+
code via Win32 `WaitForSingleObject`) and `status`
|
|
413
|
+
(`running`/`exit_code` via `GetExitCodeProcess`).
|
|
414
|
+
- Image fallback for UIA-invisible UIs (Citrix/RDP/Java/canvas):
|
|
415
|
+
`windows.find_image` and `windows.click_image` via OpenCV template
|
|
416
|
+
matching (new ``image`` extra: numpy + opencv-python).
|
|
417
|
+
- `windows.ocr` — text from an image file or screen region using the
|
|
418
|
+
built-in Windows OCR engine, zero extra dependencies (Windows
|
|
419
|
+
PowerShell 5.1 WinRT interop); optional `language` (BCP-47).
|
|
420
|
+
- Runtime secrets: `r2flow.core.assets` (`AssetProvider` protocol +
|
|
421
|
+
`EnvAssetProvider` over `R2FLOW_ASSET_*`), `R2Flow(assets=...)` and
|
|
422
|
+
`bot.asset("db.password")`. Values are fetched in bot code and never
|
|
423
|
+
pass through tool configs/results — they cannot leak into the JSONL
|
|
424
|
+
audit log.
|
|
425
|
+
- Facade wrappers: `get_table()`, `control_action()`, `process_wait()`,
|
|
426
|
+
`process_status()`, `asset()`; `windows_tools()` now bundles
|
|
427
|
+
`get_table`, `control_action`, `file`, and `excel`.
|
|
428
|
+
- `HttpQueue(allow_insecure=True)` — plain-HTTP base URLs are rejected
|
|
429
|
+
unless explicitly allowed (loopback is always permitted); retried
|
|
430
|
+
error responses are closed.
|
|
431
|
+
- `R2FLOW_OUTPUT_ROOT` sandbox for screenshot paths; screenshots opt
|
|
432
|
+
into per-monitor-v2 DPI awareness (fixes misaligned window captures
|
|
433
|
+
on scaled displays).
|
|
434
|
+
- `JsonlEventLogger` writes on a background thread (the event loop is
|
|
435
|
+
never blocked by disk I/O) and supports the context-manager protocol.
|
|
436
|
+
- `ElementSelector.from_config()` — shared config→selector building
|
|
437
|
+
(previously duplicated in three places).
|
|
438
|
+
- `InMemoryQueue.claim` is O(log n) via a per-queue heap; SQLite
|
|
439
|
+
backend enables WAL + `busy_timeout` and indexes `seq`.
|
|
440
|
+
- `RetryTool(jitter=...)` — spread retries out under contention;
|
|
441
|
+
invalid constructor args raise `InvalidInput` (consistent with core).
|
|
442
|
+
- `RetryTool`/registry: registering a tool with an empty `schema()`
|
|
443
|
+
emits a `UserWarning` (validation is silently disabled for it).
|
|
444
|
+
- Config: `R2FLOW_BLOCKING_TIMEOUT`/`R2FLOW_ALLOWED_COMMANDS`/
|
|
445
|
+
`R2FLOW_OUTPUT_ROOT` no longer leak into the robot config document;
|
|
446
|
+
`Config.__getattr__` no longer risks infinite recursion during
|
|
447
|
+
unpickling.
|
|
448
|
+
|
|
449
|
+
### Fixed
|
|
450
|
+
|
|
451
|
+
- **Correctness:** `_CONTROL_TYPE_MAP` was shifted by one from `"toolbar"`
|
|
452
|
+
onward (and `"text"` alias pointed at `edit`) — `control_type="window"`
|
|
453
|
+
matched SplitButtons, `"pane"` matched Windows, `"text"` matched Edits.
|
|
454
|
+
The table now uses the official UIA ControlTypeIds (incl. new
|
|
455
|
+
`semanticzoom`), pinned by a regression test against
|
|
456
|
+
`uiautomation.ControlType`.
|
|
457
|
+
- **Security (windows.process):** allowlist check now uses Windows path
|
|
458
|
+
semantics (`PureWindowsPath`) on every host OS; bare command names are
|
|
459
|
+
resolved via `PATH` only (never the current directory, closing the
|
|
460
|
+
exe-planting hole); `explorer.exe` removed from the default allowlist
|
|
461
|
+
(it accepts arbitrary launch targets as arguments).
|
|
462
|
+
- **Resource leaks:** selector-capture recorder listeners are now stopped
|
|
463
|
+
on every exit path (previously every session leaked global keyboard/
|
|
464
|
+
mouse hooks); retried `HTTPError` responses are closed (socket leak);
|
|
465
|
+
`run_blocking` runs each call on a dedicated executor so a hung COM
|
|
466
|
+
call cannot starve the shared pool.
|
|
467
|
+
- **Error masking:** `EventBus.emit` isolates middleware — a broken
|
|
468
|
+
middleware is logged and skipped instead of replacing a tool's result
|
|
469
|
+
or exception.
|
|
470
|
+
- **Reliability:** transient `claim` failures are retried with
|
|
471
|
+
exponential backoff and counted as system errors instead of killing
|
|
472
|
+
the run; `set_status` transport failures no longer abort the loop
|
|
473
|
+
(item stays `in_progress` until lease expiry); heartbeat `join()` is
|
|
474
|
+
bounded so shutdown cannot stall on a hung HTTP renewal.
|
|
475
|
+
- **wait tool:** a persistent UIA failure no longer yields a false
|
|
476
|
+
`disappear=True`/silent `appear` timeout — if no query ever succeeded,
|
|
477
|
+
the tool raises `PlatformError`.
|
|
478
|
+
- **highlight:** draws an outline (`NULL_BRUSH`) instead of a solid
|
|
479
|
+
white fill; `duration_ms` capped at 10 s.
|
|
480
|
+
- **click:** multi-clicks drop the inter-click wait so the OS recognizes
|
|
481
|
+
double-clicks (default 0.5 s pacing exceeded the double-click time).
|
|
482
|
+
- **set_text/input_text:** both fallback errors are reported; raw
|
|
483
|
+
COM/uiautomation exceptions are wrapped into `PlatformError` like in
|
|
484
|
+
sibling tools.
|
|
485
|
+
|
|
486
|
+
### Changed
|
|
487
|
+
|
|
488
|
+
- CI/release workflows pin all actions to commit SHAs; `id-token:
|
|
489
|
+
write` is scoped to the `publish` job only. Dependabot, SECURITY.md
|
|
490
|
+
added; broken `requirements.lock` removed in favor of `uv.lock`;
|
|
491
|
+
generated recorder outputs (`flow.json`, `recording.json`, `bot.py`,
|
|
492
|
+
`test.txt`) untracked.
|
|
493
|
+
|
|
494
|
+
## 0.5.0
|
|
495
|
+
|
|
496
|
+
Playwright-style codegen: recorded flows render as runnable bot scripts.
|
|
497
|
+
|
|
498
|
+
### Added
|
|
499
|
+
|
|
500
|
+
- Code generation (`windows/tools/selector_capture/emit.py`): any
|
|
501
|
+
capture file (`single`/`series`/`record` — same `nodes` shape)
|
|
502
|
+
renders as a `R2Flow(tools=windows_tools())` script with one
|
|
503
|
+
`await bot.*` call per node. New `emit` CLI subcommand
|
|
504
|
+
(`emit -i flow.json -o bot.py [--clip]`) plus `--emit BOT.py` on
|
|
505
|
+
every record mode for one-pass record-to-code.
|
|
506
|
+
- Honest placeholders instead of silent gaps: a `TODO` header for the
|
|
507
|
+
unseen app launch (`process_run` + PID scoping), `text="TODO: fill
|
|
508
|
+
in"` for series-mode `input_text` (keys are never captured), and
|
|
509
|
+
`WARNING` comments from static selector-fragility scoring.
|
|
510
|
+
|
|
511
|
+
## 0.4.1
|
|
512
|
+
|
|
513
|
+
Unified capture output: every recorder mode writes the same flow shape.
|
|
514
|
+
|
|
515
|
+
### Changed
|
|
516
|
+
|
|
517
|
+
- `single` mode now writes `{"tool": "selector-capture", "nodes":
|
|
518
|
+
[...]}` with one ranked node (same shape as `series`/`record`)
|
|
519
|
+
instead of the divergent `captures`/`best_selector` format. The node
|
|
520
|
+
`args` is the ranked minimal selector, `full_path` is attached, and
|
|
521
|
+
numeric control types are translated — previously `best_selector`
|
|
522
|
+
carried the raw `"50011"` the runtime rejects. The `-d`/
|
|
523
|
+
`--description` flag is still accepted but only logged, not persisted.
|
|
524
|
+
|
|
525
|
+
### Fixed
|
|
526
|
+
|
|
527
|
+
- Series-mode `windows.input_text` nodes were missing `full_path`
|
|
528
|
+
(keyboard flushes built nodes without the last clicked element's
|
|
529
|
+
path) — now every node carries it. Known limitation, now documented:
|
|
530
|
+
series mode captures the input *target*, not the typed text itself.
|
|
531
|
+
|
|
532
|
+
## 0.4.0
|
|
533
|
+
|
|
534
|
+
Playwright-style selector engine for the desktop: ranked selectors with
|
|
535
|
+
uniqueness checks and honest confidence instead of all-fields dumps.
|
|
536
|
+
|
|
537
|
+
### Added
|
|
538
|
+
|
|
539
|
+
- Selector ranking (`windows/selector_rank.py`): candidate generation in
|
|
540
|
+
priority order (automation ID → name + type → class + type, minimal
|
|
541
|
+
first), static stability scoring (dynamic digits/dates, wildcards, hex
|
|
542
|
+
runs, long names), live-desktop uniqueness check, `high`/`medium`/`low`
|
|
543
|
+
confidence with warnings. Low confidence means "add an anchor", never
|
|
544
|
+
a made-up stable selector.
|
|
545
|
+
- `ElementSelector.count_from_desktop(limit)` — bounded tree walk for
|
|
546
|
+
counting matches (strict-mode primitive; dev-time helper, not a runtime
|
|
547
|
+
search path).
|
|
548
|
+
- `resolve_element(..., strict=True)` — fail on ambiguous selectors
|
|
549
|
+
(2+ matches → `InvalidInput`) instead of silently taking the first.
|
|
550
|
+
- `generate_nodes_from_config()` — flow nodes from an already-ranked
|
|
551
|
+
minimal config.
|
|
552
|
+
- Record mode now ranks every capture: logs the winning selector,
|
|
553
|
+
confidence, and warnings, and emits nodes from the ranked config
|
|
554
|
+
(falls back to the unranked dump if the UIA walk fails).
|
|
555
|
+
|
|
556
|
+
### Fixed
|
|
557
|
+
|
|
558
|
+
- Real captures carry numeric control types (`"50000"`) which the
|
|
559
|
+
runtime rejects — `build_inline_selector` now translates them to names
|
|
560
|
+
(`"button"`) and drops untranslatable ones.
|
|
561
|
+
|
|
562
|
+
## 0.3.0
|
|
563
|
+
|
|
564
|
+
GUI batch: comfortable desktop automation on top of the 0.2.0 core.
|
|
565
|
+
|
|
566
|
+
### Added
|
|
567
|
+
|
|
568
|
+
- Ten new Windows tools: `windows.scroll` (wheel over element/point),
|
|
569
|
+
`windows.hover` (menus, tooltips), `windows.exists` (single-lookup
|
|
570
|
+
boolean), `windows.get_text` (ValuePattern → Name fallback),
|
|
571
|
+
`windows.window` (activate/minimize/maximize/restore/move/close by PID
|
|
572
|
+
via Win32), `windows.select` (dropdown/combobox/list via
|
|
573
|
+
SelectionItemPattern), `windows.drag` (two endpoints, coordinates or
|
|
574
|
+
`from_*`/`to_*` selectors), `windows.clipboard` (get/set via
|
|
575
|
+
`pyperclip`), `windows.list_elements` (direct-children dump for
|
|
576
|
+
discovering automation IDs), `windows.highlight` (colored rectangle
|
|
577
|
+
flash for debugging selectors).
|
|
578
|
+
- `R2Flow` facade methods for every new tool (`scroll`, `hover`,
|
|
579
|
+
`exists`, `get_text`, `window`, `select`, `drag`, `clipboard`,
|
|
580
|
+
`list_elements`, `highlight`), all accepting an optional `handle` for
|
|
581
|
+
PID scoping.
|
|
582
|
+
- Shared `_resolve.py` helpers: `resolve_point` (coordinates win over
|
|
583
|
+
selectors) and `resolve_element`.
|
|
584
|
+
|
|
585
|
+
### Changed
|
|
586
|
+
|
|
587
|
+
- `windows.click` now takes `button` (left/right), `clicks` (1/2), and
|
|
588
|
+
`x`/`y` coordinate clicks (double right-click = two `RightClick`
|
|
589
|
+
calls; no module-level `DoubleClick` exists in `uiautomation`).
|
|
590
|
+
- `windows.wait` now takes `wait_for` (`appear`/`disappear`) with a
|
|
591
|
+
symmetric poll loop; `PlatformError` mid-poll counts as still present.
|
|
592
|
+
- `r2flow[windows]` extra now includes `pyperclip` (clipboard support).
|
|
593
|
+
|
|
594
|
+
## 0.2.0
|
|
595
|
+
|
|
596
|
+
First minor release: transactions, config, and hardening on top of the
|
|
597
|
+
0.1.x tool core.
|
|
598
|
+
|
|
599
|
+
### Added
|
|
600
|
+
|
|
601
|
+
- Transactional queue model (`core/queue.py`): `Queue` protocol,
|
|
602
|
+
`InMemoryQueue` / `SqliteQueue` with atomic FIFO claim, lease expiry,
|
|
603
|
+
`max_attempts` requeue, idempotent add, `run_id` ownership; `HttpQueue`
|
|
604
|
+
client for the orchestrator (stdlib only, retries on 502–504).
|
|
605
|
+
- REFramework-style runner (`core/transactions.py`): `run_transactions` /
|
|
606
|
+
`run_transactions_async`, `BusinessError` vs `InfrastructureError`
|
|
607
|
+
contract, `Cancelled` cooperative stop, background lease heartbeat
|
|
608
|
+
(capped at 30 min), `on_progress` hook, `TransactionReport`.
|
|
609
|
+
- TOML robot config (`core/config.py`): `load_config` with fail-fast
|
|
610
|
+
validation (`required` / `must_exist`), frozen attribute-style `Config`,
|
|
611
|
+
`R2FLOW_*` env overlay (`__` nests, TOML-typed values).
|
|
612
|
+
- Schema validation (`core/schema.py`): `ToolRegistry.execute` validates
|
|
613
|
+
configs against `schema()` (hand-rolled subset, no new deps).
|
|
614
|
+
- Tool-level retries (`core/retry.py`): `RetryTool` wrapper
|
|
615
|
+
(`attempts` / `delay_ms` / `retry_on`, defaults to `ElementNotFound`).
|
|
616
|
+
- JSONL audit log (`core/logging.py`): `JsonlEventLogger` middleware with
|
|
617
|
+
`transaction_id`, duration, and error stamped per event.
|
|
618
|
+
- `windows_tools()` factory (`windows/tools/__init__.py`): default tool
|
|
619
|
+
set in one call, UIA imports stay lazy.
|
|
620
|
+
- `ProcessTool` allowlist is now configurable: constructor param,
|
|
621
|
+
`R2FLOW_ALLOWED_COMMANDS` env override, `allowed_commands` introspection.
|
|
622
|
+
- `parse_control_type()` is public (`windows/selector.py`).
|
|
623
|
+
- Examples: `reframework_bot.py` (dispatcher + performer skeleton),
|
|
624
|
+
`config_demo.py` with good/broken TOMLs.
|
|
625
|
+
|
|
626
|
+
### Changed
|
|
627
|
+
|
|
628
|
+
- Error model consolidated to the single `ToolError` family; the unused
|
|
629
|
+
legacy `SmithError` / `InvalidParams` / `ContextError` were removed.
|
|
630
|
+
|
|
631
|
+
## 0.1.1
|
|
632
|
+
|
|
633
|
+
- Windows UI tools (process, click, wait, delay, screenshot, input_text,
|
|
634
|
+
keyboard, set_text, get_element), selector capture CLI, middleware
|
|
635
|
+
event bus, `@tool` decorator, `R2Flow` facade.
|