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,110 +0,0 @@
1
- """Reads over the real backing stores — the only place the tiers touch them.
2
-
3
- Six tiers, six backends, one rule: never invent. A workspace/snapshot/
4
- conversation read that fails raises :class:`MemoryServiceError` so the caller
5
- learns the tier is unreadable instead of receiving an empty list; a graph or
6
- vector read that fails degrades to ``None`` because those tiers are optional
7
- and report themselves as ``unavailable`` upstream.
8
-
9
- The conversation tier has two backings: the durable SQLite conversation store
10
- when one is wired (v4), and the legacy JSON history file otherwise.
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- import json
16
- from typing import Any, Dict, List, Optional
17
-
18
- from ._contract import MemoryCore as _Core
19
- from .constants import LOGGER, MemoryServiceError
20
-
21
-
22
- class MemoryStoreReadsMixin(_Core):
23
- """The store reads. Mixed into ``MemoryService``."""
24
-
25
- # ── helpers over the underlying stores ────────────────────────────────
26
- def _workspace_memories(self, *, user_email: Optional[str], workspace_id: Optional[str]) -> List[Dict[str, Any]]:
27
- try:
28
- return list(self._store.list_memories(user_email=user_email, workspace_id=workspace_id).get("memories", []))
29
- except Exception as exc:
30
- LOGGER.exception("workspace memory read failed")
31
- raise MemoryServiceError("workspace memory backend unavailable") from exc
32
-
33
- def _all_memories(self) -> List[Dict[str, Any]]:
34
- try:
35
- return list(self._store.list_memories().get("memories", []))
36
- except Exception as exc:
37
- LOGGER.exception("global memory read failed")
38
- raise MemoryServiceError("memory backend unavailable") from exc
39
-
40
- def _snapshots(self, *, workspace_id: Optional[str]) -> List[Dict[str, Any]]:
41
- try:
42
- return list(self._store.list_memory_snapshots(workspace_id=workspace_id, limit=200).get("snapshots", []))
43
- except Exception as exc:
44
- LOGGER.exception("memory snapshot read failed")
45
- raise MemoryServiceError("memory snapshot backend unavailable") from exc
46
-
47
- def _conversations(self) -> List[Dict[str, Any]]:
48
- if self._conversation_store is not None:
49
- try:
50
- grouped: Dict[str, List[Dict[str, Any]]] = {}
51
- for item in self._conversation_store.history():
52
- grouped.setdefault(item.get("conversation_id") or "legacy-previous-history", []).append(item)
53
- return [{"id": conv_id, "messages": msgs} for conv_id, msgs in grouped.items()]
54
- except Exception as exc:
55
- LOGGER.exception("conversation store read failed")
56
- raise MemoryServiceError("conversation backend unavailable") from exc
57
- if not self._history_file.exists():
58
- return []
59
- try:
60
- with open(self._history_file, "r", encoding="utf-8") as fh:
61
- data = json.load(fh)
62
- except (OSError, json.JSONDecodeError, TypeError, ValueError) as exc:
63
- LOGGER.exception("legacy conversation history read failed")
64
- raise MemoryServiceError("conversation history is unreadable") from exc
65
- if isinstance(data, dict):
66
- convs = data.get("conversations")
67
- if isinstance(convs, list):
68
- return convs
69
- return [{"id": k, **(v if isinstance(v, dict) else {"messages": v})} for k, v in data.items()]
70
- if isinstance(data, list):
71
- return data
72
- return []
73
-
74
- def _scoped_conversations(self, *, user_email: Optional[str], workspace_id: Optional[str]) -> List[Dict[str, Any]]:
75
- if not user_email:
76
- return self._conversations()
77
- target_workspace = workspace_id or "personal"
78
- scoped: List[Dict[str, Any]] = []
79
- for conversation in self._conversations():
80
- messages = conversation.get("messages") or []
81
- if not isinstance(messages, list):
82
- continue
83
- kept = [
84
- message
85
- for message in messages
86
- if isinstance(message, dict)
87
- and message.get("user_email") == user_email
88
- and (message.get("workspace_id") or "personal") == target_workspace
89
- ]
90
- if kept:
91
- scoped.append({**conversation, "messages": kept})
92
- return scoped
93
-
94
- def _kg_stats(self) -> Optional[Dict[str, Any]]:
95
- if not self._enable_graph:
96
- return None
97
- try:
98
- return self._kg.stats()
99
- except Exception:
100
- LOGGER.exception("knowledge graph stats read failed")
101
- return None
102
-
103
- def _kg_index(self) -> Optional[Dict[str, Any]]:
104
- if not self._enable_graph:
105
- return None
106
- try:
107
- return self._kg.index_status()
108
- except Exception:
109
- LOGGER.exception("knowledge graph index status read failed")
110
- return None
@@ -1,132 +0,0 @@
1
- """The shared storage half of the scoped preference dials.
2
-
3
- ``PermissionModeService``, ``NetworkBoundaryService`` and ``HybridPolicyService``
4
- are three different policies over one identical store: a JSON file under the
5
- data dir holding ``{"default": …, "users": {…}, "workspaces": {…}}``, guarded by
6
- a lock, rebindable after lazy construction, and read defensively enough that a
7
- truncated or hand-edited file degrades to defaults instead of taking the app
8
- down.
9
-
10
- That half was written three times, character for character. Three copies of a
11
- "corrupt file falls back to defaults" rule is three chances for one of them to
12
- grow an exception path the others do not have — and the symptom would be one
13
- dial silently forgetting a user's choice while the other two remember. The
14
- storage lives here once; each service supplies only what actually differs:
15
- the file name, what an unset ``default`` means, and how a scope resolves.
16
-
17
- Deliberately *not* shared: ``set_mode``/``set_policy``. They look similar but
18
- their write shapes differ (a scalar mode replaces, a policy patch merges) and
19
- their audit events carry different payloads, so folding them together would
20
- mean a parameterised method with more branches than the two bodies it replaced.
21
- """
22
-
23
- from __future__ import annotations
24
-
25
- import json
26
- import threading
27
- from pathlib import Path
28
- from typing import Any, Callable, ClassVar, Dict, Generic, Optional, TypeVar
29
-
30
- from latticeai.core.io_utils import atomic_write_json
31
-
32
- #: What ``resolve`` hands back — an enum member for the mode dials, a plain
33
- #: dict for the policy service.
34
- ResolvedT = TypeVar("ResolvedT")
35
-
36
- __all__ = ["JsonBackedModeService"]
37
-
38
-
39
- class JsonBackedModeService(Generic[ResolvedT]):
40
- """A lock-guarded, scope-aware JSON preference file.
41
-
42
- Subclasses set :attr:`FILENAME` and implement :meth:`_default_entry` and
43
- :meth:`_resolve_from`.
44
- """
45
-
46
- #: File name under the data dir. Subclasses must set this.
47
- FILENAME: ClassVar[str] = ""
48
-
49
- def __init__(
50
- self,
51
- *,
52
- data_dir: Path,
53
- audit: Optional[Callable[..., None]] = None,
54
- ) -> None:
55
- self._path = Path(data_dir) / self.FILENAME
56
- self._audit = audit or (lambda *a, **kw: None)
57
- self._lock = threading.Lock()
58
-
59
- # ── rebinding ────────────────────────────────────────────────────────────
60
- def rebind_data_dir(self, data_dir: Path) -> None:
61
- """Point the store at the app's real data dir.
62
-
63
- The wiring may instantiate a service lazily before routers know the
64
- configured data dir; rebinding keeps one file of record instead of
65
- stranding writes under the fallback path.
66
- """
67
- with self._lock:
68
- self._path = Path(data_dir) / self.FILENAME
69
-
70
- def rebind_audit(self, audit: Callable[..., None]) -> None:
71
- """Attach the real audit sink once app wiring provides one."""
72
- with self._lock:
73
- self._audit = audit
74
-
75
- # ── what each subclass must decide ───────────────────────────────────────
76
- def _default_entry(self) -> Any:
77
- """The ``default`` bucket's value when the file does not say.
78
-
79
- Called fresh at every use because the policy service's answer is a
80
- mutable dict; a shared instance would let one caller's edit leak into
81
- the next caller's defaults.
82
- """
83
- raise NotImplementedError
84
-
85
- def _resolve_from(
86
- self,
87
- data: Dict[str, Any],
88
- *,
89
- user_email: Optional[str],
90
- workspace_id: Optional[str],
91
- ) -> ResolvedT:
92
- """Apply this dial's scope precedence to already-loaded ``data``.
93
-
94
- Pure over ``data`` and lock-free, so holders of ``_lock`` can reuse it
95
- without re-entering a non-reentrant lock.
96
- """
97
- raise NotImplementedError
98
-
99
- # ── storage ──────────────────────────────────────────────────────────────
100
- def _empty(self) -> Dict[str, Any]:
101
- return {"default": self._default_entry(), "users": {}, "workspaces": {}}
102
-
103
- def _read(self) -> Dict[str, Any]:
104
- if not self._path.exists():
105
- return self._empty()
106
- try:
107
- data = json.loads(self._path.read_text(encoding="utf-8"))
108
- except Exception:
109
- return self._empty()
110
- if not isinstance(data, dict):
111
- return self._empty()
112
- data.setdefault("default", self._default_entry())
113
- data.setdefault("users", {})
114
- data.setdefault("workspaces", {})
115
- return data
116
-
117
- def _write(self, data: Dict[str, Any]) -> None:
118
- self._path.parent.mkdir(parents=True, exist_ok=True)
119
- atomic_write_json(self._path, data)
120
-
121
- # ── reading ──────────────────────────────────────────────────────────────
122
- def resolve(
123
- self,
124
- *,
125
- user_email: Optional[str] = None,
126
- workspace_id: Optional[str] = None,
127
- ) -> ResolvedT:
128
- with self._lock:
129
- data = self._read()
130
- return self._resolve_from(
131
- data, user_email=user_email, workspace_id=workspace_id,
132
- )
@@ -1,224 +0,0 @@
1
- """Hardware-aware local model recommendation.
2
-
3
- Given a detected system profile (from :func:`auto_setup.probe`) this module
4
- classifies every model in :data:`model_catalog.ENGINE_MODEL_CATALOG` into one of
5
- three states — **recommended**, **compatible**, or **not_recommended** — and
6
- groups the result by current model family (Gemma 4, Qwen3.6, Qwen3.5, GPT-OSS,
7
- LFM2.5).
8
-
9
- It is intentionally pure and dependency-light: the only input is a plain dict
10
- describing the machine, so it is fully unit-testable without touching real
11
- hardware, and it does not import the FastAPI app or the runtime. The setup /
12
- onboarding routers build the profile via ``auto_setup.probe().to_json()`` and
13
- hand it here.
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import re
19
- from typing import Any, Dict, List, Optional
20
-
21
- from latticeai.core.model_compat import model_runtime_compatibility
22
- from latticeai.services.model_catalog import ENGINE_MODEL_CATALOG
23
-
24
- # ── status vocabulary ─────────────────────────────────────────────────────────
25
- RECOMMENDED = "recommended"
26
- COMPATIBLE = "compatible"
27
- NOT_RECOMMENDED = "not_recommended"
28
-
29
- # Engines whose models load on any OS (given the engine binary) vs. MLX which is
30
- # Apple-Silicon only. Used to decide platform availability before sizing.
31
- _APPLE_ONLY_ENGINES = {"local_mlx"}
32
-
33
- # Family display order for the grouped view (current generations, largest
34
- # lineup first). Superseded families are not listed because the capability
35
- # registry never lets them reach the catalog — they are recognised for loading
36
- # only. A family missing from this list still renders, it just sorts last.
37
- _FAMILY_ORDER = [
38
- "Gemma 4",
39
- "Qwen3.6",
40
- "Qwen3.5",
41
- "GPT-OSS",
42
- "LFM2.5",
43
- ]
44
-
45
- # Modalities the recommender will surface. Text-only models earn their place —
46
- # LFM2.5 is the only thing that runs comfortably on 8GB, and GPT-OSS 20B is the
47
- # most-downloaded entry in the catalog — so filtering to `multimodal` would have
48
- # hidden two of the tiers. Each row still reports its own `modality`, so a UI
49
- # that wants to badge "reads pictures" can, honestly.
50
- _RECOMMENDABLE_MODALITIES = {"multimodal", "text"}
51
-
52
- _SIZE_RE = re.compile(r"([\d.]+)\s*(TB|GB|MB)", re.IGNORECASE)
53
- _UNIT_GB = {"TB": 1024.0, "GB": 1.0, "MB": 1.0 / 1024.0}
54
-
55
-
56
- def parse_size_gb(size: Any) -> Optional[float]:
57
- """Parse a catalog ``size`` string (``"4.7GB"``, ``"963MB"``, ``"40GB+"``).
58
-
59
- Returns ``None`` when the size is non-numeric (e.g. ``"pull required"`` or
60
- ``"실행 도구에서 관리"``) so callers can treat it as "size unknown".
61
- """
62
- if not isinstance(size, str):
63
- return None
64
- match = _SIZE_RE.search(size)
65
- if not match:
66
- return None
67
- value = float(match.group(1))
68
- return round(value * _UNIT_GB[match.group(2).upper()], 3)
69
-
70
-
71
- def estimated_ram_gb(size_gb: float) -> float:
72
- """Rough RAM needed to run a model: weights + KV cache + OS working set."""
73
- return round(size_gb * 1.25 + 2.5, 2)
74
-
75
-
76
- def is_apple_silicon(profile: Dict[str, Any]) -> bool:
77
- os_name = str(profile.get("os") or "").lower()
78
- arch = str(profile.get("arch") or "").lower()
79
- gpu = profile.get("gpu") or {}
80
- vendor = str(gpu.get("vendor") or "").lower()
81
- return os_name == "darwin" and (vendor == "apple" or arch in {"arm64", "aarch64"})
82
-
83
-
84
- def _ram_gb(profile: Dict[str, Any]) -> float:
85
- try:
86
- return max(0.0, float(profile.get("ram_mb") or 0) / 1024.0)
87
- except (TypeError, ValueError):
88
- return 0.0
89
-
90
-
91
- def _engine_available(engine: str, profile: Dict[str, Any]) -> bool:
92
- if engine in _APPLE_ONLY_ENGINES:
93
- return is_apple_silicon(profile)
94
- # ollama / llamacpp / lmstudio / vllm run cross-platform once installed.
95
- return True
96
-
97
-
98
- def _classify_one(
99
- model: Dict[str, Any],
100
- *,
101
- engine: str,
102
- engine_available: bool,
103
- ram_gb: float,
104
- ) -> Dict[str, Any]:
105
- size_gb = parse_size_gb(model.get("size"))
106
- need_gb = estimated_ram_gb(size_gb) if size_gb is not None else None
107
- runtime = model_runtime_compatibility(str(model.get("id") or ""), engine=engine)
108
-
109
- if not engine_available:
110
- status, reason = NOT_RECOMMENDED, "Apple Silicon과 MLX-VLM이 필요합니다"
111
- elif runtime.get("supported") is False:
112
- status = NOT_RECOMMENDED
113
- reason = str(runtime.get("user_message") or "이 모델은 현재 설치된 실행 런타임에서 지원되지 않습니다")
114
- elif need_gb is None:
115
- # Tool-managed/pull models have no fixed on-disk size, so treat them as
116
- # compatible and let the execution tool validate the exact model.
117
- status, reason = COMPATIBLE, "선택한 실행 방식에서 필요할 때 모델을 받습니다"
118
- elif ram_gb <= 0:
119
- status, reason = COMPATIBLE, "메모리 정보를 확인하지 못했습니다. 불러오기 전에 검증합니다"
120
- elif need_gb <= ram_gb * 0.75:
121
- status, reason = RECOMMENDED, f"현재 메모리에서 안정적으로 사용할 가능성이 높습니다 (~{need_gb:.0f} GB / {ram_gb:.0f} GB)"
122
- elif need_gb <= ram_gb * 0.9:
123
- status, reason = COMPATIBLE, f"사용 가능하지만 여유가 적습니다 (~{need_gb:.0f} GB / {ram_gb:.0f} GB)"
124
- else:
125
- status, reason = NOT_RECOMMENDED, f"권장 메모리가 부족합니다 (~{need_gb:.0f} GB 필요, 현재 {ram_gb:.0f} GB)"
126
-
127
- rich = {
128
- "id": model.get("id"),
129
- "name": model.get("name"),
130
- "model_name": model.get("model_name") or model.get("name"),
131
- "family": model.get("family"),
132
- "tag": model.get("tag"),
133
- "modality": model.get("modality") or "multimodal",
134
- "size": model.get("size"),
135
- "size_gb": size_gb,
136
- "required_ram_gb": need_gb,
137
- "status": status,
138
- "reason": reason,
139
- "source_country": model.get("source_country"),
140
- "source_company": model.get("source_company"),
141
- "execution_method": model.get("execution_method"),
142
- "run_location": model.get("run_location"),
143
- "internet_requirement": model.get("internet_requirement"),
144
- "source_display_order": model.get("source_display_order"),
145
- "runtime_compatibility": runtime,
146
- # 5.2+ user-focused transparency
147
- "hf_repo_id": model.get("hf_repo_id"),
148
- "quantization": model.get("quantization"),
149
- "download_strategy": model.get("download_strategy"),
150
- "load_strategy": model.get("load_strategy"),
151
- "hardware": model.get("hardware"),
152
- "license": model.get("license"),
153
- "safety_notes": model.get("safety_notes"),
154
- "verification": model.get("verification"),
155
- "recommended_default": model.get("recommended_default", False),
156
- }
157
- return rich
158
-
159
-
160
- def _family_rank(family: str) -> int:
161
- try:
162
- return _FAMILY_ORDER.index(family)
163
- except ValueError:
164
- return len(_FAMILY_ORDER)
165
-
166
-
167
- def recommend_catalog(profile: Dict[str, Any], *, engine: str = "local_mlx") -> Dict[str, Any]:
168
- """Classify ``engine``'s catalog for the given machine ``profile``.
169
-
170
- ``profile`` is a dict shaped like ``auto_setup.SystemProfile.to_json()``
171
- (``os``, ``arch``, ``ram_mb``, ``gpu={vendor,vram_mb}`` …).
172
- """
173
- models = [
174
- model for model in ENGINE_MODEL_CATALOG.get(engine, [])
175
- if str(model.get("modality") or "").lower() in _RECOMMENDABLE_MODALITIES
176
- ]
177
- engine_available = _engine_available(engine, profile)
178
- ram_gb = _ram_gb(profile)
179
-
180
- classified = [
181
- _classify_one(m, engine=engine, engine_available=engine_available, ram_gb=ram_gb)
182
- for m in models
183
- ]
184
-
185
- counts = {RECOMMENDED: 0, COMPATIBLE: 0, NOT_RECOMMENDED: 0}
186
- for item in classified:
187
- counts[item["status"]] += 1
188
-
189
- # Group by family, ordered, with the best pick per family surfaced.
190
- by_family: Dict[str, Dict[str, Any]] = {}
191
- for item in classified:
192
- fam = item["family"] or "Other"
193
- bucket = by_family.setdefault(fam, {"family": fam, "models": [], "best": None})
194
- bucket["models"].append(item)
195
-
196
- def _best(models: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
197
- # Prefer recommended, then compatible; within a tier prefer the largest
198
- # model that still fits (more capable).
199
- for tier in (RECOMMENDED, COMPATIBLE):
200
- tier_models = [m for m in models if m["status"] == tier]
201
- if tier_models:
202
- return max(tier_models, key=lambda m: m["size_gb"] or 0.0)
203
- return None
204
-
205
- families = []
206
- for fam in sorted(by_family, key=_family_rank):
207
- bucket = by_family[fam]
208
- bucket["best"] = _best(bucket["models"])
209
- families.append(bucket)
210
-
211
- # Overall top pick: the largest recommended model on this machine.
212
- recommended_models = [m for m in classified if m["status"] == RECOMMENDED]
213
- top_pick = max(recommended_models, key=lambda m: m["size_gb"] or 0.0) if recommended_models else None
214
-
215
- return {
216
- "engine": engine,
217
- "engine_available": engine_available,
218
- "apple_silicon": is_apple_silicon(profile),
219
- "ram_gb": round(ram_gb, 1),
220
- "counts": counts,
221
- "top_pick": top_pick,
222
- "families": families,
223
- "models": classified,
224
- }
@@ -1,87 +0,0 @@
1
- """Cloud model verification — does this key actually answer for this model?
2
-
3
- A one-token chat completion per configured cloud model, cached per service
4
- instance for :data:`CLOUD_VERIFY_TTL_SECONDS`. A model the router already knows
5
- is unavailable is recorded as such without a network call.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import asyncio
11
- import os
12
- import time
13
- from typing import Any, Dict, Optional
14
-
15
- from latticeai.models.router import (
16
- OPENAI_COMPATIBLE_PROVIDERS,
17
- AsyncOpenAI,
18
- parse_model_ref,
19
- )
20
- from latticeai.services.model_runtime.state import ModelRuntimeState
21
-
22
- CLOUD_VERIFY_TTL_SECONDS = 600
23
-
24
- async def _probe_cloud_model(model_ref: str) -> Dict[str, Any]:
25
- provider, model_name = parse_model_ref(model_ref)
26
- config = OPENAI_COMPATIBLE_PROVIDERS.get(provider)
27
- if not config:
28
- return {"ok": False, "reason": f"Unsupported provider: {provider}"}
29
-
30
- api_key = os.getenv(config["env_key"]) or config.get("api_key_fallback")
31
- if not api_key:
32
- return {"ok": False, "reason": f"Missing API key: {config['env_key']}"}
33
-
34
- base_url = os.getenv(config.get("base_url_env", "")) if config.get("base_url_env") else None
35
- base_url = base_url or config.get("base_url")
36
- try:
37
- # base_url is passed only when configured: an explicit None is not
38
- # the same as omitting the argument.
39
- client = (
40
- AsyncOpenAI(api_key=api_key, base_url=base_url)
41
- if base_url
42
- else AsyncOpenAI(api_key=api_key)
43
- )
44
- await asyncio.wait_for(
45
- client.chat.completions.create(
46
- model=model_name,
47
- messages=[{"role": "user", "content": "ping"}],
48
- max_tokens=1,
49
- temperature=0,
50
- ),
51
- timeout=15,
52
- )
53
- return {"ok": True, "reason": "ok"}
54
- except Exception as e:
55
- return {"ok": False, "reason": str(e)[:220]}
56
-
57
-
58
- async def verify_cloud_models(
59
- force: bool = False,
60
- provider_filter: Optional[str] = None,
61
- *,
62
- state: ModelRuntimeState,
63
- cache: Dict[str, Dict[str, Any]],
64
- ) -> Dict[str, Dict]:
65
- now = time.time()
66
- r = state.router
67
- cloud_items = [item for item in (r.detected_cloud_models() if r else []) if item.get("tag") == "cloud"]
68
- if provider_filter:
69
- cloud_items = [item for item in cloud_items if item.get("provider") == provider_filter]
70
-
71
- results: Dict[str, Dict] = {}
72
- for item in cloud_items:
73
- model_ref = item["id"]
74
- cached = cache.get(model_ref)
75
- if not force and cached and (now - cached.get("ts", 0) <= CLOUD_VERIFY_TTL_SECONDS):
76
- results[model_ref] = cached
77
- continue
78
- if item.get("available") is False:
79
- record = {"ok": False, "reason": item.get("requires") or "API key missing", "ts": now}
80
- cache[model_ref] = record
81
- results[model_ref] = record
82
- continue
83
- probe = await _probe_cloud_model(model_ref)
84
- record = {"ok": bool(probe.get("ok")), "reason": probe.get("reason", ""), "ts": now}
85
- cache[model_ref] = record
86
- results[model_ref] = record
87
- return results
@@ -1,117 +0,0 @@
1
- """Persisted network-boundary preference for hybrid local + cloud turns.
2
-
3
- Mirrors PermissionModeService:
4
-
5
- * process default (env ``LATTICEAI_NETWORK_MODE`` or local_only)
6
- * per-user override
7
- * per-workspace override (wins over user)
8
-
9
- Switching to ``cloud_allowed`` requires ``acknowledge_risk=True`` once; the
10
- ack is audited. Hard node filters remain in ``latticeai.core.network_boundary``.
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- from pathlib import Path
16
- from typing import Any, Callable, Dict, Optional
17
-
18
- from latticeai.core.network_boundary import (
19
- DEFAULT_NETWORK_MODE,
20
- NetworkBoundaryMode,
21
- network_mode_catalog,
22
- network_mode_contract,
23
- normalize_network_mode,
24
- )
25
- from latticeai.services.mode_store import JsonBackedModeService
26
-
27
-
28
- class NetworkBoundaryService(JsonBackedModeService[NetworkBoundaryMode]):
29
- """Load / save the network boundary dial."""
30
-
31
- FILENAME = "network_boundary.json"
32
-
33
- def __init__(
34
- self,
35
- *,
36
- data_dir: Path,
37
- default_mode: NetworkBoundaryMode | str = DEFAULT_NETWORK_MODE,
38
- audit: Optional[Callable[..., None]] = None,
39
- ) -> None:
40
- super().__init__(data_dir=data_dir, audit=audit)
41
- self._default = normalize_network_mode(default_mode)
42
-
43
- def _default_entry(self) -> Any:
44
- return self._default.value
45
-
46
- def _resolve_from(
47
- self,
48
- data: Dict[str, Any],
49
- *,
50
- user_email: Optional[str],
51
- workspace_id: Optional[str],
52
- ) -> NetworkBoundaryMode:
53
- if workspace_id:
54
- ws = (data.get("workspaces") or {}).get(str(workspace_id))
55
- if ws:
56
- return normalize_network_mode(ws)
57
- if user_email:
58
- user = (data.get("users") or {}).get(str(user_email).lower())
59
- if user:
60
- return normalize_network_mode(user)
61
- return normalize_network_mode(data.get("default") or self._default)
62
-
63
- def get(
64
- self,
65
- *,
66
- user_email: Optional[str] = None,
67
- workspace_id: Optional[str] = None,
68
- ) -> Dict[str, Any]:
69
- mode = self.resolve(user_email=user_email, workspace_id=workspace_id)
70
- contract = network_mode_contract(mode)
71
- contract["catalog"] = network_mode_catalog()
72
- contract["scope"] = {
73
- "user_email": user_email,
74
- "workspace_id": workspace_id,
75
- }
76
- return contract
77
-
78
- def set_mode(
79
- self,
80
- mode: NetworkBoundaryMode | str,
81
- *,
82
- user_email: Optional[str] = None,
83
- workspace_id: Optional[str] = None,
84
- acknowledge_risk: bool = False,
85
- source: str = "api",
86
- ) -> Dict[str, Any]:
87
- mode = normalize_network_mode(mode)
88
- if mode == NetworkBoundaryMode.CLOUD_ALLOWED and not acknowledge_risk:
89
- raise PermissionError(
90
- "cloud_allowed mode requires acknowledge_risk=true "
91
- "(minimal related Knowledge Graph nodes may leave this machine)"
92
- )
93
- with self._lock:
94
- data = self._read()
95
- previous = self._resolve_from(
96
- data, user_email=user_email, workspace_id=workspace_id,
97
- )
98
- if workspace_id:
99
- data.setdefault("workspaces", {})[str(workspace_id)] = mode.value
100
- elif user_email:
101
- data.setdefault("users", {})[str(user_email).lower()] = mode.value
102
- else:
103
- data["default"] = mode.value
104
- self._write(data)
105
- self._audit(
106
- "network_boundary_changed",
107
- user_email=user_email,
108
- workspace_id=workspace_id,
109
- previous=previous.value,
110
- mode=mode.value,
111
- source=source,
112
- acknowledge_risk=bool(acknowledge_risk),
113
- )
114
- return self.get(user_email=user_email, workspace_id=workspace_id)
115
-
116
-
117
- __all__ = ["NetworkBoundaryService"]