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,978 +0,0 @@
1
- """Workspace OS persistence and orchestration primitives.
2
-
3
- This module keeps the 1.0 Workspace OS surface intentionally local-first:
4
- state is stored as JSON under the configured LatticeAI data directory, graph
5
- operations are additive, and snapshots are immutable files that can be
6
- exported or compared without mutating the live knowledge graph.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import json
12
- import shutil
13
- import sqlite3
14
- from contextlib import contextmanager
15
- from pathlib import Path
16
- from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional
17
-
18
- from latticeai.core.quiet import quiet
19
-
20
- from .timeutil import now_iso as _now
21
- from .workspace_computer_memory import WorkspaceComputerMemory
22
- from .workspace_graph_trace import WorkspaceGraphTrace
23
- from .workspace_indexing import WorkspaceIndexing
24
- from .workspace_memory import WorkspaceMemory
25
- from .workspace_onboarding import WorkspaceOnboarding
26
- from .workspace_os_constants import (
27
- DEFAULT_WORKSPACE_ID,
28
- EXECUTION_EVENT_TYPES,
29
- MEMORY_KINDS,
30
- ONBOARDING_STEPS,
31
- ROLE_PERMISSIONS,
32
- WORKSPACE_AREAS,
33
- WORKSPACE_OS_VERSION,
34
- WORKSPACE_PERMISSIONS,
35
- WORKSPACE_ROLES,
36
- WORKSPACE_TYPES,
37
- )
38
- from .workspace_os_state import default_state, migrate_workspaces, new_workspace_record
39
-
40
- # Extracted pure helpers (keeps this module smaller and focused on the store).
41
- from .workspace_os_utils import (
42
- _atomic_write_json,
43
- _deep_merge,
44
- _json_hash,
45
- _listify,
46
- _safe_slug,
47
- remove_skill_directory,
48
- )
49
- from .workspace_permissions import ( # type: ignore
50
- WorkspacePermissionManager,
51
- _member_role,
52
- )
53
- from .workspace_plugins import WorkspacePluginManager
54
- from .workspace_relationships import WorkspaceRelationships
55
- from .workspace_relationships import shortest_path as _shortest_path
56
- from .workspace_review_items import WorkspaceReviewItems
57
- from .workspace_runs import WorkspaceRuns
58
- from .workspace_skills import WorkspaceSkills
59
- from .workspace_snapshots import WorkspaceSnapshots
60
- from .workspace_timeline import WorkspaceTimeline
61
-
62
- __all__ = [
63
- "WORKSPACE_OS_VERSION",
64
- "WORKSPACE_TYPES",
65
- "DEFAULT_WORKSPACE_ID",
66
- "WORKSPACE_ROLES",
67
- "WORKSPACE_PERMISSIONS",
68
- "ROLE_PERMISSIONS",
69
- "WORKSPACE_AREAS",
70
- "ONBOARDING_STEPS",
71
- "MEMORY_KINDS",
72
- "EXECUTION_EVENT_TYPES",
73
- "WorkspaceOSStore",
74
- "remove_skill_directory",
75
- ]
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
- class WorkspaceOSStore:
90
- """Local-first state store for Workspace OS APIs."""
91
-
92
- def __init__(self, data_dir: Path | str, *, event_sink: Optional[Callable[[Dict[str, Any]], Any]] = None):
93
- self.data_dir = Path(data_dir).expanduser()
94
- self.state_path = self.data_dir / "workspace_os.json"
95
- self.sqlite_path = self.data_dir / "knowledge_graph.sqlite"
96
- self.snapshots_dir = self.data_dir / "workspace_snapshots"
97
- self.exports_dir = self.data_dir / "workspace_exports"
98
- self.data_dir.mkdir(parents=True, exist_ok=True)
99
- self.snapshots_dir.mkdir(parents=True, exist_ok=True)
100
- self.exports_dir.mkdir(parents=True, exist_ok=True)
101
- # Optional realtime hook: fired on every timeline event so the Realtime
102
- # bus receives all workspace activity without per-call wiring.
103
- # Defaults to None → zero behavior change for existing callers/tests.
104
- self.event_sink = event_sink
105
- # Composed managers (extracted to shrink god class per architecture rules).
106
- self.permissions = WorkspacePermissionManager(self)
107
- self._timeline = WorkspaceTimeline(self)
108
- self.plugins = WorkspacePluginManager(self)
109
- self.memory = WorkspaceMemory(self)
110
- self.snapshots = WorkspaceSnapshots(self)
111
- self.graph_trace = WorkspaceGraphTrace(self)
112
- self.runs = WorkspaceRuns(self)
113
- self.review_items = WorkspaceReviewItems(self)
114
- self.skills = WorkspaceSkills(self)
115
- self.indexing = WorkspaceIndexing(self)
116
- self.relationships = WorkspaceRelationships(self)
117
- self.computer_memory = WorkspaceComputerMemory(self)
118
- self.onboarding = WorkspaceOnboarding(self)
119
-
120
- @contextmanager
121
- def _connect_state_db(self) -> Iterator[sqlite3.Connection]:
122
- """Transactional connection that is closed when the block exits.
123
-
124
- ``with sqlite3.connect(...)`` commits but never closes; see
125
- :meth:`lattice_brain.storage.base.StorageEngine.session`.
126
- """
127
- conn = sqlite3.connect(self.sqlite_path)
128
- conn.execute("PRAGMA journal_mode=WAL")
129
- conn.execute(
130
- "CREATE TABLE IF NOT EXISTS workspace_os_state ("
131
- "id TEXT PRIMARY KEY, state_json TEXT NOT NULL, updated_at TEXT NOT NULL)"
132
- )
133
- conn.execute(
134
- "CREATE TABLE IF NOT EXISTS workspace_os_meta ("
135
- "key TEXT PRIMARY KEY, value TEXT NOT NULL)"
136
- )
137
- try:
138
- with conn:
139
- yield conn
140
- finally:
141
- conn.close()
142
-
143
- # ── moved to workspace_os_state.py (10.2.0) ───────────────────────────────
144
- # Kept as delegators: `_default_state` and `_migrate_workspaces` are called
145
- # by name in tests and by subclasses, and `_new_workspace_record` is used
146
- # across the manager classes. The behaviour lives in one place now; these
147
- # keep every existing call site working.
148
- @staticmethod
149
- def _new_workspace_record(**kwargs) -> Dict[str, Any]:
150
- return new_workspace_record(**kwargs)
151
-
152
- def _migrate_workspaces(self, state: Dict[str, Any]) -> Dict[str, Any]:
153
- return migrate_workspaces(state)
154
-
155
- def _default_state(self) -> Dict[str, Any]:
156
- return default_state()
157
-
158
- def _load_sqlite_state(self) -> Optional[Dict[str, Any]]:
159
- try:
160
- with self._connect_state_db() as conn:
161
- row = conn.execute(
162
- "SELECT state_json FROM workspace_os_state WHERE id='current'"
163
- ).fetchone()
164
- if not row:
165
- return None
166
- data = json.loads(row[0])
167
- return data if isinstance(data, dict) else None
168
- except Exception:
169
- return None
170
-
171
- def _save_sqlite_state(self, state: Dict[str, Any]) -> None:
172
- payload = json.dumps(state, ensure_ascii=False)
173
- with self._connect_state_db() as conn:
174
- conn.execute(
175
- "INSERT OR REPLACE INTO workspace_os_state(id, state_json, updated_at) VALUES('current', ?, ?)",
176
- (payload, state.get("updated_at") or _now()),
177
- )
178
-
179
- def _import_json_state_once(self, default: Dict[str, Any]) -> Dict[str, Any]:
180
- if not self.state_path.exists():
181
- return default
182
- try:
183
- loaded = json.loads(self.state_path.read_text(encoding="utf-8"))
184
- if not isinstance(loaded, dict):
185
- return default
186
- except Exception:
187
- return default
188
- try:
189
- backup = self.state_path.with_name(
190
- f"{self.state_path.name}.pre-sqlite.{_now().replace(':', '-')}.json"
191
- )
192
- if not any(self.state_path.parent.glob(f"{self.state_path.name}.pre-sqlite.*.json")):
193
- shutil.copy2(self.state_path, backup)
194
- except Exception:
195
- quiet()
196
- return _deep_merge(default, loaded)
197
-
198
-
199
-
200
- def migrate_workspace_identities(self, email_to_id: Dict[str, str]) -> int:
201
- """Rewrite workspace membership identities from legacy emails to UUIDs.
202
-
203
- The migration is additive and in-place: workspace records, memberships,
204
- and owner fields keep their shape, only identity string values change.
205
- """
206
- if not email_to_id:
207
- return 0
208
- normalized = {str(email).strip().lower(): user_id for email, user_id in email_to_id.items() if user_id}
209
- state = self.load_state()
210
- changed = 0
211
- for ws in (state.get("workspaces") or {}).values():
212
- owner = str(ws.get("owner_user_id") or "").strip().lower()
213
- if owner in normalized and ws.get("owner_user_id") != normalized[owner]:
214
- ws["owner_user_id"] = normalized[owner]
215
- changed += 1
216
- for member in _listify(ws.get("members")):
217
- member_id = str(member.get("user_id") or "").strip().lower()
218
- if member_id in normalized and member.get("user_id") != normalized[member_id]:
219
- member["user_id"] = normalized[member_id]
220
- member["updated_at"] = _now()
221
- changed += 1
222
- members = _listify(ws.get("members"))
223
- deduped = []
224
- seen_members = set()
225
- for member in members:
226
- member_id = member.get("user_id")
227
- if member_id and member_id in seen_members:
228
- changed += 1
229
- continue
230
- if member_id:
231
- seen_members.add(member_id)
232
- deduped.append(member)
233
- if len(deduped) != len(members):
234
- ws["members"] = deduped
235
- if changed:
236
- state["updated_at"] = _now()
237
- self.save_state(state)
238
- self.record_timeline_event("workspace", "identity_uuid_migrated", {"records": changed})
239
- return changed
240
-
241
-
242
- def load_state(self) -> Dict[str, Any]:
243
- default = self._default_state()
244
- loaded = self._load_sqlite_state()
245
- imported = loaded is None
246
- if loaded is None:
247
- loaded = self._import_json_state_once(default)
248
- state = _deep_merge(default, loaded)
249
- state["version"] = WORKSPACE_OS_VERSION
250
- self._migrate_workspaces(state)
251
- if imported:
252
- self.save_state(state)
253
- return state
254
-
255
- def save_state(self, state: Dict[str, Any]) -> Dict[str, Any]:
256
- state["version"] = WORKSPACE_OS_VERSION
257
- state["updated_at"] = _now()
258
- self._save_sqlite_state(state)
259
- _atomic_write_json(self.state_path, state)
260
- return state
261
-
262
- def record_timeline_event(self, area: str, event_type: str, payload: Dict[str, Any], workspace_id: Optional[str] = None) -> Dict[str, Any]:
263
- return self._timeline.record_timeline_event(area, event_type, payload, workspace_id=workspace_id)
264
-
265
- def _emit_execution_event(
266
- self,
267
- *,
268
- area: str,
269
- event_type: str,
270
- payload: Dict[str, Any],
271
- workspace_id: Optional[str],
272
- ) -> None:
273
- """Best-effort execution observability event for the realtime feed."""
274
- if event_type not in EXECUTION_EVENT_TYPES:
275
- return
276
- try:
277
- self.record_timeline_event(area, event_type, payload, workspace_id=workspace_id)
278
- except Exception:
279
- quiet()
280
-
281
- def _emit_replayable_timeline_events(
282
- self,
283
- *,
284
- area: str,
285
- run_id: str,
286
- timeline: List[Dict[str, Any]],
287
- workspace_id: Optional[str],
288
- ) -> None:
289
- for index, item in enumerate(timeline or []):
290
- event_type = item.get("event") or item.get("event_type")
291
- if event_type in EXECUTION_EVENT_TYPES:
292
- payload = {k: v for k, v in item.items() if k not in {"context_packet"}}
293
- payload["run_id"] = run_id
294
- payload["timeline_index"] = index
295
- self._emit_execution_event(area=area, event_type=event_type, payload=payload, workspace_id=workspace_id)
296
-
297
- def summary(self) -> Dict[str, Any]:
298
- state = self.load_state()
299
- return {
300
- "version": WORKSPACE_OS_VERSION,
301
- "identity": state.get("identity"),
302
- "active_workspace": state.get("active_workspace"),
303
- # The raw workspace registry (with member lists) must not leak to
304
- # non-members; WorkspaceService.summary() adds a membership-filtered
305
- # "workspace_registry" instead.
306
- "workspace_count": len(state.get("workspaces") or {}),
307
- "navigation": list(WORKSPACE_AREAS),
308
- "feature_flags": state.get("feature_flags"),
309
- "updated_at": state.get("updated_at"),
310
- "counts": {
311
- "snapshots": len(_listify(state.get("snapshots"))),
312
- "traces": len(_listify(state.get("traces"))),
313
- "memories": len(_listify(state.get("memories"))),
314
- "memory_snapshots": len(_listify(state.get("memory_snapshots"))),
315
- "agent_runs": len(_listify(state.get("agent_runs"))),
316
- "handoffs": len(_listify(state.get("handoffs"))),
317
- "workflows": len(_listify(state.get("workflows"))),
318
- "workflow_runs": len(_listify(state.get("workflow_runs"))),
319
- "skills": len(state.get("skill_registry") or {}),
320
- "plugins": len(state.get("plugin_registry") or {}),
321
- "templates": len(state.get("template_registry") or {}),
322
- "timeline": len(_listify(state.get("timeline"))),
323
- },
324
- "onboarding": state.get("onboarding"),
325
- "storage": {
326
- "state_path": str(self.state_path),
327
- "snapshots_dir": str(self.snapshots_dir),
328
- "exports_dir": str(self.exports_dir),
329
- },
330
- }
331
-
332
- # ------------------------------------------------------------------
333
- # Organization workspaces, membership, and roles
334
- # ------------------------------------------------------------------
335
-
336
- def _active_workspace_id(self, state: Optional[Dict[str, Any]] = None) -> str:
337
- state = state or self.load_state()
338
- active = state.get("active_workspace") or DEFAULT_WORKSPACE_ID
339
- if active not in (state.get("workspaces") or {}):
340
- return DEFAULT_WORKSPACE_ID
341
- return active
342
-
343
- def _resolve_scope(self, workspace_id: Optional[str], state: Optional[Dict[str, Any]] = None) -> str:
344
- """Resolve the workspace a write should be tagged with.
345
-
346
- ``None`` falls back to the active workspace (Personal by default), so
347
- legacy callers keep writing to the Personal workspace unchanged.
348
- """
349
- if workspace_id:
350
- return str(workspace_id)
351
- return self._active_workspace_id(state)
352
-
353
- @staticmethod
354
- def _record_workspace(record: Dict[str, Any]) -> str:
355
- """Workspace a stored record belongs to (legacy records map to Personal)."""
356
- return str(record.get("workspace_id") or DEFAULT_WORKSPACE_ID)
357
-
358
- def _scoped(self, records: List[Dict[str, Any]], workspace_id: Optional[str]) -> List[Dict[str, Any]]:
359
- if not workspace_id:
360
- return records
361
- target = str(workspace_id)
362
- return [item for item in records if self._record_workspace(item) == target]
363
-
364
- def list_workspaces(self, user_id: Optional[str] = None) -> Dict[str, Any]:
365
- state = self.load_state()
366
- workspaces = state.get("workspaces") or {}
367
- items = []
368
- for ws in workspaces.values():
369
- if user_id and ws.get("type") == "organization":
370
- role = self._member_role(ws, user_id)
371
- if role is None:
372
- continue
373
- items.append(self._workspace_public(ws, user_id))
374
- items.sort(key=lambda w: (w.get("type") != "personal", w.get("created_at") or ""))
375
- return {
376
- "active_workspace": self._active_workspace_id(state),
377
- "workspaces": items,
378
- "roles": list(WORKSPACE_ROLES),
379
- "permissions": {role: sorted(perms) for role, perms in ROLE_PERMISSIONS.items()},
380
- }
381
-
382
- def _workspace_public(self, ws: Dict[str, Any], user_id: Optional[str] = None) -> Dict[str, Any]:
383
- return {
384
- "workspace_id": ws.get("workspace_id") or ws.get("id"),
385
- "id": ws.get("workspace_id") or ws.get("id"),
386
- "name": ws.get("name"),
387
- "type": ws.get("type"),
388
- "owner_user_id": ws.get("owner_user_id"),
389
- "status": ws.get("status", "active"),
390
- "member_count": len(_listify(ws.get("members"))),
391
- "members": _listify(ws.get("members")),
392
- "settings": ws.get("settings") or {},
393
- "created_at": ws.get("created_at"),
394
- "updated_at": ws.get("updated_at"),
395
- "your_role": self._member_role(ws, user_id) if user_id else ("owner" if ws.get("type") == "personal" else None),
396
- }
397
-
398
- def get_workspace(self, workspace_id: str, user_id: Optional[str] = None) -> Dict[str, Any]:
399
- state = self.load_state()
400
- ws = (state.get("workspaces") or {}).get(workspace_id)
401
- if not ws:
402
- raise FileNotFoundError(workspace_id)
403
- return self._workspace_public(ws, user_id)
404
-
405
- def create_organization_workspace(
406
- self,
407
- *,
408
- name: str,
409
- owner_user_id: Optional[str],
410
- settings: Optional[Dict[str, Any]] = None,
411
- ) -> Dict[str, Any]:
412
- if not str(name or "").strip():
413
- raise ValueError("workspace name is required")
414
- state = self.load_state()
415
- workspaces = state.setdefault("workspaces", {})
416
- base = _safe_slug(f"org-{name}")
417
- workspace_id = base
418
- suffix = 2
419
- while workspace_id in workspaces:
420
- workspace_id = f"{base}-{suffix}"
421
- suffix += 1
422
- record = self._new_workspace_record(
423
- workspace_id=workspace_id,
424
- name=name.strip(),
425
- workspace_type="organization",
426
- owner_user_id=owner_user_id,
427
- settings=settings or {},
428
- )
429
- workspaces[workspace_id] = record
430
- self.save_state(state)
431
- self.record_timeline_event("workspace", "workspace_created", {"workspace_id": workspace_id, "type": "organization"})
432
- return self._workspace_public(record, owner_user_id)
433
-
434
- @staticmethod
435
- def _member_role(ws: Dict[str, Any], user_id: Optional[str]) -> Optional[str]:
436
- # Delegated to extracted module (kept for any internal direct calls).
437
- return _member_role(ws, user_id)
438
-
439
- def get_member_role(self, workspace_id: str, user_id: Optional[str]) -> Optional[str]:
440
- return self.permissions.get_member_role(workspace_id, user_id)
441
-
442
- def has_permission(self, workspace_id: str, user_id: Optional[str], permission: str) -> bool:
443
- return self.permissions.has_permission(workspace_id, user_id, permission)
444
-
445
- def _require_permission(self, ws: Dict[str, Any], actor: Optional[str], permission: str) -> None:
446
- self.permissions.require_permission(ws, actor, permission)
447
-
448
- def _load_org(self, state: Dict[str, Any], workspace_id: str) -> Dict[str, Any]:
449
- ws = (state.get("workspaces") or {}).get(workspace_id)
450
- if not ws:
451
- raise FileNotFoundError(workspace_id)
452
- if ws.get("type") != "organization":
453
- raise ValueError("operation only valid for organization workspaces")
454
- return ws
455
-
456
- def update_workspace(
457
- self,
458
- workspace_id: str,
459
- *,
460
- name: Optional[str] = None,
461
- settings: Optional[Dict[str, Any]] = None,
462
- actor: Optional[str] = None,
463
- ) -> Dict[str, Any]:
464
- state = self.load_state()
465
- ws = self._load_org(state, workspace_id)
466
- self._require_permission(ws, actor, "manage_workspace")
467
- if name is not None and str(name).strip():
468
- ws["name"] = str(name).strip()
469
- if settings is not None:
470
- ws["settings"] = {**(ws.get("settings") or {}), **settings}
471
- ws["updated_at"] = _now()
472
- self.save_state(state)
473
- self.record_timeline_event("workspace", "workspace_updated", {"workspace_id": workspace_id})
474
- return self._workspace_public(ws, actor)
475
-
476
- def archive_workspace(self, workspace_id: str, *, actor: Optional[str] = None) -> Dict[str, Any]:
477
- """Soft-archive an organization workspace. Data is never deleted."""
478
- state = self.load_state()
479
- ws = self._load_org(state, workspace_id)
480
- self._require_permission(ws, actor, "manage_workspace")
481
- ws["status"] = "archived"
482
- ws["updated_at"] = _now()
483
- if state.get("active_workspace") == workspace_id:
484
- state["active_workspace"] = DEFAULT_WORKSPACE_ID
485
- self.save_state(state)
486
- self.record_timeline_event("workspace", "workspace_archived", {"workspace_id": workspace_id})
487
- return self._workspace_public(ws, actor)
488
-
489
- def add_member(self, workspace_id: str, *, user_id: str, role: str = "member", actor: Optional[str] = None) -> Dict[str, Any]:
490
- return self.permissions.add_member(workspace_id, user_id=user_id, role=role, actor=actor)
491
-
492
- def update_member_role(self, workspace_id: str, *, user_id: str, role: str, actor: Optional[str] = None) -> Dict[str, Any]:
493
- # delegate or keep simple for now (full impl in permissions if expanded)
494
- if role not in WORKSPACE_ROLES:
495
- raise ValueError(f"unknown role: {role}")
496
- state = self.load_state()
497
- ws = self._load_org(state, workspace_id)
498
- self._require_permission(ws, actor, "manage_members")
499
- if user_id == ws.get("owner_user_id") and role != "owner":
500
- raise ValueError("cannot demote the workspace owner")
501
- member = next((m for m in _listify(ws.get("members")) if m.get("user_id") == user_id), None)
502
- if not member:
503
- raise FileNotFoundError(user_id)
504
- member["role"] = role
505
- member["updated_at"] = _now()
506
- ws["updated_at"] = _now()
507
- self.save_state(state)
508
- self.record_timeline_event("workspace", "member_role_updated", {"workspace_id": workspace_id, "user_id": user_id, "role": role})
509
- return self._workspace_public(ws, actor)
510
-
511
- def remove_member(self, workspace_id: str, *, user_id: str, actor: Optional[str] = None) -> Dict[str, Any]:
512
- state = self.load_state()
513
- ws = self._load_org(state, workspace_id)
514
- self._require_permission(ws, actor, "manage_members")
515
- if user_id == ws.get("owner_user_id"):
516
- raise ValueError("cannot remove the workspace owner")
517
- members = _listify(ws.get("members"))
518
- kept = [m for m in members if m.get("user_id") != user_id]
519
- if len(kept) == len(members):
520
- raise FileNotFoundError(user_id)
521
- ws["members"] = kept
522
- ws["updated_at"] = _now()
523
- self.save_state(state)
524
- self.record_timeline_event("workspace", "member_removed", {"workspace_id": workspace_id, "user_id": user_id})
525
- return self._workspace_public(ws, actor)
526
-
527
- def set_active_workspace(self, workspace_id: str, user_id: Optional[str] = None) -> Dict[str, Any]:
528
- state = self.load_state()
529
- ws = (state.get("workspaces") or {}).get(workspace_id)
530
- if not ws:
531
- raise FileNotFoundError(workspace_id)
532
- if ws.get("type") == "organization" and self._member_role(ws, user_id) is None:
533
- raise PermissionError(f"'{user_id or 'anonymous'}' is not a member of '{workspace_id}'")
534
- state["active_workspace"] = workspace_id
535
- self.save_state(state)
536
- self.record_timeline_event("workspace", "workspace_activated", {"workspace_id": workspace_id})
537
- return self._workspace_public(ws, user_id)
538
-
539
- def workspace_summary(self, workspace_id: str, user_id: Optional[str] = None) -> Dict[str, Any]:
540
- state = self.load_state()
541
- ws = (state.get("workspaces") or {}).get(workspace_id)
542
- if not ws:
543
- raise FileNotFoundError(workspace_id)
544
- public = self._workspace_public(ws, user_id)
545
- public["counts"] = {
546
- "snapshots": len(self._scoped(_listify(state.get("snapshots")), workspace_id)),
547
- "memories": len(self._scoped(_listify(state.get("memories")), workspace_id)),
548
- "memory_snapshots": len(self._scoped(_listify(state.get("memory_snapshots")), workspace_id)),
549
- "agent_runs": len(self._scoped(_listify(state.get("agent_runs")), workspace_id)),
550
- "handoffs": len(self._scoped(_listify(state.get("handoffs")), workspace_id)),
551
- "workflows": len(self._scoped(_listify(state.get("workflows")), workspace_id)),
552
- "workflow_runs": len(self._scoped(_listify(state.get("workflow_runs")), workspace_id)),
553
- "traces": len(self._scoped(_listify(state.get("traces")), workspace_id)),
554
- "timeline": len(self._scoped(_listify(state.get("timeline")), workspace_id)),
555
- }
556
- return public
557
-
558
- # ------------------------------------------------------------------
559
- # Onboarding
560
- # ------------------------------------------------------------------
561
-
562
- def onboarding_status(self, users: Optional[Dict[str, Any]] = None, graph_stats: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
563
- return self.onboarding.status(users=users, graph_stats=graph_stats)
564
-
565
- def update_onboarding_step(
566
- self,
567
- step: str,
568
- *,
569
- status: str = "complete",
570
- data: Optional[Dict[str, Any]] = None,
571
- error: str = "",
572
- user_email: Optional[str] = None,
573
- ) -> Dict[str, Any]:
574
- return self.onboarding.update_step(
575
- step, status=status, data=data, error=error, user_email=user_email
576
- )
577
-
578
- def complete_onboarding(self, data: Optional[Dict[str, Any]] = None, user_email: Optional[str] = None) -> Dict[str, Any]:
579
- return self.onboarding.complete(data=data, user_email=user_email)
580
-
581
- # ------------------------------------------------------------------
582
- # Graph answer traces
583
- # ------------------------------------------------------------------
584
-
585
- def build_graph_trace(
586
- self,
587
- question: str,
588
- graph: Any,
589
- context: str = "",
590
- *,
591
- limit: int = 8,
592
- allowed_workspaces=None,
593
- ) -> Dict[str, Any]:
594
- return self.graph_trace.build_graph_trace(
595
- question,
596
- graph,
597
- context,
598
- limit=limit,
599
- allowed_workspaces=allowed_workspaces,
600
- )
601
-
602
- def record_trace(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
603
- return self.graph_trace.record_trace(*args, **kwargs)
604
-
605
- def list_traces(self, conversation_id: Optional[str] = None, limit: int = 50, workspace_id: Optional[str] = None) -> Dict[str, Any]:
606
- return self.graph_trace.list_traces(conversation_id=conversation_id, limit=limit, workspace_id=workspace_id)
607
-
608
- # ------------------------------------------------------------------
609
- # Indexing dashboard
610
- # ------------------------------------------------------------------
611
-
612
- def build_indexing_dashboard(self, graph: Any, watcher_status: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
613
- return self.indexing.build_dashboard(graph, watcher_status)
614
-
615
- def pause_indexing(self, graph: Any, source_id: str, watcher: Any = None) -> Dict[str, Any]:
616
- return self.indexing.pause(graph, source_id, watcher)
617
-
618
- def resume_indexing(self, graph: Any, source_id: str, watcher: Any = None) -> Dict[str, Any]:
619
- return self.indexing.resume(graph, source_id, watcher)
620
-
621
- def remove_index_source(self, graph: Any, source_id: str, watcher: Any = None) -> Dict[str, Any]:
622
- return self.indexing.remove_source(graph, source_id, watcher)
623
-
624
- # ------------------------------------------------------------------
625
- # Snapshots, Time Machine, and diffs
626
- # ------------------------------------------------------------------
627
-
628
- def create_snapshot(
629
- self,
630
- *,
631
- name: str,
632
- graph: Any,
633
- history: Iterable[Dict[str, Any]],
634
- settings: Dict[str, Any],
635
- models: Dict[str, Any],
636
- workspace_id: Optional[str] = None,
637
- ) -> Dict[str, Any]:
638
- return self.snapshots.create_snapshot(name=name, graph=graph, history=history, settings=settings, models=models, workspace_id=workspace_id)
639
-
640
- def list_snapshots(self, workspace_id: Optional[str] = None) -> Dict[str, Any]:
641
- return self.snapshots.list_snapshots(workspace_id=workspace_id)
642
-
643
- def get_snapshot(self, snapshot_id: str) -> Dict[str, Any]:
644
- return self.snapshots.get_snapshot(snapshot_id)
645
-
646
- def snapshot_view(self, snapshot_id: str, area: str) -> Dict[str, Any]:
647
- return self.snapshots.snapshot_view(snapshot_id, area)
648
-
649
- def export_snapshot(self, snapshot_id: str) -> Dict[str, Any]:
650
- return self.snapshots.export_snapshot(snapshot_id)
651
-
652
- def restore_snapshot(
653
- self,
654
- snapshot_id: str,
655
- *,
656
- graph: Any,
657
- workspace_id: Optional[str] = None,
658
- user_email: Optional[str] = None,
659
- ) -> Dict[str, Any]:
660
- return self.snapshots.restore_snapshot(snapshot_id, graph=graph, workspace_id=workspace_id, user_email=user_email)
661
-
662
- def compare_snapshots(self, before_id: str, after_id: str) -> Dict[str, Any]:
663
- return self.snapshots.compare_snapshots(before_id, after_id)
664
-
665
- # ------------------------------------------------------------------
666
- # Personal memory
667
- # ------------------------------------------------------------------
668
-
669
- def upsert_memory(
670
- self,
671
- *,
672
- kind: str,
673
- content: str,
674
- user_email: Optional[str],
675
- tags: Optional[List[str]] = None,
676
- memory_id: Optional[str] = None,
677
- metadata: Optional[Dict[str, Any]] = None,
678
- graph: Any = None,
679
- workspace_id: Optional[str] = None,
680
- ) -> Dict[str, Any]:
681
- return self.memory.upsert_memory(
682
- kind=kind, content=content, user_email=user_email, tags=tags,
683
- memory_id=memory_id, metadata=metadata, graph=graph, workspace_id=workspace_id
684
- )
685
-
686
- def list_memories(self, user_email: Optional[str] = None, kind: Optional[str] = None, workspace_id: Optional[str] = None) -> Dict[str, Any]:
687
- return self.memory.list_memories(user_email=user_email, kind=kind, workspace_id=workspace_id)
688
-
689
- def search_memories(self, query: str, user_email: Optional[str] = None, limit: int = 20, workspace_id: Optional[str] = None) -> Dict[str, Any]:
690
- q = str(query or "").lower().strip()
691
- memories = self.list_memories(user_email=user_email, workspace_id=workspace_id).get("memories", [])
692
- if q:
693
- memories = [
694
- item for item in memories
695
- if q in str(item.get("content") or "").lower()
696
- or q in " ".join(item.get("tags") or []).lower()
697
- or q in str(item.get("kind") or "").lower()
698
- ]
699
- return {"query": query, "memories": memories[: max(1, min(limit, 100))]}
700
-
701
- def get_memory(self, memory_id: str) -> Dict[str, Any]:
702
- record = next(
703
- (item for item in _listify(self.load_state().get("memories")) if item.get("id") == memory_id),
704
- None,
705
- )
706
- if record is None:
707
- raise FileNotFoundError(memory_id)
708
- return record
709
-
710
- def delete_memory(self, memory_id: str) -> Dict[str, Any]:
711
- state = self.load_state()
712
- memories = _listify(state.get("memories"))
713
- target = next((item for item in memories if item.get("id") == memory_id), None)
714
- if target is None:
715
- raise FileNotFoundError(memory_id)
716
- state["memories"] = [item for item in memories if item.get("id") != memory_id]
717
- self.save_state(state)
718
- self.record_timeline_event(
719
- "memory", "memory_deleted", {"memory_id": memory_id}, workspace_id=target.get("workspace_id")
720
- )
721
- return {"status": "ok", "memory_id": memory_id}
722
-
723
- def create_memory_snapshot(
724
- self,
725
- *,
726
- label: str = "memory snapshot",
727
- user_email: Optional[str] = None,
728
- workspace_id: Optional[str] = None,
729
- memory_ids: Optional[List[str]] = None,
730
- reason: str = "",
731
- ) -> Dict[str, Any]:
732
- """Persist a replayable point-in-time memory view without mutation."""
733
- state = self.load_state()
734
- scope = self._resolve_scope(workspace_id, state)
735
- memories = self._scoped(_listify(state.get("memories")), scope)
736
- if user_email:
737
- memories = [item for item in memories if item.get("user_email") in {None, user_email}]
738
- if memory_ids:
739
- allowed = set(memory_ids)
740
- memories = [item for item in memories if item.get("id") in allowed]
741
- snapshot = {
742
- "id": f"memory-snapshot-{_json_hash([label, scope, memories, _now()])[:16]}",
743
- "label": label,
744
- "reason": reason,
745
- "workspace_id": scope,
746
- "user_email": user_email,
747
- "memory_count": len(memories),
748
- "memories": memories,
749
- "created_at": _now(),
750
- }
751
- state.setdefault("memory_snapshots", []).append(snapshot)
752
- self.save_state(state)
753
- self.record_timeline_event("memory", "memory_snapshot", {"snapshot_id": snapshot["id"], "memory_count": len(memories)}, workspace_id=scope)
754
- return snapshot
755
-
756
- def list_memory_snapshots(self, workspace_id: Optional[str] = None, limit: int = 50) -> Dict[str, Any]:
757
- snapshots = self._scoped(_listify(self.load_state().get("memory_snapshots")), workspace_id)
758
- return {"snapshots": list(reversed(snapshots[-max(1, min(limit, 200)):]))}
759
-
760
- # ------------------------------------------------------------------
761
- # Agent and workflow graph
762
- # ------------------------------------------------------------------
763
-
764
- def list_agents(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
765
- return self.runs.list_agents(*args, **kwargs)
766
-
767
- def record_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
768
- return self.runs.record_agent_run(*args, **kwargs)
769
-
770
- def update_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
771
- return self.runs.update_agent_run(*args, **kwargs)
772
-
773
- def get_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
774
- return self.runs.get_agent_run(*args, **kwargs)
775
-
776
- def list_handoffs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
777
- return self.runs.list_handoffs(*args, **kwargs)
778
-
779
- def create_workflow(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
780
- return self.runs.create_workflow(*args, **kwargs)
781
-
782
- def record_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
783
- return self.runs.record_workflow_run(*args, **kwargs)
784
-
785
- def update_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
786
- return self.runs.update_workflow_run(*args, **kwargs)
787
-
788
- def list_workflow_runs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
789
- return self.runs.list_workflow_runs(*args, **kwargs)
790
-
791
- def list_combined_runs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
792
- return self.runs.list_combined_runs(*args, **kwargs)
793
-
794
- def mark_workflow_run_resolved(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
795
- return self.runs.mark_workflow_run_resolved(*args, **kwargs)
796
-
797
- def get_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
798
- return self.runs.get_workflow_run(*args, **kwargs)
799
-
800
- def reconcile_interrupted_runs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
801
- return self.runs.reconcile_interrupted_runs(*args, **kwargs)
802
-
803
- @staticmethod
804
- def _replay_frames(run: Dict[str, Any], *, kind: str) -> List[Dict[str, Any]]:
805
- return WorkspaceRuns._replay_frames(run, kind=kind)
806
-
807
- def replay_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
808
- return self.runs.replay_agent_run(*args, **kwargs)
809
-
810
- def replay_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
811
- return self.runs.replay_workflow_run(*args, **kwargs)
812
-
813
- def get_workflow(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
814
- return self.runs.get_workflow(*args, **kwargs)
815
-
816
- def update_workflow_definition(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
817
- return self.runs.update_workflow_definition(*args, **kwargs)
818
-
819
- def list_workflows(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
820
- return self.runs.list_workflows(*args, **kwargs)
821
-
822
- def record_workflow_event(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
823
- return self.runs.record_workflow_event(*args, **kwargs)
824
-
825
- # ── review queue (5.6.0) ─────────────────────────────────────────────
826
- # Workspace-scoped suggestion inbox. Persistence moved to
827
- # ``workspace_review_items.WorkspaceReviewItems`` (v9.9.6); the transition
828
- # policy still lives in ReviewQueueService (services/review_queue.py).
829
-
830
- def create_review_item(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
831
- return self.review_items.create_review_item(*args, **kwargs)
832
-
833
- def list_review_items(self, *args: Any, **kwargs: Any) -> List[Dict[str, Any]]:
834
- return self.review_items.list_review_items(*args, **kwargs)
835
-
836
- def get_review_item(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
837
- return self.review_items.get_review_item(*args, **kwargs)
838
-
839
- def update_review_item(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
840
- return self.review_items.update_review_item(*args, **kwargs)
841
-
842
- # ------------------------------------------------------------------
843
- # Agent-native workspace: folder reorganization (v11.1.0)
844
- # ------------------------------------------------------------------
845
-
846
- def propose_reorganization(
847
- self, *, root: str = "", change_proposals: Any, graph: Any = None,
848
- user_email: Optional[str] = None, workspace_id: Optional[str] = None,
849
- max_moves: int = 20,
850
- ) -> Dict[str, Any]:
851
- """Stage a folder reorganization for review and record it on the timeline.
852
-
853
- The staging itself belongs to ``ChangeProposalService`` (one apply path
854
- for every staged change); this seam exists so a long-running agent
855
- session asking the Workspace OS to tidy a project leaves a trace of
856
- having asked, whether or not there was anything to propose.
857
- """
858
- result = change_proposals.propose_reorganization(
859
- root=root, graph=graph, user_email=user_email,
860
- workspace_id=workspace_id, max_moves=max_moves,
861
- )
862
- proposed = result.get("proposed") or {}
863
- self.record_timeline_event(
864
- "workspace",
865
- "workspace_reorg_proposed",
866
- {
867
- "root": root,
868
- "moves": (result.get("plan") or {}).get("move_count", 0),
869
- "proposal_id": proposed.get("id"),
870
- },
871
- workspace_id=workspace_id,
872
- )
873
- return result
874
-
875
- # ------------------------------------------------------------------
876
- # Relationship explorer
877
- # ------------------------------------------------------------------
878
-
879
- def relationship_explorer(self, graph: Any, node_id: str, target_id: Optional[str] = None, limit: int = 500) -> Dict[str, Any]:
880
- return self.relationships.explore(graph, node_id, target_id=target_id, limit=limit)
881
-
882
- @staticmethod
883
- def _shortest_path(edges: List[Dict[str, Any]], start: str, target: Optional[str]) -> List[str]:
884
- return _shortest_path(edges, start, target)
885
-
886
- # ------------------------------------------------------------------
887
- # Local Computer Memory
888
- # ------------------------------------------------------------------
889
-
890
- def configure_computer_memory(
891
- self,
892
- *,
893
- enabled: bool,
894
- approved_by: Optional[str],
895
- consent: Optional[Dict[str, Any]] = None,
896
- scopes: Optional[List[str]] = None,
897
- ) -> Dict[str, Any]:
898
- return self.computer_memory.configure(
899
- enabled=enabled, approved_by=approved_by, consent=consent, scopes=scopes
900
- )
901
-
902
- def record_computer_activity(self, activity: Dict[str, Any], graph: Any = None) -> Dict[str, Any]:
903
- return self.computer_memory.record_activity(activity, graph)
904
-
905
- # ------------------------------------------------------------------
906
- # Skills
907
- # ------------------------------------------------------------------
908
-
909
- def list_skill_registry(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
910
- return self.skills.list_skill_registry(*args, **kwargs)
911
-
912
- def set_skill_enabled(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
913
- return self.skills.set_skill_enabled(*args, **kwargs)
914
-
915
- def mark_skill_installed(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
916
- return self.skills.mark_skill_installed(*args, **kwargs)
917
-
918
- def mark_skill_uninstalled(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
919
- return self.skills.mark_skill_uninstalled(*args, **kwargs)
920
-
921
- # ------------------------------------------------------------------
922
- # Plugin SDK registry — mirrors the skill registry contract.
923
- # ------------------------------------------------------------------
924
-
925
- def list_plugin_registry(self) -> Dict[str, Any]:
926
- return self.plugins.list_plugin_registry()
927
-
928
- def set_plugin_enabled(self, plugin_id: str, enabled: bool) -> Dict[str, Any]:
929
- return self.plugins.set_plugin_enabled(plugin_id, enabled)
930
-
931
- def mark_plugin_installed(self, plugin_id: str, *, version: str = "0.0.0", metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
932
- return self.plugins.mark_plugin_installed(plugin_id, version=version, metadata=metadata)
933
-
934
- def mark_plugin_uninstalled(self, plugin_id: str) -> Dict[str, Any]:
935
- return self.plugins.mark_plugin_uninstalled(plugin_id)
936
-
937
- # ------------------------------------------------------------------
938
- # Marketplace template registry (v2.1 foundation)
939
- # ------------------------------------------------------------------
940
-
941
- @staticmethod
942
- def _template_registry_key(kind: str, template_id: str, workspace_id: str) -> str:
943
- base = f"{kind}:{template_id}"
944
- return base if workspace_id == DEFAULT_WORKSPACE_ID else f"{workspace_id}:{base}"
945
-
946
- def list_template_registry(self, workspace_id: Optional[str] = None) -> Dict[str, Any]:
947
- return self.plugins.list_template_registry(workspace_id)
948
-
949
- def mark_template_installed(
950
- self,
951
- *,
952
- kind: str,
953
- template_id: str,
954
- version: str = "1.0.0",
955
- metadata: Optional[Dict[str, Any]] = None,
956
- workspace_id: Optional[str] = None,
957
- ) -> Dict[str, Any]:
958
- return self.plugins.mark_template_installed(kind=kind, template_id=template_id, version=version, metadata=metadata, workspace_id=workspace_id)
959
-
960
- # ------------------------------------------------------------------
961
- # Audit timeline
962
- # ------------------------------------------------------------------
963
-
964
- def filter_audit_timeline(
965
- self,
966
- audit_events: Iterable[Dict[str, Any]],
967
- *,
968
- user: Optional[str] = None,
969
- event_type: Optional[str] = None,
970
- model: Optional[str] = None,
971
- since: Optional[str] = None,
972
- until: Optional[str] = None,
973
- limit: int = 100,
974
- ) -> Dict[str, Any]:
975
- return self._timeline.filter_audit_timeline(audit_events, user=user, event_type=event_type, model=model, since=since, until=until, limit=limit)
976
-
977
- def timeline(self, audit_events: Optional[Iterable[Dict[str, Any]]] = None, limit: int = 100, workspace_id: Optional[str] = None) -> Dict[str, Any]:
978
- return self._timeline.timeline(audit_events=audit_events, limit=limit, workspace_id=workspace_id)