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.
Files changed (127) hide show
  1. r2flow_engine-0.8.11/.gitignore +33 -0
  2. r2flow_engine-0.8.11/CHANGELOG.md +635 -0
  3. r2flow_engine-0.8.11/LICENSE +21 -0
  4. r2flow_engine-0.8.11/PKG-INFO +563 -0
  5. r2flow_engine-0.8.11/README.md +505 -0
  6. r2flow_engine-0.8.11/SECURITY.md +52 -0
  7. r2flow_engine-0.8.11/docs/quickstart-mvp.md +94 -0
  8. r2flow_engine-0.8.11/examples/basic_bot.py +30 -0
  9. r2flow_engine-0.8.11/examples/config_demo.py +49 -0
  10. r2flow_engine-0.8.11/examples/config_demo.toml +18 -0
  11. r2flow_engine-0.8.11/examples/config_demo_broken.toml +3 -0
  12. r2flow_engine-0.8.11/examples/custom_tool.py +36 -0
  13. r2flow_engine-0.8.11/examples/notepad_click.json +30 -0
  14. r2flow_engine-0.8.11/examples/packs/notepad/README.md +63 -0
  15. r2flow_engine-0.8.11/examples/packs/notepad/template.json +17 -0
  16. r2flow_engine-0.8.11/examples/reframework_bot.py +126 -0
  17. r2flow_engine-0.8.11/examples/reframework_bot.toml +14 -0
  18. r2flow_engine-0.8.11/examples/reframework_invoices.csv +4 -0
  19. r2flow_engine-0.8.11/pyproject.toml +103 -0
  20. r2flow_engine-0.8.11/schemas/flow-v2.schema.json +133 -0
  21. r2flow_engine-0.8.11/src/r2flow/__init__.py +116 -0
  22. r2flow_engine-0.8.11/src/r2flow/core/__init__.py +28 -0
  23. r2flow_engine-0.8.11/src/r2flow/core/asset_tools.py +84 -0
  24. r2flow_engine-0.8.11/src/r2flow/core/assets.py +258 -0
  25. r2flow_engine-0.8.11/src/r2flow/core/blocking.py +185 -0
  26. r2flow_engine-0.8.11/src/r2flow/core/config.py +211 -0
  27. r2flow_engine-0.8.11/src/r2flow/core/errors.py +105 -0
  28. r2flow_engine-0.8.11/src/r2flow/core/events.py +92 -0
  29. r2flow_engine-0.8.11/src/r2flow/core/excel.py +308 -0
  30. r2flow_engine-0.8.11/src/r2flow/core/files.py +349 -0
  31. r2flow_engine-0.8.11/src/r2flow/core/http_queue.py +416 -0
  32. r2flow_engine-0.8.11/src/r2flow/core/logging.py +173 -0
  33. r2flow_engine-0.8.11/src/r2flow/core/queue.py +581 -0
  34. r2flow_engine-0.8.11/src/r2flow/core/redact.py +67 -0
  35. r2flow_engine-0.8.11/src/r2flow/core/registry.py +86 -0
  36. r2flow_engine-0.8.11/src/r2flow/core/retry.py +115 -0
  37. r2flow_engine-0.8.11/src/r2flow/core/schema.py +92 -0
  38. r2flow_engine-0.8.11/src/r2flow/core/selectors.py +113 -0
  39. r2flow_engine-0.8.11/src/r2flow/core/tool.py +119 -0
  40. r2flow_engine-0.8.11/src/r2flow/core/transactions.py +630 -0
  41. r2flow_engine-0.8.11/src/r2flow/facade.py +908 -0
  42. r2flow_engine-0.8.11/src/r2flow/flow.py +1188 -0
  43. r2flow_engine-0.8.11/src/r2flow/pack.py +916 -0
  44. r2flow_engine-0.8.11/src/r2flow/py.typed +0 -0
  45. r2flow_engine-0.8.11/src/r2flow/run_flow.py +468 -0
  46. r2flow_engine-0.8.11/src/r2flow/trace.py +143 -0
  47. r2flow_engine-0.8.11/src/r2flow/windows/__init__.py +0 -0
  48. r2flow_engine-0.8.11/src/r2flow/windows/element.py +109 -0
  49. r2flow_engine-0.8.11/src/r2flow/windows/selector.py +373 -0
  50. r2flow_engine-0.8.11/src/r2flow/windows/selector_rank.py +286 -0
  51. r2flow_engine-0.8.11/src/r2flow/windows/tools/__init__.py +90 -0
  52. r2flow_engine-0.8.11/src/r2flow/windows/tools/_resolve.py +117 -0
  53. r2flow_engine-0.8.11/src/r2flow/windows/tools/click.py +152 -0
  54. r2flow_engine-0.8.11/src/r2flow/windows/tools/clipboard.py +82 -0
  55. r2flow_engine-0.8.11/src/r2flow/windows/tools/control_action.py +113 -0
  56. r2flow_engine-0.8.11/src/r2flow/windows/tools/delay.py +55 -0
  57. r2flow_engine-0.8.11/src/r2flow/windows/tools/drag.py +82 -0
  58. r2flow_engine-0.8.11/src/r2flow/windows/tools/exists.py +59 -0
  59. r2flow_engine-0.8.11/src/r2flow/windows/tools/get_element.py +59 -0
  60. r2flow_engine-0.8.11/src/r2flow/windows/tools/get_table.py +156 -0
  61. r2flow_engine-0.8.11/src/r2flow/windows/tools/get_text.py +76 -0
  62. r2flow_engine-0.8.11/src/r2flow/windows/tools/highlight.py +156 -0
  63. r2flow_engine-0.8.11/src/r2flow/windows/tools/hover.py +62 -0
  64. r2flow_engine-0.8.11/src/r2flow/windows/tools/image.py +278 -0
  65. r2flow_engine-0.8.11/src/r2flow/windows/tools/input_text.py +92 -0
  66. r2flow_engine-0.8.11/src/r2flow/windows/tools/keyboard.py +372 -0
  67. r2flow_engine-0.8.11/src/r2flow/windows/tools/list_elements.py +107 -0
  68. r2flow_engine-0.8.11/src/r2flow/windows/tools/ocr.py +235 -0
  69. r2flow_engine-0.8.11/src/r2flow/windows/tools/process.py +485 -0
  70. r2flow_engine-0.8.11/src/r2flow/windows/tools/screenshot.py +234 -0
  71. r2flow_engine-0.8.11/src/r2flow/windows/tools/scroll.py +100 -0
  72. r2flow_engine-0.8.11/src/r2flow/windows/tools/select.py +67 -0
  73. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/__init__.py +43 -0
  74. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/__main__.py +5 -0
  75. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/api.py +148 -0
  76. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/capture.py +387 -0
  77. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/cli.py +152 -0
  78. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/emit.py +160 -0
  79. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/flowgen.py +84 -0
  80. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/generate.py +351 -0
  81. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/record.py +211 -0
  82. r2flow_engine-0.8.11/src/r2flow/windows/tools/selector_capture/recorder.py +886 -0
  83. r2flow_engine-0.8.11/src/r2flow/windows/tools/set_text.py +116 -0
  84. r2flow_engine-0.8.11/src/r2flow/windows/tools/wait.py +149 -0
  85. r2flow_engine-0.8.11/src/r2flow/windows/tools/window.py +189 -0
  86. r2flow_engine-0.8.11/tests/conftest.py +11 -0
  87. r2flow_engine-0.8.11/tests/core/__init__.py +0 -0
  88. r2flow_engine-0.8.11/tests/core/test_asset_tools.py +163 -0
  89. r2flow_engine-0.8.11/tests/core/test_assets.py +87 -0
  90. r2flow_engine-0.8.11/tests/core/test_config.py +129 -0
  91. r2flow_engine-0.8.11/tests/core/test_errors.py +95 -0
  92. r2flow_engine-0.8.11/tests/core/test_events.py +217 -0
  93. r2flow_engine-0.8.11/tests/core/test_excel.py +84 -0
  94. r2flow_engine-0.8.11/tests/core/test_facade.py +340 -0
  95. r2flow_engine-0.8.11/tests/core/test_facade_keyed.py +215 -0
  96. r2flow_engine-0.8.11/tests/core/test_files.py +144 -0
  97. r2flow_engine-0.8.11/tests/core/test_http_queue.py +371 -0
  98. r2flow_engine-0.8.11/tests/core/test_jsonl_logging.py +119 -0
  99. r2flow_engine-0.8.11/tests/core/test_queue.py +202 -0
  100. r2flow_engine-0.8.11/tests/core/test_retry.py +108 -0
  101. r2flow_engine-0.8.11/tests/core/test_schema.py +169 -0
  102. r2flow_engine-0.8.11/tests/core/test_selectors.py +83 -0
  103. r2flow_engine-0.8.11/tests/core/test_tool_registry.py +150 -0
  104. r2flow_engine-0.8.11/tests/core/test_transactions.py +363 -0
  105. r2flow_engine-0.8.11/tests/test_audit_regressions.py +201 -0
  106. r2flow_engine-0.8.11/tests/test_dev_pipeline.py +268 -0
  107. r2flow_engine-0.8.11/tests/test_flow_runner.py +133 -0
  108. r2flow_engine-0.8.11/tests/test_flow_v2_features.py +776 -0
  109. r2flow_engine-0.8.11/tests/test_hardening_audit.py +428 -0
  110. r2flow_engine-0.8.11/tests/test_pack.py +421 -0
  111. r2flow_engine-0.8.11/tests/test_run_flow_cli.py +153 -0
  112. r2flow_engine-0.8.11/tests/test_templates.py +83 -0
  113. r2flow_engine-0.8.11/tests/windows/__init__.py +0 -0
  114. r2flow_engine-0.8.11/tests/windows/test_capture_api.py +113 -0
  115. r2flow_engine-0.8.11/tests/windows/test_emit.py +141 -0
  116. r2flow_engine-0.8.11/tests/windows/test_get_table.py +165 -0
  117. r2flow_engine-0.8.11/tests/windows/test_gui_tools.py +737 -0
  118. r2flow_engine-0.8.11/tests/windows/test_image.py +136 -0
  119. r2flow_engine-0.8.11/tests/windows/test_input_text.py +17 -0
  120. r2flow_engine-0.8.11/tests/windows/test_keyboard.py +68 -0
  121. r2flow_engine-0.8.11/tests/windows/test_ocr.py +123 -0
  122. r2flow_engine-0.8.11/tests/windows/test_process_allowlist.py +66 -0
  123. r2flow_engine-0.8.11/tests/windows/test_process_lifecycle.py +157 -0
  124. r2flow_engine-0.8.11/tests/windows/test_record_flowgen.py +175 -0
  125. r2flow_engine-0.8.11/tests/windows/test_selector.py +205 -0
  126. r2flow_engine-0.8.11/tests/windows/test_selector_capture.py +772 -0
  127. 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.