mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 00:36:35 +00:00
* fix(desktop): release a capture when its start is cancelled after launch Cancelling start_build_capture during the opened app's screenshot left the capture running, so the next start was refused. The capture is now kept for the owner's cleanup, and the next start stops it first. * fix(desktop): keep a published witness error and tag an app data refusal A second failure to close a published witness returns the original error, so its record and cleanup lease stay bound. A reset the app itself refuses is an AppDataRefusal; only a reply that is not that refusal stays unsettled. A missing child of a cut view is undecided with the same description in both evaluators, and the shared cases cover it. * fix(desktop): align a cut view's missing text with the Python check A missing child of a truncated view is undecided for the same sentence in both evaluators, including the pointer the view does not contain. * fix(desktop): keep one published witness cleanup across retries A published record and its bound cleanup error stay together. A later close failure returns that error, and the unused path that built a fresh adapter error is gone. * fix(desktop): keep a bundle task read inside the witness result A cancelled, refused, or failed read of the bundle's task files returns the witness's Result, including a cleanup lease when a handle would not close. * test(desktop): pass the settle timing when reading a witness bundle The witness takes the settle timing before the bundle path, so the bundle-read tests keep the path in its own argument. * fix(desktop): bound a guest file answer and split a long controller argument A data/get whose base64 answer would exceed a quarter of the V8 heap is refused before it is read. Controller JSON that exceeds Linux's single-argument limit travels as several arguments the controller joins. * fix(desktop): keep an underreported guest file from raising its read A data/get answer is admitted only when its base64 body fits in the heap quarter after the copies held during parse. Decoded bytes larger than the computer reported are dropped. * fix(desktop): grade shipped seeds and keep selected settings and sqlite logs Seed checks follow the files the bundle ships, so an omitted lock is absent. An incomplete checkpoint is not reported as no change. A selected link keeps its spelling, and a database's wal and journal stay supporting state. Closing the fake computer stops a detached capture. * fix(desktop): keep added-record checks off ids, dates, and joined timestamps A position-named record's fields keep their paths, so an identifier, a stamped date, and text past the limit are not suggested checks. A whole-text check does not climb into an ancestor whose text joins a timestamp. * fix(desktop): bound home fetches and keep unsettled edits readable Split home-folder reads by the guest answer budget, keep a capacity error from the capture output, and drop the superseded task-argument refusal. Grade kept text per line, leave unretained dconf unread, and reserve by-position notes for XML children. Map a linked database's sidecars through the selected spelling and stop a detached capture only when its start identity still owns the pid. * fix(desktop): describe unretained dconf as unread The key comparison treats a missing copy as unknown. The comment still said that copy counted as an empty key list. * test(desktop): keep a capture capacity error through endedCapture An output past the guest answer ceiling stays the resource constraint, with its heap recovery and non-retryable status, instead of a generic capture-exit message. * fix(desktop): keep nested save times and formatting runs out of checks A field check drops save-time leaves before it joins the field, so a nested timestamp is not part of the suggested text. A text check beside a save time uses the paragraph when the diff names a formatting run. * test(desktop): read paragraph pointers from checks that have them * test(desktop): expect a large task to export intact The 96 KiB refusal is gone. A prompt past that size reaches the computer and comes back unchanged, instead of failing before export. * fix(desktop): keep real edits that save-side heuristics were dropping Repeated unnamed fields stay content checks. A formula that still has text is not renumbered with its ExprID. One gained style attribute is the edit; a repeated fill-in stays upkeep. Phone numbers and plain clock times stay text, and a decimal is the element's whole text. An older verifier matches on the task it states, not its evaluator source. * fix(desktop): cover repeated records and keep timestamps out of checks An added record whose words are already in the seed still gets the list's text when that text fits. Readiness is a start reason, so a final check names seed locks without matching the message. Field text split by a timestamp stays separate, and a timestamp line is not offered as a guard. * fix(desktop): compare a cell's joined decimal within tolerance The whole-element check keeps numeric tolerance, so 437.80 and a long float still match 437.8, including when a run splits the digits. A different number or other text does not. Both evaluators read an element's text for that comparison. * fix(desktop): grade a number from a complete XML element only A tolerance check joins an element's text only in an XML file or an XML archive entry, and leaves that check undecided when the view is cut, so a dropped descendant cannot turn the kept runs into a passing number. A JSON object with the same shape stays the value itself. Equivalent decimals and a split run on a whole cell still match. * fix(desktop): accept an older grader only when its index helper agrees The witness still matches a bundle whose evaluator is this REA's, or the older index expression alone when no path segment is a leading-zero number. A truncated verifier, a changed grader, or that older helper beside a segment such as 01 no longer counts as the same task. * fix(desktop): require the added record and leave save times out of list checks A record a selector names is checked itself, so its words have to be in that record. When an unnamed record only repeats words the seed already has, a list that holds a save time is checked by the text around that time. A later save of the same records still passes, and a missing or rewritten record fails. * fix(desktop): judge the older index helper by pointer segments only A leading zero in a file name or a URL does not change how the older index helper reads a pointer. The witness still rejects that helper when a pointer segment is a number such as 01. * fix(desktop): do not suggest list text the seed already passes A repeated record is shown by text the seed does not hold. The seed's own text is not a check. A pointer inside a check value is not a pointer segment for the older index helper. * fix(desktop): keep a linked settings file in the folder the link points at A selected ~/.config alias no longer moves MuseScore settings to that alias. The seed keeps settings/MuseScore or Documents, which is the path the export opens the note from. * fix(desktop): split home fetches by answer size and keep a size refusal A batch of tiny files stops before each path's envelope exceeds the host answer budget. A capture output past that ceiling stays a resource error instead of becoming a generic exit. * fix(desktop): seed a selected link under the path the app uses The selected spelling wins, so ~/.config/MuseScore is not rewritten to the folder it points at and the note stays the one launched document. A database wal and journal follow that database. A launch argument is respelled the same way. Teardown signals a detached PID only when its start identity still matches. * fix(desktop): keep a witness's actions and an incomplete checkpoint A failed witness save retries the action log it first recorded. A deletion left unconfirmed between the seed and the end is not reported as no change. Windows packs npm through a shell, which can start npm.cmd. * fix(desktop): read verifier pointers from the parsed checks A brace inside a check value is not an object boundary. A leading-zero pointer after that value still rejects the older index helper. * fix(desktop): prefer the most specific home spelling and retarget launch files A selected parent directory no longer hides a settings folder walked through a link. A launch filename under that folder is replaced with the seeded file's absolute path. * fix(desktop): pack the build runtime through npm's JavaScript entry npm pack and npm ci no longer spawn npm.cmd through a shell, so a temporary directory whose path contains spaces stays one argument. * fix(desktop): spell launch settings and keep a database log exact A database log matches only that file, so a neighboring folder is not moved. A launch option and the working directory use the same home spelling as the seed. * fix(desktop): move a state database's logs with the database The write-ahead log and journal follow the spelling the database received, including a walked state link. A neighboring folder stays put. A separate --config argument uses that spelling too. * fix(desktop): keep a selected file ahead of a database-log guess A file selected as itself keeps that spelling even when its name ends in -journal. Relative launch arguments are resolved in the captured working directory before that directory moves. * fix(desktop): keep outside-home database logs and option values A selected database outside the home keeps its write-ahead log. Launch options are rewritten only when they name a captured file or settings path. * fix(desktop): follow a database mapping and own limitation text A selected database keeps its log when a parent folder moves or the file sits outside the home. Trash and seed-lock tests compare the owning notice with the paths, and launch options that are not captured paths stay as written. * fix(desktop): retarget a settings folder that holds captured files A nested profile directory is a captured path when a seeded file is inside it. An option value that only resolves under the home stays as written. * fix(desktop): keep a program word and a directory's neighbor The program after env stays the program even when a captured file sits in a folder of that name. A selected directory is not a database, so a neighboring file keeps the parent folder's spelling. * fix(desktop): record whether a selected path is a directory Capture stores the walked path's kind. A neighboring file of a selected directory keeps the parent folder's spelling even when that directory never changes and its children are not content roots. * fix(desktop): mark containing paths without a pairwise scan A recorded path marks its known ancestors as directories in one walk up the path, instead of comparing every recorded path with every other. * fix(desktop): keep a selected folder that is named like a database log A folder the caller selected stays a selected root when its name ends in -journal or -wal. A generated log beside a database file still does not. * fix(desktop): refuse a build command that exceeds the argument vector Splitting one argument still leaves the whole command under Linux's E2BIG limit. A request past that budget is refused before the process starts. * fix(desktop): remap a captured path in an env assignment The program word stays the program. A NAME=value assignment keeps its name and follows a captured path value when that path's spelling moves. * fix(desktop): keep oversized text out of draft guards A value or line longer than the draft's text bound is not copied into a guard, so one large frame cannot fill the task by itself. * fix(desktop): keep an empty env value empty An empty assignment is not a path. Resolving it named the working directory and rewrote the value when that directory's spelling moved. * fix(desktop): check build runtime export capability * fix(desktop): provide complete runtime repack command * fix(desktop): retain runtime probe failures * test(desktop): identify capture independently of schema preflight * fix(desktop): preserve document URLs and value formats * fix(desktop): bound added checks and omit noisy text guards * fix(desktop): preserve launch URLs and JSON draft values * fix(desktop): recognize captured file URL documents * fix(desktop): render captured file URLs for runtime home * fix(desktop): distinguish phone numbers from save metadata * fix(desktop): preserve launch URLs and user text * fix(desktop): preserve local file URL authority * fix(desktop): preserve local file URL authority * fix(desktop): render captured file URLs at launch * fix(desktop): classify numeric timestamps using field context * fix(desktop): classify numeric timestamps using field context * fix(desktop): retain context for generated draft values * fix(desktop): retain context for generated draft values * test(desktop): cover generated IDs in text guards * test(desktop): cover generated IDs in text guards * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): retain clean text in noisy record fields * style(desktop): format the noisy-record draft files with the pinned oxfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(desktop): move launch execution to boundary suite * fix(desktop): redact build schema failure output * refactor(desktop): normalize schema failure once * fix(desktop): omit unsafe schema parser snippets * fix(desktop): preserve draft change and selected path evidence * test(desktop): group retained dconf note regressions * fix(desktop): respect archive entry format in draft checks * test(desktop): separate unowned-file draft note cases * docs(desktop): clarify task checks before scratch export * fix(desktop): respell selected paths in file URLs * test(desktop): group captured document spelling cases * fix(desktop): bound draft preview serialization * fix(desktop): restore selected state aliases * fix(desktop): respect entry format in draft noise * test(desktop): match exported alias evidence at its JSON boundary * style: fix desktop launch test formatting * style: format native command path schema * fix(desktop): map selected external launch paths * style(desktop): format seed document regressions * test(desktop): group launch URL regressions * fix(desktop): omit redundant nested state aliases * fix(desktop): compare added free text as a whole * fix(desktop): check recompressed seeds as shipped * fix(desktop): map captured launch paths once * refactor: remove unused whole-text guard state * refactor: stop passing unused text children * test: align free-text checks with whole-note policy * fix: validate recompressed desktop archive seeds * style(desktop): format archive seed regression * fix(desktop): redact runtime schema errors * fix(desktop): normalize captured file URLs * fix(desktop): report seeded files by capture path * fix(desktop): retain startup readiness failure stage * fix(desktop): retain JSON date checks * fix(desktop): preserve archive formatting checks * fix(desktop): preserve explicitly selected journal files * fix(desktop): report build command transport limits * style(desktop): format capture review fixes --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
504 lines
31 KiB
Markdown
504 lines
31 KiB
Markdown
# Desktop application tasks
|
|
|
|
A [desktop application export](desktop-application-export.md) can carry one
|
|
task: a prompt for a computer-use agent and checks that grade what the agent
|
|
left in the application's files. An agent, not REA, writes the prompt and the
|
|
checks from the change the capture recorded. REA validates the task against
|
|
the capture and keeps it only when its checks fail on the seed and all pass on
|
|
the captured end state. REA runs no model and no code the agent wrote: the
|
|
checks are declarative, and the exported verifier evaluates them the same way
|
|
REA does.
|
|
|
|
```sh
|
|
rea describe-desktop-application-task ./mousepad-capture --format json > draft.json
|
|
# edit the draft's task into task.json: a name, the user's request, the checks
|
|
rea export-desktop-application ./mousepad-capture ./mousepad-bundle --task task.json
|
|
agent-env run ./mousepad-bundle --task add-milk --agent claude-code
|
|
```
|
|
|
|
## Describe the change
|
|
|
|
`rea describe-desktop-application-task <capture-folder>` reads the capture and
|
|
its [state view](desktop-application-state.md) and returns Evidence with
|
|
operation `describe_desktop_application_task` and predicate type
|
|
`rea.desktop-application-task-draft`:
|
|
|
|
- `application`: the environment name the export would use, the title and the
|
|
window class.
|
|
- `owned_paths`: the paths the export owns, the only ones whose files the
|
|
app's `data/get` serves and checks can read.
|
|
- `checkpoints`: the two checkpoints compared, `baseline` for the seed and
|
|
`final` for the end. They are the capture's first and last unless
|
|
`--seed-checkpoint` and `--end-checkpoint` name others, as for the
|
|
[export](desktop-application-export.md#the-seed-checkpoint), which takes
|
|
the same options; a name the capture does not hold, or an end before the
|
|
seed, is refused.
|
|
- `files`: each file under them whose state differs between those
|
|
checkpoints, with its home path, role, its versions at both (`baseline` and
|
|
`final`, the last recorded at or before each) and the net change between them, from the state view's own diff (`lines`, `values`,
|
|
`keys` or `rows` with what was added, removed and changed, or `created`,
|
|
`deleted`, `format`, `content`, `unreadable`; `content` also when the
|
|
views hold only part of a file and match while the file changed). A
|
|
database is compared by its rows, since its hash leaves out the `-wal`
|
|
file. Text changes list each line
|
|
once and add `line_counts`, the lines found at both ends whose number of
|
|
occurrences changed. An XML file's change names its members by the
|
|
[selectors](#pointers-and-selectors) checks use, as an archive's already
|
|
does, and by position only where nothing names a member or where only the
|
|
members' order changed. Views appear only for the files the suggestions
|
|
cover, with arrays past 200 items and strings past 2,000 characters cut
|
|
(`view_cut`).
|
|
- `values`, in each of those files: the values its change added, changed and
|
|
removed, each with the `pointer` a check names it by, its `kind` (`added`,
|
|
`changed` or `removed`), and `before` and `after`: the scalar, an element as
|
|
compact XML with the text the XML reader kept, spaces between runs included,
|
|
or a JSON value as compact JSON, cut at 600 characters, and null at the end
|
|
that lacks it. A file created or deleted lists what its one end holds,
|
|
`added` or `removed`: an archive's entries, an XML file's root element, a
|
|
JSON file's members or a key file's keys, each whole once. A value only
|
|
position reaches has `note: "by_position"`, and its pointer cannot go into a
|
|
check; noise an app writes as it saves has `view_state`, `renumbered`,
|
|
`volatile`, `timestamp` or `upkeep` (see `task`). `refers_to` names up to
|
|
three other elements a value equals the `key` or `id` of, with the
|
|
`attribute` holding it, as HomeBank's `category="44"` names
|
|
`<cat key="44" name="Groceries"/>`; where several elements share that key,
|
|
the one whose tag shares the attribute's stem, as `payee` and `<pay>`. What a check can
|
|
name comes first, then what only position reaches, then noise. A file lists
|
|
at most 20 values and 8 KB, the draft 24 KB, and `values_cut` says when a
|
|
limit left some out. Values cover XML, archives, JSON and key files; a text
|
|
or SQLite file's change and suggestions already carry its lines and rows.
|
|
- `task`: a draft with the name `change`, a placeholder prompt the export
|
|
refuses, and suggested checks derived from the net change of the files with
|
|
role `document`, or of the app's own state files when no document changed.
|
|
A text file gains `lines_present` for its new lines and `lines_absent` for
|
|
lines it lost (when either is at most ten), and `line_count` for each line
|
|
holding a letter whose count changed; JSON and key files gain
|
|
`value_equals` and `value_absent` by pointer; SQLite tables gain rows added,
|
|
changed and removed, by primary key, and their new row count. An XML file,
|
|
and the JSON, key file and XML views of a zip archive's entries, gain checks
|
|
by [selectors](#pointers-and-selectors) and never through an element by
|
|
position: in XML, first [`text_equals`](#whole-text) for each element whose
|
|
text changed, then elements added or removed whole, then values changed,
|
|
added and removed, an entry or a JSON value added whole through its
|
|
members, and an empty array or object as it is.
|
|
- A selector names a member by its tag, then by its identifiers (`name`,
|
|
`id`, `xid`, `xml:id`), then by one other attribute, a name-like key such
|
|
as Scribus's `ANNAME` or `inkscape:label`, a worded value or an integer of
|
|
up to five digits first, then by two such attributes together, as
|
|
Gnumeric's cells by `[@Col=3][@Row=2]`, and last by a coordinate, a long
|
|
number or a token, such as `XPOS` or `ItemID`, or an attribute naming
|
|
view state, such as which layer is `selected`. A pair counts every
|
|
member holding both its values, whichever attributes that member ranks
|
|
first. An element with identifiers is named by those alone. The selector
|
|
must name the member alone at both ends, with the same values. An
|
|
element whose one identifier changed, at its place among its tag's
|
|
members, with every other attribute and its children alike, is that
|
|
element renamed, as a MyPaint layer renamed from `Untitled` to `Sun`: it
|
|
is named by its first other attribute naming it alone at both ends, as
|
|
`[@src=data/layer-00.png]`, so the rename reads as its `name` changed,
|
|
and the old name gets a check that it names nothing, unless its change
|
|
is noise, as an id the app renumbered.
|
|
An identifier the app numbers by position names nothing: where the
|
|
members of a tag hold it as 1..N (or 0..N-1) in document order at both
|
|
ends, members were inserted or removed, and every member another
|
|
attribute names alone at both ends kept its number or moved it the way
|
|
the count did, one at least, as Planner's task `id` after a task is
|
|
inserted, members are paired by their other attributes, so an insertion
|
|
reads as one task added. A value two members swap, or an id appended at
|
|
the end, as Tellico's next `<entry id="4">`, leaves the ids identity.
|
|
- The text check names the paragraph rather than a run inside it: it goes up
|
|
from an element sitting in its parent's text, and from one named by its
|
|
tag or position alone to the nearest ancestor named by an attribute, as
|
|
AbiWord's `p` by its `xid`; where no attribute names one, to the nearest
|
|
ancestor within three levels its tag names with no step by position, as
|
|
MuseScore's `VBox` around untitled `<Text>` elements; and where only
|
|
position reaches the element, to the ancestor above the outermost step by
|
|
position, by an attribute of its own that names it alone where one does,
|
|
as an ODS sheet's `table:table[@table:name=Q1 Sales]` above its rows. The
|
|
text nodes and runs inside it get no other check, but an element holding
|
|
no text that is added or removed there, such as an image or a line break,
|
|
keeps its own, since the text check never reads it. A decimal that is an
|
|
element's only text, such as a spreadsheet cell's, is checked by its
|
|
number instead. Free text that gains a line, such as a shopping list,
|
|
uses `text_equals` on the whole text: the current XML checks cannot
|
|
compare an unstructured line as a whole item. This can reject a correct
|
|
edit that puts the item elsewhere or changes spacing. A record added
|
|
or removed whole, an element holding only elements, its fields, such as
|
|
Tellico's `<entry>` or a record of repeated `<field>` elements, never
|
|
makes the list holding records of its kind a check, whose text would hold
|
|
the records the edit left alone, except a record removed whole that only
|
|
position names, whose absence only the list's text shows. Text made of
|
|
runs is no record: an element whose tag names a paragraph or a piece of
|
|
text (`p`, Writer's `text:p` and `text:h`, `h1` to `h6`, `li`, `para`,
|
|
SVG's `text` and `tspan`, a `span` or an `a`), or whose runs split a
|
|
sentence at a space, as `Meet ` and a linked `Monday`. Nor does an
|
|
element holding a date the app stamps in parts take a text check.
|
|
- An element added to a list, where another element of its tag is at either
|
|
end, gets a `value_equals` for each of its attributes, best ranked first,
|
|
at most six, leaving out its selector's attributes, identifiers, times,
|
|
volatile values and view state, as HomeBank's new `<ope>` gets its
|
|
`amount`, `category` and `payee`. An element whose other attributes, two
|
|
or more, a like sibling holds every one of, as a new Planner task copies
|
|
its neighbour's defaults, and any other element added whole, get a check
|
|
that it exists. A record added to a list that a selector names, as
|
|
Tellico's `<entry id="4">`, gets a `text_equals` for each field, at most
|
|
five, each by its selector among the record's fields, as `[name=title]`,
|
|
leaving out a field repeating the record's identifier, a date the app
|
|
stamps in parts, and blank text or times. A record only position names,
|
|
such as Osmo's `<record>` of fields, also gets a `text_contains` for each
|
|
new field of three or more characters, at most five, at the nearest
|
|
ancestor a check can name, each absent from that ancestor's text at the
|
|
seed.
|
|
- A decimal written as text, such as `146.83`, is suggested as a number
|
|
with `tolerance: 0.001`, unless the seed's value is that close too.
|
|
- Noise leaves the suggestions when the file's change holds anything
|
|
else, a change only position reaches included: view state, which a tag
|
|
or attribute on the way names (the cursor, a selection, scrolling, zoom,
|
|
the viewport, windows, splitters, Inkscape's `namedview`, view and
|
|
configuration settings, the printer, a save's history, edit time,
|
|
editing duration and cycles, last-saved time, the generator and
|
|
document statistics), or the value of a name-like attribute or `key` on
|
|
the way, when it is one token of several words, as LibreOffice's
|
|
`config:config-item[@config:name=CursorPositionX]` inside
|
|
`[@config:name=ooo:view-settings]`; an attribute ending in `ID` or `Id`,
|
|
or an `rsid`, that the app renumbered, with everything else in its
|
|
element, as Gnumeric empties a formula cell whose shared expression it
|
|
renumbers, and an identifier the app numbers by position; volatile
|
|
values, UUIDs and epoch times, and durations and counts under a
|
|
document's metadata; times the app stamps, also as a date written in
|
|
parts whose tag, or one it sits in, names when the app made, changed or
|
|
saved something, as Tellico's `<cdate>` of `<year>`, `<month>` and
|
|
`<day>` (`cdate`, `mdate`, `created`, `modified`, `saved`, `updated`),
|
|
which no check ever reads, while a date a user enters in parts, as a
|
|
birthday, is an edit like any other; and `upkeep`, the
|
|
`loext:tab-stop-distance` attributes LibreOffice fills in `styles.xml`
|
|
while another archive entry holds an edit. However much noise
|
|
comes first, it never takes a check's place. When the change holds
|
|
nothing else, the checks stay and a note flags them: up to three in a
|
|
document, and as many as fit in the app's own state, where a view
|
|
setting may be the task.
|
|
- Whitespace an app lays out between elements is not checked, and values
|
|
that look like a time the app stamps, in ISO 8601, as C's `asctime`
|
|
writes it (`Thu Nov 5 09:20:31 2026`) or as a time of day to the
|
|
second (`05:16:21.675302540`), are left out; noise never makes its
|
|
ancestor's whole text a check.
|
|
- `guards`: checks an author may add to show the edit left the rest alone,
|
|
kept out of `task.checks`, with ids `guard-1` and on: for each suggested
|
|
`value_equals` or `text_equals` on a member of a list, a field on up to
|
|
two like siblings, nearest first, at its seed value, which the end holds
|
|
too: the check's own field where like siblings hold different values in
|
|
it, and otherwise one that varies among them, as Planner's `work` rather
|
|
than the `type="normal"` every task shares; never through a sibling only
|
|
position or a renumbered id pairs. A text file gets a `lines_present` of the
|
|
unchanged list items beside its first new line with the same marker, as
|
|
Zim's other `[ ]` action items, or else its nearest lines with words in
|
|
them. At most four a file. They pass on the seed, which the export allows,
|
|
since one check failing there is enough.
|
|
- `notes`: why `files` is empty, or why a file's suggestions are thin. Each
|
|
note has a `code` and a `message`. For a draft with no files: `no_change`
|
|
when no file under the home changed between the two checkpoints and nor
|
|
did the application's dconf keys; `unowned_changes` when files changed but
|
|
none under the owned paths, with up to five of them in `files`, each with
|
|
its home path and role (desktop services' files last), and how many more
|
|
in `more`; `uncheckable_changes` when the application's keys in a dconf
|
|
database changed, with the database in `files`: no check reads dconf, and
|
|
the export only writes the keys as they were at the seed checkpoint, so
|
|
make the task's change one the app saves in a file; `unreadable_changes`
|
|
when a dconf database changed but REA could not read it, with the read
|
|
failure in `error` (install `dconf-cli` or set `REA_DCONF_COMMAND` when it
|
|
is missing), since whose keys changed is then unknown; and `no_document`
|
|
when no file has the document role, as when no window title shows the
|
|
file's name. A capture again with `--state-path` naming the file, or
|
|
`state_paths` on a build computer's `start_build_capture`, makes it the
|
|
app's. For a drafted file, named in `file`: `by_position` when
|
|
changes reach their element only by position, the tags no attribute names
|
|
alone in `elements`, or when only the elements' order changed;
|
|
`view_state_only` when every change is noise, the checks kept in
|
|
`pointers`; `long_text` when an element's text changed past the 2,000
|
|
characters a text check compares, the elements in `pointers`; `cut_view`
|
|
when the views hold only part of the file; and `unreadable_view` when no
|
|
check can read it, as a document REA reads as binary.
|
|
|
|
The suggestions are a starting point. Keep the ones that say what the user
|
|
did; drop the ones that only follow from how the app saves its file. Read
|
|
`values` for what changed and its pointers, and `refers_to` for what a number
|
|
stands for, before writing the prompt and any check of your own. A LibreCAD
|
|
capture that added four holes to a drawing rewrote it from DXF R12 to DXF 2007,
|
|
so most of its lines changed; the draft suggests counts of `SECTION`, `ENDSEC`
|
|
and `CIRCLE`, and only `CIRCLE` going from 5 to 9 is the user's change.
|
|
|
|
## The task file
|
|
|
|
```json
|
|
{
|
|
"name": "add-milk",
|
|
"prompt": "Add buy milk to my to-do list in Mousepad and save it.",
|
|
"checks": [
|
|
{
|
|
"id": "milk",
|
|
"kind": "lines_present",
|
|
"file": "Documents/todo.txt",
|
|
"lines": ["buy milk"]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
`name` is a portable bundle name other than `smoke` (lowercase letters, digits
|
|
and hyphens); `prompt` is what the user asks for; `checks` holds 1 to 32
|
|
checks. Each check has a `kind`, a `file` given as a path under the home such as
|
|
`Documents/todo.txt`, and an optional `id` (`check-1`, `check-2` and so on by
|
|
position otherwise). A check reads the typed view `data/get` returns for that
|
|
file, in the shapes of the state view.
|
|
|
|
| Kind | Fields | Passes when |
|
|
| --------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
| `file_present` | | The file exists |
|
|
| `file_absent` | | The file does not exist |
|
|
| `lines_present` | `lines` | Every listed line is a whole line of the text view |
|
|
| `lines_absent` | `lines` | None of the listed lines is |
|
|
| `line_count` | `exact` or `contains`; `equals`, `at_least` or `at_most` | The number of lines equal to `exact`, or holding `contains`, compares so |
|
|
| `value_equals` | `pointer`, `value`; optional `tolerance` | The pointer names a value equal to `value`, or within `tolerance` of it |
|
|
| `value_absent` | `pointer` | The pointer names no value |
|
|
| `text_equals` | `pointer`, `value`; optional `normalize_whitespace` | The XML element's [whole text](#whole-text) equals `value` |
|
|
| `text_contains` | `pointer`, `value`; optional `normalize_whitespace` | The XML element's whole text holds `value` |
|
|
| `row_present` | `table`, `where` | A row of the SQLite table has every column in `where` equal |
|
|
| `row_absent` | `table`, `where` | No row does |
|
|
| `row_count` | `table`; optional `where`; `equals`, `at_least` or `at_most` | The number of rows, or of rows matching `where`, compares so |
|
|
|
|
The rules both evaluators follow:
|
|
|
|
- An absent file reads as empty: no lines, values or rows. So `lines_absent`,
|
|
`value_absent` and `row_absent` pass on it and a count is 0.
|
|
- A file that has no view (binary, over the read limit, or unreadable) fails
|
|
every check but `file_present` and `file_absent`. Line checks need a text
|
|
view, row checks a SQLite view and text checks an XML view, in a file or an
|
|
archive entry; another format fails.
|
|
- Equality is JSON equality: numbers by value, never a boolean for a number,
|
|
objects by their keys whatever their order, arrays in order.
|
|
- Pointers follow RFC 6901 (`~1` for `/`, `~0` for `~`) and must start with
|
|
`/`; on an array a segment may be a selector, below. Key files read as
|
|
`{"groups": {"GROUP": {"KEY": "VALUE"}}}`, so a setting is
|
|
`/groups/GROUP/KEY`; XML as `{"root": {"name", "attributes", "children"}}`;
|
|
SQLite as `{"tables": {"TABLE": {"columns", "rows"}}}`; a zip archive as
|
|
`{"entries": [{"name", "format", "view", …}]}`; a gzip file, such as a
|
|
Gnumeric `.gnumeric`, as the document inside it, so its pointers are those
|
|
of the same document uncompressed.
|
|
- With `tolerance`, a non-negative number, `value` must be a number, and the
|
|
value found passes when it is a number, or a string that is a decimal
|
|
numeral such as `"5.0000000000000000"`, at most `tolerance` from `value`.
|
|
`tolerance: 0` compares numerically, so `"1.2500"` equals `1.25`. Anything
|
|
else fails as not a number.
|
|
- `contains` matches a line that holds it anywhere, case and all. Checks take
|
|
no regular expressions: a pattern run on what the agent wrote could stall
|
|
the verifier, and JavaScript and Python read some patterns differently.
|
|
- A check decided by what a view leaves out (`lines_absent`, `value_absent`,
|
|
`row_absent`, the counts, `value_equals` of a whole object or array, and
|
|
the text checks of an element) fails when that view is truncated; a check
|
|
satisfied by what the view holds is decided on it. An element's text is not:
|
|
the reader may have dropped text from between the runs it kept, so
|
|
`text_contains` fails on a truncated view even where the kept runs hold its
|
|
value. A text node or attribute value is whole, and decided.
|
|
|
|
The export refuses, naming the check, a file outside the owned paths, a file
|
|
spelled with a leading slash, `.` or `..`, a pointer into a file that is text
|
|
at the seed, as the bundle ships it, or at the end checkpoint, the empty
|
|
pointer (the whole file), a value that names the captured home, or the
|
|
capture host's name or `user@host` anywhere (which the
|
|
[export removes from the seed](desktop-application-export.md#host-name-and-user)), a
|
|
`tolerance` with a value that is not a number, an empty `text_contains`
|
|
value, or one of only whitespace with `normalize_whitespace`, and a value that
|
|
looks like a date and time, in ISO 8601 or `asctime`'s form. Other volatile
|
|
content, such as a
|
|
counter the app bumps on every save, is for the author to leave out.
|
|
|
|
## Pointers and selectors
|
|
|
|
An app that rewrites a document moves things: FreeCAD 1.0 records more
|
|
properties than 0.21, so a hole's radius sits at another position among its
|
|
siblings, and a plate's entry moves once a hole is added before it. A pointer
|
|
segment applied to an array may therefore name a member by what it holds
|
|
rather than by its position:
|
|
|
|
- `[KEY=VALUE]` names the member whose string field `KEY` is `VALUE`, such as
|
|
a zip entry by `[name=Document.xml]` or an XML element by its tag,
|
|
`[name=Object]`.
|
|
- `[@KEY=VALUE]` names the member whose `attributes` hold `KEY="VALUE"`, as
|
|
the XML view keeps an element's attributes.
|
|
- Conditions can follow one another, `[name=Object][@name=Hole]`, and all must
|
|
hold. They compare strings only, the selector is escaped like any pointer
|
|
segment (`~1` for `/`), and a value cannot hold `][`.
|
|
|
|
A selector must name exactly one member. When several match, or none does
|
|
before the last segment, the check fails and says which selector matched what.
|
|
When the last segment's selector matches none, the pointer names no value:
|
|
`value_absent` passes, which is how a check says an element was removed, and
|
|
`value_equals` fails. On an object, a bracketed segment is an ordinary key.
|
|
|
|
A FreeCAD task that checks the user cut a 10 mm hole through the plate, in
|
|
FreeCAD 0.21 and 1.0 alike:
|
|
|
|
```json
|
|
{
|
|
"name": "cut-hole",
|
|
"prompt": "Cut a 10 mm hole through the middle of the plate and save it.",
|
|
"checks": [
|
|
{
|
|
"id": "cut",
|
|
"kind": "value_equals",
|
|
"file": "Documents/plate.FCStd",
|
|
"pointer": "/entries/[name=Document.xml]/view/root/children/[name=Objects]/children/[name=Object][@type=Part::Cut]/attributes/type",
|
|
"value": "Part::Cut"
|
|
},
|
|
{
|
|
"id": "radius",
|
|
"kind": "value_equals",
|
|
"file": "Documents/plate.FCStd",
|
|
"pointer": "/entries/[name=Document.xml]/view/root/children/[name=ObjectData]/children/[name=Object][@name=Hole]/children/[name=Properties]/children/[name=Property][@name=Radius]/children/[name=Float]/attributes/value",
|
|
"value": 5,
|
|
"tolerance": 0.01
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Before the edit the archive has no `Part::Cut` and no `Hole`, so both checks
|
|
fail on the seed; after it, `Radius` is `"5.0000000000000000"` and both pass.
|
|
A check decided by what a view leaves out depends, in an archive, on the entry
|
|
its pointer goes into: it fails when that entry's view is missing or
|
|
truncated, or when the entry is not listed and the listing is truncated. A
|
|
selector must see every member to know it names one, so a check whose pointer
|
|
searches an array the view may hold only part of fails as undecided: any array
|
|
of a truncated XML view, of a truncated entry's view, or the listing when it
|
|
is cut. A position is decided on what the view holds. Like a file, an entry
|
|
that has no view (binary, not read, or a database or archive inside the
|
|
archive) fails every check into its `view`; its `null` there is no value, while
|
|
an entry holding the JSON document `null` reads as `null`. Checks on its `name`,
|
|
`size`, `sha256` and `format` still read.
|
|
|
|
XML that uses namespaces is named as the document spells it, in both
|
|
evaluators: an element or attribute by its prefixed name, such as `text:p`
|
|
or `inkscape:label`, an element in a default namespace by its bare name, such
|
|
as `svg`, and namespace declarations as the attributes `xmlns` and
|
|
`xmlns:PREFIX`. Text is a string among an element's `children`. These checks
|
|
hold a poster's headline in an Inkscape SVG and a date in a LibreOffice Writer
|
|
document:
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "headline",
|
|
"kind": "value_equals",
|
|
"file": "Pictures/poster.svg",
|
|
"pointer": "/root/children/[name=g][@inkscape:label=Layer 1]/children/[name=text][@id=headline]/children/[name=tspan]/children/0",
|
|
"value": "Summer Bake Sale"
|
|
},
|
|
{
|
|
"id": "date",
|
|
"kind": "value_equals",
|
|
"file": "Documents/flyer.odt",
|
|
"pointer": "/entries/[name=content.xml]/view/root/children/[name=office:body]/children/[name=office:text]/children/[name=text:p][@text:style-name=Subtitle]/children/0",
|
|
"value": "Monday 24 November"
|
|
}
|
|
]
|
|
```
|
|
|
|
Inkscape writes a line of text as a `tspan` inside its `text` element, and
|
|
Writer keeps a paragraph's style in `text:style-name`. A prefix is the one the
|
|
document uses, so a check names the prefixes the app writes.
|
|
|
|
## Whole text
|
|
|
|
A text node is only the stretch of text between two pieces of markup, and
|
|
apps split a paragraph into runs as they save it. AbiWord wraps text typed
|
|
into a reopened document in `c` elements, so changing a time to `3:30` saves
|
|
`<p>The team meeting starts at <c>3</c>:<c>30</c> in room 4.</p>`; Writer,
|
|
which records revision IDs by default, writes text typed in one session as
|
|
`text:span` runs. A `value_equals` on the paragraph's `children/0` then reads
|
|
`"The team meeting starts at "` and fails. `text_equals` and `text_contains`
|
|
read the element's whole text instead:
|
|
|
|
```json
|
|
{
|
|
"id": "time",
|
|
"kind": "text_equals",
|
|
"file": "Documents/meeting.abw",
|
|
"pointer": "/root/children/[name=section]/children/[name=p][@xid=3]",
|
|
"value": "The team meeting starts at 3:30 in room 4."
|
|
}
|
|
```
|
|
|
|
- The pointer names an element of an XML view, in a file or in an archive
|
|
entry's `view`, or a text node or attribute value in one, whose text is its
|
|
value. Another format, a pointer into an archive outside an XML entry's
|
|
view, or any other value fails, an element's `name` and the `doctype`'s
|
|
fields included: they are strings of the view, not text of the document.
|
|
- An element's text is XPath's `string()` of what the view keeps: every text
|
|
node in it and in its descendants, CDATA included, in document order, with
|
|
nothing between them. The view drops whitespace-only text between child
|
|
elements only in an element that holds no other text, outside
|
|
`xml:space="preserve"`, so the space between two runs of a paragraph is
|
|
part of its text. Elements that stand for characters, such as Writer's
|
|
`text:s`, `text:tab` and `text:line-break`, add none.
|
|
- `text_equals` passes when the text equals `value` exactly, case and all;
|
|
`text_contains` when it holds `value`, as a time within a sentence, so the
|
|
check holds whatever the rest of the sentence says.
|
|
- `normalize_whitespace: true` collapses each run of XML whitespace (space,
|
|
tab, carriage return, line feed) in both the text and `value` to one space
|
|
and trims both ends before comparing, as XPath's `normalize-space()` does,
|
|
for an app that reflows text or a value written across lines. A no-break
|
|
space is not whitespace there.
|
|
|
|
## Validation and export
|
|
|
|
`rea export-desktop-application <capture> <directory> --task <task.json>`
|
|
evaluates the checks on the capture's state view: on each file as it was at
|
|
the seed checkpoint, which is the seed, as the bundle ships it with the
|
|
capture host's name and user removed, and at the end checkpoint, the
|
|
captured end state. Those are the capture's first and last checkpoints unless
|
|
`--seed-checkpoint` and `--end-checkpoint` name others.
|
|
It refuses a task whose every check passes on the seed, and a check that fails
|
|
on the end state, with that check's reason. Only a failure on what the seed's
|
|
view shows counts: a task whose checks fail on the seed only because a view
|
|
could not be read or may have left out what they look for, such as a line past
|
|
the 5,000 a text view keeps, is refused, naming each failure. A capture yields
|
|
one task, so the option is taken once. The result's `task` names the task, its
|
|
number of checks and those that fail on what the seed shows. A refused task
|
|
writes no bundle.
|
|
|
|
The bundle then holds, besides the app and `tasks/smoke.json`:
|
|
|
|
| Path | Contents |
|
|
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
| `tasks/<name>.json` | Deploy the app, deploy the agent and its skills, record the screen, prompt the agent, run the verifier, stop recording |
|
|
| `artifacts/<name>-check/verify.py` | Reads the app's `data/get` through the computer and evaluates the checks, one result per check |
|
|
| `evals/<env>.toml` | The task, so a run of the bundle runs it; the smoke task runs with `--task smoke` |
|
|
|
|
The verifier finds the app among the computer's child environments, calls its
|
|
`data/get` under the run's grant of the computer's control token, and reads
|
|
the `state` data part. It reads again until the app's files settle, as
|
|
[grading once the app has saved](desktop-application-export.md#grading-once-the-app-has-saved)
|
|
describes, and each result's `settled` says whether they did, and `read_cut`
|
|
whether the bound cut a read still running. It raises when the app cannot be reached, so the run
|
|
fails rather than scoring the agent. The README lists the task, its prompt, its
|
|
checks and the recipe below.
|
|
|
|
## Solve rate
|
|
|
|
REA does not run agents. To measure how often an agent solves the task, run it
|
|
k times, three by default, and count the runs whose summary line says
|
|
`passed`:
|
|
|
|
```sh
|
|
for run in 1 2 3; do
|
|
agent-env run ./mousepad-bundle --task add-milk --agent claude-code --model MODEL
|
|
done
|
|
```
|
|
|
|
The agent defaults to Claude Code and the model is the caller's choice (Claude
|
|
Sonnet 5.5 is a reasonable reference). A run reported as `scored below 1`
|
|
failed the checks and counts as a miss; one reported as `failed` did not finish,
|
|
as when the computer or the app could not be reached, and is run again rather
|
|
than counted. Each run's instance keeps the verifier's results per check under
|
|
`verifications`.
|