ltcai 11.5.2 → 11.7.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 (443) hide show
  1. package/README.md +93 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +119 -0
  4. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  5. package/docs/DEVELOPMENT.md +8 -6
  6. package/docs/ENTERPRISE.md +2 -1
  7. package/docs/MULTI_AGENT_RUNTIME.md +12 -5
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -3
  10. package/docs/REALTIME_COLLABORATION.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/WORKFLOW_DESIGNER.md +3 -2
  14. package/docs/kg-schema.md +1 -1
  15. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  16. package/lattice_brain/__init__.py +42 -79
  17. package/lattice_brain/graph/__init__.py +9 -24
  18. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  19. package/lattice_brain/ingestion/__init__.py +19 -58
  20. package/lattice_brain/ingestion/pipeline.py +20 -398
  21. package/lattice_brain/multimodal/__init__.py +7 -18
  22. package/lattice_brain/multimodal/images.py +6 -264
  23. package/lattice_brain/multimodal/video.py +8 -247
  24. package/lattice_brain/runtime/__init__.py +8 -79
  25. package/lattice_brain/runtime/hooks.py +22 -584
  26. package/latticeai/__init__.py +1 -1
  27. package/latticeai/api/agent_worker_seam.py +7 -80
  28. package/latticeai/api/health.py +6 -20
  29. package/latticeai/api/local_files.py +16 -613
  30. package/latticeai/api/models.py +12 -87
  31. package/latticeai/api/search.py +17 -255
  32. package/latticeai/api/tools.py +69 -750
  33. package/latticeai/api/voice_capture.py +8 -70
  34. package/latticeai/api/worker_compute.py +842 -0
  35. package/latticeai/api/worker_seams.py +216 -0
  36. package/latticeai/app_factory.py +23 -232
  37. package/latticeai/cli/entrypoint.py +36 -249
  38. package/latticeai/core/agent_permission.py +16 -91
  39. package/latticeai/core/messages.py +89 -531
  40. package/latticeai/runtime/access_runtime.py +0 -14
  41. package/latticeai/runtime/bootstrap.py +10 -21
  42. package/latticeai/runtime/brain_runtime.py +35 -43
  43. package/latticeai/runtime/build_phases/__init__.py +21 -37
  44. package/latticeai/runtime/build_phases/features.py +99 -389
  45. package/latticeai/runtime/build_phases/foundation.py +82 -392
  46. package/latticeai/runtime/build_phases/web.py +91 -408
  47. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  48. package/latticeai/runtime/lifespan_runtime.py +7 -16
  49. package/latticeai/runtime/platform_services_runtime.py +1 -15
  50. package/latticeai/runtime/runtime_context.py +13 -130
  51. package/latticeai/runtime/security_runtime.py +10 -103
  52. package/latticeai/services/architecture_readiness.py +82 -43
  53. package/latticeai/services/model_runtime/__init__.py +2 -11
  54. package/latticeai/services/model_runtime/service.py +4 -28
  55. package/latticeai/services/p_reinforce.py +10 -261
  56. package/latticeai/services/product_readiness.py +31 -38
  57. package/latticeai/services/search_service.py +37 -795
  58. package/latticeai/services/tool_dispatch.py +30 -386
  59. package/latticeai/services/voice_capture.py +13 -107
  60. package/latticeai/tools/__init__.py +22 -49
  61. package/latticeai/tools/commands.py +0 -163
  62. package/latticeai/tools/computer.py +0 -39
  63. package/latticeai/tools/documents.py +1 -134
  64. package/latticeai/tools/filesystem.py +1 -247
  65. package/latticeai/tools/knowledge.py +1 -52
  66. package/latticeai/tools/local_files.py +0 -20
  67. package/latticeai/worker_app.py +75 -0
  68. package/package.json +2 -3
  69. package/requirements.txt +0 -5
  70. package/scripts/agent_eval.py +16 -28
  71. package/scripts/brain_quality_eval.py +20 -183
  72. package/scripts/bump_version.py +5 -5
  73. package/scripts/check_current_release_docs.mjs +7 -4
  74. package/scripts/check_openapi_drift.mjs +10 -0
  75. package/scripts/check_server_i18n.mjs +2 -18
  76. package/scripts/compose_openapi.py +377 -0
  77. package/scripts/export_openapi.py +32 -4
  78. package/scripts/gen_messages_catalog_fixture.py +302 -0
  79. package/scripts/gen_openapi_fragments.py +365 -0
  80. package/scripts/gen_redact_fixture.py +196 -0
  81. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  82. package/scripts/generate_agent_parity_fixtures.py +33 -14
  83. package/scripts/openapi_route_families.json +2161 -0
  84. package/scripts/release_screen_claims.json +54 -0
  85. package/scripts/run_integration_tests.mjs +134 -29
  86. package/scripts/run_sidecar_e2e.mjs +104 -11
  87. package/scripts/wheel_smoke.py +32 -22
  88. package/src-tauri/Cargo.lock +375 -9
  89. package/src-tauri/Cargo.toml +1 -1
  90. package/src-tauri/src/backend.rs +151 -45
  91. package/src-tauri/src/main.rs +18 -18
  92. package/src-tauri/src/topology.rs +58 -88
  93. package/src-tauri/tauri.conf.json +1 -2
  94. package/static/app/asset-manifest.json +41 -41
  95. package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
  96. package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
  100. package/static/app/assets/Capture-BsTokYkk.js +1 -0
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
  107. package/static/app/assets/System-CMHSO9qM.js +1 -0
  108. package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
  112. package/static/app/assets/circle-check-DruOxB-4.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
  119. package/static/app/assets/index-D9x-kSNy.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
  124. package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
  125. package/static/app/assets/search-CT9aho2j.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
  134. package/static/app/index.html +4 -4
  135. package/static/sw.js +1 -1
  136. package/lattice_brain/archive.py +0 -522
  137. package/lattice_brain/context.py +0 -325
  138. package/lattice_brain/conversations.py +0 -382
  139. package/lattice_brain/core.py +0 -82
  140. package/lattice_brain/graph/_kg_contract.py +0 -249
  141. package/lattice_brain/graph/curator.py +0 -676
  142. package/lattice_brain/graph/discovery.py +0 -595
  143. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  144. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  145. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  146. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  147. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  148. package/lattice_brain/graph/documents.py +0 -380
  149. package/lattice_brain/graph/fusion.py +0 -395
  150. package/lattice_brain/graph/identity.py +0 -175
  151. package/lattice_brain/graph/image_vectors.py +0 -230
  152. package/lattice_brain/graph/ingest.py +0 -829
  153. package/lattice_brain/graph/network.py +0 -205
  154. package/lattice_brain/graph/proactive.py +0 -724
  155. package/lattice_brain/graph/projection/__init__.py +0 -42
  156. package/lattice_brain/graph/projection/curation.py +0 -500
  157. package/lattice_brain/graph/projection/v2_schema.py +0 -518
  158. package/lattice_brain/graph/provenance.py +0 -524
  159. package/lattice_brain/graph/rerank.py +0 -163
  160. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  161. package/lattice_brain/graph/retrieval/context.py +0 -197
  162. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  163. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  164. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  165. package/lattice_brain/graph/retrieval/signals.py +0 -95
  166. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  167. package/lattice_brain/graph/retrieval_policy.py +0 -180
  168. package/lattice_brain/graph/retrieval_reads.py +0 -769
  169. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  170. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  171. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  172. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  173. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  174. package/lattice_brain/graph/schema.py +0 -792
  175. package/lattice_brain/graph/store.py +0 -268
  176. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  177. package/lattice_brain/graph/vector_index/base.py +0 -167
  178. package/lattice_brain/graph/vector_index/brute_force.py +0 -110
  179. package/lattice_brain/graph/vector_index/hnsw.py +0 -290
  180. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  181. package/lattice_brain/graph/vector_index/quantized.py +0 -148
  182. package/lattice_brain/graph/vector_index/selector.py +0 -161
  183. package/lattice_brain/graph/write_master.py +0 -308
  184. package/lattice_brain/ingestion/_contract.py +0 -90
  185. package/lattice_brain/ingestion/folder_scan.py +0 -57
  186. package/lattice_brain/ingestion/folders.py +0 -258
  187. package/lattice_brain/ingestion/jobs_api.py +0 -107
  188. package/lattice_brain/ingestion/routing.py +0 -295
  189. package/lattice_brain/ingestion_jobs.py +0 -380
  190. package/lattice_brain/memory.py +0 -75
  191. package/lattice_brain/portability/__init__.py +0 -90
  192. package/lattice_brain/portability/_contract.py +0 -42
  193. package/lattice_brain/portability/backups.py +0 -338
  194. package/lattice_brain/portability/bundles.py +0 -136
  195. package/lattice_brain/portability/constants.py +0 -93
  196. package/lattice_brain/portability/fsops.py +0 -138
  197. package/lattice_brain/portability/service.py +0 -41
  198. package/lattice_brain/portability/sharing.py +0 -710
  199. package/lattice_brain/quality.py +0 -543
  200. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  201. package/lattice_brain/runtime/agent_runtime.py +0 -859
  202. package/lattice_brain/runtime/contracts.py +0 -460
  203. package/lattice_brain/runtime/multi_agent.py +0 -942
  204. package/lattice_brain/runtime/statuses.py +0 -10
  205. package/lattice_brain/sealed_box.py +0 -240
  206. package/lattice_brain/self_model.py +0 -675
  207. package/lattice_brain/sensitivity.py +0 -94
  208. package/lattice_brain/storage/__init__.py +0 -22
  209. package/lattice_brain/storage/base.py +0 -100
  210. package/lattice_brain/storage/docker.py +0 -105
  211. package/lattice_brain/storage/factory.py +0 -31
  212. package/lattice_brain/storage/migration.py +0 -191
  213. package/lattice_brain/storage/postgres.py +0 -123
  214. package/lattice_brain/storage/sqlite.py +0 -143
  215. package/lattice_brain/synthesis.py +0 -824
  216. package/lattice_brain/workflow.py +0 -497
  217. package/latticeai/api/admin.py +0 -471
  218. package/latticeai/api/agent_registry.py +0 -105
  219. package/latticeai/api/agents.py +0 -228
  220. package/latticeai/api/auth.py +0 -383
  221. package/latticeai/api/automation_intelligence.py +0 -401
  222. package/latticeai/api/brain_intelligence.py +0 -199
  223. package/latticeai/api/browser.py +0 -493
  224. package/latticeai/api/change_proposals.py +0 -89
  225. package/latticeai/api/chat.py +0 -572
  226. package/latticeai/api/chat_agent_http.py +0 -892
  227. package/latticeai/api/chat_contracts.py +0 -73
  228. package/latticeai/api/chat_documents.py +0 -276
  229. package/latticeai/api/chat_helpers.py +0 -460
  230. package/latticeai/api/chat_history.py +0 -101
  231. package/latticeai/api/chat_hybrid.py +0 -113
  232. package/latticeai/api/chat_intents.py +0 -646
  233. package/latticeai/api/chat_stream.py +0 -216
  234. package/latticeai/api/chronicle.py +0 -63
  235. package/latticeai/api/command_center.py +0 -51
  236. package/latticeai/api/computer_use.py +0 -474
  237. package/latticeai/api/evidence_actions.py +0 -48
  238. package/latticeai/api/features.py +0 -70
  239. package/latticeai/api/funnel_metrics.py +0 -31
  240. package/latticeai/api/garden.py +0 -34
  241. package/latticeai/api/hooks.py +0 -165
  242. package/latticeai/api/index_jobs.py +0 -145
  243. package/latticeai/api/invitations.py +0 -100
  244. package/latticeai/api/knowledge_graph.py +0 -536
  245. package/latticeai/api/marketplace.py +0 -105
  246. package/latticeai/api/mcp.py +0 -482
  247. package/latticeai/api/memory.py +0 -270
  248. package/latticeai/api/network.py +0 -81
  249. package/latticeai/api/network_boundary.py +0 -225
  250. package/latticeai/api/permission_mode.py +0 -61
  251. package/latticeai/api/permissions.py +0 -436
  252. package/latticeai/api/plugins.py +0 -126
  253. package/latticeai/api/portability.py +0 -391
  254. package/latticeai/api/project_sessions.py +0 -114
  255. package/latticeai/api/realtime.py +0 -118
  256. package/latticeai/api/review_queue.py +0 -364
  257. package/latticeai/api/security_dashboard.py +0 -604
  258. package/latticeai/api/setup.py +0 -319
  259. package/latticeai/api/static_routes.py +0 -354
  260. package/latticeai/api/ui_redirects.py +0 -26
  261. package/latticeai/api/workflow_designer.py +0 -394
  262. package/latticeai/api/workspace.py +0 -856
  263. package/latticeai/api/workspace_scope.py +0 -125
  264. package/latticeai/core/agent/__init__.py +0 -93
  265. package/latticeai/core/agent/_contract.py +0 -79
  266. package/latticeai/core/agent/context.py +0 -57
  267. package/latticeai/core/agent/deps.py +0 -125
  268. package/latticeai/core/agent/execution.py +0 -622
  269. package/latticeai/core/agent/planning.py +0 -145
  270. package/latticeai/core/agent/recovery.py +0 -157
  271. package/latticeai/core/agent/runtime.py +0 -210
  272. package/latticeai/core/agent/verification.py +0 -231
  273. package/latticeai/core/agent_eval.py +0 -739
  274. package/latticeai/core/agent_helpers.py +0 -493
  275. package/latticeai/core/agent_profiles.py +0 -110
  276. package/latticeai/core/agent_prompts.py +0 -171
  277. package/latticeai/core/agent_registry.py +0 -232
  278. package/latticeai/core/agent_state.py +0 -41
  279. package/latticeai/core/agent_trace.py +0 -104
  280. package/latticeai/core/artifact_ledger.py +0 -109
  281. package/latticeai/core/audit.py +0 -260
  282. package/latticeai/core/builtin_hooks.py +0 -105
  283. package/latticeai/core/context_builder.py +0 -394
  284. package/latticeai/core/document_generator.py +0 -103
  285. package/latticeai/core/enterprise.py +0 -154
  286. package/latticeai/core/enterprise_admin.py +0 -158
  287. package/latticeai/core/file_generation/__init__.py +0 -115
  288. package/latticeai/core/file_generation/bundles.py +0 -76
  289. package/latticeai/core/file_generation/extraction.py +0 -154
  290. package/latticeai/core/file_generation/inference.py +0 -235
  291. package/latticeai/core/file_generation/orchestration.py +0 -152
  292. package/latticeai/core/file_generation/prompting.py +0 -117
  293. package/latticeai/core/file_generation/repair.py +0 -114
  294. package/latticeai/core/file_generation/sanitize.py +0 -61
  295. package/latticeai/core/file_generation/validation.py +0 -201
  296. package/latticeai/core/invitations.py +0 -132
  297. package/latticeai/core/legacy_compatibility.py +0 -243
  298. package/latticeai/core/logging_safety.py +0 -46
  299. package/latticeai/core/marketplace.py +0 -293
  300. package/latticeai/core/mcp_catalog.py +0 -452
  301. package/latticeai/core/mcp_registry.py +0 -506
  302. package/latticeai/core/network_boundary.py +0 -168
  303. package/latticeai/core/oidc.py +0 -208
  304. package/latticeai/core/plugins.py +0 -432
  305. package/latticeai/core/product_hardening.py +0 -218
  306. package/latticeai/core/project_sessions.py +0 -337
  307. package/latticeai/core/realtime.py +0 -238
  308. package/latticeai/core/run_explain.py +0 -426
  309. package/latticeai/core/run_store.py +0 -252
  310. package/latticeai/core/timezones.py +0 -80
  311. package/latticeai/core/workspace_computer_memory.py +0 -84
  312. package/latticeai/core/workspace_graph_trace.py +0 -155
  313. package/latticeai/core/workspace_indexing.py +0 -102
  314. package/latticeai/core/workspace_memory.py +0 -77
  315. package/latticeai/core/workspace_onboarding.py +0 -104
  316. package/latticeai/core/workspace_os.py +0 -978
  317. package/latticeai/core/workspace_os_constants.py +0 -126
  318. package/latticeai/core/workspace_os_state.py +0 -180
  319. package/latticeai/core/workspace_os_utils.py +0 -103
  320. package/latticeai/core/workspace_permissions.py +0 -101
  321. package/latticeai/core/workspace_plugins.py +0 -97
  322. package/latticeai/core/workspace_relationships.py +0 -99
  323. package/latticeai/core/workspace_reorganization.py +0 -335
  324. package/latticeai/core/workspace_review_items.py +0 -112
  325. package/latticeai/core/workspace_runs.py +0 -726
  326. package/latticeai/core/workspace_skills.py +0 -109
  327. package/latticeai/core/workspace_snapshots.py +0 -198
  328. package/latticeai/core/workspace_timeline.py +0 -110
  329. package/latticeai/integrations/__init__.py +0 -0
  330. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  331. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  332. package/latticeai/integrations/telegram_bot/config.py +0 -86
  333. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  334. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  335. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  336. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  337. package/latticeai/runtime/audit_runtime.py +0 -76
  338. package/latticeai/runtime/automation_runtime.py +0 -81
  339. package/latticeai/runtime/chat_wiring.py +0 -141
  340. package/latticeai/runtime/context_runtime.py +0 -66
  341. package/latticeai/runtime/feature_toggle_wiring.py +0 -157
  342. package/latticeai/runtime/history_runtime.py +0 -163
  343. package/latticeai/runtime/history_writer.py +0 -138
  344. package/latticeai/runtime/hooks_runtime.py +0 -77
  345. package/latticeai/runtime/model_wiring.py +0 -68
  346. package/latticeai/runtime/namespace_runtime.py +0 -163
  347. package/latticeai/runtime/network_boundary_wiring.py +0 -117
  348. package/latticeai/runtime/network_config_runtime.py +0 -56
  349. package/latticeai/runtime/permission_mode_wiring.py +0 -112
  350. package/latticeai/runtime/persistence_runtime.py +0 -159
  351. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  352. package/latticeai/runtime/review_wiring.py +0 -42
  353. package/latticeai/runtime/router_registration.py +0 -693
  354. package/latticeai/runtime/service_singletons.py +0 -55
  355. package/latticeai/runtime/sso_config_runtime.py +0 -128
  356. package/latticeai/runtime/user_key_runtime.py +0 -106
  357. package/latticeai/runtime/web_runtime.py +0 -92
  358. package/latticeai/server_app.py +0 -51
  359. package/latticeai/services/app_context.py +0 -130
  360. package/latticeai/services/automation_execution.py +0 -266
  361. package/latticeai/services/automation_intelligence.py +0 -614
  362. package/latticeai/services/brain_automation.py +0 -191
  363. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  364. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  365. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  366. package/latticeai/services/brain_intelligence/constants.py +0 -47
  367. package/latticeai/services/brain_intelligence/digest.py +0 -258
  368. package/latticeai/services/brain_intelligence/health.py +0 -331
  369. package/latticeai/services/brain_intelligence/proposals.py +0 -259
  370. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  371. package/latticeai/services/brain_intelligence/service.py +0 -48
  372. package/latticeai/services/change_proposals.py +0 -471
  373. package/latticeai/services/chat_service.py +0 -243
  374. package/latticeai/services/chronicle.py +0 -555
  375. package/latticeai/services/cloud_egress_audit.py +0 -85
  376. package/latticeai/services/cloud_extraction.py +0 -129
  377. package/latticeai/services/cloud_streaming.py +0 -268
  378. package/latticeai/services/cloud_token_guard.py +0 -84
  379. package/latticeai/services/command_center.py +0 -548
  380. package/latticeai/services/evidence_actions.py +0 -258
  381. package/latticeai/services/feature_toggles.py +0 -502
  382. package/latticeai/services/folder_watch.py +0 -520
  383. package/latticeai/services/funnel_metrics.py +0 -307
  384. package/latticeai/services/hybrid_chat.py +0 -316
  385. package/latticeai/services/hybrid_context.py +0 -228
  386. package/latticeai/services/hybrid_policy.py +0 -129
  387. package/latticeai/services/interop_bridges.py +0 -978
  388. package/latticeai/services/local_knowledge.py +0 -465
  389. package/latticeai/services/memory_service/__init__.py +0 -52
  390. package/latticeai/services/memory_service/_contract.py +0 -100
  391. package/latticeai/services/memory_service/brief.py +0 -431
  392. package/latticeai/services/memory_service/constants.py +0 -57
  393. package/latticeai/services/memory_service/maintenance.py +0 -138
  394. package/latticeai/services/memory_service/manager.py +0 -186
  395. package/latticeai/services/memory_service/proof.py +0 -136
  396. package/latticeai/services/memory_service/recall.py +0 -225
  397. package/latticeai/services/memory_service/service.py +0 -48
  398. package/latticeai/services/memory_service/stores.py +0 -110
  399. package/latticeai/services/mode_store.py +0 -132
  400. package/latticeai/services/model_recommendation.py +0 -224
  401. package/latticeai/services/model_runtime/cloud.py +0 -87
  402. package/latticeai/services/network_boundary_service.py +0 -117
  403. package/latticeai/services/obsidian_bridge.py +0 -609
  404. package/latticeai/services/openai_compatible_adapter.py +0 -101
  405. package/latticeai/services/permission_mode_service.py +0 -122
  406. package/latticeai/services/platform_runtime.py +0 -366
  407. package/latticeai/services/review_queue.py +0 -380
  408. package/latticeai/services/router_context.py +0 -59
  409. package/latticeai/services/run_executor.py +0 -387
  410. package/latticeai/services/self_model_service.py +0 -171
  411. package/latticeai/services/setup_detection.py +0 -147
  412. package/latticeai/services/triggers.py +0 -378
  413. package/latticeai/services/upload_service.py +0 -172
  414. package/latticeai/services/workspace_service.py +0 -165
  415. package/latticeai/setup/__init__.py +0 -25
  416. package/latticeai/setup/auto_setup.py +0 -846
  417. package/latticeai/setup/demo_corpus.py +0 -98
  418. package/latticeai/setup/wizard/__init__.py +0 -126
  419. package/latticeai/setup/wizard/catalog.py +0 -175
  420. package/latticeai/setup/wizard/detect.py +0 -302
  421. package/latticeai/setup/wizard/install.py +0 -348
  422. package/latticeai/setup/wizard/paths.py +0 -165
  423. package/latticeai/setup/wizard/plans.py +0 -74
  424. package/latticeai/setup/wizard/recommend.py +0 -320
  425. package/scripts/bench_agent_smoke.py +0 -409
  426. package/scripts/bench_models.py +0 -540
  427. package/scripts/bench_vector_index.py +0 -295
  428. package/scripts/funnel_soft_gate.py +0 -192
  429. package/scripts/generate_agent_loop_fixtures.py +0 -994
  430. package/scripts/generate_rust_parity_fixtures.py +0 -908
  431. package/scripts/migrate_brain_storage.py +0 -57
  432. package/scripts/parity_fixture_corpus_context.py +0 -162
  433. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  434. package/scripts/profile_kg.py +0 -355
  435. package/server.py +0 -30
  436. package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
  437. package/static/app/assets/Capture-DiQ219jW.js +0 -1
  438. package/static/app/assets/System-CieofHQa.js +0 -1
  439. package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
  440. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  441. package/static/app/assets/index-DxmOfNRi.css +0 -2
  442. package/static/app/assets/primitives-SNp0LRJz.js +0 -1
  443. package/static/app/assets/search-BcHqkjoy.js +0 -1
