nolo-cli 0.1.8 → 0.1.10

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.
Files changed (239) hide show
  1. package/README.md +32 -0
  2. package/agentRuntimeCommands.ts +3 -3
  3. package/ai/agent/_executeModel.ts +118 -0
  4. package/ai/agent/agentSlice.ts +525 -0
  5. package/ai/agent/appWorkingMemory.ts +126 -0
  6. package/ai/agent/avatarUtils.ts +24 -0
  7. package/ai/agent/buildEditingContext.ts +373 -0
  8. package/ai/agent/buildSystemPrompt.ts +532 -0
  9. package/ai/agent/cleanAgentMessages.ts +140 -0
  10. package/ai/agent/cliChatClient.ts +119 -0
  11. package/ai/agent/cliExecutor.ts +733 -0
  12. package/ai/agent/cliPrompt.ts +10 -0
  13. package/ai/agent/contextCompiler.ts +107 -0
  14. package/ai/agent/contextLayerContract.ts +44 -0
  15. package/ai/agent/createAgentSchema.ts +234 -0
  16. package/ai/agent/executeToolCall.ts +58 -0
  17. package/ai/agent/fetchAgentContexts.ts +42 -0
  18. package/ai/agent/generatePrompt.ts +3 -0
  19. package/ai/agent/getFullChatContextKeys.ts +168 -0
  20. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  21. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  22. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  23. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  24. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  25. package/ai/agent/machineRunPermissions.ts +95 -0
  26. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  27. package/ai/agent/planSlice.ts +259 -0
  28. package/ai/agent/referenceUtils.ts +229 -0
  29. package/ai/agent/runAgentBackground.ts +238 -0
  30. package/ai/agent/runAgentClientLoop.ts +138 -0
  31. package/ai/agent/runtimeGuidance.ts +97 -0
  32. package/ai/agent/runtimeServerBase.ts +37 -0
  33. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  34. package/ai/agent/startParallelAgentStreams.ts +424 -0
  35. package/ai/agent/startupProtocol.ts +53 -0
  36. package/ai/agent/streamAgentChatTurn.ts +1278 -0
  37. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  38. package/ai/agent/types.ts +71 -0
  39. package/ai/agent/utils/imageOutput.ts +33 -0
  40. package/ai/agent/utils/sortUtils.ts +250 -0
  41. package/ai/agent/web/referencePickerUtils.ts +146 -0
  42. package/ai/ai.locale.ts +1079 -0
  43. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  44. package/ai/chat/fetchUtils.native.ts +276 -0
  45. package/ai/chat/fetchUtils.ts +153 -0
  46. package/ai/chat/parseApiError.ts +64 -0
  47. package/ai/chat/parseMultilineSSE.ts +95 -0
  48. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  49. package/ai/chat/sendOpenAICompletionsRequest.ts +703 -0
  50. package/ai/chat/sendOpenAIResponseRequest.ts +491 -0
  51. package/ai/chat/shouldUseServerProxy.ts +18 -0
  52. package/ai/chat/sseClient.native.ts +91 -0
  53. package/ai/chat/sseClient.ts +67 -0
  54. package/ai/chat/streamReader.native.ts +31 -0
  55. package/ai/chat/streamReader.ts +62 -0
  56. package/ai/chat/updateTotalUsage.ts +72 -0
  57. package/ai/context/buildReferenceContext.ts +437 -0
  58. package/ai/context/calculateContextUsage.ts +133 -0
  59. package/ai/context/retention.ts +165 -0
  60. package/ai/context/tokenUtils.ts +78 -0
  61. package/ai/index.ts +1 -0
  62. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  63. package/ai/llm/deepinfra.ts +28 -0
  64. package/ai/llm/fireworks.ts +50 -0
  65. package/ai/llm/generateRequestBody.ts +165 -0
  66. package/ai/llm/getModelContextWindow.ts +84 -0
  67. package/ai/llm/getNoloKey.ts +31 -0
  68. package/ai/llm/getPricing.ts +199 -0
  69. package/ai/llm/hooks/useModelPricing.ts +75 -0
  70. package/ai/llm/imagePricing.ts +40 -0
  71. package/ai/llm/isResponseAPIModel.ts +13 -0
  72. package/ai/llm/mimo.ts +71 -0
  73. package/ai/llm/mistral.ts +22 -0
  74. package/ai/llm/modelAvatar.ts +427 -0
  75. package/ai/llm/models.ts +45 -0
  76. package/ai/llm/openrouterModels.ts +269 -0
  77. package/ai/llm/providers.ts +306 -0
  78. package/ai/llm/reasoningModels.ts +28 -0
  79. package/ai/llm/types.ts +59 -0
  80. package/ai/llm/usageRequestOptions.ts +59 -0
  81. package/ai/memory/capture.ts +148 -0
  82. package/ai/memory/consolidate.ts +104 -0
  83. package/ai/memory/delete.ts +147 -0
  84. package/ai/memory/overlay.ts +84 -0
  85. package/ai/memory/query.ts +38 -0
  86. package/ai/memory/queryShared.ts +160 -0
  87. package/ai/memory/rank.ts +105 -0
  88. package/ai/memory/recentRelationshipRecap.ts +249 -0
  89. package/ai/memory/remember.ts +167 -0
  90. package/ai/memory/runtime.ts +76 -0
  91. package/ai/memory/store.ts +20 -0
  92. package/ai/memory/storeShared.ts +76 -0
  93. package/ai/memory/types.ts +46 -0
  94. package/ai/memory/understanding.ts +349 -0
  95. package/ai/memory/understandingGreeting.ts +264 -0
  96. package/ai/messages/type.ts +20 -0
  97. package/ai/policy/personalizationDialog.ts +333 -0
  98. package/ai/policy/runtimePolicy.ts +440 -0
  99. package/ai/policy/selfUpdateFields.ts +48 -0
  100. package/ai/policy/types.ts +64 -0
  101. package/ai/skills/referenceRuntime.ts +274 -0
  102. package/ai/skills/skillDiagnostics.ts +251 -0
  103. package/ai/skills/skillDocBuilder.ts +139 -0
  104. package/ai/skills/skillDocProtocol.ts +434 -0
  105. package/ai/skills/skillReferenceSummary.ts +63 -0
  106. package/ai/skills/skillSummaryMarker.ts +26 -0
  107. package/ai/token/calculatePrice.ts +544 -0
  108. package/ai/token/db.ts +98 -0
  109. package/ai/token/externalToolCost.ts +330 -0
  110. package/ai/token/hooks/useRecords.ts +65 -0
  111. package/ai/token/missingUsageEstimate.ts +42 -0
  112. package/ai/token/modelUsageQuery.ts +252 -0
  113. package/ai/token/normalizeUsage.ts +84 -0
  114. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  115. package/ai/token/prepareTokenUsageData.ts +88 -0
  116. package/ai/token/query.ts +88 -0
  117. package/ai/token/queryUserTokens.ts +59 -0
  118. package/ai/token/resolveBillingTarget.ts +52 -0
  119. package/ai/token/saveTokenRecord.ts +53 -0
  120. package/ai/token/serverDialogProjection.ts +78 -0
  121. package/ai/token/serverTokenWriter.ts +143 -0
  122. package/ai/token/stats.ts +21 -0
  123. package/ai/token/tokenThunks.ts +24 -0
  124. package/ai/token/types.ts +93 -0
  125. package/ai/tools/agent/agentTools.ts +176 -0
  126. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  127. package/ai/tools/agent/callAgentTool.ts +139 -0
  128. package/ai/tools/agent/createAgentTool.ts +512 -0
  129. package/ai/tools/agent/createDialogTool.ts +69 -0
  130. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  131. package/ai/tools/agent/parallelBudget.ts +221 -0
  132. package/ai/tools/agent/presets/appBuilderPreset.ts +145 -0
  133. package/ai/tools/agent/runLlmTool.ts +96 -0
  134. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  135. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  136. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  137. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  138. package/ai/tools/agent/updateAgentTool.ts +96 -0
  139. package/ai/tools/agent/updateSelfTool.ts +113 -0
  140. package/ai/tools/amazonProductScraperTool.ts +86 -0
  141. package/ai/tools/apifyActorClient.ts +45 -0
  142. package/ai/tools/appEditGuard.ts +372 -0
  143. package/ai/tools/appReadSnapshot.ts +153 -0
  144. package/ai/tools/appTools.ts +1549 -0
  145. package/ai/tools/applyEditTool.ts +256 -0
  146. package/ai/tools/applyLineEditsTool.ts +312 -0
  147. package/ai/tools/browserTools/click.ts +33 -0
  148. package/ai/tools/browserTools/closeSession.ts +29 -0
  149. package/ai/tools/browserTools/common.ts +27 -0
  150. package/ai/tools/browserTools/openSession.ts +48 -0
  151. package/ai/tools/browserTools/readContent.ts +38 -0
  152. package/ai/tools/browserTools/selectOption.ts +46 -0
  153. package/ai/tools/browserTools/typeText.ts +42 -0
  154. package/ai/tools/category/createCategoryTool.ts +66 -0
  155. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  156. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  157. package/ai/tools/cfBrowserTools.ts +319 -0
  158. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  159. package/ai/tools/checkEnvTool.ts +65 -0
  160. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  161. package/ai/tools/codeSearchTool.ts +111 -0
  162. package/ai/tools/codeTools.ts +101 -0
  163. package/ai/tools/createDocTool.ts +132 -0
  164. package/ai/tools/createPlanTool.ts +999 -0
  165. package/ai/tools/createSkillDocTool.ts +155 -0
  166. package/ai/tools/createWorkflowTool.ts +154 -0
  167. package/ai/tools/deepseekOcrTool.ts +34 -0
  168. package/ai/tools/delayTool.ts +31 -0
  169. package/ai/tools/deleteSpacesTool.ts +325 -0
  170. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  171. package/ai/tools/devReloadUtils.ts +29 -0
  172. package/ai/tools/dialogMessageSearch.ts +137 -0
  173. package/ai/tools/doctorSkillTool.ts +72 -0
  174. package/ai/tools/ecommerceScraperTool.ts +86 -0
  175. package/ai/tools/emailTools.ts +549 -0
  176. package/ai/tools/evalSkillTool.ts +92 -0
  177. package/ai/tools/exaSearchTool.ts +64 -0
  178. package/ai/tools/execBashTool.ts +379 -0
  179. package/ai/tools/executeSqlTool.ts +192 -0
  180. package/ai/tools/fetchWebpageSupport.ts +309 -0
  181. package/ai/tools/fetchWebpageTool.ts +84 -0
  182. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  183. package/ai/tools/generateDocxTool.ts +215 -0
  184. package/ai/tools/googleSearchScraperTool.ts +106 -0
  185. package/ai/tools/importDataTool.ts +133 -0
  186. package/ai/tools/importSkillTool.ts +162 -0
  187. package/ai/tools/index.ts +1858 -0
  188. package/ai/tools/listFilesTool.ts +82 -0
  189. package/ai/tools/listUserSpacesTool.ts +113 -0
  190. package/ai/tools/modelUsageTools.ts +142 -0
  191. package/ai/tools/olmOcrTool.ts +34 -0
  192. package/ai/tools/openaiImageTool.ts +218 -0
  193. package/ai/tools/paddleOcrTool.ts +34 -0
  194. package/ai/tools/prepareTools.ts +23 -0
  195. package/ai/tools/readDocTool.ts +84 -0
  196. package/ai/tools/readFileTool.ts +211 -0
  197. package/ai/tools/readTool.ts +163 -0
  198. package/ai/tools/readXPostTool.ts +233 -0
  199. package/ai/tools/rememberMemoryTool.ts +84 -0
  200. package/ai/tools/remotionVideoTool.ts +151 -0
  201. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  202. package/ai/tools/searchRepoTool.ts +115 -0
  203. package/ai/tools/searchWorkspaceTool.ts +259 -0
  204. package/ai/tools/skillFollowup.ts +86 -0
  205. package/ai/tools/surfWeatherTool.ts +169 -0
  206. package/ai/tools/table/addTableRowTool.ts +217 -0
  207. package/ai/tools/table/createTableTool.ts +315 -0
  208. package/ai/tools/table/rowTools.ts +366 -0
  209. package/ai/tools/table/schemaTools.ts +244 -0
  210. package/ai/tools/table/shareTableTool.ts +148 -0
  211. package/ai/tools/table/toolShared.ts +129 -0
  212. package/ai/tools/toolApiClient.ts +198 -0
  213. package/ai/tools/toolNameAliases.ts +57 -0
  214. package/ai/tools/toolResultError.ts +42 -0
  215. package/ai/tools/toolRunSlice.ts +303 -0
  216. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  217. package/ai/tools/toolVisibility.ts +4 -0
  218. package/ai/tools/types.ts +20 -0
  219. package/ai/tools/uiAskChoiceTool.ts +104 -0
  220. package/ai/tools/updateContentTitleTool.ts +84 -0
  221. package/ai/tools/updateDocTool.ts +105 -0
  222. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  223. package/ai/tools/whisperTool.ts +77 -0
  224. package/ai/tools/writeFileTool.ts +210 -0
  225. package/ai/tools/youtubeScraperTool.ts +116 -0
  226. package/ai/tools/ziweiChartTool.ts +678 -0
  227. package/ai/types.ts +55 -0
  228. package/ai/workflow/workflowExecutor.ts +323 -0
  229. package/ai/workflow/workflowSlice.ts +73 -0
  230. package/ai/workflow/workflowTypes.ts +106 -0
  231. package/client/compactDialog.ts +222 -0
  232. package/connector-experimental/capabilities.ts +73 -0
  233. package/connector-experimental/codexBinary.ts +41 -0
  234. package/connector-experimental/heartbeatLoop.ts +22 -0
  235. package/connector-experimental/index.ts +5 -0
  236. package/connector-experimental/machineInfo.ts +46 -0
  237. package/connector-experimental/protocol.ts +54 -0
  238. package/machineCommands.ts +4 -4
  239. package/package.json +22 -6
