ltcai 11.1.0 → 11.4.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.
Files changed (289) hide show
  1. package/README.md +47 -54
  2. package/docs/CHANGELOG.md +94 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/FEATURE_AUDIT_v11.2.0.md +393 -0
  6. package/docs/LAYOUT_REBUILD_SPEC.md +9 -1
  7. package/docs/MULTI_AGENT_RUNTIME.md +1 -1
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -2
  10. package/docs/PERMISSION_MODE.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/architecture.md +6 -2
  14. package/docs/kg-schema.md +2 -2
  15. package/docs/v11.3.0_PLAN.md +202 -0
  16. package/docs/v11.4.0_RUST_FOUNDATION_PLAN.md +176 -0
  17. package/lattice_brain/__init__.py +1 -1
  18. package/lattice_brain/gates.py +125 -0
  19. package/lattice_brain/graph/_kg_common/__init__.py +287 -0
  20. package/lattice_brain/graph/_kg_common/extraction.py +516 -0
  21. package/lattice_brain/graph/_kg_common/relations.py +161 -0
  22. package/lattice_brain/graph/_kg_common/text.py +479 -0
  23. package/lattice_brain/graph/discovery_index/__init__.py +35 -0
  24. package/lattice_brain/graph/discovery_index/cleanup.py +182 -0
  25. package/lattice_brain/graph/discovery_index/extract.py +137 -0
  26. package/lattice_brain/graph/discovery_index/scan.py +411 -0
  27. package/lattice_brain/graph/discovery_index/upsert.py +495 -0
  28. package/lattice_brain/graph/fusion.py +35 -4
  29. package/lattice_brain/graph/projection/__init__.py +42 -0
  30. package/lattice_brain/graph/projection/curation.py +500 -0
  31. package/lattice_brain/graph/{projection.py → projection/v2_schema.py} +81 -485
  32. package/lattice_brain/graph/retrieval/__init__.py +54 -0
  33. package/lattice_brain/graph/retrieval/context.py +197 -0
  34. package/lattice_brain/graph/retrieval/graph_view.py +319 -0
  35. package/lattice_brain/graph/retrieval/hybrid.py +488 -0
  36. package/lattice_brain/graph/retrieval/maintenance.py +121 -0
  37. package/lattice_brain/graph/retrieval/signals.py +95 -0
  38. package/lattice_brain/graph/retrieval_vector/__init__.py +42 -0
  39. package/lattice_brain/graph/retrieval_vector/fingerprint.py +97 -0
  40. package/lattice_brain/graph/retrieval_vector/indexing.py +347 -0
  41. package/lattice_brain/graph/retrieval_vector/search.py +560 -0
  42. package/lattice_brain/graph/retrieval_vector/status.py +374 -0
  43. package/lattice_brain/graph/schema.py +9 -0
  44. package/lattice_brain/graph/store.py +9 -0
  45. package/lattice_brain/graph/vector_index/selector.py +32 -2
  46. package/lattice_brain/ingestion/__init__.py +130 -0
  47. package/lattice_brain/ingestion/_contract.py +90 -0
  48. package/lattice_brain/ingestion/constants.py +127 -0
  49. package/lattice_brain/ingestion/folder_scan.py +57 -0
  50. package/lattice_brain/ingestion/folders.py +258 -0
  51. package/lattice_brain/ingestion/hashing.py +26 -0
  52. package/lattice_brain/ingestion/jobs_api.py +107 -0
  53. package/lattice_brain/ingestion/models.py +80 -0
  54. package/lattice_brain/ingestion/pipeline.py +486 -0
  55. package/lattice_brain/ingestion/quality.py +209 -0
  56. package/lattice_brain/ingestion/routing.py +295 -0
  57. package/lattice_brain/multimodal/__init__.py +164 -0
  58. package/lattice_brain/multimodal/audio.py +77 -0
  59. package/lattice_brain/multimodal/common.py +118 -0
  60. package/lattice_brain/{multimodal.py → multimodal/images.py} +22 -262
  61. package/lattice_brain/multimodal/ports.py +169 -0
  62. package/lattice_brain/multimodal/video.py +410 -0
  63. package/lattice_brain/portability/__init__.py +90 -0
  64. package/lattice_brain/portability/_contract.py +42 -0
  65. package/lattice_brain/portability/backups.py +338 -0
  66. package/lattice_brain/portability/bundles.py +136 -0
  67. package/lattice_brain/portability/constants.py +93 -0
  68. package/lattice_brain/portability/fsops.py +138 -0
  69. package/lattice_brain/portability/service.py +41 -0
  70. package/lattice_brain/portability/sharing.py +714 -0
  71. package/lattice_brain/runtime/__init__.py +1 -1
  72. package/lattice_brain/runtime/multi_agent.py +1 -1
  73. package/lattice_brain/sealed_box.py +244 -0
  74. package/lattice_brain/synthesis.py +24 -1
  75. package/latticeai/__init__.py +1 -1
  76. package/latticeai/api/brain_intelligence.py +4 -0
  77. package/latticeai/api/chat.py +11 -0
  78. package/latticeai/api/chat_helpers.py +16 -3
  79. package/latticeai/api/chat_hybrid.py +32 -1
  80. package/latticeai/api/chronicle.py +63 -0
  81. package/latticeai/api/features.py +70 -0
  82. package/latticeai/api/local_files.py +102 -0
  83. package/latticeai/api/portability.py +39 -4
  84. package/latticeai/api/review_queue.py +126 -0
  85. package/latticeai/api/search.py +16 -2
  86. package/latticeai/core/agent/__init__.py +93 -0
  87. package/latticeai/core/agent/_contract.py +79 -0
  88. package/latticeai/core/agent/context.py +57 -0
  89. package/latticeai/core/agent/deps.py +125 -0
  90. package/latticeai/core/agent/execution.py +622 -0
  91. package/latticeai/core/agent/planning.py +145 -0
  92. package/latticeai/core/agent/recovery.py +157 -0
  93. package/latticeai/core/agent/runtime.py +210 -0
  94. package/latticeai/core/agent/verification.py +231 -0
  95. package/latticeai/core/config.py +4 -1
  96. package/latticeai/core/context_builder.py +6 -3
  97. package/latticeai/core/embedding_providers/__init__.py +151 -0
  98. package/latticeai/core/embedding_providers/base.py +199 -0
  99. package/latticeai/core/embedding_providers/captions.py +162 -0
  100. package/latticeai/core/embedding_providers/profiles.py +126 -0
  101. package/latticeai/core/embedding_providers/text.py +350 -0
  102. package/latticeai/core/embedding_providers/vision.py +352 -0
  103. package/latticeai/core/file_generation/__init__.py +115 -0
  104. package/latticeai/core/file_generation/bundles.py +76 -0
  105. package/latticeai/core/file_generation/extraction.py +154 -0
  106. package/latticeai/core/file_generation/inference.py +235 -0
  107. package/latticeai/core/file_generation/orchestration.py +152 -0
  108. package/latticeai/core/file_generation/prompting.py +117 -0
  109. package/latticeai/core/file_generation/repair.py +114 -0
  110. package/latticeai/core/file_generation/sanitize.py +61 -0
  111. package/latticeai/core/file_generation/validation.py +201 -0
  112. package/latticeai/core/legacy_compatibility.py +1 -1
  113. package/latticeai/core/marketplace.py +1 -1
  114. package/latticeai/core/messages.py +152 -0
  115. package/latticeai/core/model_compat.py +73 -2
  116. package/latticeai/core/workspace_os_constants.py +1 -1
  117. package/latticeai/integrations/telegram_bot/__init__.py +123 -0
  118. package/latticeai/integrations/telegram_bot/__main__.py +17 -0
  119. package/latticeai/integrations/telegram_bot/config.py +86 -0
  120. package/latticeai/integrations/telegram_bot/dispatch.py +311 -0
  121. package/latticeai/integrations/telegram_bot/flows.py +478 -0
  122. package/latticeai/integrations/telegram_bot/helpers.py +322 -0
  123. package/latticeai/integrations/telegram_bot/screens.py +394 -0
  124. package/latticeai/models/model_providers.py +12 -4
  125. package/latticeai/models/router/__init__.py +88 -0
  126. package/latticeai/models/router/_contract.py +66 -0
  127. package/latticeai/models/router/branding.py +56 -0
  128. package/latticeai/models/router/catalog.py +69 -0
  129. package/latticeai/models/router/documents.py +199 -0
  130. package/latticeai/models/router/errors.py +37 -0
  131. package/latticeai/models/router/generation.py +258 -0
  132. package/latticeai/models/router/loading.py +291 -0
  133. package/latticeai/models/router/local_models.py +85 -0
  134. package/latticeai/models/router/registry.py +147 -0
  135. package/latticeai/runtime/build_phases/__init__.py +82 -0
  136. package/latticeai/runtime/build_phases/features.py +407 -0
  137. package/latticeai/runtime/build_phases/foundation.py +555 -0
  138. package/latticeai/runtime/build_phases/web.py +492 -0
  139. package/latticeai/runtime/chat_wiring.py +4 -0
  140. package/latticeai/runtime/feature_toggle_wiring.py +163 -0
  141. package/latticeai/runtime/router_registration.py +11 -0
  142. package/latticeai/runtime/runtime_context.py +1 -0
  143. package/latticeai/services/app_context.py +8 -0
  144. package/latticeai/services/architecture_readiness.py +48 -19
  145. package/latticeai/services/automation_intelligence.py +22 -2
  146. package/latticeai/services/brain_intelligence/__init__.py +58 -0
  147. package/latticeai/services/brain_intelligence/_contract.py +71 -0
  148. package/latticeai/services/brain_intelligence/consistency.py +193 -0
  149. package/latticeai/services/brain_intelligence/constants.py +47 -0
  150. package/latticeai/services/brain_intelligence/digest.py +258 -0
  151. package/latticeai/services/brain_intelligence/health.py +331 -0
  152. package/latticeai/services/brain_intelligence/proposals.py +264 -0
  153. package/latticeai/services/brain_intelligence/sampling.py +84 -0
  154. package/latticeai/services/brain_intelligence/service.py +48 -0
  155. package/latticeai/services/chronicle.py +557 -0
  156. package/latticeai/services/command_center.py +10 -4
  157. package/latticeai/services/feature_toggles.py +502 -0
  158. package/latticeai/services/folder_watch.py +122 -1
  159. package/latticeai/services/hybrid_chat.py +56 -5
  160. package/latticeai/services/interop_bridges.py +978 -0
  161. package/latticeai/services/memory_service/__init__.py +52 -0
  162. package/latticeai/services/memory_service/_contract.py +100 -0
  163. package/latticeai/services/memory_service/brief.py +431 -0
  164. package/latticeai/services/memory_service/constants.py +57 -0
  165. package/latticeai/services/memory_service/maintenance.py +138 -0
  166. package/latticeai/services/memory_service/manager.py +186 -0
  167. package/latticeai/services/memory_service/proof.py +136 -0
  168. package/latticeai/services/memory_service/recall.py +225 -0
  169. package/latticeai/services/memory_service/service.py +48 -0
  170. package/latticeai/services/memory_service/stores.py +110 -0
  171. package/latticeai/services/model_capability_registry.py +434 -261
  172. package/latticeai/services/model_catalog.py +95 -61
  173. package/latticeai/services/model_recommendation.py +18 -11
  174. package/latticeai/services/model_runtime/__init__.py +322 -0
  175. package/latticeai/services/model_runtime/cloud.py +87 -0
  176. package/latticeai/services/model_runtime/download.py +282 -0
  177. package/latticeai/services/model_runtime/engines.py +341 -0
  178. package/latticeai/services/model_runtime/loading.py +178 -0
  179. package/latticeai/services/model_runtime/service.py +129 -0
  180. package/latticeai/services/model_runtime/state.py +131 -0
  181. package/latticeai/services/model_runtime/status.py +255 -0
  182. package/latticeai/services/multimodal_ports.py +26 -1
  183. package/latticeai/services/obsidian_bridge.py +16 -25
  184. package/latticeai/services/product_readiness.py +15 -7
  185. package/latticeai/services/search_service.py +149 -2
  186. package/latticeai/services/tool_dispatch.py +4 -0
  187. package/latticeai/setup/auto_setup.py +27 -30
  188. package/latticeai/setup/wizard/__init__.py +126 -0
  189. package/latticeai/setup/wizard/catalog.py +172 -0
  190. package/latticeai/setup/wizard/detect.py +323 -0
  191. package/latticeai/setup/wizard/install.py +348 -0
  192. package/latticeai/setup/wizard/paths.py +168 -0
  193. package/latticeai/setup/wizard/plans.py +74 -0
  194. package/latticeai/setup/wizard/recommend.py +320 -0
  195. package/package.json +6 -2
  196. package/scripts/bump_version.py +14 -0
  197. package/scripts/capture_release_evidence.mjs +33 -21
  198. package/scripts/check_current_release_docs.mjs +1 -1
  199. package/scripts/check_i18n_namespace_coverage.mjs +41 -4
  200. package/scripts/check_max_file_lines.mjs +102 -0
  201. package/scripts/check_release_evidence_bound.mjs +30 -15
  202. package/scripts/check_screenshot_pixel_delta.py +34 -4
  203. package/scripts/check_server_i18n.mjs +2 -0
  204. package/scripts/generate_rust_parity_fixtures.py +562 -0
  205. package/scripts/lib/mock_server_fingerprint.mjs +94 -0
  206. package/scripts/release_screen_claims.json +44 -2
  207. package/scripts/verify_hf_model_registry.py +253 -218
  208. package/src-tauri/Cargo.lock +361 -3
  209. package/src-tauri/Cargo.toml +6 -1
  210. package/src-tauri/src/backend.rs +349 -0
  211. package/src-tauri/src/folder.rs +33 -0
  212. package/src-tauri/src/main.rs +97 -399
  213. package/src-tauri/tauri.conf.json +1 -1
  214. package/static/app/asset-manifest.json +41 -37
  215. package/static/app/assets/Act-yYpYnn0v.js +1 -0
  216. package/static/app/assets/AdminConsole-DL3Cr5pL.js +1 -0
  217. package/static/app/assets/{Brain-CzCsI1mi.js → Brain-C1HBN0Wf.js} +2 -2
  218. package/static/app/assets/BrainHome-DoXRhUUC.js +2 -0
  219. package/static/app/assets/BrainSignals-6yR6ir5t.js +1 -0
  220. package/static/app/assets/Capture-CFIRsFNE.js +1 -0
  221. package/static/app/assets/Chronicle-BZbEgiwN.js +1 -0
  222. package/static/app/assets/CommandPalette-D2pMxC2I.js +1 -0
  223. package/static/app/assets/Library-DwO3yZST.js +1 -0
  224. package/static/app/assets/{LivingBrain-BXMWIK_2.js → LivingBrain-Jn1GK0-S.js} +1 -1
  225. package/static/app/assets/ProductFlow-B-w1R4Oo.js +1 -0
  226. package/static/app/assets/ReviewCard-6B27X8Vg.js +3 -0
  227. package/static/app/assets/System-DW8F-2xL.js +1 -0
  228. package/static/app/assets/arrow-left-DXvKg9U6.js +1 -0
  229. package/static/app/assets/{bot-4BvN07ux.js → bot-IM_E_Y12.js} +1 -1
  230. package/static/app/assets/brain-Ci1CkWjM.js +1 -0
  231. package/static/app/assets/{button-CDjtnAoU.js → button-COwyqfHM.js} +1 -1
  232. package/static/app/assets/circle-check-DfInj-qD.js +1 -0
  233. package/static/app/assets/{circle-pause-D_RMn7tp.js → circle-pause-DEM4A1Y5.js} +1 -1
  234. package/static/app/assets/{circle-play-B5OpB8ae.js → circle-play-C9djDuLd.js} +1 -1
  235. package/static/app/assets/{cpu-BIlWInHf.js → cpu-DFdo1gw-.js} +1 -1
  236. package/static/app/assets/{download-BtjXfL3z.js → download-SnJL6oqk.js} +1 -1
  237. package/static/app/assets/{folder-open-DefMpxI2.js → folder-open-CqZeDkjE.js} +1 -1
  238. package/static/app/assets/{hard-drive-BQ8NZVkw.js → hard-drive-j1jJXYYf.js} +1 -1
  239. package/static/app/assets/{index-vtEfYvQY.css → index-BLPb5lmE.css} +1 -1
  240. package/static/app/assets/index-_u5iUHDr.js +10 -0
  241. package/static/app/assets/input-B0lPdRQZ.js +1 -0
  242. package/static/app/assets/link-2-CoFbooHS.js +1 -0
  243. package/static/app/assets/{permissionCopy-BqZ5tsgL.js → permissionCopy-BsyLxtao.js} +1 -1
  244. package/static/app/assets/primitives-DEbN-d6p.js +1 -0
  245. package/static/app/assets/search-BybIWPNd.js +1 -0
  246. package/static/app/assets/{share-2-D5zg_0fY.js → share-2-CVtZ_ewX.js} +1 -1
  247. package/static/app/assets/{shield-alert-B5pZzkUb.js → shield-alert-CBi2GNWM.js} +1 -1
  248. package/static/app/assets/{textarea-nEVIweKY.js → textarea-DNMpB5ih.js} +1 -1
  249. package/static/app/assets/{useFocusTrap-Cm99AHlz.js → useFocusTrap-C83t3GXF.js} +1 -1
  250. package/static/app/assets/useMutation-DtbJDoyz.js +1 -0
  251. package/static/app/assets/{useQuery-Dm__N6bL.js → useQuery-Dcp1OChy.js} +1 -1
  252. package/static/app/assets/utils-BlZr7Pd4.js +4 -0
  253. package/static/app/assets/workspace-jJY4RuAV.js +1 -0
  254. package/static/app/index.html +4 -4
  255. package/static/sw.js +1 -1
  256. package/lattice_brain/graph/_kg_common.py +0 -1331
  257. package/lattice_brain/graph/discovery_index.py +0 -1141
  258. package/lattice_brain/graph/retrieval.py +0 -1120
  259. package/lattice_brain/graph/retrieval_vector.py +0 -1293
  260. package/lattice_brain/ingestion.py +0 -1377
  261. package/lattice_brain/portability.py +0 -1210
  262. package/latticeai/core/agent.py +0 -1412
  263. package/latticeai/core/embedding_providers.py +0 -1196
  264. package/latticeai/core/file_generation.py +0 -1047
  265. package/latticeai/integrations/telegram_bot.py +0 -1390
  266. package/latticeai/models/router.py +0 -1007
  267. package/latticeai/runtime/build_phases.py +0 -1422
  268. package/latticeai/services/brain_intelligence.py +0 -967
  269. package/latticeai/services/memory_service.py +0 -1177
  270. package/latticeai/services/model_runtime.py +0 -1281
  271. package/latticeai/setup/wizard.py +0 -1277
  272. package/static/app/assets/Act-D0HWqtn0.js +0 -1
  273. package/static/app/assets/AdminConsole-D-QDW-A4.js +0 -1
  274. package/static/app/assets/BrainHome-Btns-_TA.js +0 -2
  275. package/static/app/assets/BrainSignals-2dHQNkns.js +0 -1
  276. package/static/app/assets/Capture-CT8v1StE.js +0 -1
  277. package/static/app/assets/CommandPalette-DoLXC2KH.js +0 -1
  278. package/static/app/assets/Library-DDoxFE5c.js +0 -1
  279. package/static/app/assets/ProductFlow-DOYf7JIs.js +0 -1
  280. package/static/app/assets/ReviewCard-COQsqidK.js +0 -3
  281. package/static/app/assets/System-BRllvYXd.js +0 -1
  282. package/static/app/assets/arrow-left-DnyMzss-.js +0 -1
  283. package/static/app/assets/brain-uMb_5hnO.js +0 -1
  284. package/static/app/assets/index-0AvoEBzJ.js +0 -10
  285. package/static/app/assets/input-B_5ZJ9oy.js +0 -1
  286. package/static/app/assets/primitives-CVwew78r.js +0 -1
  287. package/static/app/assets/search-DkhnOKZt.js +0 -1
  288. package/static/app/assets/utils-DcDMoZIe.js +0 -4
  289. package/static/app/assets/workspace-LtRRSKTf.js +0 -1
