import type { ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { CatalogItem, DirectResourceSummary, DirectResourceKind, JsonObject } from "../types.js"; import { dirname } from "node:path"; import { deriveId, findCatalogItem, findCatalogItemForSource, loadCatalog, normalizeSourceForLibrary, parseCatalog, addSourcesToCatalog } from "../catalog.js"; import { describeJsonReadIssue, isObject, readJson, writeJson } from "../json.js"; import { getPaths } from "../paths.js"; import { collectProjectInventory, type ProjectInventory } from "../project-inventory.js"; import { matchingPiProjectOverride, parseProjectConstruct, uniqueManagedIdInConstruct, upsertConstructItem } from "../project-settings.js"; import { rememberKnownProject } from "../projects.js"; import { packageSourceMatchValues } from "../sources.js"; import { TrustRefusedError, targetTrustDecision, type TargetTrustContext } from "../target-trust.js"; import { pickCheckboxes, showSummary, showText, waitForIdleBeforeConstructWrite, type CheckboxPickerItem } from "../ui.js"; interface LoadCandidate { kind: "package"; id: string; source: string; alreadyKnown?: boolean; disabledByFilters?: boolean; } interface DirectLoadCandidate { kind: DirectResourceKind; id: string; path: string; displayPath: string; resource: DirectResourceSummary; } type AnyLoadCandidate = LoadCandidate | DirectLoadCandidate; interface LoadArgs { queries: string[]; } function constructManagedPackageStates(inventory: ProjectInventory): Map { const sources = new Map(); for (const item of inventory.managedPackages) { for (const source of item.matchSources) sources.set(source, item.metadata.enabled); } return sources; } async function projectLoadCandidates(inventory: ProjectInventory): Promise<{ adoptable: LoadCandidate[]; alreadyManaged: LoadCandidate[] }> { const { paths } = inventory; const managedPackageStates = constructManagedPackageStates(inventory); const settingsDir = dirname(paths.projectSettingsPath); const catalogItemsBySource = new Map(); for (const item of inventory.catalog.data.items) { for (const match of await packageSourceMatchValues(item.source, settingsDir)) catalogItemsBySource.set(match, item.id); } const seen = new Set(); const adoptable: LoadCandidate[] = []; const alreadyManaged: LoadCandidate[] = []; for (const pkg of inventory.packageDeclarations) { if (pkg.form === "invalid" || !pkg.enabled || !pkg.source.trim()) continue; if (pkg.projectOverride) continue; const source = await normalizeSourceForLibrary(pkg.source, settingsDir); const matches = await packageSourceMatchValues(pkg.source, settingsDir); const seenKey = matches.at(-1) ?? source; if (seen.has(seenKey)) continue; seen.add(seenKey); const catalogId = matches.map((match) => catalogItemsBySource.get(match)).find((id): id is string => id !== undefined); const candidate: LoadCandidate = { kind: "package", id: catalogId ?? deriveId(source), source, alreadyKnown: catalogId !== undefined, disabledByFilters: pkg.disabledByFilters }; const managedMatch = matches.map((match) => managedPackageStates.get(match)).find((state) => state !== undefined); const managedKnown = managedMatch !== undefined || matches.some((match) => managedPackageStates.has(match)); if (managedMatch === false && !pkg.disabledByFilters) adoptable.push(candidate); else if (managedKnown) alreadyManaged.push(candidate); else adoptable.push(candidate); } return { adoptable: adoptable.sort((a, b) => a.id.localeCompare(b.id)), alreadyManaged: alreadyManaged.sort((a, b) => a.id.localeCompare(b.id)), }; } function projectDirectLoadCandidates(inventory: ProjectInventory): { adoptable: DirectLoadCandidate[]; alreadyManaged: DirectLoadCandidate[]; warnings: string[] } { const adoptable: DirectLoadCandidate[] = []; const alreadyManaged: DirectLoadCandidate[] = []; for (const resource of inventory.directResources.resources) { const candidate: DirectLoadCandidate = { kind: resource.kind, id: `${resource.kind}:${resource.name}`, path: resource.path, displayPath: resource.displayPath, resource, }; if (resource.managed) alreadyManaged.push(candidate); else adoptable.push(candidate); } return { adoptable: adoptable.sort((a, b) => a.id.localeCompare(b.id) || a.displayPath.localeCompare(b.displayPath)), alreadyManaged: alreadyManaged.sort((a, b) => a.id.localeCompare(b.id) || a.displayPath.localeCompare(b.displayPath)), warnings: inventory.directResources.warnings, }; } async function collectLoadCandidates(ctx: Pick): Promise<{ paths: Awaited>; adoptable: AnyLoadCandidate[]; alreadyManaged: AnyLoadCandidate[]; directWarnings: string[] }> { const inventory = await collectProjectInventory(ctx); const packageCandidates = await projectLoadCandidates(inventory); const directCandidates = projectDirectLoadCandidates(inventory); return { paths: inventory.paths, adoptable: [...packageCandidates.adoptable, ...directCandidates.adoptable], alreadyManaged: [...packageCandidates.alreadyManaged, ...directCandidates.alreadyManaged], directWarnings: directCandidates.warnings, }; } function parseLoadArgs(args: string): LoadArgs { return { queries: args.split(/\s+/).filter(Boolean) }; } async function candidateMatchesQuery(paths: Awaited>, candidate: AnyLoadCandidate, query: string): Promise { if (candidate.id === query) return true; if (candidate.kind !== "package") { return candidate.path === query || candidate.displayPath === query || candidate.resource.name === query || `${candidate.kind}:${candidate.resource.name}` === query; } if (candidate.source === query) return true; const settingsDir = dirname(paths.projectSettingsPath); const queryMatches = new Set([...(await packageSourceMatchValues(query, settingsDir)), ...(await packageSourceMatchValues(query, paths.cwd))]); const candidateMatches = await packageSourceMatchValues(candidate.source, settingsDir); return candidateMatches.some((match) => queryMatches.has(match)); } function candidateKey(candidate: AnyLoadCandidate): string { return candidate.kind === "package" ? `package:${candidate.source}` : `${candidate.kind}:${candidate.path}`; } function candidateValue(candidate: AnyLoadCandidate): string { return candidate.kind === "package" ? candidate.source : candidate.displayPath; } function candidateDescription(candidate: AnyLoadCandidate): string | undefined { if (candidate.kind === "package") { return candidate.alreadyKnown ? "Already in the Construct library; load only arms project metadata." : "Already declared in this project; load adopts it into Construct metadata and will not install or enable it."; } return candidate.resource.enabled ? `Project ${candidate.kind} discovered by Pi; load will adopt it into project Construct metadata only.` : `Project ${candidate.kind} is disabled by Pi filters; load will preserve that disabled state in Construct metadata.`; } async function findLoadCandidates( paths: Awaited>, candidates: { adoptable: AnyLoadCandidate[]; alreadyManaged: AnyLoadCandidate[] }, queries: string[], ): Promise<{ selected: AnyLoadCandidate[]; alreadyManaged: string[]; missing: string[] }> { const selected = new Map(); const alreadyManaged: string[] = []; const missing: string[] = []; for (const query of queries) { const adoptableMatches: AnyLoadCandidate[] = []; for (const candidate of candidates.adoptable) { if (await candidateMatchesQuery(paths, candidate, query)) adoptableMatches.push(candidate); } if (adoptableMatches.length > 0) { for (const candidate of adoptableMatches) selected.set(candidateKey(candidate), candidate); continue; } let managedMatch = false; for (const candidate of candidates.alreadyManaged) { if (await candidateMatchesQuery(paths, candidate, query)) { managedMatch = true; break; } } if (managedMatch) alreadyManaged.push(query); else missing.push(query); } return { selected: [...selected.values()], alreadyManaged, missing }; } export interface ConstructLoadResult { added: CatalogItem[]; alreadyKnown: number; warnings: string[]; metadataChanged: number; selectedSources: number; directMetadataChanged?: number; refused?: "untrusted" | "unknown"; } export async function loadSourcesIntoConstruct( ctx: Pick, paths: Awaited>, selectedSources: string[], options: { enabledBySource?: Map; prewrite?: () => Promise } = {}, ): Promise { const added: CatalogItem[] = []; let alreadyKnown = 0; const warnings: string[] = []; let refused: "untrusted" | "unknown" | undefined; let result: { added: CatalogItem[]; alreadyKnown: number; warnings: string[] }; try { result = await addSourcesToCatalog(ctx, selectedSources, options.prewrite); } catch (error) { if (error instanceof TrustRefusedError) { return { added: [], alreadyKnown: 0, warnings: [error.message], metadataChanged: 0, selectedSources: selectedSources.length, refused: error.reason }; } throw error; } added.push(...result.added); alreadyKnown += result.alreadyKnown; warnings.push(...result.warnings); const addedBySource = new Map(added.map((item) => [item.source, item])); let metadataChanged = 0; { const { catalog } = await loadCatalog(ctx); try { const constructRead = await readJson(paths.projectConstructPath); let construct = parseProjectConstruct(constructRead); let nextMetadataChanged = 0; for (const source of selectedSources) { const item = addedBySource.get(source) ?? (await findCatalogItemForSource(catalog.items, source, dirname(paths.projectSettingsPath))) ?? findCatalogItem(catalog.items, source); const itemId = await uniqueManagedIdInConstruct(construct, item?.id ?? deriveId(source), source, source, paths); const enabled = options.enabledBySource?.get(source); construct = upsertConstructItem(construct, itemId, source, source, paths, { enabled }); nextMetadataChanged += 1; } await options.prewrite?.(); await writeJson(paths.projectConstructPath, construct); metadataChanged = nextMetadataChanged; } catch (error) { if (error instanceof TrustRefusedError) refused = error.reason; const message = error instanceof Error ? error.message : String(error); warnings.push(`Could not update project Construct metadata: ${message}`); } } // Once a trust refusal is latched, later writes (known-project index) must not resume. if (!refused) { try { const remembered = await rememberKnownProject(ctx, options.prewrite); if (remembered.warning) warnings.push(remembered.warning); } catch (error) { if (error instanceof TrustRefusedError) refused = error.reason; else throw error; } } return { added, alreadyKnown, warnings, metadataChanged, selectedSources: selectedSources.length, ...(refused ? { refused } : {}) }; } function directBaseId(resource: DirectResourceSummary): string { return deriveId(`${resource.kind}-${resource.name}`); } function sameDirectResource(value: unknown, resource: DirectResourceSummary): boolean { return isObject(value) && value.kind === resource.kind && (value.path === resource.displayPath || value.path === resource.path); } function uniqueDirectManagedIdInConstruct(construct: JsonObject, resource: DirectResourceSummary): string { const items = isObject(construct.items) ? construct.items : {}; for (const [id, value] of Object.entries(items)) { if (sameDirectResource(value, resource)) return id; } const baseId = directBaseId(resource); const existing = new Set(Object.keys(items)); if (!existing.has(baseId)) return baseId; for (let i = 2; i < 1000; i++) { const candidate = `${baseId}-${i}`; if (!existing.has(candidate)) return candidate; } return `${baseId}-${Date.now()}`; } function projectRelativeBaseDir(paths: Awaited>, resource: DirectResourceSummary): string | undefined { if (!resource.baseDir) return undefined; const normalizedProject = paths.cwd.replace(/\/+$/, ""); if (resource.baseDir === normalizedProject) return "."; if (resource.baseDir.startsWith(`${normalizedProject}/`)) return resource.baseDir.slice(normalizedProject.length + 1); return resource.baseDir; } function upsertConstructDirectResource(construct: JsonObject, resource: DirectResourceSummary, paths: Awaited>): JsonObject { const existingItems = isObject(construct.items) ? construct.items : {}; const id = uniqueDirectManagedIdInConstruct(construct, resource); const existingItem = isObject(existingItems[id]) ? existingItems[id] : {}; const now = new Date().toISOString(); return { ...construct, version: 1, managedBy: "the-construct", loadedAt: typeof construct.loadedAt === "string" ? construct.loadedAt : now, targetCwd: paths.realCwd, items: { ...existingItems, [id]: { ...existingItem, kind: resource.kind, path: resource.displayPath, ...(resource.settingsPath ? { settingsPath: resource.settingsPath } : {}), ...(resource.baseDir ? { baseDir: projectRelativeBaseDir(paths, resource) } : {}), scope: resource.scope, origin: resource.origin, source: resource.source, enabled: resource.enabled, loadedAt: typeof existingItem.loadedAt === "string" ? existingItem.loadedAt : now, updatedAt: now, }, }, }; } export async function loadDirectResourcesIntoConstruct( ctx: Pick, paths: Awaited>, resources: DirectResourceSummary[], prewrite?: () => Promise, ): Promise<{ metadataChanged: number; warnings: string[]; refused?: "untrusted" | "unknown" }> { if (resources.length === 0) return { metadataChanged: 0, warnings: [] }; const warnings: string[] = []; let metadataChanged = 0; let refused: "untrusted" | "unknown" | undefined; try { const constructRead = await readJson(paths.projectConstructPath); let construct = parseProjectConstruct(constructRead); let nextMetadataChanged = 0; for (const resource of resources) { construct = upsertConstructDirectResource(construct, resource, paths); nextMetadataChanged += 1; } // Count only after the write actually succeeds so a refused or failed write // never reports unwritten resources as adopted. await prewrite?.(); await writeJson(paths.projectConstructPath, construct); metadataChanged = nextMetadataChanged; } catch (error) { if (error instanceof TrustRefusedError) refused = error.reason; warnings.push(`Could not update project Construct metadata for direct resources: ${error instanceof Error ? error.message : String(error)}`); } // Once a trust refusal is latched, the known-project index write must not resume. if (!refused) { try { const remembered = await rememberKnownProject(ctx, prewrite); if (remembered.warning) warnings.push(remembered.warning); } catch (error) { if (error instanceof TrustRefusedError) refused = error.reason; else throw error; } } return { metadataChanged, warnings, ...(refused ? { refused } : {}) }; } export interface ConstructLoadTrust { // Current-project callers may pass the live context; the target's own saved trust is used // for any other directory. Callers that pass no context still get native target checking. ctx?: TargetTrustContext; signal?: AbortSignal; } export async function loadProjectResourcesIntoConstruct( projectDir: string, queries: string[], trust: ConstructLoadTrust, ): Promise { const paths = await getPaths({ cwd: projectDir }); const initialTrust = await targetTrustDecision(trust.ctx, projectDir); if (initialTrust !== "trusted") { return { added: [], alreadyKnown: 0, warnings: [ initialTrust === "unknown" ? `Skipped because Pi trust state for ${projectDir} could not be read.` : `Skipped because ${projectDir} is not trusted by Pi.`, ], metadataChanged: 0, selectedSources: 0, directMetadataChanged: 0, refused: initialTrust, }; } // Forward the verified native decision (never an unconditional trust assumption) into the // resolver predicate used for inspection, and recheck freshness before every write. const projectTrusted = initialTrust === "trusted"; const projectCtx = { cwd: projectDir, isProjectTrusted: () => projectTrusted }; const refusal: { reason?: "untrusted" | "unknown" } = {}; const prewrite = async () => { if (trust.signal?.aborted) throw new Error("Construct load cancelled before write."); const decision = await targetTrustDecision(trust.ctx, projectDir); // Re-check cancellation after the native trust await, before any actual write. if (trust.signal?.aborted) throw new Error("Construct load cancelled before write."); if (decision !== "trusted") { refusal.reason = decision; throw new TrustRefusedError(projectDir, decision); } }; const settingsRead = await readJson(paths.projectSettingsPath); if (settingsRead.state === "invalid") throw new Error(`Cannot load because ${describeJsonReadIssue(".pi/settings.json", settingsRead)}`); if (settingsRead.state === "ok" && !isObject(settingsRead.data)) throw new Error("Cannot load because .pi/settings.json is not a JSON object."); const constructRead = await readJson(paths.projectConstructPath); if (constructRead.state === "invalid") throw new Error(`Cannot load because ${describeJsonReadIssue(".pi/construct.json", constructRead)}`); if (constructRead.state === "ok" && !isObject(constructRead.data)) throw new Error("Cannot load because .pi/construct.json is not a JSON object."); const catalogRead = await readJson(paths.userCatalogPath); if (catalogRead.state === "invalid") throw new Error(`Cannot load because ${describeJsonReadIssue("Construct library catalog", catalogRead)}`); const catalogCheck = parseCatalog(catalogRead); if (catalogRead.state === "ok" && catalogCheck.warnings.length > 0) { throw new Error([`Cannot load because Construct library catalog has structural warnings. Fix ${paths.userCatalogPath} first.`, ...catalogCheck.warnings].join("\n")); } const candidates = await collectLoadCandidates(projectCtx); const found = await findLoadCandidates(paths, candidates, queries); const selectionWarnings = [ ...candidates.directWarnings, ...found.alreadyManaged.map((query) => `Already Construct-managed here: ${query}`), ...found.missing.map((query) => `Not an unloaded project resource: ${query}`), ]; const selectedPackageCandidates = found.selected.filter((candidate): candidate is LoadCandidate => candidate.kind === "package"); const selectedDirectCandidates = found.selected.filter((candidate): candidate is DirectLoadCandidate => candidate.kind !== "package"); const selectedSources = selectedPackageCandidates.map((candidate) => candidate.source); const enabledBySource = new Map(selectedPackageCandidates.map((candidate) => [candidate.source, !candidate.disabledByFilters])); const result: ConstructLoadResult = selectedSources.length > 0 ? await loadSourcesIntoConstruct({ cwd: projectDir }, paths, selectedSources, { enabledBySource, prewrite }) : { added: [], alreadyKnown: 0, warnings: [], metadataChanged: 0, selectedSources: 0 }; result.warnings.push(...selectionWarnings); if (result.refused) { // Latched refusal: later direct adoption and known-project writes must not resume. result.directMetadataChanged = 0; return result; } const directResult = await loadDirectResourcesIntoConstruct( { cwd: projectDir }, paths, selectedDirectCandidates.map((candidate) => candidate.resource), prewrite, ); result.directMetadataChanged = directResult.metadataChanged; result.warnings.push(...directResult.warnings); result.refused = directResult.refused ?? refusal.reason; return result; } export function formatLoadResult(result: ConstructLoadResult): string { const skipped = Math.max(0, result.selectedSources - result.added.length); return [ "Construct load complete.", `Added to Construct: ${result.added.length}`, `Already known: ${Math.max(skipped, result.alreadyKnown)}`, `Errors: ${result.warnings.length}`, result.metadataChanged > 0 ? `Project items armed: ${result.metadataChanged}` : undefined, result.directMetadataChanged && result.directMetadataChanged > 0 ? `Direct project resources adopted: ${result.directMetadataChanged}` : undefined, ...result.warnings.map((warning) => `! ${warning}`), "No /reload needed; load only updates the Construct library and project metadata.", ] .filter((line): line is string => line !== undefined) .join("\n"); } export async function handleLoad(args: string, ctx: ExtensionCommandContext): Promise { const loadArgs = parseLoadArgs(args); const paths = await getPaths(ctx); if (!ctx.isProjectTrusted()) { showText(ctx, ["Construct load failed.", "Project is not trusted by Pi, so Construct will not write project metadata here.", "Trust this project in Pi, then run /construct load again."].join("\n")); return; } const settingsRead = await readJson(paths.projectSettingsPath); if (settingsRead.state === "invalid") { showText(ctx, `Construct load failed.\nCannot load because ${describeJsonReadIssue(".pi/settings.json", settingsRead)}`); return; } if (settingsRead.state === "ok" && !isObject(settingsRead.data)) { showText(ctx, "Construct load failed.\nCannot load because .pi/settings.json is not a JSON object."); return; } const constructRead = await readJson(paths.projectConstructPath); if (constructRead.state === "invalid") { showText(ctx, `Construct load failed.\nCannot load because ${describeJsonReadIssue(".pi/construct.json", constructRead)}`); return; } if (constructRead.state === "ok" && !isObject(constructRead.data)) { showText(ctx, "Construct load failed.\nCannot load because .pi/construct.json is not a JSON object."); return; } const catalogRead = await readJson(paths.userCatalogPath); if (catalogRead.state === "invalid") { showText(ctx, `Construct load failed.\nCannot load because ${describeJsonReadIssue("Construct library catalog", catalogRead)}`); return; } const catalogCheck = parseCatalog(catalogRead); if (catalogRead.state === "ok" && catalogCheck.warnings.length > 0) { showText(ctx, [`Construct load failed.`, `Cannot load because Construct library catalog has structural warnings. Fix ${paths.userCatalogPath} first.`, ...catalogCheck.warnings.map((warning) => `! ${warning}`)].join("\n")); return; } let candidates: { adoptable: AnyLoadCandidate[]; alreadyManaged: AnyLoadCandidate[]; directWarnings: string[] }; try { candidates = await collectLoadCandidates(ctx); } catch (error) { const message = error instanceof Error ? error.message : String(error); showText(ctx, `Construct load failed.\n${message}`); return; } if (candidates.adoptable.length === 0 && loadArgs.queries.length === 0) { showText( ctx, [ "Construct load complete.", `Project: ${paths.cwd}`, "No project resources are waiting to be loaded.", candidates.alreadyManaged.length > 0 ? `Already Construct-managed here: ${candidates.alreadyManaged.length}` : "No Construct-managed project resources found.", ...candidates.directWarnings.map((warning) => `! ${warning}`), "No files were changed.", ].join("\n"), ); return; } let selectedCandidates: AnyLoadCandidate[] = []; const selectionWarnings: string[] = [...candidates.directWarnings]; if (loadArgs.queries.length > 0) { const loadQueries: string[] = []; for (const query of loadArgs.queries) { const projectOverride = await matchingPiProjectOverride(paths, query); if (projectOverride) selectionWarnings.push(`${projectOverride} is a Pi project override (autoload: false); manage it with pi config -l.`); else loadQueries.push(query); } const direct = await findLoadCandidates(paths, candidates, loadQueries); selectedCandidates = direct.selected; selectionWarnings.push(...direct.alreadyManaged.map((query) => `Already Construct-managed here: ${query}`)); selectionWarnings.push(...direct.missing.map((query) => `Not an unloaded project resource: ${query}`)); } else if (ctx.mode === "tui") { const pickerItems: CheckboxPickerItem[] = candidates.adoptable.map((candidate) => ({ id: candidateKey(candidate), label: candidate.id, value: candidateValue(candidate), description: candidateDescription(candidate), checked: false, section: candidate.kind === "package" ? "UNLOADED PACKAGES — adopt already-declared packages" : "UNLOADED DIRECT RESOURCES — adopt metadata only", })); const selected = await pickCheckboxes(ctx, "Construct load — add project resources", pickerItems); if (!selected) { showText(ctx, "Construct load cancelled. No files were changed."); return; } const adoptableByKey = new Map(candidates.adoptable.map((candidate) => [candidateKey(candidate), candidate])); selectedCandidates = selected.selectedIds.map((key) => adoptableByKey.get(key)).filter((candidate): candidate is AnyLoadCandidate => candidate !== undefined); } else { selectedCandidates = candidates.adoptable; } if (selectedCandidates.length === 0) { showText(ctx, ["No resources selected for Construct load.", ...selectionWarnings.map((warning) => `! ${warning}`), "No files were changed."].join("\n")); return; } const ready = await waitForIdleBeforeConstructWrite(ctx, "Construct load"); if (!ready) { showText(ctx, "Construct load cancelled. No files were changed."); return; } try { const freshCandidates = await collectLoadCandidates(ctx); selectionWarnings.push(...freshCandidates.directWarnings); const selectedBeforeWait = new Set(selectedCandidates.map(candidateKey)); const freshSelected = freshCandidates.adoptable.filter((candidate) => selectedBeforeWait.has(candidateKey(candidate))); const freshSelectedKeys = new Set(freshSelected.map(candidateKey)); selectionWarnings.push(...selectedCandidates.filter((candidate) => !freshSelectedKeys.has(candidateKey(candidate))).map((candidate) => `No longer an unloaded project resource after waiting: ${candidateValue(candidate)}`)); selectedCandidates = freshSelected; candidates = freshCandidates; } catch (error) { const message = error instanceof Error ? error.message : String(error); showText(ctx, `Construct load failed.\nCould not re-check project resources after waiting.\n${message}`); return; } if (selectedCandidates.length === 0) { showText(ctx, ["No resources selected for Construct load.", ...selectionWarnings.map((warning) => `! ${warning}`), "No files were changed."].join("\n")); return; } const selectedPackageCandidates = selectedCandidates.filter((candidate): candidate is LoadCandidate => candidate.kind === "package"); const selectedDirectCandidates = selectedCandidates.filter((candidate): candidate is DirectLoadCandidate => candidate.kind !== "package"); const selectedSources = selectedPackageCandidates.map((candidate) => candidate.source); const selectedAfterWait = new Set(selectedSources); const enabledBySource = new Map(selectedPackageCandidates.filter((candidate) => selectedAfterWait.has(candidate.source)).map((candidate) => [candidate.source, !candidate.disabledByFilters])); let result: ConstructLoadResult; try { result = await loadSourcesIntoConstruct(ctx, paths, selectedSources, { enabledBySource }); const directResult = await loadDirectResourcesIntoConstruct(ctx, paths, selectedDirectCandidates.map((candidate) => candidate.resource)); result.directMetadataChanged = directResult.metadataChanged; result.warnings.push(...directResult.warnings, ...selectionWarnings); } catch (error) { const message = error instanceof Error ? error.message : String(error); showText(ctx, `Construct load failed.\n${message}`); return; } await showSummary(ctx, formatLoadResult(result)); }