Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
315 lines
8.4 KiB
TypeScript
315 lines
8.4 KiB
TypeScript
import { normalizeWorkspaceFilePath as normalizeLogicalWorkspaceFilePath } from "@tutti-os/workspace-file-manager/services";
|
|
|
|
const URL_LIKE_LINK_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:|^#/;
|
|
const LOCAL_ASSET_ROOT = "/var/cache/tsh/local-assets";
|
|
|
|
export interface ResolveWorkspaceFilePathCandidateInput {
|
|
path: string;
|
|
workspaceRoot?: string | null;
|
|
basePath?: string | null;
|
|
}
|
|
|
|
export interface ResolvedWorkspaceFilePathCandidate {
|
|
path: string;
|
|
directoryPath: string;
|
|
workspaceRoot: string;
|
|
}
|
|
|
|
/**
|
|
* Pure file-path policy shared by transcript projection and host link actions.
|
|
* It intentionally has no Workspace mention, app, issue, registry, or renderer
|
|
* dependencies.
|
|
*/
|
|
export function resolveWorkspaceFilePathCandidate({
|
|
path,
|
|
workspaceRoot,
|
|
basePath
|
|
}: ResolveWorkspaceFilePathCandidateInput): ResolvedWorkspaceFilePathCandidate | null {
|
|
const rawPath = decodeWorkspaceLinkPath(path.trim());
|
|
if (
|
|
!rawPath ||
|
|
isUrlLikeWorkspaceFilePath(rawPath) ||
|
|
isUncWorkspaceFilePath(rawPath)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const selectedRoot = normalizeWorkspaceFilePath(workspaceRoot?.trim() ?? "");
|
|
const sessionRoot = normalizeWorkspaceFilePath(basePath?.trim() ?? "");
|
|
const root = selectedRoot || sessionRoot;
|
|
const normalizedPath = normalizeWorkspaceFilePath(
|
|
stripWorkspaceFileLineAnchor(rawPath),
|
|
sessionRoot || root
|
|
);
|
|
if (isUnsupportedSpecialWorkspaceFilePath(normalizedPath)) {
|
|
return null;
|
|
}
|
|
if (isStagedLocalAssetPath(normalizedPath)) {
|
|
return null;
|
|
}
|
|
if (isHomeRelativeWorkspaceFilePath(normalizedPath)) {
|
|
const directoryPath = dirnameForHomeRelativePath(normalizedPath);
|
|
return {
|
|
path: normalizedPath,
|
|
directoryPath,
|
|
workspaceRoot: selectedRoot || directoryPath
|
|
};
|
|
}
|
|
if (
|
|
isAbsoluteLocalPath(normalizedPath) &&
|
|
(isDirectAgentGeneratedMediaPath(normalizedPath) ||
|
|
isDirectWorkspaceAppDataPath(normalizedPath))
|
|
) {
|
|
const directoryPath = dirname(normalizedPath);
|
|
return {
|
|
path: normalizedPath,
|
|
directoryPath,
|
|
workspaceRoot: selectedRoot || directoryPath
|
|
};
|
|
}
|
|
|
|
if (!root) {
|
|
return null;
|
|
}
|
|
if (isAbsoluteLocalPath(normalizedPath)) {
|
|
const directoryPath = dirname(normalizedPath);
|
|
return {
|
|
path: normalizedPath,
|
|
directoryPath,
|
|
workspaceRoot: root
|
|
};
|
|
}
|
|
const base = normalizeWorkspaceFilePath(basePath?.trim() || root);
|
|
const resolvedPath = normalizeWorkspaceFilePath(`${base}/${normalizedPath}`);
|
|
if (
|
|
!isInsideOrEqualWorkspaceFilePath(resolvedPath, root) &&
|
|
!isDirectAgentGeneratedMediaPath(resolvedPath)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
path: resolvedPath,
|
|
directoryPath: resolvedPath === root ? root : dirname(resolvedPath),
|
|
workspaceRoot: root
|
|
};
|
|
}
|
|
|
|
export function normalizeWorkspaceFilePath(
|
|
path: string,
|
|
rootPath?: string | null
|
|
): string {
|
|
const normalizedPath = path.trim().replaceAll("\\", "/");
|
|
const normalizedRootPath = rootPath?.trim().replaceAll("\\", "/");
|
|
if (
|
|
isWindowsAbsolutePath(normalizedPath) ||
|
|
isGitBashWindowsAbsolutePath(normalizedPath, normalizedRootPath)
|
|
) {
|
|
return normalizeLogicalWorkspaceFilePath(
|
|
normalizedPath,
|
|
normalizedRootPath
|
|
);
|
|
}
|
|
|
|
const drive = /^[A-Za-z]:/.exec(normalizedPath)?.[0] ?? "";
|
|
const startsWithSlash = normalizedPath.startsWith("/");
|
|
const pathBody = drive
|
|
? normalizedPath.slice(drive.length)
|
|
: startsWithSlash
|
|
? normalizedPath.slice(1)
|
|
: normalizedPath;
|
|
const parts: string[] = [];
|
|
for (const part of pathBody.split("/")) {
|
|
if (!part || part === ".") {
|
|
continue;
|
|
}
|
|
if (part === "..") {
|
|
parts.pop();
|
|
continue;
|
|
}
|
|
parts.push(part);
|
|
}
|
|
if (drive) {
|
|
return parts.length > 0 ? `${drive}/${parts.join("/")}` : `${drive}/`;
|
|
}
|
|
if (startsWithSlash) {
|
|
return parts.length > 0 ? `/${parts.join("/")}` : "/";
|
|
}
|
|
return parts.join("/");
|
|
}
|
|
|
|
export function isUrlLikeWorkspaceFilePath(path: string): boolean {
|
|
if (path.startsWith("#")) {
|
|
return true;
|
|
}
|
|
if (isWindowsAbsolutePath(path.trim().replaceAll("\\", "/"))) {
|
|
return false;
|
|
}
|
|
return URL_LIKE_LINK_PATTERN.test(path);
|
|
}
|
|
|
|
export function decodeWorkspaceLinkPath(path: string): string {
|
|
if (!path.includes("%")) {
|
|
return path;
|
|
}
|
|
try {
|
|
return decodeURI(path);
|
|
} catch {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Agent Markdown source links use a terminal `:line` suffix. File launchers
|
|
* operate on filesystem paths, so remove that presentation-only location
|
|
* anchor before resolving the path.
|
|
*/
|
|
function stripWorkspaceFileLineAnchor(path: string): string {
|
|
return path.replace(/:[1-9]\d*$/, "");
|
|
}
|
|
|
|
export function workspaceFilePathBasename(path: string): string {
|
|
const normalized = path.trim().replaceAll("\\", "/");
|
|
return normalized.split("/").filter(Boolean).at(-1) ?? path;
|
|
}
|
|
|
|
export function isInsideOrEqualWorkspaceFilePath(
|
|
path: string,
|
|
root: string
|
|
): boolean {
|
|
if (root === "/") {
|
|
return path.startsWith("/");
|
|
}
|
|
const comparison =
|
|
isWindowsAbsolutePath(root) || isWindowsAbsolutePath(path)
|
|
? { path: path.toLowerCase(), root: root.toLowerCase() }
|
|
: { path, root };
|
|
return (
|
|
comparison.path === comparison.root ||
|
|
comparison.path.startsWith(`${comparison.root}/`)
|
|
);
|
|
}
|
|
|
|
export function isDirectAgentGeneratedMediaPath(path: string): boolean {
|
|
if (!isAbsoluteLocalPath(path)) {
|
|
return false;
|
|
}
|
|
const statePath = getTuttiStatePathSegments(path);
|
|
if (!statePath) {
|
|
return false;
|
|
}
|
|
if (
|
|
statePath[1] !== "agent" ||
|
|
statePath[2] !== "runs" ||
|
|
(!statePath.includes("generated_images") &&
|
|
!statePath.includes("generated_videos"))
|
|
) {
|
|
return false;
|
|
}
|
|
return /\.(?:png|jpe?g|gif|webp|bmp|mp4|webm)$/i.test(path);
|
|
}
|
|
|
|
function isAbsoluteLocalPath(path: string): boolean {
|
|
return path.startsWith("/") || isWindowsAbsolutePath(path);
|
|
}
|
|
|
|
function isHomeRelativeWorkspaceFilePath(path: string): boolean {
|
|
return path === "~" || path.startsWith("~/");
|
|
}
|
|
|
|
function dirnameForHomeRelativePath(path: string): string {
|
|
return path === "~" ? "~" : dirname(path);
|
|
}
|
|
|
|
function isWindowsAbsolutePath(path: string): boolean {
|
|
return /^\/?[A-Za-z]:\//.test(path);
|
|
}
|
|
|
|
function isGitBashWindowsAbsolutePath(
|
|
path: string,
|
|
rootPath?: string | null
|
|
): boolean {
|
|
const rootDrive = readWindowsDrive(rootPath ?? "");
|
|
const pathDrive = /^\/([A-Za-z])(?=\/|$)/.exec(path)?.[1];
|
|
return Boolean(
|
|
rootDrive && pathDrive && `${pathDrive}:`.toUpperCase() === rootDrive
|
|
);
|
|
}
|
|
|
|
function readWindowsDrive(path: string): string {
|
|
return /^\/?([A-Za-z]:)(?=\/|$)/.exec(path)?.[1]?.toUpperCase() ?? "";
|
|
}
|
|
|
|
function isUncWorkspaceFilePath(path: string): boolean {
|
|
return /^(?:\\\\|\/\/)[^/\\]+[/\\][^/\\]+/.test(path);
|
|
}
|
|
|
|
function isUnsupportedSpecialWorkspaceFilePath(path: string): boolean {
|
|
const comparisonPath = cleanWorkspaceFilePathForComparison(path);
|
|
return (
|
|
comparisonPath === "/dev/null" ||
|
|
comparisonPath.split("/").some((segment) => {
|
|
const normalized = segment.trim().replace(/[. ]+$/g, "");
|
|
const deviceName = normalized.split(".", 1)[0]?.toUpperCase();
|
|
return deviceName === "NUL";
|
|
})
|
|
);
|
|
}
|
|
|
|
function cleanWorkspaceFilePathForComparison(path: string): string {
|
|
const normalized = path.replace(/\/+/g, "/");
|
|
const parts: string[] = [];
|
|
for (const part of normalized.split("/")) {
|
|
if (!part || part === ".") {
|
|
continue;
|
|
}
|
|
if (part === "..") {
|
|
parts.pop();
|
|
continue;
|
|
}
|
|
parts.push(part);
|
|
}
|
|
return normalized.startsWith("/") ? `/${parts.join("/")}` : parts.join("/");
|
|
}
|
|
|
|
function dirname(path: string): string {
|
|
const index = path.lastIndexOf("/");
|
|
if (index <= 0) {
|
|
return "/";
|
|
}
|
|
return path.slice(0, index);
|
|
}
|
|
|
|
function isStagedLocalAssetPath(path: string): boolean {
|
|
return (
|
|
path !== LOCAL_ASSET_ROOT &&
|
|
isInsideOrEqualWorkspaceFilePath(path, LOCAL_ASSET_ROOT)
|
|
);
|
|
}
|
|
|
|
function isDirectWorkspaceAppDataPath(path: string): boolean {
|
|
if (!isAbsoluteLocalPath(path)) {
|
|
return false;
|
|
}
|
|
const statePath = getTuttiStatePathSegments(path);
|
|
if (!statePath) {
|
|
return false;
|
|
}
|
|
return (
|
|
statePath[1] === "apps" &&
|
|
statePath[2] === "workspaces" &&
|
|
statePath.length > 5
|
|
);
|
|
}
|
|
|
|
function getTuttiStatePathSegments(path: string): string[] | null {
|
|
const segments = path.split("/").filter(Boolean);
|
|
const stateRootIndex = segments.findIndex(
|
|
(segment) => segment === ".tutti" || segment === ".tutti-dev"
|
|
);
|
|
if (stateRootIndex < 0) {
|
|
return null;
|
|
}
|
|
return segments.slice(stateRootIndex);
|
|
}
|