@@ -0,0 +1,372 @@
1
+ type AppSourceFile = { name: string; code: string };
2
+
3
+ type AppSourceSnapshot = {
4
+ code?: string | null;
5
+ files?: Array<{ name?: string | null; code?: string | null }> | null;
6
+ };
7
+
8
+ const TOKEN_FILE_PATTERN = /(tokens|theme|design-?system)\.(t|j)sx?$/i;
9
+ const SMALL_VISUAL_RE =
10
+ /(font|typography|text size|text bigger|font size|color|spacing|padding|margin|radius|shadow|line-height|letter-spacing|字号|字体|字重|颜色|配色|圆角|阴影|间距|留白)/i;
11
+ const BROAD_CHANGE_RE =
12
+ /(redesign|rewrite|refactor|new page|new feature|layout overhaul|full rebuild|重做|改版|重构|新页面|新功能|整体改|整个页面)/i;
13
+ const LOGIC_MARKERS = [
14
+ "useEffect(",
15
+ "useState(",
16
+ "fetch(",
17
+ "axios.",
18
+ "dispatch(",
19
+ "navigate(",
20
+ "createBrowserRouter(",
21
+ "addEventListener(",
22
+ "removeEventListener(",
23
+ "setInterval(",
24
+ "setTimeout(",
25
+ ];
26
+
27
+ export type SmallVisualEditGuardResult =
28
+ | {
29
+ ok: true;
30
+ reason: "not-applicable";
31
+ }
32
+ | {
33
+ ok: false;
34
+ summary: string;
35
+ displayData: string;
36
+ rawData: {
37
+ success: false;
38
+ ok: false;
39
+ error: true;
40
+ code: "SMALL_VISUAL_SCOPE_EXCEEDED";
41
+ summary: string;
42
+ requestType: "small-visual-edit";
43
+ issueCodes: string[];
44
+ evidence: string[];
45
+ retryable: true;
46
+ repairPlan: {
47
+ strategy: "targeted-repair";
48
+ scope: "existing-files";
49
+ mode: "preflight-first";
50
+ summary: string;
51
+ steps: Array<{ action: string; reason: string }>;
52
+ issueCodes: string[];
53
+ suggestedFiles: string[];
54
+ keepFiles?: string[];
55
+ revertFiles?: string[];
56
+ preferTokenFiles?: string[];
57
+ targetStyleFields?: string[];
58
+ targetElements?: string[];
59
+ rerun: ["appPreflight", "appDeploy"];
60
+ };
61
+ };
62
+ };
63
+
64
+ const toNormalizedFiles = (input: AppSourceSnapshot): AppSourceFile[] => {
65
+ const files = Array.isArray(input.files)
66
+ ? input.files
67
+ .filter(
68
+ (file): file is { name: string; code: string } =>
69
+ !!file &&
70
+ typeof file.name === "string" &&
71
+ typeof file.code === "string"
72
+ )
73
+ .map((file) => ({ name: file.name, code: file.code }))
74
+ : [];
75
+ if (files.length > 0) return files;
76
+
77
+ const code = typeof input.code === "string" ? input.code : "";
78
+ if (!code.trim()) return [];
79
+ return [{ name: "worker.ts", code }];
80
+ };
81
+
82
+ const buildFileMap = (files: AppSourceFile[]) =>
83
+ new Map(files.map((file) => [file.name, file.code]));
84
+
85
+ const extractJsxTagCounts = (code: string): Map<string, number> => {
86
+ const counts = new Map<string, number>();
87
+ const matches = code.matchAll(/<([A-Za-z][\w.-]*)\b/g);
88
+ for (const match of matches) {
89
+ const tag = match[1];
90
+ if (!tag) continue;
91
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
92
+ }
93
+ return counts;
94
+ };
95
+
96
+ const hasLargeStructureChange = (previousCode: string, nextCode: string): boolean => {
97
+ const previousTags = extractJsxTagCounts(previousCode);
98
+ const nextTags = extractJsxTagCounts(nextCode);
99
+ if (previousTags.size === 0 && nextTags.size === 0) return false;
100
+
101
+ const tagNames = new Set([...previousTags.keys(), ...nextTags.keys()]);
102
+ let totalDelta = 0;
103
+ let changedKinds = 0;
104
+ for (const tag of tagNames) {
105
+ const delta = Math.abs((previousTags.get(tag) ?? 0) - (nextTags.get(tag) ?? 0));
106
+ if (delta > 0) changedKinds += 1;
107
+ totalDelta += delta;
108
+ }
109
+
110
+ const previousLines = previousCode.split("\n").length;
111
+ const nextLines = nextCode.split("\n").length;
112
+ const lineDelta = Math.abs(previousLines - nextLines);
113
+
114
+ return totalDelta > 4 || changedKinds > 3 || lineDelta > Math.max(25, Math.round(previousLines * 0.35));
115
+ };
116
+
117
+ const hasLogicMarkerChange = (previousCode: string, nextCode: string): boolean =>
118
+ LOGIC_MARKERS.some((marker) => previousCode.includes(marker) !== nextCode.includes(marker));
119
+
120
+ const STYLE_FIELDS = [
121
+ "fontSize",
122
+ "fontWeight",
123
+ "lineHeight",
124
+ "letterSpacing",
125
+ "color",
126
+ "backgroundColor",
127
+ "padding",
128
+ "margin",
129
+ "gap",
130
+ "borderRadius",
131
+ "boxShadow",
132
+ ] as const;
133
+
134
+ const FIELD_TRIGGER_MAP: Array<{ field: string; re: RegExp }> = [
135
+ { field: "fontSize", re: /(font|text size|font size|字号|字体|字大|字小)/i },
136
+ { field: "fontWeight", re: /(font weight|字重|加粗|粗一点)/i },
137
+ { field: "lineHeight", re: /(line-height|line height|行高)/i },
138
+ { field: "letterSpacing", re: /(letter-spacing|letter spacing|字距)/i },
139
+ { field: "color", re: /(color|颜色|配色)/i },
140
+ { field: "backgroundColor", re: /(background|背景|底色)/i },
141
+ { field: "padding", re: /(padding|内边距)/i },
142
+ { field: "margin", re: /(margin|外边距|留白|间距)/i },
143
+ { field: "gap", re: /(gap|间距)/i },
144
+ { field: "borderRadius", re: /(radius|圆角)/i },
145
+ { field: "boxShadow", re: /(shadow|阴影)/i },
146
+ ];
147
+
148
+ const extractStyleFields = (code: string): string[] => {
149
+ const found = new Set<string>();
150
+ for (const field of STYLE_FIELDS) {
151
+ if (code.includes(`${field}:`)) found.add(field);
152
+ }
153
+ return [...found];
154
+ };
155
+
156
+ const ELEMENT_TRIGGER_MAP: Array<{ element: string; re: RegExp; aliases: string[] }> = [
157
+ { element: "button", re: /(button|按钮)/i, aliases: ["button"] },
158
+ { element: "body-text", re: /(正文|body text|paragraph|段落)/i, aliases: ["p"] },
159
+ { element: "title", re: /(title|headline|标题)/i, aliases: ["h1", "h2", "h3"] },
160
+ {
161
+ element: "container",
162
+ re: /(card|container|panel|section|卡片|容器|面板)/i,
163
+ aliases: ["section", "main", "div", "article"],
164
+ },
165
+ ];
166
+
167
+ const inferRequestedElements = (userInput: string | null | undefined): string[] => {
168
+ const text = typeof userInput === "string" ? userInput : "";
169
+ const found = new Set<string>();
170
+ for (const rule of ELEMENT_TRIGGER_MAP) {
171
+ if (!rule.re.test(text)) continue;
172
+ for (const alias of rule.aliases) found.add(alias);
173
+ }
174
+ return [...found];
175
+ };
176
+
177
+ const extractStyledTagSignatures = (code: string): Map<string, string[]> => {
178
+ const result = new Map<string, string[]>();
179
+ const matches = code.matchAll(/<([A-Za-z][\w.-]*)\b[^>]*style=\{\{([\s\S]*?)\}\}[^>]*>/g);
180
+ for (const match of matches) {
181
+ const tag = match[1];
182
+ const style = match[2];
183
+ if (!tag || !style) continue;
184
+ const list = result.get(tag) ?? [];
185
+ list.push(style.replace(/\s+/g, " ").trim());
186
+ result.set(tag, list);
187
+ }
188
+ return result;
189
+ };
190
+
191
+ const inferRequestedStyleFields = (userInput: string | null | undefined): string[] => {
192
+ const text = typeof userInput === "string" ? userInput : "";
193
+ const found = new Set<string>();
194
+ for (const rule of FIELD_TRIGGER_MAP) {
195
+ if (rule.re.test(text)) found.add(rule.field);
196
+ }
197
+ return [...found];
198
+ };
199
+
200
+ export const isSmallVisualEditRequest = (input: string | null | undefined): boolean => {
201
+ const text = typeof input === "string" ? input.trim() : "";
202
+ if (!text) return false;
203
+ return SMALL_VISUAL_RE.test(text) && !BROAD_CHANGE_RE.test(text);
204
+ };
205
+
206
+ export const evaluateSmallVisualEditGuard = (params: {
207
+ userInput?: string | null;
208
+ previousSource: AppSourceSnapshot;
209
+ nextSource: AppSourceSnapshot;
210
+ }): SmallVisualEditGuardResult => {
211
+ if (!isSmallVisualEditRequest(params.userInput)) {
212
+ return { ok: true, reason: "not-applicable" };
213
+ }
214
+
215
+ const previousFiles = toNormalizedFiles(params.previousSource);
216
+ const nextFiles = toNormalizedFiles(params.nextSource);
217
+ if (previousFiles.length === 0 || nextFiles.length === 0) {
218
+ return { ok: true, reason: "not-applicable" };
219
+ }
220
+
221
+ const previousByName = buildFileMap(previousFiles);
222
+ const nextByName = buildFileMap(nextFiles);
223
+ const previousNames = new Set(previousByName.keys());
224
+ const nextNames = new Set(nextByName.keys());
225
+
226
+ const addedFiles = [...nextNames].filter((name) => !previousNames.has(name));
227
+ const deletedFiles = [...previousNames].filter((name) => !nextNames.has(name));
228
+ const changedFiles = [...nextNames].filter(
229
+ (name) => previousByName.has(name) && previousByName.get(name) !== nextByName.get(name)
230
+ );
231
+
232
+ const evidence: string[] = [];
233
+ const issueCodes: string[] = [];
234
+ const tokenFiles = [...nextNames].filter((name) => TOKEN_FILE_PATTERN.test(name));
235
+ const targetStyleFields = inferRequestedStyleFields(params.userInput);
236
+ const targetElements = inferRequestedElements(params.userInput);
237
+
238
+ if (deletedFiles.length > 0) {
239
+ issueCodes.push("deleted-files");
240
+ evidence.push(`删除了文件:${deletedFiles.join(", ")}`);
241
+ }
242
+
243
+ const nonTokenAddedFiles = addedFiles.filter((name) => !TOKEN_FILE_PATTERN.test(name));
244
+ if (nonTokenAddedFiles.length > 0) {
245
+ issueCodes.push("added-non-token-files");
246
+ evidence.push(`新增了非 token 文件:${nonTokenAddedFiles.join(", ")}`);
247
+ }
248
+
249
+ if (changedFiles.length + addedFiles.length > 4) {
250
+ issueCodes.push("too-many-files");
251
+ evidence.push(
252
+ `命中了过多文件:修改 ${changedFiles.length} 个,新增 ${addedFiles.length} 个`
253
+ );
254
+ }
255
+
256
+ for (const fileName of changedFiles) {
257
+ if (TOKEN_FILE_PATTERN.test(fileName)) continue;
258
+ const previousCode = previousByName.get(fileName) ?? "";
259
+ const nextCode = nextByName.get(fileName) ?? "";
260
+
261
+ if (hasLogicMarkerChange(previousCode, nextCode)) {
262
+ issueCodes.push("logic-change");
263
+ evidence.push(`文件 ${fileName} 出现了逻辑层标记变化`);
264
+ }
265
+
266
+ if (hasLargeStructureChange(previousCode, nextCode)) {
267
+ issueCodes.push("jsx-structure-change");
268
+ evidence.push(`文件 ${fileName} 的 JSX 结构变化过大`);
269
+ }
270
+
271
+ if (targetElements.length > 0) {
272
+ const previousTags = extractStyledTagSignatures(previousCode);
273
+ const nextTags = extractStyledTagSignatures(nextCode);
274
+ const tagNames = new Set([...previousTags.keys(), ...nextTags.keys()]);
275
+ const unauthorizedTags = [...tagNames].filter((tag) => {
276
+ const previousSignature = JSON.stringify(previousTags.get(tag) ?? []);
277
+ const nextSignature = JSON.stringify(nextTags.get(tag) ?? []);
278
+ if (previousSignature === nextSignature) return false;
279
+ return !targetElements.includes(tag);
280
+ });
281
+ if (unauthorizedTags.length > 0) {
282
+ issueCodes.push("non-target-element-change");
283
+ evidence.push(
284
+ `文件 ${fileName} 改到了未点名元素:${unauthorizedTags.join(", ")}`
285
+ );
286
+ }
287
+ }
288
+ }
289
+
290
+ if (issueCodes.length === 0) {
291
+ return { ok: true, reason: "not-applicable" };
292
+ }
293
+
294
+ const suggestedFiles = [...new Set([...changedFiles, ...addedFiles])];
295
+ const revertFiles = [...new Set([...deletedFiles, ...nonTokenAddedFiles])];
296
+ const keepFiles = [
297
+ ...new Set([
298
+ ...changedFiles.filter((name) => !revertFiles.includes(name)),
299
+ ...tokenFiles,
300
+ ]),
301
+ ];
302
+ const observedStyleFields = [
303
+ ...new Set(
304
+ changedFiles.flatMap((fileName) => extractStyleFields(nextByName.get(fileName) ?? ""))
305
+ ),
306
+ ];
307
+ const scopedStyleFields = targetStyleFields.length > 0 ? targetStyleFields : observedStyleFields;
308
+ const summary =
309
+ "这次请求更像“小范围视觉微调”,但当前改动超出了安全范围,先不要直接部署。请收敛到 token / 命中的局部样式,再重新预检和部署。";
310
+ const displayData = [
311
+ "⚠️ 小视觉修改守卫已拦截本次部署。",
312
+ summary,
313
+ "",
314
+ "超范围证据:",
315
+ ...evidence.map((item) => `- ${item}`),
316
+ "",
317
+ "修复建议:",
318
+ "- 保留字体 / 颜色 / 间距 / 圆角 / 阴影相关改动。",
319
+ "- 不要新增非 token 文件,不要删除现有文件。",
320
+ "- 不要改布局结构、组件树、事件逻辑、数据流或路由。",
321
+ "- 如需统一样式,可新增一个最小 tokens/theme 文件,并只让命中的组件消费它。",
322
+ ...(keepFiles.length > 0 ? [`- 优先保留文件:${keepFiles.join(", ")}`] : []),
323
+ ...(revertFiles.length > 0 ? [`- 优先回退文件:${revertFiles.join(", ")}`] : []),
324
+ ...(scopedStyleFields.length > 0
325
+ ? [`- 这轮只允许继续调整这些视觉字段:${scopedStyleFields.join(", ")}`]
326
+ : []),
327
+ ...(targetElements.length > 0
328
+ ? [`- 这轮只允许继续调整这些元素:${targetElements.join(", ")}`]
329
+ : []),
330
+ ].join("\n");
331
+
332
+ return {
333
+ ok: false,
334
+ summary,
335
+ displayData,
336
+ rawData: {
337
+ success: false,
338
+ ok: false,
339
+ error: true,
340
+ code: "SMALL_VISUAL_SCOPE_EXCEEDED",
341
+ summary,
342
+ requestType: "small-visual-edit",
343
+ issueCodes,
344
+ evidence,
345
+ retryable: true,
346
+ repairPlan: {
347
+ strategy: "targeted-repair",
348
+ scope: "existing-files",
349
+ mode: "preflight-first",
350
+ summary,
351
+ steps: [
352
+ {
353
+ action: "回退超出视觉范围的结构与逻辑修改",
354
+ reason: "当前请求只要求小范围视觉微调,不应顺带重写页面结构或行为逻辑",
355
+ },
356
+ {
357
+ action: "把视觉参数收敛到命中的局部组件或最小 token 层",
358
+ reason: "这样可以保留局部改动并避免继续散落新的硬编码",
359
+ },
360
+ ],
361
+ issueCodes,
362
+ suggestedFiles,
363
+ ...(keepFiles.length > 0 ? { keepFiles } : {}),
364
+ ...(revertFiles.length > 0 ? { revertFiles } : {}),
365
+ ...(tokenFiles.length > 0 ? { preferTokenFiles: tokenFiles } : {}),
366
+ ...(scopedStyleFields.length > 0 ? { targetStyleFields: scopedStyleFields } : {}),
367
+ ...(targetElements.length > 0 ? { targetElements } : {}),
368
+ rerun: ["appPreflight", "appDeploy"],
369
+ },
370
+ },
371
+ };
372
+ };
@@ -0,0 +1,153 @@
1
+ type AppReadSnapshotInput = {
2
+ code?: string | null;
3
+ files?: Array<{ name?: string | null; code?: string | null }> | null;
4
+ };
5
+
6
+ export type AppReadSnapshotKind =
7
+ | "source-files"
8
+ | "single-file-source"
9
+ | "compiled-artifact";
10
+
11
+ export type AppStyleSystemStatus =
12
+ | "design-system"
13
+ | "hardcoded-inline-styles"
14
+ | "unknown";
15
+
16
+ const COMPILED_ARTIFACT_PATTERNS = [
17
+ "<!DOCTYPE html>",
18
+ '<script type="importmap">',
19
+ "react-dom/client",
20
+ "createRoot(",
21
+ "esm.sh/",
22
+ "__toESM(",
23
+ ];
24
+
25
+ export const classifyAppReadSnapshot = (
26
+ input: AppReadSnapshotInput
27
+ ): AppReadSnapshotKind => {
28
+ const files = Array.isArray(input.files)
29
+ ? input.files.filter(
30
+ (file) =>
31
+ !!file &&
32
+ typeof file.name === "string" &&
33
+ typeof file.code === "string" &&
34
+ file.code.trim().length > 0
35
+ )
36
+ : [];
37
+
38
+ if (files.length > 0) {
39
+ return "source-files";
40
+ }
41
+
42
+ const code = typeof input.code === "string" ? input.code.trim() : "";
43
+ if (!code) {
44
+ return "single-file-source";
45
+ }
46
+
47
+ const looksCompiled = COMPILED_ARTIFACT_PATTERNS.some((pattern) =>
48
+ code.includes(pattern)
49
+ );
50
+
51
+ return looksCompiled ? "compiled-artifact" : "single-file-source";
52
+ };
53
+
54
+ export const buildAppReadSnapshotWarning = (
55
+ kind: AppReadSnapshotKind
56
+ ): string | null => {
57
+ if (kind !== "compiled-artifact") return null;
58
+
59
+ return [
60
+ "⚠️ 检测到当前读到的更像“部署产物 / 打包 bundle”,不是原始可维护源码文件。",
61
+ "这类结果通常只适合理解现状,不适合在未告知用户的情况下做局部增量修改。",
62
+ "如果用户只想改一小部分,必须先明确说明当前缺少原始源码快照;未经用户确认,不要直接整站重写后 appDeploy。",
63
+ ].join("\n");
64
+ };
65
+
66
+ const STYLE_SYSTEM_FILE_PATTERN = /(tokens|theme|design-?system)\.(t|j)sx?$/i;
67
+ const STYLE_SYSTEM_EXPORT_PATTERN =
68
+ /\b(?:export\s+const|const)\s+(tokens|theme|designSystem)\b/;
69
+ const INLINE_STYLE_PATTERN = /style=\{\{/g;
70
+ const HARD_CODED_STYLE_VALUE_PATTERN =
71
+ /\b(fontSize|color|backgroundColor|padding|margin|gap|borderRadius|boxShadow|lineHeight)\s*:\s*['"`#0-9a-zA-Z.(]/g;
72
+
73
+ export const analyzeAppStyleSystem = (input: AppReadSnapshotInput): {
74
+ status: AppStyleSystemStatus;
75
+ legacyMigrationRecommended: boolean;
76
+ evidence: string[];
77
+ } => {
78
+ const files = Array.isArray(input.files)
79
+ ? input.files.filter(
80
+ (file): file is { name: string; code: string } =>
81
+ !!file &&
82
+ typeof file.name === "string" &&
83
+ typeof file.code === "string"
84
+ )
85
+ : [];
86
+
87
+ const combinedSource = (
88
+ files.length > 0
89
+ ? files.map((file) => `${file.name}\n${file.code}`).join("\n\n")
90
+ : typeof input.code === "string"
91
+ ? input.code
92
+ : ""
93
+ ).trim();
94
+
95
+ const evidence: string[] = [];
96
+
97
+ if (
98
+ files.some((file) => STYLE_SYSTEM_FILE_PATTERN.test(file.name)) ||
99
+ STYLE_SYSTEM_EXPORT_PATTERN.test(combinedSource)
100
+ ) {
101
+ if (files.some((file) => STYLE_SYSTEM_FILE_PATTERN.test(file.name))) {
102
+ evidence.push("发现 tokens/theme/design-system 文件");
103
+ }
104
+ if (STYLE_SYSTEM_EXPORT_PATTERN.test(combinedSource)) {
105
+ evidence.push("发现 tokens/theme/designSystem 导出");
106
+ }
107
+ return {
108
+ status: "design-system",
109
+ legacyMigrationRecommended: false,
110
+ evidence,
111
+ };
112
+ }
113
+
114
+ const inlineStyleMatches = combinedSource.match(INLINE_STYLE_PATTERN)?.length ?? 0;
115
+ const hardcodedStyleMatches =
116
+ combinedSource.match(HARD_CODED_STYLE_VALUE_PATTERN)?.length ?? 0;
117
+
118
+ if (inlineStyleMatches >= 1 && hardcodedStyleMatches >= 4) {
119
+ evidence.push(`检测到 ${inlineStyleMatches} 处内联 style`);
120
+ evidence.push(`检测到 ${hardcodedStyleMatches} 处硬编码视觉值`);
121
+ return {
122
+ status: "hardcoded-inline-styles",
123
+ legacyMigrationRecommended: true,
124
+ evidence,
125
+ };
126
+ }
127
+
128
+ return {
129
+ status: "unknown",
130
+ legacyMigrationRecommended: false,
131
+ evidence,
132
+ };
133
+ };
134
+
135
+ export const buildAppStyleSystemHint = (
136
+ analysis: ReturnType<typeof analyzeAppStyleSystem>
137
+ ): string | null => {
138
+ if (analysis.status === "design-system") {
139
+ return [
140
+ "🧩 检测到当前应用已经有设计系统 / token 层。",
141
+ "后续视觉微调应优先改这层共享 token,而不是把新数字继续散落回组件。",
142
+ ].join("\n");
143
+ }
144
+
145
+ if (analysis.status === "hardcoded-inline-styles") {
146
+ return [
147
+ "🧩 检测到当前应用更像旧式硬编码样式:多个视觉值直接散落在组件内联 style 中。",
148
+ "如果用户这次只是调字体、颜色、间距、圆角、阴影,默认建议先做一次最小 token 迁移(如新增 tokens.ts / theme 对象),再在 token 层完成修改。",
149
+ ].join("\n");
150
+ }
151
+
152
+ return null;
153
+ };