@@ -1,846 +0,0 @@
1
- """
2
- Lattice AI — Zero-Config Auto Setup
3
- ===================================
4
-
5
- 명세: ``lattice_ai_full_spec.pptx`` 슬라이드 16·17 (자동 환경 매트릭스 + 5단계 흐름)
6
-
7
- 5단계
8
- -----
9
- ① PROBE OS · CPU · GPU · RAM · 디스크 · 가속 SDK 감지
10
- ② RECOMMEND 사양 점수 → 최적 모델 / 런타임 / 양자화 자동 선택
11
- ③ INSTALL OS별 패키지 매니저 어댑터 호출 (winget · brew · apt · 스토어)
12
- ④ VERIFY 추론 토큰/초 측정, 첫 응답 지연, 메모리 누수 점검
13
- ⑤ PRESET 기본/고급 모드 분기 + 단축키·MCP·테마 적용
14
-
15
- 원칙
16
- ----
17
- - **표준 라이브러리 only.** 외부 패키지 import 는 모두 try/except 로 감싼다.
18
- - **변경하지 않는다, 추천만 한다.** INSTALL 단계는 *실행 명령어* 를 생성하고
19
- 돌려보낼 뿐, ``--apply`` 플래그 없이는 시스템을 건드리지 않는다.
20
- - **모든 출력은 JSON-직렬화 가능**해야 UI(설치 마법사 화면) 에서 그대로 표시 가능.
21
-
22
- 사용
23
- ----
24
- ```bash
25
- python3 auto_setup.py probe # 1단계만
26
- python3 auto_setup.py recommend # 1+2단계
27
- python3 auto_setup.py plan # 1+2+3 (설치 계획 출력)
28
- python3 auto_setup.py plan --apply --confirm-token <token>
29
- python3 auto_setup.py verify # 4단계 단독
30
- python3 auto_setup.py preset # 5단계
31
- python3 auto_setup.py all # 전체 흐름
32
- ```
33
- """
34
-
35
- from __future__ import annotations
36
-
37
- import argparse
38
- import json
39
- import os
40
- import platform
41
- import re
42
- import shutil
43
- import subprocess
44
- import sys
45
- import time
46
- from dataclasses import asdict, dataclass, field
47
- from pathlib import Path
48
- from typing import Any, Dict, List, Optional, Tuple
49
-
50
- from latticeai.core.module_probe import module_available
51
- from latticeai.core.quiet import quiet
52
- from latticeai.services.process_audit import (
53
- CommandConfirmationError,
54
- append_process_audit_event,
55
- command_plan,
56
- command_plan_for_commands,
57
- require_command_confirmation,
58
- )
59
- from latticeai.services.setup_detection import (
60
- detect_cuda,
61
- detect_tools,
62
- detect_wsl_from_text,
63
- parse_windows_cpu_info,
64
- windows_processor_features,
65
- )
66
- from latticeai.services.setup_detection import (
67
- parse_windows_video_controllers as _parse_windows_video_controllers,
68
- )
69
-
70
- __all__ = [
71
- "SystemProfile", "Recommendation", "InstallPlan",
72
- "probe", "recommend", "plan", "verify", "preset", "run_all",
73
- ]
74
-
75
-
76
- # ── 1. PROBE ────────────────────────────────────────────────────────────────
77
- @dataclass
78
- class GPUInfo:
79
- vendor: str = "unknown" # nvidia | amd | intel | apple | none
80
- model: str = ""
81
- vram_mb: int = 0
82
- sdk: List[str] = field(default_factory=list) # ['cuda', 'metal', 'mlx', ...]
83
-
84
-
85
- @dataclass
86
- class SystemProfile:
87
- os: str = "" # windows | darwin | linux | ios | android
88
- os_version: str = ""
89
- arch: str = "" # x86_64 | arm64 | …
90
- cpu_model: str = ""
91
- cpu_cores: int = 0
92
- cpu_logical_cores: int = 0
93
- cpu_instructions: List[str] = field(default_factory=list)
94
- ram_mb: int = 0
95
- disk_free_mb: int = 0
96
- gpu: GPUInfo = field(default_factory=GPUInfo)
97
- package_manager: Optional[str] = None # winget | brew | apt | dnf | pacman
98
- has_internet: bool = True
99
- python_version: str = ""
100
- is_wsl: bool = False
101
- wsl_version: str = ""
102
- cuda_available: bool = False
103
- cuda_version: str = ""
104
- tools: Dict[str, str] = field(default_factory=dict)
105
-
106
- def score(self) -> int:
107
- """LLM 적합도 점수 (0..100). RECOMMEND 의 입력."""
108
- s = 0
109
- s += min(self.cpu_cores * 2, 24)
110
- s += min(self.ram_mb // 1024 * 2, 40)
111
- s += min(self.gpu.vram_mb // 1024 * 4, 36)
112
- return min(s, 100)
113
-
114
- def to_json(self) -> Dict[str, Any]:
115
- d = asdict(self)
116
- d["score"] = self.score()
117
- return d
118
-
119
-
120
- def _read_text(path: str) -> str:
121
- try:
122
- return Path(path).read_text(encoding="utf-8", errors="replace")
123
- except Exception:
124
- return ""
125
-
126
-
127
- def _run(cmd: List[str], timeout: float = 4.0) -> str:
128
- try:
129
- out = subprocess.run(cmd, capture_output=True, text=True,
130
- timeout=timeout, check=False)
131
- return (out.stdout or "") + (out.stderr or "")
132
- except Exception:
133
- return ""
134
-
135
-
136
- def _windows_candidate_paths(binary: str) -> List[str]:
137
- local_appdata = os.environ.get("LOCALAPPDATA", "")
138
- program_files = os.environ.get("ProgramFiles", r"C:\Program Files")
139
- program_files_x86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")
140
- candidates = {
141
- "ollama": [
142
- str(Path(local_appdata) / "Programs" / "Ollama" / "ollama.exe") if local_appdata else "",
143
- str(Path(program_files) / "Ollama" / "ollama.exe"),
144
- ],
145
- "lms": [
146
- str(Path(local_appdata) / "Programs" / "LM Studio" / "resources" / "app" / ".webpack" / "lms.exe") if local_appdata else "",
147
- str(Path(program_files) / "LM Studio" / "resources" / "app" / ".webpack" / "lms.exe"),
148
- ],
149
- "nvidia-smi": [
150
- str(Path(program_files) / "NVIDIA Corporation" / "NVSMI" / "nvidia-smi.exe"),
151
- str(Path(program_files_x86) / "NVIDIA Corporation" / "NVSMI" / "nvidia-smi.exe"),
152
- ],
153
- }
154
- return [item for item in candidates.get(binary, []) if item]
155
-
156
-
157
- def _which(binary: str) -> Optional[str]:
158
- found = shutil.which(binary)
159
- if found:
160
- return found
161
- if platform.system() == "Windows":
162
- for candidate in _windows_candidate_paths(binary):
163
- if Path(candidate).exists():
164
- return candidate
165
- return None
166
-
167
-
168
- def _detect_gpu(prof_os: str, arch: str) -> GPUInfo:
169
- """OS별 휴리스틱으로 GPU 감지. 외부 라이브러리 없이 가능한 만큼만."""
170
- gpu = GPUInfo()
171
-
172
- # NVIDIA
173
- nvidia_smi = _which("nvidia-smi")
174
- if nvidia_smi:
175
- info = _run([nvidia_smi, "--query-gpu=name,memory.total",
176
- "--format=csv,noheader,nounits"])
177
- if info.strip():
178
- first = info.strip().splitlines()[0]
179
- try:
180
- name, mem = [x.strip() for x in first.split(",", 1)]
181
- gpu.vendor = "nvidia"
182
- gpu.model = name
183
- gpu.vram_mb = int(float(mem))
184
- gpu.sdk.append("cuda")
185
- except ValueError:
186
- quiet()
187
-
188
- # Apple Silicon / Metal
189
- if prof_os == "darwin":
190
- sp = _run(["system_profiler", "SPDisplaysDataType"], timeout=6.0)
191
- if "Apple" in sp and arch == "arm64":
192
- gpu.vendor = "apple"
193
- for line in sp.splitlines():
194
- if "Chipset Model" in line:
195
- gpu.model = line.split(":", 1)[-1].strip()
196
- break
197
- # Apple Silicon 의 GPU 메모리는 통합 메모리 = RAM. 별도 표기 안 함.
198
- gpu.sdk.extend(["metal", "mlx" if _has_module("mlx") else ""])
199
- gpu.sdk = [s for s in gpu.sdk if s]
200
-
201
- # Windows
202
- if prof_os == "windows" and gpu.vendor == "unknown":
203
- shell = _which("powershell") or _which("pwsh")
204
- info = ""
205
- if shell:
206
- info = _run([
207
- shell, "-NoProfile", "-Command",
208
- "Get-CimInstance Win32_VideoController | Select-Object Name,AdapterRAM | ConvertTo-Json -Compress",
209
- ], timeout=8.0)
210
- if not info:
211
- info = _run(["wmic", "path", "win32_VideoController", "get",
212
- "Name,AdapterRAM", "/format:list"])
213
- controllers = _parse_windows_video_controllers(info)
214
- if controllers:
215
- primary = max(controllers, key=lambda item: int(item.get("vram_mb") or 0))
216
- name = str(primary.get("name") or "")
217
- gpu.model = name
218
- gpu.vram_mb = int(primary.get("vram_mb") or 0)
219
- low = name.lower()
220
- if "nvidia" in low or "rtx" in low or "geforce" in low:
221
- gpu.vendor = "nvidia"; gpu.sdk.append("cuda")
222
- elif "amd" in low or "radeon" in low:
223
- gpu.vendor = "amd"; gpu.sdk.extend(["directml", "vulkan"])
224
- elif "intel" in low or "arc" in low or "iris" in low:
225
- gpu.vendor = "intel"; gpu.sdk.extend(["directml", "vulkan"])
226
-
227
- # Linux (lspci)
228
- if prof_os == "linux" and gpu.vendor == "unknown":
229
- info = _run(["lspci"], timeout=3.0).lower()
230
- if "nvidia" in info:
231
- gpu.vendor = "nvidia"; gpu.sdk.append("cuda")
232
- elif "amd/ati" in info or "advanced micro devices" in info:
233
- gpu.vendor = "amd"; gpu.sdk.extend(["rocm", "vulkan"])
234
- elif "intel corporation" in info and "vga" in info:
235
- gpu.vendor = "intel"; gpu.sdk.append("vulkan")
236
-
237
- return gpu
238
-
239
-
240
- def _detect_package_manager(prof_os: str) -> Optional[str]:
241
- if prof_os == "windows":
242
- return "winget" if _which("winget") else None
243
- if prof_os == "darwin":
244
- return "brew" if _which("brew") else None
245
- if prof_os == "linux":
246
- for pm in ("apt", "dnf", "pacman", "zypper", "apk"):
247
- if _which(pm):
248
- return pm
249
- return None
250
-
251
-
252
- def _detect_tools() -> Dict[str, str]:
253
- detected = detect_tools(_which, ("ollama", "lms", "nvidia-smi", "nvcc", "winget", "brew", "apt", "git", "node", "python", "python3"))
254
- return {binary: path for binary, path in detected.items() if path}
255
-
256
-
257
- def _detect_wsl(prof_os: str) -> Tuple[bool, str]:
258
- return detect_wsl_from_text(prof_os, _read_text("/proc/version"))
259
-
260
-
261
- def _detect_cuda() -> Tuple[bool, str]:
262
- available, version, _nvidia_smi, _nvcc = detect_cuda(_which, lambda args: _run(args, timeout=4.0))
263
- return available, version
264
-
265
-
266
- def _detect_cpu_details(prof_os: str) -> Tuple[str, int, int, List[str]]:
267
- model = platform.processor() or ""
268
- physical = os.cpu_count() or 0
269
- logical = os.cpu_count() or 0
270
- flags: List[str] = []
271
- if prof_os == "darwin":
272
- model = _run(["sysctl", "-n", "machdep.cpu.brand_string"]).strip() or model
273
- try:
274
- physical = int((_run(["sysctl", "-n", "hw.physicalcpu"]).strip() or physical))
275
- logical = int((_run(["sysctl", "-n", "hw.logicalcpu"]).strip() or logical))
276
- except ValueError:
277
- quiet()
278
- flags = [item.lower() for item in _run(["sysctl", "-n", "machdep.cpu.features"]).split()]
279
- elif prof_os == "linux":
280
- text = _read_text("/proc/cpuinfo")
281
- for line in text.splitlines():
282
- if line.lower().startswith("model name") and not model:
283
- model = line.split(":", 1)[-1].strip()
284
- if line.lower().startswith(("flags", "features")) and not flags:
285
- flags = line.split(":", 1)[-1].strip().lower().split()
286
- elif prof_os == "windows":
287
- raw = _run(["wmic", "cpu", "get", "Name,NumberOfCores,NumberOfLogicalProcessors", "/format:list"])
288
- model, physical, logical = parse_windows_cpu_info(
289
- raw, model=model, physical_cores=physical, logical_cores=logical,
290
- )
291
- flags.extend(windows_processor_features())
292
- interesting = {"avx", "avx2", "avx512f", "fma", "neon", "sse4_2", "sse", "sse2", "sse3", "rdrand"}
293
- return model, physical, logical, sorted({flag for flag in flags if flag in interesting})
294
-
295
-
296
- def _has_module(name: str) -> bool:
297
- """Importable *and loadable*.
298
-
299
- ``strict=True`` on purpose: this probe drives what the setup wizard offers
300
- to install, and an ``mlx`` wheel built for another architecture has a
301
- findable spec right up until it raises on load. Answering from the spec
302
- alone would plan a runtime that cannot start.
303
- """
304
- return module_available(name, strict=True)
305
-
306
-
307
- def probe() -> SystemProfile:
308
- """① PROBE — 외부 의존성 없이 가능한 만큼 환경을 감지한다."""
309
- prof = SystemProfile()
310
- prof.os = {"Darwin": "darwin", "Windows": "windows",
311
- "Linux": "linux"}.get(platform.system(), platform.system().lower())
312
- prof.os_version = platform.release()
313
- prof.arch = platform.machine().lower()
314
- cpu_model, cpu_cores, cpu_logical_cores, cpu_instructions = _detect_cpu_details(prof.os)
315
- prof.cpu_model = cpu_model
316
- prof.cpu_cores = cpu_cores
317
- prof.cpu_logical_cores = cpu_logical_cores
318
- prof.cpu_instructions = cpu_instructions
319
- prof.python_version = platform.python_version()
320
- prof.is_wsl, prof.wsl_version = _detect_wsl(prof.os)
321
- prof.cuda_available, prof.cuda_version = _detect_cuda()
322
- prof.tools = _detect_tools()
323
-
324
- # RAM
325
- try:
326
- if prof.os == "linux":
327
- for line in _read_text("/proc/meminfo").splitlines():
328
- if line.startswith("MemTotal:"):
329
- prof.ram_mb = int(line.split()[1]) // 1024
330
- break
331
- elif prof.os == "darwin":
332
- out = _run(["sysctl", "-n", "hw.memsize"])
333
- if out.strip():
334
- try:
335
- prof.ram_mb = int(out.strip()) // (1024 * 1024)
336
- except ValueError:
337
- prof.ram_mb = 0
338
- if not prof.ram_mb:
339
- profiler = _run(["system_profiler", "SPHardwareDataType"], timeout=8.0)
340
- m = re.search(r"Memory:\s+([\d.]+)\s*(TB|GB|MB)", profiler, re.IGNORECASE)
341
- if m:
342
- value = float(m.group(1))
343
- unit = m.group(2).lower()
344
- if unit == "tb":
345
- prof.ram_mb = int(value * 1024 * 1024)
346
- elif unit == "gb":
347
- prof.ram_mb = int(value * 1024)
348
- else:
349
- prof.ram_mb = int(value)
350
- if not prof.ram_mb:
351
- hostinfo = _run(["hostinfo"])
352
- m = re.search(r"Primary memory available:\s+([\d.]+)\s+gigabytes", hostinfo, re.IGNORECASE)
353
- if m:
354
- prof.ram_mb = int(float(m.group(1)) * 1024)
355
- elif prof.os == "windows":
356
- out = _run(["wmic", "ComputerSystem", "get", "TotalPhysicalMemory",
357
- "/format:list"])
358
- for line in out.splitlines():
359
- if line.startswith("TotalPhysicalMemory="):
360
- prof.ram_mb = int(line.split("=", 1)[-1].strip()) // (1024 * 1024)
361
- break
362
- except Exception:
363
- quiet()
364
-
365
- # Disk
366
- try:
367
- usage = shutil.disk_usage(Path.home())
368
- prof.disk_free_mb = usage.free // (1024 * 1024)
369
- except Exception:
370
- quiet()
371
-
372
- prof.gpu = _detect_gpu(prof.os, prof.arch)
373
- prof.package_manager = _detect_package_manager(prof.os)
374
- return prof
375
-
376
-
377
- # ── 2. RECOMMEND ────────────────────────────────────────────────────────────
378
- @dataclass
379
- class Recommendation:
380
- runtime: str # llama.cpp | mlx | vllm | mlc-llm | tflite
381
- backend: str # cuda | metal+mlx | directml | vulkan | rocm | cpu
382
- model_id: str # 추천 모델 (huggingface-like id)
383
- quantization: str # q4_K_M | q5_K_M | mxfp4 | f16
384
- rationale: List[str] # 왜 이걸 골랐는지 (UI에 표시)
385
- estimated_tokens_per_sec: Optional[float] = None
386
-
387
- def to_json(self) -> Dict[str, Any]:
388
- return asdict(self)
389
-
390
-
391
- # 모델 카탈로그 — RAM/VRAM 내림차순. 첫 번째로 조건을 만족하는 항목이 선택된다.
392
- # 모든 repo id 는 2026-08-10 Hugging Face API 로 확인했다(존재·비gated·정확한 대소문자).
393
- # OS 오버헤드(~4-6 GB) + KV 캐시 여유를 감안한 보수적 RAM 임계값.
394
- _MODEL_CATALOG: List[Dict[str, Any]] = [
395
- # 대형 (48GB+): 최상위 dense / Gemma 4 최대 모델
396
- {"ram": 64 * 1024, "vram": 32 * 1024,
397
- "id": "mlx-community/gemma-4-31b-it-4bit", "q": "4bit", "multimodal": True},
398
- {"ram": 48 * 1024, "vram": 24 * 1024,
399
- "id": "mlx-community/Qwen3.6-27B-4bit", "q": "4bit", "multimodal": True},
400
- # MoE (32GB+)
401
- {"ram": 40 * 1024, "vram": 24 * 1024,
402
- "id": "mlx-community/Qwen3.6-35B-A3B-4bit", "q": "4bit", "multimodal": True},
403
- {"ram": 32 * 1024, "vram": 16 * 1024,
404
- "id": "mlx-community/gemma-4-26b-a4b-it-4bit", "q": "4bit", "multimodal": True},
405
- # 범용 (24GB)
406
- {"ram": 24 * 1024, "vram": 12 * 1024,
407
- "id": "mlx-community/gpt-oss-20b-MXFP4-Q8", "q": "MXFP4-Q8", "multimodal": False},
408
- # 중형 (16GB)
409
- {"ram": 16 * 1024, "vram": 8 * 1024,
410
- "id": "mlx-community/gemma-4-12B-it-4bit", "q": "4bit", "multimodal": True},
411
- {"ram": 12 * 1024, "vram": 6 * 1024,
412
- "id": "mlx-community/Qwen3.5-9B-MLX-4bit", "q": "4bit", "multimodal": True},
413
- # 경량 (10GB) / 초경량 (8GB 이하)
414
- {"ram": 10 * 1024, "vram": 6 * 1024,
415
- "id": "mlx-community/gemma-4-e4b-it-4bit", "q": "4bit", "multimodal": True},
416
- {"ram": 8 * 1024, "vram": 4 * 1024,
417
- "id": "mlx-community/gemma-4-e2b-it-4bit", "q": "4bit", "multimodal": True},
418
- # 최소 사양: 사진은 못 읽지만 한국어로 대답은 한다.
419
- {"ram": 4 * 1024, "vram": 0,
420
- "id": "mlx-community/LFM2.5-2.6B-4bit", "q": "4bit", "multimodal": False},
421
- ]
422
-
423
-
424
- def recommend(profile: SystemProfile) -> Recommendation:
425
- """② RECOMMEND — 프로파일을 보고 런타임/모델/양자화를 결정한다."""
426
- rationale: List[str] = []
427
-
428
- # backend / runtime
429
- if profile.os == "darwin" and profile.gpu.vendor == "apple":
430
- backend = "metal+mlx"
431
- runtime = "mlx" if _has_module("mlx_vlm") else "llama.cpp"
432
- rationale.append("Apple Silicon → Metal + MLX-VLM")
433
- elif profile.gpu.vendor == "nvidia" and profile.cuda_available and (profile.os == "linux" or profile.is_wsl):
434
- backend = "cuda"
435
- runtime = "vllm" if profile.gpu.vram_mb >= 12 * 1024 else "llama.cpp"
436
- rationale.append(f"NVIDIA GPU {profile.gpu.vram_mb} MB VRAM + CUDA → {runtime}")
437
- elif profile.gpu.vendor == "nvidia":
438
- backend = "cuda" if profile.cuda_available else "vulkan"
439
- runtime = "lmstudio" if profile.tools.get("lms") else ("ollama" if profile.tools.get("ollama") else "llama.cpp")
440
- rationale.append("Windows NVIDIA는 LM Studio/Ollama 우선, vLLM은 WSL/Linux 권장")
441
- elif profile.os == "windows" and profile.gpu.vendor in ("amd", "intel"):
442
- backend = "directml/vulkan"
443
- runtime = "lmstudio" if profile.tools.get("lms") else ("ollama" if profile.tools.get("ollama") else "llama.cpp")
444
- rationale.append("Windows + AMD/Intel GPU → DirectML/Vulkan")
445
- elif profile.os == "linux" and profile.gpu.vendor == "amd":
446
- backend = "rocm" if "rocm" in profile.gpu.sdk else "vulkan"
447
- runtime = "ollama" if profile.tools.get("ollama") else "llama.cpp"
448
- rationale.append("Linux + AMD GPU → ROCm/Vulkan")
449
- else:
450
- backend = "cpu"
451
- runtime = "ollama" if profile.tools.get("ollama") else "llama.cpp"
452
- instruction_hint = ", ".join(profile.cpu_instructions) or "명령어 미감지"
453
- rationale.append(f"GPU 가속이 없거나 미감지 → CPU 추론 ({profile.cpu_logical_cores or profile.cpu_cores} threads, {instruction_hint})")
454
-
455
- # model size by RAM/VRAM
456
- pick = _MODEL_CATALOG[-1] # 가장 작은 모델 기본값
457
- for entry in _MODEL_CATALOG:
458
- if profile.ram_mb >= entry["ram"] and (
459
- backend in {"cpu", "metal+mlx"} or profile.gpu.vram_mb >= entry["vram"]
460
- ):
461
- pick = entry
462
- break
463
- rationale.append(
464
- f"RAM {profile.ram_mb} MB · VRAM {profile.gpu.vram_mb} MB → {pick['id']}"
465
- )
466
- if pick.get("multimodal"):
467
- rationale.append("최신 멀티모달 모델을 우선 선택")
468
-
469
- # 양자화: VRAM 충분 → 더 정밀한 양자화로 업그레이드
470
- quant = pick["q"]
471
- if profile.gpu.vram_mb >= 24 * 1024:
472
- quant = "f16"
473
- rationale.append("VRAM ≥ 24 GB → f16 풀 정밀도")
474
-
475
- # 거친 tokens/sec 예측 (very rough)
476
- est_tps = None
477
- if backend == "cuda":
478
- est_tps = max(8.0, profile.gpu.vram_mb / 800)
479
- elif backend == "metal+mlx":
480
- est_tps = max(6.0, (profile.ram_mb // 1024) * 0.7)
481
- elif backend == "cpu":
482
- est_tps = max(1.5, profile.cpu_cores * 0.6)
483
-
484
- return Recommendation(
485
- runtime=runtime, backend=backend,
486
- model_id=pick["id"], quantization=quant,
487
- rationale=rationale, estimated_tokens_per_sec=est_tps,
488
- )
489
-
490
-
491
- # ── 3. INSTALL plan ─────────────────────────────────────────────────────────
492
- @dataclass
493
- class InstallStep:
494
- name: str
495
- why: str
496
- command: List[str]
497
- requires_admin: bool = False
498
-
499
- def to_json(self) -> Dict[str, Any]:
500
- return {
501
- "name": self.name,
502
- "why": self.why,
503
- "command": self.command,
504
- "requires_admin": self.requires_admin,
505
- "command_plan": command_plan(
506
- self.command,
507
- name=self.name,
508
- purpose="auto_setup_install",
509
- requires_admin=self.requires_admin,
510
- metadata={"why": self.why},
511
- ),
512
- }
513
-
514
-
515
- @dataclass
516
- class InstallPlan:
517
- package_manager: Optional[str]
518
- steps: List[InstallStep]
519
- notes: List[str] = field(default_factory=list)
520
-
521
- def to_json(self) -> Dict[str, Any]:
522
- plan_summary = command_plan_for_commands(
523
- [step.command for step in self.steps],
524
- name="auto_setup_plan",
525
- purpose="auto_setup_install",
526
- metadata={"package_manager": self.package_manager},
527
- )
528
- return {
529
- "package_manager": self.package_manager,
530
- "steps": [s.to_json() for s in self.steps],
531
- "notes": self.notes,
532
- "command_plan": plan_summary,
533
- "confirmation_token": plan_summary["confirmation_token"],
534
- }
535
-
536
-
537
- # 패키지 카탈로그: 핵심 의존성을 OS별 명령으로 매핑
538
- _PKG_MAP: Dict[str, Dict[str, Tuple[str, ...]]] = {
539
- # name : { pm : (cmd parts) }
540
- "python3.11+": {
541
- "winget": ("winget", "install", "-e", "--id", "Python.Python.3.11"),
542
- "brew": ("brew", "install", "python@3.11"),
543
- "apt": ("apt-get", "install", "-y", "python3.11"),
544
- "dnf": ("dnf", "install", "-y", "python3.11"),
545
- },
546
- "node20": {
547
- "winget": ("winget", "install", "-e", "--id", "OpenJS.NodeJS.LTS"),
548
- "brew": ("brew", "install", "node@20"),
549
- "apt": ("apt-get", "install", "-y", "nodejs"),
550
- "dnf": ("dnf", "install", "-y", "nodejs"),
551
- },
552
- "ollama": {
553
- "brew": ("brew", "install", "ollama"),
554
- "winget": ("winget", "install", "-e", "--id", "Ollama.Ollama"),
555
- "apt": ("sh", "-c", "curl -fsSL https://ollama.com/install.sh | sh"),
556
- },
557
- "huggingface-cli": {
558
- "brew": ("pip3", "install", "--upgrade", "huggingface_hub"),
559
- "winget": ("pip", "install", "--upgrade", "huggingface_hub"),
560
- "apt": ("pip3", "install", "--upgrade", "huggingface_hub"),
561
- "dnf": ("pip3", "install", "--upgrade", "huggingface_hub"),
562
- },
563
- }
564
-
565
-
566
- def plan(profile: SystemProfile, rec: Recommendation) -> InstallPlan:
567
- """③ INSTALL — 추천을 만족시키는 *명령 계획* 을 만든다. 실행하지 않는다."""
568
- pm = profile.package_manager
569
- steps: List[InstallStep] = []
570
- notes: List[str] = []
571
-
572
- def need(name: str, why: str) -> None:
573
- cmd_tuple = _PKG_MAP.get(name, {}).get(pm or "")
574
- if cmd_tuple:
575
- steps.append(InstallStep(
576
- name=name, why=why,
577
- command=list(cmd_tuple),
578
- requires_admin=(cmd_tuple[0] in ("apt-get", "dnf", "pacman")),
579
- ))
580
- else:
581
- notes.append(f"패키지 매니저 어댑터 없음: {name} ({pm}) — 수동 설치 필요")
582
-
583
- if sys.version_info < (3, 11):
584
- need("python3.11+", "Lattice AI 서버는 Python 3.11 이상이 필요합니다.")
585
- if not _which("node"):
586
- need("node20", "VSCode 확장 / npm CLI 부트스트랩에 필요")
587
-
588
- # 런타임별 추가
589
- if rec.runtime == "mlx" and not _has_module("mlx_vlm"):
590
- steps.append(InstallStep(
591
- name="mlx-vlm", why="Apple Silicon 멀티모달 추론",
592
- command=["pip3", "install", "--upgrade", "mlx-vlm"],
593
- ))
594
- if rec.runtime in {"llama.cpp", "ollama"} and not _which("ollama"):
595
- need("ollama", "llama.cpp 가중치를 가장 쉽게 받는 경로")
596
- if rec.runtime == "lmstudio" and not _which("lms"):
597
- notes.append("LM Studio CLI(lms)를 찾지 못했습니다. https://lmstudio.ai/download 에서 설치하면 Windows/macOS/Linux 모델 다운로드와 GPU 백엔드를 자동 감지합니다.")
598
- if rec.runtime == "vllm" and not _has_module("vllm"):
599
- steps.append(InstallStep(
600
- name="vllm", why="NVIDIA CUDA/WSL/Linux 서버형 추론",
601
- command=["pip3", "install", "--upgrade", "vllm", "huggingface_hub"],
602
- ))
603
- if profile.gpu.vendor == "nvidia" and not profile.cuda_available:
604
- notes.append("NVIDIA GPU는 감지됐지만 CUDA/nvidia-smi를 찾지 못했습니다. Windows에서는 NVIDIA 드라이버와 CUDA Toolkit 설치 후 재검사를 권장합니다.")
605
- if profile.os == "windows" and profile.gpu.vendor == "nvidia" and not profile.is_wsl:
606
- notes.append("vLLM은 Windows native보다 WSL2/Linux에서 안정적입니다. Windows 데스크톱은 LM Studio 또는 Ollama GPU 경로를 먼저 권장합니다.")
607
-
608
- if not _which("huggingface-cli"):
609
- need("huggingface-cli", "추천 모델 가중치 다운로드용")
610
-
611
- # 모델 가중치 풀
612
- model_command = ["huggingface-cli", "download", rec.model_id, "--quiet"]
613
- if rec.runtime == "ollama":
614
- # Local import: this module keeps a stdlib-only import graph so it can be
615
- # run before the app is wired. The alias table is the single source of
616
- # truth for per-engine repo ids — the old hardcoded if/elif chain here
617
- # drifted from it and still named Llama 4 Scout and Qwen3-VL.
618
- from latticeai.services.model_catalog import MODEL_ENGINE_ALIASES
619
- ollama_target = (MODEL_ENGINE_ALIASES.get(rec.model_id.lower()) or {}).get("ollama")
620
- if ollama_target:
621
- model_command = ["ollama", "pull", ollama_target]
622
- elif rec.runtime == "lmstudio":
623
- model_command = ["lms", "get", rec.model_id]
624
- steps.append(InstallStep(
625
- name=f"weights:{rec.model_id}",
626
- why="추론에 사용할 모델 가중치",
627
- command=model_command,
628
- ))
629
-
630
- return InstallPlan(package_manager=pm, steps=steps, notes=notes)
631
-
632
-
633
- def apply_plan(
634
- plan_obj: InstallPlan,
635
- *,
636
- confirm: bool = False,
637
- confirmation_token: Optional[str] = None,
638
- ) -> List[Dict[str, Any]]:
639
- """위험: 실제로 설치 명령을 실행한다. ``confirm`` and token are required."""
640
- if not confirm:
641
- raise RuntimeError("refuse to apply: pass confirm=True")
642
- expected = plan_obj.to_json().get("confirmation_token")
643
- if str(confirmation_token or "").strip() != expected:
644
- raise CommandConfirmationError("refuse to apply: confirmation token mismatch")
645
- results: List[Dict[str, Any]] = []
646
- for step in plan_obj.steps:
647
- step_plan = command_plan(
648
- step.command,
649
- name=step.name,
650
- purpose="auto_setup_install",
651
- requires_admin=step.requires_admin,
652
- metadata={"why": step.why},
653
- )
654
- try:
655
- require_command_confirmation(
656
- step.command,
657
- step_plan["confirmation_token"],
658
- purpose="auto_setup_install",
659
- )
660
- append_process_audit_event("installer_command", plan=step_plan, status="started")
661
- r = subprocess.run(step.command, capture_output=True, text=True,
662
- timeout=300, check=False)
663
- append_process_audit_event(
664
- "installer_command",
665
- plan=step_plan,
666
- status="finished",
667
- returncode=r.returncode,
668
- stdout=r.stdout,
669
- stderr=r.stderr,
670
- )
671
- results.append({
672
- "name": step.name,
673
- "command_hash": step_plan["command_hash"],
674
- "command_preview": step_plan["command_preview"],
675
- "returncode": r.returncode,
676
- "stdout_tail": (r.stdout or "")[-2000:],
677
- "stderr_tail": (r.stderr or "")[-2000:],
678
- })
679
- except Exception as exc:
680
- append_process_audit_event("installer_command", plan=step_plan, status="error", error=str(exc))
681
- results.append({"name": step.name, "command_hash": step_plan["command_hash"], "error": str(exc)})
682
- return results
683
-
684
-
685
- # ── 4. VERIFY ───────────────────────────────────────────────────────────────
686
- def verify(profile: SystemProfile, rec: Recommendation) -> Dict[str, Any]:
687
- """④ VERIFY — 가벼운 sanity check. 실제 LLM 추론 벤치는 별도 도구로.
688
- 여기서는 ‘설치된 것들이 import 되는가’ + ‘디스크/RAM 여유’ 정도만 본다."""
689
- checks: List[Dict[str, Any]] = []
690
-
691
- def add(label: str, ok: bool, detail: str = "") -> None:
692
- checks.append({"label": label, "ok": ok, "detail": detail})
693
-
694
- add("Python 3.11+", sys.version_info >= (3, 11), platform.python_version())
695
- add("RAM ≥ 4 GB", profile.ram_mb >= 4 * 1024, f"{profile.ram_mb} MB")
696
- add("디스크 여유 ≥ 8 GB", profile.disk_free_mb >= 8 * 1024,
697
- f"{profile.disk_free_mb} MB free")
698
-
699
- if rec.runtime == "mlx":
700
- add("mlx_vlm import", _has_module("mlx_vlm"), "Apple Silicon 멀티모달 런타임")
701
- if rec.runtime in {"llama.cpp", "ollama"}:
702
- add("ollama binary", _which("ollama") is not None,
703
- _which("ollama") or "not found")
704
- if rec.runtime == "lmstudio":
705
- add("LM Studio CLI", _which("lms") is not None, _which("lms") or "not found")
706
- if rec.backend == "cuda":
707
- add("CUDA/nvidia-smi", profile.cuda_available, profile.cuda_version or "not found")
708
-
709
- # CPU/메모리 잠깐 측정
710
- t0 = time.perf_counter()
711
- _ = sum(i * i for i in range(200_000))
712
- cpu_ms = (time.perf_counter() - t0) * 1000
713
- add("CPU latency sample", cpu_ms < 200, f"{cpu_ms:.1f} ms / 200k ops")
714
-
715
- return {
716
- "checks": checks,
717
- "all_pass": all(c["ok"] for c in checks),
718
- }
719
-
720
-
721
- # ── 5. PRESET ───────────────────────────────────────────────────────────────
722
- def preset(profile: SystemProfile, rec: Recommendation) -> Dict[str, Any]:
723
- """⑤ PRESET — UX 분기 + 단축키 + 테마 + MCP 도구 기본값.
724
-
725
- PPT 슬라이드 3 (모드 선택) · 17 (PRESET) 명세를 따른다.
726
- """
727
- # 기본 모드 vs 고급 모드 자동 선택 휴리스틱
728
- advanced = (
729
- profile.gpu.vendor in ("nvidia", "apple") or
730
- profile.ram_mb >= 24 * 1024 or
731
- "code" in (profile.cpu_model or "").lower() # 개발자 머신 추정
732
- )
733
- mode = "advanced" if advanced else "basic"
734
-
735
- # 단축키는 OS별 자연 컨벤션 따름
736
- mod = "Cmd" if profile.os == "darwin" else "Ctrl"
737
- shortcuts = {
738
- "newChat": f"{mod}+N",
739
- "toggleSidebar": f"{mod}+B",
740
- "openGraph": f"{mod}+G",
741
- "search": f"{mod}+K",
742
- "toggleMode": f"{mod}+Shift+M",
743
- "submit": "Enter",
744
- "newline": "Shift+Enter",
745
- }
746
-
747
- # 기본 MCP 도구 (PPT 슬라이드 11 의 기본 5종)
748
- mcp_defaults = [
749
- {"id": "filesystem", "scope": "local", "enabled": True},
750
- {"id": "web-search", "scope": "remote", "enabled": True},
751
- {"id": "code-execute", "scope": "local", "enabled": True},
752
- {"id": "browser-automation","scope": "remote","enabled": False},
753
- {"id": "database", "scope": "remote", "enabled": False},
754
- ]
755
-
756
- # 테마: OS 다크 모드 추종이 기본
757
- theme = {"mode": "auto", # auto | light | dark
758
- "accent": "#6E4AE6", # PPT 슬라이드 19 토큰
759
- "density": "comfortable" if mode == "basic" else "compact"}
760
-
761
- # 다국어: OS locale 기반 추정
762
- locale = os.environ.get("LANG", os.environ.get("LC_ALL", "ko_KR"))
763
- lang = "ko" if locale.lower().startswith("ko") else (
764
- "ja" if locale.lower().startswith("ja") else "en"
765
- )
766
-
767
- return {
768
- "mode": mode,
769
- "model": {"id": rec.model_id, "runtime": rec.runtime,
770
- "backend": rec.backend, "quantization": rec.quantization},
771
- "shortcuts": shortcuts,
772
- "mcp": mcp_defaults,
773
- "theme": theme,
774
- "language": lang,
775
- "tips": (
776
- ["기본 모드는 카드형 액션과 큰 입력창 위주. 언제든 '고급 모드' 로 전환할 수 있어요."]
777
- if mode == "basic"
778
- else ["고급 모드: 사이드바·디테일 패널·파이프라인 도구가 모두 활성화됩니다."]
779
- ),
780
- }
781
-
782
-
783
- # ── orchestrator ────────────────────────────────────────────────────────────
784
- def run_all(*, apply_install: bool = False, confirmation_token: Optional[str] = None) -> Dict[str, Any]:
785
- p = probe()
786
- r = recommend(p)
787
- pl = plan(p, r)
788
- install_results = None
789
- if apply_install:
790
- install_results = apply_plan(pl, confirm=True, confirmation_token=confirmation_token)
791
- v = verify(p, r)
792
- ps = preset(p, r)
793
- return {
794
- "probe": p.to_json(),
795
- "recommend": r.to_json(),
796
- "plan": pl.to_json(),
797
- "install": install_results,
798
- "verify": v,
799
- "preset": ps,
800
- }
801
-
802
-
803
- # ── CLI ────────────────────────────────────────────────────────────────────
804
- def _main() -> int:
805
- parser = argparse.ArgumentParser(prog="auto_setup",
806
- description="Lattice AI zero-config setup")
807
- sub = parser.add_subparsers(dest="cmd", required=True)
808
- sub.add_parser("probe")
809
- sub.add_parser("recommend")
810
- sp_plan = sub.add_parser("plan")
811
- sp_plan.add_argument("--apply", action="store_true",
812
- help="actually run the install commands (DANGER)")
813
- sp_plan.add_argument("--confirm-token", default=None,
814
- help="confirmation token from the dry-run plan output")
815
- sub.add_parser("verify")
816
- sub.add_parser("preset")
817
- sub.add_parser("all")
818
- args = parser.parse_args()
819
-
820
- if args.cmd == "probe":
821
- print(json.dumps(probe().to_json(), indent=2, ensure_ascii=False)); return 0
822
- if args.cmd == "recommend":
823
- p = probe(); r = recommend(p)
824
- print(json.dumps({"probe": p.to_json(), "recommend": r.to_json()},
825
- indent=2, ensure_ascii=False))
826
- return 0
827
- if args.cmd == "plan":
828
- p = probe(); r = recommend(p); pl = plan(p, r)
829
- out: Dict[str, Any] = {"plan": pl.to_json()}
830
- if args.apply:
831
- out["install"] = apply_plan(pl, confirm=True, confirmation_token=args.confirm_token)
832
- print(json.dumps(out, indent=2, ensure_ascii=False)); return 0
833
- if args.cmd == "verify":
834
- p = probe(); r = recommend(p)
835
- print(json.dumps(verify(p, r), indent=2, ensure_ascii=False)); return 0
836
- if args.cmd == "preset":
837
- p = probe(); r = recommend(p)
838
- print(json.dumps(preset(p, r), indent=2, ensure_ascii=False)); return 0
839
- if args.cmd == "all":
840
- print(json.dumps(run_all(apply_install=False), indent=2, ensure_ascii=False))
841
- return 0
842
- return 2
843
-
844
-
845
- if __name__ == "__main__":
846
- raise SystemExit(_main())