qwen-code/packages/web-shell/client/components/dialogs/AddWorkspaceDialog.module.css
jinye 2523a36b52
feat(web-shell): workspace management sidebar with dynamic registration (daemon multi-workspace phase 4) (#6625)
* feat(web-shell): add workspace picker for new sessions (issue #6378 phase 4)

Multi-workspace daemons now show a new-session workspace picker in the sidebar (default primary, untrusted disabled); the chosen workspace cwd is sent on POST /session so the session spawns in that workspace. daemon-react-sdk createSession gains an optional per-call workspaceCwd override covering both the detached and active-session paths; omitting it preserves the previous primary behavior.

* feat(web-shell): workspace management with dynamic registration

Replace the new-session workspace picker with a full workspace
management sidebar. Registered workspaces render as a parallel,
collapsible list (folder icon per workspace), each with its own
sessions nested underneath, and a "+" entry registers an existing
directory as a new workspace at runtime with no daemon restart.

Backend: WorkspaceRegistry becomes mutable (add()/onChange()); a new
POST /workspaces route validates the directory (exists, not a
duplicate, not nested) and registers it; run-qwen-serve exposes a
runtime factory that builds a complete workspace runtime (bridge, fs
factory, channel factory, workspace service) on demand. The SDK
DaemonClient and daemon-react-sdk gain addWorkspace().

* fix(web-shell): show newly registered workspace without a reload

Registering a workspace via the sidebar "+" left the list unchanged
until a full page reload. handleAddWorkspace called
workspace.getCapabilities(), which returns a cached promise and only
feeds setCapabilities from the mount effect, so the refresh was a no-op.

Add DaemonWorkspaceProvider.refreshCapabilities(): it bypasses the
promise cache, issues a fresh /capabilities fetch, and pushes the
result into state so consumers re-render. handleAddWorkspace now awaits
it (best-effort, so a refresh failure never masks a successful
registration).

* fix(web-shell): address review feedback for workspace management

- registry: list() returns a frozen snapshot so callers can't mutate the
  internal runtimes array (restores the push()-throws invariant)
- POST /workspaces: reject relative paths on the raw input, canonicalize
  via realpath so symlink aliases can't bypass the duplicate/nesting
  checks, and serialize concurrent registrations to close a TOCTOU race
  that leaked bridge/channel infrastructure
- sidebar: restore a compact single-workspace project header (name,
  search toggle, collapse) so single-workspace users keep those
  affordances and searchOpen/projectExpanded are no longer dead
- daemon session: include the target workspace in the create-session
  failure message
- tests: rework WebShellSidebar tests for the WorkspaceSection UI (add
  the useWorkspace mock, query workspace buttons, cover primary->undefined),
  use the canonical DaemonWorkspaceCapability type, and add a createSession
  workspaceCwd forwarding test

* fix(cli): harden dynamic workspace registration per review

- POST /workspaces: bound cwd by MAX_WORKSPACE_PATH_LENGTH before any
  filesystem work, and return a generic 500 (log the full error to
  stderr) so responses can't leak internal filesystem paths
- createDynamicWorkspaceRuntime: log a stderr warning when a workspace's
  settings can't be read, matching the startup secondary-workspace path

* qwen: address PR review feedback (#6625)

Dynamic workspace reloadDaemonEnv now mirrors the startup secondary path:
after reloadEnvironment() it rebuilds the runtime env via
buildRuntimeEnvironment(), calls wsEnv.replace(), and updates the env
metadata (envFileReadFailed / envFileReadFailures / overlayKeys /
envFilePaths). Without this, .env changes on a dynamically registered
workspace never propagated to that workspace's spawned child processes.

* qwen: address PR review feedback (#6625)

Harden POST /workspaces and the workspace registry per review:
- canonicalize with realpathSync.native (matches startup) so the same
  physical dir on a case-insensitive FS can't register twice
- nesting guard now also checks in-flight registrations, closing a
  concurrent parent/child registration race
- error responses no longer echo resolved/other-workspace paths
- registry add() isolates onChange listener throws so a bad listener
  can't abort a caller after the workspace is already committed

* qwen: address PR review feedback (#6625)

- POST /workspaces: cap total registered workspaces (startup + dynamic)
  to guard against unbounded registration exhausting resources
- createDynamicWorkspaceRuntime: register shutdown-cleanup arrays only
  after the runtime is fully built, so a throw during workspace-service
  construction can't orphan the bridge/channel
- web-shell App: reset selectedWorkspaceCwd after session creation so the
  workspace picker is one-shot (next new chat defaults to primary)

* qwen: address human review suggestions (batch 1)

- WorkspaceSection: add console.warn on session-poll failure (was silent)
- WorkspaceSection: add aria-expanded for screen readers
- AddWorkspaceDialog: associate label/input (htmlFor/id), i18n the
  absolute-path error, accept Windows drive-letter paths
- i18n: remove unused workspaceUntrustedHint key, add addWorkspaceAbsError

* qwen: address human review suggestions (batch 2)

- Remove dead CSS (.workspacePickerSelect, .workspaceItem* classes from
  the old select-based picker, replaced by WorkspaceSection)
- Add title tooltip to single-workspace project name (shows full path)
- WorkspaceSection: sync expanded state on workspace.primary change

* qwen: address human review suggestions (batch 3)

- DaemonWorkspaceProvider: refreshCapabilities now clears error on
  success and sets error+status on failure (was incomplete vs mount)
- Remove unused onChange/WorkspaceRegistryEvent from workspace registry
  per simplicity-first (no consumer exists; defers API surface until
  a real subscriber like SSE push is needed)

* qwen: add workspace-management route test coverage

Tests cover: 501 (no factory), 400 (missing/empty/relative/long/
nonexistent cwd), 409 (duplicate canonical path), 201 (success),
and verifying error messages are generic (no path leak).

* qwen: fix CI build failure — add explicit types in route test

The CLI's tsconfig includes test files in tsc --build, so all
noImplicitAny violations in tests cause build failures. Add explicit
type annotations to mock parameters.

* qwen: add type/title to single-workspace add-button

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-10 16:30:20 +00:00

45 lines
854 B
CSS

.label {
font-size: 0.8125rem;
color: var(--text-secondary, #a0a0a0);
margin-bottom: 4px;
}
.input {
width: 100%;
padding: 8px 12px;
border-radius: 6px;
border: 1px solid var(--border-color, #333);
background: var(--input-bg, #1a1a1a);
color: var(--text-primary, #e0e0e0);
font-size: 0.875rem;
font-family: var(--font-mono, monospace);
outline: none;
}
.input:focus {
border-color: var(--accent-color, #7c5cff);
}
.input:disabled {
opacity: 0.5;
}
.error {
color: var(--error-color, #f44);
font-size: 0.8125rem;
margin-top: 4px;
}
.cancelButton {
padding: 6px 14px;
border-radius: 6px;
border: 1px solid var(--border-color, #333);
background: transparent;
color: var(--text-secondary, #a0a0a0);
cursor: pointer;
font-size: 0.8125rem;
}
.cancelButton:hover {
background: var(--hover-bg, #252525);
}