Some research
This commit is contained in:
@@ -79,7 +79,20 @@ Any other input is sent to the agent.
|
||||
/install status
|
||||
/install upgrade scheduler-service
|
||||
|
||||
Six install targets exist side by side: `binary` (`~/.local/bin/tai.py`), `bash-hook` (a guarded block in `~/.bashrc`, backed up once to `~/.bashrc.bak-tai`, adding both an `alias tai="$HOME/.local/bin/tai.py"` so the bare `tai` command resolves to the REPL instead of falling through, and a `command_not_found_handle` so any other unknown shell command is answered by the agent), `venv` (`~/.tai/venv`, created whenever the venv module exists), `scheduler-service` and `telegram-service` (systemd user units that prefer the venv python), and `container` (the sandbox, whose image carries its own `/box/venv`). `/install status` (or the `install` tool) reports exactly which of these exist, with versions and service states. `install` adds missing pieces, `upgrade` refreshes in place, `reinstall` rebuilds artifacts from scratch, `uninstall` removes them; service changes restart or start units immediately. Data handling is explicit: no action ever touches the vault (`memory.db`, secrets, schedules, records, backups).
|
||||
Eleven install targets exist side by side: `binary` (`~/.local/bin/tai.py`); a command-not-found hook per shell -- `bash-hook` (`~/.bashrc`), `zsh-hook` (`~/.zshrc`), `fish-hook` (`~/.config/fish/config.fish`, respecting `$XDG_CONFIG_HOME`) -- each a guarded block (backed up once to `<rc file>.bak-tai` before the first write) adding a `tai` alias/function so the bare `tai` command resolves to the REPL instead of falling through, plus the shell's own not-found mechanism (`command_not_found_handle` for bash, `command_not_found_handler` for zsh, a `fish_command_not_found` function for fish) so any other unknown shell command is answered by the agent instead; a plexer auto-wrap hook per shell -- `plexer-hook` (bash), `zsh-plexer-hook`, `fish-plexer-hook` -- that transparently re-execs a fresh interactive shell into a managed, scriptable plexer session (`tai.py --plexer-wrap`; skipped automatically inside tmux, inside another plexer-wrapped shell, or when `~/.tai-no-plexer` exists) using whatever that shell can actually express (`[`/`case "$-"` for bash and zsh -- byte-identical content, since both are POSIX enough for it; `status is-interactive`/`test`/`isatty` for fish, which has neither); `venv` (`~/.tai/venv`, created whenever the venv module exists); `scheduler-service` and `telegram-service` (systemd user units that prefer the venv python); and `container` (the sandbox, whose image carries its own `/box/venv`). Installing a zsh/fish-specific target on a machine without that shell skips loudly (`"zsh-hook: zsh not found, skipped"`) rather than writing a dangling hook into a config file nothing will ever source; `uninstall`/`reinstall` still act on the file regardless, so a hook installed before a shell was later removed still gets cleaned up. `/install status` (or the `install` tool) reports exactly which of these exist, with versions and service states. `install` adds missing pieces, `upgrade` refreshes in place, `reinstall` rebuilds artifacts from scratch, `uninstall` removes them; service changes restart or start units immediately. Data handling is explicit: no action ever touches the vault (`memory.db`, secrets, schedules, records, backups). `./tai.py --install` (the one-shot CLI bootstrap) always installs the bash hook and, in addition, detects `$SHELL` and installs that shell's matching hook too when it's zsh or fish.
|
||||
|
||||
## Plexer
|
||||
|
||||
A plexer session is a terminal tai can see into and type into, not just read a one-shot command's output from. There are two ways one gets active, and tai never cares which:
|
||||
|
||||
- **tmux** — if the shell is already inside a tmux pane (`$TMUX_PANE` set), that pane *is* the session. tai drives it with plain `tmux send-keys`/`capture-pane`/`list-panes`; nothing is spawned.
|
||||
- **native wrapper** — if nothing already owns the terminal, the plexer-hook/`zsh-plexer-hook`/`fish-plexer-hook` installed above re-execs the fresh interactive shell into `tai.py --plexer-wrap`, which forks the user's real shell onto a pty (`NativePlexerBackend`) and relays bytes between the real terminal and that pty in both directions, forwarding resize (`SIGWINCH`), teeing everything to a log file under `~/.tai/plexer/`, and serving control commands over a Unix socket (`TAI_PLEXER_SOCKET`) so a separate tai process — the agent the user is actually talking to — can drive the same session from outside.
|
||||
|
||||
Either way, four tools appear only while a session is actually active (`plexer_send`, `plexer_read`, `plexer_type`, `plexer_status` — hidden otherwise, not just disabled, via `TOOL_ENV_GATES`): `plexer_send` types literal keystrokes (optionally pressing Enter), `plexer_read` reads back the last N lines of real rendered output, `plexer_type` does the same as `send` but one character at a time with a delay for a visible typing effect, and `plexer_status` reports which backend is active and its details (pid/socket/log, or tmux pane id). Which backend answers a given call is autodetected from the environment (`TAI_PLEXER_SOCKET` vs `$TMUX_PANE`), never configured by hand.
|
||||
|
||||
Inside a natively-wrapped session, Ctrl+A (deliberately not tmux's Ctrl+B, so the two never collide if one is somehow nested in the other) is reserved as tai's own prefix key; `Ctrl+A q` kills the wrapped shell outright (`SIGKILL` to its process group — this is a clean kill, not a tmux-style detach-and-reattach, since there is no session persistence to reattach to). Everything else typed passes through untouched.
|
||||
|
||||
The auto-wrap checks three conditions, in the shell hook itself, before ever invoking `tai.py --plexer-wrap`: never wrap if a plexer is already active (`$TAI_PLEXER_ACTIVE`), never wrap if tmux already is (`$TMUX`), and never wrap if `~/.tai-no-plexer` exists — the hand brake. That file is the one thing to touch if the auto-wrap is ever doing the wrong thing on a given machine; it needs no flag or argument, just existing is enough, and it's rechecked on the Python side too (`plexer_should_wrap()`) as defense in depth, not trusted to the shell guard alone.
|
||||
|
||||
## Backends
|
||||
|
||||
|
||||
Reference in New Issue
Block a user