Changelog¶
AmiPilot is developed phase by phase; each one is validated on-target
under Copperline against real AmigaOS before being considered done. This
page summarizes what landed in each release, in user-facing terms — see
the repository's
docs/implementation-plan.md
for the full engineering detail and phase sequencing behind each one.
v1.1 — 2026-08-10¶
Closes out every gap the 1.0 release itself named as open: requester
support was detection-only, menu selection needed a keyboard shortcut,
and layout.gadget-nested children were unreachable — all three are
real now. Plus a new interactive discovery tool, and host-package
polish.
PICK, interactive "pick mode" discovery (issue #65): point at a gadget on the real screen, get back its exact locator directly — the platform's first genuinely interactive UIA-Inspect/browser- element-picker equivalent, not just a batch dump.PICK [SCREEN=<substring>]hit-tests the live global pointer position against a screen's windows and returns the gadget under it, if any;AmiInspect PICKis the standing-at-the-machine equivalent with no host/server session at all, looping locally and printing only when the identified window/gadget changes. Built on two newintuition-modelprimitives shared by both:AmipHitTest()(a coordinate-to-gadget hit test reusing the same role/label classificationTREE/AmiInspectalready do) andAmipReadPointerPosition(). Two real, non-obvious findings from building this, live: Intuition's own window list is NOT front-to-back z-order (Workbench's full-screen backdrop was found ahead of a real foreground window — fixed by picking the smallest-area matching window instead), and the live pointer position's Y coordinate needs an unconditional 2x correction — an interlace-gated first guess was tried and disproven against this project's own default Workbench screen. See Wire Protocol and AmiInspect Reference.- Progress feedback for long wire transfers (issue #53):
screenshot()/fs_get()accept an optionalon_progress(bytes_so_far, total_bytes)callback, called as a large payload streams in — a realSCREENSHOTover serial can take anywhere from seconds to several minutes with previously zero feedback.amipilot.stderr_progress()is a ready-made callback for the common "print a progress line" case. Defaults toNoneeverywhere — no behavior change for existing callers. - The host package can now be published to PyPI (issue #77):
host/pyproject.tomlgained the metadata (license, authors, URLs, classifiers) a real PyPI listing needs, and the release workflow gained a job that builds and publishes it via Trusted Publishing (OIDC, no stored token) once tagged. The one-time PyPI-side setup (registering the pending publisher, creating the required-reviewer environment) is still outstanding —pip install amipilotisn't live yet; installing from a clone remains the only path for now, see Installation. CLICKcan now dismiss BOTH window-owned and system-wide Requesters — issue #52 fully closed: a genuineAutoRequest()/BuildSysRequest()/EasyRequest()requester with a real owning window turns out to open as a completely ordinary, separatestruct Window(sharing its owner's exact title text) rather than attaching invisibly toFirstRequestthe way the issue's original design sketch assumed — so ordinaryCLICK <window-pattern> <gadget-id>already reaches its Yes/No gadgets today, no new locator or wire change needed.BuildSysRequest()'s own autodoc documents a fixed, app-independentGadgetIDconvention (TRUE=1 for the positive choice,FALSE=0 for the negative), confirmed live:CLICK <same-pattern> 1genuinely dismisses the requester, verified byWAITFOR REQUESTERcorrectly timing out again afterward. The system-wide case (no owning window — a real disk-swap prompt, DOS error, or Guru) works the same way:BuildSysRequest(NULL, ...)(exactly whatdos.librarycalls internally, not a simulation) confirmed live to produce a window titled EXACTLY"System Request"— Intuition's own documented fallback title, not a coincidence — whichWAITFOR REQUESTERnow matches andCLICK "System Request" 1dismisses through the same mechanism. Confirmed separately thatGETTEXTreading a requester's own body text is a permanent limit, not an open gap: a live dump of every open window'sFirstRequestwhile a realAutoRequest()was up showed it NULL on both the owning window and the requester's own window — nostruct Requesterexists anywhere reachable here to readReqTextoff of on this target's real OS/ROM.- BOOPSI/ReAction role classification for 12 WB3.2-era gadget
classes (issue #69):
clicktab.gadget,colorwheel.gadget,datebrowser.gadget,fuelgauge.gadget,getcolor.gadget,getfile.gadget,getfont.gadget,getscreenmode.gadget,gradientslider.gadget,palette.gadget,sketchboard.gadget,speedbar.gadget, andtexteditor.gadget— previously allrole=custom— now get a real, AT-SPI-style role (page_tab_list/color_wheel/calendar/progress_bar/color_chooser/file_chooser/font_chooser/screenmode_chooser/slider/palette/canvas/toolbar/text_editor), addressable via tier-2ROLE=locators.speedbar.gadgetturned up a real, easy-to-guess-wrong exception: its registered class name is literally"speedbar", not"speedbar.gadget"like every other class here — found live, not from documentation, which follows the"name.gadget"pattern uniformly. A new fixture (fixtures/reaction-classes-app) exercises one instance of each class directly (deliberately not nested insidewindow.class/layout.gadget, which would make them unreachable the same way issue #49 already documents), with a checked-in golden-tree file locking in the live-confirmed output.space.gadget,virtual.gadget,listview.gadget,tabs.gadget, andtapedeck.gadgetwere deliberately left unclassified — see Locator Tiers and Limits for why each one is an honest gap rather than an oversight. WHERE, the cooperative geometry port (issue #49): the honest escape hatch for gadgets nested inside awindow.classwindow'slayout.gadget— permanently invisible to structural walking on classic AmigaOS 3.x, so no plain manifest entry could ever name them. An application implementing this exposes a small, optional ARexx port answeringWHERE <name>with a gadget's own live geometry (it already holds the object pointer for its own event dispatch); a format-version-2 manifest names such a gadget withWHEREGADGETinstead ofGADGET.CLICK/TYPE @namethen work exactly as they would for any other manifest name — discovery is cooperative, but the click itself is still genuineinput.deviceinput, unlikeMUIREXX, where the target's own port does the acting too. Verified end to end againstfixtures/classact-app's own newCAAPP.WHEREport, whose three gadgets are now addressed entirely viaWHEREGADGET(its manifest previously, deliberately, named none at all). See ARexx Reference.MENUPICK's pointer-based fallback (issue #63): a menu item with no keyboard shortcut used to be rejected outright — now it's picked via a genuine synthesized right-mouse-button-down/move/move/ release sequence instead, chosen automatically whenever an item has no shortcut, the same "real input.device events, not a shortcut" principle every other verb already follows. Verified end to end against two new shortcut-less items onfixtures/gadtools-app's own menu strip — a top-level item and a one-level-deep submenu item.STRING_KINDvsINTEGER_KINDclassification (issue #64): both GadTools kinds create the same underlyingGTYP_STRGADGET, so nothing in the raw gadget structure told them apart — a smaller version of theBUTTON_KIND/CHECKBOX_KINDproblem, solved the same way.GT_GetGadgetAttrsA's documented per-kind tag table listsGTIN_NumberunderINTEGER_KINDonly; asking a plain string gadget for it is a safe, documented no-op, the discriminator rather than a guess. Integer gadgets now reportrole=integer. Verified against a new CountINTEGER_KINDgadget onfixtures/gadtools-app's own window.
v1.0 — 2026-08-09¶
The first full release: everything the implementation plan's 1.0 gate asked for — getting files and programs onto the machine the way a real user would, seeing what's actually on screen, and manipulating whole windows — verified end to end, including on a completely bare machine profile and against real Picasso96/RTG.
FSPUT: push a file from the host onto the Amiga, completing the file API's round trip (FSGETcould already read one back). The wire's first request to carry a raw binary body — and deliberately wire-only, with no ARexx form at all, since ARexx messages can only ever carry string arguments (a real, permanent transport asymmetry, stated rather than papered over). See Wire Protocol.WBLAUNCH: launch a program the way Workbench itself does — a genuineWBStartup/WBArgmessage to a real non-CLI process, the same technique real launcher utilities use — withTOOLTYPE=overrides (merged into a scratch copy of the icon inT:, never the application's own.infofile, because a Workbench-started program reads its tooltypes back off disk — there is no in-memory channel) andARG=project-file arguments.LAUNCH(0.4) covers the Shell-start case; this covers the other half of how real software actually starts. See Wire Protocol.SCREENSHOT: capture a screen (or one window's region of it) as raw pixels — classic planar screens and genuine Picasso96/RTG bitmaps in their native pixel formats — with all image-format work (PNG, IFF ILBM, P96 pixel-format decoding, including the documented 16-bitPC-suffix byte-order pitfall) done host-side, stdlib-only. The P96 path is verified against real Picasso96 under emulation, both CLUT and truecolor, not just compiled. Inspector tooling, not a locator mechanism. See Wire Protocol.WINDOWMOVE/WINDOWSIZE: move or resize a whole window via the same genuinely synthesized press/move/release drags gadgets already get, anchored on the window's own title bar or sizing gadget. See Wire Protocol.WAITFOR REQUESTER: wait for a genuine Intuition Requester to appear — the detection-only first slice of requester support (window-attached requesters only; addressing or clicking a requester's own gadgets is stated, open follow-up work, not quietly missing). See ARexx Reference.- Fixed:
CLICKwith aROLE=/INDEX=locator could silently act on the wrong system gadget (close/depth/drag) instead of the application gadget the locator actually matched. - Verification hardened for 1.0: the on-target suite now includes
the implementation plan's own "bare machine" lifecycle check (a
stock boot with nothing pre-staged), an automated TCP-transport
check (real
bsdsocket.libraryover Copperline's host networking, not just serial), and an automated P96SCREENSHOTcapture-path check — plus a dedicated pre-1.0 code review whose findings were all fixed before this release. - Protocol verbs promoted to stable: the implementation plan's
own 1.0 gate — every verb the
VERSIONhandshake reports is nowSTABLE(won't break within a major), notEXPERIMENTAL. See Wire Protocol.
Known gaps, stated plainly: requester support is detection-only;
menu selection still needs a keyboard shortcut (pointer-based
selection for shortcut-less items isn't built); a window.class
window's layout.gadget-nested children remain unreachable on
classic OS 3.x (a documented platform limit, not a bug — see
Locator Tiers and Limits); and TCP
remains LAN-only trust — no TLS, even though every verb is now
stable, see Securing TCP.
v0.5 — 2026-08-08¶
Reliability and reach into the wider ecosystem: waiting on real conditions instead of guessed sleeps, community-authored coverage for apps you don't control, structural regression fixtures, verification against genuine stock AmigaOS and MUI software (not just purpose-built fixtures), and a real bridge into MUI's own ARexx-driven applications.
- Wait/expectation primitives (
WAITFOR, andCLICK's trailingEXPECT=): closes the classic click-then-check race by polling server-side, in the same request, instead of a host-side loop.WAITFOR WINDOW=<pattern>/NOWINDOW=<pattern>wait for a window to appear or close;CLICK's ownEXPECT=WINDOW=<pattern>/EXPECT=NOWINDOWcompose the click atomically with the wait,EXPECT=NOWINDOWchecked by identity against the exact window the click itself resolved (not a fresh pattern search).WAITFORalso takes aTEXT=<value>condition — wait for a gadget's text to exactly equal a value — reusing the same locator parsing and gadget-text readingCLICK/TYPE/GETTEXTalready share. A condition that never becomes true is a new, distinctRC=15. See ARexx Reference. - Quirk profiles: the manifest format (Manifest contract) isn't only for an application's own developer — the same file format, loaded the same way, works for a user or the community describing a third-party application's structure, with a documented convention for recording known behavioral oddities as comment lines. No new machinery, one grammar for both cases.
- The honest-limits toolkit-to-tier table
(Locator Tiers and Limits): which
locator tier actually reaches which kind of UI, and why — plain
GadTools/top-level ReAction gadgets,
window.class+layout.gadgetnested children, MUI applications, and custom-rendered UIs each land somewhere different, stated plainly rather than left to discover the hard way. - Golden-tree fixtures: a saved structural dump doubles as a
regression fixture —
amipilot dump <window> --golden PATH [--update-golden], orAmipilot.assert_tree_matches()inside a test, compares a live window/gadget tree against a checked-in snapshot and fails loudly on drift. - The stock-app conformance set: automation verified against
genuine, unmodified stock AmigaOS software, not only hand-written
fixtures — driving AmigaOS 3.2's own Time Preferences editor
end-to-end via tier-2 locators discovered purely from
AmiInspect/amipilot dumpoutput. This also caught two real bugs: a host client socket-timeout bug (connect_with_retry()could silently break a legitimately slow-but-successfulWAITFOR/EXPECT=wait) and a genuine machine-wide hang walking a different stock application's window (a custom gadget claiming a BOOPSI object header it didn't actually have) — both fixed, the latter now regression-tested directly against the real application. - The MUI-ARexx bridge tier (
MUIREXX <app-base> [TIMEOUT=<n>] <command...>): drives a MUI (Magic User Interface) application through the ARexx port every MUI app carries automatically — a different mechanism fromCLICK/TYPE/GETTEXTentirely, since MUI internals are opaque to structural walking. Verified live against a real MUI application that MUI's own built-in ARexx support is a small, universal command set (window lifecycle plus fixed metadata), not a generic per-widget accessor —MUIREXXpasses an application's own commands through honestly rather than promising generic MUI widget control it can't deliver. See ARexx Reference.
v0.4 — 2026-08-07¶
Reach: everything 0.3's wire needed to actually be useful for driving a real application end to end — a second transport, launching the subject under test, moving files, menus, and two new ways to locate and act on a gadget.
- TCP transport (
AmiPilotServer TCP TCPPORT=n): the same wire overbsdsocket.library, for real hardware with TCP/IP or an emulator with no serial bridge — listen-mode only for now (the host connects in). Opt-in hardening:TCPALLOW(a source-IP/CIDR allowlist) andTCPPASSWORD(gates a newAUTHverb, defaulting to a public starting password so it works out of the box). Neither makes this internet-safe — no TLS, no rate-limiting; LAN/trusted- network use only, see Wire Protocol. - Program launch (
LAUNCH [STACK=n] <command-line>): starts the test subject itself over the wire —SystemTagList()-based, asynchronous, with an overridable stack size — so a test session doesn't need the target pre-staged viaS:User-Startup. - The file API (
FSLIST/FSSTAT/FSMKDIR/FSDELETE/FSGET): allowlist-scoped to directories granted at startup (FSROOT), disabled entirely otherwise. A test-staging channel for small fixtures/config/log files, not a file manager —FSGETis capped at the server's own internal buffer.FSPUT(host-to-Amiga writes) needs a wire protocol addition and isn't built yet. - Menus (
MENU/MENUPICK): walks a window's live menu strip — every pulldown, its items, checkit/checked/enabled state, and any keyboard shortcut — and selects an item via that shortcut, the same input.device path a human pressing Right-Amiga+key would use. Pointer-based selection for items with no shortcut isn't built yet. - Multi-screen support (
SCREENS,SCREEN=<substring>): lists every open screen and narrows any window-targeting verb's search to a specific one, keyed off each screen's ownDefaultTitle. - Tier-2 semantic locators (
ROLE=<role>/LABEL=<substring>/INDEX=<n>, in place of a bareGA_IDonCLICK/TYPE/GETTEXT): find a gadget by role and label text, or by position among several matches, instead of only by numeric ID or a manifest@name— see ARexx Reference. Proximity-to-a-label matching (the third tier-2 style from the design docs) isn't built yet. DRAG: a genuine press/move/release drag, either by a pixel offset from a gadget's current center (the natural shape for adjusting a slider/scroller) or onto a second gadget's center (drag-and-drop/reorder, both resolved live, zero coordinates in the script).- Host-side real serial port support
(
Amipilot.connect_serial()/WireClient.connect_serial(), the pytest plugin's--amipilot-serial-device): connect directly over a real or virtual serial port — real Amiga hardware over a real cable, or a Copperline config using a real serial device — instead of only Copperline's TCP bridge. Optionalpyserialdependency (pip install amipilot[serial]).
Known gaps, tracked as real follow-up work, not silently accepted:
- No wait/expectation primitives yet (
clickthat waits for an expected change, timeouts) — a script still adds its own polling. Carried over from 0.1–0.3. - The wire connects host-to-Amiga only; the Amiga dialing out to a configured host (useful behind NAT) is a considered future addition (#12), not yet built.
- The MUI locator tier (driving MUI apps through their own automatic ARexx port) isn't started.
- No public CI on-target run yet, same reason as 0.1–0.3:
make test-targetneeds a machine-specific Workbench install CI doesn't have.
v0.3 — 2026-08-06¶
The wire and the host client: the same command set the ARexx port speaks, now reachable from a host machine — no ARexx interpreter or even a Workbench session on the Amiga side needed to drive it.
- The wire protocol (
server/WIRE.md): a length-prefixed line protocol over serial.device, with no JSON anywhere — requests are the exact same command grammar the ARexx port already parses, responses areRC <code> <byte-count>followed by exactly that many payload bytes, binary-safe with zero escaping. AVERSIONhandshake reports the server version, the protocol number, and which verbs are stable vs. experimental. AmiPilotServer SERIAL: the commodity now optionally carries its whole verb set over serial.device (SERDEVICE/SERUNIT/BAUDto configure), alongside its existing ARexx port — the same dispatch serves both, so results are identical either way. See the new Wire Protocol page.- The host Python client (
host/,pip install -e host/): a transport-levelWireClient, andAmipilot— the Pythonic object API (tree()/click()/type()/get_text()/manifest(), plus@namelocator forms) that raises typed exceptions instead of requiring manual RC checks. amipilot dump <window>: the host half of "the inspector" — connects and prints a window's gadget tree, either in the same formatAmiInspectprints or as ready-to-paste# name = <id>suggestions for a quirk profile.- A pytest plugin: the
amipilotfixture boots a configured Copperline (or real-hardware-adjacent) session and hands a test a connected client — session-scoped, and it skips cleanly rather than failing when no emulator config is set up. This delivers the phase's actual release gate: a host pytest test types into a field, reads it back, clicks a button, and asserts the window closed — driven entirely from the host, with Copperline booted by the test itself.
Known gaps, tracked as real follow-up work, not silently accepted:
- TCP transport (for real hardware or an emulator with no serial bridge) is phase 0.4 scope, along with program launch, the file API, menus, and drag.
- No wait/expectation primitives yet (
clickthat waits for an expected change, timeouts) — a script still adds its own polling. - The wire connects host-to-Amiga only; the Amiga dialing out to a configured host (useful behind NAT) is a considered future addition, not yet built.
- No public CI on-target run yet, same reason as 0.1/0.2:
make test-targetneeds a machine-specific Workbench install CI doesn't have.
v0.2 — 2026-08-05¶
The act side of object-level GUI automation: a server commodity, driven by ARexx, with no host machine involved.
AmiPilotServer: a commodity hosting the action engine and theintuition-modelwalker behind a genuine public ARexx port. See the ARexx Reference.TREE/CLICK/TYPE/GETTEXT/QUIT— locate a window by title, click a gadget byGA_IDthrough a realinput.deviceevent (the documentedIECLASS_NEWPOINTERPOS/IESUBCLASS_PIXELmechanism, not a coordinate hack), type text into it via genuineIECLASS_RAWKEYevents paced to approximate human typing, and read state back — proven end to end by drivingAmiPilotServer's own test fixture from a real ARexx script: type into a field, read the value back, click a button, confirm the window closed.AmiInspect's gadget-tree output gains avalue=field for string and integer gadgets — their live editable contents, not just their label. See the updated AmiInspect Reference.- BOOPSI/ReAction gadget geometry (
GA_Left/GA_Top/GA_Width/GA_Height) is now read correctly, including the classicGFLG_RELWIDTH/RELHEIGHT/RELRIGHT/RELBOTTOMconvention (a gadget's size/position stored as an offset from its window's own dimensions) — needed forCLICK/TYPEto land on a BOOPSI gadget at all, not just forAmiInspectto report sane numbers. - Both
AmiInspectandAmiPilotServernow embed a standard$VER:cookie — see Installation. amipilot.lhanow ships both binaries — see Installation.
Known gaps, tracked as real follow-up work, not silently accepted:
STRING_KINDandINTEGER_KINDGadTools gadgets still aren't distinguished from each other (both report asstring) — carried over from 0.1.- No wire protocol yet — ARexx only reaches scripts running on the same Amiga. Serial.device and a host Python client are phase 0.3 scope.
- No wait/expectation primitives yet (
clickthat waits for an expected change, timeouts) — a script has to add its ownWait/polling for now. - No public CI on-target run yet, same reason as 0.1:
make test-targetneeds a machine-specific Workbench install CI doesn't have.
v0.1 — 2026-08-05¶
First release: the read side of object-level GUI automation.
intuition-model: a reusable Intuition/BOOPSI walker library, reading windows and gadgets under strictLockIBase()discipline (brief holds, copy-out, no live pointers handed out, no patching orSetFunction()anywhere).AmiInspect: a standalone Shell command that prints any window's gadget tree by role, label, class, ID, position, and state. See the AmiInspect Reference.- Plain GadTools role classification, including officially-sanctioned
GT_GetGadgetAttrsAkind-probing to distinguish a checkbox from a button (both produce the same underlying gadget type). - BOOPSI/ReAction class reading via
OCLASS()— a documented NDK mechanism, not a hack — correctly identifying real class names and mapping known ones to roles. - Verified against two purpose-built conformance fixtures and a real,
unmodified stock AmigaOS Prefs editor (
ScreenMode) — not just software built for this project. - An automated on-target regression check (
make test-target, headless under Copperline) — see Building and Testing. - Documented, permanent limits rather than silent gaps:
PLACETEXT_INbutton labels andlayout.gadget-nested gadgets are both genuinely unreadable at this tier — see Locator Tiers and Limits. amipilot.lha— a pre-built binary release archive (AmiInspect+ license + this documentation as an AmigaGuide) — see Installation.
Known gaps, tracked as real follow-up work, not silently accepted:
AmiInspectdoesn't yet embed a$VER:cookie.STRING_KINDandINTEGER_KINDGadTools gadgets aren't distinguished from each other (both report asstring).- No public CI on-target run yet —
make test-targetneeds a machine-specific Workbench install (see Building and Testing) that CI doesn't have.