# Assistant Thread Working Directory Flow
更新时间:2026-03-28
> 已过时:本文记录的是 `workspaceRef / workspaceRefKind / cwd fallback` 主导时期的线程目录流转。
>
> 当前实现请优先参考:
> [docs/architecture/task-control-plane-unification.md](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/task-control-plane-unification.md)
>
> 新文档已经把 TaskThread 的主流程图和状态图重画为基于 `workspaceBinding / executionBinding / lifecycleState` 的 Mermaid 版本。
本文记录 XWorkmate 中“任务线程独立工作目录”的变量流转关系,重点覆盖:
- UI 选中线程后,当前线程是谁
- 线程记录里哪些字段决定工作目录
- cwd 如何被解析并传给 Single Agent runner
- provider 返回真实线程目录后如何回写
- 右侧边栏如何展示当前任务工作路径
## 流转图
```mermaid
flowchart LR
A["UI选中线程
controller.switchSession(sessionKey)"] --> B["currentSessionKey"]
B --> C["按 sessionKey 读取线程记录
assistantThreadRecordsInternal[sessionKey]"]
C --> D1["workspaceRef"]
C --> D2["workspaceRefKind"]
C --> D3["executionTarget"]
C -->|没有 workspaceRef| E["defaultWorkspaceRefForSessionInternal(sessionKey)"]
E --> E2["settings.workspacePath + /.xworkmate/threads/"]
D1 --> F["assistantWorkspaceRefForSession(sessionKey)"]
E --> F
D2 --> G["assistantWorkspaceRefKindForSession(sessionKey)"]
D3 --> G2["defaultWorkspaceRefKindForTargetInternal(target)"]
F --> H["resolveSingleAgentWorkingDirectoryForSessionInternal(sessionKey, provider)"]
G --> H
G2 --> H
I["provider endpoint
https/wss or local"] --> H
H -->|localPath 且存在| J["workingDirectory = 本地线程目录"]
H -->|remotePath| K["workingDirectory = 远端线程目录字符串"]
H -->|objectStore / 解析失败| L["fallback = Directory.current.path"]
J --> M["SingleAgentRunRequest.workingDirectory"]
K --> M
L --> M
M --> N["singleAgentRunner.run(...)"]
N --> O["Direct app-server / CLI / provider"]
O --> P1["result.resolvedWorkingDirectory"]
O --> P2["result.resolvedWorkspaceRefKind"]
P1 --> Q["回写线程记录
upsertAssistantThreadRecordInternal(...)"]
P2 --> Q
Q --> R["assistantThreadRecordsInternal[sessionKey] 更新"]
R --> S["右栏显示
AssistantArtifactSidebar(workspaceRef, workspaceRefKind)"]
B --> S
```
## 核心变量
### 1. 当前线程
- 变量:`currentSessionKey`
- 作用:决定当前操作命中了哪个任务线程
- 关键位置:
- [runtime_controllers_gateway.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/runtime/runtime_controllers_gateway.dart:96)
- [app_controller_desktop_thread_actions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_actions.dart:208)
如果 `currentSessionKey` 没切对,后续 `workspaceRef`、`cwd`、右栏路径都会跟着取错。
### 2. 全局基础工作目录
- 变量:`settings.workspacePath`
- 作用:线程默认目录的根目录
- 关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:571)
默认线程目录由它派生:
`/.xworkmate/threads/`
关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:560)
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:576)
### 3. 线程自己的目录绑定
- 变量:`assistantThreadRecordsInternal[sessionKey].workspaceRef`
- 作用:线程级工作目录的真实绑定值
- 关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:121)
读取优先级:
1. 线程记录里的 `workspaceRef`
2. 默认派生目录 `defaultWorkspaceRefForSessionInternal(sessionKey)`
### 4. 线程目录类型
- 变量:`assistantThreadRecordsInternal[sessionKey].workspaceRefKind`
- 类型:
- `localPath`
- `remotePath`
- `objectStore`
- 关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:133)
它决定 `workspaceRef` 后续如何参与 cwd 解析。
## 默认目录生成
### 1. 默认目录生成函数
- `defaultWorkspaceRefForSessionInternal(sessionKey)`
- `defaultLocalWorkspaceRefForSessionInternal(sessionKey)`
关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:560)
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:565)
### 2. 线程目录名
- `threadWorkspaceDirectoryNameInternal(sessionKey)`
- 作用:把 `sessionKey` 变成稳定、可落盘的目录名
关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:581)
## 线程初始化时的写入
新任务创建时,线程上下文初始化会直接写入默认目录:
- `workspaceRef: defaultWorkspaceRefForSessionInternal(normalizedSessionKey)`
- `workspaceRefKind: defaultWorkspaceRefKindForTargetInternal(resolvedTarget)`
关键位置:
- [app_controller_desktop_workspace_execution.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_workspace_execution.dart:261)
这意味着:
- 新线程刚创建时,通常先拿到“默认派生目录”
- 后续如果 provider 返回真实线程目录,才可能再回写成远端目录
## cwd 解析
### 1. 入口
- `resolveSingleAgentWorkingDirectoryForSessionInternal(sessionKey, provider)`
关键位置:
- [app_controller_desktop_runtime_coordination_impl.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_runtime_coordination_impl.dart:183)
### 2. 参与变量
- `assistantWorkspaceRefForSession(sessionKey)`
- `assistantWorkspaceRefKindForSession(sessionKey)`
- `provider`
- provider endpoint 的 scheme / host
### 3. 解析规则
- `objectStore` -> 返回 `null`
- `remotePath` -> 直接返回 `workspaceRef`
- `localPath`
- 目录存在:返回本地目录
- 目录不存在且要求必须本地存在:返回 `null`
- 目录不存在但不强制:返回字符串本身
关键位置:
- [app_controller_desktop_runtime_coordination_impl.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_runtime_coordination_impl.dart:151)
- [app_controller_desktop_runtime_coordination_impl.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_runtime_coordination_impl.dart:160)
- [app_controller_desktop_runtime_coordination_impl.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_runtime_coordination_impl.dart:183)
## provider 对 cwd 的影响
### 1. 是否必须是本地目录
- `singleAgentProviderRequiresLocalPathRuntimeInternal(provider)`
关键位置:
- [app_controller_desktop_runtime_coordination_impl.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_runtime_coordination_impl.dart:206)
规则简述:
- `https` / `wss` 远端 provider:不强制本地路径存在
- loopback / 本地 provider:通常要求本地目录
## runner 前的最后兜底
如果上面的解析结果是 `null`,发送给 Single Agent 时会退回:
- `Directory.current.path`
历史关键位置:
- 历史 Single Agent 桥接文件,已在 2026-05 清理中移除
这就是“任务线程没有有效目录时,命令最后跑到全局/容器 cwd”的直接原因。
## provider 结果回写
Single Agent 运行后可能返回:
- `result.resolvedWorkingDirectory`
- `result.resolvedWorkspaceRefKind`
历史关键位置:
- 历史 Single Agent 桥接文件,已在 2026-05 清理中移除
当前逻辑中:
- 当返回了非空 `resolvedWorkingDirectory`
- 且 `resolvedWorkspaceRefKind == remotePath`
- 会回写到当前线程的 `assistantThreadRecord`
这样第二次开始,线程就能稳定复用 provider 返回的真实目录。
## 线程目录同步逻辑
### 1. 同步入口
- `syncAssistantWorkspaceRefForSessionInternal(sessionKey)`
关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:709)
### 2. 它什么时候会改目录
会基于:
- `defaultWorkspaceRefForSessionInternal(sessionKey)`
- `defaultWorkspaceRefKindForTargetInternal(target)`
- `shouldMigrateWorkspaceRefInternal(...)`
来判断是否要把线程目录重新同步成默认派生值。
关键位置:
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:649)
- [app_controller_desktop_thread_sessions.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart:709)
### 3. 典型迁移触发
- `workspaceRef` 为空
- 指向旧共享根目录
- 指向另一个 root 下的旧默认线程目录
- 本地目录不存在
## 右侧边栏显示
右侧边栏不决定 cwd,只负责展示:
- `workspaceRef`
- `workspaceRefKind`
传入位置:
- [assistant_page_state_closure.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/features/assistant/assistant_page_state_closure.dart:317)
渲染位置:
- [assistant_artifact_sidebar.dart](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/widgets/assistant_artifact_sidebar.dart)
因此右栏显示“未设置”或显示某个路径,本质上反映的是当前线程记录里到底有没有有效 `workspaceRef`。
## 最关键的四个变量
如果只看“谁最直接影响任务线程独立工作目录”,优先级最高的是这四个:
1. `currentSessionKey`
2. `assistantThreadRecordsInternal[sessionKey].workspaceRef`
3. `assistantThreadRecordsInternal[sessionKey].workspaceRefKind`
4. `settings.workspacePath`
## 最关键的两个回退口
如果目标是“任务线程必须严格使用独立目录,不允许悄悄落回全局目录”,最值得做取舍的是这两个点:
1. `defaultWorkspaceRefForSessionInternal(...)`
- 线程没绑定目录时,是否允许自动派生一个默认目录
2. `Directory.current.path`
- cwd 解析失败时,是否允许最后再退到进程当前目录
这两个点决定了系统是:
- “尽量可运行,但可能回退到全局目录”
- 还是
- “没有线程目录就禁止运行,必须先绑定正确目录”