diff options
Diffstat (limited to '.agents')
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 139 |
1 files changed, 105 insertions, 34 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 844e028f..7f33937f 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -64,40 +64,58 @@ $m/pane/<n>/pty/run write one line, read `exit N` + its output, or `busy` / `e (a fresh terminal: waits for its first prompt; if none comes, interrupt the read) $m/log recent events, then EOF: new|del|rename|save <serial> <name>, msg <serial|-> <text>, run <serial> <line>, exit <serial> <N|?>, send <from> <to> <repl-id>, + ask <serial> <what> <choices>, answer <serial> <choice|->, changed <serial> + [reloaded|deleted] (under unsaved edits; a clean reload; gone from disk), + unsaved <serial> <name> (each pane a refusal is about), newcol|delcol <serial>, restored|restoredcol <old> <new>, dump|restore <path>, err <serial|-> <file>: <why> - (exec 3<>$m/log; echo follow >&3; cat <&3 waits for new ones; tail -f does not; - while read -r line <&3; do ...; done loses nothing) + (exec 3<>$m/log; echo follow >&3; cat <&3 replays what is there, then waits for new + ones; `echo follow new` waits for new ones only; tail -f does not wait; + while read -r line <&3; do ...; done loses nothing within a session (through + FUSE, `read -t` never times out: use `timeout N` around the loop): a Restore + hangs every connection up, so dial again, and restart a 9ns mount) $m/screen the rendered screen as JSON, frozen per open $m/listeners this session's dial addresses $m/focus the serial of the pane with the keyboard (empty while a column/workspace tag has it); echo a serial into it to move the keyboard (a folded pane stays folded) -$m/ctl the settings, one a line as a write takes them; write a setting or a session builtin +$m/ctl the settings, one a line as a write takes them; write a setting or a session builtin; + `size <cols> <rows>` sizes a --detach session no frontend is attached to (160x50 before) (Newcol makes an empty column, Dump, Theme x; Exit QUITS the editor, Kill [word...] stops the commands pardes started (command panes, lines it typed into shells), a word matching a command line's first word; Exit and Restore refuse once, - naming every unsaved pane, `<a>, <b>: Modified (Exit again to discard them all)`, and the same - word again DISCARDS that text -- not a retry, unlike lock's `file in use`; - Kill signals only the foreground job, so of `sleep 30; echo done` the - echo still runs; Joincol needs a column to the right of the keyboard's); + logging `unsaved <serial> <name>` for each unsaved pane, then failing the write with + `<name>: Modified (Exit again to discard)` for one or `4 unsaved panes: Modified (Exit + again to discard)` for more (Restore's says `Restore again`); a second refusal names only the panes edited since the last, + as acme's does, and the same word again with nothing edited since DISCARDS them all -- not a retry, unlike lock's `file in use`; + Kill stops a command pane's whole line, `&` jobs included; of a line typed into a + shell only the foreground job, and the shell decides the rest (of `sleep 30; echo done` + bash and fish both run the echo); Joincol folds the keyboard's column into the one + on its right, its panes going below that column's own, in order, and needs such a column); a pane's builtins (Del, Save f, Collapse, which folds that pane, and the column word Delcol, which closes that pane's column) go to $m/pane/<n>/ctl -$m/commands every builtin: `Word`, `Word arg`, then `root` or `pane` (which ctl takes it) -$m/layout one line per column: serial index x width current|notcurrent empty|full pane-serials...; active <serial> +$m/commands every builtin: `Word`, `Word arg`, then `root`, `pane` or `both` (which ctl takes + it), a setting's values comma-joined, then ` -- ` and one sentence of what it does +$m/layout one line per column (16 columns at most: Newcol past that fails, no space for a column): serial index x width current|notcurrent empty|full pane-serials...; active <serial> $m/tag the workspace tag (> replaces, >> appends, one line); $m/col/<serial>/tag a column's (serials stay, as panes' do) +$m/tagexec a word as a click in the workspace tag (pane/<n>/tagexec: in that pane's tag); every exec + file reads back the serials its last write touched $m/col/<serial>/ctl Delcol, Joincol, New, Tty on that column; col/<serial>/exec a word as a click in its tag; rmdir col/<serial> closes an empty column $m/pane/new open it to make a pane (a scratch named <dir>/+New), read names it; it goes in the ACTIVE column (the one last typed or clicked in, or Newcol's), filling it if empty, else taking the bottom half of its last pane (ctl `Placement pardes`: the old rules); - rmdir $m/pane/<n> closes it; a column's last pane leaves the column EMPTY + a session holds 64 panes (16 on the board): past that, every route that opens one + fails with `no space for a pane: 64 max` (ENOSPC), and so does one whose column + has no room (each pane keeps its tag and 2 rows: `no space for a pane in that column`); rmdir $m/pane/<n> closes it; a column's last pane leaves the column EMPTY (focus reads empty, the log says only del), and the session's LAST pane QUITS it $m/pane/<n>/errors write-only: text appended to the +Errors pane of the pane's directory $m/os/ the host filesystem ``` The first field of an index row is a stable pane serial, not a slot or row -number. A row is `serial kind dirty name`, so `row.split(maxsplit=3)` keeps a -name with spaces intact. Re-read the index after anything that might open, +number. A row is `serial kind dirty name column`, the last word the pane's +column serial, so `head, col = row.rsplit(maxsplit=1)` then +`serial, kind, dirty, name = head.split(maxsplit=3)` keeps a name with spaces +intact. Re-read the index after anything that might open, reuse or close a pane. ```sh @@ -135,12 +153,23 @@ created or removed, and no read creates anything. `look` and `exec` are the editor's two clicks, one per line of a write, at the active pane from the root and at that pane from `$m/pane/<n>/look` and `$m/pane/<n>/exec`. Reading any of them answers the serials the last command -made, or the pane it focused or acted on. A command that fails is reported in -the editor, not as a write error, so inspect the resulting pane, index, message -or screen; only a malformed line fails the write itself. A word no builtin +made, or the pane it focused or acted on: an open's own last write's, or, on +an open that never wrote, the session's last. With other clients about, +write and read one open (`exec 3<>$m/look; echo x >&3; cat <&3; exec 3<&-`). +A read is a stream: once read, a second read on the same fd gives EOF (on +an open that wrote, until its next write); open again, or seek to 0, to read +it again. Each command line runs once whole, however a mount cuts a big write; a last +line with no newline runs when the open closes, and an Edit block still open +then fails there (an `err`: ``unmatched `{'``, or an a/c/i text with no `.` +line), changing nothing. One rule: a builtin that fails, whether through a +ctl, look, exec, tagexec or a column's exec, fails the write (EINVAL for a +malformed line, else EIO or an errno that fits) and logs one `err` with the +reason, no `msg`. A look that finds nothing is no failure: it answers +nothing and logs one `err`. A command line run in a command pane is judged +by its `exit` record. A word no builtin knows (a typo included) is a command line: written at a terminal at its prompt it is typed into that shell; from anywhere else it runs as a command -pane, a terminal whose child is the root ctl's `Shell` (fish unless set) +pane, a terminal whose child is the root ctl's `Shell` ($SHELL, else /bin/sh, unless set) running `-c` the line in the pane's directory, which ends showing `exit N` (a typo: `exit 127`) and logs `run <serial> <line>` and `exit <serial> <N|?>` -- `exec` reads back its serial, so follow `log` for the exit. The directory's next command reuses a finished @@ -153,12 +182,23 @@ record written back from such a file's body goes to its REPL, as the click would. `Repl -` unbinds; a bare `Repl` says the binding. With several REPLs bound for a language an exec asks which, logged `ask <serial> repl a b` (Del's side from the keyboard is `ask <serial> del k j`): answer with -`echo 'answer a' > $m/pane/<serial>/ctl`, or `answer -` to send nothing. Multi-line code +`echo 'answer a' > $m/pane/<serial>/ctl`, or `answer -` to send nothing (the +log says `answer <serial> a|-`). Kill does not stop what a REPL runs (it was +typed, not started by pardes): `echo 'sig INT' > $m/pane/<repl>/pty/ctl` +interrupts it. A range of a `.py` pane goes to its REPL by writing the event +record `MX<q0> <q1>` to that pane's `event`. A chorded exec's record (flag 8) +is followed by two, its argument and where it came from; write all three back +as read (one write or three) and it runs once with its argument. `Tty`'s argument is a shell +(`Tty fish`), and `Tty` on a pane's ctl opens a new terminal pane. After a +Restore, panes have new serials (`restored <old> <new>` in the log), a +command pane shows `exit ?` and does not run again, and REPLs are unbound. Multi-line code written to `pty/data` should be a bracketed paste, `\e[200~<code>\e[201~`, then, in a separate write once the REPL has echoed the paste (Python 3.13+ -takes a `\r` read with the paste as part of it, even for one line), `\r`, -and a second `\r` when the code's last line is indented (a `def` or `for` -body): one Enter leaves such a block open. Sent line by line, a blank line ends a Python block, and Python +takes a `\r` read with the paste as part of it, even for one line), `\r`. +A paste of one line needs that one `\r`; a paste of more than one line +needs a second `\r` unless the pasted code ends in a newline (one Enter +leaves a multi-line input at `...`). A middle click, or an event +write-back, sends what it needs by itself. Sent line by line, a blank line ends a Python block, and Python 3.14's REPL auto-indents each line it is typed. Every refused 9P write adds an `err <serial|-> <file>: <why>` record to `$m/log`; through a mount the write itself only says `Invalid argument`. @@ -172,10 +212,11 @@ Rename everywhere, or any sam edit, is one write: `echo 'Edit ,x/foo/c/bar/' v c a i d s p = m t u` and `{ }` (commands in braces one to a line, so from exec or a tag, not a one-line ctl write). Its changes are one undo step, and one that fails changes nothing and fails the write with acme's words (`Edit: -no substitution`), logged as `err`. A block goes in one write to the pane's -`ctl`, the root `ctl` (the active pane) or `exec`: an `Edit` line takes the -lines after it until its `{` closes or its `a`/`c`/`i` text ends with `.`. -bash's builtin `printf` writes line by line, so use a heredoc or `env printf`: +no substitution`), logged as `err`. A block goes to the pane's `ctl`, the +root `ctl` (the active pane) or `exec` on one open, in one write or several +(bash's `printf` writes line by line): an `Edit` line takes the lines after it +until its `{` closes or its `a`/`c`/`i` text ends with `.`, and runs then. `sel` +reads the selected text, and a write to it replaces the selection: ```sh cat > $pane/ctl <<'END' @@ -189,6 +230,23 @@ env printf 'Edit ,x/foo/{\ni/</\na/>/\n}\n' > $pane/ctl `p` and `=` print to the directory's `+Errors`. Not there: `b B D e r w f X Y`, `< | >`, and `\1`-`\9` in `s`. +> **sam gotchas** (as sam does them, pardes too) +> - `$-1` is the last line when the text ends in a newline (`a\nb\n`: `b\n`), +> but the one before it when it does not (`a\nb`: `$` is on `b`, so `a\n`). +> - With a final newline, the line after the last is the empty place at the +> end, not an error: `N+1` at the last line is `#<len>,#<len>`. Without one +> (`a\nb`), the last line runs to the end and `N+1` is out of range. +> - `^` and `$` match at the text's end too: `/^/` from the end finds the +> empty place after a final newline. +> - `2,1` is no error: it is `#<start of 2>,#<end of 1>`, an empty range at +> line 2's start (only a range whose end is before its start is refused). +> - `y` yields the stretch before the first match too, empty if the text +> starts with one: `,y/a/` on `abc` gives `` and `bc`. +> - `c/&/` puts a literal `&`; only `s` expands `&` to the match. +> - `line:col` columns count bytes from 1: `12:0` is refused +> (`a column counts from 1`); a tool's character column is the same only +> on an ASCII line. + | Operation | Shell | Python client | |---|---|---| | Read text | `cat $pane/body` | `client.read(pane + '/body')` | @@ -199,7 +257,12 @@ env printf 'Edit ,x/foo/{\ni/</\na/>/\n}\n' > $pane/ctl | Delete that range | `: > $pane/data` | truncate `data` (open with OTRUNC) | A write leaves `addr` just past what it wrote, so a second `echo x > data` -inserts after the first: write `addr` again before each replacement. +inserts after the first: write `addr` again before each replacement. Each +`data` write is one undo step; to make a loop's writes one, `echo 1 > mark` +(an undo point now), `echo 0 > mark`, the writes, then `echo 1 > mark`. +A `+New` scratch counts as unsaved (and holds up Exit, Restore, Del) only +once it holds 100 bytes or more. A read +of `data` or `xdata` moves `addr` past what it read, as in acme. Truncating `data` is pardes's own (acme ignores OTRUNC and always inserts). | Read the selection | `cat $pane/dot` (offsets), `cat $pane/sel` (text) | the same two reads | | Select the addressed range | `cp $pane/addr $pane/dot` | `client.write(pane + '/dot', client.read(pane + '/addr'))` | @@ -224,7 +287,7 @@ and a search that backtracks past a step budget (about 300 ms) fails with `regular expression search gave up, ...`. A failed address says why (`no match for regexp`, `address out of range`) and leaves no address: -`data` refuses until the next good one, so a missed target is never +`addr` reads empty and `data` refuses until the next good one, so a missed target is never written at the old one. Moving `dot` scrolls the pane to it. `limit` bounds only the end of a forward search, as in acme, and reads empty until set; truncate it to lift it. In `12:5` the column counts bytes from 1 and clamps @@ -247,13 +310,14 @@ the address comes back to where it began, or set `limit`. a reserved zero, the dirty flag, the width in cells, the font and the tab width, then `current` or `notcurrent` — and takes `get` (reload from disk), `lock`/`unlock`, and any builtin that acts on a pane (`Del`, `Save f`, -`Collapse`). A `lock` another open holds fails at once with `file in use` +`Collapse`, `Undo`/`Redo`: 256 steps; with none left they say so, and the +write succeeds). A `lock` another open holds fails at once with `file in use` (EBUSY): retry it. Session builtins and settings go to the root `ctl`, which reads back every setting in the syntax it takes. A ctl write is checked whole first and refused as `unknown control message "X"` (EINVAL) and the like, a required argument missing included (`wrong #args ... "Mount"`); then a line whose builtin reports an error fails the write with that error and the line -(EIO), after the lines before it took effect. A `Save` on a scratch fails +(EIO, or an errno that fits: ENOENT for a missing pane, file or dump), after the lines before it took effect. A `Save` on a scratch fails rather than prompt. The lock binds only clients that take it, and is held by the open that wrote it, so a shell holds an fd across the edit: `exec 3>$pane/ctl; echo lock >&3; ...; exec 3>&-`. @@ -301,15 +365,21 @@ as read acts on its text when the range is empty (the only way for a record at `0 0`). Chord reports need explicit handling. A record is `<origin><action><q0> <q1> <flag> <n> <text>\n` and its text may hold newlines: read `n` bytes of text, never up to the next newline (acme counts -runes; pardes counts bytes, as all its offsets are). +runes; pardes counts bytes, as all its offsets are). Every address lands on a +rune boundary, never inside one and never widened to a grapheme cluster: `#n` +or `line:col` inside a rune snaps back to its start, a match covers the runes +it touches, and a combining mark or a CRLF's `\r` is addressable alone. A Restore puts a new editor under every client: the Restore write is answered, then every connection is hung up (their fids name the old -editor's panes); dial again, and the new log names the restored panes then -`restore <path>` -- the authority, since a slow client may see the cut +editor's panes); dial again, and the new log has a `new` for each restored pane, then +`restore <path>`, then `restored <old> <new>` for each pane and +`restoredcol <old> <new>` for each column -- `restore <path>` the authority, since a slow client may see the cut before the answer. A 9ns older than cloud9 2a7137c could fail the Restore write with ECONNRESET although the Restore went ahead; trust the log. `Dump` writes `pardes-<date>-<time>.zon`, the time in UTC, under `DumpDir` (`$XDG_DATA_HOME/pardes`, else `~/.local/share/pardes`) and logs -`dump <path>`. +`dump <path>`. A relative `Restore <path>` is looked for in `DumpDir` first, +then in the directory pardes started in; a bare `Restore` takes the last dump +this session wrote (none yet: refused, name one). Read the event implementation before building an interceptor. Close handles in `finally`, and disconnect after a socket timeout. The service shares sixteen connection slots (the next client's version gets `too many connections`) @@ -317,8 +387,9 @@ and 32 screen/terminal-history snapshot handles. A file's qid version is the pane's revision for `body`, `data` and `xdata`, so `stat` sees an edit land without reading the text; it stays zero elsewhere. -Stat sizes are real, and for `log`, `event` and `pty/data` report the length of -the record a read would answer — zero when nothing is waiting. +Stat sizes are real: for `event` and `pty/data` the length of the record a read +would answer (zero when nothing is waiting), for `log` the whole ring, the text +an open would freeze now. ## Terminal input and screen observations |
