ltcai 11.5.2 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/README.md +91 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +75 -0
  4. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  5. package/docs/DEVELOPMENT.md +8 -6
  6. package/docs/ENTERPRISE.md +2 -1
  7. package/docs/MULTI_AGENT_RUNTIME.md +12 -5
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -3
  10. package/docs/REALTIME_COLLABORATION.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/WORKFLOW_DESIGNER.md +3 -2
  14. package/docs/kg-schema.md +1 -1
  15. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  16. package/lattice_brain/__init__.py +42 -79
  17. package/lattice_brain/graph/__init__.py +9 -24
  18. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  19. package/lattice_brain/ingestion/__init__.py +19 -58
  20. package/lattice_brain/ingestion/pipeline.py +20 -398
  21. package/lattice_brain/multimodal/__init__.py +7 -18
  22. package/lattice_brain/multimodal/images.py +6 -264
  23. package/lattice_brain/multimodal/video.py +8 -247
  24. package/lattice_brain/runtime/__init__.py +8 -79
  25. package/lattice_brain/runtime/hooks.py +22 -584
  26. package/latticeai/__init__.py +1 -1
  27. package/latticeai/api/agent_worker_seam.py +7 -80
  28. package/latticeai/api/health.py +6 -20
  29. package/latticeai/api/local_files.py +16 -613
  30. package/latticeai/api/models.py +12 -87
  31. package/latticeai/api/search.py +17 -255
  32. package/latticeai/api/tools.py +69 -750
  33. package/latticeai/api/voice_capture.py +8 -70
  34. package/latticeai/api/worker_compute.py +842 -0
  35. package/latticeai/api/worker_seams.py +216 -0
  36. package/latticeai/app_factory.py +23 -232
  37. package/latticeai/cli/entrypoint.py +36 -249
  38. package/latticeai/core/agent_permission.py +16 -91
  39. package/latticeai/core/messages.py +89 -531
  40. package/latticeai/runtime/access_runtime.py +0 -14
  41. package/latticeai/runtime/bootstrap.py +10 -21
  42. package/latticeai/runtime/brain_runtime.py +35 -43
  43. package/latticeai/runtime/build_phases/__init__.py +21 -37
  44. package/latticeai/runtime/build_phases/features.py +99 -389
  45. package/latticeai/runtime/build_phases/foundation.py +82 -392
  46. package/latticeai/runtime/build_phases/web.py +91 -408
  47. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  48. package/latticeai/runtime/lifespan_runtime.py +7 -16
  49. package/latticeai/runtime/platform_services_runtime.py +1 -15
  50. package/latticeai/runtime/runtime_context.py +13 -130
  51. package/latticeai/runtime/security_runtime.py +10 -103
  52. package/latticeai/services/architecture_readiness.py +82 -43
  53. package/latticeai/services/model_runtime/__init__.py +2 -11
  54. package/latticeai/services/model_runtime/service.py +4 -28
  55. package/latticeai/services/p_reinforce.py +10 -261
  56. package/latticeai/services/product_readiness.py +31 -38
  57. package/latticeai/services/search_service.py +37 -795
  58. package/latticeai/services/tool_dispatch.py +30 -386
  59. package/latticeai/services/voice_capture.py +13 -107
  60. package/latticeai/tools/__init__.py +22 -49
  61. package/latticeai/tools/commands.py +0 -163
  62. package/latticeai/tools/computer.py +0 -39
  63. package/latticeai/tools/documents.py +1 -134
  64. package/latticeai/tools/filesystem.py +1 -247
  65. package/latticeai/tools/knowledge.py +1 -52
  66. package/latticeai/tools/local_files.py +0 -20
  67. package/latticeai/worker_app.py +75 -0
  68. package/package.json +2 -3
  69. package/requirements.txt +0 -5
  70. package/scripts/agent_eval.py +16 -28
  71. package/scripts/brain_quality_eval.py +20 -183
  72. package/scripts/bump_version.py +5 -5
  73. package/scripts/check_current_release_docs.mjs +7 -4
  74. package/scripts/check_openapi_drift.mjs +10 -0
  75. package/scripts/check_server_i18n.mjs +2 -18
  76. package/scripts/compose_openapi.py +377 -0
  77. package/scripts/export_openapi.py +32 -4
  78. package/scripts/gen_messages_catalog_fixture.py +302 -0
  79. package/scripts/gen_openapi_fragments.py +365 -0
  80. package/scripts/gen_redact_fixture.py +196 -0
  81. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  82. package/scripts/generate_agent_parity_fixtures.py +33 -14
  83. package/scripts/openapi_route_families.json +2161 -0
  84. package/scripts/release_screen_claims.json +21 -0
  85. package/scripts/run_integration_tests.mjs +134 -29
  86. package/scripts/run_sidecar_e2e.mjs +104 -11
  87. package/scripts/wheel_smoke.py +32 -22
  88. package/src-tauri/Cargo.lock +375 -9
  89. package/src-tauri/Cargo.toml +1 -1
  90. package/src-tauri/src/backend.rs +151 -45
  91. package/src-tauri/src/main.rs +18 -18
  92. package/src-tauri/src/topology.rs +58 -88
  93. package/src-tauri/tauri.conf.json +1 -2
  94. package/static/app/asset-manifest.json +41 -41
  95. package/static/app/assets/{Act-DcQizkl1.js → Act-CToZnOHz.js} +1 -1
  96. package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
  100. package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
  107. package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
  108. package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
  112. package/static/app/assets/circle-check-CY20XMCh.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
  119. package/static/app/assets/index-BovSWRmQ.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
  124. package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
  125. package/static/app/assets/search-DTxtjQ07.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.js} +1 -1
  134. package/static/app/index.html +4 -4
  135. package/static/sw.js +1 -1
  136. package/lattice_brain/archive.py +0 -522
  137. package/lattice_brain/context.py +0 -325
  138. package/lattice_brain/conversations.py +0 -382
  139. package/lattice_brain/core.py +0 -82
  140. package/lattice_brain/graph/_kg_contract.py +0 -249
  141. package/lattice_brain/graph/curator.py +0 -676
  142. package/lattice_brain/graph/discovery.py +0 -595
  143. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  144. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  145. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  146. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  147. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  148. package/lattice_brain/graph/documents.py +0 -380
  149. package/lattice_brain/graph/fusion.py +0 -395
  150. package/lattice_brain/graph/identity.py +0 -175
  151. package/lattice_brain/graph/image_vectors.py +0 -230
  152. package/lattice_brain/graph/ingest.py +0 -829
  153. package/lattice_brain/graph/network.py +0 -205
  154. package/lattice_brain/graph/proactive.py +0 -724
  155. package/lattice_brain/graph/projection/__init__.py +0 -42
  156. package/lattice_brain/graph/projection/curation.py +0 -500
  157. package/lattice_brain/graph/projection/v2_schema.py +0 -518
  158. package/lattice_brain/graph/provenance.py +0 -524
  159. package/lattice_brain/graph/rerank.py +0 -163
  160. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  161. package/lattice_brain/graph/retrieval/context.py +0 -197
  162. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  163. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  164. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  165. package/lattice_brain/graph/retrieval/signals.py +0 -95
  166. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  167. package/lattice_brain/graph/retrieval_policy.py +0 -180
  168. package/lattice_brain/graph/retrieval_reads.py +0 -769
  169. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  170. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  171. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  172. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  173. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  174. package/lattice_brain/graph/schema.py +0 -792
  175. package/lattice_brain/graph/store.py +0 -268
  176. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  177. package/lattice_brain/graph/vector_index/base.py +0 -167
  178. package/lattice_brain/graph/vector_index/brute_force.py +0 -110
  179. package/lattice_brain/graph/vector_index/hnsw.py +0 -290
  180. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  181. package/lattice_brain/graph/vector_index/quantized.py +0 -148
  182. package/lattice_brain/graph/vector_index/selector.py +0 -161
  183. package/lattice_brain/graph/write_master.py +0 -308
  184. package/lattice_brain/ingestion/_contract.py +0 -90
  185. package/lattice_brain/ingestion/folder_scan.py +0 -57
  186. package/lattice_brain/ingestion/folders.py +0 -258
  187. package/lattice_brain/ingestion/jobs_api.py +0 -107
  188. package/lattice_brain/ingestion/routing.py +0 -295
  189. package/lattice_brain/ingestion_jobs.py +0 -380
  190. package/lattice_brain/memory.py +0 -75
  191. package/lattice_brain/portability/__init__.py +0 -90
  192. package/lattice_brain/portability/_contract.py +0 -42
  193. package/lattice_brain/portability/backups.py +0 -338
  194. package/lattice_brain/portability/bundles.py +0 -136
  195. package/lattice_brain/portability/constants.py +0 -93
  196. package/lattice_brain/portability/fsops.py +0 -138
  197. package/lattice_brain/portability/service.py +0 -41
  198. package/lattice_brain/portability/sharing.py +0 -710
  199. package/lattice_brain/quality.py +0 -543
  200. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  201. package/lattice_brain/runtime/agent_runtime.py +0 -859
  202. package/lattice_brain/runtime/contracts.py +0 -460
  203. package/lattice_brain/runtime/multi_agent.py +0 -942
  204. package/lattice_brain/runtime/statuses.py +0 -10
  205. package/lattice_brain/sealed_box.py +0 -240
  206. package/lattice_brain/self_model.py +0 -675
  207. package/lattice_brain/sensitivity.py +0 -94
  208. package/lattice_brain/storage/__init__.py +0 -22
  209. package/lattice_brain/storage/base.py +0 -100
  210. package/lattice_brain/storage/docker.py +0 -105
  211. package/lattice_brain/storage/factory.py +0 -31
  212. package/lattice_brain/storage/migration.py +0 -191
  213. package/lattice_brain/storage/postgres.py +0 -123
  214. package/lattice_brain/storage/sqlite.py +0 -143
  215. package/lattice_brain/synthesis.py +0 -824
  216. package/lattice_brain/workflow.py +0 -497
  217. package/latticeai/api/admin.py +0 -471
  218. package/latticeai/api/agent_registry.py +0 -105
  219. package/latticeai/api/agents.py +0 -228
  220. package/latticeai/api/auth.py +0 -383
  221. package/latticeai/api/automation_intelligence.py +0 -401
  222. package/latticeai/api/brain_intelligence.py +0 -199
  223. package/latticeai/api/browser.py +0 -493
  224. package/latticeai/api/change_proposals.py +0 -89
  225. package/latticeai/api/chat.py +0 -572
  226. package/latticeai/api/chat_agent_http.py +0 -892
  227. package/latticeai/api/chat_contracts.py +0 -73
  228. package/latticeai/api/chat_documents.py +0 -276
  229. package/latticeai/api/chat_helpers.py +0 -460
  230. package/latticeai/api/chat_history.py +0 -101
  231. package/latticeai/api/chat_hybrid.py +0 -113
  232. package/latticeai/api/chat_intents.py +0 -646
  233. package/latticeai/api/chat_stream.py +0 -216
  234. package/latticeai/api/chronicle.py +0 -63
  235. package/latticeai/api/command_center.py +0 -51
  236. package/latticeai/api/computer_use.py +0 -474
  237. package/latticeai/api/evidence_actions.py +0 -48
  238. package/latticeai/api/features.py +0 -70
  239. package/latticeai/api/funnel_metrics.py +0 -31
  240. package/latticeai/api/garden.py +0 -34
  241. package/latticeai/api/hooks.py +0 -165
  242. package/latticeai/api/index_jobs.py +0 -145
  243. package/latticeai/api/invitations.py +0 -100
  244. package/latticeai/api/knowledge_graph.py +0 -536
  245. package/latticeai/api/marketplace.py +0 -105
  246. package/latticeai/api/mcp.py +0 -482
  247. package/latticeai/api/memory.py +0 -270
  248. package/latticeai/api/network.py +0 -81
  249. package/latticeai/api/network_boundary.py +0 -225
  250. package/latticeai/api/permission_mode.py +0 -61
  251. package/latticeai/api/permissions.py +0 -436
  252. package/latticeai/api/plugins.py +0 -126
  253. package/latticeai/api/portability.py +0 -391
  254. package/latticeai/api/project_sessions.py +0 -114
  255. package/latticeai/api/realtime.py +0 -118
  256. package/latticeai/api/review_queue.py +0 -364
  257. package/latticeai/api/security_dashboard.py +0 -604
  258. package/latticeai/api/setup.py +0 -319
  259. package/latticeai/api/static_routes.py +0 -354
  260. package/latticeai/api/ui_redirects.py +0 -26
  261. package/latticeai/api/workflow_designer.py +0 -394
  262. package/latticeai/api/workspace.py +0 -856
  263. package/latticeai/api/workspace_scope.py +0 -125
  264. package/latticeai/core/agent/__init__.py +0 -93
  265. package/latticeai/core/agent/_contract.py +0 -79
  266. package/latticeai/core/agent/context.py +0 -57
  267. package/latticeai/core/agent/deps.py +0 -125
  268. package/latticeai/core/agent/execution.py +0 -622
  269. package/latticeai/core/agent/planning.py +0 -145
  270. package/latticeai/core/agent/recovery.py +0 -157
  271. package/latticeai/core/agent/runtime.py +0 -210
  272. package/latticeai/core/agent/verification.py +0 -231
  273. package/latticeai/core/agent_eval.py +0 -739
  274. package/latticeai/core/agent_helpers.py +0 -493
  275. package/latticeai/core/agent_profiles.py +0 -110
  276. package/latticeai/core/agent_prompts.py +0 -171
  277. package/latticeai/core/agent_registry.py +0 -232
  278. package/latticeai/core/agent_state.py +0 -41
  279. package/latticeai/core/agent_trace.py +0 -104
  280. package/latticeai/core/artifact_ledger.py +0 -109
  281. package/latticeai/core/audit.py +0 -260
  282. package/latticeai/core/builtin_hooks.py +0 -105
  283. package/latticeai/core/context_builder.py +0 -394
  284. package/latticeai/core/document_generator.py +0 -103
  285. package/latticeai/core/enterprise.py +0 -154
  286. package/latticeai/core/enterprise_admin.py +0 -158
  287. package/latticeai/core/file_generation/__init__.py +0 -115
  288. package/latticeai/core/file_generation/bundles.py +0 -76
  289. package/latticeai/core/file_generation/extraction.py +0 -154
  290. package/latticeai/core/file_generation/inference.py +0 -235
  291. package/latticeai/core/file_generation/orchestration.py +0 -152
  292. package/latticeai/core/file_generation/prompting.py +0 -117
  293. package/latticeai/core/file_generation/repair.py +0 -114
  294. package/latticeai/core/file_generation/sanitize.py +0 -61
  295. package/latticeai/core/file_generation/validation.py +0 -201
  296. package/latticeai/core/invitations.py +0 -132
  297. package/latticeai/core/legacy_compatibility.py +0 -243
  298. package/latticeai/core/logging_safety.py +0 -46
  299. package/latticeai/core/marketplace.py +0 -293
  300. package/latticeai/core/mcp_catalog.py +0 -452
  301. package/latticeai/core/mcp_registry.py +0 -506
  302. package/latticeai/core/network_boundary.py +0 -168
  303. package/latticeai/core/oidc.py +0 -208
  304. package/latticeai/core/plugins.py +0 -432
  305. package/latticeai/core/product_hardening.py +0 -218
  306. package/latticeai/core/project_sessions.py +0 -337
  307. package/latticeai/core/realtime.py +0 -238
  308. package/latticeai/core/run_explain.py +0 -426
  309. package/latticeai/core/run_store.py +0 -252
  310. package/latticeai/core/timezones.py +0 -80
  311. package/latticeai/core/workspace_computer_memory.py +0 -84
  312. package/latticeai/core/workspace_graph_trace.py +0 -155
  313. package/latticeai/core/workspace_indexing.py +0 -102
  314. package/latticeai/core/workspace_memory.py +0 -77
  315. package/latticeai/core/workspace_onboarding.py +0 -104
  316. package/latticeai/core/workspace_os.py +0 -978
  317. package/latticeai/core/workspace_os_constants.py +0 -126
  318. package/latticeai/core/workspace_os_state.py +0 -180
  319. package/latticeai/core/workspace_os_utils.py +0 -103
  320. package/latticeai/core/workspace_permissions.py +0 -101
  321. package/latticeai/core/workspace_plugins.py +0 -97
  322. package/latticeai/core/workspace_relationships.py +0 -99
  323. package/latticeai/core/workspace_reorganization.py +0 -335
  324. package/latticeai/core/workspace_review_items.py +0 -112
  325. package/latticeai/core/workspace_runs.py +0 -726
  326. package/latticeai/core/workspace_skills.py +0 -109
  327. package/latticeai/core/workspace_snapshots.py +0 -198
  328. package/latticeai/core/workspace_timeline.py +0 -110
  329. package/latticeai/integrations/__init__.py +0 -0
  330. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  331. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  332. package/latticeai/integrations/telegram_bot/config.py +0 -86
  333. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  334. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  335. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  336. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  337. package/latticeai/runtime/audit_runtime.py +0 -76
  338. package/latticeai/runtime/automation_runtime.py +0 -81
  339. package/latticeai/runtime/chat_wiring.py +0 -141
  340. package/latticeai/runtime/context_runtime.py +0 -66
  341. package/latticeai/runtime/feature_toggle_wiring.py +0 -157
  342. package/latticeai/runtime/history_runtime.py +0 -163
  343. package/latticeai/runtime/history_writer.py +0 -138
  344. package/latticeai/runtime/hooks_runtime.py +0 -77
  345. package/latticeai/runtime/model_wiring.py +0 -68
  346. package/latticeai/runtime/namespace_runtime.py +0 -163
  347. package/latticeai/runtime/network_boundary_wiring.py +0 -117
  348. package/latticeai/runtime/network_config_runtime.py +0 -56
  349. package/latticeai/runtime/permission_mode_wiring.py +0 -112
  350. package/latticeai/runtime/persistence_runtime.py +0 -159
  351. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  352. package/latticeai/runtime/review_wiring.py +0 -42
  353. package/latticeai/runtime/router_registration.py +0 -693
  354. package/latticeai/runtime/service_singletons.py +0 -55
  355. package/latticeai/runtime/sso_config_runtime.py +0 -128
  356. package/latticeai/runtime/user_key_runtime.py +0 -106
  357. package/latticeai/runtime/web_runtime.py +0 -92
  358. package/latticeai/server_app.py +0 -51
  359. package/latticeai/services/app_context.py +0 -130
  360. package/latticeai/services/automation_execution.py +0 -266
  361. package/latticeai/services/automation_intelligence.py +0 -614
  362. package/latticeai/services/brain_automation.py +0 -191
  363. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  364. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  365. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  366. package/latticeai/services/brain_intelligence/constants.py +0 -47
  367. package/latticeai/services/brain_intelligence/digest.py +0 -258
  368. package/latticeai/services/brain_intelligence/health.py +0 -331
  369. package/latticeai/services/brain_intelligence/proposals.py +0 -259
  370. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  371. package/latticeai/services/brain_intelligence/service.py +0 -48
  372. package/latticeai/services/change_proposals.py +0 -471
  373. package/latticeai/services/chat_service.py +0 -243
  374. package/latticeai/services/chronicle.py +0 -555
  375. package/latticeai/services/cloud_egress_audit.py +0 -85
  376. package/latticeai/services/cloud_extraction.py +0 -129
  377. package/latticeai/services/cloud_streaming.py +0 -268
  378. package/latticeai/services/cloud_token_guard.py +0 -84
  379. package/latticeai/services/command_center.py +0 -548
  380. package/latticeai/services/evidence_actions.py +0 -258
  381. package/latticeai/services/feature_toggles.py +0 -502
  382. package/latticeai/services/folder_watch.py +0 -520
  383. package/latticeai/services/funnel_metrics.py +0 -307
  384. package/latticeai/services/hybrid_chat.py +0 -316
  385. package/latticeai/services/hybrid_context.py +0 -228
  386. package/latticeai/services/hybrid_policy.py +0 -129
  387. package/latticeai/services/interop_bridges.py +0 -978
  388. package/latticeai/services/local_knowledge.py +0 -465
  389. package/latticeai/services/memory_service/__init__.py +0 -52
  390. package/latticeai/services/memory_service/_contract.py +0 -100
  391. package/latticeai/services/memory_service/brief.py +0 -431
  392. package/latticeai/services/memory_service/constants.py +0 -57
  393. package/latticeai/services/memory_service/maintenance.py +0 -138
  394. package/latticeai/services/memory_service/manager.py +0 -186
  395. package/latticeai/services/memory_service/proof.py +0 -136
  396. package/latticeai/services/memory_service/recall.py +0 -225
  397. package/latticeai/services/memory_service/service.py +0 -48
  398. package/latticeai/services/memory_service/stores.py +0 -110
  399. package/latticeai/services/mode_store.py +0 -132
  400. package/latticeai/services/model_recommendation.py +0 -224
  401. package/latticeai/services/model_runtime/cloud.py +0 -87
  402. package/latticeai/services/network_boundary_service.py +0 -117
  403. package/latticeai/services/obsidian_bridge.py +0 -609
  404. package/latticeai/services/openai_compatible_adapter.py +0 -101
  405. package/latticeai/services/permission_mode_service.py +0 -122
  406. package/latticeai/services/platform_runtime.py +0 -366
  407. package/latticeai/services/review_queue.py +0 -380
  408. package/latticeai/services/router_context.py +0 -59
  409. package/latticeai/services/run_executor.py +0 -387
  410. package/latticeai/services/self_model_service.py +0 -171
  411. package/latticeai/services/setup_detection.py +0 -147
  412. package/latticeai/services/triggers.py +0 -378
  413. package/latticeai/services/upload_service.py +0 -172
  414. package/latticeai/services/workspace_service.py +0 -165
  415. package/latticeai/setup/__init__.py +0 -25
  416. package/latticeai/setup/auto_setup.py +0 -846
  417. package/latticeai/setup/demo_corpus.py +0 -98
  418. package/latticeai/setup/wizard/__init__.py +0 -126
  419. package/latticeai/setup/wizard/catalog.py +0 -175
  420. package/latticeai/setup/wizard/detect.py +0 -302
  421. package/latticeai/setup/wizard/install.py +0 -348
  422. package/latticeai/setup/wizard/paths.py +0 -165
  423. package/latticeai/setup/wizard/plans.py +0 -74
  424. package/latticeai/setup/wizard/recommend.py +0 -320
  425. package/scripts/bench_agent_smoke.py +0 -409
  426. package/scripts/bench_models.py +0 -540
  427. package/scripts/bench_vector_index.py +0 -295
  428. package/scripts/funnel_soft_gate.py +0 -192
  429. package/scripts/generate_agent_loop_fixtures.py +0 -994
  430. package/scripts/generate_rust_parity_fixtures.py +0 -908
  431. package/scripts/migrate_brain_storage.py +0 -57
  432. package/scripts/parity_fixture_corpus_context.py +0 -162
  433. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  434. package/scripts/profile_kg.py +0 -355
  435. package/server.py +0 -30
  436. package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
  437. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  438. package/static/app/assets/index-DxmOfNRi.css +0 -2
  439. package/static/app/assets/search-BcHqkjoy.js +0 -1
