feat(cli): add --session/--resume flag with interactive picker and CJK-safe shorten#1716
Conversation
…K-safe shorten Add --session/--resume (-S/-r) as an optional-value flag to resume sessions: - Without argument: open interactive session picker (shell UI only) - With session ID: resume that specific session (errors if not found) - Mutually exclusive with --continue Replace all textwrap.shorten calls with a custom CJK-safe shorten() that truncates gracefully at word boundaries and falls back to hard cut for CJK text without spaces. BREAKING CHANGE: --session ID now errors when the session is not found instead of silently creating a new session. Closes #1366
There was a problem hiding this comment.
Pull request overview
This PR reintroduces a session picker/resume capability for the CLI via a unified optional-value --session/--resume flag (aligned with Claude Code’s --resume), and adds a CJK-safe shorten() utility to avoid textwrap.shorten collapsing no-space text to only an ellipsis.
Changes:
- Add
--session/--resume(-S/-r) optional-value flag with shell-only interactive picker and stricter “session not found” behavior. - Introduce
kimi_cli.utils.string.shorten()and migrate multiple call sites away fromtextwrap.shorten. - Add unit tests for
shorten()and E2E snapshot tests for new CLI error cases; update docs/changelogs (EN/ZH), including breaking-change notes.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/kimi_cli/cli/_lazy_group.py |
Implements Click parsing hack to support optional-value flags for session_id. |
src/kimi_cli/cli/__init__.py |
Adds --session/--resume options, picker-mode logic, and “not found” error behavior. |
src/kimi_cli/utils/string.py |
Adds CJK-safe shorten() implementation (whitespace normalize + word-boundary cut + hard-cut fallback). |
src/kimi_cli/utils/export.py |
Switches to the new shorten() for exported hints/overviews. |
src/kimi_cli/session.py |
Switches session title derivation to the new shorten(). |
src/kimi_cli/web/store/sessions.py |
Switches wire-derived title fallback to the new shorten(). |
src/kimi_cli/web/api/sessions.py |
Uses new shorten() for generated/fallback session titles. |
tests/utils/test_shorten.py |
Adds unit tests covering whitespace normalization, word-boundary truncation, and CJK hard-cut behavior. |
tests/e2e/test_cli_error_output.py |
Adds snapshot tests for new flag conflicts, picker misuse in print mode, and not-found session ID. |
CHANGELOG.md |
Documents the new flags, shorten() migration, and breaking change. |
docs/en/release-notes/changelog.md |
Mirrors changelog entries for Unreleased. |
docs/en/release-notes/breaking-changes.md |
Documents the changed behavior for --session ID when session is missing. |
docs/en/reference/kimi-command.md |
Updates CLI reference to include --resume alias and optional argument semantics. |
docs/en/guides/sessions.md |
Updates sessions guide with picker usage and new error behavior. |
docs/zh/release-notes/changelog.md |
Mirrors changelog entries for 未发布. |
docs/zh/release-notes/breaking-changes.md |
Documents the breaking change in Chinese. |
docs/zh/reference/kimi-command.md |
Updates CLI reference in Chinese for --session [ID] / --resume [ID]. |
docs/zh/guides/sessions.md |
Updates sessions guide in Chinese with picker usage and new error behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ## Unreleased | ||
|
|
||
| - CLI: Add `--session`/`--resume` (`-S`/`-r`) flag to resume sessions — without an argument opens an interactive session picker (shell UI only); with a session ID resumes that specific session; replaces the reverted `--pick-session`/`--list-sessions` design with a unified optional-value flag | ||
| - CLI: Add CJK-safe `shorten()` utility — replaces all `textwrap.shorten` calls so that CJK text without spaces is truncated gracefully instead of collapsing to just the placeholder |
There was a problem hiding this comment.
The changelog claims this change “replaces all textwrap.shorten calls”, but there is still at least one remaining usage in src/kimi_cli/soul/kimisoul.py (auto-title after first turn). That path will still have the old CJK-collapsing behavior, so either update that call site to use kimi_cli.utils.string.shorten too, or soften this wording to avoid overstating the coverage.
| - CLI: Add CJK-safe `shorten()` utility — replaces all `textwrap.shorten` calls so that CJK text without spaces is truncated gracefully instead of collapsing to just the placeholder | |
| - CLI: Add CJK-safe `shorten()` utility — replaces most `textwrap.shorten` calls so that CJK text without spaces is truncated gracefully instead of collapsing to just the placeholder |
| ## Unreleased | ||
|
|
||
| - CLI: Add `--session`/`--resume` (`-S`/`-r`) flag to resume sessions — without an argument opens an interactive session picker (shell UI only); with a session ID resumes that specific session; replaces the reverted `--pick-session`/`--list-sessions` design with a unified optional-value flag | ||
| - CLI: Add CJK-safe `shorten()` utility — replaces all `textwrap.shorten` calls so that CJK text without spaces is truncated gracefully instead of collapsing to just the placeholder |
There was a problem hiding this comment.
This release note says the new shorten() “replaces all textwrap.shorten calls”, but the codebase still has at least one textwrap.shorten usage (src/kimi_cli/soul/kimisoul.py, auto-title). Please either migrate that remaining call site as well (so the CJK fix applies everywhere) or adjust this wording.
| - CLI: Add CJK-safe `shorten()` utility — replaces all `textwrap.shorten` calls so that CJK text without spaces is truncated gracefully instead of collapsing to just the placeholder | |
| - CLI: Add CJK-safe `shorten()` utility — now used for most CLI truncation logic so that CJK text without spaces is truncated gracefully instead of collapsing to just the placeholder |
| ## 未发布 | ||
|
|
||
| - CLI:新增 `--session`/`--resume`(`-S`/`-r`)参数用于恢复会话——不带参数时打开交互式会话选择器(仅 Shell UI);带会话 ID 时恢复指定会话;以统一的可选值参数设计替代了被回退的 `--pick-session`/`--list-sessions` | ||
| - CLI:新增 CJK 安全的 `shorten()` 工具函数——替换所有 `textwrap.shorten` 调用,使不含空格的中日韩文本能优雅截断,而非被折叠成仅剩省略号 |
Copilot
AI
Apr 2, 2026
•
There was a problem hiding this comment.
这里写“替换所有 textwrap.shorten 调用”,但代码里仍有至少一处还在用 textwrap.shorten(src/kimi_cli/soul/kimisoul.py 自动生成标题逻辑)。建议要么把那一处也迁移到 kimi_cli.utils.string.shorten,要么把文案改成不绝对的表述,避免与实际不符。
| - CLI:新增 CJK 安全的 `shorten()` 工具函数——替换所有 `textwrap.shorten` 调用,使不含空格的中日韩文本能优雅截断,而非被折叠成仅剩省略号 | |
| - CLI:新增 CJK 安全的 `shorten()` 工具函数——用于替换 `textwrap.shorten` 调用,使不含空格的中日韩文本能优雅截断,而非被折叠成仅剩省略号 |
It says "replace all textwrap.shorten calls" here, but there is still at least one place in the code that still uses textwrap.shorten (src/kimi_cli/soul/kimisoul.py automatically generates title logic). It is recommended to either migrate that part to kimi_cli.utils.string.shorten, or change the text to a non-absolute expression to avoid inconsistency with the actual situation.
| - CLI:新增 CJK 安全的 `shorten()` 工具函数——替换所有 `textwrap.shorten` 调用,使不含空格的中日韩文本能优雅截断,而非被折叠成仅剩省略号 | |
| - CLI: Added CJK-safe `shorten()` utility function - used to replace the `textwrap.shorten` call so that Chinese, Japanese and Korean text without spaces can be gracefully truncated instead of being folded into only ellipsis |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 66354867f4
ℹ️ 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".
| else: | ||
| resumed = True # Session was actually found | ||
| logger.info("Switching to session: {session_id}", session_id=session.id) | ||
| resumed = True |
There was a problem hiding this comment.
Emit resume source for --session/--resume resumes
This path now always resumes an existing session (resumed = True), but the SessionStart hook source is still computed only from continue_ later in kimi(). In practice, resumes via --session/--resume (including picker selections) are reported as source="startup", so hooks matching resume never fire for these flows. Please derive the hook source from actual resume state (e.g., resumed) instead of only --continue.
Useful? React with 👍 / 👎.
…hook source - Revert --session ID behavior to create new session when not found - Migrate textwrap.shorten in kimisoul.py to kimi_cli.utils.string.shorten - Fix SessionStart hook source to use resumed flag instead of continue_ - Remove test_session_not_found_is_reported test - Update docs and changelog to remove breaking change references
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2649d365d
ℹ️ 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".
|
|
||
| # --- SessionStart hook --- | ||
| _session_source = "resume" if continue_ else "startup" | ||
| _session_source = "resume" if resumed else "startup" |
There was a problem hiding this comment.
Keep startup source for newly created reload targets
Deriving SessionStart source from resumed regresses flows that create a fresh session and then reload into it (for example /new creates a session and raises Reload(session_id=...)): _run marks any found session_id as resumed, so these fresh sessions now emit source="resume" and skip startup-matched hooks. This change means startup hooks no longer fire for new-session reload flows introduced by shell commands, even though the session was just created.
Useful? React with 👍 / 👎.
Signed-off-by: Kai <me@kaiyi.cool>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1ceb8f6bd
ℹ️ 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_id = asyncio.run(_pick_session()) | ||
|
|
||
| try: | ||
| switch_target, exit_code = asyncio.run(_reload_loop(session_id)) |
There was a problem hiding this comment.
Handle picker failures in the existing CLI error wrapper
Run-time failures from the new picker path bypass the CLI’s guarded error handling because asyncio.run(_pick_session()) executes before the try/except that normalizes fatal errors. If Session.list() raises (for example due to malformed kimi.json metadata or filesystem errors), users get an uncaught traceback instead of the usual logged error + stable exit path used by _reload_loop, which is a regression specific to --session/--resume without an ID.
Useful? React with 👍 / 👎.
…K-safe shorten (MoonshotAI#1716) Signed-off-by: Kai <me@kaiyi.cool>
|
非常好,session select 真的很需要 |
…default_plan_mode config Ports upstream commits: - 619327e feat(cli): add --plan flag and default_plan_mode config (MoonshotAI#1665) - 42bb381 feat(cli): add --session/--resume flag with interactive picker (MoonshotAI#1716)
Sync docs and package configs from upstream commits MoonshotAI#1665, MoonshotAI#1716, MoonshotAI#1734.
Related Issue
Resolve #1366
Description
Re-implement the session picker feature (originally PR #1376, reverted in #1608 due to doc/flag naming inconsistencies) with a unified
--session/--resumeoptional-value flag design.--session/--resume(-S/-r) — optional-value flagprompt_toolkit.ChoiceInput--continue/-CThe optional-value behavior is implemented via Click's
_flag_needs_value+flag_valuemechanism inLazySubcommandGroup.make_context().CJK-safe
shorten()utilityReplaces all
textwrap.shortencalls across the codebase with a customshorten()inkimi_cli.utils.stringthat truncates gracefully at word boundaries and falls back to hard cut for CJK text without spaces (instead of collapsing to just the placeholder).Tests
--session/--resume+--continueconflict, picker with print mode, session not found,--resumealias conflictshorten(): short text, exact width, word boundary, CJK hard cut, whitespace normalization, empty string, edge cases, custom placeholder