refactor(subagents): unify subagent execution, approvals, and tracing - #1552
Conversation
There was a problem hiding this comment.
Pull request overview
This PR replaces the legacy multi-agent flow with persistent subagent instances (foreground/background + resume), introduces a session-scoped approval runtime bridged through a root wire hub, and extends the vis + shell/wire surfaces to browse subagent traces/tasks and handle approvals consistently across runtimes.
Changes:
- Introduces
Agenttool +SubagentStore/builder/runner infrastructure, removing legacyTask/CreateSubagentmultiagent tools and dynamic subagent session state. - Adds
ApprovalRuntime+RootWireHubbridging so approvals (and other out-of-turn messages) can be surfaced/resolved across wire, shell, ACP, and background agents. - Extends vis UI/API to list subagents, scope wire/context views by agent, and show SubagentEvent metadata (agent id/type) across viewer components.
Reviewed changes
Copilot reviewed 119 out of 119 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| vis/src/lib/api.ts | Adds subagent types + client API helpers for listing subagents and fetching subagent wire/context. |
| vis/src/features/wire-viewer/wire-viewer.tsx | Adds agentScope to view main-agent vs subagent wire. |
| vis/src/features/wire-viewer/wire-filters.tsx | Adds a preset filter for SubagentEvent. |
| vis/src/features/wire-viewer/wire-event-card.tsx | Improves SubagentEvent summarization using agent id/type + new parent tool call id field. |
| vis/src/features/wire-viewer/turn-tree.tsx | Updates subagent grouping to use parent_tool_call_id and captures agent metadata. |
| vis/src/features/wire-viewer/turn-efficiency.tsx | Counts token usage from SubagentEvent-wrapped StatusUpdate events. |
| vis/src/features/wire-viewer/timeline-view.tsx | Adds per-subagent timeline bars, tooltip enhancements, and color mapping by type. |
| vis/src/features/wire-viewer/decision-path.tsx | Highlights Agent tool calls distinctly and extracts agent metadata from tool args. |
| vis/src/features/sessions-explorer/sessions-explorer.tsx | Trims search input before filtering. |
| vis/src/features/sessions-explorer/session-card.tsx | Shows subagent count badge in session cards. |
| vis/src/features/dual-view/dual-view.tsx | Adds agentScope support for wire+context dual view. |
| vis/src/features/context-viewer/context-viewer.tsx | Adds agentScope support + renders a scope indicator bar. |
| vis/src/features/agents-panel/agent-scope-bar.tsx | New scope selector UI to switch between main agent and subagents. |
| vis/src/App.tsx | Adds Agents tab + agent scoping state and routes scoping into viewer components. |
| tests_e2e/test_wire_steer.py | Renames/relaxes a steer lifecycle test to avoid racey behavior. |
| tests_e2e/test_wire_skills_mcp.py | Updates approval request/response snapshots to include new source fields + feedback. |
| tests_e2e/test_wire_sessions.py | Updates replay expectations after approval event behavior changes. |
| tests_e2e/test_wire_prompt.py | Updates approval request/response snapshots to include new source fields + feedback. |
| tests_e2e/test_wire_approvals_tools.py | Updates approval snapshots; adjusts rejection messaging; adds source metadata fields. |
| tests/utils/test_pyinstaller_utils.py | Updates PyInstaller data/hiddenimports expectations for new tool files and removed multiagent tools. |
| tests/ui_and_conv/test_shell_prompt_router.py | Adds running_prompt_accepts_submission() to prompt-session fakes. |
| tests/ui_and_conv/test_prompt_tips.py | Expands modal prompt behavior tests (input hiding, separator/label behavior, submission state). |
| tests/tools/test_tool_schemas.py | Replaces Task/CreateSubagent schema assertions with Agent tool schema; updates TaskOutput default. |
| tests/tools/test_tool_descriptions.py | Replaces Task/CreateSubagent description assertions with Agent tool description; updates background tool guidance text. |
| tests/tools/test_create_subagent.py | Removes tests for deleted CreateSubagent tool. |
| tests/tools/test_background_tools.py | Adds coverage for TaskOutput non-blocking default and agent-task live output logic; updates role names. |
| tests/tools/test_additional_dirs.py | Updates additional_dirs sharing tests to use copy_for_subagent. |
| tests/core/test_wire_server_steer.py | Adds tests for shutdown approval behavior, root hub loop resilience, and prompt cleanup semantics. |
| tests/core/test_wire_plan_mode.py | Updates mock labor market field name usage. |
| tests/core/test_wire_message.py | Updates wire serde snapshots for ApprovalResponse feedback + SubagentEvent field rename + legacy compatibility. |
| tests/core/test_subagent_store.py | New tests for subagent instance persistence/update semantics. |
| tests/core/test_subagent_builder.py | New tests for builtin subagent build tool policies + model selection priority. |
| tests/core/test_startup_progress.py | Adds test ensuring stale running-foreground subagents are marked failed at startup. |
| tests/core/test_session_state.py | Removes dynamic subagent state assertions; adds legacy-field-ignored coverage; updates approval runtime interactions. |
| tests/core/test_runtime_roles.py | New test asserting runtime roles are now only root and subagent. |
| tests/core/test_resume_protocol.py | New test ensuring resume reports requested vs actual subagent type correctly. |
| tests/core/test_notifications.py | Adds tests for approval bridging via root hub during KimiCLI.run. |
| tests/core/test_load_agent.py | Updates to builtin-type registration model (no fixed/dynamic injection). |
| tests/core/test_kimisoul_steer.py | Updates approval mocking to new Approval.request signature. |
| tests/core/test_kimisoul_ralph_loop.py | Updates tool-rejection messaging expectation. |
| tests/core/test_approval_runtime.py | New unit tests for ApprovalRuntime create/resolve/cancel + wire hub publishing + source context behavior. |
| tests/core/test_agent_cutover.py | New test asserting default root agent exposes Agent tool and not legacy multiagent tools. |
| tests/conftest.py | Updates runtime fixtures to register builtin subagent types + provide Agent tool fixture; removes legacy tools. |
| tests/background/test_models.py | New tests for TaskSpec owner_role normalization/compatibility. |
| src/kimi_cli/wire/types.py | Adds SubagentEvent legacy compat; expands ApprovalRequest metadata; adds ApprovalResponse feedback; updates resolve signature. |
| src/kimi_cli/wire/server.py | Adds root hub subscription loop + approval runtime replay on initialize; ensures runtime approvals are resolved/rejected on shutdown/cleanup. |
| src/kimi_cli/wire/root_hub.py | New broadcast hub for session-level out-of-turn wire messages. |
| src/kimi_cli/vis/api/sessions.py | Adds subagent_count scanning; adds subagent listing + per-subagent wire/context/meta endpoints; includes nested files in session download zips. |
| src/kimi_cli/ui/shell/console.py | Adds render_to_ansi() helper for prompt_toolkit integration. |
| src/kimi_cli/tools/utils.py | Enhances ToolRejectedError messaging and adds has_feedback flag. |
| src/kimi_cli/tools/shell/powershell.md | Updates background-task guidance to reflect TaskOutput non-blocking default. |
| src/kimi_cli/tools/shell/bash.md | Updates background-task guidance to reflect TaskOutput non-blocking default. |
| src/kimi_cli/tools/shell/init.py | Migrates approval handling to ApprovalResult (supporting feedback) and updates guidance strings. |
| src/kimi_cli/tools/multiagent/task.py | Deletes legacy Task tool implementation. |
| src/kimi_cli/tools/multiagent/task.md | Deletes legacy Task tool documentation. |
| src/kimi_cli/tools/multiagent/create.py | Deletes legacy CreateSubagent tool implementation. |
| src/kimi_cli/tools/multiagent/create.md | Deletes legacy CreateSubagent tool documentation. |
| src/kimi_cli/tools/multiagent/init.py | Removes multiagent exports. |
| src/kimi_cli/tools/file/write.py | Migrates approval handling to ApprovalResult (supporting feedback). |
| src/kimi_cli/tools/file/replace.py | Migrates approval handling to ApprovalResult (supporting feedback). |
| src/kimi_cli/tools/background/output.md | Updates TaskOutput guidance to emphasize non-blocking default. |
| src/kimi_cli/tools/background/init.py | Makes TaskOutput block default false; adds live output routing for running agent tasks; migrates approval handling to ApprovalResult. |
| src/kimi_cli/tools/agent/description.md | New Agent tool documentation. |
| src/kimi_cli/tools/agent/init.py | Implements Agent tool (foreground/background, resume, model override validation). |
| src/kimi_cli/tools/init.py | Updates key-argument extraction to use Agent description instead of Task/CreateSubagent. |
| src/kimi_cli/tools/AGENTS.md | Updates tooling guideline re: wire type references. |
| src/kimi_cli/subagents/store.py | New persistent store for subagent instances (meta/context/wire/prompt/output). |
| src/kimi_cli/subagents/registry.py | New LaborMarket registry for builtin subagent types. |
| src/kimi_cli/subagents/output.py | New unified transcript writer for subagent output logs. |
| src/kimi_cli/subagents/models.py | New dataclasses for type definitions, launch specs, and instance records. |
| src/kimi_cli/subagents/builder.py | New builder for constructing builtin subagent instances with model selection. |
| src/kimi_cli/subagents/init.py | Exposes new subagent-related APIs. |
| src/kimi_cli/soul/toolset.py | Migrates MCP tool approval handling to ApprovalResult. |
| src/kimi_cli/soul/kimisoul.py | Sets default foreground approval source per turn; changes tool-rejection stop behavior to account for feedback and subagents. |
| src/kimi_cli/soul/context.py | Moves system prompt write into a thread using sync I/O for atomic prepend. |
| src/kimi_cli/soul/approval.py | Replaces internal queue with ApprovalRuntime; returns ApprovalResult with feedback + rejection error shaping. |
| src/kimi_cli/soul/agent.py | Adds session-scoped subagent/approval/root hub infra to Runtime; replaces legacy LaborMarket with builtin-type registry; registers subagent types at load time. |
| src/kimi_cli/session_state.py | Removes persisted dynamic_subagents field. |
| src/kimi_cli/session.py | Adds subagents_dir helper and updates state docstring. |
| src/kimi_cli/llm.py | Adds clone_llm_with_model_alias() for model override cloning. |
| src/kimi_cli/background/summary.py | Includes agent_id/subagent_type in formatted output for agent tasks. |
| src/kimi_cli/background/store.py | Adds path_override for reading output from alternate paths. |
| src/kimi_cli/background/models.py | Expands TaskStatus; normalizes legacy owner roles to subagent; makes kind_payload dict[str, Any]. |
| src/kimi_cli/background/manager.py | Adds background agent task creation, lifecycle tracking, approval-cancel on kill, and status transitions. |
| src/kimi_cli/background/agent_runner.py | New in-process background agent runner with approval source + runtime event subscription and output archiving. |
| src/kimi_cli/approval_runtime/runtime.py | New session-scoped approval runtime with wait/resolve/cancel + root hub projection. |
| src/kimi_cli/approval_runtime/models.py | New approval runtime model definitions (records, sources, events). |
| src/kimi_cli/approval_runtime/init.py | Exposes approval runtime public API. |
| src/kimi_cli/app.py | Cleans up stale foreground subagents on startup; bridges root hub approvals into KimiCLI.run. |
| src/kimi_cli/agentspec.py | Adds model/when_to_use/allowed_tools support; merges these through extend. |
| src/kimi_cli/agents/okabe/agent.yaml | Switches from legacy Task tool to Agent tool; adds background + plan tools. |
| src/kimi_cli/agents/default/system.md | Documents Agent tool usage + updated background TaskOutput semantics + unified approvals. |
| src/kimi_cli/agents/default/sub.yaml | Removes legacy subagent spec file. |
| src/kimi_cli/agents/default/plan.yaml | Adds Plan builtin subagent type spec with allowlisted tools and guidance. |
| src/kimi_cli/agents/default/explore.yaml | Adds Explore builtin subagent type spec with allowlisted tools and guidance. |
| src/kimi_cli/agents/default/coder.yaml | Adds Coder builtin subagent type spec with allowlisted tools and guidance. |
| src/kimi_cli/agents/default/agent.yaml | Replaces legacy multiagent tools with Agent tool; registers builtin subagent types. |
| src/kimi_cli/acp/tools.py | Migrates ACP shell tool approval handling to ApprovalResult. |
| AGENTS.md | Updates repo-level architecture docs to reflect new subagent + approval runtime design. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| assert sorted(datas) == snapshot( | ||
| [ |
There was a problem hiding this comment.
The test builds a dynamic expected_datas list (based on sys.version_info and site_packages) but then ignores it and snapshots a hard-coded list that includes .venv/lib/python3.14/... and fastmcp-2.12.5. This will break on other Python versions / dependency versions and makes the test non-portable. Use expected_datas for the assertion (e.g., snapshot sorted(expected_datas)), or snapshot only the project-owned paths and keep third-party paths computed via site_packages.
| // Agent scope: null = main agent, string = sub-agent ID | ||
| const [agentScope, setAgentScope] = useState<string | null>(null); | ||
| const [agentScopeType, setAgentScopeType] = useState<string | null>(null); |
There was a problem hiding this comment.
agentScopeType state is set/reset but never read. This is dead state that can trigger lint warnings and makes the component harder to reason about. Either remove it, or use it (e.g., to render the current scope label/badge) so it has an effect.
| Explore: | ||
| path: ./explore.yaml | ||
| description: "Fast codebase exploration with prompt-enforced read-only behavior." | ||
| Plan: |
There was a problem hiding this comment.
The built-in subagent type keys here mix casing (coder vs Explore/Plan). Since subagent_type is used as an identifier across the CLI/tool params and the vis UI (where color/type maps use lowercase keys), this inconsistency can lead to hard-to-debug KeyErrors (when users pass explore) and missing UI styling. Consider normalizing type identifiers (e.g., make all keys lowercase, and/or make lookup case-insensitive in LaborMarket.require_builtin_type).
| Explore: | |
| path: ./explore.yaml | |
| description: "Fast codebase exploration with prompt-enforced read-only behavior." | |
| Plan: | |
| explore: | |
| path: ./explore.yaml | |
| description: "Fast codebase exploration with prompt-enforced read-only behavior." | |
| plan: |
| <Bot size={12} className="text-indigo-500" /> | ||
| <span className="font-medium">{selectedAgent.description || selectedAgent.agent_id.slice(0, 8)}</span> | ||
| <span className={`h-1.5 w-1.5 rounded-full ${TYPE_DOTS[selectedAgent.subagent_type] ?? "bg-indigo-500"}`} /> | ||
| <span className="text-[10px] text-muted-foreground">{selectedAgent.subagent_type}</span> | ||
| </> |
There was a problem hiding this comment.
TYPE_DOTS is keyed with lowercase type names, but agent.subagent_type may come back with different casing (e.g., Explore/Plan from agent specs). As a result, the dot color and fallback behavior will be inconsistent. Normalize subagent_type (e.g., agent.subagent_type.toLowerCase()) before indexing these maps, or align the backend to always emit normalized type identifiers.
| const SUBAGENT_COLORS: Record<string, string> = { | ||
| coder: "violet", | ||
| explore: "cyan", | ||
| plan: "amber", | ||
| "general-purpose": "blue", | ||
| }; | ||
| for (const [taskId, sa] of subagentData) { | ||
| if (sa.eventCount === 0) continue; | ||
| const typeLabel = sa.subagentType ? ` [${sa.subagentType}]` : ""; | ||
| bars.push({ | ||
| label: `Sub-agent`, | ||
| label: `Sub-agent${typeLabel}`, | ||
| eventIndex: sa.eventIndex, | ||
| startSec: sa.startTime, | ||
| endSec: sa.endTime, | ||
| durationSec: sa.endTime - sa.startTime, | ||
| depth: 3, | ||
| color: "indigo", | ||
| color: (sa.subagentType && SUBAGENT_COLORS[sa.subagentType]) || "indigo", | ||
| tooltipData: { |
There was a problem hiding this comment.
SUBAGENT_COLORS is keyed with lowercase type names, but sa.subagentType may be differently cased (e.g., Explore/Plan). This will silently fall back to indigo and lose the intended per-type coloring. Normalize sa.subagentType (e.g., lowercasing) before using it as a key, or ensure the backend emits normalized type identifiers.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88d868b1c8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ), | ||
| ) | ||
| self._start_background_task(watcher.run_forever()) | ||
| self._start_background_task(self._watch_root_wire_hub()) |
There was a problem hiding this comment.
Initialize approval bridge for single-command shell runs
_watch_root_wire_hub() is only started in the interactive branch, but run(command=...) returns before this setup. After this refactor, approval requests are emitted through the root hub (not the old per-turn wire pipe), so kimi --command/-c ... runs that trigger any approval-gated tool (e.g., Shell, WriteFile, TaskStop) can block indefinitely waiting for a response that is never surfaced to the UI.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5f38cad21
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| async def _ui_loop_fn(wire: Wire) -> None: | ||
| wire_ui = wire.ui_side(merge=True) | ||
| while True: | ||
| msg = await wire_ui.receive() | ||
| output.write_wire_message(msg) |
There was a problem hiding this comment.
Handle interactive wire requests in background agent runs
The background subagent UI loop only records wire messages and never forwards or resolves request-type messages, so interactive tools can deadlock background jobs. In this path, QuestionRequest/ToolCallRequest emitted by the subagent are dropped, while tools like AskUserQuestion await request.wait() and never complete; a custom subagent type that allows such tools will hang indefinitely in run_in_background. Either block unsupported interactive tools for background mode or bridge these request/response messages through a runtime channel.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa259129f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| task = asyncio.create_task(self._apply_approval_runtime_event(event)) | ||
| self._approval_update_tasks.add(task) | ||
| task.add_done_callback(self._approval_update_tasks.discard) | ||
| task.add_done_callback(self._log_approval_update_failure) |
There was a problem hiding this comment.
Serialize background approval status transitions
_on_approval_runtime_event launches one async task per runtime event, which allows request_created and request_resolved updates for the same task to run out of order. If an approval is resolved quickly, _mark_task_running can execute first and then a delayed request_created handler can overwrite the task back to awaiting_approval, even though no approvals are pending; this leaves TaskList/TaskOutput showing a phantom wait state. Please apply approval events in-order per task (or re-check pending state before marking awaiting) so status transitions stay monotonic.
Useful? React with 👍 / 👎.
| ), | ||
| ) | ||
| self._start_background_task(watcher.run_forever()) | ||
| self._start_background_task(self._watch_root_wire_hub()) |
There was a problem hiding this comment.
🔴 _watch_root_wire_hub task is killed after first interactive turn, breaking approval delivery for subsequent turns
In src/kimi_cli/ui/shell/__init__.py, the _watch_root_wire_hub coroutine is started once before the interactive prompt loop via self._start_background_task(self._watch_root_wire_hub()) (line 214). However, after every turn, run_agent()'s finally block at line 371 calls self._cancel_background_tasks(), which cancels all background tasks (including the root hub watcher) and clears the list. The watcher is never re-started for subsequent turns.
Since the old _pipe_approval_to_wire mechanism was removed from KimiSoul (src/kimi_cli/soul/kimisoul.py:562-621), all approval requests now flow exclusively through the ApprovalRuntime → RootWireHub → Shell's _watch_root_wire_hub → UI. Without the watcher, _handle_root_hub_message is never called, so approval requests published to the root hub by tools (foreground or background) during the second turn and beyond are never surfaced to the Shell UI. The tool call blocks indefinitely waiting for approval, causing the turn to hang.
Relevant code paths
Watcher started once at src/kimi_cli/ui/shell/__init__.py:214:
self._start_background_task(self._watch_root_wire_hub())Killed after every turn at src/kimi_cli/ui/shell/__init__.py:371:
self._cancel_background_tasks()Never re-started in the prompt loop or in _run_soul/run_agent.
Prompt for agents
In src/kimi_cli/ui/shell/__init__.py, the _watch_root_wire_hub background task is started once at line 214 before the interactive prompt loop, but _cancel_background_tasks() at line 371 (in run_agent's finally block) cancels it after every turn. It is never re-started.
The fix should ensure the root hub watcher survives across turns. Options:
1. Re-start the _watch_root_wire_hub task at the beginning of each _run_soul or run_agent call (alongside the visualize loop setup).
2. Move the watcher out of self._background_tasks into a separate long-lived task attribute (e.g. self._root_hub_watcher_task) that is only cancelled in the Shell's top-level run() cleanup, not per-turn.
3. In _cancel_background_tasks, skip cancelling specific long-lived tasks.
Option 2 is cleanest: store the root hub watcher task separately, start it in run(), and cancel it only when the Shell session ends.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ab75d619d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if not self._initialized: | ||
| continue |
There was a problem hiding this comment.
Do not drop root-hub approvals before initialize
The root-hub consumer currently continues whenever _initialized is false, which drops approval messages instead of buffering or forwarding them. Because prompt is still accepted without an initialize handshake, any prompt in that mode that triggers an approval-gated tool will create a pending approval but never send an ApprovalRequest to the client, leaving the tool blocked on wait_for_response() and the turn hanging indefinitely.
Useful? React with 👍 / 👎.
| finally: | ||
| if approval_source_token is not None: | ||
| reset_current_approval_source(approval_source_token) |
There was a problem hiding this comment.
Cancel foreground approvals when a turn is interrupted
This finally only resets the approval-source context token and never cancels pending approvals for that foreground turn. If a shell turn is interrupted while waiting on approval, the waiter is cancelled but the approval record remains pending in ApprovalRuntime, so stale approval UI can persist into later prompts until manually resolved. The turn cleanup should cancel requests for that foreground_turn source ID before resetting context.
Useful? React with 👍 / 👎.
| for task in approval_bridge_tasks.values(): | ||
| with contextlib.suppress(asyncio.CancelledError): | ||
| await task |
There was a problem hiding this comment.
Iterate bridge-task cleanup over a stable snapshot
This loop awaits tasks from approval_bridge_tasks.values(), but each bridge task removes itself from approval_bridge_tasks in its own finally. With multiple pending bridge tasks, awaiting one can mutate the dict during iteration and raise RuntimeError: dictionary changed size during iteration, causing KimiCLI.run() to fail during shutdown. Iterate over list(approval_bridge_tasks.values()) when cancelling/awaiting.
Useful? React with 👍 / 👎.
| def _clear_buffer(buffer: Buffer) -> None: | ||
| if buffer.text: |
There was a problem hiding this comment.
🟡 _PromptLiveView._clear_buffer doesn't bypass read-only, raises EditReadOnlyBuffer
The _PromptLiveView._clear_buffer at src/kimi_cli/ui/shell/visualize.py:1196 uses buffer.document = Document(...), which internally calls buffer.set_document(value, bypass_readonly=False). When the buffer is read-only — which happens whenever _current_approval_request_panel is set, since running_prompt_allows_text_input() returns False at src/kimi_cli/ui/shell/visualize.py:1143-1144 — this raises EditReadOnlyBuffer. The exception is caught by prompt_toolkit's event loop (so no crash), but the buffer text is silently not cleared.
Both ApprovalPromptDelegate._clear_buffer (src/kimi_cli/ui/shell/approval_panel.py:424-425) and QuestionPromptDelegate._clear_buffer (src/kimi_cli/ui/shell/question_panel.py:563-565) correctly use buffer.set_document(Document(...), bypass_readonly=True). This method should do the same.
(Refers to lines 1194-1196)
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Testing
uv run pytest tests/tools/test_background_tools.py tests/core/test_approval_runtime.py tests/core/test_background_agent_kill.py tests/ui_and_conv/test_modal_lifecycle.py tests/core/test_wire_message.py -quv run pytest tests/core/test_wire_server_steer.py tests/core/test_notifications.py tests/ui_and_conv/test_shell_task_slash.py tests/e2e/test_subagent_smoke_e2e.py -q -k 'approval or root_hub or background_agent'