@@ -1,99 +0,0 @@
1
- """Relationship explorer: what a node connects to, and the way between two.
2
-
3
- Extracted from ``WorkspaceOSStore``. Pure graph reading — it touches no state
4
- file and records no events, so it holds no reference to the store at all.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from collections import deque
10
- from typing import Any, Dict, List, Optional
11
-
12
- from latticeai.core.quiet import quiet
13
-
14
- __all__ = ["WorkspaceRelationships", "shortest_path"]
15
-
16
-
17
- def shortest_path(edges: List[Dict[str, Any]], start: str, target: Optional[str]) -> List[str]:
18
- """Fewest-hops path between two nodes, treating every edge as undirected.
19
-
20
- Breadth-first, so the first arrival at ``target`` is a shortest one.
21
- Returns ``[]`` when either end is missing or nothing connects them.
22
- """
23
- if not start or not target:
24
- return []
25
- adjacency: Dict[str, List[str]] = {}
26
- for edge in edges:
27
- src = edge.get("from")
28
- dst = edge.get("to")
29
- if src and dst:
30
- adjacency.setdefault(src, []).append(dst)
31
- adjacency.setdefault(dst, []).append(src)
32
- queue: deque[List[str]] = deque([[start]])
33
- seen = {start}
34
- while queue:
35
- path = queue.popleft()
36
- node = path[-1]
37
- if node == target:
38
- return path
39
- for neighbor in adjacency.get(node, []):
40
- if neighbor not in seen:
41
- seen.add(neighbor)
42
- queue.append(path + [neighbor])
43
- return []
44
-
45
-
46
- class WorkspaceRelationships:
47
- """Answers "what is this connected to" for one node of the graph."""
48
-
49
- def __init__(self, store: Any) -> None:
50
- self.store = store
51
-
52
- def explore(
53
- self,
54
- graph: Any,
55
- node_id: str,
56
- target_id: Optional[str] = None,
57
- limit: int = 500,
58
- ) -> Dict[str, Any]:
59
- if graph is None:
60
- return {
61
- "node_id": node_id,
62
- "inbound": [],
63
- "outbound": [],
64
- "related_entities": [],
65
- "shortest_path": [],
66
- }
67
- data = graph.graph(limit=limit)
68
- nodes = {node.get("id"): node for node in data.get("nodes") or [] if node.get("id")}
69
- edges = data.get("edges") or []
70
- inbound = [edge for edge in edges if edge.get("to") == node_id]
71
- outbound = [edge for edge in edges if edge.get("from") == node_id]
72
- # The capped `graph(limit=…)` window may not contain this node at all.
73
- # Asking the store for its neighbours directly is the difference between
74
- # "not connected" and "outside the page we happened to fetch".
75
- if node_id not in nodes:
76
- try:
77
- neighbors = graph.neighbors(node_id)
78
- for node in neighbors.get("neighbors") or []:
79
- nodes[node.get("id")] = node
80
- edges.extend(neighbors.get("edges") or [])
81
- inbound = [edge for edge in edges if edge.get("to") == node_id]
82
- outbound = [edge for edge in edges if edge.get("from") == node_id]
83
- except Exception:
84
- quiet()
85
-
86
- related_ids = []
87
- for edge in inbound + outbound:
88
- other = edge.get("from") if edge.get("to") == node_id else edge.get("to")
89
- if other:
90
- related_ids.append(other)
91
- related = [nodes.get(rid, {"id": rid}) for rid in dict.fromkeys(related_ids)]
92
- return {
93
- "node_id": node_id,
94
- "node": nodes.get(node_id, {"id": node_id}),
95
- "inbound": inbound,
96
- "outbound": outbound,
97
- "related_entities": related,
98
- "shortest_path": shortest_path(edges, node_id, target_id) if target_id else [],
99
- }
@@ -1,335 +0,0 @@
1
- """Agent-native workspace reorganization — proposals, never a silent tidy-up.
2
-
3
- "이 프로젝트를 정리해줘" is a request an agent can now answer without being
4
- trusted with the filesystem. The Brain looks at a folder, asks the graph what
5
- each file is *about*, and stages one review proposal describing every move it
6
- would make. Nothing on disk changes until a person approves it, and approving
7
- applies exactly the moves that were reviewed.
8
-
9
- Three rules make this safe enough to hand to a long-running session:
10
-
11
- * **No deletions, ever.** The planner emits moves only. There is no code path
12
- here that removes a file, so the worst outcome of a bad proposal is a file
13
- in a folder you did not want (recoverable) rather than a file that is gone
14
- (not). Plan §10's conservative mitigation, made structural.
15
- * **Only what the Brain actually knows.** A file is moved because the graph
16
- links it to a topic — never because of a guess from its extension. Files the
17
- Brain has nothing to say about are reported as ``unplaced`` with a reason
18
- instead of being swept somewhere plausible.
19
- * **Deterministic.** The same folder and the same graph produce the same plan,
20
- in the same order: candidates are sorted, topics are chosen by
21
- (weight, title), and collisions resolve to the first path alphabetically.
22
-
23
- Every path in and out of this module is workspace-relative and resolved
24
- through the caller's sandboxed ``resolve_path`` (the agent-root resolver), so
25
- a proposal can never name a target outside the workspace.
26
- """
27
-
28
- from __future__ import annotations
29
-
30
- import logging
31
- import os
32
- import re
33
- from pathlib import Path
34
- from typing import Any, Callable, Dict, List, Optional, Tuple
35
-
36
- from latticeai.core.timeutil import now_iso as _now
37
-
38
- LOGGER = logging.getLogger(__name__)
39
-
40
- #: Review-queue kind for a reorganization proposal. Source stays
41
- #: ``change_proposal`` so the Review Center's approve path applies it through
42
- #: :class:`~latticeai.services.change_proposals.ChangeProposalService`.
43
- REORG_KIND = "folder_reorganization"
44
-
45
- #: Folder the plan groups into. One level, named after the topic, so the result
46
- #: is legible without a map.
47
- TOPIC_ROOT = "topics"
48
-
49
- #: Node types that stand for a file on disk, and the ones that stand for what a
50
- #: file is about.
51
- FILE_NODE_TYPES = frozenset(
52
- {
53
- "File", "CodeFile", "Document", "Image", "Spreadsheet", "SlideDeck",
54
- "ImageText", "Audio",
55
- }
56
- )
57
- TOPIC_NODE_TYPES = frozenset({"Topic", "Concept", "Project", "Feature"})
58
-
59
- DEFAULT_MAX_MOVES = 20
60
- DEFAULT_SCAN_LIMIT = 400
61
- DEFAULT_GRAPH_LIMIT = 800
62
-
63
- _SLUG_RE = re.compile(r"[^0-9A-Za-z가-힣._-]+")
64
-
65
-
66
- def _slug(value: str) -> str:
67
- """Folder-safe topic name (Korean kept — the user reads these folders)."""
68
- return _SLUG_RE.sub("-", str(value or "").strip()).strip("-.").lower()[:48]
69
-
70
-
71
- def _rel(path: Path, base: Path) -> str:
72
- return path.relative_to(base).as_posix()
73
-
74
-
75
- def _scan(base: Path, limit: int) -> List[Path]:
76
- """Visible files under ``base``, deepest-stable order, capped at ``limit``."""
77
- found: List[Path] = []
78
- for path in sorted(base.rglob("*")):
79
- if len(found) >= limit:
80
- break
81
- if not path.is_file():
82
- continue
83
- if any(part.startswith(".") for part in path.relative_to(base).parts):
84
- continue
85
- found.append(path)
86
- return found
87
-
88
-
89
- def _file_node_index(graph: Any, limit: int) -> Tuple[Dict[str, str], str]:
90
- """``{file name → graph node id}`` plus the basis for the plan.
91
-
92
- A missing or unreadable graph yields an empty index and a basis that says
93
- so, which is what makes an empty plan honest rather than mysterious.
94
- """
95
- if graph is None:
96
- return {}, "no_graph"
97
- try:
98
- data = graph.graph(limit)
99
- except Exception: # noqa: BLE001 — a broken read proposes nothing
100
- LOGGER.exception("workspace reorganization graph read failed")
101
- return {}, "graph_unavailable"
102
- index: Dict[str, str] = {}
103
- for node in data.get("nodes") or []:
104
- if str(node.get("type")) not in FILE_NODE_TYPES:
105
- continue
106
- for key in _file_keys(node):
107
- index.setdefault(key, str(node.get("id")))
108
- return index, "graph"
109
-
110
-
111
- def _topic_for(graph: Any, node_id: str) -> str:
112
- """The topic a file node belongs to, or ``""`` when the Brain has none.
113
-
114
- One hop, strongest edge first; ties break on the topic title so the same
115
- graph always produces the same folder.
116
- """
117
- try:
118
- data = graph.neighbors(node_id)
119
- except Exception: # noqa: BLE001 — one unreadable node is not a failure
120
- LOGGER.exception("workspace reorganization neighbour read failed")
121
- return ""
122
- by_id = {str(node.get("id")): node for node in data.get("neighbors") or []}
123
- best: Optional[Tuple[float, str]] = None
124
- for edge in data.get("edges") or []:
125
- far = edge.get("to") if str(edge.get("from")) == node_id else edge.get("from")
126
- node = by_id.get(str(far))
127
- if node is None or str(node.get("type")) not in TOPIC_NODE_TYPES:
128
- continue
129
- title = str(node.get("title") or "").strip()
130
- if not title:
131
- continue
132
- candidate = (-float(edge.get("weight") or 1.0), title)
133
- if best is None or candidate < best:
134
- best = candidate
135
- return best[1] if best is not None else ""
136
-
137
-
138
- def _file_keys(node: Dict[str, Any]) -> List[str]:
139
- """Every name a graph file node might be matched by (lowercased)."""
140
- metadata = node.get("metadata") or {}
141
- candidates = [
142
- metadata.get("relative_path"),
143
- metadata.get("filename"),
144
- metadata.get("path"),
145
- node.get("title"),
146
- ]
147
- keys: List[str] = []
148
- for candidate in candidates:
149
- text = str(candidate or "").strip()
150
- if not text:
151
- continue
152
- for key in (text.lower(), Path(text).name.lower()):
153
- if key and key not in keys:
154
- keys.append(key)
155
- return keys
156
-
157
-
158
- def plan_reorganization(
159
- *,
160
- root: str = "",
161
- resolve_path: Callable[[str], Path],
162
- graph: Any = None,
163
- max_moves: int = DEFAULT_MAX_MOVES,
164
- scan_limit: int = DEFAULT_SCAN_LIMIT,
165
- graph_limit: int = DEFAULT_GRAPH_LIMIT,
166
- ) -> Dict[str, Any]:
167
- """What the Brain would move, and what it deliberately would not.
168
-
169
- Read-only: this touches no file and creates no proposal.
170
- """
171
- base = resolve_path(root)
172
- if not base.is_dir():
173
- return _empty_plan(root, "folder_missing")
174
- index, basis = _file_node_index(graph, graph_limit)
175
- moves: List[Dict[str, Any]] = []
176
- unplaced: List[Dict[str, Any]] = []
177
- claimed: set = set()
178
- for path in _scan(base, scan_limit):
179
- source = _rel(path, base)
180
- node_id = index.get(source.lower()) or index.get(path.name.lower())
181
- topic = _topic_for(graph, node_id) if node_id else ""
182
- if not topic:
183
- unplaced.append({"path": source, "reason": "brain_has_no_topic"})
184
- continue
185
- target = f"{TOPIC_ROOT}/{_slug(topic)}/{path.name}"
186
- if target == source:
187
- unplaced.append({"path": source, "reason": "already_in_place"})
188
- continue
189
- if target in claimed or (base / target).exists():
190
- unplaced.append({"path": source, "reason": "target_taken"})
191
- continue
192
- claimed.add(target)
193
- moves.append({
194
- "source": source,
195
- "target": target,
196
- "topic": topic,
197
- "reason": f"'{topic}' 주제와 이어져 있습니다",
198
- })
199
- trimmed = len(moves) > max_moves
200
- return {
201
- "available": True,
202
- "root": root,
203
- "basis": basis,
204
- "moves": moves[: max(0, int(max_moves))],
205
- "move_count": len(moves[: max(0, int(max_moves))]),
206
- "truncated": trimmed,
207
- "unplaced": unplaced,
208
- "unplaced_count": len(unplaced),
209
- # Structural, not a policy toggle: this planner has no delete path.
210
- "deletes": [],
211
- "generated_at": _now(),
212
- }
213
-
214
-
215
- def _empty_plan(root: str, reason: str) -> Dict[str, Any]:
216
- return {
217
- "available": False,
218
- "root": root,
219
- "basis": reason,
220
- "moves": [],
221
- "move_count": 0,
222
- "truncated": False,
223
- "unplaced": [],
224
- "unplaced_count": 0,
225
- "deletes": [],
226
- "generated_at": _now(),
227
- }
228
-
229
-
230
- def propose_reorganization(
231
- *,
232
- root: str = "",
233
- resolve_path: Callable[[str], Path],
234
- review_queue: Any,
235
- graph: Any = None,
236
- user_email: Optional[str] = None,
237
- workspace_id: Optional[str] = None,
238
- max_moves: int = DEFAULT_MAX_MOVES,
239
- audit: Optional[Callable[..., None]] = None,
240
- ) -> Dict[str, Any]:
241
- """Stage one review proposal describing the whole reorganization.
242
-
243
- Returns ``{"proposed": None, ...}`` when there is nothing to propose — an
244
- empty proposal is noise, and a Brain that knows nothing about a folder
245
- should say so rather than invent a structure.
246
- """
247
- plan = plan_reorganization(
248
- root=root, resolve_path=resolve_path, graph=graph, max_moves=max_moves
249
- )
250
- if not plan["moves"]:
251
- return {"proposed": None, "plan": plan, "reason": plan["basis"]}
252
- preview = ", ".join(move["source"] for move in plan["moves"][:3])
253
- item = review_queue.create(
254
- title=f"폴더 정리 제안: {root or '작업 폴더'} 파일 {plan['move_count']}개",
255
- summary=(
256
- f"{preview} 등 {plan['move_count']}개 파일을 주제별 폴더로 옮기는 제안입니다. "
257
- "삭제는 제안하지 않으며, 승인하기 전에는 아무것도 움직이지 않습니다."
258
- ),
259
- source="change_proposal",
260
- kind=REORG_KIND,
261
- payload={
262
- "root": root,
263
- "moves": plan["moves"],
264
- "unplaced": plan["unplaced"],
265
- "basis": plan["basis"],
266
- "contract": {"moves": "proposal", "deletions": "never_proposed"},
267
- },
268
- provenance={
269
- "proposed_by": "agent",
270
- "reason": "workspace reorganization",
271
- "source_detail": "brain topic grouping",
272
- },
273
- user_email=user_email,
274
- workspace_id=workspace_id,
275
- )
276
- if audit is not None:
277
- audit(
278
- "workspace_reorg_proposed",
279
- user_email=user_email,
280
- proposal_id=item.get("id"),
281
- moves=plan["move_count"],
282
- )
283
- return {"proposed": item, "plan": plan, "reason": ""}
284
-
285
-
286
- def apply_reorganization(
287
- payload: Dict[str, Any], *, resolve_path: Callable[[str], Path]
288
- ) -> Dict[str, Any]:
289
- """Carry out the reviewed moves. Nothing is ever deleted or overwritten.
290
-
291
- Each move is checked again at apply time: a source that vanished and a
292
- target that appeared are both reported as skipped rather than forced, so a
293
- stale proposal degrades into an honest partial result.
294
- """
295
- applied: List[Dict[str, Any]] = []
296
- skipped: List[Dict[str, Any]] = []
297
- for move in payload.get("moves") or []:
298
- source_rel = str(move.get("source") or "")
299
- target_rel = str(move.get("target") or "")
300
- if not source_rel or not target_rel:
301
- skipped.append({"source": source_rel, "reason": "incomplete_move"})
302
- continue
303
- source = resolve_path(_join(payload, source_rel))
304
- target = resolve_path(_join(payload, target_rel))
305
- if not source.is_file():
306
- skipped.append({"source": source_rel, "reason": "source_missing"})
307
- continue
308
- if target.exists():
309
- skipped.append({"source": source_rel, "reason": "target_exists"})
310
- continue
311
- target.parent.mkdir(parents=True, exist_ok=True)
312
- os.replace(source, target)
313
- applied.append({"source": source_rel, "target": target_rel})
314
- return {
315
- "applied": applied,
316
- "applied_count": len(applied),
317
- "skipped": skipped,
318
- "skipped_count": len(skipped),
319
- "deleted": 0,
320
- }
321
-
322
-
323
- def _join(payload: Dict[str, Any], relative: str) -> str:
324
- root = str(payload.get("root") or "").strip("/")
325
- return f"{root}/{relative}" if root else relative
326
-
327
-
328
- __all__ = [
329
- "DEFAULT_MAX_MOVES",
330
- "REORG_KIND",
331
- "TOPIC_ROOT",
332
- "apply_reorganization",
333
- "plan_reorganization",
334
- "propose_reorganization",
335
- ]
@@ -1,112 +0,0 @@
1
- """Review-queue persistence extracted from WorkspaceOSStore (v9.9.6).
2
-
3
- Workspace-scoped suggestion inbox: automation/trigger runs write drafts here
4
- for the user to approve/dismiss/snooze. **Persistence only** — the transition
5
- policy lives in :mod:`latticeai.services.review_queue`.
6
-
7
- Behaviour-preserving move (review 2026-07-27 P2 #8 "대형 모듈 추가 분해 —
8
- 동작 보존 이동 우선"): the store delegates the same method names to this
9
- collaborator, following the existing ``WorkspaceRuns`` / ``WorkspaceGraphTrace``
10
- pattern.
11
- """
12
- from __future__ import annotations
13
-
14
- from typing import Any, Dict, List, Optional
15
-
16
- from .timeutil import now_iso as _now
17
- from .workspace_os_utils import WorkspaceStoreMixin, _json_hash, _listify
18
-
19
-
20
- class WorkspaceReviewItems(WorkspaceStoreMixin):
21
- def create_review_item(
22
- self,
23
- *,
24
- title: str,
25
- summary: str = "",
26
- source: str = "workflow_run",
27
- kind: str = "suggestion",
28
- payload: Optional[Dict[str, Any]] = None,
29
- provenance: Optional[Dict[str, Any]] = None,
30
- user_email: Optional[str] = None,
31
- workspace_id: Optional[str] = None,
32
- ) -> Dict[str, Any]:
33
- if not str(title or "").strip():
34
- raise ValueError("title is required")
35
- state = self.load_state()
36
- resolved_workspace = self._resolve_scope(workspace_id, state)
37
- now = _now()
38
- # ``now`` has second resolution, so two identical drafts written inside
39
- # the same second would hash to the same id and the second one would be
40
- # unreachable — ``get``/``update`` both return the first match. Rehash
41
- # with a sequence suffix until the candidate is unique. The no-collision
42
- # path is unchanged, so existing ids keep their bytes.
43
- existing_ids = {it.get("id") for it in _listify(state.get("review_items"))}
44
- item_id = f"review-{_json_hash([title, source, kind, user_email, now])[:16]}"
45
- seq = 0
46
- while item_id in existing_ids:
47
- seq += 1
48
- item_id = f"review-{_json_hash([title, source, kind, user_email, now, seq])[:16]}"
49
- item = {
50
- "id": item_id,
51
- "status": "pending",
52
- "title": title,
53
- "summary": summary or "",
54
- "source": source or "workflow_run",
55
- "kind": kind or "suggestion",
56
- "payload": dict(payload or {}),
57
- "provenance": dict(provenance or {}),
58
- "snoozed_until": None,
59
- "user_email": user_email,
60
- "workspace_id": resolved_workspace,
61
- "created_at": now,
62
- "updated_at": now,
63
- }
64
- state.setdefault("review_items", []).append(item)
65
- self.save_state(state)
66
- self.record_timeline_event(
67
- "review", "review_item_created",
68
- {"item_id": item["id"], "source": item["source"], "kind": item["kind"]},
69
- workspace_id=resolved_workspace,
70
- )
71
- return item
72
-
73
- def list_review_items(
74
- self, *, workspace_id: Optional[str] = None, user_email: Optional[str] = None,
75
- source: Optional[str] = None,
76
- ) -> List[Dict[str, Any]]:
77
- items = self._scoped(_listify(self.load_state().get("review_items")), workspace_id)
78
- if user_email:
79
- items = [item for item in items if item.get("user_email") in {None, user_email}]
80
- if source:
81
- items = [item for item in items if item.get("source") == source]
82
- return list(reversed(items))
83
-
84
- def get_review_item(self, item_id: str, *, workspace_id: Optional[str] = None) -> Dict[str, Any]:
85
- item = next(
86
- (it for it in _listify(self.load_state().get("review_items")) if it.get("id") == item_id),
87
- None,
88
- )
89
- if item is None or (workspace_id and self._record_workspace(item) != str(workspace_id)):
90
- raise FileNotFoundError(item_id)
91
- return item
92
-
93
- def update_review_item(
94
- self, item_id: str, *, workspace_id: Optional[str] = None, **fields: Any,
95
- ) -> Dict[str, Any]:
96
- state = self.load_state()
97
- item = next((it for it in _listify(state.get("review_items")) if it.get("id") == item_id), None)
98
- if item is None or (workspace_id and self._record_workspace(item) != str(workspace_id)):
99
- raise FileNotFoundError(item_id)
100
- for key, value in fields.items():
101
- item[key] = value
102
- item["updated_at"] = _now()
103
- self.save_state(state)
104
- self.record_timeline_event(
105
- "review", "review_item_updated",
106
- {"item_id": item_id, "status": item.get("status")},
107
- workspace_id=self._record_workspace(item),
108
- )
109
- return item
110
-
111
-
112
- __all__ = ["WorkspaceReviewItems"]