Files
agent-desktop/console/src/utils/navigationMode.ts
T
Pine ae3ab5feb5 阶段5: 完整引入上游 OS Shell + Hub + Mailbox
- os/ 67 文件(DesktopOS/Dock/Launcher/MenuBar/MissionControl/NotificationCenter/AppStore/Window 系统 + os*Store/hooks)
- pages/Hub + api/modules/hub.ts + ImportHubModal(已有)
- Inbox 采用上游 Mailbox(approvals/messages 双 Tab + MailAccessControlDrawer/useMailPendingCount/mailDetail),保留云超服 Harvest 组件
- App.tsx 引入 OS Shell 分支(isOsPath→DesktopOSPage, Router 外)+ /hub/admin 路由 + BackendModeRouter + RuntimeAvailabilityGuard,保留云超服 AuthGuard/chatSocket/OPC 主题/vi-th-my-lo
- 双改合并:usePluginLoader(loadPawApp)/pluginMarket(isMarketPluginApp)/registry store(removeBySource)/hostExternals(removePlugin)/LanguageSwitcher(persistRemotely+ja-ru-pt-id)/BackendLoadingPage(override props)/ApprovalCard(reasoning)/auth.ts(mode)/auth/gate.ts
- SidebarSettingsPanel 加 Desktop Mode 入口(OS Shell 切换)
- locales 合并 os/hub/projectDirectory/inbox/account(zh/en/vi 用上游,lo/my/th 用 en 占位)
- 验证:tsc 0 错误、build 通过(1m23s)、阶段5 新增 35 文件 271 测试全过;全量回归 30 失败均预存在(stash 验证)
2026-09-04 10:36:41 +08:00

98 lines
2.9 KiB
TypeScript

interface LocationLike {
pathname: string;
search?: string;
hash?: string;
}
const CONSOLE_BASENAME = "/console";
const OS_PAW_APP_STATE_KEY = "osPawAppId";
function historyStateRecord(state: unknown): Record<string, unknown> {
if (!state || typeof state !== "object" || Array.isArray(state)) return {};
return state as Record<string, unknown>;
}
function pathnameOnly(path: string): string {
return path.split(/[?#]/, 1)[0] || "/";
}
export function getRouterBasename(pathname: string): string | undefined {
return /^\/console(?:\/|$)/.test(pathname) ? CONSOLE_BASENAME : undefined;
}
export function stripRouterBasename(pathname: string): string {
const basename = getRouterBasename(pathname);
if (!basename) return pathname || "/";
return pathname.slice(basename.length) || "/";
}
export function isOsPath(path: string): boolean {
const pathname = stripRouterBasename(pathnameOnly(path));
// Descendants still enter the shell so it can canonicalize them to /os.
return pathname === "/os" || pathname.startsWith("/os/");
}
export function isLoginPath(pathname: string): boolean {
return stripRouterBasename(pathname) === "/login";
}
export function getAppRelativeLocation(location: LocationLike): string {
const pathname = stripRouterBasename(location.pathname);
return `${pathname}${location.search ?? ""}${location.hash ?? ""}`;
}
export function getLoginPath(location: LocationLike): string {
const redirect = encodeURIComponent(getAppRelativeLocation(location));
return `/login?redirect=${redirect}`;
}
export function getLoginHref(location: LocationLike): string {
const basename = getRouterBasename(location.pathname) ?? "";
return `${basename}${getLoginPath(location)}`;
}
export function addRouterBasename(
currentPathname: string,
appRelativePath: string,
): string {
const basename = getRouterBasename(currentPathname) ?? "";
return `${basename}${appRelativePath}`;
}
export function getPostLoginHref(
currentPathname: string,
redirect: string,
): string | null {
if (!isOsPath(redirect)) return null;
return getOsRootHref(currentPathname);
}
export function getOsRootHref(currentPathname: string): string {
return addRouterBasename(currentPathname, "/os");
}
export function getOsPawAppIdFromHistoryState(
state: unknown,
): string | undefined {
const appId = historyStateRecord(state)[OS_PAW_APP_STATE_KEY];
return typeof appId === "string" && appId ? appId : undefined;
}
export function withOsPawAppHistoryState(
state: unknown,
appId: string | null,
): Record<string, unknown> {
const nextState = { ...historyStateRecord(state) };
if (appId) {
nextState[OS_PAW_APP_STATE_KEY] = appId;
} else {
delete nextState[OS_PAW_APP_STATE_KEY];
}
return nextState;
}
/** Build the classic console entry URL while preserving an optional basename. */
export function getConsoleRootHref(currentPathname: string): string {
return addRouterBasename(currentPathname, "/chat");
}