@@ -14,7 +14,7 @@ every capability is honestly ``None``.
14
14
  from __future__ import annotations
15
15
 
16
16
  import os
17
- from typing import Any, Callable, Dict, Optional
17
+ from typing import Any, Callable, Dict, List, Optional
18
18
 
19
19
  from lattice_brain.ingestion import ALLOW_MULTIMODAL_ENV
20
20
  from lattice_brain.multimodal import MODALITY_IMAGE, MultimodalPorts
@@ -65,11 +65,35 @@ def build_multimodal_ports(
65
65
  captioner=vision_caption_port(captioner),
66
66
  vision_embedder=resolved.as_port(),
67
67
  transcriber=transcriber,
68
+ text_to_image_embedder=text_to_image_port(provider),
68
69
  vision_model_id=provider.model_id if provider is not None else "",
69
70
  vision_space=resolved.space,
70
71
  )
71
72
 
72
73
 
74
+ def text_to_image_port(
75
+ provider: Any,
76
+ ) -> Optional[Callable[[str], List[float]]]:
77
+ """A *query-text* → image-space vector port, only from a shared-space model.
78
+
79
+ This is what lets a typed question reach a picture through the image index
80
+ instead of only through its OCR text. It exists as its own port precisely
81
+ because most vision models cannot do it: a CLIP image vector and a BGE text
82
+ vector are not comparable, and `VisionEmbeddingProvider.embed_batch` refuses
83
+ outright unless the model declares a shared space. An image-space model
84
+ therefore yields ``None`` here — the honest answer, and the one the search
85
+ surface reports rather than silently ranking on a meaningless number.
86
+ """
87
+ if provider is None or not getattr(provider, "shares_text_space", False):
88
+ return None
89
+
90
+ def _embed(query: str) -> List[float]:
91
+ vectors = provider.embed_batch([str(query or "")])
92
+ return [float(value) for value in (vectors[0] if vectors else [])]
93
+
94
+ return _embed
95
+
96
+
73
97
  def describe_multimodal(ports: MultimodalPorts) -> Dict[str, Any]:
