mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-28 10:24:45 +00:00
* 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>
60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
import {
|
|
DAEMON_APPROVAL_MODES,
|
|
type DaemonApprovalMode,
|
|
} from '@qwen-code/webui/daemon-react-sdk';
|
|
|
|
type PromptSessionActions = {
|
|
createSession: (options?: { workspaceCwd?: string }) => Promise<unknown>;
|
|
attachSession: () => Promise<void>;
|
|
closeSession: () => Promise<void>;
|
|
clearSession: () => Promise<void>;
|
|
setModel: (modelId: string) => Promise<unknown>;
|
|
setApprovalMode: (mode: DaemonApprovalMode) => Promise<unknown>;
|
|
};
|
|
|
|
export function isDaemonApprovalMode(mode: string): mode is DaemonApprovalMode {
|
|
return DAEMON_APPROVAL_MODES.includes(mode as DaemonApprovalMode);
|
|
}
|
|
|
|
export async function createAndAttachSessionForPrompt({
|
|
sessionActions,
|
|
modelId,
|
|
modeId,
|
|
workspaceCwd,
|
|
warn = console.warn,
|
|
}: {
|
|
sessionActions: PromptSessionActions;
|
|
modelId?: string;
|
|
modeId?: string;
|
|
workspaceCwd?: string;
|
|
warn?: (message?: unknown, ...optionalParams: unknown[]) => void;
|
|
}): Promise<void> {
|
|
await sessionActions.createSession({ workspaceCwd });
|
|
try {
|
|
await sessionActions.attachSession();
|
|
} catch (error) {
|
|
warn('[WebShell] failed to attach new session:', error);
|
|
await sessionActions.closeSession().catch((closeError: unknown) => {
|
|
warn('[WebShell] failed to close unattached session:', closeError);
|
|
});
|
|
await sessionActions.clearSession().catch((clearError: unknown) => {
|
|
warn('[WebShell] failed to clear unattached session:', clearError);
|
|
});
|
|
throw error;
|
|
}
|
|
await Promise.all([
|
|
modelId
|
|
? sessionActions.setModel(modelId).catch((error: unknown) => {
|
|
warn('[WebShell] failed to set model for new session:', error);
|
|
})
|
|
: Promise.resolve(),
|
|
modeId && isDaemonApprovalMode(modeId)
|
|
? sessionActions.setApprovalMode(modeId).catch((error: unknown) => {
|
|
warn(
|
|
'[WebShell] failed to set approval mode for new session:',
|
|
error,
|
|
);
|
|
})
|
|
: Promise.resolve(),
|
|
]);
|
|
}
|