diff --git a/web/hwlab-cloud-web/scripts/tsc-check.ts b/web/hwlab-cloud-web/scripts/tsc-check.ts index a178b143..bf2c69ec 100644 --- a/web/hwlab-cloud-web/scripts/tsc-check.ts +++ b/web/hwlab-cloud-web/scripts/tsc-check.ts @@ -25,7 +25,11 @@ if (explicitAnyCount > 0) { process.exit(2); } -const vueTscBin = path.join(rootDir, "node_modules/.bin/vue-tsc"); +const vueTscBin = resolveVueTscBin(rootDir); +if (!vueTscBin) { + console.error("hwlab-cloud-web tsc-check: vue-tsc not found in ancestor node_modules/.bin or PATH"); + process.exit(2); +} const result = spawnSync(vueTscBin, ["--noEmit", "--project", path.join(rootDir, "tsconfig.json")], { cwd: rootDir, encoding: "utf8" }); if (result.stdout) process.stdout.write(result.stdout); if (result.stderr) process.stderr.write(result.stderr); @@ -36,6 +40,30 @@ if (result.status !== 0) { } console.log("hwlab-cloud-web tsc-check: passed (strict Vue TS, 0 explicit any)"); +function resolveVueTscBin(startDir: string): string | null { + let current = startDir; + while (true) { + const candidate = path.join(current, "node_modules/.bin/vue-tsc"); + if (isRunnableFile(candidate)) return candidate; + const parent = path.dirname(current); + if (parent === current) break; + current = parent; + } + for (const dir of (process.env.PATH ?? "").split(path.delimiter).filter(Boolean)) { + const candidate = path.join(dir, process.platform === "win32" ? "vue-tsc.cmd" : "vue-tsc"); + if (isRunnableFile(candidate)) return candidate; + } + return null; +} + +function isRunnableFile(candidate: string): boolean { + try { + return statSync(candidate).isFile(); + } catch { + return false; + } +} + function collectSourceFiles(dir: string): string[] { const out: string[] = []; for (const entry of readdirSync(dir)) { diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index 7d8a2895..ea5f811b 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -13,6 +13,10 @@ const TRACE_HYDRATION_MAX_PAGES = 60; const TRACE_HYDRATION_MAX_ATTEMPTS = 3; const TRACE_HYDRATION_RETRY_DELAY_MS = 700; +interface HydrateOptions { + conversationId?: string | null; +} + export const useWorkbenchStore = defineStore("workbench", () => { const projectId = ref(resolveInitialWorkbenchProjectId()); const workspace = ref(window.HWLAB_CLOUD_WEB_WORKSPACE_BOOTSTRAP?.workspace ?? null); @@ -52,7 +56,7 @@ export const useWorkbenchStore = defineStore("workbench", () => { activityRef.value = { lastActivityAt: now, lastActivityIso: new Date(now).toISOString(), waitingFor: "code-agent", lastEventLabel: label }; } - async function hydrate(): Promise { + async function hydrate(options: HydrateOptions = {}): Promise { const requestEpoch = workspaceSelectionEpoch.value; const previousConversationId = activeConversationId.value; conversationsReady.value = conversations.value.length > 0; @@ -67,7 +71,8 @@ export const useWorkbenchStore = defineStore("workbench", () => { const nextWorkspace = workspaceResult.data?.workspace ?? null; const nextProjectId = workspaceProjectId(nextWorkspace, projectId.value); projectId.value = nextProjectId; - const selectedConversationId = selectedConversationIdFromWorkspace(nextWorkspace); + const routeConversationId = normalizeWorkbenchConversationId(options.conversationId); + const selectedConversationId = routeConversationId ?? selectedConversationIdFromWorkspace(nextWorkspace); const [conversationsResult, selectedConversationResult] = await Promise.all([ api.workbench.conversations(nextProjectId, { includeConversationId: selectedConversationId }), selectedConversationId ? api.workbench.conversation(selectedConversationId, { projectId: nextProjectId }) : Promise.resolve(null) @@ -89,7 +94,11 @@ export const useWorkbenchStore = defineStore("workbench", () => { error.value = conversations.value.length > 0 ? null : conversationsResult.error ?? "session list unavailable"; } if (selectedConversationId && selectedConversationResult && !selectedConversationResult.ok) error.value = selectedConversationResult.error ?? "conversation unavailable"; - const workspaceSnapshot = selectedConversation ? workspaceWithSelectedConversation(nextWorkspace, selectedConversation, conversationProjectId(selectedConversation, nextProjectId)) : nextWorkspace; + const workspaceSnapshot = selectedConversation + ? workspaceWithSelectedConversation(nextWorkspace, selectedConversation, conversationProjectId(selectedConversation, nextProjectId)) + : routeConversationId + ? workspaceWithSelectedConversationId(nextWorkspace, routeConversationId, nextProjectId) + : nextWorkspace; if (shouldApplyWorkspaceSnapshot({ requestEpoch, currentEpoch: workspaceSelectionEpoch.value, currentConversationId: activeConversationId.value, workspace: workspaceSnapshot })) { workspace.value = workspaceSnapshot; providerProfile.value = firstNonEmptyString(workspace.value?.providerProfile, workspace.value?.workspace?.providerProfile, providerProfile.value) ?? providerProfile.value; @@ -733,6 +742,22 @@ function workspaceWithSelectedConversation(current: WorkspaceRecord | null, conv }; } +function workspaceWithSelectedConversationId(current: WorkspaceRecord | null, conversationId: string, projectId: string): WorkspaceRecord | null { + if (!current) return current; + const selectedConversation = current.selectedConversation?.conversationId === conversationId ? current.selectedConversation : undefined; + return { + ...current, + projectId, + selectedConversationId: conversationId, + selectedConversation, + workspace: { + ...(current.workspace ?? {}), + projectId, + selectedConversationId: conversationId + } + }; +} + function conversationProjectId(conversation: ConversationRecord | null | undefined, fallback: string): string { return firstNonEmptyString(conversation?.projectId, fallback) ?? DEFAULT_WORKBENCH_PROJECT_ID; } diff --git a/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue b/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue index 1b9cd600..051fc137 100644 --- a/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue +++ b/web/hwlab-cloud-web/src/views/workbench/CodeWorkbenchView.vue @@ -21,7 +21,7 @@ onMounted(async () => { componentActive.value = true; applyingRouteConversation.value = Boolean(routeConversationId.value); try { - await workbench.hydrate(); + await workbench.hydrate({ conversationId: routeConversationId.value }); await applyRouteConversation(); } finally { applyingRouteConversation.value = false;