# Interactive terminal response design

Status: implemented public package contract

## Problem

AKK's terminal approval boundary historically exposes one operation: approve
the adapter-proven first, one-time `Yes` choice. `cancel` interrupts the Turn;
it is not a semantic `No` selection. Modern Claude Code and Codex TUI surfaces
also ask ordinary questions with choices, optional free-form input, multiple
steps, and a final confirmation. Treating every such surface as approval would
conflate permission authority with task input and would make safe replay and
auditing impossible.

This design therefore keeps two boundaries:

- **Permission decision**: a closed adapter-owned decision such as
  `approve_once` or `reject`. Persistent authorization choices are never
  offered. The caller never supplies terminal keys, menu indexes, or labels.
- **Questionnaire response**: a versioned public projection containing opaque
  question and option ids, plus typed answers. The terminal key plan and all
  mutation authority remain owner-private.

## Public model-facing contract

AKK projects one currently visible interaction:

```json
{
  "schema": "agent-knock-knock/terminal-interaction",
  "version": 1,
  "interaction_id": "opaque-id",
  "turn_id": "turn-...",
  "agent": "claude",
  "kind": "questionnaire",
  "state": "pending",
  "step": { "index": 1, "total": 2 },
  "questions": [
    {
      "question_id": "opaque-question-id",
      "header": "Color",
      "prompt": "Which color do you prefer?",
      "required": true,
      "response_kind": "single_select",
      "options": [
        { "option_id": "opaque-red-id", "label": "Red" },
        { "option_id": "opaque-blue-id", "label": "Blue" }
      ]
    }
  ],
  "capabilities": {
    "batch_response": false,
    "free_text": false,
    "multi_select": false
  }
}
```

The corresponding mutation accepts only the advertised semantic ids and typed
answer shape:

```json
{
  "interaction_id": "opaque-id",
  "turn_id": "turn-...",
  "answers": [
    {
      "question_id": "opaque-question-id",
      "response_kind": "single_select",
      "selected_option_ids": ["opaque-blue-id"]
    }
  ]
}
```

The OpenClaw tool should be named
`agent_knock_knock_respond_interaction`. It is deliberately separate from
ordinary `respond({turn_id, request})`, which injects a new composer message,
and from `approve({turn_id, decision})`, which resolves a permission prompt.

The public implementation is step-oriented: one response call resolves only
the currently projected step. The managed monitor recaptures the next native
question, publishes a new `interaction_id`, and emits a new
`interaction_required` callback for each supported actionable step. This makes
multi-question and final-confirm flows explicit and avoids a blind batch of
terminal input.

The callback is a durable wake-up signal, not response authority. It contains
only the public interaction projection and never the private prompt
fingerprint or key plan. The owning OpenClaw conversation must call Status for
the exact `turn_id`; displaying that fresh result creates the short-lived,
conversation-bound private offer required by `respond_interaction`. A delayed
or replayed callback therefore cannot authorize input by itself.

## Private authority

The Store may retain a private authority record binding all of the following:

- exact Turn and owning OpenClaw session;
- terminal endpoint, process incarnation, native thread, and binding
  generation;
- transcript/rollout source identity and bounded observation point when
  available;
- an exact unredacted prompt-region fingerprint;
- the current native step and the adapter-owned semantic-id-to-input plan;
- expiry, reservation, last proven stage, and uncertain-outcome state.

None of those mutation fields are copied into the public projection. Labels
and redacted excerpts are presentation only and never mutation authority.

## Recognition and dispatch

Every supported surface has a closed adapter/version profile. Detection must
isolate one bottom-most complete prompt, prove its footer and frame, reject
duplicate/out-of-order choices, and produce a digest before redaction.

For each response AKK:

1. acquires terminal, Store-writer, and Turn-state locks in canonical order;
2. revalidates owner, Turn status, binding generation, process/native identity,
   and the current private interaction offer; an elapsed displayed expiry
   forces this live revalidation but does not claim the native prompt died;
3. recaptures the screen and requires the same exact prompt fingerprint;
4. resolves opaque semantic ids to an adapter-owned input plan;
5. reserves the response durably before possible terminal input;
6. dispatches one bounded native step;
7. records an audit receipt without raw answer content and resumes the monitor;
   and
8. resumes the managed monitor, which recaptures the next native state, rotates
   the interaction id when the exact prompt changes, and emits the next
   supported-step callback. Status can always refresh the same projection
   explicitly.

If a failure occurs before terminal input, the response is safely rejected.
If input may have occurred, the Turn becomes `response_uncertain`; AKK sends no
automatic retry. Unknown versions, secret-input questions, ambiguous screens,
unsupported option counts, changed prompts, or unprovable postconditions are
`manual_required` and send zero input.

## Native behavior profiles

### Claude Code 2.1.263, 2.1.266, and 2.1.267

Claude Code 2.1.263 was observed locally in an isolated tmux session; the
versioned profiles retain this exact behavior contract:

- single-select: digit selects and advances/submits;
- multi-select: digits toggle checkboxes, then `Tab` advances to review;
- Other/free text: select `Type something`, enter a single-line response below
  Claude's 800-character paste-collapse threshold, then Enter;
- multi-question: a tab header shows checked/unanswered sections;
- final review: `Submit answers` / `Cancel` is a distinct confirmation step;
- Bash permission prompt: the exact four-row form has one-time Yes as the
  current row and `4. No`; `Escape` cancels the dialog and is not modeled as
  semantic reject.

The installed 2.1.266 binary and Anthropic's official 2.1.267 Darwin arm64
artifact retain the same bounded questionnaire, collapsed-paste, and Status
anchors. Each version has a distinct interaction profile so a durable offer
cannot cross a client-version change. Claude Code 2.1.261 also introduced an
optional `Organization policy` Status row when managed policy loading fails;
the 2.1.263/2.1.266/2.1.267 profiles allow that exact field and redact its
value.

