Skip to content

feat(cli): interactive terminal tool - #11394

Merged
catrielmuller merged 7 commits into
mainfrom
shy-tellurium
Jun 29, 2026
Merged

feat(cli): interactive terminal tool#11394
catrielmuller merged 7 commits into
mainfrom
shy-tellurium

Conversation

@catrielmuller

@catrielmuller catrielmuller commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Issue

No tracking issue. This is a self-contained feature addition to the CLI; opening it directly for review.

Fixes #

Context

The agent currently has no way to hand the user direct keyboard control of a running process. Commands that genuinely need a TTY or private human input — gh auth login, vercel login, SSH/GPG passphrase and 2FA prompts, interactive scaffolders like npm create, and REPLs like python3/psql — either hang the bash tool waiting on a human who can't reach the process, or have to be awkwardly worked around with flags and piped input.

This adds an interactive_terminal tool that runs a single command in a real PTY, opens a terminal dialog over the session input area, streams live output, and hands keyboard control to the user. The tool blocks until the process exits or the user closes the terminal, then returns the captured output and how it ended (exit code, user close, or cancellation) back to the model.

Implementation

The feature is isolated under src/kilocode/ as much as possible; shared upstream files carry small, marked kilocode_change hooks.

Core enginesrc/kilocode/interactive-terminal/index.ts owns PTY lifecycle as an instance-scoped service:

  • Enforces a single active terminal per session.
  • Output is buffered and flushed on a 25ms timer as interactive_terminal.data bus events; a rolling 2 MB UTF-8-safe cap (output.ts) bounds memory. The live stream keeps raw bytes for VT rendering, while the model-facing result is ANSI-stripped.
  • Output-capture gating: the command is wrapped so the spawned shell blocks on a single read before executing (stty -echo; read on POSIX, pause/ReadKey on cmd/PowerShell). The server only writes the release byte after the onData handler is attached, so no early output is lost to a startup race.
  • A finalizer kills any live terminals on instance dispose; stopSession and the tool's abort signal also terminate the PTY.

Permissions — rather than duplicate bash's command scanning, src/tool/shell.ts is refactored to extract a reusable ShellPermission Effect service (the largest shared-file change — worth a close look). The new tool reuses the exact same tree-sitter path scanning and permission prompt as bash, plus a directory permission when workdir is outside the project.

Surfaces — live UI in two places:

  • TUI session route (routes/session/terminal.tsx) renders over the input area and takes input ownership while active; context/sync.tsx folds the bus events into the store.
  • kilo run direct-interactive footer (src/kilocode/cli/cmd/run/interactive-terminal.tsx + footer/runtime/session-data glue), with mouse enabled for close/scroll.
  • A minimal terminal emulator (src/kilocode/cli/cmd/tui/vt/vt-screen.ts) turns PTY escape sequences into a static text snapshot with scrollback/viewport offset for OpenTUI's <text>.
  • HTTP API (/interactive-terminal list/get/write/resize/close) plus regenerated SDK and OpenAPI for client control and SSE events.

Availability gating — the tool is CLI-only (Flag.KILO_CLIENT === "cli"), primary-agent only (hidden from subagents in the registry), and denied for subagents, system/utility agents, and non-interactive kilo run.

Screenshots / Video

TUI

image image image image image

CLI

image image image

How to Test

Manual/local verification

  • bun test ./test/kilocode/interactive-terminal.test.ts ./test/kilocode/vt-screen.test.ts → 27 pass (agent-executed).
  • bun test ./test/kilocode/cli/cmd/run/interactive-terminal.test.ts → 2 pass (agent-executed).
  • Modified suites tool-registry-indexing, task-nesting, cli/auto-mode, agent/agent, tool/task pass (agent-executed).
  • bun run typecheck in packages/opencode/ is clean; the cross-package pre-push turbo typecheck (all 17 tasks) succeeded (agent-executed).

Reviewer test steps

  1. Build and launch the CLI TUI in a primary agent.
  2. Ask the agent to run something interactive, e.g. "log me into GitHub" (gh auth login) or "open a python REPL".
  3. Confirm a terminal dialog opens over the input area, streams live output, and accepts keystrokes; type into the prompt and complete the flow.
  4. Press Ctrl+C to send an interrupt, and click x (or press Esc in run mode) to force-close; confirm the tool result reports exit code / user close accordingly.
  5. Confirm the tool is offered only to the primary agent (not subagents) and not in non-interactive kilo run.

