Skip to content

feat(harness): add pi harness adapter and interactive dialog lifting - #397

Closed
raffaelenatale wants to merge 2 commits into
AltanS:mainfrom
raffaelenatale:feat/pi-adapter
Closed

raffaelenatale wants to merge 2 commits into
AltanS:mainfrom
raffaelenatale:feat/pi-adapter

Conversation

@raffaelenatale

@raffaelenatale raffaelenatale commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Overview

This PR adds a harness adapter for Pi (pi-coding-agent, agent: "pi") covering both Tier 1 chrome parsing and Tier 2 interactive dialog lifting.

It lifts single-select options, questionnaire multi-step tabs and review screens, multi-select dialogs, and the inline free-answer editor into native tappable buttons on mobile, while keeping full fail-closed bounds and supporting unread modal escape via positive footer evidence.

It also introduces two harness-neutral infrastructure additions:

  1. walkSpace multi-select toggle choreography (with opt-in advanceNeedsChecked flag).
  2. dialogAcceptsTyping adapter probe allowing mobile composer input while an interactive dialog is active on screen.

1. Core Harness Infrastructure

  • walkSpace Choreography (web/src/lib/harness/multi-select-model.ts, web/src/lib/multi-select-action.ts):
    Navigates arrow keys directly onto the pointed row based on the fresh terminal read from guardDialog, verifies pointer settlement on a fresh re-read, and sends Space without Enter. Multi-select models are decoupled from WALK_PAIRS (which remain prompt-select only).
  • advanceNeedsChecked opt-in:
    Enforces that the advance/confirm button is disabled when zero options are checked or when the pointer rests on non-option rows.
  • dialogAcceptsTyping hook (web/src/lib/harness/types.ts, web/src/components/composer.tsx, web/src/components/agent-chat.tsx):
    Allows adapters whose dialogs support custom keyboard answers (such as Pi's question tool) to keep the mobile composer enabled while a lifted card is displayed, routing typed text through type-then-verify without relaxing safety check PWA send renders in the pane's input box but never reaches the input buffer (silently lost) #34 for other harnesses.
  • customInput Action & Composer Pre-fill:
    Dialogs offering free-form input ("Type something.") render a dedicated action button (Type a custom answer below…) that focuses the mobile composer with an answer-specific placeholder (Type an answer…). When a custom answer is already recorded, the button reflects it (Custom answer: … ✓) and populates the composer on tap for in-place editing.
  • Review submitAction Intent:
    WizardModel review phase supports an explicit submitAction intent ("submit" | "goToFirstUnanswered"). On incomplete Review screens where Enter only jumps to the next unanswered question (as in Pi), the button accurately communicates Go to first unanswered question instead of misleadingly offering to submit incomplete answers, while leaving Claude and OpenCode review buttons untouched as Submit answers.

2. Pi Harness Adapter (agent: "pi")

  • Tier 1 — Chrome & Statusline:
    • composer.ts: pure StyledLine[] scanner recognizing Pi's horizontal border frame (─{8,}), statusline tail, slash palette, and nested rules inside multiline drafts.
    • modal.ts: positive evidence probe (modalOnScreen) for Pi core modal selectors (such as /model or /theme), arming the unread-dialog card with cancelKey: "Escape".
    • answer-editor.ts: inline free-answer editor reader and prompt binding (newlineSubmits: true).
  • Tier 2 — Interactive Dialog Lifting:
    • question.ts: standalone single-choice dialogs lifted to prompt-select with numeric key bindings (["1", "Enter"] ... ["9", "Enter"]), and customInput / customAnswer detection.
    • tabs.ts: questionnaire tab bar navigation (← □ Q1 ... ■ Q2 ... ✓ Submit →) and Review screen lifting into wizard, setting submitAction: "goToFirstUnanswered" when incomplete.
    • multi.ts: multi-select checkbox lists ([ ] / [x]) with walkSpace toggle, supporting both Space to tick and Space to toggle footer conventions (@juicesharp/rpiv-ask-user-question parity).
    • frame.ts: resilient to transient terminal resize redraw artifacts (duplicate border rules and interim spinner rows).
    • index.ts: newlineSubmits answers true on all active dialog pages so multiline drafts typed on the phone are refused in preflight before sending raw newlines that would select an option underfoot.

Evidence & Verification

  • 25 Byte-faithful ANSI Fixtures:
    Captured in web/src/fixtures/panes/pi--v110-*.txt and documented in web/src/fixtures/panes/README.md, covering idle composers, statuslines, single questions, questionnaire tabs, multi-select toggles, and review screens. Verified via corpus.test.ts.
  • Canary Pass & Ledger Stamping:
    bun run canary --agent pi passes all 6 scenarios (idle, drafts, sends, journal, narrow, start-exit). Stamped in web/src/lib/harness/verified-versions.json (how: "canary").
    Updated scripts/harness-canary/readers.ts and transport.ts to freshen pane liveness on successful reads (M46 parity).
  • Live Mobile Verification:
    Verified against a live Pi instance driven by mobile Chromium on Xvfb (402×874 viewport) and real mobile hardware:
    • Single-choice taps (["1", "Enter"] plan) submitted and settled immediately.
    • Questionnaire tab navigation, multi-question answers, and Review submission completed.
    • Multi-select toggle via Space (walkSpace) verified with disabled confirm button at 0 ticks.
    • dialogAcceptsTyping validated with typed custom answer submitted while dialog was visible.
    • Custom answer editing in-place verified on revisited question tabs.
    • Core /model selector dismissed cleanly with 2-tap Escape card.
    • Terminal resize resilience verified with dynamic redraws.
  • Test Suite & Checks:
    • bun run typecheck & cd web && bun run typecheck → 0 errors.
    • oxlint --max-warnings 0 (including vendored anti-slop plugin rules) → 0 warnings, 0 errors.
    • cd web && bunx vitest run → 386 files, 22,828 tests passed.
    • bun test ./bridge ./cli ./scripts → all passed.
    • scripts/check-version.sh → clean (v1.19.1 consistent).

Base branch

  • Opened against main

Checks

  • bun run typecheck (root and web/), bun run lint, bun test ./bridge ./cli ./scripts, cd web && bun run test all pass
  • CHANGELOG entry included below for maintainer (fork PR exempt from modifying version files)

CHANGELOG entry for the release

Per CONTRIBUTING (fork PRs leave version files and CHANGELOG untouched), entry for maintainer to place under ## [Unreleased] → ### Added:

  • Harness adapter for Pi (pi-coding-agent). Lifts Pi's interactive question tool dialogs (single-select options, questionnaire tabs, Review screen, and multi-select checkboxes) into native tappable mobile buttons. Adds walkSpace multi-select choreography and the dialogAcceptsTyping adapter probe. (fixtures: web/src/fixtures/panes/pi--v110-*.txt, notes: web/src/lib/harness/pi/PI_NOTES.md)

@github-actions github-actions Bot added area: web Phone PWA — UI, mirror, composer, service worker area: harness Per-agent adapters and send guards (claude/codex/pi/omp) area: install bin/collie verbs, Herdr plugin lifecycle, update, tailscale serve documentation Improvements or additions to documentation labels Oct 10, 2026
@raffaelenatale
raffaelenatale force-pushed the feat/pi-adapter branch 6 times, most recently from 988a6d9 to 4853460 Compare October 10, 2026 09:20
@raffaelenatale
raffaelenatale marked this pull request as draft October 10, 2026 09:43
@raffaelenatale
raffaelenatale force-pushed the feat/pi-adapter branch 2 times, most recently from 6682a25 to 4807498 Compare October 10, 2026 10:44
…hook

Two harness-neutral capabilities a dialog grammar can opt into:

- MultiSelectModel gains the `walkSpace` toggle recipe: Up/Down walk the
  pointer onto the target row, a fresh read verifies it landed, then Space
  toggles it, bound to that read (ADR 0080). The optional
  `advanceNeedsChecked` flag disables the card's advance button and refuses
  the advance unless at least one option is checked and the pointer rests on
  an option row; the keys path re-checks it on a fresh read before writing.
- HarnessAdapter gains the optional `dialogAcceptsTyping` probe. When it
  answers true for the lifted dialog on screen, the composer sends through
  the normal type-then-verify path instead of the AltanS#34 "a dialog is waiting"
  refusal; the composerReady pre-flight still runs. Absent or false, the
  refusal stands as before.
- WizardModel gains optional `submitAction` and `submitLabel` on review phase,
  and single-choice dialog cards support `customInput` action focusing the
  mobile composer with an answer-specific placeholder.
Tier 1 chrome stripping plus Tier 2 dialog lifts for pi 1.1.0 with the pi
question extension:

- `composer.ts`: frame-anchored scanner at the buffer tail. Strips top and
  bottom horizontal rules (`─{8,}`), user draft, slash palette, and bounded
  footer (1..4 rows, default 2-row cwd+stats or custom extension powerbar).
  Extracts draft and statuslines; bounds `composerPrompt` within the 6-row
  bridge tail window.
- `frame.ts`: tail-anchored detection of dialog frames bounded by matching
  accent rules, identifying question body, key-hint footer (`Esc to cancel`),
  and questionnaire tab bar (`← ▢ Q1 ▣ Q2 ✓ Review →`). Resilient to
  terminal resize redraw artifacts.
- `question.ts`: lifts standalone single-select questions into PromptSelectBlock
  (`prompt-select`, family "select") with digit plans `["N", "Enter"]`, and
  `customInput` / `customAnswer` detection.
- `multi.ts`: lifts standalone and questionnaire multi-select pages into
  MultiSelectBlock (`multi-select`, phase "checkbox") with `walkSpace`
  toggles, `advanceKeys: ["Enter"]`, and `advanceNeedsChecked: true`.
- `tabs.ts`: lifts questionnaire single pages and review into WizardBlock
  (`wizard`), with digit selection on question pages and Enter submit on
  Review, setting `submitAction: "goToFirstUnanswered"` when incomplete.
- `answer-editor.ts`: reads inline free-answer editor (`newlineSubmits: true`).
- `modal.ts`: positive evidence for unread dialog card (`cancelKey: "Escape"`).
- `index.ts`: `newlineSubmits` answers true on active dialog pages to reject
  multiline sends in preflight; `dialogAcceptsTyping` answers false on multi-select.
- Verified versions ledger entry updated for pi 1.1.0 (`how: "canary"`).
- Canary runner: fixes readers liveness freshening during reader invocations.
- Fixtures and test suite covering Tier 1 chrome and lifted dialog variants.
@raffaelenatale
raffaelenatale marked this pull request as ready for review October 10, 2026 11:29
AltanS pushed a commit that referenced this pull request Oct 10, 2026
The canary transport never stamped lib/liveness.ts after a pane read, so
the M46 isLive gate in sendGuardedReply refused every canary send. The
transport now reports each 200 or 304 read, and run.ts hands that to the
readers' markLive, as the client's fetchPane does. An older --readers
checkout without a liveness module is left alone.

Extracted from PR #397 by @raffaelenatale, harness-canary hunks only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AltanS

AltanS commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Thanks @raffaelenatale, one part of this lands in 1.19.2 and the rest waits. The harness-canary change that stamps a pane live after each read is in, extracted as its own commit under your name, since the M46 gate had been refusing every canary send. The pi screen adapter and the dialog lifts are held. The dialog grammar reads an extension I cannot identify: the strings match neither pi's own question examples nor @juicesharp/rpiv-ask-user-question, so please name the extension and its version. Five walkSpace tests in multi-select-action.test.ts fail on the branch as pushed, dialogAcceptsTyping answers true on every question page rather than only while the "Type something." row is up, the custom-answer button reaches for document.querySelector("textarea") instead of the composer handle, and submitLabel is a hard-coded English string. The direction I lean to for pi dialogs is a small Collie-owned pi extension that hands the question to the bridge as data, instead of a second grammar read off the screen; I will write that up separately. If you want to split the PR, the composer, statusline and the /model Escape card can come first on their own.

@AltanS

AltanS commented Oct 12, 2026

Copy link
Copy Markdown
Owner

Thanks @raffaelenatale, part of this lands in 1.20.1 and I am closing the PR. Your captures and three composer facts are in, with you as co-author on the commit and named in the changelog. Instead of a grammar for your extension, one reader now serves every pi question screen by its shape and the keys its footer names; against your captures it gives buttons for single questions, tab pages and a complete Review page, but I could not run your extension live, so please confirm on your setup. The shared dialogAcceptsTyping and walkSpace changes are not taken, because they change how every adapter types and toggles, and multi-select stays in the terminal view.

@AltanS AltanS closed this Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: harness Per-agent adapters and send guards (claude/codex/pi/omp) area: install bin/collie verbs, Herdr plugin lifecycle, update, tailscale serve area: web Phone PWA — UI, mirror, composer, service worker documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants