ltcai 11.5.2 → 11.6.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 (439) hide show
  1. package/README.md +91 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +75 -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 +21 -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-CToZnOHz.js} +1 -1
  96. package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
  100. package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
  107. package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
  108. package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
  112. package/static/app/assets/circle-check-CY20XMCh.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
  119. package/static/app/assets/index-BovSWRmQ.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
  124. package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
  125. package/static/app/assets/search-DTxtjQ07.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.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/arrow-left-kfsrk0mv.js +0 -1
  437. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  438. package/static/app/assets/index-DxmOfNRi.css +0 -2
  439. package/static/app/assets/search-BcHqkjoy.js +0 -1
@@ -1,506 +0,0 @@
1
- """
2
- Lattice AI — MCP Registry data & pure helper functions.
3
-
4
- Extracted from server.py to reduce module size.
5
- """
6
-
7
- import asyncio
8
- import json
9
- import logging
10
- import os
11
- import subprocess
12
- import sys
13
- from datetime import datetime, timedelta
14
- from pathlib import Path
15
- from typing import Callable, Dict, List, Optional
16
-
17
- import httpx
18
- from fastapi import HTTPException
19
-
20
- # ── MCP Registry (built-in tool definitions) ─────────────────────────────────
21
- # Built-in MCP server catalog data → .mcp_catalog (re-exported).
22
- from .mcp_catalog import MCP_REGISTRY # noqa: F401
23
-
24
- # ── Remote MCP Registry (registry.modelcontextprotocol.io) ───────────────────
25
- _REMOTE_REGISTRY_CACHE: List[Dict] = []
26
- _REMOTE_REGISTRY_FETCHED_AT: Optional[datetime] = None
27
- _REMOTE_REGISTRY_TTL = timedelta(hours=1)
28
- _REMOTE_REGISTRY_URL = "https://registry.modelcontextprotocol.io/v0/servers"
29
- _LOCAL_IDS = {e["id"] for e in MCP_REGISTRY}
30
-
31
- async def _fetch_remote_mcp_registry() -> List[Dict]:
32
- global _REMOTE_REGISTRY_CACHE, _REMOTE_REGISTRY_FETCHED_AT
33
- now = datetime.now()
34
- if _REMOTE_REGISTRY_FETCHED_AT and (now - _REMOTE_REGISTRY_FETCHED_AT) < _REMOTE_REGISTRY_TTL:
35
- return _REMOTE_REGISTRY_CACHE
36
- try:
37
- result: List[Dict] = []
38
- cursor = None
39
- async with httpx.AsyncClient(timeout=10.0) as client:
40
- while True:
41
- params: Dict = {"limit": 100}
42
- if cursor:
43
- params["cursor"] = cursor
44
- resp = await client.get(_REMOTE_REGISTRY_URL, params=params)
45
- resp.raise_for_status()
46
- data = resp.json()
47
- for s in data.get("servers", []):
48
- srv = s["server"]
49
- meta = s.get("_meta", {}).get("io.modelcontextprotocol.registry/official", {})
50
- if not meta.get("isLatest", True):
51
- continue
52
- pkg = next(
53
- (p for p in srv.get("packages", [])
54
- if p.get("transport", {}).get("type") == "stdio"
55
- and p.get("registryType") in ("npm", "pypi")),
56
- None,
57
- )
58
- if not pkg:
59
- continue
60
- entry_id = srv["name"].replace("/", "-").replace(".", "-")
61
- if entry_id in _LOCAL_IDS:
62
- continue
63
- result.append({
64
- "id": entry_id,
65
- "name": srv.get("title") or srv["name"],
66
- "category": "MCP Registry",
67
- "install_mode": pkg["registryType"],
68
- "package": pkg["identifier"],
69
- "package_version": pkg.get("version"),
70
- "description": srv.get("description", ""),
71
- "keywords": [],
72
- "capabilities": [],
73
- "source": "registry",
74
- "homepage": (srv.get("repository") or {}).get("url"),
75
- })
76
- cursor = data.get("nextCursor")
77
- if not cursor:
78
- break
79
- _REMOTE_REGISTRY_CACHE = result
80
- _REMOTE_REGISTRY_FETCHED_AT = now
81
- logging.info("Fetched %d stdio MCP servers from remote registry", len(result))
82
- except Exception as e:
83
- logging.warning("Failed to fetch remote MCP registry: %s", e)
84
- return _REMOTE_REGISTRY_CACHE
85
-
86
- async def _get_combined_registry() -> List[Dict]:
87
- remote = await _fetch_remote_mcp_registry()
88
- return MCP_REGISTRY + remote
89
-
90
- # ── Anthropic Skills Marketplace (Apache 2.0) ─────────────────────────────────
91
- _MARKETPLACE_RAW = "https://raw.githubusercontent.com/anthropics/claude-plugins-official/main"
92
- _MARKETPLACE_API = "https://api.github.com/repos/anthropics/claude-plugins-official/contents"
93
-
94
- # 검증된 서드파티 skills 소스 (Apache-2.0 / MIT)
95
- _THIRD_PARTY_SKILL_SOURCES: List[Dict] = [
96
- {
97
- "plugin": "adobe-for-creativity", "author": "Adobe", "license": "Apache-2.0",
98
- "repo": "adobe/skills", "branch": "main",
99
- "plugin_path": "plugins/creative-cloud/adobe-for-creativity",
100
- "category": "design",
101
- },
102
- {
103
- "plugin": "airtable", "author": "Airtable", "license": "MIT",
104
- "repo": "Airtable/skills", "branch": "main",
105
- "plugin_path": "plugins/airtable",
106
- "category": "productivity",
107
- },
108
- {
109
- "plugin": "auth0", "author": "Auth0", "license": "Apache-2.0",
110
- "repo": "auth0/agent-skills", "branch": "main",
111
- "plugin_path": "plugins/auth0",
112
- "category": "security",
113
- },
114
- {
115
- "plugin": "expo", "author": "Expo", "license": "MIT",
116
- "repo": "expo/skills", "branch": "main",
117
- "plugin_path": "plugins/expo",
118
- "category": "development",
119
- },
120
- {
121
- "plugin": "logfire", "author": "Pydantic", "license": "MIT",
122
- "repo": "pydantic/skills", "branch": "main",
123
- "plugin_path": "plugins/logfire",
124
- "category": "monitoring",
125
- },
126
- ]
127
-
128
- # 검증된 레포 라이선스 맵 (GitHub API 없이 빠르게 조회)
129
- _KNOWN_REPO_LICENSES: Dict[str, str] = {
130
- # Apache-2.0
131
- "adobe/skills": "Apache-2.0", "awslabs/agent-plugins": "Apache-2.0",
132
- "auth0/agent-skills": "Apache-2.0", "aws/agent-toolkit-for-aws": "Apache-2.0",
133
- "carta/plugins": "Apache-2.0", "circlefin/skills": "Apache-2.0",
134
- "clickhouse/clickhouse-docs": "Apache-2.0", "cloudflare/agents": "Apache-2.0",
135
- "cockroachdb/claude-code": "Apache-2.0", "codspeed-hq/codspeed-claude": "Apache-2.0",
136
- "DataDog/datadog-claude-code": "Apache-2.0", "datahub-project/datahub-skills": "Apache-2.0",
137
- "neondatabase/agent-skills": "Apache-2.0", "PagerDuty/pd-ai-agents-plugins": "Apache-2.0",
138
- "getpostman/postman-mcp-server": "Apache-2.0", "qdrant/qdrant-skills": "Apache-2.0",
139
- "rootlyhq/rootly-plugins": "Apache-2.0", "snowflake-labs/snowflake-claude": "Apache-2.0",
140
- "sumup/sumup-claude": "Apache-2.0", "zilliz-labs/zilliz-skills": "Apache-2.0",
141
- "mercadopago/mercadopago-claude-marketplace": "Apache-2.0",
142
- # MIT
143
- "Airtable/skills": "MIT", "endorlabs/ai-plugins": "MIT",
144
- "apollographql/apollo-claude-skills": "MIT", "appwrite/skills": "MIT",
145
- "atlan-inc/claude-code-skills": "MIT", "boxer/boxerbox": "MIT",
146
- "buildkite/claude-code": "MIT", "coderabbitai/coderabbit-skills": "MIT",
147
- "CrowdStrike/crowdstrike-skills": "MIT", "microsoft/Dataverse-skills": "MIT",
148
- "duckdb/duckdb-skills": "MIT", "expo/skills": "MIT",
149
- "intercom/intercom-skills": "MIT", "pydantic/skills": "MIT",
150
- "mapbox/mapbox-skills": "MIT", "mintlify/mintlify-skills": "MIT",
151
- "miroapp/miro-ai": "MIT", "netlify/netlify-skills": "MIT",
152
- "pinecone-io/pinecone-skills": "MIT", "railwayapp/railway-skills": "MIT",
153
- "resend/resend-skills": "MIT", "sanity-io/sanity-skills": "MIT",
154
- "getsentry/sentry-ai-skills": "MIT", "Shopify/liquid-skills": "MIT",
155
- "slackapi/slack-skills": "MIT", "stripe/stripe-skills": "MIT",
156
- "twilio-labs/twilio-skills": "MIT", "workos/workos-skills": "MIT",
157
- "zoom/zoom-skills": "MIT", "aws-samples/sample-claude-code-plugins-for-startups": "MIT-0",
158
- }
159
-
160
- _SKILLS_MARKETPLACE_CACHE: List[Dict] = []
161
- _SKILLS_MARKETPLACE_FETCHED_AT: Optional[datetime] = None
162
- _SKILLS_MARKETPLACE_TTL = timedelta(hours=1)
163
-
164
- def _extract_skill_desc(skill_md: str, fallback: str) -> str:
165
- for line in skill_md.splitlines():
166
- if line.startswith("description:"):
167
- return line.split(":", 1)[1].strip()
168
- return fallback
169
-
170
- async def _fetch_plugin_skills(client: httpx.AsyncClient, source: Dict) -> List[Dict]:
171
- """단일 소스에서 skill 목록을 fetch해 반환"""
172
- repo, branch, plugin_path = source["repo"], source["branch"], source["plugin_path"]
173
- raw_base = f"https://raw.githubusercontent.com/{repo}/{branch}"
174
- api_base = f"https://api.github.com/repos/{repo}/contents"
175
- homepage_base = f"https://github.com/{repo}/tree/{branch}"
176
-
177
- dir_resp = await client.get(f"{api_base}/{plugin_path}/skills")
178
- if dir_resp.status_code != 200:
179
- return []
180
- skill_dirs = [f["name"] for f in dir_resp.json() if f["type"] == "dir"]
181
-
182
- skills: List[Dict] = []
183
- for skill_name in skill_dirs:
184
- skill_md_url = f"{raw_base}/{plugin_path}/skills/{skill_name}/SKILL.md"
185
- sm_resp = await client.get(skill_md_url)
186
- if sm_resp.status_code != 200:
187
- continue
188
- skills.append({
189
- "plugin": source["plugin"],
190
- "skill": skill_name,
191
- "category": source.get("category", "development"),
192
- "description": _extract_skill_desc(sm_resp.text, source.get("description", "")),
193
- "skill_md_url": skill_md_url,
194
- "homepage": f"{homepage_base}/{plugin_path}/skills/{skill_name}",
195
- "license": source["license"],
196
- "author": source["author"],
197
- })
198
- return skills
199
-
200
- async def _fetch_skills_marketplace() -> List[Dict]:
201
- global _SKILLS_MARKETPLACE_CACHE, _SKILLS_MARKETPLACE_FETCHED_AT
202
- now = datetime.now()
203
- if _SKILLS_MARKETPLACE_FETCHED_AT and (now - _SKILLS_MARKETPLACE_FETCHED_AT) < _SKILLS_MARKETPLACE_TTL:
204
- return _SKILLS_MARKETPLACE_CACHE
205
- try:
206
- result: List[Dict] = []
207
- async with httpx.AsyncClient(timeout=15.0) as client:
208
- # ── Anthropic 공식 skills (Apache-2.0) ──────────────────────────
209
- mp_resp = await client.get(f"{_MARKETPLACE_RAW}/.claude-plugin/marketplace.json")
210
- mp_resp.raise_for_status()
211
- marketplace_json = mp_resp.json()
212
- anthropic_plugins = [
213
- p for p in marketplace_json.get("plugins", [])
214
- if (p.get("author") or {}).get("name") == "Anthropic"
215
- and isinstance(p.get("source"), str)
216
- and p["source"].startswith("./")
217
- ]
218
- for plugin in anthropic_plugins:
219
- plugin_path = plugin["source"].lstrip("./")
220
- result.extend(await _fetch_plugin_skills(client, {
221
- "plugin": plugin["name"],
222
- "author": "Anthropic",
223
- "license": "Apache-2.0",
224
- "repo": "anthropics/claude-plugins-official",
225
- "branch": "main",
226
- "plugin_path": plugin_path,
227
- "category": plugin.get("category", "development"),
228
- "description": plugin.get("description", ""),
229
- }))
230
- # ── 검증된 서드파티 skills ────────────────────────────────────────
231
- for source in _THIRD_PARTY_SKILL_SOURCES:
232
- result.extend(await _fetch_plugin_skills(client, source))
233
-
234
- _SKILLS_MARKETPLACE_CACHE = result
235
- _SKILLS_MARKETPLACE_FETCHED_AT = now
236
- logging.info("Fetched %d skills from marketplace (%d sources)",
237
- len(result), len(anthropic_plugins) + len(_THIRD_PARTY_SKILL_SOURCES))
238
- except Exception as e:
239
- logging.warning("Failed to fetch skills marketplace: %s", e)
240
- return _SKILLS_MARKETPLACE_CACHE
241
-
242
- # ── Plugin Directory ──────────────────────────────────────────────────────────
243
- _PLUGIN_DIRECTORY_CACHE: List[Dict] = []
244
- _PLUGIN_DIRECTORY_FETCHED_AT: Optional[datetime] = None
245
- _PLUGIN_DIRECTORY_TTL = timedelta(hours=1)
246
- _OPEN_LICENSES = {"Apache-2.0", "MIT", "MIT-0", "CC-BY-4.0"}
247
- _REPO_LICENSE_CACHE: Dict[str, str] = {}
248
-
249
- async def _get_repo_license(client: httpx.AsyncClient, repo: str) -> str:
250
- if repo in _REPO_LICENSE_CACHE:
251
- return _REPO_LICENSE_CACHE[repo]
252
- if repo in _KNOWN_REPO_LICENSES:
253
- _REPO_LICENSE_CACHE[repo] = _KNOWN_REPO_LICENSES[repo]
254
- return _KNOWN_REPO_LICENSES[repo]
255
- try:
256
- r = await client.get(f"https://api.github.com/repos/{repo}", timeout=5.0)
257
- lic = (r.json().get("license") or {}).get("spdx_id", "") if r.status_code == 200 else ""
258
- except Exception:
259
- lic = ""
260
- _REPO_LICENSE_CACHE[repo] = lic
261
- return lic
262
-
263
- async def _fetch_plugin_directory() -> List[Dict]:
264
- global _PLUGIN_DIRECTORY_CACHE, _PLUGIN_DIRECTORY_FETCHED_AT
265
- now = datetime.now()
266
- if _PLUGIN_DIRECTORY_FETCHED_AT and (now - _PLUGIN_DIRECTORY_FETCHED_AT) < _PLUGIN_DIRECTORY_TTL:
267
- return _PLUGIN_DIRECTORY_CACHE
268
- try:
269
- result: List[Dict] = []
270
- async with httpx.AsyncClient(timeout=15.0) as client:
271
- mp_resp = await client.get(f"{_MARKETPLACE_RAW}/.claude-plugin/marketplace.json")
272
- mp_resp.raise_for_status()
273
- plugins = mp_resp.json().get("plugins", [])
274
-
275
- for p in plugins:
276
- author = (p.get("author") or {}).get("name", "")
277
- src = p.get("source", {})
278
-
279
- # Anthropic 같은 레포 플러그인 → Apache-2.0 확인됨
280
- if isinstance(src, str) and src.startswith("./") and author == "Anthropic":
281
- plugin_path = src.lstrip("./")
282
- result.append({
283
- "name": p["name"],
284
- "description": p.get("description", ""),
285
- "category": p.get("category", ""),
286
- "author": author,
287
- "license": "Apache-2.0",
288
- "homepage": p.get("homepage") or f"https://github.com/anthropics/claude-plugins-official/tree/main/{plugin_path}",
289
- "source_type": "anthropic",
290
- })
291
- continue
292
-
293
- # 외부 레포 플러그인 → 라이선스 확인
294
- if not isinstance(src, dict):
295
- continue
296
- repo_url = src.get("url", "").replace("https://github.com/", "").replace(".git", "").split("/tree/")[0]
297
- if not repo_url:
298
- continue
299
- license_id = await _get_repo_license(client, repo_url)
300
- if license_id not in _OPEN_LICENSES:
301
- continue
302
- result.append({
303
- "name": p["name"],
304
- "description": p.get("description", ""),
305
- "category": p.get("category", ""),
306
- "author": author or repo_url.split("/")[0],
307
- "license": license_id,
308
- "homepage": p.get("homepage") or f"https://github.com/{repo_url}",
309
- "source_type": "third-party",
310
- })
311
-
312
- _PLUGIN_DIRECTORY_CACHE = result
313
- _PLUGIN_DIRECTORY_FETCHED_AT = now
314
- logging.info("Fetched plugin directory: %d open-source plugins", len(result))
315
- except Exception as e:
316
- logging.warning("Failed to fetch plugin directory: %s", e)
317
- return _PLUGIN_DIRECTORY_CACHE
318
-
319
- # ─────────────────────────────────────────────────────────────────────────────
320
-
321
- SKILLS_DIR = Path(__file__).resolve().parent / "skills"
322
-
323
- async def install_skill(plugin: str, skill: str) -> Dict:
324
- marketplace = await _fetch_skills_marketplace()
325
- entry = next((s for s in marketplace if s["plugin"] == plugin and s["skill"] == skill), None)
326
- if not entry:
327
- raise HTTPException(status_code=404, detail=f"Skill '{plugin}/{skill}' not found in marketplace")
328
- skill_dir = SKILLS_DIR / skill
329
- skill_dir.mkdir(parents=True, exist_ok=True)
330
- skill_md_path = skill_dir / "SKILL.md"
331
- async with httpx.AsyncClient(timeout=15.0) as client:
332
- resp = await client.get(entry["skill_md_url"])
333
- resp.raise_for_status()
334
- content = resp.text
335
- # 출처 표기 (Apache-2.0 / MIT 공통)
336
- repo_hint = entry.get("homepage", "")
337
- attribution = f"<!-- Source: {repo_hint}, {entry['license']} -->\n"
338
- if not content.startswith("<!--"):
339
- content = attribution + content
340
- skill_md_path.write_text(content, encoding="utf-8")
341
- risk_path = skill_dir / "risk.json"
342
- if not risk_path.exists():
343
- risk_path.write_text(json.dumps({
344
- "risk": "read", "destructive": False,
345
- "shell": False, "network": False,
346
- "auto_approve": True, "sandbox": "workspace", "rollback": "none"
347
- }, indent=2), encoding="utf-8")
348
- return {
349
- "status": "installed",
350
- "plugin": plugin,
351
- "skill": skill,
352
- "path": str(skill_dir),
353
- "license": entry["license"],
354
- "author": entry["author"],
355
- }
356
-
357
-
358
- def _run_installer(command: List[str], timeout: int) -> "subprocess.CompletedProcess[str]":
359
- """Run one package-installer command. Blocking — call off the event loop."""
360
- return subprocess.run(
361
- command, capture_output=True, text=True, timeout=timeout, check=False
362
- )
363
-
364
-
365
- def create_mcp_install_state(data_dir: Path) -> Dict[str, Callable]:
366
- """Return bound MCP state helpers for given data_dir. No global side effects."""
367
- MCP_FILE = Path(data_dir) / "mcp_installs.json"
368
-
369
- def load_mcp_installs() -> Dict:
370
- if not os.path.exists(MCP_FILE):
371
- return {"installed": {}, "updated_at": None}
372
- try:
373
- with open(MCP_FILE, "r", encoding="utf-8") as f:
374
- data = json.load(f)
375
- if "installed" not in data:
376
- data["installed"] = {}
377
- return data
378
- except Exception as e:
379
- logging.warning("load_mcp_installs failed: %s", e)
380
- return {"installed": {}, "updated_at": None}
381
-
382
- def save_mcp_installs(data: Dict) -> None:
383
- data["updated_at"] = datetime.now().isoformat()
384
- with open(MCP_FILE, "w", encoding="utf-8") as f:
385
- json.dump(data, f, ensure_ascii=False, indent=2)
386
-
387
- def mcp_public_item(item: Dict, installed_state: Dict) -> Dict:
388
- state = installed_state.get(item.get("id"), {}) or {}
389
- installed = item.get("install_mode") in {"builtin", "bundled"} or bool(state.get("installed"))
390
- connector_pending = item.get("install_mode") == "connector" and not state.get("authenticated")
391
- authenticated = item.get("install_mode") != "connector" or bool(state.get("authenticated"))
392
- return {
393
- "id": item.get("id"),
394
- "name": item.get("name"),
395
- "category": item.get("category", ""),
396
- "install_mode": item.get("install_mode"),
397
- "description": item.get("description", ""),
398
- "capabilities": item.get("capabilities", []),
399
- "connector_url": item.get("connector_url"),
400
- "external_url": item.get("external_url"),
401
- "package": item.get("package"),
402
- "homepage": item.get("homepage"),
403
- "source": item.get("source", "local"),
404
- "installed": installed,
405
- "status": state.get("status") or ("active" if installed and not connector_pending else "needs_auth" if connector_pending else "available"),
406
- "authenticated": authenticated,
407
- "updated_at": state.get("updated_at"),
408
- }
409
-
410
- async def recommend_mcps(query: str, limit: int = 5) -> List[Dict]:
411
- text = (query or "").lower()
412
- installed = load_mcp_installs().get("installed", {})
413
- registry = await _get_combined_registry()
414
- scored: List[Dict] = []
415
- for item in registry:
416
- score = 0
417
- hits: List[str] = []
418
- for keyword in item.get("keywords", []):
419
- if keyword.lower() in text:
420
- score += 3 if len(keyword) > 2 else 1
421
- hits.append(keyword)
422
- if not hits and text:
423
- desc_words = item.get("description", "").lower().split()
424
- for word in text.split():
425
- if len(word) > 2 and word in desc_words:
426
- score += 1
427
- hits.append(word)
428
- if item.get("id") == "filesystem" and any(w in text for w in ["만들", "구현", "build", "deploy", "코드", "앱"]):
429
- score += 2
430
- if score:
431
- public = mcp_public_item(item, installed)
432
- public["score"] = score
433
- public["matched_keywords"] = hits[:6]
434
- scored.append(public)
435
- if not scored:
436
- fallback_ids = ["filesystem", "browser", "documents"]
437
- scored = [
438
- {**mcp_public_item(item, installed), "score": 1, "matched_keywords": []}
439
- for item in registry if item.get("id") in fallback_ids
440
- ]
441
- return sorted(scored, key=lambda x: x["score"], reverse=True)[:max(1, min(limit, 24))]
442
-
443
- async def install_mcp(mcp_id: str) -> Dict:
444
- registry = await _get_combined_registry()
445
- item = next((entry for entry in registry if entry.get("id") == mcp_id), None)
446
- if not item:
447
- raise HTTPException(status_code=404, detail="MCP를 찾을 수 없습니다.")
448
- data = load_mcp_installs()
449
- state = data.setdefault("installed", {})
450
- status = "active"
451
- message = "MCP가 활성화되었습니다."
452
- if item.get("install_mode") == "connector":
453
- status = "needs_auth"
454
- message = "커넥터 인증이 필요합니다. Codex 앱의 connector 설정에서 계정을 연결하면 바로 사용할 수 있습니다."
455
- elif item.get("install_mode") == "pip":
456
- # Package installers are minutes of network I/O. On the event loop
457
- # they froze every other request for the whole install; each one now
458
- # runs on a worker thread instead.
459
- packages = item.get("pip_packages") or []
460
- for pkg in packages:
461
- completed = await asyncio.to_thread(
462
- _run_installer,
463
- [sys.executable, "-m", "pip", "install", "--upgrade", pkg],
464
- 900,
465
- )
466
- if completed.returncode != 0:
467
- raise HTTPException(status_code=500, detail=(completed.stderr or "")[-2000:] or f"{pkg} 설치 실패")
468
- message = f"필수 패키지 설치 완료: {', '.join(packages)}"
469
- elif item.get("install_mode") == "pypi":
470
- pkg = item.get("package", "")
471
- version = item.get("package_version")
472
- pkg_str = f"{pkg}=={version}" if version else pkg
473
- completed = await asyncio.to_thread(
474
- _run_installer, [sys.executable, "-m", "pip", "install", pkg_str], 300
475
- )
476
- if completed.returncode != 0:
477
- raise HTTPException(status_code=500, detail=(completed.stderr or "")[-2000:] or f"{pkg} 설치 실패")
478
- message = f"pip 패키지 설치 완료: {pkg_str}"
479
- elif item.get("install_mode") == "npm":
480
- pkg = item.get("package", "")
481
- version = item.get("package_version")
482
- pkg_str = f"{pkg}@{version}" if version else pkg
483
- completed = await asyncio.to_thread(
484
- _run_installer, ["npm", "install", "-g", pkg_str], 300
485
- )
486
- if completed.returncode != 0:
487
- raise HTTPException(status_code=500, detail=(completed.stderr or "")[-2000:] or f"{pkg} 설치 실패")
488
- message = f"npm 패키지 설치 완료: {pkg_str}"
489
- state[mcp_id] = {
490
- "installed": True,
491
- "status": status,
492
- "authenticated": item.get("install_mode") != "connector",
493
- "updated_at": datetime.now().isoformat(),
494
- }
495
- save_mcp_installs(data)
496
- public = mcp_public_item(item, state)
497
- public["message"] = message
498
- return public
499
-
500
- return {
501
- "load_mcp_installs": load_mcp_installs,
502
- "save_mcp_installs": save_mcp_installs,
503
- "mcp_public_item": mcp_public_item,
504
- "recommend_mcps": recommend_mcps,
505
- "install_mcp": install_mcp,
506
- }
@@ -1,168 +0,0 @@
1
- """Network boundary modes for hybrid local + cloud LLM usage.
2
-
3
- Local-first principle: the Knowledge Graph and all durable memory stay on the
4
- machine. Cloud LLMs are an explicit, opt-in worker. This module defines the
5
- dial that controls whether any knowledge is allowed to leave the host.
6
-
7
- Modes
8
- -----
9
- * ``local_only`` — default. No chat context is ever sent to a cloud provider.
10
- * ``cloud_allowed`` — user has explicitly opted in. Only the *minimal* related
11
- nodes selected by the hybrid context extractor may be sent. Sensitive types
12
- and hard filters still apply (mode-invariant).
13
-
14
- The mode is orthogonal to PermissionMode (agent autonomy). A session can be
15
- ``cloud_allowed`` + ``strict`` at the same time.
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- from enum import Enum
21
- from typing import Any, Dict, FrozenSet, Mapping, Optional
22
-
23
-
24
- class NetworkBoundaryMode(str, Enum):
25
- LOCAL_ONLY = "local_only"
26
- CLOUD_ALLOWED = "cloud_allowed"
27
-
28
-
29
- DEFAULT_NETWORK_MODE = NetworkBoundaryMode.LOCAL_ONLY
30
-
31
- # Node types that must never be included in a cloud payload, regardless of mode.
32
- # Extend carefully; this is a hard circuit breaker.
33
- #
34
- # This was empty through 10.1.x ("keep empty for v1 scaffolding"), which meant
35
- # the type arm of the filter could never fire. Combined with nothing being able
36
- # to *set* the metadata flags below, the whole guard was unreachable — correct
37
- # code protecting nothing.
38
- HARD_BLOCK_NODE_TYPES: FrozenSet[str] = frozenset({
39
- "Credential",
40
- "Secret",
41
- "ApiKey",
42
- "Token",
43
- "Password",
44
- "PrivateKey",
45
- })
46
-
47
- # Metadata keys that, when present and truthy, block a node from leaving.
48
- HARD_BLOCK_METADATA_FLAGS: FrozenSet[str] = frozenset({
49
- "sensitive",
50
- "private",
51
- "do_not_share",
52
- "local_only",
53
- })
54
-
55
- # Path-based never-leaves rules live in Brain Core: they describe the data, not
56
- # the transport, and Brain Core cannot import from this package. Re-exported
57
- # here so the boundary module stays the single place the app layer reads.
58
- from lattice_brain.sensitivity import ( # noqa: E402
59
- SENSITIVE_FILENAMES,
60
- SENSITIVE_PATH_FRAGMENTS,
61
- sensitive_reason_for_path,
62
- )
63
-
64
-
65
- def normalize_network_mode(value: Any) -> NetworkBoundaryMode:
66
- """Parse user/API/env input into a NetworkBoundaryMode; unknown → local_only."""
67
- if isinstance(value, NetworkBoundaryMode):
68
- return value
69
- text = str(value or "").strip().lower()
70
- aliases = {
71
- "local_only": NetworkBoundaryMode.LOCAL_ONLY,
72
- "local": NetworkBoundaryMode.LOCAL_ONLY,
73
- "local-only": NetworkBoundaryMode.LOCAL_ONLY,
74
- "offline": NetworkBoundaryMode.LOCAL_ONLY,
75
- "cloud_allowed": NetworkBoundaryMode.CLOUD_ALLOWED,
76
- "cloud": NetworkBoundaryMode.CLOUD_ALLOWED,
77
- "cloud-allowed": NetworkBoundaryMode.CLOUD_ALLOWED,
78
- "hybrid": NetworkBoundaryMode.CLOUD_ALLOWED,
79
- "online": NetworkBoundaryMode.CLOUD_ALLOWED,
80
- }
81
- return aliases.get(text, DEFAULT_NETWORK_MODE)
82
-
83
-
84
- def network_mode_catalog() -> list[Dict[str, Any]]:
85
- """UI/API catalog for the network boundary selector."""
86
- return [
87
- {
88
- "id": NetworkBoundaryMode.LOCAL_ONLY.value,
89
- "label": "Local only",
90
- "label_ko": "로컬만",
91
- "summary": "Nothing leaves this machine. Answers use local models and the local Brain only.",
92
- "summary_ko": "이 컴퓨터를 벗어나지 않습니다. 로컬 모델과 로컬 Brain만 사용합니다.",
93
- "risk": "low",
94
- "requires_ack": False,
95
- },
96
- {
97
- "id": NetworkBoundaryMode.CLOUD_ALLOWED.value,
98
- "label": "Cloud streaming allowed",
99
- "label_ko": "클라우드 스트리밍 허용",
100
- "summary": (
101
- "Minimal related Knowledge Graph nodes may be sent to a cloud LLM. "
102
- "The streamed answer is written back into the local Brain with provenance."
103
- ),
104
- "summary_ko": (
105
- "관련된 최소 Knowledge Graph 노드만 클라우드 LLM으로 전송될 수 있습니다. "
106
- "스트리밍 답변은 provenance와 함께 로컬 Brain에 다시 기록됩니다."
107
- ),
108
- "risk": "medium",
109
- "requires_ack": True,
110
- "warning": (
111
- "Cloud mode sends a compact summary of selected local nodes to an external provider. "
112
- "Sensitive nodes remain blocked. You can switch back to Local only at any time."
113
- ),
114
- "warning_ko": (
115
- "클라우드 모드는 선택된 로컬 노드의 압축 요약을 외부 제공자에게 전송합니다. "
116
- "민감 노드는 계속 차단됩니다. 언제든지 로컬만으로 되돌릴 수 있습니다."
117
- ),
118
- },
119
- ]
120
-
121
-
122
- def is_node_blocked_for_cloud(node: Mapping[str, Any]) -> Optional[str]:
123
- """Return a reason string when this node must never leave the host."""
124
- node_type = str(node.get("type") or "")
125
- if node_type in HARD_BLOCK_NODE_TYPES:
126
- return f"node type {node_type!r} is blocked from cloud payloads"
127
-
128
- meta = node.get("metadata") or {}
129
- if not isinstance(meta, Mapping):
130
- return None
131
- for flag in HARD_BLOCK_METADATA_FLAGS:
132
- if meta.get(flag):
133
- return f"node flagged {flag!r} is blocked from cloud payloads"
134
- return None
135
-
136
-
137
- def network_mode_contract(mode: NetworkBoundaryMode | str) -> Dict[str, Any]:
138
- """Serializable contract for API / agent / UI."""
139
- mode = normalize_network_mode(mode)
140
- entry = next(
141
- (m for m in network_mode_catalog() if m["id"] == mode.value),
142
- network_mode_catalog()[0],
143
- )
144
- return {
145
- "mode": mode.value,
146
- "label": entry["label"],
147
- "label_ko": entry["label_ko"],
148
- "risk": entry["risk"],
149
- "requires_ack": entry["requires_ack"],
150
- "allows_cloud": mode == NetworkBoundaryMode.CLOUD_ALLOWED,
151
- "hard_block_node_types": sorted(HARD_BLOCK_NODE_TYPES),
152
- "hard_block_metadata_flags": sorted(HARD_BLOCK_METADATA_FLAGS),
153
- }
154
-
155
-
156
- __all__ = [
157
- "NetworkBoundaryMode",
158
- "DEFAULT_NETWORK_MODE",
159
- "HARD_BLOCK_NODE_TYPES",
160
- "HARD_BLOCK_METADATA_FLAGS",
161
- "normalize_network_mode",
162
- "network_mode_catalog",
163
- "is_node_blocked_for_cloud",
164
- "sensitive_reason_for_path",
165
- "SENSITIVE_PATH_FRAGMENTS",
166
- "SENSITIVE_FILENAMES",
167
- "network_mode_contract",
168
- ]