The initial public implementation executes exact single-select rows (including the transition into
`Type something`), the recaptured single-line custom-text editor, and the
separate final Submit/Cancel review. Custom text is capped at 799 characters;
larger answers fail before dispatch reservation until the editor gains the
same collapsed-paste proof as managed Send. It detects multi-select screens
but keeps them `manual_required`: safely toggling several checkboxes requires
a recapture after every toggle and is intentionally deferred. `Tab to amend`,
persistent Yes, auto mode, resized/wrapped variants, and any changed menu shape
also remain manual.

### Codex 0.153.4, 0.154.0, and 0.155.1

The installed client matches the current official TUI protocol:

- `RequestUserInput` contains 1-3 questions with 2-3 options and automatic
  Other;
- numeric selection advances an ordinary choice question, but would bypass
  Notes and submit the literal label for the native Other row;
- the client-generated `None of the above` row is Codex's canonical Notes
  carrier; when the model did not supply a `Type something.` alias, AKK derives
  that separate semantic free-text choice beside the proven native row;
- choosing the guarded or derived free-text choice moves to native Other with
  bounded cursor movement and invokes Codex's native Accept action, which
  opens Notes without submitting a literal label; choosing native Other itself
  retains its distinct numeric direct-submit behavior;
- the recaptured empty `Add notes` composer becomes a separate free-text
  interaction step;
- Codex submits that text as `user_note: ...` alongside `None of the above`;
  it does not expose a Claude-style bare custom-answer value;
- semantic free-text dispatch crosses the verified 121ms paste-suppression
  window before its sole Enter;
- the exact multi-question free-form footer is handled one current step at a
  time;
- unanswered-question confirmation is its own `Proceed` / `Go back` step.

Codex does not currently expose a native multi-select question in this
protocol. Any future or unrecognized shape remains manual until a new profile
and tests are added.

Codex 0.154.0 and 0.155.1 preserve those blocking `request_user_input` frames
under separate version-bound profiles. Codex 0.154.0 also adds
`request_user_input_async`; unlike a blocking `questionnaire`, its question is
projected as `kind="async_question"` while the agent Turn remains `working`.
The projection advertises semantic `delivery_modes`. A typed response defaults
to advertised `steer_current_turn` or explicitly selects advertised `queue_next_turn`: `steer_current_turn` delivers the answer to
the running turn, while `queue_next_turn` queues it for the next turn. Blocking
questionnaires do not advertise and must reject that field.

For async questions, the advertised native Other option opens a separate
`free_text` step; it is not the blocking questionnaire's Notes workflow.
An answer queued for the next turn uses the same exact question editor and
must verify the injected answer before the final submit/queue key. If that
editor closes or changes, input stops with an uncertain result and no retry.

Collapsed async countdown redraws are not question changes: dispatch compares
the independently parsed question, pending count, opening binding, subject and
terminal identity, while retaining the rendered digest for evidence. A different
question, count, input mode or binding still rejects before input.

For managed user Send on profiled Codex 0.154.0/0.155.1 using Astra, animated
empty Composer proof requires the full dim placeholder, styled marker and shared painted
background, only the known decorated cells, and a complete supported footer.
Activity, approval and input-owner checks run first. This proof does not grant
native lifecycle or model-control authority. After the managed pre-text proof,
user-explicit Send retains its one-paste/one-Enter contract without a post-text
Composer veto.

AKK still treats input ownership independently: an exact collapsed question
summary leaves the main Composer sendable, while an expanded, clipped, or
ambiguous async editor is a zero-input boundary so an explicit task cannot
become a question answer. Response execution uses only semantic question and
option identities; raw shortcut keys and menu indexes never cross the public
contract. Codex 0.154.0 and 0.155.1 rely on exact version-bound TUI and durable
task evidence. Notification and stable-question-identity response wrappers
added on Codex main after 0.155.1 must not be assumed to exist in either
release. The 0.154/0.155 active-writer `/resume` viewer is likewise read-only
and receives no AKK terminal input.

Codex 0.155.1 can also display native user-verification prompts for MCP
requests. AKK classifies the exact prompt and waiting surfaces as blocked but
never exposes them as approvable: completing platform user verification
requires direct user presence and is outside terminal approval automation.

## Delivery stages

1. **Permission parity (implemented)**: expose `reject` only when an
   adapter proves an exact safe native reject action; retain `approve_once`
   compatibility.
2. **Read-only projection (implemented)**: detect and report supported
   questionnaire steps while changed or unsupported shapes remain manual.
3. **Initial public response contract (implemented)**: enable one current
   single-select, free-text, or confirmation step with pre-input fencing,
   durable one-shot reservation, and uncertain-result audit.
4. **Managed interaction callback (implemented)**: durably notify the owning
   controller about each actionable current step, require a fresh Status call
   before response, permanently fence a consumed prompt fingerprint from
   replay, and notify the next distinct questionnaire step independently.
5. **Expanded native coverage (not implemented)**: multi-select (tracked in
   [#292](https://github.com/scotthuang/agent-knock-knock/issues/292)),
   arbitrary per-option Notes, broader navigation, and more version profiles
   only with captured fixtures and live regression evidence.
6. **Host-native UX**: OpenClaw may render the projection using a future public
   session-bound `requestUserInput` API. Until then, the structured AKK tool is
   the authority boundary; a plugin must not use private Gateway methods.

## Non-goals

- no raw tmux keys, numeric menu indexes, or arbitrary labels in public tools;
- no automatic permission decisions or persistent authorization grants;
- no secret/password response relay;
- no blind retry after possible input;
- no attempt to emulate an arbitrary terminal UI.
