aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_skill.py (follow)
Commit message (Collapse)AuthorAge
* slosh: rename the project from sl0pptyuser2026-08-18
| | | | | | | | | | | | | | | | | | | | | sl0p + pty was error-prone to type and the zero made it worse; slosh (slop + sh) is one syllable, impossible to typo, and slosh.foo is ours. Mechanical rename everywhere it appears: binary, env vars (SLOSH, SLOSH_SESSION, SLOSH_BIN, SLOSH_CONFIG), socket dir (/tmp/slosh-<uid>, $XDG_RUNTIME_DIR/slosh), config path (~/.config/slosh), header guards, src/sl0ppty.h -> src/slosh.h, contrib/sl0ppty-dev -> contrib/slosh-dev, the driving-slosh skill, docs and tests. The sl0ppi fork references and the sl0p theme stay: one is history, the other is the brand. The rename surfaced a latent bug in test_osc5577: the hello-reply answer always appeared on screen twice (ECHOCTL echo plus cat -v), but a seven-character implementation name happened to wrap the second copy across the 60-column boundary, so counting substrings in wrapped text found one and passed for the wrong reason. A five-character name moved the wrap and exposed it. The loop guard itself is correct -- with echo off before the request there is exactly one reply -- so the test now counts on de-wrapped text and tolerates the pane's own echo.
* sl0ppty: ruff format the Pythonuser2026-08-17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The bumpy one, again. Every `.py` outside `vendor/` plus the three `contrib/` scripts that are Python with no extension: 60 files, 6,714 lines rewritten, no behaviour changed. Nothing here is worth reading -- the next commit adds it to `.git-blame-ignore-revs` so `git blame` walks past it. It is a bigger reformat than the C was, 60% of the lines against 17%, and for a reason worth writing down rather than tuning away: ruff and black break a call that does not fit onto one argument per line, where this code aligned the continuation under the opening paren. The test suite is thousands of `check("some prose", cond, detail)` calls, so nearly every one of them changed shape and the suite grew 3,308 lines. Line length was the one lever that moved it -- 120 columns would have been a 40% reformat and no growth at all -- and it was left at 88, because picking an unusual width to dodge a one-time diff is paying forever to avoid paying once. Import sorting came with it. Two files had `import os, pty, uuid, select, struct, sys, termios, time, fcntl, signal` on one line and now have ten. Verified after, not before, because a formatter that alters behaviour would be worse than none: - `make retest`, 1801 checks green -- and this reformats the suite that does the checking, including `harness.py`, which every one of those files imports. - `make test-live`, the tty tests the fast suite skips. They matter here more than usual: `live_input.py` and `live_m0.py` are two of the files whose comma-imports were split, and nothing in the fast suite would have noticed if that broke them. - All 60 files still compile, `make fmt` twice in a row changes nothing the second time, and `ruff check --select I` has nothing left to say.
* sl0ppty: a --script pane is told no session rather than somebody else'suser2026-08-17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | `server.c` set `SL0PPTY_SESSION` for its panes and carried a comment saying the value was "deliberately unset under `--script`". Nothing unset it. A value never set is a value inherited, so a script driver started from inside a pane handed its own panes the *outer* session's name -- and the recipe the skill documents, `-s ${SL0PPTY_SESSION:-main} cmd`, then sent its commands to a session it was not in. Not a fallback to a guess: a confident wrong answer, because the name it read was a real session that really answers. Both variables now go through one `session_env()`, called by the server with its name and by the headless driver with NULL, because the two modes have to agree about what a pane is told and a comment in one of them is not agreement. `SL0PPTY_BIN` is set there too rather than inherited, so it names the binary that actually made the pane instead of one that made some pane somewhere -- which is the whole reason that variable exists. Found while explaining a stale pane title, from a check in `test_skill.py` that failed under `make test` and passed on its own. It was not flaky: the suite runs from inside a pane, where these are set, and the tool I ran it from by hand had an environment where they were not. A test whose answer depends on where you run it looks exactly like a race. So it now passes the lie in deliberately: the session under test is started with `SL0PPTY_SESSION=somebody-elses-session`, which makes the difference between "never set" and "cleared" visible from any shell. The mirror check is next to it -- a real session tells its panes its *own* name, even when started from inside another one -- so the obvious wrong fix, unsetting it in both modes, fails. Both were verified by making both wrong versions and watching each test bite. The two title checks now wait for the line instead of sampling after 120ms. That was not the bug (a shell prints in about 12ms here, even under disk churn) and it is one assumption fewer.
* sl0ppty: a skill for driving it, and the two variables that made one possibleuser2026-08-17
`.agents/skills/driving-sl0ppty/SKILL.md`. The convention is `.agents/skills/<name>/SKILL.md` with YAML frontmatter -- already in this tree under `vendor/libghostty-vt`, so it is not an invention -- and it means an agent working in a checkout picks the skill up without being told, while a directory per skill leaves room for adjuncts later. `contrib/README.md` and `docs/scripting.md` say where it is and how to use it elsewhere: copy or symlink the directory into wherever your agent keeps skills. **Writing it turned up the reason there was no skill worth writing.** A pane is told `SL0PPTY=1` and nothing else, so a program inside one knows it is in a session and cannot find out *which* -- `ls` lists them all and none of them is labelled "the one you are in". So the whole most valuable case, an agent driving the session it is running in, was guesswork. The server now sets `SL0PPTY_SESSION` and `SL0PPTY_BIN` in its own environment, which every pane inherits through fork: one place, no signatures threaded, and unset under `--script`, which has no socket to name. `SL0PPTY_BIN` is read from `/proc/self/exe` rather than `argv[0]`, and exists because the failure it prevents is the one I hit writing the examples -- a session started from a build tree, and `sl0ppty` not on the pane's `PATH`, so the skill's own commands did not run. What the skill actually teaches, none of which `docs/scripting.md` says because a reference is not allowed to have opinions: - Work belongs in a pane that was **given** a command, not typed into somebody's shell. Then `alive` and `exit_code` off `panes` are how you wait, and the output survives the program (D14) so it is still there to read. - There is **no wait primitive over the socket**: `settle` is headless-only, and `deadline` is about repaints and says nothing about program output. Poll. - `purpose` is the handle. Titles change underneath you; a declared purpose cannot be relabelled by the program in the pane, which is what makes it safe to address. - `snapshot` is the whole composited session, and only what is on screen. Cut a pane out with its `content_*` rect; do not expect scrollback. - **The echoed command line matches your own marker.** `echo DONE` puts `DONE` on screen twice. Count, or use a marker you did not send. - Write `\e`, never `\033`: the unescaper takes `\0` first, so `\033` arrives as NUL followed by `33` and nothing complains. That bug was live in this repo's own test suite until last week. - A program in a pane can draw its own status and real buttons with OSC 5577 and read the click off its stdin, which is how it asks a question instead of printing a prompt and hoping. - Close what you made; do not `quit` a session you did not start. `tests/test_skill.py` is the part that keeps it true. A skill is documentation an agent acts on without a human reading it first, so a stale one is worse than a missing one: a wrong verb is a failed tool call somebody else has to debug. Every verb the file names is sent to a live session and must not answer `unknown cmd`; every `panes` field its central recipe polls must exist; the environment it promises is read out of a real pane; the recipe itself is run end to end and must report exit status 3 with its output still on screen. It also greps the file for paths out of a home directory, because "plug and play" is a property somebody has to keep. That test found `close-pane` in the draft, which is the *keybinding* action name -- the verb is `close`. Exactly the class of error the test exists for, found before anybody's agent ran it. Verified: 1795 checks, all green, 43 of them in `tests/test_skill.py`. Then the skill's recipe run verbatim against a real session -- tagged pane, polled to `alive:false`, `exit status: 3`, `compiling`/`built`/`exited: status 3` still readable -- and from inside a pane, `session=agentdemo bin=/…/sl0ppty` and a `building 3/7` status drawn in its own frame.