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,478 +0,0 @@
1
- """One question, end to end: ask, render honestly, and get approval before acting.
2
-
3
- The conversational half of the bridge. :func:`ask_ai` is the single door to the
4
- Lattice server for a user's message; everything else in this module renders what
5
- came back or negotiates what happens next:
6
-
7
- * **honest rendering** β€” the artifact card, the grounding badge and the run
8
- explanation report exactly the flags the server issued. An absent flag is
9
- never promoted into a claim (SURFACE_PARITY, v9.9.6/9.9.7).
10
- * **Review Center** β€” staged change proposals with inline approve/reject over
11
- the same ``/api/proposals`` surface the web app uses.
12
- * **plan approval** β€” the human-in-the-loop pause. ``_bot_pending_plans`` is the
13
- bridge's only mutable module state and lives *here*, beside the two functions
14
- that write and consume it, so a stub or a reset reaches the real dictionary.
15
-
16
- Stubbing note: ``_server_client``, ``ask_ai``, ``send_message``,
17
- ``send_chat_action``, ``send_generated_files``, ``send_preview_links``,
18
- ``send_run_explanation`` and ``send_artifact_card`` are read as *this* module's
19
- globals, so a test standing in for any of them patches this module.
20
- """
21
-
22
- import json
23
- from typing import Any, Dict
24
-
25
- from latticeai.core.logging_safety import safe_log_text
26
- from latticeai.core.quiet import quiet
27
-
28
- from .config import (
29
- AGENT_RESUME_URL,
30
- AGENT_URL,
31
- CHAT_URL,
32
- PROPOSALS_URL,
33
- _server_client,
34
- logger,
35
- )
36
- from .helpers import (
37
- collect_generated_files,
38
- collect_preview_urls,
39
- send_chat_action,
40
- send_generated_files,
41
- send_message,
42
- send_preview_links,
43
- )
44
-
45
- # ── AI chat ───────────────────────────────────────────────────────────────────
46
-
47
- async def ask_ai(client, message, image_data=None, agent_mode=False,
48
- planning_model=None, executing_model=None, reviewing_model=None):
49
- try:
50
- if agent_mode and not image_data:
51
- url = AGENT_URL
52
- payload = {
53
- "message": message, "source": "telegram",
54
- "human_in_loop": True,
55
- "planning_model": planning_model,
56
- "executing_model": executing_model,
57
- "reviewing_model": reviewing_model,
58
- }
59
- else:
60
- url = CHAT_URL
61
- payload = {"message": message, "source": "telegram", "stream": False}
62
- if image_data:
63
- payload["image_data"] = image_data
64
- async with _server_client() as sc:
65
- res = await sc.post(url, json=payload, timeout=300.0)
66
- if res.status_code == 200:
67
- ct = res.headers.get("content-type", "")
68
- if "text/event-stream" in ct:
69
- text = ""
70
- for line in res.text.splitlines():
71
- if line.startswith("data:"):
72
- try:
73
- chunk = json.loads(line[5:].strip()).get("chunk", "")
74
- text += chunk
75
- except Exception:
76
- quiet()
77
- return {"response": text.strip() or "⚠️ 빈 응닡"}
78
- return res.json()
79
- try:
80
- detail = res.json().get("detail", "")
81
- except Exception:
82
- detail = ""
83
- if res.status_code == 400 and "model" in detail.lower():
84
- return {"response": "⚠️ λ‘œλ“œλœ λͺ¨λΈμ΄ μ—†μŠ΅λ‹ˆλ‹€. λ¨Όμ € /model λͺ…λ ΉμœΌλ‘œ λͺ¨λΈμ„ μ„ νƒν•΄μ£Όμ„Έμš”."}
85
- return {"response": f"❌ μ„œλ²„ μ—λŸ¬ ({res.status_code}){': ' + detail if detail else ''}"}
86
- except Exception as e:
87
- return {"response": f"❌ μ„œλ²„ μ—°κ²° μ‹€νŒ¨: {e}"}
88
-
89
- def format_artifact_card(data, *, language: str = "ko") -> str:
90
- """Artifact card summary for a finished run (v10.4.0).
91
-
92
- Telegram used to send the produced files and nothing else, so a
93
- deterministically repaired scaffold arrived looking exactly like clean
94
- model output β€” SURFACE_PARITY recorded that as ◐. This renders the same
95
- ``artifacts[]`` flags the web cards show, and reports only what the server
96
- said: an absent flag is never promoted into a claim of validity.
97
- """
98
- if not isinstance(data, dict):
99
- return ""
100
- artifacts = data.get("artifacts")
101
- if not isinstance(artifacts, list) or not artifacts:
102
- return ""
103
- korean = not str(language or "ko").startswith("en")
104
- lines = ["πŸ“„ λ§Œλ“  파일" if korean else "πŸ“„ Files produced"]
105
- for item in artifacts[:8]:
106
- if not isinstance(item, dict):
107
- continue
108
- path = str(item.get("path") or "").strip()
109
- if not path:
110
- continue
111
- notes = []
112
- size = item.get("bytes")
113
- if isinstance(size, (int, float)) and size > 0:
114
- notes.append(f"{int(size):,} B")
115
- if item.get("repaired") is True:
116
- notes.append("μžλ™ 보정됨" if korean else "auto-repaired")
117
- if item.get("valid") is False:
118
- notes.append("검증 μ‹€νŒ¨" if korean else "failed validation")
119
- suffix = f" ({' Β· '.join(notes)})" if notes else ""
120
- lines.append(f"β€’ {path}{suffix}")
121
- extra = len(artifacts) - 8
122
- if extra > 0:
123
- lines.append(f"… +{extra}")
124
- return "\n".join(lines) if len(lines) > 1 else ""
125
-
126
-
127
- async def send_artifact_card(client, chat_id, data, *, language: str = "ko") -> None:
128
- text = format_artifact_card(data, language=language)
129
- if text:
130
- await send_message(client, chat_id, text)
131
-
132
-
133
- def format_grounding_badge(data, *, language: str = "ko") -> str:
134
- """Answer-grounding badge line for a `/chat` reply (v9.9.7).
135
-
136
- Telegram used to show only the answer text, so a reply the Brain could not
137
- ground looked identical to one built on real sources. This renders exactly
138
- the verdict the server issued β€” and reports an absent verdict as unknown
139
- rather than promoting it to "근거 있음".
140
- """
141
- if not isinstance(data, dict):
142
- return ""
143
- grounding = data.get("grounding")
144
- if not isinstance(grounding, dict):
145
- return ""
146
- status = str(grounding.get("status") or "")
147
- if not status:
148
- return ""
149
- korean = not str(language or "ko").startswith("en")
150
- cited = [
151
- str(item.get("title") or "").strip()
152
- for item in (grounding.get("cited") or [])
153
- if isinstance(item, dict) and str(item.get("title") or "").strip()
154
- ]
155
- if status == "supported":
156
- head = "βœ… κ·Όκ±° 있음" if korean else "βœ… grounded"
157
- if cited:
158
- head += " β€” " + ", ".join(cited[:3])
159
- return head
160
- if status in {"unsupported", "no_context"}:
161
- head = "⚠️ κ·Όκ±° μ—†μŒ" if korean else "⚠️ not grounded"
162
- reason = str(grounding.get("reason") or "").strip()
163
- return f"{head} β€” {reason}" if reason else head
164
- return "❔ κ·Όκ±° 확인 λΆˆκ°€" if korean else "❔ grounding unknown"
165
-
166
-
167
- async def send_grounding_badge(client, chat_id, data):
168
- text = format_grounding_badge(data)
169
- if text:
170
- await send_message(client, chat_id, text)
171
-
172
-
173
- def format_proposals(payload) -> list:
174
- """`GET /api/proposals` β†’ ``[(item_id, label)]`` for the Review Center.
175
-
176
- Rows without an id are dropped: an un-actionable row is worse than none.
177
- """
178
- root = payload if isinstance(payload, dict) else {}
179
- rows = payload if isinstance(payload, list) else (root.get("items") or [])
180
- out = []
181
- for raw in rows if isinstance(rows, list) else []:
182
- if not isinstance(raw, dict):
183
- continue
184
- item_id = str(raw.get("id") or "").strip()
185
- if not item_id:
186
- continue
187
- raw_body = raw.get("payload")
188
- raw_prov = raw.get("provenance")
189
- body: Dict[str, Any] = raw_body if isinstance(raw_body, dict) else {}
190
- provenance: Dict[str, Any] = raw_prov if isinstance(raw_prov, dict) else {}
191
- path = str(body.get("path") or provenance.get("path") or "")
192
- title = str(raw.get("title") or path or item_id)
193
- change_class = str(body.get("change_class") or provenance.get("change_class") or "")
194
- label = title if not change_class else f"{title} ({change_class})"
195
- out.append((item_id, label))
196
- return out
197
-
198
-
199
- async def show_review_center(client, chat_id):
200
- """Review Center parity (v9.9.7): staged change proposals with inline
201
- approve/reject, using the same `/api/proposals` surface as the web app."""
202
- try:
203
- async with _server_client() as sc:
204
- res = await sc.get(PROPOSALS_URL, timeout=30.0)
205
- if res.status_code != 200:
206
- await send_message(client, chat_id, f"검토함을 λΆˆλŸ¬μ˜€μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€ ({res.status_code})")
207
- return
208
- items = format_proposals(res.json())
209
- except Exception as exc:
210
- await send_message(client, chat_id, f"검토함 쑰회 μ‹€νŒ¨: {exc}")
211
- return
212
- if not items:
213
- await send_message(client, chat_id, "πŸ—‚ κ²€ν† ν•  λ³€κ²½ μ œμ•ˆμ΄ μ—†μŠ΅λ‹ˆλ‹€.")
214
- return
215
- lines = ["πŸ—‚ λ³€κ²½ μ œμ•ˆ (μŠΉμΈν•΄μ•Ό μ μš©λ©λ‹ˆλ‹€)"]
216
- keyboard = []
217
- for item_id, label in items[:8]:
218
- lines.append(f"- {label}")
219
- keyboard.append([
220
- {"text": f"βœ… 승인 β€” {label}"[:64], "callback_data": f"proposal:approve:{item_id}"[:64]},
221
- {"text": "❌ 거절"[:64], "callback_data": f"proposal:reject:{item_id}"[:64]},
222
- ])
223
- if len(items) > 8:
224
- lines.append(f"… μ™Έ {len(items) - 8}건")
225
- await send_message(
226
- client, chat_id, "\n".join(lines),
227
- reply_markup={"inline_keyboard": keyboard},
228
- )
229
-
230
-
231
- async def handle_proposal_callback(client, chat_id, data: str):
232
- """Apply or reject one staged proposal.
233
-
234
- A 409 means the target file changed since staging β€” nothing was written,
235
- and the user is told exactly that instead of a silent retry.
236
- """
237
- try:
238
- _, decision, item_id = data.split(":", 2)
239
- except ValueError:
240
- return
241
- if not item_id:
242
- return
243
- suffix = "approve" if decision == "approve" else "reject"
244
- url = f"{PROPOSALS_URL}/{item_id}/{suffix}"
245
- try:
246
- async with _server_client() as sc:
247
- res = await sc.post(url, json={}, timeout=120.0)
248
- except Exception as exc:
249
- await send_message(client, chat_id, f"❌ 처리 μ‹€νŒ¨: {exc}")
250
- return
251
- if res.status_code == 409:
252
- await send_message(
253
- client, chat_id,
254
- "⚠️ μ œμ•ˆμ„ λ§Œλ“  λ’€ 파일이 λ°”λ€Œμ–΄μ„œ μ μš©ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 아무것도 μ“°μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.",
255
- )
256
- return
257
- if res.status_code != 200:
258
- await send_message(client, chat_id, f"❌ μ„œλ²„ μ—λŸ¬ ({res.status_code})")
259
- return
260
- if suffix == "approve":
261
- try:
262
- applied = res.json().get("path") or item_id
263
- except Exception:
264
- applied = item_id
265
- await send_message(client, chat_id, f"βœ… μ μš©ν–ˆμŠ΅λ‹ˆλ‹€: {applied}")
266
- else:
267
- await send_message(client, chat_id, "🚫 μ œμ•ˆμ„ κ±°μ ˆν–ˆμŠ΅λ‹ˆλ‹€.")
268
-
269
-
270
- def format_run_explanation(agent_data, *, language: str = "ko") -> str:
271
- """Plain-language outcome line for a finished agent run (v9.9.6).
272
-
273
- Telegram used to show only the final message, so a NEEDS_REVIEW run read
274
- exactly like a success. The server already computes the honest sentence
275
- (`explanation`); this renders it. Returns "" when there is nothing to add
276
- β€” a clean, verified run gets no extra noise.
277
- """
278
- if not isinstance(agent_data, dict):
279
- return ""
280
- explanation = agent_data.get("explanation")
281
- if not isinstance(explanation, dict):
282
- return ""
283
- lang = "ko" if str(language or "ko").startswith("ko") else "en"
284
-
285
- def pick(entry):
286
- return str(entry.get(lang) or "") if isinstance(entry, dict) else ""
287
-
288
- details = [pick(item) for item in explanation.get("details") or []]
289
- details = [line for line in details if line]
290
- if explanation.get("ok") and not details:
291
- return ""
292
- headline = pick(explanation.get("headline"))
293
- marker = "βœ…" if explanation.get("ok") else "⚠️"
294
- lines = [f"{marker} {headline}" if headline else marker]
295
- lines.extend(f"Β· {line}" for line in details[:4])
296
- return "\n".join(lines)
297
-
298
-
299
- async def send_run_explanation(client, chat_id, agent_data):
300
- text = format_run_explanation(agent_data)
301
- if text:
302
- await send_message(client, chat_id, text)
303
-
304
-
305
- # ── Plan approval (Human-in-the-loop) ────────────────────────────────────────
306
-
307
- # Pending plan approvals keyed by run_id (or legacy context_id).
308
- # Values: chat_id, models, and the resume credentials (token and/or legacy).
309
- _bot_pending_plans: dict[str, dict] = {}
310
-
311
-
312
- def _approval_pause_id(data: dict) -> str:
313
- """Stable key for a paused plan: run_id preferred, legacy context_id fallback."""
314
- return str(data.get("run_id") or data.get("context_id") or "")
315
-
316
-
317
- def _is_approval_pause(data: dict) -> bool:
318
- status = str(data.get("status") or "")
319
- return status in {"waiting_approval", "awaiting_approval"}
320
-
321
-
322
- async def send_plan_for_approval(client, chat_id, data: dict) -> None:
323
- """Show the agent plan to the user and present Done/Cancel buttons.
324
-
325
- Handles both the modern ``awaiting_approval`` (run_id + token) and the
326
- legacy ``waiting_approval`` / ``context_id`` wire contracts (v9.9.5
327
- SURFACE_PARITY β€” Telegram approval flow).
328
- """
329
- pause_id = _approval_pause_id(data)
330
- if not pause_id:
331
- await send_message(client, chat_id, "❌ μŠΉμΈν•  κ³„νšμ„ 식별할 수 μ—†μŠ΅λ‹ˆλ‹€.")
332
- return
333
- plan = data.get("plan", {}) or {}
334
- goal = plan.get("goal") or (data.get("approval") or {}).get("plan_summary") or ""
335
- steps = plan.get("steps", []) or data.get("non_auto_steps") or []
336
- p_model = data.get("planning_model", "current")
337
- e_model = data.get("executing_model", "current")
338
- r_model = data.get("reviewing_model", "current")
339
- approval = data.get("approval") or {}
340
- expires_at = approval.get("expires_at")
341
-
342
- lines = ["πŸ“‹ *ν”Œλž˜λ‹ μ™„λ£Œ* β€” μ‹€ν–‰ μ „ ν™•μΈν•΄μ£Όμ„Έμš”\n"]
343
- if goal:
344
- lines.append(f"*λͺ©ν‘œ:* {goal}\n")
345
- for i, step in enumerate(steps, 1):
346
- if isinstance(step, dict):
347
- desc = step.get("description") or step.get("action") or str(step)
348
- else:
349
- desc = str(step)
350
- lines.append(f"{i}. {desc}")
351
- lines.append(f"\n🧠 ν”Œλž˜λ‹: `{p_model}`")
352
- lines.append(f"βš™οΈ μ‹€ν–‰: `{e_model}`")
353
- lines.append(f"πŸ” κ²€ν† : `{r_model}`")
354
- if expires_at:
355
- lines.append(f"⏳ 승인 만료: `{expires_at}`")
356
-
357
- _bot_pending_plans[pause_id] = {
358
- "chat_id": chat_id,
359
- "run_id": data.get("run_id") or pause_id,
360
- "context_id": data.get("context_id"),
361
- "approval_token": approval.get("token"),
362
- "legacy": data.get("status") == "waiting_approval" or bool(data.get("context_id")),
363
- "executing_model": data.get("executing_model"),
364
- "reviewing_model": data.get("reviewing_model"),
365
- }
366
-
367
- # callback_data max 64 bytes β€” pause ids are token_urlsafe(16) (~22 chars).
368
- keyboard = {"inline_keyboard": [[
369
- {"text": "βœ… Done β€” μ‹€ν–‰ μ‹œμž‘", "callback_data": f"plan:approve:{pause_id}"},
370
- {"text": "❌ μ·¨μ†Œ", "callback_data": f"plan:cancel:{pause_id}"},
371
- ]]}
372
- await send_message(client, chat_id, "\n".join(lines), reply_markup=keyboard)
373
-
374
-
375
- async def handle_plan_callback(client, chat_id, data: str) -> None:
376
- """Handle Done/Cancel callback from plan approval buttons."""
377
- parts = data.split(":", 2)
378
- if len(parts) != 3:
379
- return
380
- _, action, pause_id = parts
381
- pending = _bot_pending_plans.get(pause_id)
382
-
383
- # A callback is bound to the chat that received the plan. Even two allowed
384
- # chats cannot approve or cancel each other's plan by replaying callback
385
- # data.
386
- if pending and int(pending.get("chat_id") or 0) != int(chat_id):
387
- await send_message(client, chat_id, "❌ λ‹€λ₯Έ μ±„νŒ…μ˜ μž‘μ—…μ€ μ²˜λ¦¬ν•  수 μ—†μŠ΅λ‹ˆλ‹€.")
388
- return
389
- pending = _bot_pending_plans.pop(pause_id, None)
390
-
391
- if action == "cancel":
392
- if pending:
393
- try:
394
- resume_body = _resume_payload(pending, approved=False)
395
- async with _server_client() as sc:
396
- await sc.post(AGENT_RESUME_URL, json=resume_body, timeout=30.0)
397
- except Exception as exc: # noqa: BLE001 β€” cancel is best-effort
398
- logger.warning("telegram cancel resume failed: %s", safe_log_text(exc))
399
- await send_message(client, chat_id, "❌ μž‘μ—…μ΄ μ·¨μ†Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
400
- return
401
- if not pending:
402
- await send_message(client, chat_id, "❌ μž‘μ—…μ΄ μ·¨μ†Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
403
- return
404
-
405
- await send_message(client, chat_id, "βš™οΈ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€. μž μ‹œ κΈ°λ‹€λ €μ£Όμ„Έμš”...")
406
- await send_chat_action(client, chat_id, "typing")
407
-
408
- try:
409
- async with _server_client() as sc:
410
- res = await sc.post(
411
- AGENT_RESUME_URL,
412
- json=_resume_payload(pending, approved=True),
413
- timeout=300.0,
414
- )
415
- data_r = res.json() if res.status_code == 200 else {}
416
- ans = data_r.get("response", f"❌ μ„œλ²„ μ—λŸ¬ ({res.status_code})")
417
- await send_message(client, chat_id, str(ans))
418
- if isinstance(data_r, dict):
419
- await send_run_explanation(client, chat_id, data_r)
420
- await send_artifact_card(client, chat_id, data_r)
421
- await send_generated_files(client, chat_id, collect_generated_files(data_r))
422
- await send_preview_links(client, chat_id, collect_preview_urls(data_r))
423
- except Exception as e:
424
- await send_message(client, chat_id, f"❌ μ‹€ν–‰ 쀑 였λ₯˜: {e}")
425
-
426
-
427
- def _resume_payload(pending: dict, *, approved: bool) -> dict:
428
- """Build /agent/resume body: token path preferred, else legacy context_id."""
429
- body: dict = {
430
- "approved": approved,
431
- "executing_model": pending.get("executing_model"),
432
- "reviewing_model": pending.get("reviewing_model"),
433
- }
434
- token = pending.get("approval_token")
435
- run_id = pending.get("run_id")
436
- # Unified durable store (9.9.5): token-gated resume works for both the
437
- # modern awaiting_approval and the legacy human_in_loop pause.
438
- if token and run_id:
439
- body["run_id"] = run_id
440
- body["approval_token"] = token
441
- return body
442
- body["context_id"] = pending.get("context_id") or run_id
443
- return body
444
-
445
-
446
- # ── AI request task ───────────────────────────────────────────────────────────
447
-
448
- async def process_ai_request(client, chat_id, user_text, image_data=None):
449
- try:
450
- await send_chat_action(client, chat_id, "upload_photo" if image_data else "typing")
451
- logger.info("ask_ai 호좜 μ‹œμž‘: chat_id=%s text=%r", chat_id, safe_log_text(user_text[:30]))
452
- data = await ask_ai(client, user_text, image_data, agent_mode=not image_data)
453
- logger.info("ask_ai μ™„λ£Œ: chat_id=%s result_keys=%s", chat_id, list(data.keys()) if isinstance(data, dict) else type(data))
454
-
455
- # Approval pause (legacy waiting_approval or modern awaiting_approval)
456
- if isinstance(data, dict) and _is_approval_pause(data):
457
- await send_plan_for_approval(client, chat_id, data)
458
- return
459
-
460
- ans = data.get("response", str(data)) if isinstance(data, dict) else str(data)
461
- if not ans or not str(ans).strip():
462
- ans = "⚠️ AIκ°€ 닡변을 μƒμ„±ν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€."
463
- await send_message(client, chat_id, str(ans))
464
- if isinstance(data, dict):
465
- # Recall parity (v9.9.7): the same grounding verdict the web app
466
- # badges, never promoted when the server issued none.
467
- await send_grounding_badge(client, chat_id, data)
468
- if not image_data and isinstance(data, dict):
469
- await send_run_explanation(client, chat_id, data)
470
- await send_artifact_card(client, chat_id, data)
471
- await send_generated_files(client, chat_id, collect_generated_files(data))
472
- await send_preview_links(client, chat_id, collect_preview_urls(data))
473
- except Exception as e:
474
- logger.error("process_ai_request μ‹€νŒ¨ (chat_id=%s): %s", chat_id, safe_log_text(e), exc_info=True)
475
- try:
476
- await send_message(client, chat_id, "⚠️ 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.")
477
- except Exception:
478
- quiet()