summaryrefslogtreecommitdiff
path: root/.agents
diff options
context:
space:
mode:
Diffstat (limited to '.agents')
-rw-r--r--.agents/skills/pardes-9p/SKILL.md139
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