summaryrefslogtreecommitdiff
path: root/.agents
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-06 18:11:36 -0300
committerGabriel Schneider <[email protected]>2026-09-07 13:59:12 -0300
commit60367d8fe23f6af98ec28e3cf6c2094dfe332df0 (patch)
tree310fc734173cf771881f4691c71909135fadde97 /.agents
parentfa82cac885cb4738fe36d1e49b4749b5a3e31a4a (diff)
downloadpardes-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.md168
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).