74
98
  """Operator-facing status: enabled, plus what is actually wired."""
75
99
  return {"enabled": multimodal_enabled(), **ports.describe()}
@@ -84,4 +108,5 @@ __all__ = [
84
108
  "build_multimodal_ports",
85
109
  "describe_multimodal",
86
110
  "multimodal_enabled",
111
+ "text_to_image_port",
87
112
  ]
@@ -35,7 +35,6 @@ be a lie in the summary.
35
35
 
36
36
  from __future__ import annotations
37
37
 
38
- import json
39
38
  import os
40
39
  import re
41
40
  from dataclasses import dataclass, field
@@ -48,6 +47,7 @@ from urllib.parse import unquote
48
47
  # store-extracted topic the same node instead of two nodes with one label.
49
48
  from lattice_brain.graph.ingest import _scoped_slug_id
50
49
  from lattice_brain.ingestion import IngestionItem
50
+ from latticeai.services.interop_bridges import edge_row, topic_row
51
51
 
52
52
  SOURCE_TYPE = "obsidian"
53
53
  NOTE_EXTENSIONS = frozenset({".md", ".markdown"})
@@ -567,15 +567,9 @@ class ObsidianVaultBridge:
567
567
  "index": outcome.get("index"),
568
568
  }
569
569
 
