ltcai 11.5.1 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/README.md +92 -117
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +129 -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/HYBRID_CLOUD_KG_STREAMING.md +8 -4
  8. package/docs/MULTI_AGENT_RUNTIME.md +12 -5
  9. package/docs/ONBOARDING.md +1 -1
  10. package/docs/OPERATIONS.md +6 -3
  11. package/docs/REALTIME_COLLABORATION.md +1 -1
  12. package/docs/TRUST_MODEL.md +1 -1
  13. package/docs/WHY_LATTICE.md +1 -1
  14. package/docs/WORKFLOW_DESIGNER.md +3 -2
  15. package/docs/architecture.md +4 -4
  16. package/docs/kg-schema.md +1 -1
  17. package/docs/v11.5.2_TIGHT_SHIP_PLAN.md +144 -0
  18. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  19. package/lattice_brain/__init__.py +42 -79
  20. package/lattice_brain/graph/__init__.py +9 -24
  21. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  22. package/lattice_brain/ingestion/__init__.py +19 -58
  23. package/lattice_brain/ingestion/pipeline.py +20 -398
  24. package/lattice_brain/multimodal/__init__.py +7 -18
  25. package/lattice_brain/multimodal/images.py +6 -264
  26. package/lattice_brain/multimodal/video.py +8 -247
  27. package/lattice_brain/runtime/__init__.py +8 -79
  28. package/lattice_brain/runtime/hooks.py +22 -597
  29. package/latticeai/__init__.py +1 -1
  30. package/latticeai/api/agent_worker_seam.py +7 -80
  31. package/latticeai/api/health.py +26 -20
  32. package/latticeai/api/local_files.py +25 -608
  33. package/latticeai/api/models.py +12 -87
  34. package/latticeai/api/search.py +17 -231
  35. package/latticeai/api/tools.py +69 -750
  36. package/latticeai/api/voice_capture.py +8 -70
  37. package/latticeai/api/worker_compute.py +842 -0
  38. package/latticeai/api/worker_seams.py +216 -0
  39. package/latticeai/app_factory.py +23 -232
  40. package/latticeai/cli/entrypoint.py +36 -249
  41. package/latticeai/cli/runtime.py +7 -3
  42. package/latticeai/core/agent_permission.py +16 -91
  43. package/latticeai/core/config.py +47 -3
  44. package/latticeai/core/csrf.py +28 -2
  45. package/latticeai/core/embedding_providers/__init__.py +3 -5
  46. package/latticeai/core/embedding_providers/base.py +1 -1
  47. package/latticeai/core/embedding_providers/text.py +1 -1
  48. package/latticeai/core/http_origin.py +146 -0
  49. package/latticeai/core/io_utils.py +2 -11
  50. package/latticeai/core/messages.py +94 -531
  51. package/latticeai/core/model_compat.py +2 -8
  52. package/latticeai/core/module_probe.py +37 -0
  53. package/latticeai/core/security.py +13 -0
  54. package/latticeai/core/sessions.py +3 -2
  55. package/latticeai/core/sse.py +25 -0
  56. package/latticeai/core/users.py +0 -9
  57. package/latticeai/models/router/documents.py +0 -35
  58. package/latticeai/models/router/registry.py +0 -4
  59. package/latticeai/runtime/access_runtime.py +16 -18
  60. package/latticeai/runtime/bootstrap.py +10 -21
  61. package/latticeai/runtime/brain_runtime.py +35 -43
  62. package/latticeai/runtime/build_phases/__init__.py +21 -37
  63. package/latticeai/runtime/build_phases/features.py +101 -389
  64. package/latticeai/runtime/build_phases/foundation.py +82 -392
  65. package/latticeai/runtime/build_phases/web.py +91 -406
  66. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  67. package/latticeai/runtime/lifespan_runtime.py +7 -16
  68. package/latticeai/runtime/platform_services_runtime.py +1 -15
  69. package/latticeai/runtime/runtime_context.py +14 -143
  70. package/latticeai/runtime/security_runtime.py +10 -103
  71. package/latticeai/services/architecture_readiness.py +82 -43
  72. package/latticeai/services/model_capability_registry.py +0 -15
  73. package/latticeai/services/model_catalog.py +0 -7
  74. package/latticeai/services/model_loading.py +3 -2
  75. package/latticeai/services/model_runtime/__init__.py +2 -68
  76. package/latticeai/services/model_runtime/engines.py +27 -128
  77. package/latticeai/services/model_runtime/loading.py +2 -2
  78. package/latticeai/services/model_runtime/service.py +4 -28
  79. package/latticeai/services/p_reinforce.py +10 -261
  80. package/latticeai/services/product_readiness.py +31 -38
  81. package/latticeai/services/search_service.py +37 -795
  82. package/latticeai/services/tool_dispatch.py +30 -390
  83. package/latticeai/services/voice_capture.py +13 -107
  84. package/latticeai/tools/__init__.py +22 -49
  85. package/latticeai/tools/commands.py +0 -163
  86. package/latticeai/tools/computer.py +0 -39
  87. package/latticeai/tools/documents.py +1 -134
  88. package/latticeai/tools/filesystem.py +1 -247
  89. package/latticeai/tools/knowledge.py +1 -52
  90. package/latticeai/tools/local_files.py +0 -20
  91. package/latticeai/worker_app.py +75 -0
  92. package/package.json +3 -6
  93. package/requirements.txt +0 -5
  94. package/scripts/agent_eval.py +16 -28
  95. package/scripts/brain_quality_eval.py +20 -183
  96. package/scripts/bump_version.py +19 -6
  97. package/scripts/check_current_release_docs.mjs +7 -4
  98. package/scripts/check_legacy_debt.mjs +1 -1
  99. package/scripts/check_openapi_drift.mjs +10 -0
  100. package/scripts/check_server_i18n.mjs +2 -18
  101. package/scripts/compose_openapi.py +377 -0
  102. package/scripts/export_openapi.py +32 -4
  103. package/scripts/gen_messages_catalog_fixture.py +302 -0
  104. package/scripts/gen_openapi_fragments.py +365 -0
  105. package/scripts/gen_redact_fixture.py +196 -0
  106. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  107. package/scripts/generate_agent_parity_fixtures.py +33 -14
  108. package/scripts/openapi_route_families.json +2161 -0
  109. package/scripts/release_screen_claims.json +31 -0
  110. package/scripts/run_integration_tests.mjs +134 -29
  111. package/scripts/run_sidecar_e2e.mjs +104 -11
  112. package/scripts/wheel_smoke.py +32 -22
  113. package/src-tauri/Cargo.lock +375 -9
  114. package/src-tauri/Cargo.toml +1 -1
  115. package/src-tauri/src/backend.rs +223 -43
  116. package/src-tauri/src/main.rs +27 -17
  117. package/src-tauri/src/topology.rs +58 -88
  118. package/src-tauri/tauri.conf.json +2 -3
  119. package/static/app/asset-manifest.json +41 -41
  120. package/static/app/assets/{Act-Drs_jd-O.js → Act-CToZnOHz.js} +1 -1
  121. package/static/app/assets/{AdminConsole-BqNx5oF6.js → AdminConsole-D5kbrBu8.js} +1 -1
  122. package/static/app/assets/{Brain-C7bdNmz-.js → Brain-C6zCdv4S.js} +1 -1
  123. package/static/app/assets/{BrainHome-DcTnINcI.js → BrainHome-D4n3EDUt.js} +1 -1
  124. package/static/app/assets/{BrainSignals-Df1MRIPD.js → BrainSignals-CzeZ-vQ9.js} +1 -1
  125. package/static/app/assets/{Capture-Dgsxvnbx.js → Capture-DPcyTczq.js} +1 -1
  126. package/static/app/assets/{Chronicle-BrkTxJK1.js → Chronicle-D3Nx-oWS.js} +1 -1
  127. package/static/app/assets/{CommandPalette-DDziw7Oj.js → CommandPalette-DPPvpp28.js} +1 -1
  128. package/static/app/assets/{Library-CYxeuraA.js → Library-Co2qTu3I.js} +1 -1
  129. package/static/app/assets/{LivingBrain-CNp6mvCm.js → LivingBrain-Bsmzwy8_.js} +1 -1
  130. package/static/app/assets/{ProductFlow-BVsowu3Z.js → ProductFlow-CBLdmesT.js} +1 -1
  131. package/static/app/assets/{ReviewCard-D1LbOfJS.js → ReviewCard-CfR6DLs7.js} +1 -1
  132. package/static/app/assets/{System-DcRAq7wj.js → System-Ce-P2YL6.js} +1 -1
  133. package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
  134. package/static/app/assets/{bot-BK2xQ9mN.js → bot-C5-XIww2.js} +1 -1
  135. package/static/app/assets/{brain-BZNztFBb.js → brain-CFRJUvdX.js} +1 -1
  136. package/static/app/assets/{button-CjueubVZ.js → button-DI1KcQP-.js} +1 -1
  137. package/static/app/assets/circle-check-CY20XMCh.js +1 -0
  138. package/static/app/assets/{circle-pause-HRjSmpyY.js → circle-pause-C8HHyood.js} +1 -1
  139. package/static/app/assets/{circle-play-CMcKDVwu.js → circle-play-Cufc235E.js} +1 -1
  140. package/static/app/assets/{cpu-B_MyTfYr.js → cpu-DW2_JVsv.js} +1 -1
  141. package/static/app/assets/{download-CTy0EByV.js → download-gbdKTBb8.js} +1 -1
  142. package/static/app/assets/{folder-open-DF6bLhTg.js → folder-open-C9ydyX33.js} +1 -1
  143. package/static/app/assets/{hard-drive-CpWHG77C.js → hard-drive-BxNMzBM3.js} +1 -1
  144. package/static/app/assets/index-BovSWRmQ.css +2 -0
  145. package/static/app/assets/{index-Dd6abJHX.js → index-q9XyDY7A.js} +3 -3
  146. package/static/app/assets/{input-bVgIRN3s.js → input-GLD94Y_K.js} +1 -1
  147. package/static/app/assets/{link-2-bg6CG5Vk.js → link-2-B4Y0gz1S.js} +1 -1
  148. package/static/app/assets/{permissionCopy-DFTf1HDZ.js → permissionCopy-CxrV0fvU.js} +1 -1
  149. package/static/app/assets/{primitives-D7D-sQ3T.js → primitives-2ANdAsu2.js} +1 -1
  150. package/static/app/assets/search-DTxtjQ07.js +1 -0
  151. package/static/app/assets/{share-2-BLzo7U4L.js → share-2-DxArfiuI.js} +1 -1
  152. package/static/app/assets/{shield-alert-BmngqnsJ.js → shield-alert-CgftuFFs.js} +1 -1
  153. package/static/app/assets/{textarea-DOSlAQQ3.js → textarea-BHVBAoCr.js} +1 -1
  154. package/static/app/assets/{useFocusTrap-pZhgeee4.js → useFocusTrap-Dzq2j_OG.js} +1 -1
  155. package/static/app/assets/{useMutation-BMDwNk4I.js → useMutation-BcqfMLNq.js} +1 -1
  156. package/static/app/assets/{useQuery-CSjKttRo.js → useQuery-DBzLKCOv.js} +1 -1
  157. package/static/app/assets/{utils-D3u_yv7B.js → utils-B-Aah1bO.js} +1 -1
  158. package/static/app/assets/{workspace-H3bMjYXC.js → workspace-B6fFhoko.js} +1 -1
  159. package/static/app/index.html +4 -4
  160. package/static/sw.js +1 -1
  161. package/desktop/electron/README.md +0 -9
  162. package/desktop/electron/main.cjs +0 -58
  163. package/desktop/electron/preload.cjs +0 -5
  164. package/lattice_brain/archive.py +0 -522
  165. package/lattice_brain/context.py +0 -325
  166. package/lattice_brain/conversations.py +0 -382
  167. package/lattice_brain/core.py +0 -82
  168. package/lattice_brain/graph/_kg_contract.py +0 -249
  169. package/lattice_brain/graph/curator.py +0 -676
  170. package/lattice_brain/graph/discovery.py +0 -595
  171. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  172. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  173. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  174. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  175. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  176. package/lattice_brain/graph/documents.py +0 -380
  177. package/lattice_brain/graph/fusion.py +0 -395
  178. package/lattice_brain/graph/identity.py +0 -175
  179. package/lattice_brain/graph/image_vectors.py +0 -230
  180. package/lattice_brain/graph/ingest.py +0 -829
  181. package/lattice_brain/graph/network.py +0 -205
  182. package/lattice_brain/graph/proactive.py +0 -724
  183. package/lattice_brain/graph/projection/__init__.py +0 -42
  184. package/lattice_brain/graph/projection/curation.py +0 -500
  185. package/lattice_brain/graph/projection/v2_schema.py +0 -546
  186. package/lattice_brain/graph/provenance.py +0 -524
  187. package/lattice_brain/graph/rerank.py +0 -163
  188. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  189. package/lattice_brain/graph/retrieval/context.py +0 -197
  190. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  191. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  192. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  193. package/lattice_brain/graph/retrieval/signals.py +0 -95
  194. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  195. package/lattice_brain/graph/retrieval_policy.py +0 -180
  196. package/lattice_brain/graph/retrieval_reads.py +0 -769
  197. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  198. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  199. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  200. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  201. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  202. package/lattice_brain/graph/schema.py +0 -792
  203. package/lattice_brain/graph/store.py +0 -268
  204. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  205. package/lattice_brain/graph/vector_index/base.py +0 -170
  206. package/lattice_brain/graph/vector_index/brute_force.py +0 -114
  207. package/lattice_brain/graph/vector_index/hnsw.py +0 -293
  208. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  209. package/lattice_brain/graph/vector_index/quantized.py +0 -151
  210. package/lattice_brain/graph/vector_index/selector.py +0 -161
  211. package/lattice_brain/graph/write_master.py +0 -308
  212. package/lattice_brain/ingestion/_contract.py +0 -90
  213. package/lattice_brain/ingestion/folder_scan.py +0 -57
  214. package/lattice_brain/ingestion/folders.py +0 -258
  215. package/lattice_brain/ingestion/jobs_api.py +0 -107
  216. package/lattice_brain/ingestion/routing.py +0 -295
  217. package/lattice_brain/ingestion_jobs.py +0 -383
  218. package/lattice_brain/memory.py +0 -102
  219. package/lattice_brain/portability/__init__.py +0 -90
  220. package/lattice_brain/portability/_contract.py +0 -42
  221. package/lattice_brain/portability/backups.py +0 -338
  222. package/lattice_brain/portability/bundles.py +0 -136
  223. package/lattice_brain/portability/constants.py +0 -93
  224. package/lattice_brain/portability/fsops.py +0 -138
  225. package/lattice_brain/portability/service.py +0 -41
  226. package/lattice_brain/portability/sharing.py +0 -714
  227. package/lattice_brain/quality.py +0 -592
  228. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  229. package/lattice_brain/runtime/agent_runtime.py +0 -859
  230. package/lattice_brain/runtime/contracts.py +0 -460
  231. package/lattice_brain/runtime/multi_agent.py +0 -942
  232. package/lattice_brain/runtime/statuses.py +0 -10
  233. package/lattice_brain/sealed_box.py +0 -244
  234. package/lattice_brain/self_model.py +0 -675
  235. package/lattice_brain/sensitivity.py +0 -94
  236. package/lattice_brain/storage/__init__.py +0 -22
  237. package/lattice_brain/storage/base.py +0 -100
  238. package/lattice_brain/storage/docker.py +0 -105
  239. package/lattice_brain/storage/factory.py +0 -31
  240. package/lattice_brain/storage/migration.py +0 -191
  241. package/lattice_brain/storage/postgres.py +0 -123
  242. package/lattice_brain/storage/sqlite.py +0 -143
  243. package/lattice_brain/synthesis.py +0 -824
  244. package/lattice_brain/workflow.py +0 -497
  245. package/latticeai/api/admin.py +0 -471
  246. package/latticeai/api/agent_registry.py +0 -105
  247. package/latticeai/api/agents.py +0 -228
  248. package/latticeai/api/auth.py +0 -360
  249. package/latticeai/api/automation_intelligence.py +0 -401
  250. package/latticeai/api/brain_intelligence.py +0 -199
  251. package/latticeai/api/browser.py +0 -493
  252. package/latticeai/api/change_proposals.py +0 -89
  253. package/latticeai/api/chat.py +0 -572
  254. package/latticeai/api/chat_agent_http.py +0 -901
  255. package/latticeai/api/chat_contracts.py +0 -73
  256. package/latticeai/api/chat_documents.py +0 -276
  257. package/latticeai/api/chat_helpers.py +0 -463
  258. package/latticeai/api/chat_history.py +0 -101
  259. package/latticeai/api/chat_hybrid.py +0 -113
  260. package/latticeai/api/chat_intents.py +0 -646
  261. package/latticeai/api/chat_stream.py +0 -216
  262. package/latticeai/api/chronicle.py +0 -63
  263. package/latticeai/api/command_center.py +0 -51
  264. package/latticeai/api/computer_use.py +0 -485
  265. package/latticeai/api/evidence_actions.py +0 -48
  266. package/latticeai/api/features.py +0 -70
  267. package/latticeai/api/funnel_metrics.py +0 -31
  268. package/latticeai/api/garden.py +0 -34
  269. package/latticeai/api/hooks.py +0 -165
  270. package/latticeai/api/index_jobs.py +0 -145
  271. package/latticeai/api/invitations.py +0 -100
  272. package/latticeai/api/knowledge_graph.py +0 -536
  273. package/latticeai/api/marketplace.py +0 -105
  274. package/latticeai/api/mcp.py +0 -482
  275. package/latticeai/api/memory.py +0 -270
  276. package/latticeai/api/network.py +0 -81
  277. package/latticeai/api/network_boundary.py +0 -225
  278. package/latticeai/api/permission_mode.py +0 -61
  279. package/latticeai/api/permissions.py +0 -410
  280. package/latticeai/api/plugins.py +0 -126
  281. package/latticeai/api/portability.py +0 -391
  282. package/latticeai/api/project_sessions.py +0 -114
  283. package/latticeai/api/realtime.py +0 -118
  284. package/latticeai/api/review_queue.py +0 -364
  285. package/latticeai/api/security_dashboard.py +0 -604
  286. package/latticeai/api/setup.py +0 -319
  287. package/latticeai/api/static_routes.py +0 -351
  288. package/latticeai/api/ui_redirects.py +0 -26
  289. package/latticeai/api/workflow_designer.py +0 -394
  290. package/latticeai/api/workspace.py +0 -856
  291. package/latticeai/api/workspace_scope.py +0 -125
  292. package/latticeai/core/agent/__init__.py +0 -93
  293. package/latticeai/core/agent/_contract.py +0 -79
  294. package/latticeai/core/agent/context.py +0 -57
  295. package/latticeai/core/agent/deps.py +0 -125
  296. package/latticeai/core/agent/execution.py +0 -622
  297. package/latticeai/core/agent/planning.py +0 -145
  298. package/latticeai/core/agent/recovery.py +0 -157
  299. package/latticeai/core/agent/runtime.py +0 -210
  300. package/latticeai/core/agent/verification.py +0 -231
  301. package/latticeai/core/agent_eval.py +0 -739
  302. package/latticeai/core/agent_helpers.py +0 -493
  303. package/latticeai/core/agent_profiles.py +0 -110
  304. package/latticeai/core/agent_prompts.py +0 -171
  305. package/latticeai/core/agent_registry.py +0 -232
  306. package/latticeai/core/agent_state.py +0 -41
  307. package/latticeai/core/agent_trace.py +0 -104
  308. package/latticeai/core/artifact_ledger.py +0 -109
  309. package/latticeai/core/audit.py +0 -260
  310. package/latticeai/core/builtin_hooks.py +0 -105
  311. package/latticeai/core/context_builder.py +0 -394
  312. package/latticeai/core/document_generator.py +0 -103
  313. package/latticeai/core/enterprise.py +0 -157
  314. package/latticeai/core/enterprise_admin.py +0 -158
  315. package/latticeai/core/file_generation/__init__.py +0 -115
  316. package/latticeai/core/file_generation/bundles.py +0 -76
  317. package/latticeai/core/file_generation/extraction.py +0 -154
  318. package/latticeai/core/file_generation/inference.py +0 -235
  319. package/latticeai/core/file_generation/orchestration.py +0 -152
  320. package/latticeai/core/file_generation/prompting.py +0 -117
  321. package/latticeai/core/file_generation/repair.py +0 -114
  322. package/latticeai/core/file_generation/sanitize.py +0 -61
  323. package/latticeai/core/file_generation/validation.py +0 -201
  324. package/latticeai/core/graph_curator.py +0 -11
  325. package/latticeai/core/hooks.py +0 -11
  326. package/latticeai/core/invitations.py +0 -131
  327. package/latticeai/core/legacy_compatibility.py +0 -243
  328. package/latticeai/core/local_embeddings.py +0 -104
  329. package/latticeai/core/logging_safety.py +0 -46
  330. package/latticeai/core/marketplace.py +0 -293
  331. package/latticeai/core/mcp_catalog.py +0 -452
  332. package/latticeai/core/mcp_registry.py +0 -506
  333. package/latticeai/core/multi_agent.py +0 -11
  334. package/latticeai/core/network_boundary.py +0 -168
  335. package/latticeai/core/oidc.py +0 -208
  336. package/latticeai/core/plugins.py +0 -432
  337. package/latticeai/core/product_hardening.py +0 -218
  338. package/latticeai/core/project_sessions.py +0 -337
  339. package/latticeai/core/realtime.py +0 -238
  340. package/latticeai/core/run_explain.py +0 -423
  341. package/latticeai/core/run_store.py +0 -252
  342. package/latticeai/core/timezones.py +0 -80
  343. package/latticeai/core/workflow_engine.py +0 -11
  344. package/latticeai/core/workspace_computer_memory.py +0 -84
  345. package/latticeai/core/workspace_graph_trace.py +0 -161
  346. package/latticeai/core/workspace_indexing.py +0 -102
  347. package/latticeai/core/workspace_memory.py +0 -77
  348. package/latticeai/core/workspace_onboarding.py +0 -104
  349. package/latticeai/core/workspace_os.py +0 -988
  350. package/latticeai/core/workspace_os_constants.py +0 -126
  351. package/latticeai/core/workspace_os_state.py +0 -180
  352. package/latticeai/core/workspace_os_utils.py +0 -69
  353. package/latticeai/core/workspace_permissions.py +0 -101
  354. package/latticeai/core/workspace_plugins.py +0 -97
  355. package/latticeai/core/workspace_relationships.py +0 -99
  356. package/latticeai/core/workspace_reorganization.py +0 -335
  357. package/latticeai/core/workspace_review_items.py +0 -118
  358. package/latticeai/core/workspace_runs.py +0 -732
  359. package/latticeai/core/workspace_skills.py +0 -114
  360. package/latticeai/core/workspace_snapshots.py +0 -198
  361. package/latticeai/core/workspace_timeline.py +0 -110
  362. package/latticeai/integrations/__init__.py +0 -0
  363. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  364. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  365. package/latticeai/integrations/telegram_bot/config.py +0 -86
  366. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  367. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  368. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  369. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  370. package/latticeai/runtime/audit_runtime.py +0 -76
  371. package/latticeai/runtime/automation_runtime.py +0 -81
  372. package/latticeai/runtime/chat_wiring.py +0 -141
  373. package/latticeai/runtime/context_runtime.py +0 -66
  374. package/latticeai/runtime/feature_toggle_wiring.py +0 -163
  375. package/latticeai/runtime/history_runtime.py +0 -163
  376. package/latticeai/runtime/history_writer.py +0 -138
  377. package/latticeai/runtime/hooks_runtime.py +0 -77
  378. package/latticeai/runtime/model_wiring.py +0 -68
  379. package/latticeai/runtime/namespace_runtime.py +0 -163
  380. package/latticeai/runtime/network_boundary_wiring.py +0 -128
  381. package/latticeai/runtime/network_config_runtime.py +0 -56
  382. package/latticeai/runtime/permission_mode_wiring.py +0 -119
  383. package/latticeai/runtime/persistence_runtime.py +0 -159
  384. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  385. package/latticeai/runtime/review_wiring.py +0 -42
  386. package/latticeai/runtime/router_registration.py +0 -689
  387. package/latticeai/runtime/sso_config_runtime.py +0 -128
  388. package/latticeai/runtime/user_key_runtime.py +0 -106
  389. package/latticeai/runtime/web_runtime.py +0 -92
  390. package/latticeai/server_app.py +0 -51
  391. package/latticeai/services/app_context.py +0 -130
  392. package/latticeai/services/automation_execution.py +0 -266
  393. package/latticeai/services/automation_intelligence.py +0 -614
  394. package/latticeai/services/brain_automation.py +0 -191
  395. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  396. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  397. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  398. package/latticeai/services/brain_intelligence/constants.py +0 -47
  399. package/latticeai/services/brain_intelligence/digest.py +0 -258
  400. package/latticeai/services/brain_intelligence/health.py +0 -331
  401. package/latticeai/services/brain_intelligence/proposals.py +0 -264
  402. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  403. package/latticeai/services/brain_intelligence/service.py +0 -48
  404. package/latticeai/services/change_proposals.py +0 -505
  405. package/latticeai/services/chat_service.py +0 -243
  406. package/latticeai/services/chronicle.py +0 -557
  407. package/latticeai/services/cloud_egress_audit.py +0 -85
  408. package/latticeai/services/cloud_extraction.py +0 -129
  409. package/latticeai/services/cloud_streaming.py +0 -268
  410. package/latticeai/services/cloud_token_guard.py +0 -84
  411. package/latticeai/services/command_center.py +0 -548
  412. package/latticeai/services/evidence_actions.py +0 -255
  413. package/latticeai/services/feature_toggles.py +0 -502
  414. package/latticeai/services/folder_watch.py +0 -520
  415. package/latticeai/services/funnel_metrics.py +0 -307
  416. package/latticeai/services/hybrid_chat.py +0 -316
  417. package/latticeai/services/hybrid_context.py +0 -228
  418. package/latticeai/services/hybrid_policy.py +0 -178
  419. package/latticeai/services/ingestion.py +0 -11
  420. package/latticeai/services/interop_bridges.py +0 -978
  421. package/latticeai/services/kg_portability.py +0 -11
  422. package/latticeai/services/local_knowledge.py +0 -465
  423. package/latticeai/services/memory_service/__init__.py +0 -52
  424. package/latticeai/services/memory_service/_contract.py +0 -100
  425. package/latticeai/services/memory_service/brief.py +0 -431
  426. package/latticeai/services/memory_service/constants.py +0 -57
  427. package/latticeai/services/memory_service/maintenance.py +0 -138
  428. package/latticeai/services/memory_service/manager.py +0 -186
  429. package/latticeai/services/memory_service/proof.py +0 -136
  430. package/latticeai/services/memory_service/recall.py +0 -225
  431. package/latticeai/services/memory_service/service.py +0 -48
  432. package/latticeai/services/memory_service/stores.py +0 -110
  433. package/latticeai/services/model_recommendation.py +0 -224
  434. package/latticeai/services/model_runtime/cloud.py +0 -87
  435. package/latticeai/services/multimodal_streaming.py +0 -129
  436. package/latticeai/services/network_boundary_service.py +0 -154
  437. package/latticeai/services/obsidian_bridge.py +0 -609
  438. package/latticeai/services/openai_compatible_adapter.py +0 -101
  439. package/latticeai/services/permission_mode_service.py +0 -170
  440. package/latticeai/services/platform_runtime.py +0 -366
  441. package/latticeai/services/review_queue.py +0 -380
  442. package/latticeai/services/router_context.py +0 -59
  443. package/latticeai/services/run_executor.py +0 -387
  444. package/latticeai/services/self_model_service.py +0 -171
  445. package/latticeai/services/setup_detection.py +0 -81
  446. package/latticeai/services/triggers.py +0 -378
  447. package/latticeai/services/upload_service.py +0 -185
  448. package/latticeai/services/workspace_service.py +0 -171
  449. package/latticeai/setup/__init__.py +0 -25
  450. package/latticeai/setup/auto_setup.py +0 -857
  451. package/latticeai/setup/demo_corpus.py +0 -98
  452. package/latticeai/setup/wizard/__init__.py +0 -126
  453. package/latticeai/setup/wizard/catalog.py +0 -172
  454. package/latticeai/setup/wizard/detect.py +0 -323
  455. package/latticeai/setup/wizard/install.py +0 -348
  456. package/latticeai/setup/wizard/paths.py +0 -168
  457. package/latticeai/setup/wizard/plans.py +0 -74
  458. package/latticeai/setup/wizard/recommend.py +0 -320
  459. package/scripts/bench_agent_smoke.py +0 -409
  460. package/scripts/bench_models.py +0 -540
  461. package/scripts/bench_vector_index.py +0 -295
  462. package/scripts/funnel_soft_gate.py +0 -192
  463. package/scripts/generate_agent_loop_fixtures.py +0 -907
  464. package/scripts/generate_rust_parity_fixtures.py +0 -1000
  465. package/scripts/measure_brain_home_fill.mjs +0 -141
  466. package/scripts/migrate_brain_storage.py +0 -57
  467. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  468. package/scripts/profile_kg.py +0 -355
  469. package/server.py +0 -30
  470. package/static/app/assets/arrow-left-BHYmOTWU.js +0 -1
  471. package/static/app/assets/circle-check-CLeWJr67.js +0 -1
  472. package/static/app/assets/index-DxmOfNRi.css +0 -2
  473. package/static/app/assets/search-BgSVX6NG.js +0 -1
