diff --git a/apps/desktop/src/main/services/context/contextDocBuilder.ts b/apps/desktop/src/main/services/context/contextDocBuilder.ts index 3bbbbcdf..087db948 100644 --- a/apps/desktop/src/main/services/context/contextDocBuilder.ts +++ b/apps/desktop/src/main/services/context/contextDocBuilder.ts @@ -531,9 +531,16 @@ export function readContextStatus(deps: { warnings: latestWarnings, generation: { state: "idle", + requestedAt: null, startedAt: null, finishedAt: null, error: null, + source: null, + event: null, + reason: null, + provider: null, + modelId: null, + reasoningEffort: null, }, }; } diff --git a/apps/desktop/src/main/services/context/contextDocService.test.ts b/apps/desktop/src/main/services/context/contextDocService.test.ts index 9bd020d3..82fae7ce 100644 --- a/apps/desktop/src/main/services/context/contextDocService.test.ts +++ b/apps/desktop/src/main/services/context/contextDocService.test.ts @@ -46,6 +46,16 @@ import { runContextDocGeneration, } from "./contextDocBuilder"; +function createDeferred() { + let resolve!: (value: T | PromiseLike) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + function createLogger() { return { debug: () => {}, @@ -221,4 +231,99 @@ describe("contextDocService", () => { expect(service.getDocPath("prd_ade")).toBe(path.join(projectRoot, "prd_ade.md")); expect(resolveContextDocPath).toHaveBeenCalledWith(projectRoot, "prd_ade"); }); + + it("persists active auto-refresh status as pending/running with trigger metadata", async () => { + const { service } = await createFixture(); + const deferred = createDeferred>>(); + vi.mocked(runContextDocGeneration).mockReturnValueOnce(deferred.promise as ReturnType); + + await service.savePrefs({ + provider: "unified", + modelId: "gpt-5", + reasoningEffort: "medium", + events: { onPrLand: true }, + }); + + const refreshPromise = service.maybeAutoRefreshDocs({ + event: "pr_land", + reason: "prs_land:123", + }); + + const duringRun = service.getStatus().generation; + expect(["pending", "running"]).toContain(duringRun.state); + expect(duringRun.source).toBe("auto"); + expect(duringRun.event).toBe("pr_land"); + expect(duringRun.reason).toBe("prs_land:123"); + expect(duringRun.provider).toBe("unified"); + expect(duringRun.modelId).toBe("gpt-5"); + expect(duringRun.reasoningEffort).toBe("medium"); + + deferred.resolve({ + provider: "unified", + generatedAt: "2026-03-05T12:01:00.000Z", + prdPath: "/tmp/PRD.ade.md", + architecturePath: "/tmp/ARCHITECTURE.ade.md", + usedFallbackPath: false, + warnings: [], + outputPreview: "generated", + }); + + await expect(refreshPromise).resolves.toMatchObject({ + provider: "unified", + generatedAt: "2026-03-05T12:01:00.000Z", + }); + + expect(service.getStatus().generation).toMatchObject({ + state: "succeeded", + source: "auto", + event: "pr_land", + reason: "prs_land:123", + provider: "unified", + modelId: "gpt-5", + reasoningEffort: "medium", + finishedAt: "2026-03-05T12:01:00.000Z", + }); + }); + + it("records manual generation metadata on completion", async () => { + const { service } = await createFixture(); + + await service.generateDocs({ + provider: "codex", + modelId: "gpt-5-codex", + reasoningEffort: "high", + events: { onPrCreate: true }, + }); + + expect(service.getStatus().generation).toMatchObject({ + state: "succeeded", + source: "manual", + event: null, + reason: "manual_generate", + provider: "codex", + modelId: "gpt-5-codex", + reasoningEffort: "high", + finishedAt: "2026-03-05T12:00:00.000Z", + }); + }); + + it("maps legacy idle generation records with a finish time to succeeded", async () => { + const { db, service } = await createFixture(); + + db.setJson("context:docs:generationStatus.v1", { + state: "idle", + finishedAt: "2026-03-05T09:30:00.000Z", + source: "auto", + event: "pr_create", + reason: "legacy_run", + }); + + expect(service.getStatus().generation).toMatchObject({ + state: "succeeded", + source: "auto", + event: "pr_create", + reason: "legacy_run", + finishedAt: "2026-03-05T09:30:00.000Z", + }); + }); }); diff --git a/apps/desktop/src/main/services/context/contextDocService.ts b/apps/desktop/src/main/services/context/contextDocService.ts index 21696140..75f67c8c 100644 --- a/apps/desktop/src/main/services/context/contextDocService.ts +++ b/apps/desktop/src/main/services/context/contextDocService.ts @@ -12,6 +12,9 @@ import { import { getErrorMessage, nowIso, toOptionalString } from "../shared/utils"; import type { AdeDb } from "../state/kvDb"; import type { + ContextDocGenerationEvent, + ContextDocGenerationSource, + ContextDocGenerationStatus, ContextDocPrefs, ContextDocStatus, ContextGenerateDocsArgs, @@ -22,14 +25,7 @@ import type { } from "../../../shared/types"; /** Event names that can trigger auto-refresh of context docs. */ -export type ContextRefreshEventName = - | "session_end" - | "commit" - | "pr_create" - | "pr_land" - | "mission_start" - | "mission_end" - | "lane_create"; +export type ContextRefreshEventName = ContextDocGenerationEvent; type ContextDocRefreshPrefs = { cadence: ContextRefreshTrigger; @@ -40,6 +36,16 @@ type ContextDocRefreshPrefs = { updatedAt: string; }; +type GenerationRunMeta = { + source: ContextDocGenerationStatus["source"]; + event?: ContextDocGenerationStatus["event"]; + reason?: string | null; + requestedAt?: string | null; + provider?: ContextDocGenerationStatus["provider"]; + modelId?: string | null; + reasoningEffort?: string | null; +}; + const CONTEXT_DOC_PREFS_KEY = "context:docs:preferences.v1"; const CONTEXT_DOC_LAST_RUN_KEY = "context:docs:lastRun.v1"; const CONTEXT_DOC_GENERATION_STATUS_KEY = "context:docs:generationStatus.v1"; @@ -104,6 +110,28 @@ function normalizeEvents(value: unknown): ContextRefreshEvents { return events; } +function normalizeGenerationEvent(value: unknown): ContextDocGenerationEvent | null { + const normalized = String(value ?? "").trim(); + if ( + normalized === "session_end" + || normalized === "commit" + || normalized === "pr_create" + || normalized === "pr_land" + || normalized === "mission_start" + || normalized === "mission_end" + || normalized === "lane_create" + ) { + return normalized; + } + return null; +} + +function normalizeGenerationSource(value: unknown): ContextDocGenerationSource | null { + const normalized = String(value ?? "").trim(); + if (normalized === "manual" || normalized === "auto") return normalized; + return null; +} + export function createContextDocService(args: { db: AdeDb; logger: Logger; @@ -179,12 +207,36 @@ export function createContextDocService(args: { const readGenerationStatus = (): ContextStatus["generation"] => { const raw = db.getJson>(CONTEXT_DOC_GENERATION_STATUS_KEY); - const state = raw?.state === "running" || raw?.state === "failed" ? raw.state : "idle"; + const finishedAt = toOptionalString(raw?.finishedAt) ?? null; + const error = toOptionalString(raw?.error) ?? null; + const rawState = toOptionalString(raw?.state); + const state: ContextDocGenerationStatus["state"] = (() => { + if ( + rawState === "pending" + || rawState === "running" + || rawState === "succeeded" + || rawState === "failed" + ) { + return rawState; + } + if (rawState === "idle" && finishedAt && !error) return "succeeded"; + return "idle"; + })(); + const sourceValue = normalizeGenerationSource(raw?.source); + const eventValue = normalizeGenerationEvent(raw?.event); + const providerValue = toOptionalString(raw?.provider); return { state, + requestedAt: toOptionalString(raw?.requestedAt) ?? null, startedAt: toOptionalString(raw?.startedAt) ?? null, - finishedAt: toOptionalString(raw?.finishedAt) ?? null, - error: toOptionalString(raw?.error) ?? null, + finishedAt, + error, + source: sourceValue, + event: eventValue, + reason: toOptionalString(raw?.reason) ?? null, + provider: providerValue === "codex" || providerValue === "claude" || providerValue === "unified" ? providerValue : null, + modelId: toOptionalString(raw?.modelId) ?? null, + reasoningEffort: toOptionalString(raw?.reasoningEffort) ?? null, }; }; @@ -192,40 +244,108 @@ export function createContextDocService(args: { db.setJson(CONTEXT_DOC_GENERATION_STATUS_KEY, next); }; + const buildGenerationStatus = (args: { + state: ContextDocGenerationStatus["state"]; + requestedAt?: string | null; + startedAt?: string | null; + finishedAt?: string | null; + error?: string | null; + meta?: GenerationRunMeta | null; + previous?: ContextDocGenerationStatus | null; + }): ContextDocGenerationStatus => { + const previous = args.previous ?? readGenerationStatus(); + return { + state: args.state, + requestedAt: args.requestedAt ?? args.meta?.requestedAt ?? previous.requestedAt ?? null, + startedAt: args.startedAt ?? previous.startedAt ?? null, + finishedAt: args.finishedAt ?? previous.finishedAt ?? null, + error: args.error ?? null, + source: args.meta?.source ?? previous.source ?? null, + event: args.meta?.event ?? previous.event ?? null, + reason: args.meta?.reason ?? previous.reason ?? null, + provider: args.meta?.provider ?? previous.provider ?? null, + modelId: args.meta?.modelId ?? previous.modelId ?? null, + reasoningEffort: args.meta?.reasoningEffort ?? previous.reasoningEffort ?? null, + }; + }; + let activeGeneration: Promise | null = null; - const generateDocs = async (docArgs: ContextGenerateDocsArgs): Promise => { + const generateDocsInternal = async ( + docArgs: ContextGenerateDocsArgs, + meta: GenerationRunMeta, + ): Promise => { // If generation is already in-flight, wait for it instead of starting a second one if (activeGeneration) { - logger.info("context_docs.generation_already_running", {}); + logger.info("context_docs.generation_already_running", { + source: meta.source, + event: meta.event ?? null, + reason: meta.reason ?? null, + }); return activeGeneration; } + const provider = normalizeContextProvider(docArgs.provider); + const modelId = toOptionalString(docArgs.modelId); + const reasoningEffort = toOptionalString(docArgs.reasoningEffort); + const requestedAt = meta.requestedAt ?? nowIso(); + const startedAt = nowIso(); + persistContextDocRefreshPrefs(docArgs); - writeGenerationStatus({ - state: "running", - startedAt: nowIso(), - finishedAt: null, - error: null, - }); + writeGenerationStatus( + buildGenerationStatus({ + state: "running", + requestedAt, + startedAt, + finishedAt: null, + error: null, + meta: { + ...meta, + requestedAt, + provider, + modelId, + reasoningEffort, + }, + }) + ); const run = async (): Promise => { try { const result = await runContextDocGenerationImpl(projectPackBuilderDeps, docArgs); - writeGenerationStatus({ - state: "idle", - startedAt: readGenerationStatus().startedAt, - finishedAt: result.generatedAt, - error: null, - }); + writeGenerationStatus( + buildGenerationStatus({ + state: "succeeded", + requestedAt, + startedAt, + finishedAt: result.generatedAt, + error: null, + meta: { + ...meta, + requestedAt, + provider, + modelId, + reasoningEffort, + }, + }) + ); return result; } catch (error) { - writeGenerationStatus({ - state: "failed", - startedAt: readGenerationStatus().startedAt, - finishedAt: nowIso(), - error: getErrorMessage(error), - }); + writeGenerationStatus( + buildGenerationStatus({ + state: "failed", + requestedAt, + startedAt, + finishedAt: nowIso(), + error: getErrorMessage(error), + meta: { + ...meta, + requestedAt, + provider, + modelId, + reasoningEffort, + }, + }) + ); throw error; } finally { activeGeneration = null; @@ -236,6 +356,15 @@ export function createContextDocService(args: { return activeGeneration; }; + const generateDocs = async (docArgs: ContextGenerateDocsArgs): Promise => + await generateDocsInternal(docArgs, { + source: "manual", + reason: "manual_generate", + provider: normalizeContextProvider(docArgs.provider), + modelId: toOptionalString(docArgs.modelId), + reasoningEffort: toOptionalString(docArgs.reasoningEffort), + }); + /** * Resolves which events are enabled, merging project config with stored prefs. * Priority: project config > stored prefs > defaults. @@ -266,9 +395,30 @@ export function createContextDocService(args: { const eventKey = EVENT_NAME_TO_KEY[event]; if (!eventKey) return null; + const generationBeforeAttempt = readGenerationStatus(); + const requestedAt = nowIso(); + const pendingMeta = (prefs: ContextDocRefreshPrefs | null): GenerationRunMeta => ({ + source: "auto", + event, + reason: docArgs.reason ?? null, + requestedAt, + provider: prefs?.provider ?? null, + modelId: prefs?.modelId ?? null, + reasoningEffort: prefs?.reasoningEffort ?? null, + }); + const settlePendingWithoutRun = (): void => { + if (activeGeneration) return; + const restored = + generationBeforeAttempt.state === "running" || generationBeforeAttempt.state === "pending" + ? { ...generationBeforeAttempt, state: "idle" as const, error: null } + : generationBeforeAttempt; + writeGenerationStatus(restored); + }; + // Check if this event is enabled const enabledEvents = resolveEnabledEvents(); if (!enabledEvents[eventKey]) { + settlePendingWithoutRun(); logger.debug("context_docs.auto_refresh_event_disabled", { event, reason: docArgs.reason ?? null, @@ -278,13 +428,30 @@ export function createContextDocService(args: { // Need stored prefs for provider/model info const prefs = readContextDocRefreshPrefs(); - if (!prefs) return null; + if (!prefs) { + settlePendingWithoutRun(); + return null; + } + + if (!activeGeneration) { + writeGenerationStatus( + buildGenerationStatus({ + state: "pending", + requestedAt, + startedAt: null, + finishedAt: null, + error: null, + meta: pendingMeta(prefs), + }) + ); + } // Throttle: check min interval const minIntervalMs = AUTO_REFRESH_MIN_INTERVAL_MS[event]; if (!docArgs.force) { const lastRunAt = readLastContextDocRunAt(); if (lastRunAt != null && Date.now() - lastRunAt < minIntervalMs) { + settlePendingWithoutRun(); logger.debug("context_docs.auto_refresh_skipped_recent", { event, reason: docArgs.reason ?? null, @@ -301,11 +468,18 @@ export function createContextDocService(args: { provider: prefs.provider, modelId: prefs.modelId, }); - return await generateDocs({ + return await generateDocsInternal({ provider: prefs.provider, ...(prefs.modelId ? { modelId: prefs.modelId } : {}), ...(prefs.reasoningEffort ? { reasoningEffort: prefs.reasoningEffort } : {}), events: enabledEvents, + }, { + source: "auto", + event, + reason: docArgs.reason ?? null, + provider: prefs.provider, + modelId: prefs.modelId, + reasoningEffort: prefs.reasoningEffort, }); } catch (error) { logger.warn("context_docs.auto_refresh_failed", { diff --git a/apps/desktop/src/renderer/components/app/AppShell.tsx b/apps/desktop/src/renderer/components/app/AppShell.tsx index 7d90acd1..0ddf2216 100644 --- a/apps/desktop/src/renderer/components/app/AppShell.tsx +++ b/apps/desktop/src/renderer/components/app/AppShell.tsx @@ -311,7 +311,7 @@ export function AppShell({ children }: { children: React.ReactNode }) { // Poll context status while generation is running so banners update in real-time useEffect(() => { if (!project?.rootPath) return; - if (contextStatus?.generation.state !== "running") return; + if (contextStatus?.generation.state !== "pending" && contextStatus?.generation.state !== "running") return; const poll = window.setInterval(() => { void window.ade.context.getStatus().then((status) => { setContextStatus(status); @@ -566,7 +566,7 @@ export function AppShell({ children }: { children: React.ReactNode }) { ) : null} - {!hideSidebar && project?.rootPath && !showWelcome && contextStatus?.generation.state === "running" ? ( + {!hideSidebar && project?.rootPath && !showWelcome && (contextStatus?.generation.state === "pending" || contextStatus?.generation.state === "running") ? (
ADE context docs are generating in the background. Open context settings
@@ -578,7 +578,7 @@ export function AppShell({ children }: { children: React.ReactNode }) { ) : null} - {!hideSidebar && project?.rootPath && !showWelcome && contextStatus?.generation.state !== "running" && missingContextDocs.length > 0 ? ( + {!hideSidebar && project?.rootPath && !showWelcome && contextStatus?.generation.state !== "pending" && contextStatus?.generation.state !== "running" && missingContextDocs.length > 0 ? (
Missing ADE context docs: {missingContextDocs.map((doc) => ` ${doc.label}`).join(", ")}. diff --git a/apps/desktop/src/renderer/components/onboarding/ProjectSetupPage.tsx b/apps/desktop/src/renderer/components/onboarding/ProjectSetupPage.tsx index 08879832..c2da1d4a 100644 --- a/apps/desktop/src/renderer/components/onboarding/ProjectSetupPage.tsx +++ b/apps/desktop/src/renderer/components/onboarding/ProjectSetupPage.tsx @@ -67,6 +67,15 @@ const EVENT_TOGGLES: { key: keyof ContextRefreshEvents; label: string; help: str const DEFAULT_EVENTS: ContextRefreshEvents = { onPrCreate: true, onMissionStart: true }; +function isContextGenerationActive(status: ContextStatus["generation"] | null | undefined): boolean { + return status?.state === "pending" || status?.state === "running"; +} + +function hasReadyContextDocs(status: ContextStatus | null): boolean { + const docs = status?.docs; + return !!docs?.length && docs.every((doc) => doc.exists && doc.sizeBytes >= 200); +} + export function ProjectSetupPage() { const navigate = useNavigate(); const project = useAppStore((s) => s.project); @@ -146,15 +155,15 @@ export function ProjectSetupPage() { // Poll while generating const pollRef = React.useRef | null>(null); useEffect(() => { - if (contextStatus?.generation.state === "running" && !pollRef.current) { + if (isContextGenerationActive(contextStatus?.generation) && !pollRef.current) { pollRef.current = setInterval(() => { void reloadContextStatus(); }, 2500); } - if (contextStatus?.generation.state !== "running" && pollRef.current) { + if (!isContextGenerationActive(contextStatus?.generation) && pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } return () => { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } }; - }, [contextStatus?.generation.state, reloadContextStatus]); + }, [contextStatus?.generation, reloadContextStatus]); const progressLabel = useMemo(() => `${stepIndex + 1} / ${STEP_ORDER.length}`, [stepIndex]); @@ -234,7 +243,7 @@ export function ProjectSetupPage() { return () => { if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current); }; }, [prefsLoaded, contextModelId, contextReasoningEffort, contextEvents]); - const isGenerating = contextStatus?.generation.state === "running"; + const isGenerating = isContextGenerationActive(contextStatus?.generation); const stepContent = (() => { if (step === "tools") return ; @@ -269,14 +278,16 @@ export function ProjectSetupPage() { {contextLoading ? "Checking status..." : isGenerating - ? "Generating docs — this can take a minute or two depending on your model and repo size." + ? (contextStatus?.generation.state === "pending" + ? "Doc generation is queued and will start shortly." + : "Generating docs — this can take a minute or two depending on your model and repo size.") : contextStatus?.generation.state === "failed" ? `Last generation failed${contextStatus.generation.error ? `: ${contextStatus.generation.error}` : "."}` - : contextStatus?.docs?.every((d) => d.exists) + : hasReadyContextDocs(contextStatus) ? "Both docs are present. You can regenerate if needed." : !contextModelId.trim() ? "Select a model above to generate docs." - : `Missing: ${contextStatus?.docs?.filter((d) => !d.exists).map((d) => d.label).join(", ") || "checking..."}`} + : `Missing or incomplete: ${contextStatus?.docs?.filter((d) => !d.exists || d.sizeBytes < 200).map((d) => d.label).join(", ") || "checking..."}`}
{isGenerating ? ( diff --git a/apps/desktop/src/renderer/components/settings/ContextSection.tsx b/apps/desktop/src/renderer/components/settings/ContextSection.tsx index de776ee1..2c38cd3b 100644 --- a/apps/desktop/src/renderer/components/settings/ContextSection.tsx +++ b/apps/desktop/src/renderer/components/settings/ContextSection.tsx @@ -46,6 +46,25 @@ const EVENT_TOGGLES: EventToggle[] = [ const DEFAULT_EVENTS: ContextRefreshEvents = { onPrCreate: true, onMissionStart: true }; +function isContextGenerationActive(status: ContextStatus["generation"] | null | undefined): boolean { + return status?.state === "pending" || status?.state === "running"; +} + +function describeGenerationSource(status: ContextStatus["generation"] | null | undefined): string | null { + if (!status || !isContextGenerationActive(status)) return null; + if (status.source !== "auto") return null; + switch (status.event) { + case "pr_create": return "Auto-refresh triggered by PR create."; + case "pr_land": return "Auto-refresh triggered by PR land."; + case "commit": return "Auto-refresh triggered by commit."; + case "mission_start": return "Auto-refresh triggered by mission start."; + case "mission_end": return "Auto-refresh triggered by mission end."; + case "session_end": return "Auto-refresh triggered by session end."; + case "lane_create": return "Auto-refresh triggered by lane create."; + default: return "Auto-refresh triggered in the background."; + } +} + /* ═══════════════════════════════════════════════════════════════════════════ Context Section ═══════════════════════════════════════════════════════════════════════════ */ @@ -145,7 +164,7 @@ export function ContextSection() { try { const status = await window.ade.context.getStatus(); setDocsStatus(status); - if (status.generation.state !== "running") { + if (!isContextGenerationActive(status.generation)) { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } setGenerating(false); } @@ -160,7 +179,7 @@ export function ContextSection() { // When docsStatus updates, detect if generation is running (e.g. kicked off from setup page) const genState = docsStatus?.generation.state; React.useEffect(() => { - if (genState === "running") { + if (genState === "pending" || genState === "running") { setGenerating(true); if (!pollRef.current) startStatusPoll(); } else if (generating && !pollRef.current) { @@ -217,9 +236,12 @@ export function ContextSection() { Canonical docs remain the stable source for bootstrap and memory ingestion. - {docsStatus?.generation.state === "running" ? ( + {isContextGenerationActive(docsStatus?.generation) ? (
- Context docs are being generated. This may take a minute depending on your model and project size. + {docsStatus?.generation.state === "pending" + ? "Context doc generation is queued and will start shortly." + : "Context docs are being generated. This may take a minute depending on your model and project size."} + {describeGenerationSource(docsStatus?.generation) ? ` ${describeGenerationSource(docsStatus?.generation)}` : ""}
) : null} @@ -233,12 +255,12 @@ export function ContextSection() {
Loading docs status...
) : docsStatus?.docs?.length ? ( docsStatus.docs.map((doc) => { - const isGenerating = docsStatus.generation.state === "running"; + const isGenerating = isContextGenerationActive(docsStatus.generation); const MIN_DOC_SIZE = 200; const hasContent = doc.exists && doc.sizeBytes >= MIN_DOC_SIZE; const statusColor = isGenerating ? COLORS.info : hasContent ? COLORS.success : COLORS.warning; const statusText = isGenerating - ? "generating..." + ? (docsStatus.generation.state === "pending" ? "queued..." : "generating...") : hasContent ? `ready \u00b7 updated ${relativeTime(doc.updatedAt)}` : doc.exists diff --git a/apps/desktop/src/shared/types/packs.ts b/apps/desktop/src/shared/types/packs.ts index d5f209fe..b5dece67 100644 --- a/apps/desktop/src/shared/types/packs.ts +++ b/apps/desktop/src/shared/types/packs.ts @@ -390,11 +390,29 @@ export type ContextDocGenerationWarning = { actionPath?: string; }; +export type ContextDocGenerationSource = "manual" | "auto"; + +export type ContextDocGenerationEvent = + | "session_end" + | "commit" + | "pr_create" + | "pr_land" + | "mission_start" + | "mission_end" + | "lane_create"; + export type ContextDocGenerationStatus = { - state: "idle" | "running" | "failed"; + state: "idle" | "pending" | "running" | "succeeded" | "failed"; + requestedAt: string | null; startedAt: string | null; finishedAt: string | null; error: string | null; + source: ContextDocGenerationSource | null; + event: ContextDocGenerationEvent | null; + reason: string | null; + provider: ContextDocProvider | null; + modelId: string | null; + reasoningEffort: string | null; }; export type ContextStatus = {