diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml
index e56a3f91bb..c296e10fb1 100644
--- a/docs/architecture.i18n.yaml
+++ b/docs/architecture.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/architecture.md
-architecture.md: 054985ac5ea32a44b9daca3c1abfd58dcdc5d897
-architecture.zh.md: 84876faf2ae27069ba8bd026bcfbc56e32f65574
+architecture.md: 2ae982eba49b6dbd2365496915f9917071167813
+architecture.zh.md: abaef961504ff64dbcd1e8e8ba9bd002406fa7f4
diff --git a/docs/architecture.md b/docs/architecture.md
index 054985ac5e..2ae982eba4 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -25,27 +25,28 @@ Harnesses are [Cordis](cordis-primer.md) contexts; packages contribute services,
| ctx key | Package family | Role |
|---|---|---|
-| `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry and streaming model calls |
-| `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | singleton replay-aware request and surface pressure |
+| `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry, streaming model calls |
+| `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | replay-aware request and surface pressure |
| `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution |
-| `ctx.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | managed child-process trees for the bash executors, the LSP host, and the ACP subagent backend |
+| `ctx.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | managed child-process trees for bash, LSP, and ACP subagent backends |
| `ctx.pty` | [`pty/`](../packages/pty/README.md) | owner-scoped persistent terminal sessions |
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | same-world process confinement through argv wrapping and per-call policy |
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | shared sandbox policy home |
| `ctx.codeRuntime` | [`code-runtime/`](../packages/code-runtime/README.md) | model-written program execution |
| `ctx.fs` | [`fs/`](../packages/fs/README.md) | filesystem provider primitives and policy events |
| `ctx.lsp` | [`lsp/`](../packages/lsp/README.md) | semantic navigation registry |
-| `ctx.skills` | [`skill/`](../packages/skill/README.md) | skill provider registry and progressive disclosure |
+| `ctx.skills` | [`skill/`](../packages/skill/README.md) | skill provider registry, progressive disclosure |
| `ctx.web` | [`web/`](../packages/web/README.md) | search/fetch provider registries |
-| `ctx.compact`, `ctx.toolResultPrune` | [`compact/`](../packages/compact/README.md)/[`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune/README.md) | summary compaction and optional model-free result pruning |
+| `ctx.compact`, `ctx.toolResultPrune` | [`compact/`](../packages/compact/README.md)/[`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune/README.md) | summary compaction, optional model-free result pruning |
| `ctx.subagents` | [`subagent/`](../packages/subagent/README.md) | named delegation providers |
| `ctx.planMode` | [`plan/`](../packages/plan/README.md) | logged plan collaboration state |
-| `ctx.tasks` | [`tasks/`](../packages/tasks/README.md) | background task registry and generic `task_*` controls |
+| `ctx.tasks` | [`tasks/`](../packages/tasks/README.md) | background task registry, generic `task_*` controls |
| `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | script-driven multi-agent orchestration |
| `ctx.goals` | [`goal/`](../packages/goal/README.md) | persisted same-session goals |
| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable session-log storage |
-| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | live-preferred exact/filter/trace interface, SQLite FTS backend, workspace-authorized model tools |
-| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | log-backed fallbacks and one optional asynchronous provider |
+| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | live-preferred exact/filter/trace queries over SQLite FTS, workspace-authorized model tools |
+| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | log-backed fallbacks, one optional asynchronous provider |
+| `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI-host directory picking (`native`/`browse` interactions) |
| `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | package-name-selected registry of package-owned runtime checks |
## Event
diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md
index 84876faf2a..abaef96150 100644
--- a/docs/architecture.zh.md
+++ b/docs/architecture.zh.md
@@ -28,7 +28,7 @@
| `ctx.llm` | [`llm/`](../packages/llm/README.md) | 适配器注册表和模型流式调用 |
| `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | 感知回放的单实例请求压力与表面压力 |
| `ctx.bash` | [`bash/`](../packages/bash/README.md) | 前台和后台命令执行 |
-| `ctx.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | 供 bash 执行器、LSP host 与 ACP subagent 后端使用的受管子进程树 |
+| `ctx.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | 供 bash、LSP 与 ACP subagent 后端使用的受管子进程树 |
| `ctx.pty` | [`pty/`](../packages/pty/README.md) | 按 owner 隔离的持久化终端会话 |
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | 通过 argv 包装和逐调用策略限制同一执行环境内的进程 |
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | 共享沙箱策略归属点 |
@@ -44,8 +44,9 @@
| `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | 脚本驱动的多 agent 编排 |
| `ctx.goals` | [`goal/`](../packages/goal/README.md) | 持久化的同会话目标 |
| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | 会话日志的持久化存储 |
-| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | 实时优先的精确检索/过滤/追踪接口、SQLite 全文搜索后端、经工作区授权的模型工具 |
+| `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | 基于 SQLite 全文搜索的实时优先精确检索/过滤/追踪、经工作区授权的模型工具 |
| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | 基于日志的回退标题和单个可选异步提供方 |
+| `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI 宿主目录选取(`native`/`browse` 交互) |
| `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | 按包名筛选包自有运行时检查的注册表 |
## 事件
diff --git a/packages/client/ui-workspace/src/client/WorkspacePicker.tsx b/packages/client/ui-workspace/src/client/WorkspacePicker.tsx
index e98d6d4e26..83c06b369c 100644
--- a/packages/client/ui-workspace/src/client/WorkspacePicker.tsx
+++ b/packages/client/ui-workspace/src/client/WorkspacePicker.tsx
@@ -81,6 +81,11 @@ export function WorkspaceCreateFlow({
const [pickingFolder, setPickingFolder] = useState(false)
const [folderConflict, setFolderConflict] = useState(false)
const composingRef = useRef(false)
+ // One picking interaction at a time: while the flow is open (native chooser
+ // pending, browse dialog up) or its pick is being adopted, every other
+ // menu action stays disabled — a late outcome must not race a concurrent
+ // selection or creation.
+ const flowBusy = flowOpen || pickingFolder
const normalizedWorkspaceName = workspaceName.trim()
const duplicateWorkspaceName = !creating && normalizedWorkspaceName !== ''
&& workspaces.some(workspace => workspace.title === normalizedWorkspaceName)
@@ -91,9 +96,9 @@ export function WorkspaceCreateFlow({
// activation, and the menu re-renders on every toggle.
const createEntries: MenuEntry[] = [
...(hasDirectoryFlow()
- ? [{ id: OPEN_LOCAL_FOLDER, label: 'Open local folder…', icon: , disabled: pickingFolder }]
+ ? [{ id: OPEN_LOCAL_FOLDER, label: 'Open local folder…', icon: , disabled: flowBusy }]
: []),
- { id: CREATE_NEW, label: 'Create a new workspace', icon: , disabled: pickingFolder },
+ { id: CREATE_NEW, label: 'Create a new workspace', icon: , disabled: flowBusy },
]
// With workspaces listed, the create actions pin below the scroll region
// (divider + always visible); otherwise they ARE the menu.
@@ -103,7 +108,7 @@ export function WorkspaceCreateFlow({
id: workspace.workspaceId,
label: workspace.title,
icon: ,
- disabled: pickingFolder,
+ disabled: flowBusy,
}))
: createEntries
diff --git a/packages/client/ui-workspace/tests/workspace-picker.spec.tsx b/packages/client/ui-workspace/tests/workspace-picker.spec.tsx
index dbbb444de2..61d353fe45 100644
--- a/packages/client/ui-workspace/tests/workspace-picker.spec.tsx
+++ b/packages/client/ui-workspace/tests/workspace-picker.spec.tsx
@@ -149,12 +149,16 @@ describe('WorkspacePicker', () => {
expect(b.onPick).not.toHaveBeenCalled()
})
- it('disables the create actions and reports busy to the flow while adopting', async () => {
+ it('disables every menu action from flow open through adoption, and reports busy to the flow', async () => {
let resolve!: (workspace: WorkspaceView) => void
const pending = new Promise((settle) => { resolve = settle })
const created = workspace('adopted')
- const b = mount([], vi.fn(() => pending))
+ const b = mount([workspace('alpha', 'Alpha')], vi.fn(() => pending))
chooseItem('Open local folder…')
+ // The flow is open but nothing is picked yet: a chooser pending on the
+ // host display must already block concurrent workspace actions.
+ expect(screen.getByRole('menuitem', { name: 'Alpha' }).disabled).toBe(true)
+ expect(screen.getByRole('menuitem', { name: 'Create a new workspace' }).disabled).toBe(true)
act(() => { b.probe.owner!.onPicked('/tmp/project') })
expect(b.probe.owner!.busy).toBe(true)
expect(screen.getByRole('menuitem', { name: 'Open local folder…' }).disabled).toBe(true)
diff --git a/packages/host/directory-picker-browse/README.i18n.yaml b/packages/host/directory-picker-browse/README.i18n.yaml
index 34c3e35a79..150a9c2323 100644
--- a/packages/host/directory-picker-browse/README.i18n.yaml
+++ b/packages/host/directory-picker-browse/README.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/host/directory-picker-browse/README.md
-README.md: 688a60894cb0ab066a6d501e4df310f8d31bfb5f
-README.zh.md: c19bccc2ff9268cb7a6c931da4671bebc9f76b0c
+README.md: 632dfec3dac57cac9ea7a02225959fe6e3acf6a0
+README.zh.md: 81a1eb53eac0d3b5a1ef8f2f98c4359ef6a4c5fd
diff --git a/packages/host/directory-picker-browse/README.md b/packages/host/directory-picker-browse/README.md
index 688a60894c..632dfec3da 100644
--- a/packages/host/directory-picker-browse/README.md
+++ b/packages/host/directory-picker-browse/README.md
@@ -16,6 +16,7 @@ None; this package neither assembles nor sends a provider request.
## Known Limitations and Deferred Work
+- **No client half yet** — the in-app browsing dialog that consumes these primitives lands in the next PR of this stack; until then a `-browse` composition hides the picking affordance entirely (ui-workspace's documented empty-hole default) and the listing/creation RPCs go unconsumed.
- **Windows hidden attribute is not read** — Node dirents do not expose `FILE_ATTRIBUTE_HIDDEN`, so `hidden` means dot-prefixed on every platform until a native probe is worth its cost.
- **No drive-root enumeration** — on Windows the ancestry stops at the drive root; crossing drives waits for the browser UI's path-entry affordance rather than an enumeration primitive here.
- **Whole-filesystem scope** — no per-deployment browse-root restriction; `workspace.create` accepts arbitrary paths today, so a root here would be UX scoping, not a boundary — deferred until a deployment needs it.
diff --git a/packages/host/directory-picker-browse/README.zh.md b/packages/host/directory-picker-browse/README.zh.md
index c19bccc2ff..81a1eb53ea 100644
--- a/packages/host/directory-picker-browse/README.zh.md
+++ b/packages/host/directory-picker-browse/README.zh.md
@@ -16,6 +16,7 @@
## 已知限制与延期工作
+- **尚无 client half**——消费这些原语的应用内浏览对话框在本栈的下一个 PR 落地;在那之前 `-browse` 组合会完全隐藏选目录入口(ui-workspace 文档化的空洞默认行为),列举/创建 RPC 无消费者。
- **不读取 Windows 隐藏属性**——Node 的 dirent 不暴露 `FILE_ATTRIBUTE_HIDDEN`,因此在所有平台上 `hidden` 都意味着点前缀,直到原生探测值回其成本为止。
- **不枚举盘符根**——Windows 上祖先链止于盘符根;跨盘依赖浏览器 UI 的路径输入入口,而不是这里的枚举原语。
- **全盘可浏览**——没有按部署限定的浏览根;`workspace.create` 今天就接受任意路径,这里的根只会是 UX 范围而非边界——等到有部署需要时再做。