@@ -1,988 +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_reorganization import plan_reorganization
57
- from .workspace_review_items import WorkspaceReviewItems
58
- from .workspace_runs import WorkspaceRuns
59
- from .workspace_skills import WorkspaceSkills
60
- from .workspace_snapshots import WorkspaceSnapshots
61
- from .workspace_timeline import WorkspaceTimeline
62
-
63
- __all__ = [
64
- "WORKSPACE_OS_VERSION",
65
- "WORKSPACE_TYPES",
66
- "DEFAULT_WORKSPACE_ID",
67
- "WORKSPACE_ROLES",
68
- "WORKSPACE_PERMISSIONS",
69
- "ROLE_PERMISSIONS",
70
- "WORKSPACE_AREAS",
71
- "ONBOARDING_STEPS",
72
- "MEMORY_KINDS",
73
- "EXECUTION_EVENT_TYPES",
74
- "WorkspaceOSStore",
75
- "remove_skill_directory",
76
- ]
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
- class WorkspaceOSStore:
91
- """Local-first state store for Workspace OS APIs."""
92
-
93
- def __init__(self, data_dir: Path | str, *, event_sink: Optional[Callable[[Dict[str, Any]], Any]] = None):
94
- self.data_dir = Path(data_dir).expanduser()
95
- self.state_path = self.data_dir / "workspace_os.json"
96
- self.sqlite_path = self.data_dir / "knowledge_graph.sqlite"
97
- self.snapshots_dir = self.data_dir / "workspace_snapshots"
98
- self.exports_dir = self.data_dir / "workspace_exports"
99
- self.data_dir.mkdir(parents=True, exist_ok=True)
100
- self.snapshots_dir.mkdir(parents=True, exist_ok=True)
101
- self.exports_dir.mkdir(parents=True, exist_ok=True)
102
- # Optional realtime hook: fired on every timeline event so the Realtime
103
- # bus receives all workspace activity without per-call wiring.
104
- # Defaults to None → zero behavior change for existing callers/tests.
105
- self.event_sink = event_sink
106
- # Composed managers (extracted to shrink god class per architecture rules).
107
- self.permissions = WorkspacePermissionManager(self)
108
- self._timeline = WorkspaceTimeline(self)
109
- self.plugins = WorkspacePluginManager(self)
110
- self.memory = WorkspaceMemory(self)
111
- self.snapshots = WorkspaceSnapshots(self)
112
- self.graph_trace = WorkspaceGraphTrace(self)
113
- self.runs = WorkspaceRuns(self)
114
- self.review_items = WorkspaceReviewItems(self)
115
- self.skills = WorkspaceSkills(self)
116
- self.indexing = WorkspaceIndexing(self)
117
- self.relationships = WorkspaceRelationships(self)
118
- self.computer_memory = WorkspaceComputerMemory(self)
119
- self.onboarding = WorkspaceOnboarding(self)
120
-
121
- @contextmanager
122
- def _connect_state_db(self) -> Iterator[sqlite3.Connection]:
123
- """Transactional connection that is closed when the block exits.
124
-
125
- ``with sqlite3.connect(...)`` commits but never closes; see
126
- :meth:`lattice_brain.storage.base.StorageEngine.session`.
127
- """
128
- conn = sqlite3.connect(self.sqlite_path)
129
- conn.execute("PRAGMA journal_mode=WAL")
130
- conn.execute(
131
- "CREATE TABLE IF NOT EXISTS workspace_os_state ("
132
- "id TEXT PRIMARY KEY, state_json TEXT NOT NULL, updated_at TEXT NOT NULL)"
133
- )
134
- conn.execute(
135
- "CREATE TABLE IF NOT EXISTS workspace_os_meta ("
136
- "key TEXT PRIMARY KEY, value TEXT NOT NULL)"
137
- )
138
- try:
139
- with conn:
140
- yield conn
141
- finally:
142
- conn.close()
143
-
144
- # ── moved to workspace_os_state.py (10.2.0) ───────────────────────────────
145
- # Kept as delegators: `_default_state` and `_migrate_workspaces` are called
146
- # by name in tests and by subclasses, and `_new_workspace_record` is used
147
- # across the manager classes. The behaviour lives in one place now; these
148
- # keep every existing call site working.
149
- @staticmethod
150
- def _new_workspace_record(**kwargs) -> Dict[str, Any]:
151
- return new_workspace_record(**kwargs)
152
-
153
- def _migrate_workspaces(self, state: Dict[str, Any]) -> Dict[str, Any]:
154
- return migrate_workspaces(state)
155
-
156
- def _default_state(self) -> Dict[str, Any]:
157
- return default_state()
158
-
159
- def _load_sqlite_state(self) -> Optional[Dict[str, Any]]:
160
- try:
161
- with self._connect_state_db() as conn:
162
- row = conn.execute(
163
- "SELECT state_json FROM workspace_os_state WHERE id='current'"
164
- ).fetchone()
165
- if not row:
166
- return None
167
- data = json.loads(row[0])
168
- return data if isinstance(data, dict) else None
169
- except Exception:
170
- return None
171
-
172
- def _save_sqlite_state(self, state: Dict[str, Any]) -> None:
173
- payload = json.dumps(state, ensure_ascii=False)
174
- with self._connect_state_db() as conn:
175
- conn.execute(
176
- "INSERT OR REPLACE INTO workspace_os_state(id, state_json, updated_at) VALUES('current', ?, ?)",
177
- (payload, state.get("updated_at") or _now()),
178
- )
179
-
180
- def _import_json_state_once(self, default: Dict[str, Any]) -> Dict[str, Any]:
181
- if not self.state_path.exists():
182
- return default
183
- try:
184
- loaded = json.loads(self.state_path.read_text(encoding="utf-8"))
185
- if not isinstance(loaded, dict):
186
- return default
187
- except Exception:
188
- return default
189
- try:
190
- backup = self.state_path.with_name(
191
- f"{self.state_path.name}.pre-sqlite.{_now().replace(':', '-')}.json"
192
- )
193
- if not any(self.state_path.parent.glob(f"{self.state_path.name}.pre-sqlite.*.json")):
194
- shutil.copy2(self.state_path, backup)
195
- except Exception:
196
- quiet()
197
- return _deep_merge(default, loaded)
198
-
199
-
200
-
201
- def migrate_workspace_identities(self, email_to_id: Dict[str, str]) -> int:
202
- """Rewrite workspace membership identities from legacy emails to UUIDs.
203
-
204
- The migration is additive and in-place: workspace records, memberships,
205
- and owner fields keep their shape, only identity string values change.
206
- """
207
- if not email_to_id:
208
- return 0
209
- normalized = {str(email).strip().lower(): user_id for email, user_id in email_to_id.items() if user_id}
210
- state = self.load_state()
211
- changed = 0
212
- for ws in (state.get("workspaces") or {}).values():
213
- owner = str(ws.get("owner_user_id") or "").strip().lower()
214
- if owner in normalized and ws.get("owner_user_id") != normalized[owner]:
215
- ws["owner_user_id"] = normalized[owner]
216
- changed += 1
217
- for member in _listify(ws.get("members")):
218
- member_id = str(member.get("user_id") or "").strip().lower()
219
- if member_id in normalized and member.get("user_id") != normalized[member_id]:
220
- member["user_id"] = normalized[member_id]
221
- member["updated_at"] = _now()
222
- changed += 1
223
- members = _listify(ws.get("members"))
224
- deduped = []
225
- seen_members = set()
226
- for member in members:
227
- member_id = member.get("user_id")
228
- if member_id and member_id in seen_members:
229
- changed += 1
230
- continue
231
- if member_id:
232
- seen_members.add(member_id)
233
- deduped.append(member)
234
- if len(deduped) != len(members):
235
- ws["members"] = deduped
236
- if changed:
237
- state["updated_at"] = _now()
238
- self.save_state(state)
239
- self.record_timeline_event("workspace", "identity_uuid_migrated", {"records": changed})
240
- return changed
241
-
242
-
243
- def load_state(self) -> Dict[str, Any]:
244
- default = self._default_state()
245
- loaded = self._load_sqlite_state()
246
- imported = loaded is None
247
- if loaded is None:
248
- loaded = self._import_json_state_once(default)
249
- state = _deep_merge(default, loaded)
250
- state["version"] = WORKSPACE_OS_VERSION
251
- self._migrate_workspaces(state)
252
- if imported:
253
- self.save_state(state)
254
- return state
255
-
256
- def save_state(self, state: Dict[str, Any]) -> Dict[str, Any]:
257
- state["version"] = WORKSPACE_OS_VERSION
258
- state["updated_at"] = _now()
259
- self._save_sqlite_state(state)
260
- _atomic_write_json(self.state_path, state)
261
- return state
262
-
263
- def record_timeline_event(self, area: str, event_type: str, payload: Dict[str, Any], workspace_id: Optional[str] = None) -> Dict[str, Any]:
264
- return self._timeline.record_timeline_event(area, event_type, payload, workspace_id=workspace_id)
265
-
266
- def _emit_execution_event(
267
- self,
268
- *,
269
- area: str,
270
- event_type: str,
271
- payload: Dict[str, Any],
272
- workspace_id: Optional[str],
273
- ) -> None:
274
- """Best-effort execution observability event for the realtime feed."""
275
- if event_type not in EXECUTION_EVENT_TYPES:
276
- return
277
- try:
278
- self.record_timeline_event(area, event_type, payload, workspace_id=workspace_id)
279
- except Exception:
280
- quiet()
281
-
282
- def _emit_replayable_timeline_events(
283
- self,
284
- *,
285
- area: str,
286
- run_id: str,
287
- timeline: List[Dict[str, Any]],
288
- workspace_id: Optional[str],
289
- ) -> None:
290
- for index, item in enumerate(timeline or []):
291
- event_type = item.get("event") or item.get("event_type")
292
- if event_type in EXECUTION_EVENT_TYPES:
293
- payload = {k: v for k, v in item.items() if k not in {"context_packet"}}
294
- payload["run_id"] = run_id
295
- payload["timeline_index"] = index
296
- self._emit_execution_event(area=area, event_type=event_type, payload=payload, workspace_id=workspace_id)
297
-
298
- def summary(self) -> Dict[str, Any]:
299
- state = self.load_state()
300
- return {
301
- "version": WORKSPACE_OS_VERSION,
302
- "identity": state.get("identity"),
303
- "active_workspace": state.get("active_workspace"),
304
- # The raw workspace registry (with member lists) must not leak to
305
- # non-members; WorkspaceService.summary() adds a membership-filtered
306
- # "workspace_registry" instead.
307
- "workspace_count": len(state.get("workspaces") or {}),
308
- "navigation": list(WORKSPACE_AREAS),
309
- "feature_flags": state.get("feature_flags"),
310
- "updated_at": state.get("updated_at"),
311
- "counts": {
312
- "snapshots": len(_listify(state.get("snapshots"))),
313
- "traces": len(_listify(state.get("traces"))),
314
- "memories": len(_listify(state.get("memories"))),
315
- "memory_snapshots": len(_listify(state.get("memory_snapshots"))),
316
- "agent_runs": len(_listify(state.get("agent_runs"))),
317
- "handoffs": len(_listify(state.get("handoffs"))),
318
- "workflows": len(_listify(state.get("workflows"))),
319
- "workflow_runs": len(_listify(state.get("workflow_runs"))),
320
- "skills": len(state.get("skill_registry") or {}),
321
- "plugins": len(state.get("plugin_registry") or {}),
322
- "templates": len(state.get("template_registry") or {}),
323
- "timeline": len(_listify(state.get("timeline"))),
324
- },
325
- "onboarding": state.get("onboarding"),
326
- "storage": {
327
- "state_path": str(self.state_path),
328
- "snapshots_dir": str(self.snapshots_dir),
329
- "exports_dir": str(self.exports_dir),
330
- },
331
- }
332
-
333
- # ------------------------------------------------------------------
334
- # Organization workspaces, membership, and roles
335
- # ------------------------------------------------------------------
336
-
337
- def _active_workspace_id(self, state: Optional[Dict[str, Any]] = None) -> str:
338
- state = state or self.load_state()
339
- active = state.get("active_workspace") or DEFAULT_WORKSPACE_ID
340
- if active not in (state.get("workspaces") or {}):
341
- return DEFAULT_WORKSPACE_ID
342
- return active
343
-
344
- def _resolve_scope(self, workspace_id: Optional[str], state: Optional[Dict[str, Any]] = None) -> str:
345
- """Resolve the workspace a write should be tagged with.
346
-
347
- ``None`` falls back to the active workspace (Personal by default), so
348
- legacy callers keep writing to the Personal workspace unchanged.
349
- """
350
- if workspace_id:
351
- return str(workspace_id)
352
- return self._active_workspace_id(state)
353
-
354
- @staticmethod
355
- def _record_workspace(record: Dict[str, Any]) -> str:
356
- """Workspace a stored record belongs to (legacy records map to Personal)."""
357
- return str(record.get("workspace_id") or DEFAULT_WORKSPACE_ID)
358
-
359
- def _scoped(self, records: List[Dict[str, Any]], workspace_id: Optional[str]) -> List[Dict[str, Any]]:
360
- if not workspace_id:
361
- return records
362
- target = str(workspace_id)
363
- return [item for item in records if self._record_workspace(item) == target]
364
-
365
- def list_workspaces(self, user_id: Optional[str] = None) -> Dict[str, Any]:
366
- state = self.load_state()
367
- workspaces = state.get("workspaces") or {}
368
- items = []
369
- for ws in workspaces.values():
370
- if user_id and ws.get("type") == "organization":
371
- role = self._member_role(ws, user_id)
372
- if role is None:
373
- continue
374
- items.append(self._workspace_public(ws, user_id))
375
- items.sort(key=lambda w: (w.get("type") != "personal", w.get("created_at") or ""))
376
- return {
377
- "active_workspace": self._active_workspace_id(state),
378
- "workspaces": items,
379
- "roles": list(WORKSPACE_ROLES),
380
- "permissions": {role: sorted(perms) for role, perms in ROLE_PERMISSIONS.items()},
381
- }
382
-
383
- def _workspace_public(self, ws: Dict[str, Any], user_id: Optional[str] = None) -> Dict[str, Any]:
384
- return {
385
- "workspace_id": ws.get("workspace_id") or ws.get("id"),
386
- "id": ws.get("workspace_id") or ws.get("id"),
387
- "name": ws.get("name"),
388
- "type": ws.get("type"),
389
- "owner_user_id": ws.get("owner_user_id"),
390
- "status": ws.get("status", "active"),
391
- "member_count": len(_listify(ws.get("members"))),
392
- "members": _listify(ws.get("members")),
393
- "settings": ws.get("settings") or {},
394
- "created_at": ws.get("created_at"),
395
- "updated_at": ws.get("updated_at"),
396
- "your_role": self._member_role(ws, user_id) if user_id else ("owner" if ws.get("type") == "personal" else None),
397
- }
398
-
399
- def get_workspace(self, workspace_id: str, user_id: Optional[str] = None) -> Dict[str, Any]:
400
- state = self.load_state()
401
- ws = (state.get("workspaces") or {}).get(workspace_id)
402
- if not ws:
403
- raise FileNotFoundError(workspace_id)
404
- return self._workspace_public(ws, user_id)
405
-
406
- def create_organization_workspace(
407
- self,
408
- *,
409
- name: str,
410
- owner_user_id: Optional[str],
411
- settings: Optional[Dict[str, Any]] = None,
412
- ) -> Dict[str, Any]:
413
- if not str(name or "").strip():
414
- raise ValueError("workspace name is required")
415
- state = self.load_state()
416
- workspaces = state.setdefault("workspaces", {})
417
- base = _safe_slug(f"org-{name}")
418
- workspace_id = base
419
- suffix = 2
420
- while workspace_id in workspaces:
421
- workspace_id = f"{base}-{suffix}"
422
- suffix += 1
423
- record = self._new_workspace_record(
424
- workspace_id=workspace_id,
425
- name=name.strip(),
426
- workspace_type="organization",
427
- owner_user_id=owner_user_id,
428
- settings=settings or {},
429
- )
430
- workspaces[workspace_id] = record
431
- self.save_state(state)
432
- self.record_timeline_event("workspace", "workspace_created", {"workspace_id": workspace_id, "type": "organization"})
433
- return self._workspace_public(record, owner_user_id)
434
-
435
- @staticmethod
436
- def _member_role(ws: Dict[str, Any], user_id: Optional[str]) -> Optional[str]:
437
- # Delegated to extracted module (kept for any internal direct calls).
438
- return _member_role(ws, user_id)
439
-
440
- def get_member_role(self, workspace_id: str, user_id: Optional[str]) -> Optional[str]:
441
- return self.permissions.get_member_role(workspace_id, user_id)
442
-
443
- def has_permission(self, workspace_id: str, user_id: Optional[str], permission: str) -> bool:
444
- return self.permissions.has_permission(workspace_id, user_id, permission)
445
-
446
- def _require_permission(self, ws: Dict[str, Any], actor: Optional[str], permission: str) -> None:
447
- self.permissions.require_permission(ws, actor, permission)
448
-
449
- def _load_org(self, state: Dict[str, Any], workspace_id: str) -> Dict[str, Any]:
450
- ws = (state.get("workspaces") or {}).get(workspace_id)
451
- if not ws:
452
- raise FileNotFoundError(workspace_id)
453
- if ws.get("type") != "organization":
454
- raise ValueError("operation only valid for organization workspaces")
455
- return ws
456
-
457
- def update_workspace(
458
- self,
459
- workspace_id: str,
460
- *,
461
- name: Optional[str] = None,
462
- settings: Optional[Dict[str, Any]] = None,
463
- actor: Optional[str] = None,
464
- ) -> Dict[str, Any]:
465
- state = self.load_state()
466
- ws = self._load_org(state, workspace_id)
467
- self._require_permission(ws, actor, "manage_workspace")
468
- if name is not None and str(name).strip():
469
- ws["name"] = str(name).strip()
470
- if settings is not None:
471
- ws["settings"] = {**(ws.get("settings") or {}), **settings}
472
- ws["updated_at"] = _now()
473
- self.save_state(state)
474
- self.record_timeline_event("workspace", "workspace_updated", {"workspace_id": workspace_id})
475
- return self._workspace_public(ws, actor)
476
-
477
- def archive_workspace(self, workspace_id: str, *, actor: Optional[str] = None) -> Dict[str, Any]:
478
- """Soft-archive an organization workspace. Data is never deleted."""
479
- state = self.load_state()
480
- ws = self._load_org(state, workspace_id)
481
- self._require_permission(ws, actor, "manage_workspace")
482
- ws["status"] = "archived"
483
- ws["updated_at"] = _now()
484
- if state.get("active_workspace") == workspace_id:
485
- state["active_workspace"] = DEFAULT_WORKSPACE_ID
486
- self.save_state(state)
487
- self.record_timeline_event("workspace", "workspace_archived", {"workspace_id": workspace_id})
488
- return self._workspace_public(ws, actor)
489
-
490
- def add_member(self, workspace_id: str, *, user_id: str, role: str = "member", actor: Optional[str] = None) -> Dict[str, Any]:
491
- return self.permissions.add_member(workspace_id, user_id=user_id, role=role, actor=actor)
492
-
493
- def update_member_role(self, workspace_id: str, *, user_id: str, role: str, actor: Optional[str] = None) -> Dict[str, Any]:
494
- # delegate or keep simple for now (full impl in permissions if expanded)
495
- if role not in WORKSPACE_ROLES:
496
- raise ValueError(f"unknown role: {role}")
497
- state = self.load_state()
498
- ws = self._load_org(state, workspace_id)
499
- self._require_permission(ws, actor, "manage_members")
500
- if user_id == ws.get("owner_user_id") and role != "owner":
501
- raise ValueError("cannot demote the workspace owner")
502
- member = next((m for m in _listify(ws.get("members")) if m.get("user_id") == user_id), None)
503
- if not member:
504
- raise FileNotFoundError(user_id)
505
- member["role"] = role
506
- member["updated_at"] = _now()
507
- ws["updated_at"] = _now()
508
- self.save_state(state)
509
- self.record_timeline_event("workspace", "member_role_updated", {"workspace_id": workspace_id, "user_id": user_id, "role": role})
510
- return self._workspace_public(ws, actor)
511
-
512
- def remove_member(self, workspace_id: str, *, user_id: str, actor: Optional[str] = None) -> Dict[str, Any]:
513
- state = self.load_state()
514
- ws = self._load_org(state, workspace_id)
515
- self._require_permission(ws, actor, "manage_members")
516
- if user_id == ws.get("owner_user_id"):
517
- raise ValueError("cannot remove the workspace owner")
518
- members = _listify(ws.get("members"))
519
- kept = [m for m in members if m.get("user_id") != user_id]
520
- if len(kept) == len(members):
521
- raise FileNotFoundError(user_id)
522
- ws["members"] = kept
523
- ws["updated_at"] = _now()
524
- self.save_state(state)
525
- self.record_timeline_event("workspace", "member_removed", {"workspace_id": workspace_id, "user_id": user_id})
526
- return self._workspace_public(ws, actor)
527
-
528
- def set_active_workspace(self, workspace_id: str, user_id: Optional[str] = None) -> Dict[str, Any]:
529
- state = self.load_state()
530
- ws = (state.get("workspaces") or {}).get(workspace_id)
531
- if not ws:
532
- raise FileNotFoundError(workspace_id)
533
- if ws.get("type") == "organization" and self._member_role(ws, user_id) is None:
534
- raise PermissionError(f"'{user_id or 'anonymous'}' is not a member of '{workspace_id}'")
535
- state["active_workspace"] = workspace_id
536
- self.save_state(state)
537
- self.record_timeline_event("workspace", "workspace_activated", {"workspace_id": workspace_id})
538
- return self._workspace_public(ws, user_id)
539
-
540
- def workspace_summary(self, workspace_id: str, user_id: Optional[str] = None) -> Dict[str, Any]:
541
- state = self.load_state()
542
- ws = (state.get("workspaces") or {}).get(workspace_id)
543
- if not ws:
544
- raise FileNotFoundError(workspace_id)
545
- public = self._workspace_public(ws, user_id)
546
- public["counts"] = {
547
- "snapshots": len(self._scoped(_listify(state.get("snapshots")), workspace_id)),
548
- "memories": len(self._scoped(_listify(state.get("memories")), workspace_id)),
549
- "memory_snapshots": len(self._scoped(_listify(state.get("memory_snapshots")), workspace_id)),
550
- "agent_runs": len(self._scoped(_listify(state.get("agent_runs")), workspace_id)),
551
- "handoffs": len(self._scoped(_listify(state.get("handoffs")), workspace_id)),
552
- "workflows": len(self._scoped(_listify(state.get("workflows")), workspace_id)),
553
- "workflow_runs": len(self._scoped(_listify(state.get("workflow_runs")), workspace_id)),
554
- "traces": len(self._scoped(_listify(state.get("traces")), workspace_id)),
555
- "timeline": len(self._scoped(_listify(state.get("timeline")), workspace_id)),
556
- }
557
- return public
558
-
559
- # ------------------------------------------------------------------
560
- # Onboarding
561
- # ------------------------------------------------------------------
562
-
563
- def onboarding_status(self, users: Optional[Dict[str, Any]] = None, graph_stats: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
564
- return self.onboarding.status(users=users, graph_stats=graph_stats)
565
-
566
- def update_onboarding_step(
567
- self,
568
- step: str,
569
- *,
570
- status: str = "complete",
571
- data: Optional[Dict[str, Any]] = None,
572
- error: str = "",
573
- user_email: Optional[str] = None,
574
- ) -> Dict[str, Any]:
575
- return self.onboarding.update_step(
576
- step, status=status, data=data, error=error, user_email=user_email
577
- )
578
-
579
- def complete_onboarding(self, data: Optional[Dict[str, Any]] = None, user_email: Optional[str] = None) -> Dict[str, Any]:
580
- return self.onboarding.complete(data=data, user_email=user_email)
581
-
582
- # ------------------------------------------------------------------
583
- # Graph answer traces
584
- # ------------------------------------------------------------------
585
-
586
- def build_graph_trace(
587
- self,
588
- question: str,
589
- graph: Any,
590
- context: str = "",
591
- *,
592
- limit: int = 8,
593
- allowed_workspaces=None,
594
- ) -> Dict[str, Any]:
595
- return self.graph_trace.build_graph_trace(
596
- question,
597
- graph,
598
- context,
599
- limit=limit,
600
- allowed_workspaces=allowed_workspaces,
601
- )
602
-
603
- def record_trace(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
604
- return self.graph_trace.record_trace(*args, **kwargs)
605
-
606
- def list_traces(self, conversation_id: Optional[str] = None, limit: int = 50, workspace_id: Optional[str] = None) -> Dict[str, Any]:
607
- return self.graph_trace.list_traces(conversation_id=conversation_id, limit=limit, workspace_id=workspace_id)
608
-
609
- # ------------------------------------------------------------------
610
- # Indexing dashboard
611
- # ------------------------------------------------------------------
612
-
613
- def build_indexing_dashboard(self, graph: Any, watcher_status: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
614
- return self.indexing.build_dashboard(graph, watcher_status)
615
-
616
- def pause_indexing(self, graph: Any, source_id: str, watcher: Any = None) -> Dict[str, Any]:
617
- return self.indexing.pause(graph, source_id, watcher)
618
-
619
- def resume_indexing(self, graph: Any, source_id: str, watcher: Any = None) -> Dict[str, Any]:
620
- return self.indexing.resume(graph, source_id, watcher)
621
-
622
- def remove_index_source(self, graph: Any, source_id: str, watcher: Any = None) -> Dict[str, Any]:
623
- return self.indexing.remove_source(graph, source_id, watcher)
624
-
625
- # ------------------------------------------------------------------
626
- # Snapshots, Time Machine, and diffs
627
- # ------------------------------------------------------------------
628
-
629
- def create_snapshot(
630
- self,
631
- *,
632
- name: str,
633
- graph: Any,
634
- history: Iterable[Dict[str, Any]],
635
- settings: Dict[str, Any],
636
- models: Dict[str, Any],
637
- workspace_id: Optional[str] = None,
638
- ) -> Dict[str, Any]:
639
- return self.snapshots.create_snapshot(name=name, graph=graph, history=history, settings=settings, models=models, workspace_id=workspace_id)
640
-
641
- def list_snapshots(self, workspace_id: Optional[str] = None) -> Dict[str, Any]:
642
- return self.snapshots.list_snapshots(workspace_id=workspace_id)
643
-
644
- def get_snapshot(self, snapshot_id: str) -> Dict[str, Any]:
645
- return self.snapshots.get_snapshot(snapshot_id)
646
-
647
- def snapshot_view(self, snapshot_id: str, area: str) -> Dict[str, Any]:
648
- return self.snapshots.snapshot_view(snapshot_id, area)
649
-
650
- def export_snapshot(self, snapshot_id: str) -> Dict[str, Any]:
651
- return self.snapshots.export_snapshot(snapshot_id)
652
-
653
- def restore_snapshot(
654
- self,
655
- snapshot_id: str,
656
- *,
657
- graph: Any,
658
- workspace_id: Optional[str] = None,
659
- user_email: Optional[str] = None,
660
- ) -> Dict[str, Any]:
661
- return self.snapshots.restore_snapshot(snapshot_id, graph=graph, workspace_id=workspace_id, user_email=user_email)
662
-
663
- def compare_snapshots(self, before_id: str, after_id: str) -> Dict[str, Any]:
664
- return self.snapshots.compare_snapshots(before_id, after_id)
665
-
666
- # ------------------------------------------------------------------
667
- # Personal memory
668
- # ------------------------------------------------------------------
669
-
670
- def upsert_memory(
671
- self,
672
- *,
673
- kind: str,
674
- content: str,
675
- user_email: Optional[str],
676
- tags: Optional[List[str]] = None,
677
- memory_id: Optional[str] = None,
678
- metadata: Optional[Dict[str, Any]] = None,
679
- graph: Any = None,
680
- workspace_id: Optional[str] = None,
681
- ) -> Dict[str, Any]:
682
- return self.memory.upsert_memory(
683
- kind=kind, content=content, user_email=user_email, tags=tags,
684
- memory_id=memory_id, metadata=metadata, graph=graph, workspace_id=workspace_id
685
- )
686
-
687
- def list_memories(self, user_email: Optional[str] = None, kind: Optional[str] = None, workspace_id: Optional[str] = None) -> Dict[str, Any]:
688
- return self.memory.list_memories(user_email=user_email, kind=kind, workspace_id=workspace_id)
689
-
690
- def search_memories(self, query: str, user_email: Optional[str] = None, limit: int = 20, workspace_id: Optional[str] = None) -> Dict[str, Any]:
691
- q = str(query or "").lower().strip()
692
- memories = self.list_memories(user_email=user_email, workspace_id=workspace_id).get("memories", [])
693
- if q:
694
- memories = [
695
- item for item in memories
696
- if q in str(item.get("content") or "").lower()
697
- or q in " ".join(item.get("tags") or []).lower()
698
- or q in str(item.get("kind") or "").lower()
699
- ]
700
- return {"query": query, "memories": memories[: max(1, min(limit, 100))]}
701
-
702
- def get_memory(self, memory_id: str) -> Dict[str, Any]:
703
- record = next(
704
- (item for item in _listify(self.load_state().get("memories")) if item.get("id") == memory_id),
705
- None,
706
- )
707
- if record is None:
708
- raise FileNotFoundError(memory_id)
709
- return record
710
-
711
- def delete_memory(self, memory_id: str) -> Dict[str, Any]:
712
- state = self.load_state()
713
- memories = _listify(state.get("memories"))
714
- target = next((item for item in memories if item.get("id") == memory_id), None)
715
- if target is None:
716
- raise FileNotFoundError(memory_id)
717
- state["memories"] = [item for item in memories if item.get("id") != memory_id]
718
- self.save_state(state)
719
- self.record_timeline_event(
720
- "memory", "memory_deleted", {"memory_id": memory_id}, workspace_id=target.get("workspace_id")
721
- )
722
- return {"status": "ok", "memory_id": memory_id}
723
-
724
- def create_memory_snapshot(
725
- self,
726
- *,
727
- label: str = "memory snapshot",
728
- user_email: Optional[str] = None,
729
- workspace_id: Optional[str] = None,
730
- memory_ids: Optional[List[str]] = None,
731
- reason: str = "",
732
- ) -> Dict[str, Any]:
733
- """Persist a replayable point-in-time memory view without mutation."""
734
- state = self.load_state()
735
- scope = self._resolve_scope(workspace_id, state)
736
- memories = self._scoped(_listify(state.get("memories")), scope)
737
- if user_email:
738
- memories = [item for item in memories if item.get("user_email") in {None, user_email}]
739
- if memory_ids:
740
- allowed = set(memory_ids)
741
- memories = [item for item in memories if item.get("id") in allowed]
742
- snapshot = {
743
- "id": f"memory-snapshot-{_json_hash([label, scope, memories, _now()])[:16]}",
744
- "label": label,
745
- "reason": reason,
746
- "workspace_id": scope,
747
- "user_email": user_email,
748
- "memory_count": len(memories),
749
- "memories": memories,
750
- "created_at": _now(),
751
- }
752
- state.setdefault("memory_snapshots", []).append(snapshot)
753
- self.save_state(state)
754
- self.record_timeline_event("memory", "memory_snapshot", {"snapshot_id": snapshot["id"], "memory_count": len(memories)}, workspace_id=scope)
755
- return snapshot
756
-
757
- def list_memory_snapshots(self, workspace_id: Optional[str] = None, limit: int = 50) -> Dict[str, Any]:
758
- snapshots = self._scoped(_listify(self.load_state().get("memory_snapshots")), workspace_id)
759
- return {"snapshots": list(reversed(snapshots[-max(1, min(limit, 200)):]))}
760
-
761
- # ------------------------------------------------------------------
762
- # Agent and workflow graph
763
- # ------------------------------------------------------------------
764
-
765
- def list_agents(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
766
- return self.runs.list_agents(*args, **kwargs)
767
-
768
- def record_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
769
- return self.runs.record_agent_run(*args, **kwargs)
770
-
771
- def update_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
772
- return self.runs.update_agent_run(*args, **kwargs)
773
-
774
- def get_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
775
- return self.runs.get_agent_run(*args, **kwargs)
776
-
777
- def list_handoffs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
778
- return self.runs.list_handoffs(*args, **kwargs)
779
-
780
- def create_workflow(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
781
- return self.runs.create_workflow(*args, **kwargs)
782
-
783
- def record_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
784
- return self.runs.record_workflow_run(*args, **kwargs)
785
-
786
- def update_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
787
- return self.runs.update_workflow_run(*args, **kwargs)
788
-
789
- def list_workflow_runs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
790
- return self.runs.list_workflow_runs(*args, **kwargs)
791
-
792
- def list_combined_runs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
793
- return self.runs.list_combined_runs(*args, **kwargs)
794
-
795
- def mark_workflow_run_resolved(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
796
- return self.runs.mark_workflow_run_resolved(*args, **kwargs)
797
-
798
- def get_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
799
- return self.runs.get_workflow_run(*args, **kwargs)
800
-
801
- def reconcile_interrupted_runs(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
802
- return self.runs.reconcile_interrupted_runs(*args, **kwargs)
803
-
804
- @staticmethod
805
- def _replay_frames(run: Dict[str, Any], *, kind: str) -> List[Dict[str, Any]]:
806
- return WorkspaceRuns._replay_frames(run, kind=kind)
807
-
808
- def replay_agent_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
809
- return self.runs.replay_agent_run(*args, **kwargs)
810
-
811
- def replay_workflow_run(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
812
- return self.runs.replay_workflow_run(*args, **kwargs)
813
-
814
- def get_workflow(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
815
- return self.runs.get_workflow(*args, **kwargs)
816
-
817
- def update_workflow_definition(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
818
- return self.runs.update_workflow_definition(*args, **kwargs)
819
-
820
- def list_workflows(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
821
- return self.runs.list_workflows(*args, **kwargs)
822
-
823
- def record_workflow_event(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
824
- return self.runs.record_workflow_event(*args, **kwargs)
825
-
826
- # ── review queue (5.6.0) ─────────────────────────────────────────────
827
- # Workspace-scoped suggestion inbox. Persistence moved to
828
- # ``workspace_review_items.WorkspaceReviewItems`` (v9.9.6); the transition
829
- # policy still lives in ReviewQueueService (services/review_queue.py).
830
-
831
- def create_review_item(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
832
- return self.review_items.create_review_item(*args, **kwargs)
833
-
834
- def list_review_items(self, *args: Any, **kwargs: Any) -> List[Dict[str, Any]]:
835
- return self.review_items.list_review_items(*args, **kwargs)
836
-
837
- def get_review_item(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
838
- return self.review_items.get_review_item(*args, **kwargs)
839
-
840
- def update_review_item(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
841
- return self.review_items.update_review_item(*args, **kwargs)
842
-
843
- # ------------------------------------------------------------------
844
- # Agent-native workspace: folder reorganization (v11.1.0)
845
- # ------------------------------------------------------------------
846
-
847
- def preview_reorganization(
848
- self, *, root: str = "", resolve_path: Callable[[str], Any], graph: Any = None,
849
- max_moves: int = 20,
850
- ) -> Dict[str, Any]:
851
- """What a reorganization would do — read-only, nothing is staged."""
852
- return plan_reorganization(
853
- root=root, resolve_path=resolve_path, graph=graph, max_moves=max_moves
854
- )
855
-
856
- def propose_reorganization(
857
- self, *, root: str = "", change_proposals: Any, graph: Any = None,
858
- user_email: Optional[str] = None, workspace_id: Optional[str] = None,
859
- max_moves: int = 20,
860
- ) -> Dict[str, Any]:
861
- """Stage a folder reorganization for review and record it on the timeline.
862
-
863
- The staging itself belongs to ``ChangeProposalService`` (one apply path
864
- for every staged change); this seam exists so a long-running agent
865
- session asking the Workspace OS to tidy a project leaves a trace of
866
- having asked, whether or not there was anything to propose.
867
- """
868
- result = change_proposals.propose_reorganization(
869
- root=root, graph=graph, user_email=user_email,
870
- workspace_id=workspace_id, max_moves=max_moves,
871
- )
872
- proposed = result.get("proposed") or {}
873
- self.record_timeline_event(
874
- "workspace",
875
- "workspace_reorg_proposed",
876
- {
877
- "root": root,
878
- "moves": (result.get("plan") or {}).get("move_count", 0),
879
- "proposal_id": proposed.get("id"),
880
- },
881
- workspace_id=workspace_id,
882
- )
883
- return result
884
-
885
- # ------------------------------------------------------------------
886
- # Relationship explorer
887
- # ------------------------------------------------------------------
888
-
889
- def relationship_explorer(self, graph: Any, node_id: str, target_id: Optional[str] = None, limit: int = 500) -> Dict[str, Any]:
890
- return self.relationships.explore(graph, node_id, target_id=target_id, limit=limit)
891
-
892
- @staticmethod
893
- def _shortest_path(edges: List[Dict[str, Any]], start: str, target: Optional[str]) -> List[str]:
894
- return _shortest_path(edges, start, target)
895
-
896
- # ------------------------------------------------------------------
897
- # Local Computer Memory
898
- # ------------------------------------------------------------------
899
-
900
- def configure_computer_memory(
901
- self,
902
- *,
903
- enabled: bool,
904
- approved_by: Optional[str],
905
- consent: Optional[Dict[str, Any]] = None,
906
- scopes: Optional[List[str]] = None,
907
- ) -> Dict[str, Any]:
908
- return self.computer_memory.configure(
909
- enabled=enabled, approved_by=approved_by, consent=consent, scopes=scopes
910
- )
911
-
912
- def record_computer_activity(self, activity: Dict[str, Any], graph: Any = None) -> Dict[str, Any]:
913
- return self.computer_memory.record_activity(activity, graph)
914
-
915
- # ------------------------------------------------------------------
916
- # Skills
917
- # ------------------------------------------------------------------
918
-
919
- def list_skill_registry(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
920
- return self.skills.list_skill_registry(*args, **kwargs)
921
-
922
- def set_skill_enabled(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
923
- return self.skills.set_skill_enabled(*args, **kwargs)
924
-
925
- def mark_skill_installed(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
926
- return self.skills.mark_skill_installed(*args, **kwargs)
927
-
928
- def mark_skill_uninstalled(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
929
- return self.skills.mark_skill_uninstalled(*args, **kwargs)
930
-
931
- # ------------------------------------------------------------------
932
- # Plugin SDK registry — mirrors the skill registry contract.
933
- # ------------------------------------------------------------------
934
-
935
- def list_plugin_registry(self) -> Dict[str, Any]:
936
- return self.plugins.list_plugin_registry()
937
-
938
- def set_plugin_enabled(self, plugin_id: str, enabled: bool) -> Dict[str, Any]:
939
- return self.plugins.set_plugin_enabled(plugin_id, enabled)
940
-
941
- def mark_plugin_installed(self, plugin_id: str, *, version: str = "0.0.0", metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
942
- return self.plugins.mark_plugin_installed(plugin_id, version=version, metadata=metadata)
943
-
944
- def mark_plugin_uninstalled(self, plugin_id: str) -> Dict[str, Any]:
945
- return self.plugins.mark_plugin_uninstalled(plugin_id)
946
-
947
- # ------------------------------------------------------------------
948
- # Marketplace template registry (v2.1 foundation)
949
- # ------------------------------------------------------------------
950
-
951
- @staticmethod
952
- def _template_registry_key(kind: str, template_id: str, workspace_id: str) -> str:
953
- base = f"{kind}:{template_id}"
954
- return base if workspace_id == DEFAULT_WORKSPACE_ID else f"{workspace_id}:{base}"
955
-
956
- def list_template_registry(self, workspace_id: Optional[str] = None) -> Dict[str, Any]:
957
- return self.plugins.list_template_registry(workspace_id)
958
-
959
- def mark_template_installed(
960
- self,
961
- *,
962
- kind: str,
963
- template_id: str,
964
- version: str = "1.0.0",
965
- metadata: Optional[Dict[str, Any]] = None,
966
- workspace_id: Optional[str] = None,
967
- ) -> Dict[str, Any]:
968
- return self.plugins.mark_template_installed(kind=kind, template_id=template_id, version=version, metadata=metadata, workspace_id=workspace_id)
969
-
970
- # ------------------------------------------------------------------
971
- # Audit timeline
972
- # ------------------------------------------------------------------
973
-
974
- def filter_audit_timeline(
975
- self,
976
- audit_events: Iterable[Dict[str, Any]],
977
- *,
978
- user: Optional[str] = None,
979
- event_type: Optional[str] = None,
980
- model: Optional[str] = None,
981
- since: Optional[str] = None,
982
- until: Optional[str] = None,
983
- limit: int = 100,
984
- ) -> Dict[str, Any]:
985
- return self._timeline.filter_audit_timeline(audit_events, user=user, event_type=event_type, model=model, since=since, until=until, limit=limit)
986
-
987
- def timeline(self, audit_events: Optional[Iterable[Dict[str, Any]]] = None, limit: int = 100, workspace_id: Optional[str] = None) -> Dict[str, Any]:
988
- return self._timeline.timeline(audit_events=audit_events, limit=limit, workspace_id=workspace_id)