mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-06 23:35:34 +00:00
* feat(sdk): support workspace display names * docs: add Web Shell screenshot * feat(web-shell): add workspace display names * fix(serve): harden workspace display name updates * refactor(serve): simplify workspace display names * fix(serve): validate trimmed workspace display names * feat(serve): add workspace update API * docs(serve): clarify workspace display name null handling * docs(sdk): list addWorkspace in daemon client methods
80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type {
|
|
DaemonCapabilities,
|
|
DaemonSessionSummary,
|
|
DaemonWorkspaceCapability,
|
|
} from '@qwen-code/sdk/daemon';
|
|
|
|
/**
|
|
* Last path segment of an absolute workspace cwd, for a compact per-workspace
|
|
* label (e.g. `/home/me/projects/api` → `api`). Falls back to the full path when
|
|
* it has no segments.
|
|
*/
|
|
export function workspaceBasename(cwd: string): string {
|
|
const parts = cwd.split(/[\\/]+/).filter(Boolean);
|
|
return parts.at(-1) ?? cwd;
|
|
}
|
|
|
|
export function workspaceLabel(
|
|
workspace: Pick<DaemonWorkspaceCapability, 'cwd' | 'displayName'>,
|
|
): string {
|
|
return workspace.displayName?.trim() || workspaceBasename(workspace.cwd);
|
|
}
|
|
|
|
export function workspaceLabelForCwd(
|
|
cwd: string,
|
|
workspaces:
|
|
| readonly Pick<DaemonWorkspaceCapability, 'cwd' | 'displayName'>[]
|
|
| undefined,
|
|
): string {
|
|
const workspace = workspaces?.find((entry) => entry.cwd === cwd);
|
|
return workspace ? workspaceLabel(workspace) : workspaceBasename(cwd);
|
|
}
|
|
|
|
/**
|
|
* True when the daemon advertises more than one registered workspace — i.e. the
|
|
* multi-workspace session surfaces (per-workspace labels/tags) should show.
|
|
* A single-workspace daemon omits `workspaces` (or lists just the primary), so
|
|
* every workspace-scoped affordance stays hidden and the UI is unchanged.
|
|
*/
|
|
export function hasMultipleWorkspaces(
|
|
capabilities: DaemonCapabilities | undefined,
|
|
): boolean {
|
|
return (capabilities?.workspaces?.length ?? 0) > 1;
|
|
}
|
|
|
|
/**
|
|
* Whether a session belongs to a workspace other than the primary one. Both cwds
|
|
* are daemon-canonicalized, so a raw string compare is correct. Returns false
|
|
* when either cwd is unknown (treat as primary) so single-workspace never tags.
|
|
*/
|
|
export function isNonPrimaryWorkspaceSession(
|
|
workspaceCwd: string | undefined,
|
|
primaryCwd: string | undefined,
|
|
): boolean {
|
|
return !!workspaceCwd && !!primaryCwd && workspaceCwd !== primaryCwd;
|
|
}
|
|
|
|
/**
|
|
* Merge the primary workspace's sessions with the sessions collected from other
|
|
* workspaces into one list, keyed by `sessionId` (primary wins on the unlikely
|
|
* id collision). Returns the primary list unchanged (same reference) when there
|
|
* are no other-workspace sessions, so the single-workspace path is a no-op.
|
|
*/
|
|
export function mergeSessionsById(
|
|
primary: DaemonSessionSummary[],
|
|
others: DaemonSessionSummary[],
|
|
): DaemonSessionSummary[] {
|
|
if (others.length === 0) return primary;
|
|
const byId = new Map<string, DaemonSessionSummary>();
|
|
for (const session of primary) byId.set(session.sessionId, session);
|
|
for (const session of others) {
|
|
if (!byId.has(session.sessionId)) byId.set(session.sessionId, session);
|
|
}
|
|
return [...byId.values()];
|
|
}
|