diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-06 18:11:36 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-07 13:59:12 -0300 |
| commit | 60367d8fe23f6af98ec28e3cf6c2094dfe332df0 (patch) | |
| tree | 310fc734173cf771881f4691c71909135fadde97 /.agents | |
| parent | fa82cac885cb4738fe36d1e49b4749b5a3e31a4a (diff) | |
| download | pardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.tar.gz pardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.zip | |
Refactor panes and filesystem; replace FUSE with 9P
Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples.
Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
Diffstat (limited to '.agents')
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md new file mode 100644 index 00000000..09dee666 --- /dev/null +++ b/.agents/skills/pardes-9p/SKILL.md @@ -0,0 +1,168 @@ +--- +name: pardes-9p +description: Inspect and drive a running Pardes editor over 9P, or exercise its panes, builtins, terminal input and rendered output in an isolated session. Use for Pardes interaction, plugin development and end-to-end debugging through its control filesystem. +--- + +# Pardes over 9P + +Use the existing [Python client](../../../test/ninep.py) for ad hoc interaction +and functional tests. Do not build another wire client. Project tooling stays +in Zig. Run the examples from the repository root; paths below are relative to +that root unless linked. + +## Connect and identify panes + +Every native session opens a Unix socket. Inside a pane, `PARDES_9P` names the +socket and `PARDES_PANE` is that pane's serial. Outside Pardes, find +`pardes-9p-*.sock` under `$XDG_RUNTIME_DIR`, or `~/.local/state/pardes` when +that variable is unset. Select the intended session explicitly; do not assume +the newest socket is the right one. Names come from `--9p=name`, the detached +session name, or the process ID. + +```sh +PYTHONPATH=test python3 -B - "$PARDES_9P" <<'PY' +import sys +from ninep import Client + +with Client(sys.argv[1]) as client: + print(client.read('/self/index').decode(), end='') + print(client.read('/self/listeners').decode(), end='') +PY +``` + +`Client` takes a raw Unix socket path, or `(numeric_ip, port)` for TCP; it +does not parse Pardes dial strings or implement QUIC. It negotiates 9P2000, +uses a five-second socket timeout, and closes on leaving `with`. + +The first field of each index row is a stable pane serial, not a slot or row +number. The tag starts after five numeric fields; use `row.split(maxsplit=5)` +to preserve spaces. Inspect `/self/pane/<serial>/tag`, `body`, or directory +entries before choosing a target. Re-read the index after actions that might +open, reuse or close panes. + +Wire paths start with `/self` or `/os`. `/n/self`, `/n/os`, `/n/peer` and +`/virtual` belong to editor Look paths, not the server root. For example, +Look `/virtual/src/pardes.zig` corresponds to reading `/self/src/pardes.zig`. + +## Edit, Look and execute + +For a file or scratch pane, with a connected `client` and a confirmed +`serial`, let `pane = f'/self/pane/{serial}'`. Terminal body writes instead +send child input; truncation does not erase terminal history. + +| Operation | Client call | +|---|---| +| Read text | `client.read(pane + '/body')` | +| Append text | `client.write(pane + '/body', b'text\n')` | +| Replace all text | `client.write(pane + '/body', b'text\n', truncate=True)` | +| Select a byte range | `client.write(pane + '/addr', b'#0,#2')` | +| Replace that range | `client.write(pane + '/data', b'pub fn')` | +| Show the addressed selection | `client.write(pane + '/ctl', b'dot=addr\n')` | +| Look from this pane | `client.write(pane + '/ctl', b'look /virtual/src/pardes.zig:10\n')` | +| Save | `client.write(pane + '/ctl', b'put\n')` | +| Reload | `client.write(pane + '/ctl', b'get\n')` | +| Close, refusing dirty text | `client.write(pane + '/ctl', b'del\n')` | + +`delete` force-closes, discarding unsaved text. `get` replaces edits with file +contents. Use those only when that loss is intended. Reading `/self/new/ctl` +creates a scratch pane and returns its serial as the first field; it is not +an observational read. + +`ctl` is not a builtin interpreter. Execute builtins through a body Exec event +on an owned scratch pane: + +```python +from fs import new_pane, execute + +control = new_pane(client, b'') +execute(client, control, 'Msg 9p-ready') +``` + +`execute` overwrites that pane's body with the command, then writes +`MX0 <UTF-8-byte-length>\n` to its `event` file. Keep the control pane separate +from user text. The same helper can run `Mini path`, `Mount peer dial`, and +`Unmount peer`; commands resolve relative to the control pane's directory. +A successful event write acknowledges dispatch, not completion: inspect the +resulting pane, message or screen for success. + +Opening `addr` resets its range. To inspect the current selection, open +`addr` first, write `addr=dot\n` to `ctl`, then use `read_fid` on that already +open handle. Do not replace that last step with `client.read`, which reopens +and resets it. Address state is shared by the pane, not private to a client. + +## Terminal input and screen observations + +Only terminal panes have `pty/`. Write keystroke bytes to `pty/data`, not +`body`: `client.write(pane + '/pty/data', b'printf hello\r')` submits a shell +command. For an interactive application, send its actual input bytes; +`b'\x03'` is Ctrl-C, and Ctrl-U is `b'\x15'` where that application supports it. +These inputs go to the child terminal, not Pardes editor key bindings. + +`client.screen()` returns `cols`, `rows`, `cursor`, `styles`, and row-major +`cells` of `[grapheme, style_index]`. Reconstruct rows using `cols`; resolve +each cell's style through `styles` when checking highlighting. Compare colors +and attributes, not just style-table indices or flattened text. + +Each screen open freezes one frame. A terminal `body` freezes history on its +first read. `client.read` and `client.screen` reopen each time; use repeated +calls for fresh observations. Do not poll a stale handle. Poll a specific +condition with a deadline and a short delay, rather than a fixed long sleep. +For large histories, measure whole-body reads separately from screen polling; +9P write-to-observation timing includes RPC, rendering and polling overhead. + +For live terminal output or plugin events, use `open` / `read_fid` / `close`, +not the read-until-EOF helper. `pty/data` captures output while held open; +it is not a history replay. Both files are shared, consuming queues, not +per-client broadcasts; slow readers can lose older data. Holding `event` open +intercepts Look/Exec clicks, so it is not a passive logger. For Look/Exec +records whose offsets identify the intended file/tag text, forward the short +record `<origin><action><q0> <q1>\n`, not the entire report with flags and text. +Expansion/chord reports need explicit handling; terminal-body events can have +empty ranges with text carried only in the report, so short writeback cannot +reproduce them. Read the event implementation before building an interceptor. +Close handles in `finally`; disconnect after a socket timeout. The service +shares four connection slots and 32 screen/terminal-history snapshot handles. + +## Exercise an isolated session + +Reuse [test/fs.py](../../../test/fs.py), which starts a private session with +temporary configuration and cleans up its editor process. Pass an existing +native binary, not a benchmark executable: + +```sh +PYTHONPATH=test python3 -B - /absolute/path/to/pardes <<'PY' +from pathlib import Path +import sys +import tempfile +from fs import session, new_pane, execute + +binary = str(Path(sys.argv[1]).resolve()) +with tempfile.TemporaryDirectory(prefix='pardes-9p-skill-') as directory: + with session(binary, Path(directory), 'skill') as (client, address): + serial = new_pane(client, b'fn main() void {}\n') + pane = f'/self/pane/{serial}' + client.write(pane + '/ctl', b'name probe.zig\n') + client.write(pane + '/addr', b'#0,#2') + client.write(pane + '/data', b'pub fn') + assert client.read(pane + '/body') == b'pub fn main() void {}\n' + control = new_pane(client, b'') + execute(client, control, 'Msg 9p-ready') + frame = client.screen() + assert '9p-ready' in ''.join(cell[0] for cell in frame['cells']) + print('9P edit, builtin and screen checks passed') +PY +``` + +For terminal tests, use `session(..., tty=True)` and read +[test/agent_session.py](../../../test/agent_session.py) for bounded interactive +driving. Its readiness text and history threshold are application-specific; +session cleanup alone does not guarantee arbitrary grandchildren have exited. +Use [test/snapshot.zig](../../../test/snapshot.zig) when editor key/mouse input +or an independent terminal-rendering comparison matters; 9P screen inspection +alone does not test physical input routing or the host renderer. + +Read [docs/fs.md](../../../docs/fs.md) for runtime mounts, TCP/QUIC listeners, +Plan9port and Linux v9fs compatibility. Unix is always available; network +listeners are opt-in and grant full session/OS-file access. Use isolated +loopback listeners for tests. For less common control verbs or event details, +read their implementation and tests in [src/fs.zig](../../../src/fs.zig). |
