mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
Address a tab by the bare id --json prints, and stop the skill sending workers in headless (#699)
* fix(cli): address a tab by the bare id --json hands back `parse_tab` required the `@` sigil, so the tab id from `tty7 tab new --json` — the one id a caller is certain of — was the one shape the CLI refused. `parse_pane` already made `%` optional for exactly this reason (#538); this aligns tabs with it, keeping the digits-only guard so a leading `+` cannot read as an ordinal now that the sigil is gone. * docs(skill): hand a pane worker its interactive mode The worked example passed the task with `-p`, which draws nothing: the pane stays blank until the turn ends, `capture --plain` reads back empty, and the user watching their tty7 window sees a worker that looks hung. Putting a piped worker in a pane discards the only reason it is in one. Also documents three things that cost real debugging time: a fresh pane can swallow the Enter while its shell is still running startup files, `tty7 procs` reports nothing running for a pane with a live agent in it, and the OSC 777 event stream in a raw `capture` is what actually answers "is it moving".
This commit is contained in:
@@ -60,19 +60,28 @@ pub fn parse_pane(s: &str) -> Result<u64> {
|
||||
}
|
||||
|
||||
pub fn parse_tab(s: &str) -> Result<TabAddress> {
|
||||
let body = s
|
||||
.strip_prefix('@')
|
||||
.ok_or_else(|| anyhow!("'{s}' is not a tab address — tabs look like @7"))?;
|
||||
// The `@` is optional, for the reason it is optional on a pane (#538): every
|
||||
// `--json` payload spells tabs bare, so the id `tty7 tab new --json` just
|
||||
// handed back has to address the tab it created. Demanding the sigil made
|
||||
// the one id you are certain of the one shape the CLI refused.
|
||||
let body = s.strip_prefix('@').unwrap_or(s);
|
||||
let not_an_address =
|
||||
|| anyhow!("'{s}' is not a tab address — @7 as numbered by `tty7 ls`, or a full tab id");
|
||||
if body.is_empty() {
|
||||
bail!("'{s}' is not a tab address — tabs look like @7");
|
||||
return Err(not_an_address());
|
||||
}
|
||||
if let Ok(n) = body.parse::<u64>() {
|
||||
return Ok(TabAddress::Ordinal(n));
|
||||
// Digits and nothing else. `u64::from_str` also takes a leading `+`, and
|
||||
// with the `@` gone that would read `+5` as tab 5 rather than as a typo.
|
||||
if body.bytes().all(|b| b.is_ascii_digit()) {
|
||||
return body
|
||||
.parse()
|
||||
.map(TabAddress::Ordinal)
|
||||
.map_err(|_| not_an_address());
|
||||
}
|
||||
if looks_like_uuid(body) {
|
||||
return Ok(TabAddress::Id(body.to_string()));
|
||||
}
|
||||
bail!("'{s}' is not a tab address — @7 as numbered by `tty7 ls`, or @<full tab id>");
|
||||
Err(not_an_address())
|
||||
}
|
||||
|
||||
fn looks_like_uuid(s: &str) -> bool {
|
||||
@@ -163,6 +172,34 @@ mod tests {
|
||||
assert_eq!(parse_pane("%42").unwrap(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bare_tab_id_addresses_the_same_tab_as_the_marked_one() {
|
||||
// Every `--json` payload spells tabs bare, and the id `tty7 tab new`
|
||||
// hands back is the one tab you are certain of — refusing it there made
|
||||
// naming a tab you just created impossible without counting `@N` again.
|
||||
let id = "0d4e1a54-0000-4000-8000-000000000003";
|
||||
assert_eq!(parse_tab(id).unwrap(), TabAddress::Id(id.into()));
|
||||
assert_eq!(
|
||||
parse_tab(&format!("@{id}")).unwrap(),
|
||||
TabAddress::Id(id.into())
|
||||
);
|
||||
assert_eq!(parse_tab("7").unwrap(), TabAddress::Ordinal(7));
|
||||
assert_eq!(parse_tab("@7").unwrap(), TabAddress::Ordinal(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tab_ordinal_is_digits_and_nothing_else() {
|
||||
// Same guard as the pane one, and it matters for the same reason now
|
||||
// that the sigil is optional on both.
|
||||
for not_a_tab in ["+5", "@+5", "-5", " 5", "5 ", "", "@", "5.0", "build"] {
|
||||
assert!(
|
||||
parse_tab(not_a_tab).is_err(),
|
||||
"'{not_a_tab}' must not read as a tab address"
|
||||
);
|
||||
}
|
||||
assert!(parse_tab("99999999999999999999999").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_address_is_digits_and_nothing_else() {
|
||||
// `u64::from_str` takes a leading `+`; an address must not, or a bare
|
||||
|
||||
+65
-2
@@ -62,10 +62,17 @@ Reach for tty7 when one of these is true:
|
||||
|---|---|---|
|
||||
| `%42` | a pane | yes — a pane keeps its id for its whole life |
|
||||
| `@7` | a tab, numbered across the **whole machine** in tree order | **no** — it shifts whenever a workspace or tab appears or disappears |
|
||||
| `@<full tab UUID>` | that same tab, by id | yes |
|
||||
| `api` / `76698a44` / a full UUID | a workspace, by name, by unique id prefix, or by id | yes |
|
||||
|
||||
Re-resolve `@N` right before you use it; never cache one across a step that
|
||||
creates or removes a tab. Pane ids and workspace ids are safe to remember.
|
||||
creates or removes a tab. Pane ids, tab ids and workspace ids are safe to
|
||||
remember — so when you create a tab and mean to address it again later, keep the
|
||||
id `tty7 tab new --json` hands back rather than counting `@N` a second time.
|
||||
|
||||
The sigils are optional wherever an address is expected: `%42` and `42` are the
|
||||
same pane, `@7` and `7` the same tab. Ids copied out of `--json` paste straight
|
||||
back in.
|
||||
|
||||
Omitting the address inside a tty7 shell means "this pane" / "this workspace".
|
||||
An explicit address always wins over the environment.
|
||||
@@ -127,6 +134,22 @@ For keystrokes rather than characters — Ctrl-C, Escape, the arrow keys — use
|
||||
`--key` (see [Answering a prompt](#answering-a-prompt)). Typing `^C` as text
|
||||
does nothing; it arrives as two characters.
|
||||
|
||||
**A brand-new pane can swallow the Enter.** A shell still working through its
|
||||
startup files — a prompt framework, `fastfetch`, anything that paints on login —
|
||||
takes the text you send but loses the carriage return that follows it, and the
|
||||
command just sits on the prompt line unexecuted. Nothing reports this: the
|
||||
`send` succeeded, and the pane looks like a worker that has not got going yet.
|
||||
So after sending the first command into a pane you just created, read the screen
|
||||
back and check it actually left the prompt:
|
||||
|
||||
```bash
|
||||
tty7 capture "$PANE" --plain | tail -3 # command still sitting on the prompt?
|
||||
tty7 send "$PANE" --enter # then give it the Enter it lost
|
||||
```
|
||||
|
||||
Cheaper than diagnosing it later, and only the first `send` into a fresh pane
|
||||
needs the check.
|
||||
|
||||
## Reading a pane
|
||||
|
||||
### If you want the screen, use `--plain`
|
||||
@@ -197,6 +220,11 @@ If you want the process tree itself — "what is running in there", "which port
|
||||
this pane serving" — that is `tty7 procs %83`: indented by depth, `*` on the
|
||||
foreground process, then the ports those processes are listening on.
|
||||
|
||||
It is not the way to check on a coding agent, though. `procs` reports
|
||||
`nothing running in this pane` for a pane with a busy agent in it, so reading it
|
||||
as "the worker died" is wrong. Ask `tty7 agents` about those, or see
|
||||
[When a worker never moves](#when-a-worker-never-moves).
|
||||
|
||||
## Handing work to another agent
|
||||
|
||||
Everything above also works when the thing in the pane is a coding agent, and
|
||||
@@ -206,7 +234,7 @@ process tree:
|
||||
|
||||
```bash
|
||||
PANE=$(tty7 split --v)
|
||||
tty7 send "$PANE" 'claude -p "add tests for the parser"' --enter
|
||||
tty7 send "$PANE" 'claude --dangerously-skip-permissions "add tests for the parser"' --enter
|
||||
tty7 wait "$PANE" --until waiting,done --changed --timeout 900
|
||||
tty7 capture "$PANE" --plain | tail -40
|
||||
tty7 pane close "$PANE"
|
||||
@@ -216,6 +244,27 @@ Five steps: give it a pane, hand it the task, sleep until it needs you or
|
||||
finishes, read what happened, clean up. The third is the one worth
|
||||
understanding.
|
||||
|
||||
### Give the worker its interactive mode
|
||||
|
||||
Hand the task as an argument, **not** with `-p`. Both run one turn and stop, so
|
||||
the difference is not what the worker does — it is what anybody can see while it
|
||||
does it.
|
||||
|
||||
Interactive is the mode that draws a TUI, so the pane fills with the worker's
|
||||
reasoning and tool calls as they happen. That is visible to the user in their
|
||||
tty7 window, and it is what `capture --plain` reads back. `-p` is the piped
|
||||
mode: it draws nothing, streams its answer to stdout when the turn ends, and
|
||||
until then the pane's screen stays **empty** — `capture --plain` on it returns
|
||||
nothing at all, which reads exactly like a worker that hung. Putting a `-p`
|
||||
worker in a pane throws away the only reason it is in a pane.
|
||||
|
||||
Interactive also leaves the session alive at the prompt, so you can `send` a
|
||||
follow-up into the same context. A `-p` worker is gone after its one turn.
|
||||
|
||||
Reach for `-p` only when you want the answer as a string and nobody needs to
|
||||
watch — and then prefer `tty7 run` or the Bash tool, which is what that shape
|
||||
is for.
|
||||
|
||||
### What the states mean
|
||||
|
||||
| State | The pane is |
|
||||
@@ -293,6 +342,20 @@ it can see the gap, and `tty7 doctor` reports where every agent's hooks stand.
|
||||
Hooks are installed from the GUI's **Settings → Agents**; tell the user rather
|
||||
than trying to install them yourself.
|
||||
|
||||
Before concluding anything, check whether it is moving. Those same hooks emit an
|
||||
OSC 777 line on every tool call, and `capture` **without** `--plain` shows them —
|
||||
one of the few times the raw bytes beat the rendered screen:
|
||||
|
||||
```bash
|
||||
tty7 capture "$PANE" | grep -c 'tool-complete' # rising = alive and working
|
||||
```
|
||||
|
||||
That is also the answer when a worker's screen looks empty: a `-p` worker paints
|
||||
nothing until its turn ends, so `capture --plain` is blank the whole way through
|
||||
while the event stream underneath is busy. Two things that do *not* answer this
|
||||
question: `tty7 procs`, which reports nothing running for a pane with a live
|
||||
agent in it, and the absence of output on a `--plain` capture.
|
||||
|
||||
## Looking around
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user