Blocked checks and substitute verification

  • The full bun test run surfaced one failure in test/session/prompt.test.ts ("shell correlates the persisted tool part with its completed v2 record"). This test is not modified by this branch and fails only because the local developer shell's direnv hook injects a direnv: unloading banner into command stdout. Substitute verification: inspected the assertion diff and confirmed the only delta is the injected direnv banner (not a logic regression); the test passes in a clean environment / CI without direnv shell hooks.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

The branch base referenced a removed WithInstance.provide helper; align
with the renamed provideTestInstance fixture export (matching main) so
the package typechecks and the pre-push hook passes.
targetFps: 30,
maxFps: 60,
useMouse: false,
useMouse: true, // kilocode_change - interactive terminal close and scroll controls

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING]: useMouse: true is now enabled for every kilo run interactive session, not only while the terminal dialog is open.

This flips mouse capture on globally for the run renderer (previously false). When the interactive terminal isn't active, mouse events will still be captured by OpenTUI, which can regress native terminal mouse behaviors in the rest of the run footer (text selection, scrollback wheel scrolling). Consider enabling mouse only while the terminal view is mounted, or scope the flag to terminal-active state, so non-terminal run sessions keep the previous no-mouse-capture behavior.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

description?: string
}

export const ShellPermission = Effect.gen(function* () {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION]: Fork-hygiene: ShellPermission adds ~110 lines to a shared upstream file.

This is the largest shared-file change in the PR and restructures upstream bash-permission logic into a new reusable service inside shell.ts. To minimize the upstream diff for future merges, consider whether the ShellPermission service itself could live under src/kilocode/tool/shell-permission.ts (reusing exported primitives from shell.ts), keeping only thin marked hooks here. The current placement is defensible since the helpers (cygpath, collect, argpath, etc.) are private, but it's worth a conscious call since it widens our diff against opencode in a hot shared file.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/src/cli/cmd/run/footer.ts
  • packages/opencode/src/cli/cmd/run/session-data.ts
  • packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
  • packages/opencode/test/agent/agent.test.ts
Previous Review Summaries (3 snapshots, latest commit 9d9a5d0)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 9d9a5d0)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts

Previous review (commit df70221)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/cli/cmd/tui/context/sync.tsx 806 Reconnect bootstrap only inserts missing interactive-terminal snapshots and never replaces or removes existing ones, so a missed interactive_terminal.deleted/data event can leave stale terminal state that keeps the normal session prompt hidden.
Files Reviewed (30 files)
  • packages/opencode/src/cli/cmd/run.ts, runtime.ts, types.ts, stream.transport.ts - run runtime and transport follow-up changes
  • packages/opencode/src/cli/cmd/run/footer.ts, footer.view.tsx, session-data.ts, tool.ts - run footer state and tool rendering
  • packages/opencode/src/cli/cmd/tui/context/sync.tsx, routes/session/index.tsx, packages/opencode/src/kilocode/cli/cmd/run/interactive-terminal.tsx, packages/opencode/src/kilocode/cli/cmd/tui/vt/vt-screen.ts - TUI terminal sync and prompt ownership
  • packages/opencode/src/agent/agent.ts, session/session.ts, tool/registry.ts, tool/task.ts, tool/shell.ts, packages/opencode/src/kilocode/tool/registry.ts, packages/opencode/src/kilocode/tool/task.ts - permission and tool-registry changes
  • packages/opencode/src/kilocode/server/httpapi/server.ts, packages/opencode/src/server/routes/instance/httpapi/api.ts, packages/sdk/js/src/v2/gen/sdk.gen.ts, packages/sdk/js/src/v2/gen/types.gen.ts, packages/sdk/openapi.json - API exposure and generated surface
  • packages/opencode/test/agent/agent.test.ts, packages/opencode/test/cli/run/footer.view.test.tsx, packages/opencode/test/kilocode/interactive-terminal.test.ts, packages/opencode/test/kilocode/task-nesting.test.ts, packages/opencode/test/kilocode/tool-registry-indexing.test.ts, packages/opencode/test/tool/task.test.ts - incremental test updates

Fix these issues in Kilo Cloud

Previous review (commit 4e3a4da)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts 181 useMouse: true flips on global mouse capture for all kilo run interactive sessions, not just while the terminal dialog is open — potential regression for non-terminal mouse behavior (selection/scroll).

SUGGESTION

