Files
command-center/agent/managed-skills/omp-extension-live-tui-verify/SKILL.md
T

4.7 KiB
Raw Blame History

name, description
name description
omp-extension-live-tui-verify Verify an omp extension's slash commands and event hooks against a real TUI session: pty submit semantics, observable side-effect instrument, transient-toast trap, agent_end willContinue law, PipeWire output triage.

Verify that a .ts file in agent/extensions/ actually works inside the interactive TUI — the black-box bats seam cannot see harness-side wiring (event names, arg passing, mode gating), and it will pass beside a dead hook.

Distinct from omp-tui-live-visual-verify (config/theme rendering proven by screenshot): here the instrument is observable side effects, not pixels, because extension notifications are transient toasts that vanish before the pty buffer is read.

1. Launch the exact build under test

omp --no-extensions --no-session -e /abs/path/agent/extensions/<name>.ts

as a pty service (pty: true, ready: {"log": "╭|❯|Tip:|─", "timeout": 60}). --no-extensions + explicit -e proves the file loads by path and nothing else interferes. Neutral cwd (/tmp) so repo state cannot color the run.

2. Submit input correctly (the costly gotcha)

write proc://<id> with a trailing \n does not submit — the text just sits in the composer.

  • Works: two writes — first "/cmd args" (no terminator), then a separate write whose whole content is \r.
  • Broken: "/cmd args\r" in one write. It still submits, but the trailing \r arrives inside the command args. Symptom: unknown voice 'narrator1\r', an unexpected extra utterance, or a chat message whose transcript line ends in a literal \r. That is a harness artifact — never "fix" it by stripping control characters in the extension (.trim() already covers what a keyboard can produce).

3. Instrument with a stub, never with ears or eyes

Point the extension's player/handler at a script that appends evidence:

#!/usr/bin/env bash
echo "$1" >> /tmp/stub.log; cp "$1" "/tmp/out-$$.wav"; exit 0

Run the TUI with that stub in the env the extension reads (DANKVOICE_PLAYER=… style). Then every assertion is a file: wc -l of the log (how many times it fired), ffprobe -show_entries format=duration (what it produced), ls -d <tmp-glob> (that cleanup ran).

Sequence per behavior: command → CR → short sleep → count. Count before and after each command; the delta is the only honest proof a subcommand stayed silent.

4. Discriminate "fired twice" from "fired once per turn"

Stray CR writes can create extra empty turns, each with its own agent_end — two utterances for one intended prompt is usually two turns, not a duplicate speak. Confirm by counting assistant turns in the transcript (read proc://<id>), and by matching each clip's duration to its own answer's length.

5. Event-hook contract traps

  • agent_end payload (dist/types/extensibility/shared-events.d.ts): { type, messages: AgentMessage[], willContinue? }. willContinue: true means the harness already scheduled a continuation (auto-retry / empty-stop retry) — a subscriber that narrates or notifies there speaks an answer the model is about to redo. Gate on it.
  • Read the real event/payload shape from ~/.bun/install/global/node_modules/@oh-my-pi/pi-coding-agent/dist/types/…; grep dist/cli.js for the literal event names to confirm they exist.
  • A driver test that calls the handler by hand proves nothing about emission — that is exactly the gap this live check closes.

6. Audio output goes silent while every command reports success

pw-play exits 0 and prints stream drained even when nobody hears anything. Triage order:

wpctl status                       # Sinks, the `*` default, and live Streams
wpctl get-volume @DEFAULT_AUDIO_SINK@
  • A Default Configured Devices entry naming a bluez_output.* node that is not in the Sinks list = remembered-but-offline Bluetooth: the classic silent-route cause.
  • Confirm the stream really links: a short clip can vanish before inspection, so use a multi-second clip and read wpctl status mid-play, or target explicitly: pw-play --target <node-id> <file> (node ids from wpctl status; wpctl inspect <id> gives node.name).
  • Prove the synth path independently of the player: curl -o x.wav … then file x.wav (expect RIFF … WAVE … PCM).

7. Land it

Live result belongs in the commit message, the reference page, and the work log — and the guard the live check uncovered gets its own behavior test (assert observable output only: fired count, payload content, silence).

Cleanup

/exit or proc://<id>/kill the service; move stub scripts, logs, and clips to ~/.AGENTTRASH/<date>-<topic>/ — never rm. Kill background jobs that keep playing to the operator's speakers.