* Get all workspace metadata by loading config and metadata files. * * Returns FrontendWorkspaceMetadata with paths already computed. * This eliminates the need for separate "enrichment" - paths are computed * once during the loop when we already have all the necessary data. * * NE
()
| 1672 | * saved to config for subsequent loads. |
| 1673 | */ |
| 1674 | async getAllWorkspaceMetadata(): Promise<FrontendWorkspaceMetadata[]> { |
| 1675 | const config = this.loadConfigOrDefault(); |
| 1676 | const workspaceMetadata: FrontendWorkspaceMetadata[] = []; |
| 1677 | let configModified = false; |
| 1678 | |
| 1679 | for (const [projectPath, projectConfig] of config.projects) { |
| 1680 | // Validate project path is not empty (defensive check for corrupted config) |
| 1681 | if (!projectPath) { |
| 1682 | log.warn("Skipping project with empty path in config", { |
| 1683 | workspaceCount: projectConfig.workspaces?.length ?? 0, |
| 1684 | }); |
| 1685 | continue; |
| 1686 | } |
| 1687 | |
| 1688 | const projectName = this.getProjectName(projectPath); |
| 1689 | |
| 1690 | for (const workspace of projectConfig.workspaces) { |
| 1691 | // Extract workspace basename from path (could be stable ID or legacy name) |
| 1692 | const workspaceBasename = |
| 1693 | workspace.path.split("/").pop() ?? workspace.path.split("\\").pop() ?? "unknown"; |
| 1694 | |
| 1695 | const workspaceProjects = workspace.projects?.length ? workspace.projects : undefined; |
| 1696 | const primaryWorkspaceProject = workspaceProjects?.[0]; |
| 1697 | const resolvedProjectPath = primaryWorkspaceProject?.projectPath ?? projectPath; |
| 1698 | const resolvedProjectName = workspaceProjects |
| 1699 | ? workspaceProjects.map((projectRef) => projectRef.projectName).join("+") |
| 1700 | : projectName; |
| 1701 | |
| 1702 | try { |
| 1703 | // NEW FORMAT: If workspace has metadata in config, use it directly |
| 1704 | if (workspace.id && workspace.name) { |
| 1705 | const metadata: WorkspaceMetadata = { |
| 1706 | id: workspace.id, |
| 1707 | name: workspace.name, |
| 1708 | title: workspace.title, |
| 1709 | pendingAutoTitle: workspace.pendingAutoTitle, |
| 1710 | forkFamilyBaseName: workspace.forkFamilyBaseName, |
| 1711 | projectName: resolvedProjectName, |
| 1712 | projectPath: resolvedProjectPath, |
| 1713 | // GUARANTEE: All workspaces must have createdAt (assign now if missing) |
| 1714 | createdAt: workspace.createdAt ?? new Date().toISOString(), |
| 1715 | // GUARANTEE: All workspaces must have runtimeConfig (apply default if missing) |
| 1716 | runtimeConfig: workspace.runtimeConfig ?? DEFAULT_RUNTIME_CONFIG, |
| 1717 | aiSettings: workspace.aiSettings, |
| 1718 | heartbeat: normalizeWorkspaceMetadataHeartbeat(workspace.heartbeat, config), |
| 1719 | goalDefaults: workspace.goalDefaults, |
| 1720 | aiSettingsByAgent: |
| 1721 | workspace.aiSettingsByAgent ?? |
| 1722 | (workspace.aiSettings |
| 1723 | ? { |
| 1724 | plan: workspace.aiSettings, |
| 1725 | exec: workspace.aiSettings, |
| 1726 | } |
| 1727 | : undefined), |
| 1728 | parentWorkspaceId: workspace.parentWorkspaceId, |
| 1729 | agentType: workspace.agentType, |
| 1730 | agentId: workspace.agentId, |
| 1731 | tags: workspace.tags, |