Skip to content

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

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

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.
@GCWing
GCWing requested a review from wsp1911 July 2, 2026 09:37

@wsp1911 wsp1911 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review 意见

  1. 核心语义不对:/cd 不应直接修改正在运行 session 的 workspace
    这个 PR 当前实现是把当前会话的 session.config.workspace_path 改成新路径,同时保留同一个 session id、同一段历史和原存储目录。BitFun 桌面端的多工作区语义是“切换到目标 workspace,并打开/创建目标 workspace 下的会话”,而不是把 A workspace 的会话直接改成 B workspace 的会话。
    这样会把旧项目上下文、消息历史、memory、workspace id、usage、session listing 和新项目 cwd 混在一起,agent 很容易基于旧 repo 的假设在新 repo 里操作。建议重做为 workspace switch flow:保存当前会话,切到目标 workspace,恢复目标 workspace 最近会话或新建会话;当前 session 仍归属原 workspace。

  2. workspace_path 更新不完整,会留下旧 workspace 的派生身份
    PR 只更新了 session.config.workspace_path,但没有同步重算或清空 workspace_id、remote identity 等派生信息。当前 build_workspace_binding 会优先使用已有 workspace_id,导致后续工具 cwd 可能是新路径,但 workspace-scoped metadata、related dirs、注册信息仍可能来自旧 workspace。

  3. 存储锚点和 CLI 命令语义会错位
    PR 试图用 storage_workspace_path 让 session 文件继续留在原 workspace,但 /cd 后 CLI adapter 的 workspace 已经变成新目录。这样 /usage/sessions、session delete/restore 等命令会按新 workspace 查找,而当前会话仍存储在旧 workspace,容易出现 usage 找不到、会话列表丢失、删除错目录或删除失败。

  4. remote workspace 和多工作区边界没有设计
    /cd 通过本地 canonicalize 校验目标路径,只支持本地文件系统语义。若 CLI 后续或部分路径涉及 remote workspace,这个命令既没有 remote 支持,也没有明确 unsupported-state。按仓库规则,不能让 remote 场景静默变成错误路径或本地路径。

  5. 测试覆盖不足
    当前测试主要覆盖命令注册和 path helper。这个功能改变 session persistence/workspace binding 语义,至少需要覆盖:切换后下一轮工具 cwd、session list/restore/delete、usage report、workspace_id 更新、存储目录保持、fork/subagent 行为,以及失败回滚。

  6. PR 目前不能直接重放到主线
    当前主线已经把 SessionConfig owner 移到 bitfun-agent-runtimesrc/crates/assembly/core/src/agentic/core/session.rs 只是 re-export。PR 在旧位置加 storage_workspace_path 会和主线冲突;rebase 后应改到 runtime owner crate,并按当前 session storage/index 逻辑重新适配。

@wsp1911

wsp1911 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

感谢贡献,由于问题较多,暂时关闭

@wsp1911 wsp1911 closed this Jul 8, 2026
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.

2 participants