570
- @staticmethod
571
- def _edge_row(from_id: str, to_id: str, relation: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
572
- return {
573
- "from_node": from_id,
574
- "to_node": to_id,
575
- "type": relation,
576
- "weight": 1.0,
577
- "metadata_json": json.dumps(metadata, ensure_ascii=False),
578
- }
570
+ # Row shapes are shared with every other bridge (v11.2.0): two copies of
571
+ # "what an edge row looks like" is two places to forget an encoding.
572
+ _edge_row = staticmethod(edge_row)
579
573
 
580
574
  @staticmethod
581
575
  def _topic_row(
@@ -586,21 +580,18 @@ class ObsidianVaultBridge:
586
580
  owner: Optional[str],
587
581
  workspace_id: Optional[str],
588
582
  ) -> Dict[str, Any]:
589
- metadata = {
590
- "topic": tag,
591
- "source": SOURCE_TYPE,
592
- "vault": vault,
593
- "owner": owner,
594
- "workspace_id": workspace_id,
595
- }
596
- return {
597
- "id": topic_id,
598
- "type": TOPIC_NODE_TYPE,
599
- "title": tag,
600
- "summary": f"Obsidian tag #{tag}",
601
- "metadata_json": json.dumps(metadata, ensure_ascii=False),
602
- "raw_json": "{}",
603
- }
583
+ return topic_row(
584
+ topic_id,
585
+ tag,
586
+ summary=f"Obsidian tag #{tag}",
587
+ metadata={
588
+ "topic": tag,
589
+ "source": SOURCE_TYPE,
590
+ "vault": vault,
591
+ "owner": owner,
592
+ "workspace_id": workspace_id,
593
+ },
594
+ )
604
595
 
605
596
 
606
597
  __all__ = [
@@ -18,7 +18,7 @@ from typing import Any, Dict, List
18
18
 
19
19
  from latticeai.services.architecture_readiness import architecture_readiness
20
20
 
21
- PRODUCT_VERSION_TARGET = "11.1.0"
21
+ PRODUCT_VERSION_TARGET = "11.4.0"
22
22
 
23
23
 
24
24
  @dataclass(frozen=True)
@@ -44,7 +44,10 @@ PRODUCT_GATES: List[ProductGate] = [
44
44
  "frontend/src/features/brain/BrainHome.tsx",
45
45
  "frontend/src/App.tsx::brain-mobile-nav",
46
46
  "latticeai/setup/auto_setup.py",
47
- "latticeai/setup/wizard.py",
47
+ # v11.3.0: the wizard is a package (paths / detect / plans /
48
+ # catalog / recommend / install); the recommender is the piece
49
+ # first-run actually shows.
50
+ "latticeai/setup/wizard/recommend.py",
48
51
  ],
49
52
  ),
50
53
  ProductGate(
@@ -64,8 +67,11 @@ PRODUCT_GATES: List[ProductGate] = [
64
67
  "latticeai/api/chat_intents.py::direct_write_file",
65
68
  # v9.9.2 ArtifactWritePipeline: every write path shares the same
66
69
  # extract → validate → repair guarantee, proven by the FG harness.
67
- "latticeai/core/file_generation.py::sanitize_write_content",
68
- "latticeai/core/agent.py::content_sanitize",
70
+ # v11.3.0: both modules became packages — the write-side door is
71
+ # its own submodule, and the agent's use of it lives in the
72
+ # EXECUTE phase.
73
+ "latticeai/core/file_generation/sanitize.py::sanitize_write_content",
74
+ "latticeai/core/agent/execution.py::content_sanitize",
69
75
  "tests/unit/test_artifact_write_scenarios.py::test_fg06_agent_dispatch_strips_fences_from_write_file_content",
70
76
  "tests/unit/test_chat_telegram_decoupling.py::test_chat_file_creation_intent_writes_real_file",
71
77
  ],
@@ -110,12 +116,14 @@ PRODUCT_GATES: List[ProductGate] = [
110
116
  f"docs/CHANGELOG.md::## [{PRODUCT_VERSION_TARGET}]",
111
117
  "FEATURE_STATUS.md",
112
118
  f"RELEASE_NOTES_v{PRODUCT_VERSION_TARGET}.md",
113
- "latticeai/core/agent.py::SingleAgentRuntime",
119
+ # v11.3.0: the single-agent loop is a package; the composed
120
+ # class and its boundary contract live in the runtime half.
121
+ "latticeai/core/agent/runtime.py::SingleAgentRuntime",
114
122
  "lattice_brain/runtime/agent_runtime.py::class AgentRuntime",
115
123
  "lattice_brain/runtime/contracts.py::runtime-boundary/v1",
116
124
  "lattice_brain/runtime/contracts.py::RuntimeBoundaryProtocol",
117
125
  "lattice_brain/runtime/agent_runtime.py::def boundary",
118
- "latticeai/core/agent.py::def boundary",
126
+ "latticeai/core/agent/runtime.py::def boundary",
119
127
  "latticeai/services/architecture_readiness.py::lattice-architecture-contract/v1",
120
128
  "latticeai/services/tool_dispatch.py::rollback_file",
121
129
  ],
@@ -148,7 +156,7 @@ PRODUCT_GATES: List[ProductGate] = [
148
156
  "scripts/brain_quality_eval.py",
149
157
  "scripts/product_readiness.py",
150
158
  "tests/unit/test_product_readiness.py",
151
- "tests/visual/v3.spec.js::Brain Chat Home",
159
+ "tests/visual/v3.surfaces.spec.js::Brain Chat Home",
152
160
  ".github/workflows/ci.yml::scripts/product_readiness.py",
153
161
  ".github/workflows/release.yml::npm run lint",
154
162
  ],
@@ -8,9 +8,11 @@ from __future__ import annotations
8
8
 
9
9
  from dataclasses import dataclass
10
10
  from datetime import datetime
11
- from typing import Any, Dict, List, Mapping, Optional
11
+ from typing import Any, Dict, List, Mapping, Optional, Tuple
12
12
 
13
+ from lattice_brain.gates import FeatureGate
13
14
  from lattice_brain.graph._kg_fsutil import _parse_iso, _recency_score
15
+ from lattice_brain.graph.image_vectors import DEFAULT_IMAGE_FUSION_WEIGHT
14
16
  from lattice_brain.graph.retrieval_policy import resolve_policy
15
17
 
16
18
  DEFAULT_HYBRID_WEIGHTS = {
@@ -19,6 +21,33 @@ DEFAULT_HYBRID_WEIGHTS = {
19
21
  "graph": 0.25,
20
22
  }
21
23
 
24
+ # ── text query → image space (v11.2.0) ───────────────────────────────────────
25
+ # 11.1.0 shipped image late fusion as API-only, because the two vector spaces
26
+ # are not comparable and nothing produced a query vector for the image index.
27
+ # A *shared-space* vision model can produce one, and this is the switch that
28
+ # lets the search API do it automatically. Off by default and resolved when
29
+ # asked, so a settings toggle can move it without a restart; with it off the
30
+ # hybrid response is byte-identical to 11.1.0's.
31
+ TEXT_IMAGE_FUSION_ENV = "LATTICEAI_TEXT_IMAGE_FUSION"
32
+ IMAGE_QUERY_FUSION_GATE = FeatureGate(
33
+ TEXT_IMAGE_FUSION_ENV,
34
+ default=False,
35
+ name="text_image_fusion",
36
+ detail=(
37
+ "Typed questions can also be scored against the image index when a "
38
+ "shared-space vision model is configured."
39
+ ),
40
+ )
41
+ IMAGE_FUSION_UNAVAILABLE = (
42
+ "no shared-space vision model is configured, so a typed question cannot be "
43
+ "scored against image vectors; pictures are still found through their OCR "
44
+ "text and captions"
45
+ )
46
+ IMAGE_FUSION_DISABLED = (
47
+ "automatic image fusion is off for this install "
48
+ f"({TEXT_IMAGE_FUSION_ENV}); the caller may still supply an image vector"
49
+ )
50
+
22
51
 
23
52
  def _clean(text: Any, limit: int = 1000) -> str:
24
53
  return " ".join(str(text or "").split())[:limit]
@@ -31,6 +60,115 @@ def _result_key(result: Mapping[str, Any]) -> str:
31
60
  @dataclass
32
61
  class SearchService:
33
62
  graph_store: Any
63
+ #: ``(query_text) -> image-space vector``. Defaults to the port the store
64
+ #: already carries (``multimodal_ports.text_to_image_embedder``), so no new
65
+ #: wiring is needed; the field exists so a test — or an install with its
66
+ #: own encoder — can supply one directly.
67
+ image_query_embedder: Any = None
68
+
69
+ # ── image-space query channel (v11.2.0) ──────────────────────────────
70
+ def image_query_status(self) -> Dict[str, Any]:
71
+ """Whether a typed question can reach the image index here, and why not."""
72
+ port, detail = self._image_query_port()
73
+ return {
74
+ "available": port is not None,
75
+ "gate": IMAGE_QUERY_FUSION_GATE.describe(),
76
+ "detail": detail,
77
+ }
78
+
79
+ def _image_query_port(self) -> Tuple[Any, Optional[str]]:
80
+ """The text→image port and, when there is none, the honest reason."""
81
+ if self.image_query_embedder is not None:
82
+ return self.image_query_embedder, None
83
+ ports = getattr(self.graph_store, "multimodal_ports", None)
84
+ port = getattr(ports, "text_to_image_embedder", None)
85
+ return (port, None) if port is not None else (None, IMAGE_FUSION_UNAVAILABLE)
86
+
87
+ def _image_query_vector(self, query: str) -> Tuple[Optional[List[float]], Optional[str]]:
88
+ port, detail = self._image_query_port()
89
+ if port is None:
90
+ return None, detail
91
+ try:
92
+ vector = [float(value) for value in (port(query) or [])]
93
+ except Exception as exc: # noqa: BLE001 — a broken encoder never fails a search
94
+ return None, f"the vision model could not embed the query: {exc}"
95
+ if not vector:
96
+ return None, "the vision model returned an empty query vector"
97
+ return vector, None
98
+
99
+ def _image_channel(
100
+ self, query: str, matches: List[Dict[str, Any]], *, requested: bool, limit: int
101
+ ) -> Optional[Dict[str, Any]]:
102
+ """Late-fuse the image index into an already-ranked match list.
103
+
104
+ Returns ``None`` when the caller did not ask, which is what keeps the
105
+ response byte-identical for everyone who did not opt in. When it *was*
106
+ asked for and could not run, it returns the reason rather than silently
107
+ producing the same answer as before.
108
+ """
109
+ if not requested:
110
+ return None
111
+ report: Dict[str, Any] = {
112
+ "requested": True,
113
+ "applied": False,
114
+ "weight": DEFAULT_IMAGE_FUSION_WEIGHT,
115
+ "candidates": 0,
116
+ "fused": 0,
117
+ "detail": None,
118
+ }
119
+ if not IMAGE_QUERY_FUSION_GATE.enabled():
120
+ report["detail"] = IMAGE_FUSION_DISABLED
121
+ return {"image_fusion": report}
122
+ vector, detail = self._image_query_vector(query)
123
+ if vector is None:
124
+ report["detail"] = detail
125
+ return {"image_fusion": report}
126
+ from lattice_brain.graph.image_vectors import image_similarity_search
127
+
128
+ try:
129
+ found = image_similarity_search(
130
+ self.graph_store, vector, top_k=max(1, int(limit) * 2)
131
+ )
132
+ except Exception as exc: # noqa: BLE001 — the text answer must survive
133
+ report["detail"] = f"image index unavailable: {exc}"
134
+ return {"image_fusion": report}
135
+ report["candidates"] = int(found.get("candidates") or 0)
136
+ scores = {
137
+ str(match["node_id"]): float(match["score"])
138
+ for match in found.get("matches") or []
139
+ }
140
+ report["fused"] = self._blend_image_scores(matches, scores)
141
+ report["applied"] = True
142
+ report["detail"] = found.get("detail")
143
+ return {"image_fusion": report}
144
+
145
+ @staticmethod
146
+ def _blend_image_scores(
147
+ matches: List[Dict[str, Any]],
148
+ scores: Dict[str, float],
149
+ *,
150
+ weight: float = DEFAULT_IMAGE_FUSION_WEIGHT,
151
+ ) -> int:
152
+ """The graph layer's blend, over this layer's ``source_scores`` shape.
153
+
154
+ Late fusion by construction: the two rankings were produced
155
+ independently and only the final numbers meet, so neither space is ever
156
+ asked to interpret the other's.
157
+ """
158
+ touched = 0
159
+ for match in matches:
160
+ raw = scores.get(_result_key(match))
161
+ if raw is None:
162
+ continue
163
+ touched += 1
164
+ match.setdefault("source_scores", {})["image"] = round(raw, 6)
165
+ blended = (1.0 - weight) * float(match.get("score") or 0.0) + weight * raw
166
+ match["score"] = round(blended, 6)
167
+ if touched:
168
+ matches.sort(key=lambda item: float(item.get("score") or 0.0), reverse=True)
169
+ for rank, match in enumerate(matches, start=1):
170
+ match["rank"] = rank
171
+ return touched
34
172
 
35
173
  def _require_graph(self) -> Any:
36
174
  if self.graph_store is None:
@@ -410,6 +548,7 @@ class SearchService:
410
548
  weights: Optional[Mapping[str, float]] = None,
411
549
  allowed_workspaces=None,
412
550
  include_legacy_global: bool = False,
551
+ image_fusion: bool = False,
413
552
  ) -> Dict[str, Any]:
414
553
  # Single retrieval policy (review Wave 0.2): when the caller does not
415
554
  # pin explicit weights, resolve the query class + per-class channel
@@ -513,7 +652,10 @@ class SearchService:
513
652
  }
514
653
  if query_class is not None:
515
654
  match["fusion"]["query_class"] = query_class
516
- return {
655
+ multimodal = self._image_channel(
656
+ query, matches, requested=image_fusion, limit=limit,
657
+ )
658
+ report: Dict[str, Any] = {
517
659
  "query": query,
518
660
  "mode": "hybrid",
519
661
  "query_class": query_class,
@@ -529,6 +671,11 @@ class SearchService:
529
671
  },
530
672
  "matches": matches,
531
673
  }
674
+ # Additive key only when the caller asked, so an untouched response is
675
+ # the one 11.1.0 returned.
676
+ if multimodal is not None:
677
+ report["multimodal"] = multimodal
678
+ return report
532
679
 
533
680
  def graph(
534
681
  self,
@@ -486,6 +486,7 @@ def build_agent_runtime(
486
486
  brain_memory: Any = None,
487
487
  dispatch_service: ToolDispatchService = DEFAULT_TOOL_DISPATCH_SERVICE,
488
488
  permission_mode: Any = None,
489
+ self_model_summary: Any = None,
489
490
  ) -> SingleAgentRuntime:
490
491
  ensure_agent_root()
491
492
  mode = permission_mode if permission_mode is not None else dispatch_service.permission_mode
@@ -513,6 +514,9 @@ def build_agent_runtime(
513
514
  hooks=hooks,
514
515
  brain_memory=brain_memory,
515
516
  permission_mode=mode,
517
+ # v11.2.0: what the Brain knows about its owner reaches the agent loop.
518
+ # ``None`` keeps the pre-11.2.0 prompt bytes exactly.
519
+ self_model_summary=self_model_summary,
516
520
  )
517
521
  return SingleAgentRuntime(deps)
518
522
 
@@ -399,36 +399,36 @@ class Recommendation:
399
399
  return asdict(self)
400
400
 
401
401
 
402
- # 모델 카탈로그. PPT 슬라이드 16 의 "추천 모델" 열과 동기화.
402
+ # 모델 카탈로그 — RAM/VRAM 내림차순. 첫 번째로 조건을 만족하는 항목이 선택된다.
403
+ # 모든 repo id 는 2026-08-10 Hugging Face API 로 확인했다(존재·비gated·정확한 대소문자).
404
+ # OS 오버헤드(~4-6 GB) + KV 캐시 여유를 감안한 보수적 RAM 임계값.
403
405
  _MODEL_CATALOG: List[Dict[str, Any]] = [
404
- # (min_ram_mb, min_vram_mb, model_id, quant, runtime_preference)
405
- # OS 오버헤드(~4-6 GB) + KV 캐시 여유를 감안한 보수적 RAM 임계값
406
+ # 대형 (48GB+): 최상위 dense / Gemma 4 최대 모델
406
407
  {"ram": 64 * 1024, "vram": 32 * 1024,
407
408
  "id": "mlx-community/gemma-4-31b-it-4bit", "q": "4bit", "multimodal": True},
408
- {"ram": 64 * 1024, "vram": 32 * 1024,
409
- "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", "q": "q4_K_M", "multimodal": True},
410
409
  {"ram": 48 * 1024, "vram": 24 * 1024,
411
- "id": "mlx-community/gemma-4-31b-it-4bit", "q": "4bit", "multimodal": True},
410
+ "id": "mlx-community/Qwen3.6-27B-4bit", "q": "4bit", "multimodal": True},
411
+ # MoE (32GB+)
412
+ {"ram": 40 * 1024, "vram": 24 * 1024,
413
+ "id": "mlx-community/Qwen3.6-35B-A3B-4bit", "q": "4bit", "multimodal": True},
412
414
  {"ram": 32 * 1024, "vram": 16 * 1024,
413
415
  "id": "mlx-community/gemma-4-26b-a4b-it-4bit", "q": "4bit", "multimodal": True},
414
- {"ram": 48 * 1024, "vram": 24 * 1024,
415
- "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", "q": "q4_K_M", "multimodal": True},
416
- {"ram": 24 * 1024, "vram": 12 * 1024,
417
- "id": "mlx-community/Llama-4-Scout-17B-16E-Instruct-4bit", "q": "4bit", "multimodal": True},
418
- {"ram": 16 * 1024, "vram": 8 * 1024,
419
- "id": "mlx-community/gemma-4-12b-it-4bit", "q": "4bit", "multimodal": True},
420
- {"ram": 32 * 1024, "vram": 16 * 1024,
421
- "id": "Qwen/Qwen3-VL-8B-Instruct", "q": "q5_K_M", "multimodal": True},
416
+ # 범용 (24GB)
422
417
  {"ram": 24 * 1024, "vram": 12 * 1024,
423
- "id": "Qwen/Qwen3-VL-8B-Instruct", "q": "q4_K_M", "multimodal": True},
418
+ "id": "mlx-community/gpt-oss-20b-MXFP4-Q8", "q": "MXFP4-Q8", "multimodal": False},
419
+ # 중형 (16GB)
424
420
  {"ram": 16 * 1024, "vram": 8 * 1024,
425
- "id": "Qwen/Qwen3-VL-8B-Instruct", "q": "q4_K_M", "multimodal": True},
421
+ "id": "mlx-community/gemma-4-12B-it-4bit", "q": "4bit", "multimodal": True},
426
422
  {"ram": 12 * 1024, "vram": 6 * 1024,
427
- "id": "Qwen/Qwen3-VL-4B-Instruct", "q": "q4_K_M", "multimodal": True},
423
+ "id": "mlx-community/Qwen3.5-9B-MLX-4bit", "q": "4bit", "multimodal": True},
424
+ # 경량 (10GB) / 초경량 (8GB 이하)
425
+ {"ram": 10 * 1024, "vram": 6 * 1024,
426
+ "id": "mlx-community/gemma-4-e4b-it-4bit", "q": "4bit", "multimodal": True},
428
427
  {"ram": 8 * 1024, "vram": 4 * 1024,
429
- "id": "Qwen/Qwen3-VL-4B-Instruct", "q": "q4_K_M", "multimodal": True},
428
+ "id": "mlx-community/gemma-4-e2b-it-4bit", "q": "4bit", "multimodal": True},
429
+ # 최소 사양: 사진은 못 읽지만 한국어로 대답은 한다.
430
430
  {"ram": 4 * 1024, "vram": 0,
431
- "id": "Qwen/Qwen3-VL-4B-Instruct", "q": "q4_K_M", "multimodal": True},
431
+ "id": "mlx-community/LFM2.5-2.6B-4bit", "q": "4bit", "multimodal": False},
432
432
  ]
