@autoview/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +407 -0
- package/lib/AutoViewAgent.d.ts +109 -0
- package/lib/AutoViewAgent.js +123 -0
- package/lib/AutoViewAgent.js.map +1 -0
- package/lib/agent/emitMcpServer.d.ts +15 -0
- package/lib/agent/emitMcpServer.js +157 -0
- package/lib/agent/emitMcpServer.js.map +1 -0
- package/lib/agent/emitReport.d.ts +14 -0
- package/lib/agent/emitReport.js +85 -0
- package/lib/agent/emitReport.js.map +1 -0
- package/lib/agent/toolSurface.d.ts +130 -0
- package/lib/agent/toolSurface.js +342 -0
- package/lib/agent/toolSurface.js.map +1 -0
- package/lib/agent/verifyAgentTasks.d.ts +87 -0
- package/lib/agent/verifyAgentTasks.js +126 -0
- package/lib/agent/verifyAgentTasks.js.map +1 -0
- package/lib/cli/main.d.ts +2 -0
- package/lib/cli/main.js +295 -0
- package/lib/cli/main.js.map +1 -0
- package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
- package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
- package/lib/constants/AutoViewFrontendTemplate.js +46 -0
- package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
- package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
- package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
- package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
- package/lib/context/IAutoViewAgentContext.d.ts +60 -0
- package/lib/context/IAutoViewAgentContext.js +3 -0
- package/lib/context/IAutoViewAgentContext.js.map +1 -0
- package/lib/fromSwagger.d.ts +53 -0
- package/lib/fromSwagger.js +513 -0
- package/lib/fromSwagger.js.map +1 -0
- package/lib/generateDeterministic.d.ts +26 -0
- package/lib/generateDeterministic.js +75 -0
- package/lib/generateDeterministic.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
- package/lib/orchestrate/orchestrateAutoView.js +491 -0
- package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
- package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
- package/lib/orchestrate/utils/HistoryMessage.js +25 -0
- package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
- package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
- package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
- package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
- package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
- package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
- package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
- package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
- package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
- package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
- package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
- package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
- package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
- package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
- package/lib/preview/renderPreview.d.ts +22 -0
- package/lib/preview/renderPreview.js +198 -0
- package/lib/preview/renderPreview.js.map +1 -0
- package/lib/typings/compiler.d.ts +39 -0
- package/lib/typings/compiler.js +3 -0
- package/lib/typings/compiler.js.map +1 -0
- package/lib/typings/events.d.ts +106 -0
- package/lib/typings/events.js +3 -0
- package/lib/typings/events.js.map +1 -0
- package/lib/typings/index.d.ts +10 -0
- package/lib/typings/index.js +27 -0
- package/lib/typings/index.js.map +1 -0
- package/lib/typings/misc.d.ts +78 -0
- package/lib/typings/misc.js +3 -0
- package/lib/typings/misc.js.map +1 -0
- package/lib/utils/ArrayUtil.d.ts +8 -0
- package/lib/utils/ArrayUtil.js +30 -0
- package/lib/utils/ArrayUtil.js.map +1 -0
- package/lib/utils/StringUtil.d.ts +11 -0
- package/lib/utils/StringUtil.js +28 -0
- package/lib/utils/StringUtil.js.map +1 -0
- package/lib/utils/classifyEndpoints.d.ts +62 -0
- package/lib/utils/classifyEndpoints.js +216 -0
- package/lib/utils/classifyEndpoints.js.map +1 -0
- package/lib/utils/endpointFilter.d.ts +26 -0
- package/lib/utils/endpointFilter.js +0 -0
- package/lib/utils/endpointFilter.js.map +1 -0
- package/lib/utils/extractFields.d.ts +85 -0
- package/lib/utils/extractFields.js +231 -0
- package/lib/utils/extractFields.js.map +1 -0
- package/lib/utils/index.d.ts +13 -0
- package/lib/utils/index.js +30 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/normalizeForNestia.d.ts +34 -0
- package/lib/utils/normalizeForNestia.js +133 -0
- package/lib/utils/normalizeForNestia.js.map +1 -0
- package/lib/utils/resourcePlan.d.ts +39 -0
- package/lib/utils/resourcePlan.js +95 -0
- package/lib/utils/resourcePlan.js.map +1 -0
- package/lib/utils/sliceDocument.d.ts +17 -0
- package/lib/utils/sliceDocument.js +114 -0
- package/lib/utils/sliceDocument.js.map +1 -0
- package/lib/utils/toEndpoints.d.ts +90 -0
- package/lib/utils/toEndpoints.js +227 -0
- package/lib/utils/toEndpoints.js.map +1 -0
- package/lib/verify/runWorkflows.d.ts +25 -0
- package/lib/verify/runWorkflows.js +366 -0
- package/lib/verify/runWorkflows.js.map +1 -0
- package/lib/verify/workflows.d.ts +53 -0
- package/lib/verify/workflows.js +107 -0
- package/lib/verify/workflows.js.map +1 -0
- package/package.json +82 -0
- package/prompts/AUTOVIEW_RENDER.md +398 -0
- package/prompts/AUTOVIEW_REVIEW.md +60 -0
- package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
- package/src/AutoViewAgent.ts +222 -0
- package/src/agent/emitMcpServer.integration.test.ts +168 -0
- package/src/agent/emitMcpServer.test.ts +51 -0
- package/src/agent/emitMcpServer.ts +178 -0
- package/src/agent/emitReport.ts +117 -0
- package/src/agent/toolSurface.test.ts +243 -0
- package/src/agent/toolSurface.ts +501 -0
- package/src/agent/verifyAgentTasks.test.ts +106 -0
- package/src/agent/verifyAgentTasks.ts +171 -0
- package/src/cli/main.ts +363 -0
- package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
- package/src/constants/AutoViewFrontendTemplate.ts +42 -0
- package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
- package/src/context/IAutoViewAgentContext.ts +84 -0
- package/src/fromSwagger.test.ts +269 -0
- package/src/fromSwagger.ts +500 -0
- package/src/generateDeterministic.test.ts +39 -0
- package/src/generateDeterministic.ts +77 -0
- package/src/index.ts +30 -0
- package/src/orchestrate/orchestrateAutoView.ts +590 -0
- package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
- package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
- package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
- package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
- package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
- package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
- package/src/orchestrate/renderNavTs.test.ts +74 -0
- package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
- package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
- package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
- package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
- package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
- package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
- package/src/orchestrate/utils/HistoryMessage.ts +41 -0
- package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
- package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
- package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
- package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
- package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
- package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
- package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
- package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
- package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
- package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
- package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
- package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
- package/src/preview/renderPreview.ts +273 -0
- package/src/typings/compiler.ts +47 -0
- package/src/typings/events.ts +155 -0
- package/src/typings/index.ts +10 -0
- package/src/typings/misc.ts +93 -0
- package/src/utils/ArrayUtil.ts +16 -0
- package/src/utils/StringUtil.ts +29 -0
- package/src/utils/classifyEndpoints.test.ts +86 -0
- package/src/utils/classifyEndpoints.ts +291 -0
- package/src/utils/endpointFilter.test.ts +50 -0
- package/src/utils/endpointFilter.ts +0 -0
- package/src/utils/extractFields.test.ts +82 -0
- package/src/utils/extractFields.ts +306 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/normalizeForNestia.test.ts +93 -0
- package/src/utils/normalizeForNestia.ts +139 -0
- package/src/utils/resourcePlan.test.ts +104 -0
- package/src/utils/resourcePlan.ts +180 -0
- package/src/utils/sliceDocument.test.ts +85 -0
- package/src/utils/sliceDocument.ts +119 -0
- package/src/utils/toEndpoints.test.ts +251 -0
- package/src/utils/toEndpoints.ts +343 -0
- package/src/verify/runWorkflows.ts +403 -0
- package/src/verify/workflows.test.ts +117 -0
- package/src/verify/workflows.ts +154 -0
- package/template/CLAUDE.md +140 -0
- package/template/Dockerfile +31 -0
- package/template/PROMPT.md +80 -0
- package/template/SANDBOX.md +70 -0
- package/template/app/api/health/route.ts +10 -0
- package/template/app/globals.css +97 -0
- package/template/app/layout.tsx +30 -0
- package/template/app/page.tsx +19 -0
- package/template/components/AppShell.tsx +114 -0
- package/template/components/auto/CatalogGrid.tsx +159 -0
- package/template/components/auto/ConfirmButton.tsx +67 -0
- package/template/components/auto/EmbeddedCollection.tsx +144 -0
- package/template/components/auto/ResourceDashboard.tsx +104 -0
- package/template/components/auto/ResourceDetail.tsx +93 -0
- package/template/components/auto/ResourceForm.tsx +235 -0
- package/template/components/auto/ResourceIcon.tsx +88 -0
- package/template/components/auto/ResourceLanding.tsx +155 -0
- package/template/components/auto/ResourceTable.tsx +223 -0
- package/template/components/auto/formatValue.tsx +186 -0
- package/template/components/auto/types.ts +42 -0
- package/template/components/ui/badge.tsx +40 -0
- package/template/components/ui/button.tsx +57 -0
- package/template/components/ui/card.tsx +86 -0
- package/template/components/ui/dialog.tsx +119 -0
- package/template/components/ui/input.tsx +23 -0
- package/template/components/ui/label.tsx +24 -0
- package/template/components/ui/pagination.tsx +117 -0
- package/template/components/ui/select.tsx +92 -0
- package/template/components/ui/sheet.tsx +135 -0
- package/template/components/ui/skeleton.tsx +15 -0
- package/template/components/ui/table.tsx +120 -0
- package/template/components/ui/tabs.tsx +55 -0
- package/template/lib/utils.ts +35 -0
- package/template/next.config.mjs +52 -0
- package/template/package.json +46 -0
- package/template/postcss.config.js +6 -0
- package/template/scripts/start-shopping-backend.sh +56 -0
- package/template/tailwind.config.ts +96 -0
- package/template/tsconfig.json +29 -0
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
import { OpenApi } from "@typia/interface";
|
|
2
|
+
import { IAutoViewEndpoint, toEndpoints } from "../utils";
|
|
3
|
+
import { IAgenticaController, IAgenticaHistoryJson } from "@agentica/core";
|
|
4
|
+
import {
|
|
5
|
+
AutoBeAutoViewRenderPageEvent,
|
|
6
|
+
} from "../typings";
|
|
7
|
+
import { IPointer, Singleton } from "tstl";
|
|
8
|
+
import ts from "typescript";
|
|
9
|
+
import typia, { ILlmApplication } from "typia";
|
|
10
|
+
import { v7 } from "uuid";
|
|
11
|
+
|
|
12
|
+
import { AutoViewSystemPromptConstant } from "../constants/AutoViewSystemPromptConstant";
|
|
13
|
+
import { IAutoViewAgentContext } from "../context/IAutoViewAgentContext";
|
|
14
|
+
import { IAutoViewProductPlan } from "./structures/IAutoViewProductPlan";
|
|
15
|
+
import { IAutoViewRenderApplication } from "./structures/IAutoViewRenderApplication";
|
|
16
|
+
import { IAutoViewSdkMap } from "./structures/IAutoViewSdkMap";
|
|
17
|
+
import { HistoryMessage } from "./utils/HistoryMessage";
|
|
18
|
+
import {
|
|
19
|
+
describeEndpointPropsShape,
|
|
20
|
+
describeRequestBodyHint,
|
|
21
|
+
} from "./utils/describeEndpointPropsShape";
|
|
22
|
+
import { describeEndpointRequestBodyShape } from "./utils/describeEndpointRequestBodyShape";
|
|
23
|
+
import { describeEndpointResponseShape } from "./utils/describeEndpointResponseShape";
|
|
24
|
+
import { executeCachedBatch } from "./utils/executeCachedBatch";
|
|
25
|
+
|
|
26
|
+
// Five tries (one initial + four retries). The earlier limit of two
|
|
27
|
+
// retries silently dropped landing pages with multiple orthogonal
|
|
28
|
+
// defects — TC39 mix, unclosed JSX, regex escapes — because the model
|
|
29
|
+
// only managed to fix the first issue per attempt. Five tries gives
|
|
30
|
+
// the retry loop enough budget to converge on common LLM mistakes
|
|
31
|
+
// without ballooning per-page latency past ~30s.
|
|
32
|
+
const MAX_PARSE_RETRIES = 4;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Phase 4 of the AutoView agent — Render.
|
|
36
|
+
*
|
|
37
|
+
* Generates one TSX page per screen in {@link IAutoViewProductPlan} via parallel
|
|
38
|
+
* LLM calls. Each call returns the complete page source; the orchestrator runs
|
|
39
|
+
* it through TypeScript's parser and, on syntax failure, re-invokes the model
|
|
40
|
+
* with the diagnostic. Pages that still fail after {@link MAX_PARSE_RETRIES}
|
|
41
|
+
* retries are replaced with an inline error placeholder so the wider project
|
|
42
|
+
* keeps compiling.
|
|
43
|
+
*
|
|
44
|
+
* Returns a `path → content` patch keyed by the screen file path
|
|
45
|
+
* (`app/.../page.tsx`) that the orchestrator merges into the scaffold output
|
|
46
|
+
* before the `autoViewComplete` event.
|
|
47
|
+
*/
|
|
48
|
+
export interface IAutoViewRenderOutput {
|
|
49
|
+
/** `app/.../page.tsx` → TSX source for every rendered screen. */
|
|
50
|
+
files: Record<string, string>;
|
|
51
|
+
|
|
52
|
+
/** Per-screen render metadata, consumed by the Review phase. */
|
|
53
|
+
screens: IAutoViewRenderOutput.IScreenReport[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export namespace IAutoViewRenderOutput {
|
|
57
|
+
export interface IScreenReport {
|
|
58
|
+
pagePath: string;
|
|
59
|
+
uiPattern: string;
|
|
60
|
+
actor: string;
|
|
61
|
+
rationale: string;
|
|
62
|
+
attempts: number;
|
|
63
|
+
ok: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Parser diagnostic when `ok === false`. Empty string when the page
|
|
66
|
+
* rendered cleanly.
|
|
67
|
+
*/
|
|
68
|
+
diagnostic: string;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function orchestrateAutoViewRender(
|
|
73
|
+
ctx: IAutoViewAgentContext,
|
|
74
|
+
props: {
|
|
75
|
+
document: OpenApi.IDocument;
|
|
76
|
+
sdkMap: IAutoViewSdkMap;
|
|
77
|
+
productPlan: IAutoViewProductPlan;
|
|
78
|
+
designTheme: string;
|
|
79
|
+
step: number;
|
|
80
|
+
},
|
|
81
|
+
): Promise<IAutoViewRenderOutput> {
|
|
82
|
+
const total = props.productPlan.screens.length;
|
|
83
|
+
const completed = { value: 0 };
|
|
84
|
+
const files: Record<string, string> = {};
|
|
85
|
+
const screens: IAutoViewRenderOutput.IScreenReport[] = [];
|
|
86
|
+
// Pre-compute the list of frontend routes the plan declared so the
|
|
87
|
+
// render prompt can teach the LLM which `<Link href=...>` targets
|
|
88
|
+
// actually exist. Without this the model routinely linked to SDK
|
|
89
|
+
// accessor paths (`/shoppings/customers/systematic/channels`) that
|
|
90
|
+
// 404 the moment the user clicks them.
|
|
91
|
+
const allScreenPaths = props.productPlan.screens.map((s) => s.path);
|
|
92
|
+
|
|
93
|
+
const semaphoreCap =
|
|
94
|
+
typeof ctx.vendor.semaphore === "number"
|
|
95
|
+
? ctx.vendor.semaphore
|
|
96
|
+
: ctx.vendor.semaphore !== undefined
|
|
97
|
+
? ctx.vendor.semaphore.max()
|
|
98
|
+
: 8;
|
|
99
|
+
await executeCachedBatch(
|
|
100
|
+
semaphoreCap,
|
|
101
|
+
props.productPlan.screens.map((screen) => async (promptCacheKey) => {
|
|
102
|
+
const counter = new Singleton(() => ++completed.value);
|
|
103
|
+
const result = await renderOneScreen(ctx, {
|
|
104
|
+
screen,
|
|
105
|
+
document: props.document,
|
|
106
|
+
sdkMap: props.sdkMap,
|
|
107
|
+
designTheme: props.designTheme,
|
|
108
|
+
promptCacheKey,
|
|
109
|
+
allScreenPaths,
|
|
110
|
+
});
|
|
111
|
+
const filePath = screenPathToFile(screen.path);
|
|
112
|
+
files[filePath] = result.tsx;
|
|
113
|
+
// Preserve the LLM's final raw attempt next to the wiki when the
|
|
114
|
+
// page fell back to the placeholder. Without this the operator
|
|
115
|
+
// (and future render-prompt iterations) cannot see what the model
|
|
116
|
+
// actually emitted — only the placeholder text — so the same
|
|
117
|
+
// class of defect keeps recurring across archives.
|
|
118
|
+
if (!result.ok && result.lastAttemptTsx.length > 0) {
|
|
119
|
+
const slug =
|
|
120
|
+
screen.path.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "") ||
|
|
121
|
+
"root";
|
|
122
|
+
files[`wiki/render-failed/${slug}.tsx`] = result.lastAttemptTsx;
|
|
123
|
+
}
|
|
124
|
+
screens.push({
|
|
125
|
+
pagePath: screen.path,
|
|
126
|
+
uiPattern: screen.uiPattern as string,
|
|
127
|
+
actor: screen.actor,
|
|
128
|
+
rationale: result.rationale,
|
|
129
|
+
attempts: result.attempts,
|
|
130
|
+
ok: result.ok,
|
|
131
|
+
diagnostic: result.diagnostic,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
ctx.dispatch({
|
|
135
|
+
type: "autoViewRenderPage",
|
|
136
|
+
id: v7(),
|
|
137
|
+
created_at: new Date().toISOString(),
|
|
138
|
+
step: props.step,
|
|
139
|
+
total,
|
|
140
|
+
completed: counter.get(),
|
|
141
|
+
pagePath: screen.path,
|
|
142
|
+
uiPattern: screen.uiPattern as string,
|
|
143
|
+
attempts: result.attempts,
|
|
144
|
+
ok: result.ok,
|
|
145
|
+
tokenUsage: result.tokenUsage,
|
|
146
|
+
metric: result.metric,
|
|
147
|
+
} satisfies AutoBeAutoViewRenderPageEvent);
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return { files, screens };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Re-render a single screen after the typecheck phase reported diagnostics on
|
|
156
|
+
* its file. Reuses the parser-driven retry loop inside the per-screen render —
|
|
157
|
+
* the first attempt uses {@link buildTypecheckRetryHistories} (which surfaces
|
|
158
|
+
* the diagnostics + previous TSX), and any subsequent retries fall back to
|
|
159
|
+
* {@link buildRetryHistories} so the model still benefits from the deterministic
|
|
160
|
+
* `??`/`||` auto-fix and the syntax retry budget.
|
|
161
|
+
*
|
|
162
|
+
* Returns `null` when the model could not produce parser-valid TSX after the
|
|
163
|
+
* full retry budget; the caller should leave the existing (broken) file in
|
|
164
|
+
* place and report the failure so the operator still gets some output.
|
|
165
|
+
*/
|
|
166
|
+
export async function rerenderScreenForTypecheck(
|
|
167
|
+
ctx: IAutoViewAgentContext,
|
|
168
|
+
args: {
|
|
169
|
+
screen: IAutoViewProductPlan.IScreen;
|
|
170
|
+
document: OpenApi.IDocument;
|
|
171
|
+
sdkMap: IAutoViewSdkMap;
|
|
172
|
+
designTheme: string;
|
|
173
|
+
promptCacheKey: string;
|
|
174
|
+
allScreenPaths: readonly string[];
|
|
175
|
+
previousTsx: string;
|
|
176
|
+
typecheckErrors: ITypecheckErrorLine[];
|
|
177
|
+
},
|
|
178
|
+
): Promise<{ tsx: string; ok: boolean; diagnostic: string } | null> {
|
|
179
|
+
const relevantOperations = toEndpoints(args.document).filter((op) =>
|
|
180
|
+
args.screen.endpoints.includes((op.accessor ?? []).join(".")),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
let previousTsx = args.previousTsx;
|
|
184
|
+
let lastParseError: string | null = null;
|
|
185
|
+
let attempt = 0;
|
|
186
|
+
for (; attempt <= MAX_PARSE_RETRIES; attempt++) {
|
|
187
|
+
const pointer: IPointer<IAutoViewRenderApplication.IProps | null> = {
|
|
188
|
+
value: null,
|
|
189
|
+
};
|
|
190
|
+
const histories =
|
|
191
|
+
attempt === 0
|
|
192
|
+
? buildTypecheckRetryHistories({
|
|
193
|
+
...args,
|
|
194
|
+
relevantOperations,
|
|
195
|
+
previousTsx,
|
|
196
|
+
typecheckErrors: args.typecheckErrors,
|
|
197
|
+
})
|
|
198
|
+
: buildRetryHistories({
|
|
199
|
+
...args,
|
|
200
|
+
relevantOperations,
|
|
201
|
+
lastTsx: previousTsx,
|
|
202
|
+
lastError: lastParseError ?? "",
|
|
203
|
+
});
|
|
204
|
+
const userMessage =
|
|
205
|
+
attempt === 0
|
|
206
|
+
? `Fix the typecheck errors on ${args.screen.path}. Re-emit the entire page TSX with the corrections. Call \`renderPage\` once.`
|
|
207
|
+
: `The corrected attempt failed TypeScript parsing: ${lastParseError}. Regenerate the entire page TSX with the fix applied.`;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await ctx.conversate({
|
|
211
|
+
source: "autoViewRenderPage",
|
|
212
|
+
target: args.screen.path,
|
|
213
|
+
controller: createController({
|
|
214
|
+
build: (next) => {
|
|
215
|
+
pointer.value = next;
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
enforceFunctionCall: true,
|
|
219
|
+
promptCacheKey: args.promptCacheKey,
|
|
220
|
+
histories,
|
|
221
|
+
userMessage,
|
|
222
|
+
});
|
|
223
|
+
} catch (error) {
|
|
224
|
+
// Vendor-side failure during the typecheck-driven retry. Don't let
|
|
225
|
+
// it reject the whole retry batch — record and retry, falling
|
|
226
|
+
// through to `null` if the budget runs out so the caller keeps the
|
|
227
|
+
// previously-rendered (typecheck-broken) file instead of aborting
|
|
228
|
+
// the entire run.
|
|
229
|
+
lastParseError = error instanceof Error ? error.message : String(error);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (pointer.value === null) {
|
|
233
|
+
// Model refused to call renderPage on this attempt — escalate to
|
|
234
|
+
// the parser-retry pathway with the previous TSX still in scope.
|
|
235
|
+
lastParseError = "model did not call renderPage";
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const candidate =
|
|
239
|
+
autoWrapNullishCoalescingMix(pointer.value.tsx) ?? pointer.value.tsx;
|
|
240
|
+
const parseError = findSyntaxError(candidate);
|
|
241
|
+
if (parseError === null) {
|
|
242
|
+
return {
|
|
243
|
+
tsx: candidate,
|
|
244
|
+
ok: true,
|
|
245
|
+
diagnostic: "",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
previousTsx = candidate;
|
|
249
|
+
lastParseError = parseError;
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* -------------------------------------------------------------------------- */
|
|
255
|
+
/* per-screen render with parser-driven retry */
|
|
256
|
+
/* -------------------------------------------------------------------------- */
|
|
257
|
+
|
|
258
|
+
interface IRenderResult {
|
|
259
|
+
tsx: string;
|
|
260
|
+
rationale: string;
|
|
261
|
+
attempts: number;
|
|
262
|
+
ok: boolean;
|
|
263
|
+
/** Parser diagnostic when `ok === false`; empty string otherwise. */
|
|
264
|
+
diagnostic: string;
|
|
265
|
+
/**
|
|
266
|
+
* The TSX the LLM emitted on the final attempt — preserved even when the page
|
|
267
|
+
* fell back to the placeholder so the operator can inspect what the model
|
|
268
|
+
* kept trying. Empty string when `ok === true` (the shipped `tsx` is already
|
|
269
|
+
* the LLM's last attempt).
|
|
270
|
+
*/
|
|
271
|
+
lastAttemptTsx: string;
|
|
272
|
+
tokenUsage: AutoBeAutoViewRenderPageEvent["tokenUsage"];
|
|
273
|
+
metric: AutoBeAutoViewRenderPageEvent["metric"];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function renderOneScreen(
|
|
277
|
+
ctx: IAutoViewAgentContext,
|
|
278
|
+
args: {
|
|
279
|
+
screen: IAutoViewProductPlan.IScreen;
|
|
280
|
+
document: OpenApi.IDocument;
|
|
281
|
+
sdkMap: IAutoViewSdkMap;
|
|
282
|
+
designTheme: string;
|
|
283
|
+
promptCacheKey: string;
|
|
284
|
+
allScreenPaths: readonly string[];
|
|
285
|
+
},
|
|
286
|
+
): Promise<IRenderResult> {
|
|
287
|
+
// Restrict the SDK context to the schemas the screen actually
|
|
288
|
+
// composes; the full document would blow the prompt budget on
|
|
289
|
+
// larger projects.
|
|
290
|
+
const relevantOperations = toEndpoints(args.document).filter((op) =>
|
|
291
|
+
args.screen.endpoints.includes((op.accessor ?? []).join(".")),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
let lastTsx: string | null = null;
|
|
295
|
+
let lastError: string | null = null;
|
|
296
|
+
let lastRationale: string = "";
|
|
297
|
+
let attempts = 0;
|
|
298
|
+
let lastTokenUsage: AutoBeAutoViewRenderPageEvent["tokenUsage"] | null = null;
|
|
299
|
+
let lastMetric: AutoBeAutoViewRenderPageEvent["metric"] | null = null;
|
|
300
|
+
|
|
301
|
+
for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt++) {
|
|
302
|
+
attempts = attempt + 1;
|
|
303
|
+
const pointer: IPointer<IAutoViewRenderApplication.IProps | null> = {
|
|
304
|
+
value: null,
|
|
305
|
+
};
|
|
306
|
+
const histories =
|
|
307
|
+
attempt === 0
|
|
308
|
+
? buildInitialHistories({ ...args, relevantOperations })
|
|
309
|
+
: buildRetryHistories({
|
|
310
|
+
...args,
|
|
311
|
+
relevantOperations,
|
|
312
|
+
lastTsx: lastTsx ?? "",
|
|
313
|
+
lastError: lastError ?? "",
|
|
314
|
+
});
|
|
315
|
+
const userMessage =
|
|
316
|
+
attempt === 0
|
|
317
|
+
? `Render the page for ${args.screen.path} (${args.screen.uiPattern}). Call \`renderPage\` once.`
|
|
318
|
+
: `The previous attempt failed TypeScript parsing: ${lastError}. Regenerate the entire page TSX with the fix applied. Pay attention to: escaping \`/\` inside regex literals as \`\\/\`, wrapping mixed \`??\` and \`||\` with parentheses, and closing every \`{\` opened inside JSX.`;
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
const { metric, tokenUsage } = await ctx.conversate({
|
|
322
|
+
source: "autoViewRenderPage",
|
|
323
|
+
target: args.screen.path,
|
|
324
|
+
controller: createController({
|
|
325
|
+
build: (next) => {
|
|
326
|
+
pointer.value = next;
|
|
327
|
+
},
|
|
328
|
+
}),
|
|
329
|
+
enforceFunctionCall: true,
|
|
330
|
+
promptCacheKey: args.promptCacheKey,
|
|
331
|
+
histories,
|
|
332
|
+
userMessage,
|
|
333
|
+
});
|
|
334
|
+
lastTokenUsage = tokenUsage;
|
|
335
|
+
lastMetric = metric;
|
|
336
|
+
} catch (error) {
|
|
337
|
+
// Vendor-side failure (connection drop / undici `terminated`, 5xx,
|
|
338
|
+
// rate limit). On larger SDKs the per-screen render fan-out keeps
|
|
339
|
+
// dozens of LLM calls in flight and one dropped socket would
|
|
340
|
+
// otherwise reject the whole `executeCachedBatch` and abort every
|
|
341
|
+
// other screen mid-flight. Treat it like a failed attempt: record
|
|
342
|
+
// the reason, let the retry loop try again, and fall back to the
|
|
343
|
+
// placeholder if the budget runs out — the project still ships.
|
|
344
|
+
lastError =
|
|
345
|
+
error instanceof Error
|
|
346
|
+
? `vendor error: ${error.message}`
|
|
347
|
+
: `vendor error: ${String(error)}`;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (pointer.value === null) {
|
|
352
|
+
// No function call. Treat as fatal — break to the placeholder.
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
// Deterministic auto-fix: LLMs reliably re-emit `a ?? b || c` /
|
|
356
|
+
// `a || b ?? c` even with hard rules in the prompt. Walk the AST,
|
|
357
|
+
// parenthesize every nullish-coalescing branch nested inside a
|
|
358
|
+
// logical-or / logical-and (and vice versa), and re-check. This
|
|
359
|
+
// costs zero extra LLM calls and turns a guaranteed placeholder
|
|
360
|
+
// into a working page.
|
|
361
|
+
const candidate =
|
|
362
|
+
autoWrapNullishCoalescingMix(pointer.value.tsx) ?? pointer.value.tsx;
|
|
363
|
+
lastTsx = candidate;
|
|
364
|
+
lastRationale = pointer.value.rationale;
|
|
365
|
+
const parseError = findSyntaxError(candidate);
|
|
366
|
+
if (parseError === null) {
|
|
367
|
+
return {
|
|
368
|
+
tsx: candidate,
|
|
369
|
+
rationale: lastRationale,
|
|
370
|
+
attempts,
|
|
371
|
+
ok: true,
|
|
372
|
+
diagnostic: "",
|
|
373
|
+
lastAttemptTsx: "",
|
|
374
|
+
tokenUsage: lastTokenUsage!,
|
|
375
|
+
metric: lastMetric!,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
lastError = parseError;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Exhausted retries — ship a placeholder so the rest of the project
|
|
382
|
+
// still compiles. Surface the reason inline.
|
|
383
|
+
return {
|
|
384
|
+
tsx: buildPlaceholderPage(args.screen, lastError ?? "no rendering"),
|
|
385
|
+
rationale: lastRationale,
|
|
386
|
+
attempts,
|
|
387
|
+
ok: false,
|
|
388
|
+
diagnostic: lastError ?? "model did not call renderPage",
|
|
389
|
+
lastAttemptTsx: lastTsx ?? "",
|
|
390
|
+
tokenUsage: lastTokenUsage ?? zeroTokenUsage(),
|
|
391
|
+
metric: lastMetric ?? zeroMetric(),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function zeroTokenUsage(): AutoBeAutoViewRenderPageEvent["tokenUsage"] {
|
|
396
|
+
return {
|
|
397
|
+
total: 0,
|
|
398
|
+
input: { total: 0, cached: 0 },
|
|
399
|
+
output: {
|
|
400
|
+
total: 0,
|
|
401
|
+
reasoning: 0,
|
|
402
|
+
accepted_prediction: 0,
|
|
403
|
+
rejected_prediction: 0,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function zeroMetric(): AutoBeAutoViewRenderPageEvent["metric"] {
|
|
409
|
+
return {
|
|
410
|
+
attempt: 0,
|
|
411
|
+
success: 0,
|
|
412
|
+
invalidJson: 0,
|
|
413
|
+
validationFailure: 0,
|
|
414
|
+
consent: 0,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/* -------------------------------------------------------------------------- */
|
|
419
|
+
/* prompt history builders */
|
|
420
|
+
/* -------------------------------------------------------------------------- */
|
|
421
|
+
|
|
422
|
+
type ConversateHistory =
|
|
423
|
+
| IAgenticaHistoryJson.ISystemMessage
|
|
424
|
+
| IAgenticaHistoryJson.IAssistantMessage;
|
|
425
|
+
|
|
426
|
+
function buildInitialHistories(args: {
|
|
427
|
+
screen: IAutoViewProductPlan.IScreen;
|
|
428
|
+
sdkMap: IAutoViewSdkMap;
|
|
429
|
+
designTheme: string;
|
|
430
|
+
document: OpenApi.IDocument;
|
|
431
|
+
relevantOperations: IAutoViewEndpoint[];
|
|
432
|
+
allScreenPaths: readonly string[];
|
|
433
|
+
}): ConversateHistory[] {
|
|
434
|
+
// Deterministic per-endpoint `props` shape. The LLM has repeatedly
|
|
435
|
+
// flattened body fields into the top level of `props` (e.g.
|
|
436
|
+
// `index(connection, { limit: 24 })` instead of
|
|
437
|
+
// `index(connection, { body: { limit: 24 } })`), which makes the
|
|
438
|
+
// simulator return a masked 400 and the page renders the empty
|
|
439
|
+
// state. Pre-computing the exact shape kills that whole class of
|
|
440
|
+
// defect at zero LLM cost. Parameterless endpoints render as
|
|
441
|
+
// `(connection)` because the Nestia SDK omits the second arg
|
|
442
|
+
// entirely when there are no path params or body — emitting
|
|
443
|
+
// `(connection, {})` here teaches the LLM a signature that fails
|
|
444
|
+
// `npm run typecheck` with "Expected 1 arguments, but got 2".
|
|
445
|
+
const propsSignatures = args.relevantOperations
|
|
446
|
+
.map((op) => {
|
|
447
|
+
const accessor = (op.accessor ?? []).join(".");
|
|
448
|
+
const propsShape = describeEndpointPropsShape(op);
|
|
449
|
+
const signature =
|
|
450
|
+
propsShape === "{}"
|
|
451
|
+
? `api.functional.${accessor}(connection)`
|
|
452
|
+
: `api.functional.${accessor}(connection, ${propsShape})`;
|
|
453
|
+
return `- \`${signature}\``;
|
|
454
|
+
})
|
|
455
|
+
.join("\n");
|
|
456
|
+
|
|
457
|
+
// Per-endpoint required-fields hint + sample body literal. Stops the
|
|
458
|
+
// model from sending `{ body: {} }` to endpoints with required fields
|
|
459
|
+
// (cart create, etc.) — which used to come back as a typia validation
|
|
460
|
+
// 400 the user sees as "Unable to load …" with no actionable signal.
|
|
461
|
+
const bodyHints = args.relevantOperations
|
|
462
|
+
.map((op) => {
|
|
463
|
+
const hint = describeRequestBodyHint(op, args.document);
|
|
464
|
+
if (hint.length === 0) return null;
|
|
465
|
+
const accessor = (op.accessor ?? []).join(".");
|
|
466
|
+
return `- \`${accessor}\` → ${hint}`;
|
|
467
|
+
})
|
|
468
|
+
.filter((line): line is string => line !== null)
|
|
469
|
+
.join("\n");
|
|
470
|
+
|
|
471
|
+
// Per-endpoint return-type shape, fully expanded one or two levels. The LLM
|
|
472
|
+
// has the raw OpenAPI JSON below, but routinely invents plausible-sounding
|
|
473
|
+
// properties (`auth.member.email` on an `IAuthorized` whose real shape is
|
|
474
|
+
// `{ id, created_at, token }`). Spelling out the actual fields up front
|
|
475
|
+
// anchors the render against the SDK return type instead of the model's
|
|
476
|
+
// domain priors.
|
|
477
|
+
const responseShapes = args.relevantOperations
|
|
478
|
+
.map((op) => {
|
|
479
|
+
const accessor = (op.accessor ?? []).join(".");
|
|
480
|
+
const shape = describeEndpointResponseShape(op, args.document);
|
|
481
|
+
return `### \`api.functional.${accessor}\` → Promise<${shape}>`;
|
|
482
|
+
})
|
|
483
|
+
.join("\n\n");
|
|
484
|
+
|
|
485
|
+
// Symmetric input-side expansion. The one-line `bodyHints` block above shows
|
|
486
|
+
// required keys + a sample literal but stops short of nested union / array
|
|
487
|
+
// shapes, which is exactly where the LLM hallucinates (sort grammars as
|
|
488
|
+
// `{ field, direction }[]` instead of `("created_at.asc" | ...)[]`, or
|
|
489
|
+
// invented top-level `title` fields on search request types). Surfacing the
|
|
490
|
+
// full body shape closes that gap without changing any other prompt
|
|
491
|
+
// mechanics.
|
|
492
|
+
const requestShapes = args.relevantOperations
|
|
493
|
+
.map((op) => {
|
|
494
|
+
const shape = describeEndpointRequestBodyShape(op, args.document);
|
|
495
|
+
if (shape === null) return null;
|
|
496
|
+
const accessor = (op.accessor ?? []).join(".");
|
|
497
|
+
return `### \`api.functional.${accessor}\` body shape\n\n\`\`\`ts\n${shape}\n\`\`\``;
|
|
498
|
+
})
|
|
499
|
+
.filter((line): line is string => line !== null)
|
|
500
|
+
.join("\n\n");
|
|
501
|
+
|
|
502
|
+
return [
|
|
503
|
+
HistoryMessage.system(AutoViewSystemPromptConstant.AUTOVIEW_RENDER),
|
|
504
|
+
HistoryMessage.assistant`
|
|
505
|
+
Here is the screen to render and everything you need to compose
|
|
506
|
+
it.
|
|
507
|
+
|
|
508
|
+
## Screen
|
|
509
|
+
|
|
510
|
+
\`\`\`json
|
|
511
|
+
${JSON.stringify(args.screen, null, 2)}
|
|
512
|
+
\`\`\`
|
|
513
|
+
|
|
514
|
+
## Allowed internal route paths (use only these in \`<Link href=...>\` / \`router.push()\`)
|
|
515
|
+
|
|
516
|
+
${args.allScreenPaths.map((p) => `- \`${p}\``).join("\n")}
|
|
517
|
+
|
|
518
|
+
## SDK call signatures (use these \`props\` shapes verbatim)
|
|
519
|
+
|
|
520
|
+
${propsSignatures}
|
|
521
|
+
|
|
522
|
+
## Body required-fields hint (start each \`body: { ... }\` with at least these keys)
|
|
523
|
+
|
|
524
|
+
${bodyHints.length > 0 ? bodyHints : "(no endpoints with a request body)"}
|
|
525
|
+
|
|
526
|
+
## SDK return type shapes (use these exact field names + nesting — do not invent properties)
|
|
527
|
+
|
|
528
|
+
${responseShapes.length > 0 ? responseShapes : "(no endpoints with a response body)"}
|
|
529
|
+
|
|
530
|
+
## SDK request body shapes (use these exact field names + nesting — do not invent properties)
|
|
531
|
+
|
|
532
|
+
${requestShapes.length > 0 ? requestShapes : "(no endpoints with a request body)"}
|
|
533
|
+
|
|
534
|
+
## SDK endpoints used by this screen (full operation payloads)
|
|
535
|
+
|
|
536
|
+
\`\`\`json
|
|
537
|
+
${JSON.stringify(args.relevantOperations, null, 2)}
|
|
538
|
+
\`\`\`
|
|
539
|
+
|
|
540
|
+
## Design theme
|
|
541
|
+
|
|
542
|
+
${args.designTheme.length > 0 ? args.designTheme : "(none — prototype-first, content-first defaults)"}
|
|
543
|
+
|
|
544
|
+
## Actor context (from the SDK map)
|
|
545
|
+
|
|
546
|
+
\`\`\`json
|
|
547
|
+
${JSON.stringify(
|
|
548
|
+
args.sdkMap.actors.find((a) => a.name === args.screen.actor) ?? null,
|
|
549
|
+
null,
|
|
550
|
+
2,
|
|
551
|
+
)}
|
|
552
|
+
\`\`\`
|
|
553
|
+
`,
|
|
554
|
+
];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function buildRetryHistories(args: {
|
|
558
|
+
screen: IAutoViewProductPlan.IScreen;
|
|
559
|
+
sdkMap: IAutoViewSdkMap;
|
|
560
|
+
designTheme: string;
|
|
561
|
+
document: OpenApi.IDocument;
|
|
562
|
+
relevantOperations: IAutoViewEndpoint[];
|
|
563
|
+
allScreenPaths: readonly string[];
|
|
564
|
+
lastTsx: string;
|
|
565
|
+
lastError: string;
|
|
566
|
+
}): ConversateHistory[] {
|
|
567
|
+
return [
|
|
568
|
+
...buildInitialHistories(args),
|
|
569
|
+
HistoryMessage.assistant`
|
|
570
|
+
My previous attempt failed TypeScript syntax check.
|
|
571
|
+
|
|
572
|
+
## Previous TSX
|
|
573
|
+
|
|
574
|
+
\`\`\`tsx
|
|
575
|
+
${args.lastTsx}
|
|
576
|
+
\`\`\`
|
|
577
|
+
|
|
578
|
+
## Syntax error reported by the compiler
|
|
579
|
+
|
|
580
|
+
\`\`\`
|
|
581
|
+
${args.lastError}
|
|
582
|
+
\`\`\`
|
|
583
|
+
`,
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* History builder for the typecheck-driven retry pass. Differs from
|
|
589
|
+
* {@link buildRetryHistories} in that it lists every `tsc --noEmit` diagnostic
|
|
590
|
+
* on this file (not a single parser error) and explicitly anchors the LLM to
|
|
591
|
+
* the SDK-shape sections from the initial history — the typical failure mode is
|
|
592
|
+
* the model inventing property names that look plausible against domain priors
|
|
593
|
+
* instead of reading the schema we already spelled out.
|
|
594
|
+
*/
|
|
595
|
+
function buildTypecheckRetryHistories(args: {
|
|
596
|
+
screen: IAutoViewProductPlan.IScreen;
|
|
597
|
+
sdkMap: IAutoViewSdkMap;
|
|
598
|
+
designTheme: string;
|
|
599
|
+
document: OpenApi.IDocument;
|
|
600
|
+
relevantOperations: IAutoViewEndpoint[];
|
|
601
|
+
allScreenPaths: readonly string[];
|
|
602
|
+
previousTsx: string;
|
|
603
|
+
typecheckErrors: ITypecheckErrorLine[];
|
|
604
|
+
}): ConversateHistory[] {
|
|
605
|
+
const lines = args.typecheckErrors
|
|
606
|
+
.map((e) => `- L${e.line}:${e.column} \`${e.code}\` — ${e.message}`)
|
|
607
|
+
.join("\n");
|
|
608
|
+
return [
|
|
609
|
+
...buildInitialHistories(args),
|
|
610
|
+
HistoryMessage.assistant`
|
|
611
|
+
My previous attempt compiled cleanly but failed \`tsc --noEmit\` against
|
|
612
|
+
the assembled project. The errors all anchor on the SDK boundary — they
|
|
613
|
+
are caused by accessing properties, calling functions, or passing values
|
|
614
|
+
whose shape does not match what the SDK actually exposes.
|
|
615
|
+
|
|
616
|
+
## Previous TSX
|
|
617
|
+
|
|
618
|
+
\`\`\`tsx
|
|
619
|
+
${args.previousTsx}
|
|
620
|
+
\`\`\`
|
|
621
|
+
|
|
622
|
+
## TypeScript errors on this file
|
|
623
|
+
|
|
624
|
+
${lines}
|
|
625
|
+
|
|
626
|
+
Re-emit the entire page TSX with these errors fixed. Use only field
|
|
627
|
+
names and call signatures that match the "SDK return type shapes" and
|
|
628
|
+
"SDK request body shapes" sections above — do not invent properties,
|
|
629
|
+
do not assume snake_case when the schema uses camelCase, and do not add
|
|
630
|
+
a second argument to a parameterless endpoint.
|
|
631
|
+
`,
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Tsc diagnostic line carried into the typecheck retry prompt. Mirrors the
|
|
637
|
+
* shape {@link FrontendTypecheckSession} produces but only the fields the prompt
|
|
638
|
+
* actually needs, so the orchestrator can adapt other compilers in the future
|
|
639
|
+
* without touching this history builder.
|
|
640
|
+
*/
|
|
641
|
+
export interface ITypecheckErrorLine {
|
|
642
|
+
line: number;
|
|
643
|
+
column: number;
|
|
644
|
+
code: string;
|
|
645
|
+
message: string;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* History builder for the Phase 6 runtime-driven retry pass. The Playwright
|
|
650
|
+
* audit captures console errors, uncaught page exceptions, and navigation
|
|
651
|
+
* failures that the parser + typecheck gates cannot see — typically
|
|
652
|
+
* `obj?.array.method(...)` undefined-access on simulator data, react render
|
|
653
|
+
* crashes against missing optional fields, or hydration mismatches. Surfacing
|
|
654
|
+
* the captured messages back into the render prompt lets the LLM rewrite the
|
|
655
|
+
* page against the actual failure mode instead of guessing.
|
|
656
|
+
*/
|
|
657
|
+
function buildRuntimeRetryHistories(args: {
|
|
658
|
+
screen: IAutoViewProductPlan.IScreen;
|
|
659
|
+
sdkMap: IAutoViewSdkMap;
|
|
660
|
+
designTheme: string;
|
|
661
|
+
document: OpenApi.IDocument;
|
|
662
|
+
relevantOperations: IAutoViewEndpoint[];
|
|
663
|
+
allScreenPaths: readonly string[];
|
|
664
|
+
previousTsx: string;
|
|
665
|
+
runtimeErrors: IRuntimeErrorLine[];
|
|
666
|
+
}): ConversateHistory[] {
|
|
667
|
+
const lines = args.runtimeErrors
|
|
668
|
+
.map((e) => `- \`${e.type}\` — ${e.message}`)
|
|
669
|
+
.join("\n");
|
|
670
|
+
return [
|
|
671
|
+
...buildInitialHistories(args),
|
|
672
|
+
HistoryMessage.assistant`
|
|
673
|
+
My previous attempt typechecked cleanly but crashed at runtime when
|
|
674
|
+
Playwright loaded it under \`next dev\` (simulator mode). The errors
|
|
675
|
+
below are the live console / uncaught exception / navigation
|
|
676
|
+
diagnostics — typescript could not see them because the access pattern
|
|
677
|
+
uses optional chaining that short-circuits past the unsafe call site.
|
|
678
|
+
|
|
679
|
+
## Previous TSX
|
|
680
|
+
|
|
681
|
+
\`\`\`tsx
|
|
682
|
+
${args.previousTsx}
|
|
683
|
+
\`\`\`
|
|
684
|
+
|
|
685
|
+
## Runtime errors on this page
|
|
686
|
+
|
|
687
|
+
${lines}
|
|
688
|
+
|
|
689
|
+
Re-emit the entire page TSX with these errors fixed. Common causes:
|
|
690
|
+
- \`obj?.array.method(...)\` — fix by writing \`(obj?.array ?? []).method(...)\`
|
|
691
|
+
or \`obj?.array?.method(...)\` so the short-circuit covers the call.
|
|
692
|
+
- Property access on a value that can be \`null\`/\`undefined\` in
|
|
693
|
+
simulator data — narrow with explicit guards before reading.
|
|
694
|
+
- Components that crash on empty arrays — render an empty state when
|
|
695
|
+
the SDK returns \`[]\`.
|
|
696
|
+
Use only field names and shapes from the "SDK return type shapes" and
|
|
697
|
+
"SDK request body shapes" sections above. Do not invent properties.
|
|
698
|
+
`,
|
|
699
|
+
];
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Runtime diagnostic carried into the audit retry prompt. Subset of
|
|
704
|
+
* `IRuntimeAuditDiagnostic` from `auditFrontendRuntime` — the prompt only needs
|
|
705
|
+
* the type label and the message.
|
|
706
|
+
*/
|
|
707
|
+
export interface IRuntimeErrorLine {
|
|
708
|
+
type: "console" | "pageerror" | "navigation";
|
|
709
|
+
message: string;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Re-render a single screen after the Phase 6 runtime audit reported broken
|
|
714
|
+
* navigation / console / pageerror diagnostics on its page. Identical
|
|
715
|
+
* retry-loop shape to {@link rerenderScreenForTypecheck}: the first attempt uses
|
|
716
|
+
* {@link buildRuntimeRetryHistories} (which surfaces the live runtime errors +
|
|
717
|
+
* previous TSX) and any subsequent retries fall back to
|
|
718
|
+
* {@link buildRetryHistories} so the parser-driven safety net still applies.
|
|
719
|
+
*
|
|
720
|
+
* Returns `null` when the model could not produce parser-valid TSX after the
|
|
721
|
+
* full retry budget; the caller should leave the existing (broken) file in
|
|
722
|
+
* place.
|
|
723
|
+
*/
|
|
724
|
+
export async function rerenderScreenForRuntime(
|
|
725
|
+
ctx: IAutoViewAgentContext,
|
|
726
|
+
args: {
|
|
727
|
+
screen: IAutoViewProductPlan.IScreen;
|
|
728
|
+
document: OpenApi.IDocument;
|
|
729
|
+
sdkMap: IAutoViewSdkMap;
|
|
730
|
+
designTheme: string;
|
|
731
|
+
promptCacheKey: string;
|
|
732
|
+
allScreenPaths: readonly string[];
|
|
733
|
+
previousTsx: string;
|
|
734
|
+
runtimeErrors: IRuntimeErrorLine[];
|
|
735
|
+
},
|
|
736
|
+
): Promise<{ tsx: string; ok: boolean; diagnostic: string } | null> {
|
|
737
|
+
const relevantOperations = toEndpoints(args.document).filter((op) =>
|
|
738
|
+
args.screen.endpoints.includes((op.accessor ?? []).join(".")),
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
let previousTsx = args.previousTsx;
|
|
742
|
+
let lastParseError: string | null = null;
|
|
743
|
+
for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt++) {
|
|
744
|
+
const pointer: IPointer<IAutoViewRenderApplication.IProps | null> = {
|
|
745
|
+
value: null,
|
|
746
|
+
};
|
|
747
|
+
const histories =
|
|
748
|
+
attempt === 0
|
|
749
|
+
? buildRuntimeRetryHistories({
|
|
750
|
+
...args,
|
|
751
|
+
relevantOperations,
|
|
752
|
+
previousTsx,
|
|
753
|
+
runtimeErrors: args.runtimeErrors,
|
|
754
|
+
})
|
|
755
|
+
: buildRetryHistories({
|
|
756
|
+
...args,
|
|
757
|
+
relevantOperations,
|
|
758
|
+
lastTsx: previousTsx,
|
|
759
|
+
lastError: lastParseError ?? "",
|
|
760
|
+
});
|
|
761
|
+
const userMessage =
|
|
762
|
+
attempt === 0
|
|
763
|
+
? `Fix the runtime errors on ${args.screen.path}. Re-emit the entire page TSX with the corrections. Call \`renderPage\` once.`
|
|
764
|
+
: `The corrected attempt failed TypeScript parsing: ${lastParseError}. Regenerate the entire page TSX with the fix applied.`;
|
|
765
|
+
|
|
766
|
+
try {
|
|
767
|
+
await ctx.conversate({
|
|
768
|
+
source: "autoViewRenderPage",
|
|
769
|
+
target: args.screen.path,
|
|
770
|
+
controller: createController({
|
|
771
|
+
build: (next) => {
|
|
772
|
+
pointer.value = next;
|
|
773
|
+
},
|
|
774
|
+
}),
|
|
775
|
+
enforceFunctionCall: true,
|
|
776
|
+
promptCacheKey: args.promptCacheKey,
|
|
777
|
+
histories,
|
|
778
|
+
userMessage,
|
|
779
|
+
});
|
|
780
|
+
} catch (error) {
|
|
781
|
+
// Vendor-side failure during the runtime-driven retry. Same policy
|
|
782
|
+
// as the typecheck retry: record, retry, and fall through to `null`
|
|
783
|
+
// so the caller keeps the previous file rather than aborting.
|
|
784
|
+
lastParseError = error instanceof Error ? error.message : String(error);
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (pointer.value === null) {
|
|
788
|
+
lastParseError = "model did not call renderPage";
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
const candidate =
|
|
792
|
+
autoWrapNullishCoalescingMix(pointer.value.tsx) ?? pointer.value.tsx;
|
|
793
|
+
const parseError = findSyntaxError(candidate);
|
|
794
|
+
if (parseError === null) {
|
|
795
|
+
return { tsx: candidate, ok: true, diagnostic: "" };
|
|
796
|
+
}
|
|
797
|
+
previousTsx = candidate;
|
|
798
|
+
lastParseError = parseError;
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/* -------------------------------------------------------------------------- */
|
|
804
|
+
/* syntax validation */
|
|
805
|
+
/* -------------------------------------------------------------------------- */
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Run the rendered TSX through TypeScript's tolerant parser plus a focused
|
|
809
|
+
* regex for `??` / `||` mixing — the model's most common grammar error that the
|
|
810
|
+
* parser flags only post-parse.
|
|
811
|
+
*/
|
|
812
|
+
/**
|
|
813
|
+
* Walk the TypeScript AST and parenthesize every `??` branch that sits directly
|
|
814
|
+
* inside a `||` / `&&` expression, and vice versa. TC39 forbids mixing the two
|
|
815
|
+
* without explicit parens, and even with the prompt rule the model keeps
|
|
816
|
+
* re-emitting `a ?? b || c`. Doing it deterministically after the LLM call
|
|
817
|
+
* costs no extra inference and avoids the retry budget being burned on
|
|
818
|
+
* grammar-only defects.
|
|
819
|
+
*
|
|
820
|
+
* Returns the rewritten source when at least one fix landed, `null` when
|
|
821
|
+
* nothing needed changing or the rewrite failed validation (so the caller can
|
|
822
|
+
* fall back to the original text).
|
|
823
|
+
*/
|
|
824
|
+
const NULLISH_MIX_REGEX =
|
|
825
|
+
/\?\?[^()?|]{0,400}\|\||\|\|[^()|?]{0,400}\?\?|\?\?[^()?&]{0,400}&&|&&[^()&?]{0,400}\?\?/;
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Last-resort textual rewrite for the cases the AST visitor below cannot reach
|
|
829
|
+
* (parser bails out at the diagnostic, printer optimizes the parens away,
|
|
830
|
+
* etc.). Iteratively wraps every `??`-pair adjacent to a `||` or `&&` token.
|
|
831
|
+
* Pattern recognizes identifiers, dotted property access, subscript access,
|
|
832
|
+
* function calls with no nested parens, and template literals — enough to
|
|
833
|
+
* handle the LLM's typical landing-page expressions.
|
|
834
|
+
*
|
|
835
|
+
* Conservative on purpose: complex expressions where the operands cross parens
|
|
836
|
+
* or another operator are left alone, so the retry loop still has the original
|
|
837
|
+
* code to repair from.
|
|
838
|
+
*/
|
|
839
|
+
function regexWrapNullishMix(code: string): string {
|
|
840
|
+
// An "atom" the wrapper is comfortable picking up as one of the
|
|
841
|
+
// `??` operands. Covers:
|
|
842
|
+
// - identifiers and dotted chains (`obj.foo.bar`)
|
|
843
|
+
// - optional chains (`obj?.foo?.bar`)
|
|
844
|
+
// - subscript access (`arr[i]`, single-level)
|
|
845
|
+
// - balanced function calls (`fn(...)`, single nesting level)
|
|
846
|
+
// - type-cast wrappers (`(value as Type)` and `(value as Type).prop`)
|
|
847
|
+
// - string / number / boolean / null literals
|
|
848
|
+
// - empty / shallow array / object literals (`[]`, `{}`)
|
|
849
|
+
// Anything more exotic falls back to the AST visitor or the retry
|
|
850
|
+
// budget — but this covers ~95% of LLM-generated landing-page code.
|
|
851
|
+
const ID = String.raw`[A-Za-z_$][\w$]*`;
|
|
852
|
+
const CAST = String.raw`\(\s*${ID}(?:\.${ID})*\s+as\s+[A-Za-z_$][\w$<>.,\s\[\]?|&]*?\s*\)`;
|
|
853
|
+
const LITERAL = String.raw`"[^"\n]{0,80}"|'[^'\n]{0,80}'|\d+(?:\.\d+)?|true|false|null|undefined|\[\s*\]|\{\s*\}`;
|
|
854
|
+
const HEAD = String.raw`(?:${LITERAL}|${CAST}|${ID})`;
|
|
855
|
+
const TAIL = String.raw`(?:\??\.${ID}|\[[^\[\]\n]{1,80}\]|\([^()\n]{0,200}\))*`;
|
|
856
|
+
const ATOM = `${HEAD}${TAIL}`;
|
|
857
|
+
const pairs: Array<[RegExp, string]> = [
|
|
858
|
+
[
|
|
859
|
+
new RegExp(
|
|
860
|
+
`(?<![\\w(])(${ATOM}\\s*\\?\\?\\s*${ATOM})(\\s*(?:\\|\\||&&))`,
|
|
861
|
+
"g",
|
|
862
|
+
),
|
|
863
|
+
"($1)$2",
|
|
864
|
+
],
|
|
865
|
+
[
|
|
866
|
+
new RegExp(
|
|
867
|
+
`((?:\\|\\||&&)\\s*)(${ATOM}\\s*\\?\\?\\s*${ATOM})(?![\\w)])`,
|
|
868
|
+
"g",
|
|
869
|
+
),
|
|
870
|
+
"$1($2)",
|
|
871
|
+
],
|
|
872
|
+
];
|
|
873
|
+
|
|
874
|
+
let prev = "";
|
|
875
|
+
let curr = code;
|
|
876
|
+
let iterations = 0;
|
|
877
|
+
while (prev !== curr && iterations < 10) {
|
|
878
|
+
prev = curr;
|
|
879
|
+
for (const [re, replacement] of pairs) {
|
|
880
|
+
curr = curr.replace(re, replacement);
|
|
881
|
+
}
|
|
882
|
+
iterations++;
|
|
883
|
+
}
|
|
884
|
+
return curr;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function autoWrapNullishCoalescingMix(code: string): string | null {
|
|
888
|
+
const source = ts.createSourceFile(
|
|
889
|
+
"Page.tsx",
|
|
890
|
+
code,
|
|
891
|
+
ts.ScriptTarget.ES2022,
|
|
892
|
+
/*setParentNodes*/ true,
|
|
893
|
+
ts.ScriptKind.TSX,
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
let changed = false;
|
|
897
|
+
const isNullish = (n: ts.Node): boolean =>
|
|
898
|
+
ts.isBinaryExpression(n) &&
|
|
899
|
+
n.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken;
|
|
900
|
+
const isLogical = (n: ts.Node): boolean =>
|
|
901
|
+
ts.isBinaryExpression(n) &&
|
|
902
|
+
(n.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
|
|
903
|
+
n.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken);
|
|
904
|
+
|
|
905
|
+
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
|
|
906
|
+
const visit: ts.Visitor = (node) => {
|
|
907
|
+
const visited = ts.visitEachChild(node, visit, context);
|
|
908
|
+
if (!ts.isBinaryExpression(visited)) return visited;
|
|
909
|
+
|
|
910
|
+
const wrap = (child: ts.Expression): ts.Expression => {
|
|
911
|
+
changed = true;
|
|
912
|
+
return ts.factory.createParenthesizedExpression(child);
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
const op = visited.operatorToken.kind;
|
|
916
|
+
if (
|
|
917
|
+
op === ts.SyntaxKind.BarBarToken ||
|
|
918
|
+
op === ts.SyntaxKind.AmpersandAmpersandToken
|
|
919
|
+
) {
|
|
920
|
+
return ts.factory.updateBinaryExpression(
|
|
921
|
+
visited,
|
|
922
|
+
isNullish(visited.left) ? wrap(visited.left) : visited.left,
|
|
923
|
+
visited.operatorToken,
|
|
924
|
+
isNullish(visited.right) ? wrap(visited.right) : visited.right,
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (op === ts.SyntaxKind.QuestionQuestionToken) {
|
|
928
|
+
return ts.factory.updateBinaryExpression(
|
|
929
|
+
visited,
|
|
930
|
+
isLogical(visited.left) ? wrap(visited.left) : visited.left,
|
|
931
|
+
visited.operatorToken,
|
|
932
|
+
isLogical(visited.right) ? wrap(visited.right) : visited.right,
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
return visited;
|
|
936
|
+
};
|
|
937
|
+
return (root) => ts.visitNode(root, visit) as ts.SourceFile;
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
const result = ts.transform(source, [transformer]);
|
|
941
|
+
const printer = ts.createPrinter({
|
|
942
|
+
newLine: ts.NewLineKind.LineFeed,
|
|
943
|
+
removeComments: false,
|
|
944
|
+
});
|
|
945
|
+
let printed = changed ? printer.printFile(result.transformed[0]) : code;
|
|
946
|
+
result.dispose();
|
|
947
|
+
|
|
948
|
+
// Fallback for cases the AST visitor cannot reach (TS parser bails
|
|
949
|
+
// out at the diagnostic, the printer optimizes the parens away,
|
|
950
|
+
// etc.). Iteratively wrap remaining `?? ||` / `?? &&` pairs with a
|
|
951
|
+
// textual rewrite limited to atomic operands. Brute force but
|
|
952
|
+
// reliable for the LLM's typical landing-page expressions.
|
|
953
|
+
if (NULLISH_MIX_REGEX.test(printed)) {
|
|
954
|
+
const swept = regexWrapNullishMix(printed);
|
|
955
|
+
if (swept !== printed) {
|
|
956
|
+
printed = swept;
|
|
957
|
+
changed = true;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (!changed) return null;
|
|
962
|
+
|
|
963
|
+
// Safety net: only return the rewrite when it actually clears the
|
|
964
|
+
// grammar-error regex. If both AST and the textual fallback gave up,
|
|
965
|
+
// leave the original alone so the retry loop still has a shot.
|
|
966
|
+
if (NULLISH_MIX_REGEX.test(printed)) {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
return printed;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function findSyntaxError(code: string): string | null {
|
|
973
|
+
const source = ts.createSourceFile(
|
|
974
|
+
"Page.tsx",
|
|
975
|
+
code,
|
|
976
|
+
ts.ScriptTarget.ES2022,
|
|
977
|
+
/*setParentNodes*/ false,
|
|
978
|
+
ts.ScriptKind.TSX,
|
|
979
|
+
);
|
|
980
|
+
const diagnostics: readonly ts.Diagnostic[] =
|
|
981
|
+
(source as unknown as { parseDiagnostics?: ts.Diagnostic[] })
|
|
982
|
+
.parseDiagnostics ?? [];
|
|
983
|
+
if (diagnostics.length > 0) {
|
|
984
|
+
const first = diagnostics[0]!;
|
|
985
|
+
const message =
|
|
986
|
+
typeof first.messageText === "string"
|
|
987
|
+
? first.messageText
|
|
988
|
+
: first.messageText.messageText;
|
|
989
|
+
if (first.start !== undefined) {
|
|
990
|
+
const { line, character } = source.getLineAndCharacterOfPosition(
|
|
991
|
+
first.start,
|
|
992
|
+
);
|
|
993
|
+
// Line + column alone is not enough for the model to fix the
|
|
994
|
+
// error — it cannot see the source, so it tends to repeat the
|
|
995
|
+
// same defect. Include a small slice of the surrounding code so
|
|
996
|
+
// the retry attempt has concrete context.
|
|
997
|
+
const lines = code.split("\n");
|
|
998
|
+
const start = Math.max(0, line - 2);
|
|
999
|
+
const end = Math.min(lines.length, line + 3);
|
|
1000
|
+
const snippet = lines
|
|
1001
|
+
.slice(start, end)
|
|
1002
|
+
.map((text, idx) => {
|
|
1003
|
+
const lineNo = start + idx + 1;
|
|
1004
|
+
const marker = lineNo === line + 1 ? ">" : " ";
|
|
1005
|
+
return `${marker} ${lineNo.toString().padStart(4, " ")} | ${text}`;
|
|
1006
|
+
})
|
|
1007
|
+
.join("\n");
|
|
1008
|
+
return `Line ${line + 1}, column ${character + 1}: ${message}\n\n${snippet}`;
|
|
1009
|
+
}
|
|
1010
|
+
return message;
|
|
1011
|
+
}
|
|
1012
|
+
if (NULLISH_MIX_REGEX.test(code)) {
|
|
1013
|
+
return "Mixing `??` with `||` / `&&` is a TC39 grammar error — wrap the nullish-coalescing branch in parentheses (e.g. `(a ?? b) || c`).";
|
|
1014
|
+
}
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/* -------------------------------------------------------------------------- */
|
|
1019
|
+
/* path mapping + placeholder fallback */
|
|
1020
|
+
/* -------------------------------------------------------------------------- */
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Map a planner-emitted screen path (`/`, `/cart`, `/todos/[id]/edit`) to the
|
|
1024
|
+
* Next.js app-router file the scaffold writes for it. Exposed so the
|
|
1025
|
+
* typecheck-driven retry loop can look up which screen owns a given broken file
|
|
1026
|
+
* and re-render it directly.
|
|
1027
|
+
*/
|
|
1028
|
+
export function screenPathToFile(routePath: string): string {
|
|
1029
|
+
const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1030
|
+
if (trimmed.length === 0) return "app/page.tsx";
|
|
1031
|
+
return `app/${trimmed}/page.tsx`;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Inverse of {@link screenPathToFile}. Returns `null` when the path does not
|
|
1036
|
+
* match the `app/.../page.tsx` shape the scaffold writes — e.g. for shared
|
|
1037
|
+
* components or wiki files, which are not screen-owned and therefore cannot be
|
|
1038
|
+
* fixed by re-rendering a single screen.
|
|
1039
|
+
*/
|
|
1040
|
+
export function fileToScreenPath(filePath: string): string | null {
|
|
1041
|
+
if (filePath === "app/page.tsx") return "/";
|
|
1042
|
+
const match = /^app\/(.+)\/page\.tsx$/.exec(filePath);
|
|
1043
|
+
if (match === null) return null;
|
|
1044
|
+
return `/${match[1]}`;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function buildPlaceholderPage(
|
|
1048
|
+
screen: IAutoViewProductPlan.IScreen,
|
|
1049
|
+
reason: string,
|
|
1050
|
+
): string {
|
|
1051
|
+
const title = JSON.stringify(screen.title);
|
|
1052
|
+
const purpose = JSON.stringify(screen.purpose);
|
|
1053
|
+
const pattern = JSON.stringify(screen.uiPattern);
|
|
1054
|
+
const actor = JSON.stringify(screen.actor);
|
|
1055
|
+
const endpointsArrayLit = JSON.stringify(screen.endpoints);
|
|
1056
|
+
const reasonLit = JSON.stringify(reason);
|
|
1057
|
+
return `"use client";
|
|
1058
|
+
|
|
1059
|
+
// AutoView Render: this page failed to generate cleanly after the
|
|
1060
|
+
// retry budget. The placeholder keeps \`next build\` green; check
|
|
1061
|
+
// \`wiki/sdk-feedback.md\` for the root cause.
|
|
1062
|
+
|
|
1063
|
+
export default function Page() {
|
|
1064
|
+
const endpoints: readonly string[] = ${endpointsArrayLit};
|
|
1065
|
+
return (
|
|
1066
|
+
<main className="container mx-auto py-10">
|
|
1067
|
+
<p className="text-xs uppercase tracking-wide text-destructive">
|
|
1068
|
+
AutoView render failed · {${pattern}}
|
|
1069
|
+
</p>
|
|
1070
|
+
<h1 className="mt-2 text-3xl font-bold tracking-tight">{${title}}</h1>
|
|
1071
|
+
<p className="mt-3 text-muted-foreground">{${purpose}}</p>
|
|
1072
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
1073
|
+
Actor: <span className="font-mono">{${actor}}</span>
|
|
1074
|
+
</p>
|
|
1075
|
+
{endpoints.length > 0 ? (
|
|
1076
|
+
<div className="mt-4 rounded-md border bg-muted/30 p-3">
|
|
1077
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
1078
|
+
Planned SDK endpoints
|
|
1079
|
+
</p>
|
|
1080
|
+
<ul className="mt-2 space-y-1 text-xs font-mono">
|
|
1081
|
+
{endpoints.map((accessor) => (
|
|
1082
|
+
<li key={accessor}>{accessor}</li>
|
|
1083
|
+
))}
|
|
1084
|
+
</ul>
|
|
1085
|
+
</div>
|
|
1086
|
+
) : null}
|
|
1087
|
+
<details className="mt-6 rounded-lg border border-destructive/40 bg-destructive/5 p-4">
|
|
1088
|
+
<summary className="cursor-pointer text-sm font-medium text-destructive">Show parser diagnostic</summary>
|
|
1089
|
+
<pre className="mt-2 whitespace-pre-wrap text-xs text-muted-foreground">{${reasonLit}}</pre>
|
|
1090
|
+
</details>
|
|
1091
|
+
</main>
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
`;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/* -------------------------------------------------------------------------- */
|
|
1098
|
+
/* controller wiring */
|
|
1099
|
+
/* -------------------------------------------------------------------------- */
|
|
1100
|
+
|
|
1101
|
+
function createController(props: {
|
|
1102
|
+
build: (next: IAutoViewRenderApplication.IProps) => void;
|
|
1103
|
+
}): IAgenticaController.IClass {
|
|
1104
|
+
const application: ILlmApplication =
|
|
1105
|
+
typia.llm.application<IAutoViewRenderApplication>();
|
|
1106
|
+
|
|
1107
|
+
return {
|
|
1108
|
+
protocol: "class",
|
|
1109
|
+
name: "autoViewRenderPage",
|
|
1110
|
+
application,
|
|
1111
|
+
execute: {
|
|
1112
|
+
renderPage: (next) => {
|
|
1113
|
+
props.build(next);
|
|
1114
|
+
},
|
|
1115
|
+
} satisfies IAutoViewRenderApplication,
|
|
1116
|
+
};
|
|
1117
|
+
}
|