Skip to content

feat(cli): add /cd slash command for in-session workspace switch#1316

Open
jarvis24young wants to merge 1 commit into
GCWing:mainfrom
jarvis24young:feat/cli-cd-command
Open

feat(cli): add /cd slash command for in-session workspace switch#1316
jarvis24young wants to merge 1 commit into
GCWing:mainfrom
jarvis24young:feat/cli-cd-command

Conversation

@jarvis24young

Copy link
Copy Markdown
Contributor

Summary

Adds a /cd <path> slash command to the CLI chat mode that re-points the active session's working directory without restarting the session. The session id, message history, and prompt-cache prefix are preserved; subsequent turns simply execute against the new cwd.

Ported from Claude Code's /cd semantics (changelog 2.1.169).

Fixes # (open — please link if there is a tracking issue)

Type and Areas

  • Type: Feature
  • Areas: Rust core, CLI

Motivation / Impact

Today, switching the working directory in a CLI session requires /clear + restart (losing prompt cache) or relaunching the binary with a different --workspace flag. /cd lets users pivot context mid-session — common when an agent task spans multiple repos, or when the user realizes halfway through that the wrong directory was opened.

Key design choice: logical cwd is split from persistence anchor.

  • SessionConfig.workspace_path (existing) continues to drive tool execution — build_workspace_binding reads it, so subsequent turns run in the new cwd.
  • New SessionConfig.storage_workspace_path (optional, serde-default None, backward-compatible) freezes the original workspace on the first /cd. Storage helpers prefer this anchor, so the session file, turn snapshots, and prompt cache stay rooted at the original directory and remain discoverable from its workspace listing.

This matches the documented /cd semantics: prompt-cache prefix and session id stay intact, and the session stays listed under its original workspace (UX tradeoff called out in Reviewer Notes).

Verification

Build + tests on top of origin/main (commit b57aa652):

cargo check --workspace                                       # clean
cargo test -p bitfun-core --lib                               # 806 passed, 1 ignored
cargo test --bin bitfun-cli                                   # 27 passed (10 new)

New unit tests:

  • commands::tests::test_cd_registered/cd is in COMMAND_SPECS
  • modes::chat::cd_helper_tests::* (10 tests) — quote stripping, tilde expansion, relative/absolute resolution, Windows drive-relative rejection

Manual sanity (CLI): /cd with no arg prints current workspace; /cd ~/projects switches; /cd nonexistent reports an error and leaves state untouched; /cd during a processing turn is rejected.

Reviewer Notes

Design walkthrough

  • SessionManager::update_session_workspace_path is the storage-side entry point. On first invocation it freezes the existing non-empty workspace_path into storage_workspace_path. Subsequent calls only mutate workspace_path. The in-memory update is followed by a persistence save routed through effective_session_workspace_path (which now prefers the storage anchor).
  • ConversationCoordinator::update_session_workspace_path is a thin pass-through with empty-path validation.
  • start_dialog_turn_internal resolves its fallback finalize storage path via effective_session_workspace_path instead of session_workspace.session_storage_path(), so safety-net turn persistence honors the anchor. The live session_workspace (used for tool execution) still reads config.workspace_path — that is the bug fix's whole point.
  • fork_agent::build_child_session_config resets storage_workspace_path = None so child subagent sessions get their own anchor (rather than inheriting the parent's post-/cd history).

Backward compatibility

storage_workspace_path is #[serde(default, skip_serializing_if = "Option::is_none")]. Old session files on disk deserialize to None and the storage helper falls back to workspace_path, byte-for-byte preserving prior behavior. SessionConfig does not set deny_unknown_fields, so older clients reading newer files also work.

Known UX tradeoff (by design)

After /cd /new_dir, the session remains listed under its original workspace directory (so it stays discoverable where the user opened it). It will not appear in /new_dir's session list. This matches Claude Code's behavior and preserves prompt-cache locality. A dual-listing/index-relocation feature is a possible follow-up but is intentionally out of scope here.

Review process

The implementation went through 4 rounds of independent review by Codex (GPT-5). Round 1 surfaced that the CLI-only fix was insufficient because start_dialog_turn_internal builds WorkspaceBinding from session.config.workspace_path, not the passed-in workspace_path parameter — so tool calls would still land in the old cwd. Round 2 caught that turn snapshots and prompt cache writes would diverge from the session file location after /cd, plus a partial-failure inconsistency in the CLI. Rounds 3 and 4 confirmed the split-anchor design closes those issues and validated subagent / remote / serde-compat edge cases. All findings are resolved; the design notes above encode the constraints learned from that process.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable. (No locale strings are touched; the command surfaces plain English status messages consistent with existing CLI commands. Happy to add zh-CN mirrors if the maintainers prefer.)

Adds a `/cd <path>` slash command to the CLI chat mode that re-points the
active session's working directory without restarting it. The session id,
message history, and prompt-cache prefix are preserved; subsequent turns
simply execute against the new cwd. Ported from Claude Code's `/cd`
semantics (changelog 2.1.169).

The key design choice is splitting "logical cwd" from "persistence anchor":

- `SessionConfig.workspace_path` (existing) continues to drive tool
  execution and `build_workspace_binding` — so subsequent turns run in
  the new cwd.
- A new `SessionConfig.storage_workspace_path` (optional, serde-default
  `None`, backward-compatible) freezes the original workspace on the
  first `/cd`. Storage helpers
  (`CoreSessionStorePort::resolve_storage_path_for_config`,
  `SessionManager::effective_session_workspace_path`) prefer this anchor
  so the session file, turn snapshots, and prompt cache stay rooted at
  the original directory and remain discoverable from its workspace
  listing.
- `start_dialog_turn_internal` resolves its fallback finalize storage
  path via `effective_session_workspace_path` instead of the live
  workspace binding, so safety-net turn persistence honors the anchor.
- Forked subagents reset `storage_workspace_path = None` so a child
  session's anchor follows its own (current) workspace, not the
  parent's post-`/cd` history.

CLI resolution handles `~`/`~/...` expansion, relative paths joined
against the current cwd, ASCII quote stripping, and rejects Windows
drive-relative forms (`C:foo`) that would otherwise be silently anchored
to the shell's per-drive cwd. The core update runs before the adapter
mutation so a core-side failure leaves session state internally
consistent. Switching is rejected while a turn is in flight.

Pre-`/cd` sessions and existing on-disk session files are unaffected:
`storage_workspace_path` deserializes to `None` and the helper falls
back to `workspace_path`, preserving prior behavior byte-for-byte.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant