Skip to content
Closed
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
6 changes: 6 additions & 0 deletions src/kimi_cli/agents/default/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ When working on existing codebase, you should:

The operating environment is not in a sandbox. Any action especially mutation you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.

Current host summary:

```
${KIMI_ENV_CONTEXT}
```

## Working Directory

The current working directory is `${KIMI_WORK_DIR}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, if so, you should strictly follow the requirements.
Expand Down
9 changes: 8 additions & 1 deletion src/kimi_cli/soul/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from kimi_cli.session import Session
from kimi_cli.soul.approval import Approval
from kimi_cli.soul.denwarenji import DenwaRenji
from kimi_cli.utils.environment import EnvironmentContext, collect_environment_context
from kimi_cli.utils.logging import logger


Expand All @@ -24,6 +25,8 @@ class BuiltinSystemPromptArgs(NamedTuple):
"""The directory listing of current working directory."""
KIMI_AGENTS_MD: str # TODO: move to first message from system prompt
"""The content of AGENTS.md."""
KIMI_ENV_CONTEXT: str
"""Environment summary for prompt injection."""


def load_agents_md(work_dir: Path) -> str | None:
Expand Down Expand Up @@ -68,6 +71,7 @@ class Runtime(NamedTuple):
builtin_args: BuiltinSystemPromptArgs
denwa_renji: DenwaRenji
approval: Approval
environment: EnvironmentContext

@staticmethod
async def create(
Expand All @@ -76,9 +80,10 @@ async def create(
session: Session,
yolo: bool,
) -> "Runtime":
ls_output, agents_md = await asyncio.gather(
ls_output, agents_md, environment = await asyncio.gather(
asyncio.to_thread(_list_work_dir, session.work_dir),
asyncio.to_thread(load_agents_md, session.work_dir),
asyncio.to_thread(collect_environment_context, session.work_dir),
)

return Runtime(
Expand All @@ -90,7 +95,9 @@ async def create(
KIMI_WORK_DIR=session.work_dir,
KIMI_WORK_DIR_LS=ls_output,
KIMI_AGENTS_MD=agents_md or "",
KIMI_ENV_CONTEXT=environment.as_prompt(),
),
denwa_renji=DenwaRenji(),
approval=Approval(yolo=yolo),
environment=environment,
)
61 changes: 61 additions & 0 deletions src/kimi_cli/utils/environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Environment detection helpers."""

from __future__ import annotations

import os
import platform
import sys
from dataclasses import dataclass
from pathlib import Path


@dataclass(slots=True)
class EnvironmentContext:
"""Snapshot of host environment characteristics."""

os_name: str
os_version: str
machine: str
shell: str

def as_prompt(self) -> str:
"""Format a succinct prompt-friendly summary."""
details = [
f"OS: {self.os_name} {self.os_version} ({self.machine})",
f"Shell: {self.shell}",
]
return "\n".join(f"- {line}" for line in details)


def detect_shell(os_name: str) -> str:
if os.name == "nt" or os_name.lower().startswith("win"):
return os.environ.get("COMSPEC", "cmd.exe")
return os.environ.get("SHELL", "/bin/sh")


def collect_environment_context(work_dir: Path | None = None) -> EnvironmentContext:
"""Collect environment data for prompt injection and tooling."""

try:
os_name = platform.system() or sys.platform
except OSError:
os_name = sys.platform

try:
os_version = platform.version()
except OSError:
os_version = "unknown"

machine = platform.machine() or "unknown"

return EnvironmentContext(
os_name=os_name,
os_version=os_version,
machine=machine,
shell=detect_shell(os_name),
)


def format_environment_context(work_dir: Path) -> tuple[EnvironmentContext, str]:
ctx = collect_environment_context(work_dir)
return ctx, ctx.as_prompt()
20 changes: 19 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from kimi_cli.tools.todo import SetTodoList
from kimi_cli.tools.web.fetch import FetchURL
from kimi_cli.tools.web.search import SearchWeb
from kimi_cli.utils.environment import EnvironmentContext


@pytest.fixture
Expand Down Expand Up @@ -68,13 +69,28 @@ def temp_share_dir() -> Generator[Path]:


@pytest.fixture
def builtin_args(temp_work_dir: Path) -> BuiltinSystemPromptArgs:
def environment_context() -> EnvironmentContext:
"""Fake environment context for tests."""

return EnvironmentContext(
os_name="TestOS",
os_version="1.0",
machine="x86_64",
shell="/bin/sh",
)


@pytest.fixture
def builtin_args(
temp_work_dir: Path, environment_context: EnvironmentContext
) -> BuiltinSystemPromptArgs:
"""Create builtin arguments with temporary work directory."""
return BuiltinSystemPromptArgs(
KIMI_NOW="1970-01-01T00:00:00+00:00",
KIMI_WORK_DIR=temp_work_dir,
KIMI_WORK_DIR_LS="Test ls content",
KIMI_AGENTS_MD="Test agents content",
KIMI_ENV_CONTEXT=environment_context.as_prompt(),
)


Expand Down Expand Up @@ -108,6 +124,7 @@ def runtime(
denwa_renji: DenwaRenji,
session: Session,
approval: Approval,
environment_context: EnvironmentContext,
) -> Runtime:
"""Create a Runtime instance."""
return Runtime(
Expand All @@ -117,6 +134,7 @@ def runtime(
denwa_renji=denwa_renji,
session=session,
approval=approval,
environment=environment_context,
)


Expand Down
12 changes: 10 additions & 2 deletions tests/test_default_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
@pytest.mark.asyncio
async def test_default_agent(runtime: Runtime):
agent = await load_agent(DEFAULT_AGENT_FILE, runtime, mcp_configs=[])
assert agent.system_prompt.replace(
normalized = agent.system_prompt.replace(
f"{runtime.builtin_args.KIMI_WORK_DIR}", "/path/to/work/dir"
) == snapshot(
)
assert normalized == snapshot(
"""\
You are Kimi CLI. You are an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.

Expand Down Expand Up @@ -61,6 +62,13 @@ async def test_default_agent(runtime: Runtime):

The operating environment is not in a sandbox. Any action especially mutation you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.

Current host summary:

```
- OS: TestOS 1.0 (x86_64)
- Shell: /bin/sh
```

## Working Directory

The current working directory is `/path/to/work/dir`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, if so, you should strictly follow the requirements.
Expand Down
48 changes: 48 additions & 0 deletions tests/test_environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from pathlib import Path

from kimi_cli.utils import environment


def test_collect_environment_context_windows(monkeypatch, tmp_path):
monkeypatch.setenv("COMSPEC", "C:/Windows/System32/cmd.exe")
monkeypatch.setattr(environment.platform, "system", lambda: "Windows", raising=False)
monkeypatch.setattr(environment.platform, "version", lambda: "11", raising=False)
monkeypatch.setattr(environment.platform, "machine", lambda: "x86_64", raising=False)
ctx = environment.collect_environment_context(tmp_path)

summary = ctx.as_prompt()
assert "Windows 11" in summary
assert "cmd.exe" in summary
assert summary.splitlines() == [
"- OS: Windows 11 (x86_64)",
"- Shell: C:/Windows/System32/cmd.exe",
]


def test_collect_environment_context_posix_default_shell(monkeypatch):
monkeypatch.delenv("SHELL", raising=False)
monkeypatch.setattr(environment.os, "name", "posix", raising=False)
monkeypatch.setattr(environment.platform, "system", lambda: "Linux", raising=False)
monkeypatch.setattr(environment.platform, "version", lambda: "6.0", raising=False)
monkeypatch.setattr(environment.platform, "machine", lambda: "arm64", raising=False)

ctx = environment.collect_environment_context(Path("/tmp"))
assert ctx.shell == "/bin/sh"
assert ctx.os_name == "Linux"
assert ctx.os_version == "6.0"
assert ctx.machine == "arm64"


def test_format_environment_context_returns_prompt(monkeypatch, tmp_path):
monkeypatch.setattr(environment.platform, "system", lambda: "TestOS", raising=False)
monkeypatch.setattr(environment.platform, "version", lambda: "1.0", raising=False)
monkeypatch.setattr(environment.platform, "machine", lambda: "amd64", raising=False)
monkeypatch.setattr(environment.os, "name", "posix", raising=False)
monkeypatch.setenv("SHELL", "/bin/zsh")

ctx, prompt = environment.format_environment_context(tmp_path)
assert prompt == ctx.as_prompt()
assert prompt.splitlines() == [
"- OS: TestOS 1.0 (amd64)",
"- Shell: /bin/zsh",
]
7 changes: 7 additions & 0 deletions tests/test_task_subagents.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ def test_task_subagents(task_tool: Task, temp_work_dir: Path):

The operating environment is not in a sandbox. Any action especially mutation you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.

Current host summary:

```
- OS: TestOS 1.0 (x86_64)
- Shell: /bin/sh
```

## Working Directory

The current working directory is `/path/to/work/dir`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, if so, you should strictly follow the requirements.
Expand Down