Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
673e604
feat(desktop): capture a Linux desktop application in use
earakely-scale Nov 4, 2026
5b37ce6
fix(desktop): own launched processes, confirm walk coverage and keep …
earakely-scale Nov 4, 2026
55192fb
fix(desktop): keep owned cleanup failures and fresh digests
earakely-scale Nov 4, 2026
7a4fe9d
wip(desktop): state view over a capture folder
earakely-scale Nov 4, 2026
def249e
fix(desktop): keep failed file-handle closes owned for retry
earakely-scale Nov 4, 2026
0ce79d3
Merge branch 'feat/capture-desktop-application' into feat/desktop-app…
earakely-scale Nov 4, 2026
e907a64
feat(desktop): read a captured application's state
earakely-scale Nov 4, 2026
11bf4c7
fix(desktop): report process and handle cleanup failures together
earakely-scale Nov 4, 2026
7db3543
Merge branch 'feat/capture-desktop-application' into feat/desktop-app…
earakely-scale Nov 4, 2026
79d7225
feat(desktop): export a captured application as an agentenv computer app
earakely-scale Nov 4, 2026
e9287c5
fix(desktop): keep state views faithful to the capture
earakely-scale Nov 4, 2026
72328ae
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
46c89d6
fix(desktop): keep coverage, logs and handles honest in the state view
earakely-scale Nov 4, 2026
8f62ebb
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
076291a
fix(desktop): export only what the computer can install and seed
earakely-scale Nov 4, 2026
fde89f2
fix(desktop): keep staged folders owned, symlinked state roots and XM…
earakely-scale Nov 4, 2026
f717ca7
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
610a61f
fix(desktop): read XML in the exported app the way the state view does
earakely-scale Nov 4, 2026
4d206f5
fix(desktop): release retained handles with their owners, read older …
earakely-scale Nov 4, 2026
2153b7a
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
652cca2
fix(desktop): boot without the working folder, bound XML, accept mirrors
earakely-scale Nov 4, 2026
634c823
fix(desktop): state view review round
earakely-scale Nov 4, 2026
b456810
fix(desktop): export review round
earakely-scale Nov 4, 2026
18039fc
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
b79c710
fix(desktop): capture review round
earakely-scale Nov 4, 2026
30fc04d
Merge branch 'feat/capture-desktop-application' into feat/desktop-app…
earakely-scale Nov 4, 2026
635b5a8
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
9612ef9
fix(desktop): read an unset client list as no windows when supported
earakely-scale Nov 4, 2026
7c3f50d
Merge branch 'feat/capture-desktop-application' into feat/desktop-app…
earakely-scale Nov 4, 2026
081e002
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
2a813c4
fix(desktop): accept packages by apt's archive origin
earakely-scale Nov 4, 2026
af7253d
feat(desktop): capture and export apps from vendor apt repositories
earakely-scale Nov 4, 2026
ea252fb
fix(desktop): report a locked database at once in data/get
earakely-scale Nov 4, 2026
f9630d9
Merge remote-tracking branch 'origin/main' into feat/capture-desktop-…
earakely-scale Nov 4, 2026
42e1b42
Merge branch 'feat/capture-desktop-application' into feat/desktop-app…
earakely-scale Nov 4, 2026
5b420c6
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
cbb4975
Merge branch 'feat/desktop-application-export' into feat/desktop-vend…
earakely-scale Nov 4, 2026
9fa9d1c
perf(desktop): detect key files without splitting the whole text
earakely-scale Nov 4, 2026
81430bd
fix(desktop): keep symlinked homes and state folders under the home i…
earakely-scale Nov 4, 2026
e087320
fix(desktop): end captures on zombies and keep cleanup owners
earakely-scale Nov 4, 2026
452ea43
Merge branch 'feat/capture-desktop-application' into feat/desktop-app…
earakely-scale Nov 4, 2026
ccbb84a
Merge branch 'feat/desktop-application-state' into feat/desktop-appli…
earakely-scale Nov 4, 2026
4ba2cd9
fix(desktop): vendor package review round
earakely-scale Nov 4, 2026
b364f4e
Merge branch 'feat/desktop-application-export' into feat/desktop-vend…
earakely-scale Nov 4, 2026
7a6ac88
docs(desktop): keep the vendor spec's gpgv command on one line
earakely-scale Nov 4, 2026
770ae41
fix(desktop): keep exported views strict JSON for infinite and inexac…
earakely-scale Nov 5, 2026
9710354
fix(desktop): record database logs and keep state changes distinct
earakely-scale Nov 5, 2026
22afbdb
fix(desktop): keep watching through unread samples and late workers
earakely-scale Nov 5, 2026
e0b401e
Merge remote-tracking branch 'fork/feat/capture-desktop-application' …
earakely-scale Nov 5, 2026
4746f91
Merge remote-tracking branch 'fork/feat/desktop-application-state' in…
earakely-scale Nov 5, 2026
61e0264
fix(desktop): bound apt sources, keep older captures and cleanup owners
earakely-scale Nov 5, 2026
cbaeb4e
Merge remote-tracking branch 'fork/feat/desktop-application-export' i…
earakely-scale Nov 5, 2026
7701d7b
fix(desktop): read relative PATH entries from the launch folder
earakely-scale Nov 5, 2026
7d2f169
perf(desktop): compare JSON views on one shared path
earakely-scale Nov 5, 2026
f99b336
fix(desktop): keep JSON Lines as text in the exported reader
earakely-scale Nov 5, 2026
d1d2830
Merge remote-tracking branch 'fork/feat/capture-desktop-application' …
earakely-scale Nov 5, 2026
4cedafe
Merge remote-tracking branch 'fork/feat/desktop-application-state' in…
earakely-scale Nov 5, 2026
963c8af
fix(desktop): keep the state-path help free of dash-led words
earakely-scale Nov 5, 2026
8ad0fab
fix(desktop): never record or export apt source credentials
earakely-scale Nov 5, 2026
674c22a
Merge remote-tracking branch 'fork/feat/desktop-application-state' in…
earakely-scale Nov 5, 2026
bbb21b6
Merge remote-tracking branch 'fork/feat/desktop-application-export' i…
earakely-scale Nov 5, 2026
ea0550f
fix(desktop): look programs up without normalizing their paths
earakely-scale Nov 5, 2026
925b695
fix(desktop): note inexact JSON numbers with a flag, not a list
earakely-scale Nov 5, 2026
4c2ec7d
fix(desktop): refuse older captures that still hold apt credentials
earakely-scale Nov 5, 2026
f42b9ec
Merge remote-tracking branch 'fork/feat/capture-desktop-application' …
earakely-scale Nov 5, 2026
c0352c3
Merge remote-tracking branch 'fork/feat/desktop-application-state' in…
earakely-scale Nov 5, 2026
ae8cfbf
Merge remote-tracking branch 'fork/feat/desktop-application-export' i…
earakely-scale Nov 5, 2026
7c49485
test(desktop): keep the credential fixture on an example host
earakely-scale Nov 5, 2026
ba5df82
fix(desktop): tidy looked-up paths and search relative entries only f…
earakely-scale Nov 5, 2026
60b73a1
fix(desktop): keep excluded and unwalked database logs unknown
earakely-scale Nov 5, 2026
cf26293
fix(desktop): judge JSON numbers after duplicate keys settle
earakely-scale Nov 5, 2026
8f590b5
fix(desktop): redact a repository's policy source when it names it
earakely-scale Nov 5, 2026
4af0541
Merge remote-tracking branch 'fork/feat/capture-desktop-application' …
earakely-scale Nov 5, 2026
1472caf
Merge remote-tracking branch 'fork/feat/desktop-application-state' in…
earakely-scale Nov 5, 2026
a5d90b8
Merge remote-tracking branch 'fork/feat/desktop-application-export' i…
earakely-scale Nov 5, 2026
b279986
fix(desktop): never signal the launch group on unknown membership
earakely-scale Nov 5, 2026
4ddd9ec
fix(desktop): look programs up in the launch's order and refuse host-…
earakely-scale Nov 5, 2026
eeda095
fix(desktop): count unchecked paths and keep logs behind them unknown
earakely-scale Nov 5, 2026
add83e5
perf(desktop): scan parsed JSON with one iterator per level
earakely-scale Nov 5, 2026
3eab240
Merge remote-tracking branch 'fork/feat/capture-desktop-application' …
earakely-scale Nov 5, 2026
a3c6b05
Merge remote-tracking branch 'fork/feat/desktop-application-state' in…
earakely-scale Nov 5, 2026
0dbab91
Merge remote-tracking branch 'fork/feat/desktop-application-export' i…
earakely-scale Nov 5, 2026
db32282
fix(desktop): start a relative program from its captured folder
earakely-scale Nov 5, 2026
b6c207c
Merge remote-tracking branch 'fork/feat/desktop-application-export' i…
earakely-scale Nov 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ export default defineConfig({
{ text: "Electron observation", link: "/electron-observation" },
{ text: "Electron fuses", link: "/electron-fuses" },
{ text: "Process capture", link: "/process-capture" },
{
text: "Desktop application capture",
link: "/desktop-application-capture",
},
{
text: "Desktop application state",
link: "/desktop-application-state",
},
{
text: "Desktop application export",
link: "/desktop-application-export",
},
{ text: ".NET assemblies", link: "/managed-code-analysis" },
{
text: "Python packaged applications",
Expand Down
7 changes: 6 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,9 @@ Choose the guide for the target and intended interaction:
- [Electron fuses](electron-fuses.md): read packaged fuse bytes before expecting `--inspect` to reach an Electron main process.
- [Node/Electron Inspector](javascript-runtime-observation.md): record script locations and execution contexts.
- [Process capture](process-capture.md): run an executable and compare terminal, exit and filesystem observations.
- [Desktop application capture](desktop-application-capture.md): launch a Linux desktop application and record its windows and home-directory state at checkpoints while it is used.
- [Desktop application state](desktop-application-state.md): read a capture folder into the application's state files, typed views of each version and kinds of change.
- [Desktop application export](desktop-application-export.md): write a capture folder as an agentenv computer app bundle with its class, skill and seed.

Runtime requests name the target and actions. Launched targets run with your
user permissions; consult the chosen guide for host requirements and effects.
Expand Down Expand Up @@ -1316,7 +1319,9 @@ the command's signal handling. Commands routed through the shared
`withCommandCancellation` handler in the CLI set status `130` for SIGINT or
`143` for SIGTERM after their operation settles. Current source registrations
use that handler for process capture and comparison (`capture-process`,
`compare-process-captures`), JavaScript application analysis (`analyze` when it
`compare-process-captures`), desktop application capture and state
(`capture-desktop-application`, `inspect-desktop-application-state`,
`export-desktop-application`), JavaScript application analysis (`analyze` when it
selects the JavaScript lane, and `analyze-javascript-application`), selected
browser, web and Electron commands (including `analyze-web-bundle`,
`observe-web-execution`, `capture-browser-scenario` and
Expand Down
284 changes: 284 additions & 0 deletions docs/desktop-application-capture.md

Large diffs are not rendered by default.

166 changes: 166 additions & 0 deletions docs/desktop-application-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Desktop application export

`rea export-desktop-application <capture-folder> <directory>` turns a folder
written by [desktop application capture](desktop-application-capture.md) into an
agentenv bundle holding one computer app environment: the real application,
installed from the distribution archive or its vendor's apt repository on the
`ubuntu-24.04` computer base, launched at boot, seeded with the user's state and
serving that state through agentenv's data operations.

```sh
rea export-desktop-application ./mousepad-capture ./mousepad-bundle
agent-env run ./mousepad-bundle --dry-run
```

The export reads the capture and its [state view](desktop-application-state.md);
it does not run the application. `--name` sets the environment name; otherwise
it comes from the desktop entry id or the program, with an `app-` prefix when
that name would not be a portable bundle name (for example all digits). The
output directory must be new or empty.

The command returns Evidence with operation `export_desktop_application` and
predicate type `rea.desktop-application-export`, listing the environment name,
owned paths, seed files and written files. A failed export, cancellation
included, removes the bundle it had not yet published, so no partial copy of the
user's data remains. If the bundle was published but its output handles could
not be closed, the command reports a cleanup error that carries the export
Evidence; the bundle itself is complete.

## The bundle

| Path | Contents |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| `envs/<name>/env.toml` | `type = "computer_app"`, the module and class, `computer = "ubuntu-24.04"`, `driver = "reference"` |
| `envs/<name>/<file>.py` | The `ComputerAppEnvironment` class |
| `envs/<name>/SKILL.md` | What the app is, its window class and where its files live |
| `envs/<name>/seed/` | The application's files from before the capture, by home-relative path |
| `envs/<name>/apt-keys/` | The signing key of each vendor apt repository the app installs from |
| `tasks/smoke.json` | A task that deploys the app |
| `README.md` | What was exported, from which capture, and what to keep in mind |

The class file is `<name>_app.py` with hyphens as underscores, for example
`mousepad_app.py`: agentenv imports it under its stem, so a plain `<name>.py`
could shadow a standard library module such as `calendar`. A class name that
would collide with a name the module imports or defines, such as `Path`, takes
an `App` suffix.

The class declares:

- `Packages(apt=[...])` with the package the capture found. A vendor package
is named `<package>/<release>`, its repository's release (the Suite apt
reports as `a=`, or else its Codename), so apt takes it from that repository
even when the Ubuntu archive offers a higher version or epoch, as noble's
transitional `firefox` does over packages.mozilla.org's; it is named alone
when the release names neither, or when the capture was written before
releases were recorded. Its library packages follow, so a package that
leaves out dependencies on the libraries it loads, as Typora's Electron
package does, still starts; library packages the capture found neither the
Ubuntu 24.04 archive nor a declared repository offers are left out, with a
limitation. An archive package's declared dependencies are trusted as they
are. The package must come from the Ubuntu 24.04 archive or a mirror of it,
or from a vendor apt repository whose signing key the capture verified and
retained. A source counts as the archive when apt reports its origin as
`Ubuntu` for a noble suite; a capture made before origins were recorded
counts only Canonical's own archive hosts.
- For a vendor repository, `sources=[AptSource(apt=..., suite=...,
components=[...], key="apt-keys/<name>.gpg")]`, with the retained key copied
to that path (`.asc` when it is armored, which agentenv tells by its
content). apt takes one key per URI and suite, so sources that share them,
such as one repository listed for two architectures, are declared once, with
their components merged; one with a different key is left out. A repository
whose key the capture did not verify, an Ubuntu Pro (ESM) repository, one
whose sources entry's URI held a user name or password (which the capture
did not record and the export never writes; the reason names neither them
nor the URI), one not reached over http or https, or one whose `arch=`
restriction leaves out the capture host's architecture is refused with its
reason (a capture
written before restrictions were recorded counts as unrestricted); when
another usable repository lists the same version it is left out instead,
with a limitation. The export also refuses local `.deb` files, snaps,
flatpaks, AppImages and captures that recorded no repositories, since the
computer could not install them by name, and a capture written before
credentials were removed whose repository URI still holds them, since it
counted that repository's libraries as installable; capture it again.
- `@computer_app(supports=...)` with both `amd64` and `arm64` for an archive
package, which the archive serves for both, and only the capture host's
architecture for a vendor package, since its repository may serve no other.
A vendor package captured on any other architecture is refused.
- A launch at boot with the captured command, ready when a window of the
captured class appears. Arguments that are the captured home or under it, and
`<option>=<path>` values under it, are respelled under the computer user's
`$HOME`. agentenv starts the app in its own folder, so when an argument, or
the program's own path such as `./app`, is relative the command first
changes to the captured working directory
(`"$HOME"` when that was the home), and still starts if that folder is
missing. The export refuses a capture that saw no application window.
- For an Electron application (the capture found its fuse sentinel or a
`resources/app.asar`), `--no-sandbox` right after the program unless the
command already has it: Chromium's sandbox needs user namespaces the
computer's container does not grant, and Electron exits without them. The
README and the result's limitations say the sandbox is off.
- `paths`: the state directories holding the application's state files and the
folders holding its documents, nested folders collapsed. The home itself and
the shared `.config`, `.local`, `.local/share`, `.local/state` and `.cache`
folders are never owned whole: a file directly in one is owned as itself.
Files the capture recorded under a symlinked state directory's physical
spelling are mapped back to the innermost such directory's home spelling,
unless that spelling is outside the captured home, as when the home itself is
a symbolic link. When two files map to one home path, the export uses the one
present before the capture, else the first by path, and a limitation names
the other.
- Data hooks, because declaring any hook switches off agentenv's default data
plane: the class seeds the home once per boot and keeps it as the initial
state with `keep_initial`, `data/add` and `data/reset` call agentenv's `add`
and `reset`, and `data/get` returns the files under `paths` plus a data part
with a typed JSON view of each, read in a worker thread. An application with
no observed state declares no paths and no hooks.

Seeded text files (text, JSON, key files and XML in the state view) have the
captured home path respelled to the computer's home wherever it is followed by
`/`, the end of the text, or a character that cannot continue a file name;
other files are copied as captured. A file under the owned paths whose starting
bytes the capture did not retain is not seeded: one in the state view without
retained baseline content, or one the capture omitted (for example too large
or past the content budget). The result lists them in `unseeded_files` and the
README names them. When the application held files open in folders whose
starting content the capture did not retain, a limitation names those folders.

The application's dconf keys at the baseline, if any, are written once when the
app first starts, also for an application that owns no files, and the class adds
`dconf-cli` to its apt packages for that. The server starts before the
desktop's D-Bus session, so the class does not use `dconf load`: it dumps any
existing user database through a `file-db:` profile, then runs `dconf compile`
on that dump and the captured keys, in that name order so the captured keys
win, and replaces `~/.config/dconf/user`. If that fails, the server prints a
one-line warning and the app starts without the captured keys. `data/reset` does
not restore them.

## Typed views

The views follow the [state view](desktop-application-state.md)'s formats and
shapes: SQLite tables (at most 64 ordinary tables, `tables_truncated` past
that, and per table `columns`, `primary_key`, at most 500 `rows` and
`truncated`, with blobs, unsafe integers and text that is not UTF-8 tagged as
REA tags them), JSON, XML as `{root, truncated}` with DOCTYPEs refused and REA's
node, depth and whitespace rules, key file groups, and text as
`{lines, line_count, truncated}` split on `\n` and `\r\n`. A JSON file
holding a number JavaScript cannot read exactly, such as an integer above 2^53,
gets an `error` instead of a view, as in REA, while text that is not one JSON
document, such as JSON Lines, stays text. Format detection follows REA's
rules. Files larger than 64 MiB get an `error` instead of a view
and are not read. Known differences remain:

- XML names: element and attribute names are Python's `{namespace}local`
spelling rather than REA's prefixed names, namespace declarations are not
listed as attributes, and CDATA sections merge with the text around them, so
they count as one node.
- dconf databases are reported as binary, since REA reads them with `dconf`.
- SQLite databases are read in place, read-only, rather than from a staged copy;
a write lock the running app holds is waited out for a quarter of a second
and then gives the file an `error`, and so does a table or column name that
is not UTF-8.
- JSON nested deeper than Python's recursion limit gives the file an `error`.

The seed is the user's real data. Keep the bundle local and do not publish it.
The class was checked against agentenv-framework 0.9.1431 with
`python -m agentenv_protocol.computer._declare` and `agent-env run --dry-run`.
Loading
Loading