433
433
 
434
434
 
@@ -622,17 +622,14 @@ def plan(profile: SystemProfile, rec: Recommendation) -> InstallPlan:
622
622
  # 모델 가중치 풀
623
623
  model_command = ["huggingface-cli", "download", rec.model_id, "--quiet"]
624
624
  if rec.runtime == "ollama":
625
- lower = rec.model_id.lower()
626
- if "gemma-4-31b" in lower:
627
- model_command = ["ollama", "pull", "hf.co/ggml-org/gemma-4-31B-it-GGUF:Q4_K_M"]
628
- elif "gemma-4-12b" in lower:
629
- model_command = ["ollama", "pull", "hf.co/ggml-org/gemma-4-12B-it-GGUF:Q4_K_M"]
630
- elif "llama-4-scout" in lower:
631
- model_command = ["ollama", "pull", "hf.co/ggml-org/Llama-4-Scout-17B-16E-Instruct-GGUF:Q4_K_M"]
632
- elif "qwen3-vl-8b" in lower:
633
- model_command = ["ollama", "pull", "qwen3-vl:8b"]
634
- elif "qwen3-vl-4b" in lower:
635
- model_command = ["ollama", "pull", "qwen3-vl:4b"]
625
+ # Local import: this module keeps a stdlib-only import graph so it can be
626
+ # run before the app is wired. The alias table is the single source of
627
+ # truth for per-engine repo ids — the old hardcoded if/elif chain here
628
+ # drifted from it and still named Llama 4 Scout and Qwen3-VL.
629
+ from latticeai.services.model_catalog import MODEL_ENGINE_ALIASES
630
+ ollama_target = (MODEL_ENGINE_ALIASES.get(rec.model_id.lower()) or {}).get("ollama")
631
+ if ollama_target:
632
+ model_command = ["ollama", "pull", ollama_target]
636
633
  elif rec.runtime == "lmstudio":
