feat(webui): add project workspaces and access controls (#4007)

* feat(webui): add project workspaces and access controls

* feat(webui): add project workspaces and access controls

* refactor(tools): centralize workspace access resolution

* refactor(webui): remove unused workspace host state

* fix(webui): hide estimated file edit label

* fix(webui): clarify file edit deletion feedback

* fix(webui): label deleted file activity

* fix(webui): flatten file edit activity rows

* fix(core): remove path-only patch deletion

* fix(core): keep apply patch non-destructive

* refactor(webui): trim workspace host plumbing

* fix(tools): register exec with tools config
This commit is contained in:
Xubin Ren
2026-05-29 03:42:53 +08:00
committed by GitHub
parent 84428136e6
commit 3a420136bb
111 changed files with 9972 additions and 1822 deletions
+56
View File
@@ -0,0 +1,56 @@
import type { WorkspaceAccessMode, WorkspaceScopePayload } from "@/lib/types";
export function scopeWithAccessMode(
scope: WorkspaceScopePayload,
accessMode: WorkspaceAccessMode,
): WorkspaceScopePayload {
return {
...scope,
access_mode: accessMode,
restrict_to_workspace: accessMode === "restricted",
};
}
export function projectNameFromPath(path: string): string {
const normalized = path.replace(/\\/g, "/").replace(/\/+$/, "");
return normalized.split("/").filter(Boolean).pop() || path;
}
export function shortWorkspacePath(path: string): string {
const normalized = path.replace(/\\/g, "/");
const parts = normalized.split("/").filter(Boolean);
if (parts.length <= 3) return path;
return `.../${parts.slice(-3).join("/")}`;
}
export function isAbsoluteWorkspacePath(path: string): boolean {
const trimmed = path.trim();
return (
trimmed === "~"
|| trimmed.startsWith("~/")
|| trimmed.startsWith("~\\")
|| trimmed.startsWith("/")
|| /^[A-Za-z]:[\\/]/.test(trimmed)
);
}
export function selectedProjectScope(
scope: WorkspaceScopePayload | null,
defaultScope: WorkspaceScopePayload | null,
): WorkspaceScopePayload | null {
if (!scope || !defaultScope) return null;
return sameWorkspacePath(scope.project_path, defaultScope.project_path) ? null : scope;
}
export function normalizeWorkspacePath(path: string | null | undefined): string {
const normalized = (path ?? "").replace(/\\/g, "/").replace(/\/+$/, "");
return normalized || "/";
}
export function sameWorkspacePath(
a: string | null | undefined,
b: string | null | undefined,
): boolean {
if (!a || !b) return false;
return normalizeWorkspacePath(a) === normalizeWorkspacePath(b);
}