Skip to content

Commit d34c71e

Browse files
Merge pull request #1393 from corbitsdev/cl-10189-show-required-input-for-primary-session-ask_operator-at-the
feat(tui): show required operator input at the composer
2 parents dc4fd1c + b1b64b2 commit d34c71e

17 files changed

Lines changed: 1507 additions & 10 deletions

‎docs/TUI.md‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ terminal with nothing optional showing, the transcript floor is 12 rows
2929
(`IDLE_TRANSCRIPT_FLOOR`); with an inset overlay open the floor drops to 8 rows (`OVERLAY_TRANSCRIPT_FLOOR`) so the log stays glanceable
3030
underneath a permission prompt. When space is scarce, collapse follows a
3131
fixed order — transient banners first, then settings/plugin notices, then
32-
task/agents strips, then progress, then the prompt itself shrinks one
32+
task/agents strips, then progress, then the INPUT REQUIRED strip (the last
33+
optional row), then the prompt itself shrinks one
3334
row at a time down to its 3-row base — never the transcript
3435
(`COLLAPSE_ORDER` in `zones.ts`).
3536

@@ -331,6 +332,31 @@ truncates first, then requester metadata and routing copy shorten, while the
331332
waiting label and the count stay. Primary-session `ask_operator` prompts keep
332333
their own overlay and never use this strip.
333334

335+
### INPUT REQUIRED strip
336+
337+
While a primary-session `ask_operator` question is unanswered, a one-row
338+
**INPUT REQUIRED** strip sits on the prompt box (zone `input_required`):
339+
340+
```
341+
◆ INPUT REQUIRED · operator answer needed · Which environment should I deploy to? (+1 more)
342+
```
343+
344+
It keeps the owed answer in view while the operator scrolls, types, or has
345+
the operator modal open. It is display only: the modal stays the one place
346+
to answer, and the strip never changes focus, key routing, overlay order,
347+
timeouts, or cancellation. Only gates the primary session's `ask_operator`
348+
callback marks with `source: "primary-ask-operator"` raise it; permission
349+
gates, MCP-trust gates, and worker `ask_director` questions never do. An
350+
item's identity is its gate id, and it leaves when that gate settles
351+
(answered, cancelled with Esc, timed out, aborted, or torn down); host
352+
dispose clears every item (`src/tui/operator-input-required.ts`, fed from
353+
`gate-wire.ts` and painted by `paintChrome` in `shell/chrome.ts`). One
354+
question shows at a time, with `(+N more)` for the rest. At narrow widths the
355+
question truncates and then drops first, then the routing copy. Below 26
356+
cells the `(+N more)` count is sliced away (cut at 25, gone at 16), leaving
357+
only the label. When WORKER WAITING is also showing, it sits directly above
358+
this strip.
359+
334360
### Transcript spawn_agent rows (history anchors)
335361

336362
`runtime-bridge` paints each `spawn_agent` call as a transcript stream row for

‎src/tui/gate-events.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import type {
44
} from "../permission/types.js";
55
import type { OperatorResult } from "../agent/tools.js";
66

7+
/** Provenance minted only by the primary session's ask_operator callback. */
8+
export const PRIMARY_ASK_OPERATOR_SOURCE = "primary-ask-operator" as const;
9+
10+
export type OperatorGateSource = typeof PRIMARY_ASK_OPERATOR_SOURCE;
11+
712
/** Fail-closed settle when no approval UI can bind the operator's accept. */
813
export const APPROVAL_UNAVAILABLE_MESSAGE =
914
"no approval UI available; request denied" as const;
@@ -27,6 +32,18 @@ export interface OperatorGateEvent {
2732
* queue — the modal cannot outlive a tool that already finished.
2833
*/
2934
signal?: AbortSignal;
35+
/**
36+
* Optional display provenance minted by the session emitter, never inferred
37+
* from an operator question by the TUI.
38+
*/
39+
source?: OperatorGateSource;
40+
}
41+
42+
/** Only an explicitly marked primary ask may enter input-required chrome. */
43+
export function isPrimaryAskOperatorEvent(
44+
event: OperatorGateEvent,
45+
): event is OperatorGateEvent & { readonly source: OperatorGateSource } {
46+
return event.source === PRIMARY_ASK_OPERATOR_SOURCE;
3047
}
3148

3249
export interface PermissionGateEvent {

0 commit comments

Comments
 (0)