637
634
  model_command = ["lms", "get", rec.model_id]
638
635
  steps.append(InstallStep(
@@ -0,0 +1,126 @@
1
+ """
2
+ Smart Setup Wizard — Environment Scanner, Recommender & Auto-Installer
3
+ Detects hardware, tools, and API keys; returns tailored recommendations;
4
+ streams SSE installation progress.
5
+
6
+ Formerly the root ``setup.py``; renamed in v4 so it no longer collides with
7
+ the setuptools build entrypoint and actually ships in the wheel
8
+ (``pyproject.toml`` py-modules). Packaging is owned entirely by
9
+ ``pyproject.toml`` — there is deliberately no root ``setup.py``.
10
+
11
+ Split into cohesive submodules in v11.3.0 (no behaviour change): ``paths``
12
+ (PATH repair + binary lookup + the module-level ``COMMON_PATH_DIRS``),
13
+ ``detect`` (hardware/OS/tool probes and ``scan_environment``), ``plans``
14
+ (SSE framing + confirmation-token command plans), ``catalog`` (model tables),
15
+ ``recommend`` (``get_recommendations``) and ``install`` (verify/repair and the
16
+ SSE ``install_stream``). This module re-exports every name the single file
17
+ exposed, so ``latticeai.setup.wizard.X`` keeps working.
18
+
19
+ Stubbing note: rebinding one of these names *here* changes only this module's
20
+ name. The submodule that calls it holds its own reference, so a test standing
21
+ in for a helper patches the submodule that uses it.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ # The single file had no ``__all__``, so its public surface was "every module
27
+ # global" — including the names it imported for its own use. Every re-export
28
+ # below therefore uses the redundant-alias form: it reproduces exactly that
29
+ # surface, and it marks each name as deliberate rather than a leftover import.
30
+ from latticeai.core.quiet import quiet as quiet
31
+ from latticeai.services.process_audit import (
32
+ CommandConfirmationError as CommandConfirmationError,
33
+ )
34
+ from latticeai.services.process_audit import (
35
+ append_process_audit_event as append_process_audit_event,
36
+ )
37
+ from latticeai.services.process_audit import command_plan as command_plan
38
+ from latticeai.services.process_audit import (
39
+ command_plan_for_commands as command_plan_for_commands,
40
+ )
41
+ from latticeai.services.process_audit import (
42
+ require_command_confirmation as require_command_confirmation,
43
+ )
44
+ from latticeai.services.setup_detection import detect_cuda as detect_cuda
45
+ from latticeai.services.setup_detection import detect_tools as detect_tools
46
+ from latticeai.services.setup_detection import (
47
+ detect_wsl_from_text as detect_wsl_from_text,
48
+ )
49
+ from latticeai.setup.wizard.catalog import (
50
+ _BEST_MODEL_TIERS as _BEST_MODEL_TIERS,
51
+ )
52
+ from latticeai.setup.wizard.catalog import (
53
+ _CROSS_PLATFORM_MODEL_CATALOG as _CROSS_PLATFORM_MODEL_CATALOG,
54
+ )
55
+ from latticeai.setup.wizard.catalog import (
56
+ _MODEL_CATALOG as _MODEL_CATALOG,
57
+ )
58
+ from latticeai.setup.wizard.catalog import (
59
+ _VERSIONED_MODEL_PATTERNS as _VERSIONED_MODEL_PATTERNS,
60
+ )
61
+ from latticeai.setup.wizard.catalog import (
62
+ _best_model_for_engine as _best_model_for_engine,
63
+ )
64
+ from latticeai.setup.wizard.catalog import (
65
+ _catalog_row_family_version as _catalog_row_family_version,
66
+ )
67
+ from latticeai.setup.wizard.catalog import (
68
+ _filter_lower_family_versions as _filter_lower_family_versions,
69
+ )
70
+ from latticeai.setup.wizard.catalog import (
71
+ _version_tuple as _version_tuple,
72
+ )
73
+ from latticeai.setup.wizard.detect import _cmd as _cmd
74
+ from latticeai.setup.wizard.detect import _detect_api_keys as _detect_api_keys
75
+ from latticeai.setup.wizard.detect import _detect_chip as _detect_chip
76
+ from latticeai.setup.wizard.detect import _detect_cpu as _detect_cpu
77
+ from latticeai.setup.wizard.detect import _detect_cuda as _detect_cuda
78
+ from latticeai.setup.wizard.detect import (
79
+ _detect_disk_free_gb as _detect_disk_free_gb,
80
+ )
81
+ from latticeai.setup.wizard.detect import _detect_gpu as _detect_gpu
82
+ from latticeai.setup.wizard.detect import _detect_mlx as _detect_mlx
83
+ from latticeai.setup.wizard.detect import _detect_ram_gb as _detect_ram_gb
84
+ from latticeai.setup.wizard.detect import _detect_tools as _detect_tools
85
+ from latticeai.setup.wizard.detect import _detect_wsl as _detect_wsl
86
+ from latticeai.setup.wizard.detect import (
87
+ _parse_windows_video_controllers as _parse_windows_video_controllers,
88
+ )
89
+ from latticeai.setup.wizard.detect import scan_environment as scan_environment
90
+ from latticeai.setup.wizard.install import _brew_install as _brew_install
91
+ from latticeai.setup.wizard.install import _pip_install as _pip_install
92
+ from latticeai.setup.wizard.install import _repair_action as _repair_action
93
+ from latticeai.setup.wizard.install import _verify_action as _verify_action
94
+ from latticeai.setup.wizard.install import _verify_binary as _verify_binary
95
+ from latticeai.setup.wizard.install import _wait_for_binary as _wait_for_binary
96
+ from latticeai.setup.wizard.install import install_stream as install_stream
97
+ from latticeai.setup.wizard.install import open_url as open_url
98
+ from latticeai.setup.wizard.paths import COMMON_PATH_DIRS as COMMON_PATH_DIRS
99
+ from latticeai.setup.wizard.paths import OFFICIAL_DOWNLOADS as OFFICIAL_DOWNLOADS
100
+ from latticeai.setup.wizard.paths import PACKAGE_MODULES as PACKAGE_MODULES
101
+ from latticeai.setup.wizard.paths import (
102
+ WINDOWS_BINARY_CANDIDATES as WINDOWS_BINARY_CANDIDATES,
103
+ )
104
+ from latticeai.setup.wizard.paths import _component_detail as _component_detail
105
+ from latticeai.setup.wizard.paths import _merge_path_dirs as _merge_path_dirs
106
+ from latticeai.setup.wizard.paths import _module_available as _module_available
107
+ from latticeai.setup.wizard.paths import _package_module as _package_module
108
+ from latticeai.setup.wizard.paths import _persist_extra_path as _persist_extra_path
109
+ from latticeai.setup.wizard.paths import _project_env_file as _project_env_file
110
+ from latticeai.setup.wizard.paths import _update_env_file as _update_env_file
111
+ from latticeai.setup.wizard.paths import _which_any as _which_any
112
+ from latticeai.setup.wizard.paths import _which_detail as _which_detail
113
+ from latticeai.setup.wizard.paths import repair_path_for as repair_path_for
114
+ from latticeai.setup.wizard.plans import _action_command_plan as _action_command_plan
115
+ from latticeai.setup.wizard.plans import _action_commands as _action_commands
116
+ from latticeai.setup.wizard.plans import _attach_action_plan as _attach_action_plan
117
+ from latticeai.setup.wizard.plans import (
118
+ _hydrate_install_actions as _hydrate_install_actions,
119
+ )
120
+ from latticeai.setup.wizard.plans import _sse as _sse
121
+ from latticeai.setup.wizard.plans import (
122
+ _verify_action_confirmation as _verify_action_confirmation,
123
+ )
124
+ from latticeai.setup.wizard.recommend import (
125
+ get_recommendations as get_recommendations,
126
+ )