File Line Issue
packages/opencode/src/tool/shell.ts 321 Fork-hygiene: ~110-line ShellPermission service is added to a shared upstream file; consider relocating to src/kilocode/ to shrink the upstream diff.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
packages/opencode/src/permission/index.ts 503 SCALAR_ONLY_PERMISSIONS omits interactive_terminal. Like question/suggest, it is a tool-level scalar permission; toConfig will serialize persisted interactive_terminal rules in object form ({ "*": action }) instead of the scalar form used by Permission.fromConfig({ interactive_terminal: "allow" }). Worth adding for round-trip consistency. (Low impact: the tool never calls ctx.ask with this permission, so user-granted scalar rules are unlikely.)
bun.lock 24–31 Lockfile adds @pierre/diffs and moves @opencode-ai/ui into kilo-web-ui's deps, but no packages/kilo-web-ui/package.json change ships in this PR. Confirm the lockfile is consistent with the committed package.json files (looks like stray regen from unrelated branch state).
packages/kilo-web-ui/src/components/session-review.tsx, script/upstream/opencode-changesets.ts Unrelated prettier-only reformatting churn bundled into this feature PR. Harmless, but inflates the diff.
Notes on memory-leak review

The custom instruction to scrutinize memory leaks was applied to the new PTY engine (packages/opencode/src/kilocode/interactive-terminal/index.ts). Findings:

  • state.terminals entries are removed in finish (line 273); active.timer is cleared in both flush and finish; active.data/active.exit disposers and the abort listener are released in finish; an Effect.addFinalizer kills any live terminals on instance dispose. No leaks found in the engine path.
  • appendTerminalOutput caps retained output at 2 MB with UTF-8-safe trimming, so the per-terminal buffer is bounded.
  • The client TerminalPrompt chains writes on a single state.input promise and guards with closing(); the chain drains after close and the SDK calls are .catch-suppressed, so no unbounded promise chain accumulates.
Files Reviewed (15 of 49)
  • packages/opencode/src/kilocode/interactive-terminal/index.ts - core PTY engine (no issues)
  • packages/opencode/src/kilocode/interactive-terminal/output.ts - output cap (no issues)
  • packages/opencode/src/kilocode/tool/interactive-terminal.ts - tool def (no issues)
  • packages/opencode/src/kilocode/cli/cmd/tui/vt/vt-screen.ts - VT emulator (no issues)
  • packages/opencode/src/cli/cmd/tui/routes/session/terminal.tsx - TUI terminal prompt (no issues)
  • packages/opencode/src/kilocode/cli/cmd/run/interactive-terminal.tsx - run footer terminal body (no issues)
  • packages/opencode/src/cli/cmd/tui/context/sync.tsx - terminal event sync (no issues)
  • packages/opencode/src/cli/cmd/run/session-data.ts - footer reducer (no issues)
  • packages/opencode/src/tool/shell.ts - ShellPermission refactor (1 suggestion)
  • packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts - useMouse change (1 warning)
  • packages/opencode/src/agent/agent.ts, src/cli/cmd/run.ts, src/tool/task.ts, src/kilocode/tool/task.ts, src/kilocode/tool/registry.ts - permission gating (no issues)

Fix these issues in Kilo Cloud


Reviewed by gpt-5.4-20260305 · Input: 88.4K · Output: 5.8K · Cached: 328.5K

Review guidance: REVIEW.md from base branch main

# Conflicts:
#	bun.lock
#	packages/opencode/src/cli/cmd/run/footer.ts
#	packages/opencode/src/cli/cmd/run/footer.view.tsx
#	packages/opencode/src/cli/cmd/tui/context/sync.tsx
#	packages/opencode/src/kilocode/tool/registry.ts
#	packages/opencode/src/session/session.ts
#	packages/opencode/src/tool/shell.ts
#	packages/opencode/test/kilocode/task-nesting.test.ts
#	packages/opencode/test/kilocode/tool-registry-indexing.test.ts
#	packages/opencode/test/session/prompt.test.ts
for (const [sessionID, list] of Object.entries(next)) {
const items = new Map((draft[sessionID] ?? []).map((item) => [item.info.id, item]))
for (const item of list) {
if (!items.has(item.info.id)) items.set(item.info.id, item)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING]: Reconnect bootstrap keeps stale terminal snapshots

interactiveTerminal.list() is the authoritative server snapshot after reconnect, but this merge only inserts missing IDs and never replaces or removes existing entries. If the client misses an interactive_terminal.deleted or interactive_terminal.data event while disconnected, the stale local snapshot survives every bootstrap: closed terminals still occupy sync.data.interactive_terminal, and existing terminals never refresh their output/cursor from the server. Because Session() hides the normal prompt whenever any terminal snapshot remains active, a missed delete can strand the user in a dead terminal overlay until a full reload.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@catrielmuller
catrielmuller merged commit 58a6883 into main Jun 29, 2026
32 of 34 checks passed
@catrielmuller
catrielmuller deleted the shy-tellurium branch June 29, 2026 20:54
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
feat(cli): interactive terminal tool
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants