Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Only write entries that are worth mentioning to users.

## Unreleased

- Core: Fix `UserPromptSubmit` hook receiving empty prompt text for non-string user input — content part lists (images, attachments) now have their text extracted via `Message.extract_text` instead of being replaced with `""`

## 1.44.0 (2026-05-13)

- Shell: Add slash command alias resolution — aliases such as `/h`, `?`, and `status` now resolve to their canonical commands (`/help`, `/usage`); the completer and help output display alias matches as `/name (alias)` for clarity
Expand Down
2 changes: 2 additions & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ This page documents the changes in each Kimi Code CLI release.

## Unreleased

- Core: Fix `UserPromptSubmit` hook receiving empty prompt text for non-string user input — content part lists (images, attachments) now have their text extracted via `Message.extract_text` instead of being replaced with `""`

## 1.44.0 (2026-05-13)

- Shell: Add slash command alias resolution — aliases such as `/h`, `?`, and `status` now resolve to their canonical commands (`/help`, `/usage`); the completer and help output display alias matches as `/name (alias)` for clarity
Expand Down
2 changes: 2 additions & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

## 未发布

- Core:修复 `UserPromptSubmit` hook 在用户输入为非字符串内容时收到空 prompt 文本的问题——包含图片、附件等 ContentPart 列表的输入现在通过 `Message.extract_text` 提取可读文本,而非被替换为空字符串

## 1.44.0 (2026-05-13)

- Shell:新增斜杠命令别名解析——别名(如 `/h`、`?`、`status`)现在能正确解析到对应的正式命令(`/help`、`/usage`);补全器和帮助输出会将别名匹配项显示为 `/name (alias)`,方便识别
Expand Down
6 changes: 5 additions & 1 deletion src/kimi_cli/soul/kimisoul.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,11 @@ async def run(
# they are not user input, and a user-configured prompt-blocking
# hook would drop the notification and hang the wait loop.
if not skip_user_prompt_hook:
text_input_for_hook = user_input if isinstance(user_input, str) else ""
if isinstance(user_input, str):
text_input_for_hook = user_input
else:
user_message = Message(role="user", content=user_input)
text_input_for_hook = user_message.extract_text(" ").strip()

hook_results = await self._hook_engine.trigger(
"UserPromptSubmit",
Expand Down
50 changes: 50 additions & 0 deletions tests/core/test_kimisoul_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Tests for KimiSoul lifecycle hook integration."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import AsyncMock

import pytest

import kimi_cli.soul.kimisoul as kimisoul_module
from kosong.tooling.empty import EmptyToolset

from kimi_cli.hooks.config import HookDef
from kimi_cli.hooks.engine import HookEngine
from kimi_cli.soul.agent import Agent, Runtime
from kimi_cli.soul.context import Context
from kimi_cli.soul.kimisoul import KimiSoul
from kimi_cli.wire.types import TextPart


@pytest.mark.asyncio
async def test_user_prompt_submit_extracts_text_from_content_parts(
runtime: Runtime, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""UserPromptSubmit hook receives extracted text when input is list[ContentPart]."""
captured_payloads: list[dict] = []

async def _capture_trigger(event: str, *, matcher_value: str = "", input_data: dict | None = None) -> list:
captured_payloads.append(input_data or {})
return []

agent = Agent(
name="Test Agent",
system_prompt="Test system prompt.",
toolset=EmptyToolset(),
runtime=runtime,
)
soul = KimiSoul(agent, context=Context(file_backend=tmp_path / "history.jsonl"))
soul._turn = AsyncMock(return_value=None) # type: ignore[method-assign]
monkeypatch.setattr(kimisoul_module, "wire_send", lambda _msg: None)

hook_engine = HookEngine([HookDef(event="UserPromptSubmit", command="echo ok", timeout=5)])
monkeypatch.setattr(hook_engine, "trigger", _capture_trigger)
soul.set_hook_engine(hook_engine)

await soul.run([TextPart(text="hello world")])

user_prompt_payloads = [p for p in captured_payloads if p.get("hook_event_name") == "UserPromptSubmit"]
assert len(user_prompt_payloads) == 1
assert user_prompt_payloads[0].get("prompt") == "hello world"
Loading