Conversation
Use shutil.which() as the primary method for detecting bash location, falling back to hardcoded paths only when necessary. This enables proper shell detection on systems where bash is not located at standard paths like /bin/bash. Also updates tests to normalize bash paths for cross-platform compatibility.
When using kimi -c "prompt" or kimi --command "prompt", the CLI now automatically runs in print mode (non-interactive) similar to python -c or claude -c behavior. This makes the -c flag work as expected for single-shot command execution without requiring --print flag.
Patch StreamableHttpTransport to set terminate_on_close=False when connecting to MCP servers via HTTP. This fixes the annoying "Session termination failed: 404" warning that appears when using servers like Supabase that don't support session DELETE requests.
Add SIGWINCH signal handler to detect terminal resize events and force a recompose of the Live display. This fixes the issue where text content would get stuck at a narrow width when the terminal window was resized smaller, and not expand when resized back larger.
Clear Rich's cached console dimensions (_width, _height) and Live's render shape when SIGWINCH is received. This fixes the empty gap issue when resizing terminals in Hyprland and other WMs.
- Add ClipboardVideo class and grab_video_from_clipboard() in clipboard.py - Add video support to AttachmentCache with store_video_reference() - Add _try_paste_video() method in prompt.py for Ctrl-V handling - Add _extract_video_paths() and _build_content_parts() in print/__init__.py - Export VIDEO_EXTENSIONS constant from file utils - Update documentation for video clipboard paste - Add tests for video attachment handling
Remove device fingerprinting data from HTTP headers: - Device name, model, OS version now return 'anonymous'/'unknown' - Device ID is now a static zeroed UUID - Default aiohttp User-Agent replaced with minimal KimiCLI/<version> Also cleaned up unused functions and imports in oauth module. Bumps version to 1.15.0
## Summary Add structured session logging for better debugging and audit trails. ## Changes ### kimi_cli/ - `project_log.py`: NEW - Core logging module with SessionLogger and SessionLogRecorder - `app.py`: Create SessionLogger and pass to run_soul - `soul/__init__.py`: Integrate session logging for events, prompts, tool calls - `tools/multiagent/task.py`: Pass logger to subagents - `ui/print/__init__.py`: Logger integration - `ui/shell/__init__.py`: Logger integration - `wire/server.py`: Logger integration ## Features - **Session logging**: Logs to `~/.kimi/sessions/<hash>/<session>/logs/` in JSON Lines format - **Comprehensive events**: Session start/end, prompts, thinking, tool calls, approvals
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47941d37f0
ℹ️ 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".
| session_recorder = SessionLogRecorder(session_logger) | ||
| session_recorder.start() | ||
| await session_logger.log_session_start() |
There was a problem hiding this comment.
Feed wire events into the session log recorder
run_soul starts a SessionLogRecorder here, but nothing ever enqueues messages into it (the existing wire_send path still only calls wire.soul_side.send). That means this feature only writes session_start.jsonl/session_end.jsonl, while event logs like user_prompt_submit.jsonl, thinking.jsonl, and tool_calls.jsonl never get populated. Please wire the recorder to actual wire traffic (e.g., by forwarding each emitted WireMessage or subscribing the recorder to a wire queue) before shutdown.
Useful? React with 👍 / 👎.
| for arg in sys.argv[1:]: | ||
| if arg in ("-c", "--command"): | ||
| command_mode = True |
There was a problem hiding this comment.
Recognize attached
--command values when enabling print mode
This detection only matches raw argv tokens equal to -c or --command, so valid option forms with attached values (such as --command=... or -c...) are not treated as command mode. In those cases prompt is populated but command_mode stays false, so the run falls back to shell UI instead of the auto-print behavior added by this change. Use parsed option state (or handle attached forms explicitly) rather than exact-token matching.
Useful? React with 👍 / 👎.
| session_logger = SessionLogger( | ||
| work_dir=Path(str(self._runtime.session.work_dir)), | ||
| session_id=self._runtime.session.id, | ||
| session_dir=self._runtime.session.dir, | ||
| ) |
There was a problem hiding this comment.
🔴 Task tool references non-existent self._runtime attribute, causing AttributeError
The newly added session logging code in Task._run_subagent references self._runtime at lines 134-136, but self._runtime is never assigned anywhere. The Task.__init__ (src/kimi_cli/tools/multiagent/task.py:57-70) only stores self._labor_market = runtime.labor_market and self._session = runtime.session, and the parent class CallableTool2 has no _runtime attribute either.
Root Cause and Impact
The code at line 134 does self._runtime.session.work_dir, but self._runtime doesn't exist. The existing code already has the session stored as self._session (line 70: self._session = runtime.session), so the correct references should be self._session.work_dir, self._session.id, and self._session.dir.
This will raise an AttributeError every time a subagent is spawned via the Task tool, causing the subagent call to fail with "Failed to run subagent" error.
| session_logger = SessionLogger( | |
| work_dir=Path(str(self._runtime.session.work_dir)), | |
| session_id=self._runtime.session.id, | |
| session_dir=self._runtime.session.dir, | |
| ) | |
| session_logger = SessionLogger( | |
| work_dir=Path(str(self._session.work_dir)), | |
| session_id=self._session.id, | |
| session_dir=self._session.dir, | |
| ) |
Was this helpful? React with 👍 or 👎 to provide feedback.
| except Exception: | ||
| end_reason = "error" | ||
| raise |
There was a problem hiding this comment.
🟡 Cancelled run logs end_reason as "error" instead of "cancelled"
In run_soul, when a run is cancelled, end_reason is set to "cancelled" on line 180, and then RunCancelled is raised on line 184. However, RunCancelled extends Exception (src/kimi_cli/soul/__init__.py:118), so it is caught by the except Exception: block on line 191, which overwrites end_reason to "error" before re-raising.
Detailed Explanation
The flow is:
end_reason = "cancelled"(line 180)raise RunCancelled from None(line 184)except Exception:catchesRunCancelled(line 191)end_reason = "error"(line 192) — overwrites the correct valueraisere-raisesRunCancelled(line 193)finally:logsend_reasonas"error"instead of"cancelled"(line 197)
The same issue applies to MaxStepsReached and LLMNotSet and other expected exceptions that go through line 190's soul_task.result() — they are all logged as "error" rather than being distinguished.
To fix, the except Exception handler should preserve the already-set end_reason (e.g., only overwrite if it's still "completed").
| except Exception: | |
| end_reason = "error" | |
| raise | |
| except RunCancelled: | |
| end_reason = "cancelled" | |
| raise | |
| except Exception: | |
| end_reason = "error" | |
| raise |
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Add structured session logging for better debugging and audit trails.
Changes
project_log.pywith SessionLogger and SessionLogRecorderFeatures
~/.kimi/sessions/<hash>/<session>/logs/Testing
kimiin shell mode~/.kimi/sessions/<hash>/<session>/logs/for log files