* feat(kiro-native): surface TUI approvals in Chat Signed-off-by: Michael Gardner <gardnmi@gmail.com> * chore: remove Kiro elicitation plan from PR Signed-off-by: Michael Gardner <gardnmi@gmail.com> * fix(kiro-native): harden permission mirror per review Address review findings on the Kiro permission mirror: - Reap finished web-delivery tasks from the pending map each poll, so a completed *or failed* keystroke delivery frees the single-prompt slot. Previously a failed delivery left the slot occupied forever, silently blocking every later prompt from reaching the web mirror. - Re-validate the visible prompt's focus and title for `accept` after the pre-Enter settle delay (symmetric with the decline path), so a focus or title drift during the settle window fails closed instead of pressing Enter on the wrong row. - Drop the redundant `event.request_id in pending` skip clause (subsumed by the `or pending` guard). - Correct docs/kiro-native-elicitation.md: cancelling a parked task only reliably aborts a verdict still waiting on the web user; a mid-delivery keystroke worker cannot be interrupted, and the per-keypress focus/title re-validation is what prevents a stray verdict from landing on a later prompt. Also document the one-at-a-time / Terminal-only fallback. Adds regression tests for the reaping behavior and the accept re-validation. Co-authored-by: Isaac * fix(test): use a benign completion token in kiro elicitation e2e The approve-path e2e asked Kiro to echo a `kiro-approval-<hex>` token right after a tool-approval prompt. A safety-conscious model reads "reply with this exact token" in an approval context as an attempt to emit a spoofed tool-approval signal and declines, so the turn-complete assertion failed even though the card -> approve -> Kiro-continues loop succeeded. Use a neutral `kiro-pwd-done-<hex>` token and plain framing, matching the render-parity sibling's benign-token pattern. Co-authored-by: Isaac * fix(kiro-native): truncate the title in the elicitation message content_preview was already capped at _PREVIEW_MAX but the card message interpolated the full untruncated title, so untrusted Kiro-derived text could reach the card unbounded. Reuse the truncated preview for both, matching the doc's untrusted-input handling. Co-authored-by: Isaac * fix(test): prove kiro approval continuation structurally, not via token echo Renaming the completion token was not enough: a safety-conscious model refuses the whole pattern of "after the approved command, output this exact token," reading it as an attempt to forge an approval signal, and runs the command but declines to emit the token. Drop the token entirely and assert continuation structurally instead -- after web approve, the gate releases, an assistant reply renders, and the turn finishes (no lingering working indicator). This no longer depends on model compliance or a machine-specific command output. Co-authored-by: Isaac * docs(kiro-native): document the single-slot reaper in race handling The race-handling section described the one-at-a-time slot but not the mechanism that frees it. Note that the slot is released when the delivery task finishes (delivered, failed checks, or timed out), not only on a recorder response, so a stuck verdict cannot wedge the slot for the session. Co-authored-by: Isaac --------- Signed-off-by: Michael Gardner <gardnmi@gmail.com> Co-authored-by: Dhruv Gupta <dhruv.gupta@databricks.com> Co-authored-by: Pat Sukprasert <pattara.sk127@gmail.com>
5.6 KiB
Kiro-native Elicitation
Status: implemented for one-time tool approvals observed on Kiro CLI 2.8.1.
Code: omnigent/kiro_native_permissions.py, omnigent/kiro_native_bridge.py, runner wiring in omnigent/runner/app.py.
Behavior
omnigent kiro still runs Kiro's own terminal UI. When Kiro shows a tool approval prompt in the embedded Terminal, Omnigent also mirrors supported one-time approvals into Chat as an approval card. The Terminal prompt remains authoritative and answerable; the Chat card is additive.
Supported today:
- Kiro ACP
session/request_permissionrecords from the samekiro-cli chat --tuisession. - Prompt options containing
allow_onceandreject_once. - Web
acceptmapped to Kiro's default one-time allow option. - Web
decline/cancelmapped to Kiro's one-time reject option.
Not surfaced today:
- Persistent trust options such as
allow_always. - Prompt types without stable ACP request ids or without
allow_once/reject_onceoptions. - Prompts already visible before the mirror starts, unless Kiro re-emits them after the recorder is attached.
Signal Source
Kiro's persisted CLI session JSONL under ~/.kiro/sessions/cli mirrors transcript records, but during the characterization probe it did not contain pending permission records. It contained conversation/tool-result records such as Prompt, AssistantMessage, and ToolResults.
The usable permission signal is Kiro's TUI ACP recorder. The runner sets KIRO_ACP_RECORD_PATH to a per-session file under the Kiro bridge directory, then omnigent/kiro_native_permissions.py tails that JSONL file. The observed record wrapper is:
{"dir":"out","msg":"{...json-rpc message...}","ts":"..."}
A pending permission is a JSON-RPC message with:
{
"id": "stable-request-id",
"method": "session/request_permission",
"params": {
"toolCall": {"toolCallId": "stable-tool-call-id", "title": "Running: pwd"},
"options": [
{"optionId": "allow_once", "kind": "allow_once"},
{"optionId": "allow_always", "kind": "allow_always"},
{"optionId": "reject_once", "kind": "reject_once"}
]
}
}
A terminal-side resolution is a JSON-RPC response with the same id and a selected result.outcome.optionId, for example allow_once or reject_once.
Verdict Delivery
Kiro's public docs describe KIRO_ACP_RECORD_PATH as a traffic recorder, not as a writable control channel. This implementation therefore does not write ACP responses. It delivers web verdicts to the active visible TUI prompt through tmux keystrokes:
accept:Enter, becauseYes, single permissionis the default focused option.decline/cancel:Down,Down,Enter, sent one key at a time with render gaps.
The render gaps are required. A live probe showed that sending Down Down Enter as one burst could still select the default approval because the TUI had not processed the intermediate selection movement.
Immediately before pressing Enter, the bridge re-verifies that Kiro's approval prompt is visible, focused on the intended row, and associated with the parsed request title — the one-time allow row for accept (re-checked after the pre-Enter settle delay), or the one-time reject row for decline / cancel after moving down one row at a time. If those checks fail, the bridge raises instead of typing, so no verdict is delivered and the Terminal remains usable.
Race Handling
The mirror starts at the current end of the recorder file. Historical recorder entries are not replayed into Chat because the Terminal is already the fallback and replaying old prompts risks stale approval cards.
For new records:
- A request followed by its response in the same poll batch is skipped, because the prompt already resolved before a web card could safely park.
- A response for a still-parked request posts
external_elicitation_resolved, clears the web card when the Terminal wins, and cancels the parked web-delivery task. Cancelling reliably aborts a verdict still waiting on the web user. If a web verdict is already mid-delivery through tmux, the keystroke worker cannot be interrupted, so the per-keypress focus and title re-validation (above) is what stops it: a verdict whose prompt has changed or vanished fails closed rather than landing on a later prompt. - A web verdict delivered through tmux is treated as a delivery attempt; Kiro's matching ACP result remains the internal confirmation that the prompt resolved.
- Once a prompt is parked, the mirror handles one approval at a time; any further Kiro prompt that arrives while it is pending stays Terminal-only (the authoritative fallback) rather than queuing a second card.
- The single slot is released as soon as the parked delivery task finishes, not only when a recorder response arrives. A verdict that was delivered, that failed its focus/title checks, or that timed out therefore cannot leave the slot occupied for the rest of the session and silently block every later prompt. A late recorder response for an already-released request finds no parked entry and is ignored.
Security Notes
- The runner sets
KIRO_ACP_RECORD_PATHitself inside the allowlisted child environment. It does not inherit an arbitrary recorder path from the parent shell. - Kiro-derived prompt text is treated as untrusted UI input and truncated before it is sent as a card preview.
- The web UI never exposes persistent trust for Kiro. Users who want persistent trust must use Kiro's own trust flags or TUI controls deliberately.
- Kiro remains authenticated by Kiro's own CLI login and does not use Omnigent Databricks, OpenAI, or Anthropic provider credentials.