Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
09406d4
feat(web): add YOLO mode support to web interface
lucaspdude Apr 5, 2026
d090419
docs: add changelog entry for YOLO mode web support
lucaspdude Apr 5, 2026
c6e9fb4
docs: add YOLO mode toggle documentation in Web UI reference
lucaspdude Apr 5, 2026
a344513
Update web/src/hooks/useYoloMode.ts
lucaspdude Apr 5, 2026
c263fea
fix(web): initialize YOLO mode from server config on session connect
lucaspdude Apr 5, 2026
9b9b6dc
Merge pull request #1 from lucaspdude/pr-1767
lucaspdude Apr 5, 2026
6ff001c
fix(web): remove problematic ref-sync effect in useYoloMode hook
lucaspdude Apr 5, 2026
0ff5cde
Merge branch 'pr-1767' into feat/yolo-mode-web
lucaspdude Apr 5, 2026
de8346a
Merge branch 'feat/yolo-mode-web' of github.com:lucaspdude/kimi-cli i…
lucaspdude Apr 5, 2026
1519ec8
fix(web): add YOLO mode yellow dashed border to prompt composer
lucaspdude Apr 5, 2026
3e94503
fix(web): add AbortController to useYoloMode hook to prevent race con…
lucaspdude Apr 5, 2026
f6fcb87
Merge branch 'pr-1767' into feat/yolo-mode-web
lucaspdude Apr 5, 2026
27b3f7c
fix(web): preserve YOLO mode state when reconnecting to same session
lucaspdude Apr 5, 2026
83bbe37
fix(web): avoid race condition in update_yolo_status endpoint
lucaspdude Apr 5, 2026
77f2b1b
fix(web): guard against stale YOLO hydration overwriting WebSocket up…
lucaspdude Apr 5, 2026
e33c691
feat(web): add YOLO mode support to web interface
lucaspdude Apr 5, 2026
47921b6
docs: add changelog entry for YOLO mode web support
lucaspdude Apr 5, 2026
989e5a3
Update web/src/hooks/useYoloMode.ts
lucaspdude Apr 5, 2026
7de9005
fix(web): initialize YOLO mode from server config on session connect
lucaspdude Apr 5, 2026
5eef9af
fix(web): remove problematic ref-sync effect in useYoloMode hook
lucaspdude Apr 5, 2026
eea1d17
fix(web): add YOLO mode yellow dashed border to prompt composer
lucaspdude Apr 5, 2026
1f8cf24
fix(web): add AbortController to useYoloMode hook to prevent race con…
lucaspdude Apr 5, 2026
1131a93
Merge branch 'pr-1767' into feat/yolo-mode-web
lucaspdude Apr 8, 2026
18c4f8a
fix(web): fix empty useEffect body for YOLO mode hydration
lucaspdude Apr 8, 2026
8c93973
Merge branch 'main' into feat/yolo-mode-web
lucaspdude Apr 8, 2026
3638038
fix(web): persist YOLO status to wire.jsonl for stopped sessions
lucaspdude Apr 8, 2026
fb6fcf7
test(web): add unit tests for YOLO mode API endpoints
lucaspdude Apr 8, 2026
dcab089
Merge upstream/main into feat/yolo-mode-web
lucaspdude Apr 9, 2026
203da08
fix(web): guard against stale YOLO fetch results after session switches
lucaspdude Apr 9, 2026
7006825
Merge remote-tracking branch 'upstream/main' into pr-1767
lucaspdude Apr 10, 2026
af706de
Merge remote-tracking branch 'upstream/main' into pr-1767
lucaspdude Apr 10, 2026
0dc0d7e
Merge branch 'pr-1767' into feat/yolo-mode-web
lucaspdude Apr 10, 2026
4a0d00b
Merge branch 'main' into feat/yolo-mode-web
lucaspdude Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ node_modules/
static/
.memo/
.entire
.claude
.claude
.local/

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Only write entries that are worth mentioning to users.
- Core: Add shared `file_filter` module — unifies file mention logic between shell and web UIs via `src/kimi_cli/utils/file_filter.py`, providing consistent path filtering, ignored directory exclusion, and git-aware file discovery
- Shell: Prevent path traversal in file mention scope parameter — the `scope` parameter in file completer requests is now validated to prevent directory traversal attacks
- Web: Restore unfiltered directory listing in file browser API — file browser endpoint no longer applies git-aware filtering, ensuring all files are visible in the web UI file picker
- Web: Add YOLO mode support to web interface — users can now toggle auto-approve mode directly from the web UI; adds `/api/sessions/{id}/yolo` endpoints and `yolo_mode` field to `StatusUpdate` (Wire 1.9)
- Todo: Refactor SetTodoList to persist state and prevent tool call storms — todos are now persisted to session state (root agent) and independent state files (sub-agents); adds query mode (omit `todos` to read current state) and clear mode (pass `[]`); includes anti-storm guidance in tool description to prevent repeated calls without progress (fixes #1710)
- ReadFile: Add total line count to every read response and support negative `line_offset` for tail mode — the tool now reports `Total lines in file: N.` in its message so the model can plan subsequent reads; negative `line_offset` (e.g. `-100`) reads the last N lines using a sliding window, useful for viewing recent log output without shell commands; the absolute value is capped at 1000 (MAX_LINES)
- Shell: Fix black background on inline code and code blocks in Markdown rendering — `NEUTRAL_MARKDOWN_THEME` now overrides all Rich default `markdown.*` styles to `"none"`, preventing Rich's built-in `"cyan on black"` from leaking through on non-black terminals
Expand Down
3 changes: 2 additions & 1 deletion docs/en/reference/kimi-web.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,10 @@ Web UI provides a unified prompt toolbar above the input box, displaying various
- **File changes**: Detects Git repository status, showing the number of new, modified, and deleted files (including untracked files). Click to view a detailed list of changes
- **Todo list**: When the `SetTodoList` tool is active, shows task progress with support for expanding to view the detailed list
- **Plan mode**: Toggle plan mode on/off from the input toolbar. When plan mode is active, the composer displays a dashed blue border. Plan mode can also be set programmatically via the `set_plan_mode` Wire protocol method
- **YOLO mode**: Toggle YOLO (auto-approve) mode on/off from the input toolbar. When YOLO mode is active, the composer displays a dashed yellow border and all operations are automatically approved without confirmation

::: info Changed
Git diff status bar added in version 1.5. Activity status indicator added in version 1.9. Version 1.10 unified it into the prompt toolbar. Version 1.11 moved the context usage indicator to the prompt toolbar. Plan mode toggle added in version 1.20.
Git diff status bar added in version 1.5. Activity status indicator added in version 1.9. Version 1.10 unified it into the prompt toolbar. Version 1.11 moved the context usage indicator to the prompt toolbar. Plan mode toggle added in version 1.20. YOLO mode toggle added in version 1.30.
:::

### Open-in functionality
Expand Down
1 change: 1 addition & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ This page documents the changes in each Kimi Code CLI release.
- Core: Add shared `file_filter` module — unifies file mention logic between shell and web UIs via `src/kimi_cli/utils/file_filter.py`, providing consistent path filtering, ignored directory exclusion, and git-aware file discovery
- Shell: Prevent path traversal in file mention scope parameter — the `scope` parameter in file completer requests is now validated to prevent directory traversal attacks
- Web: Restore unfiltered directory listing in file browser API — file browser endpoint no longer applies git-aware filtering, ensuring all files are visible in the web UI file picker
- Web: Add YOLO mode support to web interface — users can now toggle auto-approve mode directly from the web UI; adds `/api/sessions/{id}/yolo` endpoints and `yolo_mode` field to `StatusUpdate` (Wire 1.9)
- Todo: Refactor SetTodoList to persist state and prevent tool call storms — todos are now persisted to session state (root agent) and independent state files (sub-agents); adds query mode (omit `todos` to read current state) and clear mode (pass `[]`); includes anti-storm guidance in tool description to prevent repeated calls without progress (fixes #1710)
- ReadFile: Add total line count to every read response and support negative `line_offset` for tail mode — the tool now reports `Total lines in file: N.` in its message so the model can plan subsequent reads; negative `line_offset` (e.g. `-100`) reads the last N lines using a sliding window, useful for viewing recent log output without shell commands; the absolute value is capped at 1000 (MAX_LINES)
- Shell: Fix black background on inline code and code blocks in Markdown rendering — `NEUTRAL_MARKDOWN_THEME` now overrides all Rich default `markdown.*` styles to `"none"`, preventing Rich's built-in `"cyan on black"` from leaking through on non-black terminals
Expand Down
3 changes: 2 additions & 1 deletion docs/zh/reference/kimi-web.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,10 @@ Web UI 在输入框上方提供统一的提示工具栏,以可折叠标签页
- **文件变更**:检测 Git 仓库状态,显示新增、修改和删除的文件数量(包含未跟踪文件),点击可查看详细的变更列表
- **待办事项**:当 `SetTodoList` 工具处于活动状态时,显示任务进度,支持展开查看详细列表
- **Plan 模式**:在输入工具栏中切换 Plan 模式开关。Plan 模式激活时,输入框显示蓝色虚线边框。也可以通过 `set_plan_mode` Wire 协议方法程序化设置
- **YOLO 模式**:在输入工具栏中切换 YOLO(自动审批)模式开关。YOLO 模式激活时,输入框显示黄色虚线边框,所有操作将自动批准无需确认

::: info 变更
Git diff 状态栏新增于 1.5 版本。1.9 版本添加了活动状态指示器。1.10 版本将其统一为提示工具栏。1.11 版本将上下文用量指示器移至提示工具栏。1.20 版本新增 Plan 模式切换。
Git diff 状态栏新增于 1.5 版本。1.9 版本添加了活动状态指示器。1.10 版本将其统一为提示工具栏。1.11 版本将上下文用量指示器移至提示工具栏。1.20 版本新增 Plan 模式切换。1.30 版本新增 YOLO 模式切换。
:::

### Open-in 功能
Expand Down
1 change: 1 addition & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- Core:新增共享的 `file_filter` 模块——通过 `src/kimi_cli/utils/file_filter.py` 统一 Shell 和 Web 的文件引用逻辑,提供一致的路径过滤、忽略目录排除和 Git 感知文件发现
- Shell:防止文件引用 scope 参数的路径遍历——文件补全器请求中的 `scope` 参数现在会经过验证,防止目录遍历攻击
- Web:恢复文件浏览器 API 中的未过滤目录列表——文件浏览器端点不再应用 Git 感知过滤,确保 Web UI 文件选择器中显示所有文件
- Web:Web 界面新增 YOLO 模式支持——用户可直接在 Web UI 中开关自动审批模式;新增 `/api/sessions/{id}/yolo` 接口,并在 `StatusUpdate` 中增加 `yolo_mode` 字段(Wire 1.9)
- Todo:重构 `SetTodoList` 工具,支持状态持久化并防止工具调用风暴——待办事项现在会持久化到会话状态(主 Agent)和独立状态文件(子 Agent);新增查询模式(省略 `todos` 参数可读取当前状态)和清空模式(传 `[]` 清空);工具描述中增加了防风暴指导,防止在没有实际进展的情况下反复调用(修复 #1710)
- ReadFile:每次读取返回文件总行数,并支持负数 `line_offset` 实现 tail 模式——工具现在会在消息中报告 `Total lines in file: N.`,方便模型规划后续读取;负数 `line_offset`(如 `-100`)通过滑动窗口读取文件末尾 N 行,适用于无需 Shell 命令即可查看最新日志输出的场景;绝对值上限为 1000(MAX_LINES)
- Shell:修复 Markdown 渲染中行内代码和代码块出现黑色背景的问题——`NEUTRAL_MARKDOWN_THEME` 现在将所有 Rich 默认的 `markdown.*` 样式覆盖为 `"none"`,防止 Rich 内置的 `"cyan on black"` 在非黑色背景终端上泄露
Expand Down
66 changes: 66 additions & 0 deletions src/kimi_cli/web/api/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
from starlette.websockets import WebSocket, WebSocketDisconnect

from kimi_cli import logger
from kimi_cli.config import load_config
from kimi_cli.metadata import load_metadata, save_metadata
from kimi_cli.session import Session as KimiCLISession
from kimi_cli.session_state import load_session_state, save_session_state
from kimi_cli.utils.subprocess_env import get_clean_env
from kimi_cli.web.auth import is_origin_allowed, is_private_ip, verify_token
from kimi_cli.web.models import (
Expand All @@ -33,6 +35,8 @@
Session,
SessionStatus,
UpdateSessionRequest,
UpdateYoloRequest,
YoloStatus,
)
from kimi_cli.web.runner.messages import new_session_status_message, send_history_complete
from kimi_cli.web.runner.process import KimiCLIRunner
Expand All @@ -51,6 +55,7 @@
JSONRPCPromptMessage,
)
from kimi_cli.wire.serde import deserialize_wire_message
from kimi_cli.wire.file import WireFile
from kimi_cli.wire.types import is_request

router = APIRouter(prefix="/api/sessions", tags=["sessions"])
Expand Down Expand Up @@ -332,6 +337,14 @@ async def create_session(request: CreateSessionRequest | None = None) -> Session
else:
work_dir = KaosPath.unsafe_from_local_path(Path.home())
kimi_cli_session = await KimiCLISession.create(work_dir=work_dir)

# Apply default_yolo config setting to new session
config = load_config()
if config.default_yolo:
state = load_session_state(kimi_cli_session.dir)
state.approval.yolo = True
save_session_state(state, kimi_cli_session.dir)

context_file = kimi_cli_session.dir / "context.jsonl"
invalidate_sessions_cache()
invalidate_work_dirs_cache()
Expand Down Expand Up @@ -869,6 +882,59 @@ async def generate_session_title(
return GenerateTitleResponse(title=title)


@router.get("/{session_id}/yolo", summary="Get YOLO mode status")
async def get_yolo_status(session_id: UUID) -> YoloStatus:
"""Get the YOLO (auto-approve) mode status for a session."""
session = load_session_by_id(session_id)
if session is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")

session_dir = session.kimi_cli_session.dir
state = load_session_state(session_dir)

return YoloStatus(
enabled=state.approval.yolo,
auto_approve_actions=list(state.approval.auto_approve_actions),
)


@router.post("/{session_id}/yolo", summary="Update YOLO mode status")
async def update_yolo_status(
session_id: UUID,
request: UpdateYoloRequest,
runner: KimiCLIRunner = Depends(get_runner),
) -> YoloStatus:
"""Enable or disable YOLO (auto-approve) mode for a session."""
session = load_session_by_id(session_id)
if session is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")

session_dir = session.kimi_cli_session.dir
state = load_session_state(session_dir)

# Update state
state.approval.yolo = request.enabled

# If session is running, notify the worker to update runtime state.
# The worker will persist state via its notify_change callback, so we skip
# saving here to avoid race conditions with concurrent worker changes.
session_process = runner.get_session(session_id)
if session_process is not None and session_process.is_alive:
await session_process.set_yolo_mode(request.enabled)
else:
# Only save directly to disk when the worker is not alive
save_session_state(state, session_dir)
Comment thread
lucaspdude marked this conversation as resolved.
# Also persist a status update to wire.jsonl so replay shows correct YOLO state
wire_file = WireFile(session_dir / "wire.jsonl")
from kimi_cli.wire.types import StatusUpdate
await wire_file.append_message(StatusUpdate(yolo_mode=request.enabled))

return YoloStatus(
enabled=state.approval.yolo,
auto_approve_actions=list(state.approval.auto_approve_actions),
)


@router.websocket("/{session_id}/stream")
async def session_stream(
session_id: UUID,
Expand Down
15 changes: 15 additions & 0 deletions src/kimi_cli/web/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,18 @@ class GenerateTitleResponse(BaseModel):
"""Generate title response."""

title: str


class YoloStatus(BaseModel):
"""YOLO (auto-approve) mode status."""

enabled: bool = Field(..., description="Whether YOLO mode is enabled")
auto_approve_actions: list[str] = Field(
default_factory=list, description="List of auto-approved action types"
)


class UpdateYoloRequest(BaseModel):
"""Update YOLO mode request."""

enabled: bool = Field(..., description="Enable or disable YOLO mode")
19 changes: 19 additions & 0 deletions src/kimi_cli/web/runner/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,25 @@ async def _close_all_websockets(self) -> None:
# Ignore errors closing already-disconnected WebSockets
pass

async def set_yolo_mode(self, enabled: bool) -> None:
"""Set YOLO mode for the running session.

Sends a set_yolo_mode message to the worker process.
"""
if not self.is_alive:
return

message = json.dumps(
{
"jsonrpc": "2.0",
"method": "set_yolo_mode",
"id": str(uuid4()),
"params": {"enabled": enabled},
},
ensure_ascii=False,
)
await self.send_message(message)

async def remove_websocket(self, ws: WebSocket) -> None:
"""Remove a WebSocket connection from this session."""
async with self._ws_lock:
Expand Down
23 changes: 22 additions & 1 deletion src/kimi_cli/wire/jsonrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ class JSONRPCSetPlanModeMessage(_MessageBase):
params: _SetPlanModeParams


class _SetYoloModeParams(BaseModel):
enabled: bool

model_config = ConfigDict(extra="ignore")


class JSONRPCSetYoloModeMessage(_MessageBase):
method: Literal["set_yolo_mode"] = "set_yolo_mode"
id: str
params: _SetYoloModeParams


class JSONRPCCancelMessage(_MessageBase):
method: Literal["cancel"] = "cancel"
id: str
Expand Down Expand Up @@ -212,10 +224,19 @@ def _validate_params(cls, value: Any) -> Request:
| JSONRPCSteerMessage
| JSONRPCReplayMessage
| JSONRPCSetPlanModeMessage
| JSONRPCSetYoloModeMessage
| JSONRPCCancelMessage
)
JSONRPCInMessageAdapter = TypeAdapter[JSONRPCInMessage](JSONRPCInMessage)
JSONRPC_IN_METHODS = {"initialize", "prompt", "steer", "replay", "set_plan_mode", "cancel"}
JSONRPC_IN_METHODS = {
"initialize",
"prompt",
"steer",
"replay",
"set_plan_mode",
"set_yolo_mode",
"cancel",
}

type JSONRPCOutMessage = (
JSONRPCSuccessResponse
Expand Down
28 changes: 28 additions & 0 deletions src/kimi_cli/wire/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
JSONRPCReplayMessage,
JSONRPCRequestMessage,
JSONRPCSetPlanModeMessage,
JSONRPCSetYoloModeMessage,
JSONRPCSteerMessage,
JSONRPCSuccessResponse,
Statuses,
Expand Down Expand Up @@ -365,6 +366,8 @@ async def _dispatch_msg(self, msg: JSONRPCInMessage) -> None:
resp = await self._handle_steer(msg)
case JSONRPCSetPlanModeMessage():
resp = await self._handle_set_plan_mode(msg)
case JSONRPCSetYoloModeMessage():
resp = await self._handle_set_yolo_mode(msg)
case JSONRPCCancelMessage():
resp = await self._handle_cancel(msg)
case JSONRPCSuccessResponse() | JSONRPCErrorResponse():
Expand Down Expand Up @@ -763,6 +766,31 @@ async def _handle_set_plan_mode(
result={"status": "ok", "plan_mode": new_state},
)

async def _handle_set_yolo_mode(
self, msg: JSONRPCSetYoloModeMessage
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
if not isinstance(self._soul, KimiSoul):
return JSONRPCErrorResponse(
id=msg.id,
error=JSONRPCErrorObject(
code=ErrorCodes.INVALID_STATE,
message="YOLO mode is not supported",
),
)

# Update the approval state
self._soul.runtime.approval.set_yolo(msg.params.enabled)
new_state = self._soul.runtime.approval.is_yolo()

status = StatusUpdate(yolo_mode=new_state)
await self._send_msg(JSONRPCEventMessage(params=status))
# Persist to wire file so replay reconstructs yolo mode state
await self._soul.wire_file.append_message(status)
return JSONRPCSuccessResponse(
id=msg.id,
result={"status": "ok", "yolo_mode": new_state},
)

async def _handle_replay(
self, msg: JSONRPCReplayMessage
) -> JSONRPCSuccessResponse | JSONRPCErrorResponse:
Expand Down
2 changes: 2 additions & 0 deletions src/kimi_cli/wire/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ class StatusUpdate(BaseModel):
"""The message ID of the current step."""
plan_mode: bool | None = None
"""Whether plan mode (read-only) is active. None means no change."""
yolo_mode: bool | None = None
"""Whether YOLO (auto-approve) mode is active. None means no change."""
mcp_status: MCPStatusSnapshot | None = None
"""The current MCP startup snapshot. None means no change."""

Expand Down
38 changes: 38 additions & 0 deletions tests/core/test_wire_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ async def test_wire_message_serde():
"token_usage": None,
"message_id": None,
"plan_mode": None,
"yolo_mode": None,
"mcp_status": {
"loading": True,
"connected": 0,
Expand All @@ -168,6 +169,43 @@ async def test_wire_message_serde():
)
_test_serde(msg)

# StatusUpdate with yolo_mode explicitly set
msg = StatusUpdate(yolo_mode=True)
assert serialize_wire_message(msg) == snapshot(
{
"type": "StatusUpdate",
"payload": {
"context_usage": None,
"context_tokens": None,
"max_context_tokens": None,
"token_usage": None,
"message_id": None,
"plan_mode": None,
"yolo_mode": True,
"mcp_status": None,
},
}
)
_test_serde(msg)

msg = StatusUpdate(yolo_mode=False)
assert serialize_wire_message(msg) == snapshot(
{
"type": "StatusUpdate",
"payload": {
"context_usage": None,
"context_tokens": None,
"max_context_tokens": None,
"token_usage": None,
"message_id": None,
"plan_mode": None,
"yolo_mode": False,
"mcp_status": None,
},
}
)
_test_serde(msg)

msg = Notification(
id="n1234567",
category="task",
Expand Down
Loading