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,391 +0,0 @@
1
- """Knowledge Graph portability routes — local export / import / backup / restore.
2
-
3
- Status reads require a signed-in user. Whole-graph exports, provenance, and all
4
- mutating operations require admin because the graph is machine-global, not
5
- workspace-scoped. Nothing here touches a cloud service.
6
-
7
- The ``/share`` family (v11.1.0) adds the selective Brain Network prototype:
8
- export a *chosen* subgraph signed by this device, and receive one as review
9
- proposals rather than a merge. It is gated on ``LATTICEAI_BRAIN_NETWORK`` and
10
- answers 403 with the reason while that flag is off — except ``GET
11
- /api/knowledge-graph/share``, which reports ``enabled: false`` so a UI can say
12
- why the feature is absent instead of guessing.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- from typing import Any, Callable, Dict, List, Optional
18
-
19
- from fastapi import APIRouter, HTTPException, Request
20
- from pydantic import BaseModel
21
-
22
- from latticeai.core.messages import http_error, resolve_language, translate
23
- from latticeai.services.review_queue import InvalidReviewTransition
24
-
25
-
26
- class ImportRequest(BaseModel):
27
- artifact: dict
28
- mode: str = "merge"
29
- dry_run: bool = False
30
-
31
-
32
- class BackupRequest(BaseModel):
33
- path: Optional[str] = None
34
-
35
-
36
- class RestoreRequest(BaseModel):
37
- path: str
38
- verify: bool = True
39
- dry_run: bool = False
40
- confirm: bool = False
41
-
42
-
43
- class EncryptedArchiveRequest(BaseModel):
44
- path: Optional[str] = None
45
- passphrase: str
46
-
47
-
48
- class EncryptedRestoreRequest(BaseModel):
49
- path: str
50
- passphrase: str
51
- dry_run: bool = False
52
- confirm: bool = False
53
-
54
-
55
- class EncryptedInspectRequest(BaseModel):
56
- path: str
57
- passphrase: Optional[str] = None
58
-
59
-
60
- class EncryptedVerifyRequest(BaseModel):
61
- path: str
62
- passphrase: str
63
-
64
-
65
- class DockerPostgresRequest(BaseModel):
66
- consent: bool = False
67
- dry_run: bool = False
68
- port: int = 5432
69
-
70
-
71
- class SQLiteToPostgresRequest(BaseModel):
72
- dsn: str
73
- schema_name: str = "lattice_brain"
74
- dry_run: bool = True
75
-
76
-
77
- class SubgraphSelection(BaseModel):
78
- """What to share. At least one selector is required by the service."""
79
-
80
- node_ids: List[str] = []
81
- node_types: List[str] = []
82
- source_types: List[str] = []
83
- workspace_id: Optional[str] = None
84
- include_legacy_global: bool = False
85
- include_neighbors: bool = False
86
- redact_provenance: bool = True
87
-
88
-
89
- class SubgraphArchiveRequest(SubgraphSelection):
90
- """Exactly one recipient mechanism — a passphrase, or a public key.
91
-
92
- Both optional at the schema level and validated in the service, so the
93
- error a caller gets names the rule ("choose one") instead of a field.
94
- """
95
-
96
- path: Optional[str] = None
97
- passphrase: Optional[str] = None
98
- #: The recipient's X25519 public key, from their ``/share/recipient-key``.
99
- recipient_public_key: Optional[str] = None
100
-
101
-
102
- class SubgraphImportRequest(BaseModel):
103
- """Either an inline ``artifact`` or a ``path`` (+ passphrase) on disk."""
104
-
105
- artifact: Optional[Dict[str, Any]] = None
106
- path: Optional[str] = None
107
- passphrase: Optional[str] = None
108
- workspace_id: Optional[str] = None
109
- dry_run: bool = False
110
-
111
-
112
- class SubgraphAcceptRequest(BaseModel):
113
- workspace_id: Optional[str] = None
114
-
115
-
116
- def create_portability_router(
117
- *,
118
- service: Any,
119
- require_user: Callable[[Request], str],
120
- require_admin: Callable[[Request], Any],
121
- review_queue: Any = None,
122
- ) -> APIRouter:
123
- router = APIRouter()
124
-
125
- def _require_service(request: Request):
126
- if service is None or not service.available():
127
- raise http_error(503, "common.graph_disabled", resolve_language(request))
128
-
129
- def _require_review_queue(request: Request):
130
- if review_queue is None:
131
- raise http_error(503, "portability.review_queue_unavailable", resolve_language(request))
132
-
133
- @router.get("/api/knowledge-graph/portability")
134
- async def portability_status(request: Request):
135
- require_user(request)
136
- _require_service(request)
137
- return service.snapshot_metadata()
138
-
139
- @router.get("/api/brain/storage")
140
- async def brain_storage_status(request: Request):
141
- require_user(request)
142
- _require_service(request)
143
- return service.storage_status()
144
-
145
- @router.get("/api/knowledge-graph/backup-health")
146
- async def backup_health(request: Request):
147
- require_user(request)
148
- _require_service(request)
149
- return service.backup_health()
150
-
151
- @router.get("/api/knowledge-graph/provenance")
152
- async def recent_provenance(request: Request, limit: int = 50, source_type: Optional[str] = None):
153
- """Recent ingestions (provenance trail) for the ingestion-sources UI."""
154
- require_admin(request)
155
- _require_service(request)
156
- return service.recent_ingestions(limit=limit, source_type=source_type)
157
-
158
- @router.post("/api/knowledge-graph/export")
159
- async def export_graph(request: Request):
160
- """Logical JSON export of the whole graph (read-only)."""
161
- require_admin(request)
162
- _require_service(request)
163
- return service.export()
164
-
165
- @router.post("/api/knowledge-graph/export-file")
166
- async def export_graph_file(request: Request):
167
- require_admin(request)
168
- _require_service(request)
169
- return service.export_to_file()
170
-
171
- @router.post("/api/knowledge-graph/import")
172
- async def import_graph(req: ImportRequest, request: Request):
173
- require_admin(request)
174
- _require_service(request)
175
- try:
176
- return service.import_data(req.artifact, mode=req.mode, dry_run=req.dry_run)
177
- except ValueError as exc:
178
- raise HTTPException(status_code=400, detail=str(exc))
179
-
180
- @router.post("/api/knowledge-graph/backup")
181
- async def backup_graph(req: BackupRequest, request: Request):
182
- require_admin(request)
183
- _require_service(request)
184
- return service.backup(req.path)
185
-
186
- @router.post("/api/knowledge-graph/restore")
187
- async def restore_graph(req: RestoreRequest, request: Request):
188
- require_admin(request)
189
- _require_service(request)
190
- try:
191
- return service.restore(req.path, verify=req.verify, dry_run=req.dry_run, confirm=req.confirm)
192
- except (ValueError, FileNotFoundError) as exc:
193
- raise HTTPException(status_code=400, detail=str(exc))
194
-
195
- @router.post("/api/knowledge-graph/archive")
196
- async def encrypted_archive(req: EncryptedArchiveRequest, request: Request):
197
- require_admin(request)
198
- _require_service(request)
199
- try:
200
- return service.encrypted_archive(req.path, passphrase=req.passphrase)
201
- except (ValueError, FileNotFoundError) as exc:
202
- raise HTTPException(status_code=400, detail=str(exc))
203
-
204
- @router.post("/api/knowledge-graph/archive/inspect")
205
- async def inspect_encrypted_archive(req: EncryptedInspectRequest, request: Request):
206
- require_admin(request)
207
- _require_service(request)
208
- try:
209
- return service.inspect_encrypted_archive(req.path, passphrase=req.passphrase)
210
- except (ValueError, FileNotFoundError) as exc:
211
- raise HTTPException(status_code=400, detail=str(exc))
212
-
213
- @router.post("/api/knowledge-graph/archive/verify")
214
- async def verify_encrypted_archive(req: EncryptedVerifyRequest, request: Request):
215
- require_admin(request)
216
- _require_service(request)
217
- result = service.verify_encrypted_archive(req.path, passphrase=req.passphrase)
218
- if not result.get("ok"):
219
- raise HTTPException(
220
- status_code=400,
221
- detail="; ".join(
222
- result.get("errors")
223
- or [translate("portability.verification_failed", resolve_language(request))]
224
- ),
225
- )
226
- return result
227
-
228
- @router.post("/api/knowledge-graph/archive/import")
229
- async def import_encrypted_archive(req: EncryptedRestoreRequest, request: Request):
230
- require_admin(request)
231
- _require_service(request)
232
- try:
233
- return service.import_encrypted_archive(
234
- req.path,
235
- passphrase=req.passphrase,
236
- dry_run=req.dry_run,
237
- confirm=req.confirm,
238
- )
239
- except (ValueError, FileNotFoundError) as exc:
240
- raise HTTPException(status_code=400, detail=str(exc))
241
-
242
- @router.post("/api/knowledge-graph/archive/restore")
243
- async def restore_encrypted_archive(req: EncryptedRestoreRequest, request: Request):
244
- require_admin(request)
245
- _require_service(request)
246
- try:
247
- return service.restore_encrypted_archive(
248
- req.path,
249
- passphrase=req.passphrase,
250
- dry_run=req.dry_run,
251
- confirm=req.confirm,
252
- )
253
- except (ValueError, FileNotFoundError) as exc:
254
- raise HTTPException(status_code=400, detail=str(exc))
255
-
256
- # ── selective subgraph share (opt-in; 403 with the reason while off) ─────
257
- @router.get("/api/knowledge-graph/share")
258
- async def share_status(request: Request):
259
- """Reports whether sharing is on, and names the flag when it is not."""
260
- require_user(request)
261
- status = service.share_status()
262
- if not status.get("enabled"):
263
- status["detail"] = translate(
264
- "portability.brain_network_disabled", resolve_language(request)
265
- )
266
- return status
267
-
268
- @router.post("/api/knowledge-graph/share/export")
269
- async def share_export(req: SubgraphSelection, request: Request):
270
- """Signed JSON bundle of the selected subgraph (no file written)."""
271
- require_admin(request)
272
- _require_service(request)
273
- try:
274
- return service.export_subgraph(**req.model_dump())
275
- except PermissionError:
276
- raise http_error(403, "portability.brain_network_disabled", resolve_language(request))
277
- except (ValueError, RuntimeError) as exc:
278
- raise HTTPException(status_code=400, detail=str(exc))
279
-
280
- @router.get("/api/knowledge-graph/share/recipient-key")
281
- async def share_recipient_key(request: Request):
282
- """This Brain's X25519 receiving key, for a sender to seal a bundle to.
283
-
284
- Public by construction — a public key encrypts, it does not decrypt —
285
- so handing it out is the whole point. Still admin-gated and still
286
- behind the share flag, because minting the key is a decision to
287
- participate in sharing at all.
288
- """
289
- require_admin(request)
290
- _require_service(request)
291
- try:
292
- return service.recipient_key()
293
- except PermissionError:
294
- raise http_error(403, "portability.brain_network_disabled", resolve_language(request))
295
-
296
- @router.post("/api/knowledge-graph/share/archive")
297
- async def share_archive(req: SubgraphArchiveRequest, request: Request):
298
- """Same bundle, written as an encrypted ``.latticebrain`` file."""
299
- require_admin(request)
300
- _require_service(request)
301
- body = req.model_dump()
302
- path = body.pop("path")
303
- passphrase = body.pop("passphrase")
304
- recipient_public_key = body.pop("recipient_public_key")
305
- try:
306
- return service.export_subgraph_archive(
307
- path,
308
- passphrase=passphrase,
309
- recipient_public_key=recipient_public_key,
310
- **body,
311
- )
312
- except PermissionError:
313
- raise http_error(403, "portability.brain_network_disabled", resolve_language(request))
314
- except (ValueError, RuntimeError) as exc:
315
- raise HTTPException(status_code=400, detail=str(exc))
316
-
317
- @router.post("/api/knowledge-graph/share/import")
318
- async def share_import(req: SubgraphImportRequest, request: Request):
319
- """Receive a bundle as review proposals. Never merges on its own."""
320
- require_admin(request)
321
- _require_service(request)
322
- user = require_user(request)
323
- _require_review_queue(request)
324
- try:
325
- artifact = req.artifact
326
- if artifact is None:
327
- if not req.path:
328
- raise ValueError(
329
- "Provide an artifact, or the path of a bundle on disk."
330
- )
331
- # A sealed bundle needs no passphrase — the envelope says which
332
- # mechanism it used and the service refuses if the wrong one is
333
- # supplied, so the route does not have to guess here.
334
- artifact = service.read_subgraph_archive(req.path, passphrase=req.passphrase)
335
- return service.import_subgraph_proposals(
336
- artifact,
337
- review_sink=review_queue,
338
- workspace_id=req.workspace_id,
339
- user_email=user or None,
340
- dry_run=req.dry_run,
341
- )
342
- except PermissionError:
343
- raise http_error(403, "portability.brain_network_disabled", resolve_language(request))
344
- except FileNotFoundError as exc:
345
- raise HTTPException(status_code=404, detail=str(exc))
346
- except ValueError as exc:
347
- raise HTTPException(status_code=400, detail=str(exc))
348
-
349
- @router.post("/api/knowledge-graph/share/proposals/{item_id}/accept")
350
- async def share_accept(item_id: str, req: SubgraphAcceptRequest, request: Request):
351
- """Merge one reviewed proposal into this Brain and approve the item."""
352
- require_admin(request)
353
- _require_service(request)
354
- _require_review_queue(request)
355
- try:
356
- return service.accept_subgraph_proposal(
357
- item_id, review_sink=review_queue, workspace_id=req.workspace_id,
358
- )
359
- except PermissionError:
360
- raise http_error(403, "portability.brain_network_disabled", resolve_language(request))
361
- except FileNotFoundError:
362
- raise http_error(404, "review.item_not_found", resolve_language(request))
363
- except InvalidReviewTransition as exc:
364
- raise HTTPException(status_code=409, detail=str(exc))
365
- except ValueError as exc:
366
- raise HTTPException(status_code=400, detail=str(exc))
367
-
368
- @router.post("/api/brain/storage/postgres/docker")
369
- async def setup_postgres_docker(req: DockerPostgresRequest, request: Request):
370
- require_admin(request)
371
- _require_service(request)
372
- return service.postgres_docker_setup(
373
- consent=req.consent,
374
- dry_run=req.dry_run,
375
- port=req.port,
376
- )
377
-
378
- @router.post("/api/brain/storage/migrate-postgres")
379
- async def migrate_sqlite_to_postgres(req: SQLiteToPostgresRequest, request: Request):
380
- require_admin(request)
381
- _require_service(request)
382
- try:
383
- return service.migrate_sqlite_to_postgres(
384
- dsn=req.dsn,
385
- schema=req.schema_name,
386
- dry_run=req.dry_run,
387
- )
388
- except (ValueError, FileNotFoundError, RuntimeError) as exc:
389
- raise HTTPException(status_code=400, detail=str(exc))
390
-
391
- return router
@@ -1,114 +0,0 @@
1
- """Project session router — the multi-turn project loop (v9.9.6).
2
-
3
- CRUD over :class:`~latticeai.core.project_sessions.ProjectSessionStore`. A
4
- project session carries what a single agent run cannot: the files the project
5
- already produced, what is still open, and the last honest verification result.
6
- Runs fold themselves in through the agent HTTP layer; this surface is what the
7
- user drives.
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- from typing import Any, Callable, List, Optional
13
-
14
- from fastapi import APIRouter, Request
15
- from pydantic import BaseModel, Field
16
-
17
- from latticeai.core.messages import http_error, resolve_language
18
-
19
-
20
- class CreateProjectRequest(BaseModel):
21
- title: str = Field(..., min_length=1, max_length=200)
22
- goal: str = ""
23
-
24
-
25
- class UpdateProjectRequest(BaseModel):
26
- title: Optional[str] = None
27
- goal: Optional[str] = None
28
- status: Optional[str] = None
29
-
30
-
31
- class TodosRequest(BaseModel):
32
- todos: List[Any] = Field(default_factory=list)
33
-
34
-
35
- def create_project_sessions_router(
36
- *,
37
- store: Any,
38
- require_user: Callable[[Request], Any],
39
- gate_read: Optional[Callable[[Request], Optional[str]]] = None,
40
- gate_write: Optional[Callable[[Request], Optional[str]]] = None,
41
- ) -> APIRouter:
42
- router = APIRouter()
43
-
44
- def _scope(request: Request, write: bool = False):
45
- gate = gate_write if write else gate_read
46
- return gate(request) if gate is not None else None
47
-
48
- @router.get("/api/projects")
49
- async def list_projects(request: Request, status: str = "active"):
50
- user = require_user(request)
51
- return store.list(
52
- user_email=user, workspace_id=_scope(request), status=status
53
- )
54
-
55
- @router.post("/api/projects")
56
- async def create_project(req: CreateProjectRequest, request: Request):
57
- user = require_user(request)
58
- return store.create(
59
- title=req.title,
60
- goal=req.goal,
61
- user_email=user,
62
- workspace_id=_scope(request, write=True),
63
- )
64
-
65
- @router.get("/api/projects/{session_id}")
66
- async def get_project(session_id: str, request: Request):
67
- user = require_user(request)
68
- record = store.get(session_id, user_email=user, workspace_id=_scope(request))
69
- if record is None:
70
- raise http_error(404, "project.not_found", resolve_language(request))
71
- return record
72
-
73
- @router.patch("/api/projects/{session_id}")
74
- async def update_project(session_id: str, req: UpdateProjectRequest, request: Request):
75
- user = require_user(request)
76
- record = store.update(
77
- session_id,
78
- title=req.title,
79
- goal=req.goal,
80
- status=req.status,
81
- user_email=user,
82
- workspace_id=_scope(request, write=True),
83
- )
84
- if record is None:
85
- raise http_error(404, "project.not_found", resolve_language(request))
86
- return record
87
-
88
- @router.put("/api/projects/{session_id}/todos")
89
- async def set_todos(session_id: str, req: TodosRequest, request: Request):
90
- user = require_user(request)
91
- record = store.set_todos(
92
- session_id,
93
- req.todos,
94
- user_email=user,
95
- workspace_id=_scope(request, write=True),
96
- )
97
- if record is None:
98
- raise http_error(404, "project.not_found", resolve_language(request))
99
- return record
100
-
101
- @router.delete("/api/projects/{session_id}")
102
- async def delete_project(session_id: str, request: Request):
103
- user = require_user(request)
104
- removed = store.delete(
105
- session_id, user_email=user, workspace_id=_scope(request, write=True)
106
- )
107
- if not removed:
108
- raise http_error(404, "project.not_found", resolve_language(request))
109
- return {"status": "deleted", "id": session_id}
110
-
111
- return router
112
-
113
-
114
- __all__ = ["create_project_sessions_router"]
@@ -1,118 +0,0 @@
1
- """Realtime Collaboration API router (v2).
2
-
3
- Server-Sent-Events stream + presence + activity feed over
4
- :class:`latticeai.core.realtime.RealtimeBus`. Workspace isolation is enforced by
5
- resolving each caller's allowed workspace scope before subscribing; single-user
6
- local mode works with no scope restriction.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import secrets
12
- from pathlib import Path
13
- from typing import Any, Callable, Optional, Set
14
-
15
- from fastapi import APIRouter, HTTPException, Request
16
- from fastapi.responses import StreamingResponse
17
- from pydantic import BaseModel
18
-
19
- from lattice_brain.runtime.contracts import contract_views
20
- from latticeai.api.ui_redirects import app_redirect
21
-
22
-
23
- class PresenceRequest(BaseModel):
24
- client_id: Optional[str] = None
25
- workspace_id: Optional[str] = None
26
-
27
-
28
- def create_realtime_router(
29
- *,
30
- bus,
31
- require_user: Callable[[Request], str],
32
- get_current_user: Callable[[Request], Optional[str]],
33
- allowed_scopes: Callable[[Optional[str]], Optional[Set[str]]],
34
- ui_file_response: Optional[Callable[[Path], Any]] = None,
35
- static_dir: Optional[Path] = None,
36
- ) -> APIRouter:
37
- router = APIRouter()
38
-
39
- @router.get("/activity")
40
- async def activity_page(request: Request):
41
- require_user(request)
42
- return app_redirect("activity", request)
43
-
44
- @router.get("/realtime/stream")
45
- async def realtime_stream(request: Request):
46
- user = require_user(request)
47
- scope = allowed_scopes(user or None)
48
- sub_id = secrets.token_urlsafe(12)
49
- sub = bus.add_subscriber(sub_id, workspace_scope=scope, user=user or None)
50
-
51
- def refresh_authorization(current_sub) -> bool:
52
- try:
53
- refreshed_user = require_user(request)
54
- except Exception:
55
- return False
56
- if refreshed_user != user:
57
- return False
58
- current_sub.workspace_scope = allowed_scopes(refreshed_user or None)
59
- return True
60
-
61
- async def event_gen():
62
- async for frame in bus.stream(
63
- sub,
64
- refresh_authorization=refresh_authorization,
65
- ):
66
- if await request.is_disconnected():
67
- break
68
- yield frame
69
-
70
- return StreamingResponse(
71
- event_gen(),
72
- media_type="text/event-stream",
73
- headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no", "Connection": "keep-alive"},
74
- )
75
-
76
- @router.get("/realtime/feed")
77
- async def realtime_feed(request: Request, limit: int = 50):
78
- user = require_user(request)
79
- scope = allowed_scopes(user or None)
80
- events = bus.recent(limit=limit, workspace_scope=scope)
81
- return {"events": events, "contracts": contract_views(events), "stats": bus.stats()}
82
-
83
- @router.get("/realtime/presence")
84
- async def realtime_presence(request: Request):
85
- user = require_user(request)
86
- scope = allowed_scopes(user or None)
87
- return {"presence": bus.presence(workspace_scope=scope), "stats": bus.stats()}
88
-
89
- @router.post("/realtime/presence/join")
90
- async def realtime_join(req: PresenceRequest, request: Request):
91
- user = require_user(request)
92
- scope = allowed_scopes(user or None)
93
- workspace_id = req.workspace_id
94
- if scope is not None:
95
- if workspace_id is None:
96
- if not scope:
97
- raise HTTPException(status_code=403, detail="No accessible workspace for presence.")
98
- workspace_id = "personal" if "personal" in scope else sorted(scope)[0]
99
- elif workspace_id not in scope:
100
- raise HTTPException(status_code=403, detail="Workspace presence access denied.")
101
- client_id = req.client_id or secrets.token_urlsafe(8)
102
- try:
103
- record = bus.join(client_id, user=user or None, workspace_id=workspace_id)
104
- except PermissionError as exc:
105
- raise HTTPException(status_code=403, detail=str(exc)) from exc
106
- return {"presence": record}
107
-
108
- @router.post("/realtime/presence/leave")
109
- async def realtime_leave(req: PresenceRequest, request: Request):
110
- user = require_user(request)
111
- if req.client_id:
112
- try:
113
- bus.leave(req.client_id, user=user or None)
114
- except PermissionError as exc:
115
- raise HTTPException(status_code=403, detail=str(exc)) from exc
116
- return {"status": "ok"}
117
-
118
- return router