ltcai 11.5.2 → 11.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (443) hide show
  1. package/README.md +93 -148
  2. package/bin/ltcai.js +234 -24
  3. package/docs/CHANGELOG.md +119 -0
  4. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  5. package/docs/DEVELOPMENT.md +8 -6
  6. package/docs/ENTERPRISE.md +2 -1
  7. package/docs/MULTI_AGENT_RUNTIME.md +12 -5
  8. package/docs/ONBOARDING.md +1 -1
  9. package/docs/OPERATIONS.md +6 -3
  10. package/docs/REALTIME_COLLABORATION.md +1 -1
  11. package/docs/TRUST_MODEL.md +1 -1
  12. package/docs/WHY_LATTICE.md +1 -1
  13. package/docs/WORKFLOW_DESIGNER.md +3 -2
  14. package/docs/kg-schema.md +1 -1
  15. package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
  16. package/lattice_brain/__init__.py +42 -79
  17. package/lattice_brain/graph/__init__.py +9 -24
  18. package/lattice_brain/graph/_kg_common/__init__.py +13 -24
  19. package/lattice_brain/ingestion/__init__.py +19 -58
  20. package/lattice_brain/ingestion/pipeline.py +20 -398
  21. package/lattice_brain/multimodal/__init__.py +7 -18
  22. package/lattice_brain/multimodal/images.py +6 -264
  23. package/lattice_brain/multimodal/video.py +8 -247
  24. package/lattice_brain/runtime/__init__.py +8 -79
  25. package/lattice_brain/runtime/hooks.py +22 -584
  26. package/latticeai/__init__.py +1 -1
  27. package/latticeai/api/agent_worker_seam.py +7 -80
  28. package/latticeai/api/health.py +6 -20
  29. package/latticeai/api/local_files.py +16 -613
  30. package/latticeai/api/models.py +12 -87
  31. package/latticeai/api/search.py +17 -255
  32. package/latticeai/api/tools.py +69 -750
  33. package/latticeai/api/voice_capture.py +8 -70
  34. package/latticeai/api/worker_compute.py +842 -0
  35. package/latticeai/api/worker_seams.py +216 -0
  36. package/latticeai/app_factory.py +23 -232
  37. package/latticeai/cli/entrypoint.py +36 -249
  38. package/latticeai/core/agent_permission.py +16 -91
  39. package/latticeai/core/messages.py +89 -531
  40. package/latticeai/runtime/access_runtime.py +0 -14
  41. package/latticeai/runtime/bootstrap.py +10 -21
  42. package/latticeai/runtime/brain_runtime.py +35 -43
  43. package/latticeai/runtime/build_phases/__init__.py +21 -37
  44. package/latticeai/runtime/build_phases/features.py +99 -389
  45. package/latticeai/runtime/build_phases/foundation.py +82 -392
  46. package/latticeai/runtime/build_phases/web.py +91 -408
  47. package/latticeai/runtime/build_phases/worker_profile.py +244 -0
  48. package/latticeai/runtime/lifespan_runtime.py +7 -16
  49. package/latticeai/runtime/platform_services_runtime.py +1 -15
  50. package/latticeai/runtime/runtime_context.py +13 -130
  51. package/latticeai/runtime/security_runtime.py +10 -103
  52. package/latticeai/services/architecture_readiness.py +82 -43
  53. package/latticeai/services/model_runtime/__init__.py +2 -11
  54. package/latticeai/services/model_runtime/service.py +4 -28
  55. package/latticeai/services/p_reinforce.py +10 -261
  56. package/latticeai/services/product_readiness.py +31 -38
  57. package/latticeai/services/search_service.py +37 -795
  58. package/latticeai/services/tool_dispatch.py +30 -386
  59. package/latticeai/services/voice_capture.py +13 -107
  60. package/latticeai/tools/__init__.py +22 -49
  61. package/latticeai/tools/commands.py +0 -163
  62. package/latticeai/tools/computer.py +0 -39
  63. package/latticeai/tools/documents.py +1 -134
  64. package/latticeai/tools/filesystem.py +1 -247
  65. package/latticeai/tools/knowledge.py +1 -52
  66. package/latticeai/tools/local_files.py +0 -20
  67. package/latticeai/worker_app.py +75 -0
  68. package/package.json +2 -3
  69. package/requirements.txt +0 -5
  70. package/scripts/agent_eval.py +16 -28
  71. package/scripts/brain_quality_eval.py +20 -183
  72. package/scripts/bump_version.py +5 -5
  73. package/scripts/check_current_release_docs.mjs +7 -4
  74. package/scripts/check_openapi_drift.mjs +10 -0
  75. package/scripts/check_server_i18n.mjs +2 -18
  76. package/scripts/compose_openapi.py +377 -0
  77. package/scripts/export_openapi.py +32 -4
  78. package/scripts/gen_messages_catalog_fixture.py +302 -0
  79. package/scripts/gen_openapi_fragments.py +365 -0
  80. package/scripts/gen_redact_fixture.py +196 -0
  81. package/scripts/gen_worker_allowlist_fixture.py +115 -0
  82. package/scripts/generate_agent_parity_fixtures.py +33 -14
  83. package/scripts/openapi_route_families.json +2161 -0
  84. package/scripts/release_screen_claims.json +54 -0
  85. package/scripts/run_integration_tests.mjs +134 -29
  86. package/scripts/run_sidecar_e2e.mjs +104 -11
  87. package/scripts/wheel_smoke.py +32 -22
  88. package/src-tauri/Cargo.lock +375 -9
  89. package/src-tauri/Cargo.toml +1 -1
  90. package/src-tauri/src/backend.rs +151 -45
  91. package/src-tauri/src/main.rs +18 -18
  92. package/src-tauri/src/topology.rs +58 -88
  93. package/src-tauri/tauri.conf.json +1 -2
  94. package/static/app/asset-manifest.json +41 -41
  95. package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
  96. package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
  97. package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
  98. package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
  99. package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
  100. package/static/app/assets/Capture-BsTokYkk.js +1 -0
  101. package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
  102. package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
  103. package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
  104. package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
  105. package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
  106. package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
  107. package/static/app/assets/System-CMHSO9qM.js +1 -0
  108. package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
  109. package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
  110. package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
  111. package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
  112. package/static/app/assets/circle-check-DruOxB-4.js +1 -0
  113. package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
  114. package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
  115. package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
  116. package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
  117. package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
  118. package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
  119. package/static/app/assets/index-D9x-kSNy.css +2 -0
  120. package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
  121. package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
  122. package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
  123. package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
  124. package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
  125. package/static/app/assets/search-CT9aho2j.js +1 -0
  126. package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
  127. package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
  128. package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
  129. package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
  130. package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
  131. package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
  132. package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
  133. package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
  134. package/static/app/index.html +4 -4
  135. package/static/sw.js +1 -1
  136. package/lattice_brain/archive.py +0 -522
  137. package/lattice_brain/context.py +0 -325
  138. package/lattice_brain/conversations.py +0 -382
  139. package/lattice_brain/core.py +0 -82
  140. package/lattice_brain/graph/_kg_contract.py +0 -249
  141. package/lattice_brain/graph/curator.py +0 -676
  142. package/lattice_brain/graph/discovery.py +0 -595
  143. package/lattice_brain/graph/discovery_index/__init__.py +0 -35
  144. package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
  145. package/lattice_brain/graph/discovery_index/extract.py +0 -137
  146. package/lattice_brain/graph/discovery_index/scan.py +0 -411
  147. package/lattice_brain/graph/discovery_index/upsert.py +0 -495
  148. package/lattice_brain/graph/documents.py +0 -380
  149. package/lattice_brain/graph/fusion.py +0 -395
  150. package/lattice_brain/graph/identity.py +0 -175
  151. package/lattice_brain/graph/image_vectors.py +0 -230
  152. package/lattice_brain/graph/ingest.py +0 -829
  153. package/lattice_brain/graph/network.py +0 -205
  154. package/lattice_brain/graph/proactive.py +0 -724
  155. package/lattice_brain/graph/projection/__init__.py +0 -42
  156. package/lattice_brain/graph/projection/curation.py +0 -500
  157. package/lattice_brain/graph/projection/v2_schema.py +0 -518
  158. package/lattice_brain/graph/provenance.py +0 -524
  159. package/lattice_brain/graph/rerank.py +0 -163
  160. package/lattice_brain/graph/retrieval/__init__.py +0 -54
  161. package/lattice_brain/graph/retrieval/context.py +0 -197
  162. package/lattice_brain/graph/retrieval/graph_view.py +0 -319
  163. package/lattice_brain/graph/retrieval/hybrid.py +0 -488
  164. package/lattice_brain/graph/retrieval/maintenance.py +0 -121
  165. package/lattice_brain/graph/retrieval/signals.py +0 -95
  166. package/lattice_brain/graph/retrieval_docgen.py +0 -253
  167. package/lattice_brain/graph/retrieval_policy.py +0 -180
  168. package/lattice_brain/graph/retrieval_reads.py +0 -769
  169. package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
  170. package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
  171. package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
  172. package/lattice_brain/graph/retrieval_vector/search.py +0 -560
  173. package/lattice_brain/graph/retrieval_vector/status.py +0 -374
  174. package/lattice_brain/graph/schema.py +0 -792
  175. package/lattice_brain/graph/store.py +0 -268
  176. package/lattice_brain/graph/vector_index/__init__.py +0 -85
  177. package/lattice_brain/graph/vector_index/base.py +0 -167
  178. package/lattice_brain/graph/vector_index/brute_force.py +0 -110
  179. package/lattice_brain/graph/vector_index/hnsw.py +0 -290
  180. package/lattice_brain/graph/vector_index/jobs.py +0 -287
  181. package/lattice_brain/graph/vector_index/quantized.py +0 -148
  182. package/lattice_brain/graph/vector_index/selector.py +0 -161
  183. package/lattice_brain/graph/write_master.py +0 -308
  184. package/lattice_brain/ingestion/_contract.py +0 -90
  185. package/lattice_brain/ingestion/folder_scan.py +0 -57
  186. package/lattice_brain/ingestion/folders.py +0 -258
  187. package/lattice_brain/ingestion/jobs_api.py +0 -107
  188. package/lattice_brain/ingestion/routing.py +0 -295
  189. package/lattice_brain/ingestion_jobs.py +0 -380
  190. package/lattice_brain/memory.py +0 -75
  191. package/lattice_brain/portability/__init__.py +0 -90
  192. package/lattice_brain/portability/_contract.py +0 -42
  193. package/lattice_brain/portability/backups.py +0 -338
  194. package/lattice_brain/portability/bundles.py +0 -136
  195. package/lattice_brain/portability/constants.py +0 -93
  196. package/lattice_brain/portability/fsops.py +0 -138
  197. package/lattice_brain/portability/service.py +0 -41
  198. package/lattice_brain/portability/sharing.py +0 -710
  199. package/lattice_brain/quality.py +0 -543
  200. package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
  201. package/lattice_brain/runtime/agent_runtime.py +0 -859
  202. package/lattice_brain/runtime/contracts.py +0 -460
  203. package/lattice_brain/runtime/multi_agent.py +0 -942
  204. package/lattice_brain/runtime/statuses.py +0 -10
  205. package/lattice_brain/sealed_box.py +0 -240
  206. package/lattice_brain/self_model.py +0 -675
  207. package/lattice_brain/sensitivity.py +0 -94
  208. package/lattice_brain/storage/__init__.py +0 -22
  209. package/lattice_brain/storage/base.py +0 -100
  210. package/lattice_brain/storage/docker.py +0 -105
  211. package/lattice_brain/storage/factory.py +0 -31
  212. package/lattice_brain/storage/migration.py +0 -191
  213. package/lattice_brain/storage/postgres.py +0 -123
  214. package/lattice_brain/storage/sqlite.py +0 -143
  215. package/lattice_brain/synthesis.py +0 -824
  216. package/lattice_brain/workflow.py +0 -497
  217. package/latticeai/api/admin.py +0 -471
  218. package/latticeai/api/agent_registry.py +0 -105
  219. package/latticeai/api/agents.py +0 -228
  220. package/latticeai/api/auth.py +0 -383
  221. package/latticeai/api/automation_intelligence.py +0 -401
  222. package/latticeai/api/brain_intelligence.py +0 -199
  223. package/latticeai/api/browser.py +0 -493
  224. package/latticeai/api/change_proposals.py +0 -89
  225. package/latticeai/api/chat.py +0 -572
  226. package/latticeai/api/chat_agent_http.py +0 -892
  227. package/latticeai/api/chat_contracts.py +0 -73
  228. package/latticeai/api/chat_documents.py +0 -276
  229. package/latticeai/api/chat_helpers.py +0 -460
  230. package/latticeai/api/chat_history.py +0 -101
  231. package/latticeai/api/chat_hybrid.py +0 -113
  232. package/latticeai/api/chat_intents.py +0 -646
  233. package/latticeai/api/chat_stream.py +0 -216
  234. package/latticeai/api/chronicle.py +0 -63
  235. package/latticeai/api/command_center.py +0 -51
  236. package/latticeai/api/computer_use.py +0 -474
  237. package/latticeai/api/evidence_actions.py +0 -48
  238. package/latticeai/api/features.py +0 -70
  239. package/latticeai/api/funnel_metrics.py +0 -31
  240. package/latticeai/api/garden.py +0 -34
  241. package/latticeai/api/hooks.py +0 -165
  242. package/latticeai/api/index_jobs.py +0 -145
  243. package/latticeai/api/invitations.py +0 -100
  244. package/latticeai/api/knowledge_graph.py +0 -536
  245. package/latticeai/api/marketplace.py +0 -105
  246. package/latticeai/api/mcp.py +0 -482
  247. package/latticeai/api/memory.py +0 -270
  248. package/latticeai/api/network.py +0 -81
  249. package/latticeai/api/network_boundary.py +0 -225
  250. package/latticeai/api/permission_mode.py +0 -61
  251. package/latticeai/api/permissions.py +0 -436
  252. package/latticeai/api/plugins.py +0 -126
  253. package/latticeai/api/portability.py +0 -391
  254. package/latticeai/api/project_sessions.py +0 -114
  255. package/latticeai/api/realtime.py +0 -118
  256. package/latticeai/api/review_queue.py +0 -364
  257. package/latticeai/api/security_dashboard.py +0 -604
  258. package/latticeai/api/setup.py +0 -319
  259. package/latticeai/api/static_routes.py +0 -354
  260. package/latticeai/api/ui_redirects.py +0 -26
  261. package/latticeai/api/workflow_designer.py +0 -394
  262. package/latticeai/api/workspace.py +0 -856
  263. package/latticeai/api/workspace_scope.py +0 -125
  264. package/latticeai/core/agent/__init__.py +0 -93
  265. package/latticeai/core/agent/_contract.py +0 -79
  266. package/latticeai/core/agent/context.py +0 -57
  267. package/latticeai/core/agent/deps.py +0 -125
  268. package/latticeai/core/agent/execution.py +0 -622
  269. package/latticeai/core/agent/planning.py +0 -145
  270. package/latticeai/core/agent/recovery.py +0 -157
  271. package/latticeai/core/agent/runtime.py +0 -210
  272. package/latticeai/core/agent/verification.py +0 -231
  273. package/latticeai/core/agent_eval.py +0 -739
  274. package/latticeai/core/agent_helpers.py +0 -493
  275. package/latticeai/core/agent_profiles.py +0 -110
  276. package/latticeai/core/agent_prompts.py +0 -171
  277. package/latticeai/core/agent_registry.py +0 -232
  278. package/latticeai/core/agent_state.py +0 -41
  279. package/latticeai/core/agent_trace.py +0 -104
  280. package/latticeai/core/artifact_ledger.py +0 -109
  281. package/latticeai/core/audit.py +0 -260
  282. package/latticeai/core/builtin_hooks.py +0 -105
  283. package/latticeai/core/context_builder.py +0 -394
  284. package/latticeai/core/document_generator.py +0 -103
  285. package/latticeai/core/enterprise.py +0 -154
  286. package/latticeai/core/enterprise_admin.py +0 -158
  287. package/latticeai/core/file_generation/__init__.py +0 -115
  288. package/latticeai/core/file_generation/bundles.py +0 -76
  289. package/latticeai/core/file_generation/extraction.py +0 -154
  290. package/latticeai/core/file_generation/inference.py +0 -235
  291. package/latticeai/core/file_generation/orchestration.py +0 -152
  292. package/latticeai/core/file_generation/prompting.py +0 -117
  293. package/latticeai/core/file_generation/repair.py +0 -114
  294. package/latticeai/core/file_generation/sanitize.py +0 -61
  295. package/latticeai/core/file_generation/validation.py +0 -201
  296. package/latticeai/core/invitations.py +0 -132
  297. package/latticeai/core/legacy_compatibility.py +0 -243
  298. package/latticeai/core/logging_safety.py +0 -46
  299. package/latticeai/core/marketplace.py +0 -293
  300. package/latticeai/core/mcp_catalog.py +0 -452
  301. package/latticeai/core/mcp_registry.py +0 -506
  302. package/latticeai/core/network_boundary.py +0 -168
  303. package/latticeai/core/oidc.py +0 -208
  304. package/latticeai/core/plugins.py +0 -432
  305. package/latticeai/core/product_hardening.py +0 -218
  306. package/latticeai/core/project_sessions.py +0 -337
  307. package/latticeai/core/realtime.py +0 -238
  308. package/latticeai/core/run_explain.py +0 -426
  309. package/latticeai/core/run_store.py +0 -252
  310. package/latticeai/core/timezones.py +0 -80
  311. package/latticeai/core/workspace_computer_memory.py +0 -84
  312. package/latticeai/core/workspace_graph_trace.py +0 -155
  313. package/latticeai/core/workspace_indexing.py +0 -102
  314. package/latticeai/core/workspace_memory.py +0 -77
  315. package/latticeai/core/workspace_onboarding.py +0 -104
  316. package/latticeai/core/workspace_os.py +0 -978
  317. package/latticeai/core/workspace_os_constants.py +0 -126
  318. package/latticeai/core/workspace_os_state.py +0 -180
  319. package/latticeai/core/workspace_os_utils.py +0 -103
  320. package/latticeai/core/workspace_permissions.py +0 -101
  321. package/latticeai/core/workspace_plugins.py +0 -97
  322. package/latticeai/core/workspace_relationships.py +0 -99
  323. package/latticeai/core/workspace_reorganization.py +0 -335
  324. package/latticeai/core/workspace_review_items.py +0 -112
  325. package/latticeai/core/workspace_runs.py +0 -726
  326. package/latticeai/core/workspace_skills.py +0 -109
  327. package/latticeai/core/workspace_snapshots.py +0 -198
  328. package/latticeai/core/workspace_timeline.py +0 -110
  329. package/latticeai/integrations/__init__.py +0 -0
  330. package/latticeai/integrations/telegram_bot/__init__.py +0 -123
  331. package/latticeai/integrations/telegram_bot/__main__.py +0 -17
  332. package/latticeai/integrations/telegram_bot/config.py +0 -86
  333. package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
  334. package/latticeai/integrations/telegram_bot/flows.py +0 -478
  335. package/latticeai/integrations/telegram_bot/helpers.py +0 -322
  336. package/latticeai/integrations/telegram_bot/screens.py +0 -394
  337. package/latticeai/runtime/audit_runtime.py +0 -76
  338. package/latticeai/runtime/automation_runtime.py +0 -81
  339. package/latticeai/runtime/chat_wiring.py +0 -141
  340. package/latticeai/runtime/context_runtime.py +0 -66
  341. package/latticeai/runtime/feature_toggle_wiring.py +0 -157
  342. package/latticeai/runtime/history_runtime.py +0 -163
  343. package/latticeai/runtime/history_writer.py +0 -138
  344. package/latticeai/runtime/hooks_runtime.py +0 -77
  345. package/latticeai/runtime/model_wiring.py +0 -68
  346. package/latticeai/runtime/namespace_runtime.py +0 -163
  347. package/latticeai/runtime/network_boundary_wiring.py +0 -117
  348. package/latticeai/runtime/network_config_runtime.py +0 -56
  349. package/latticeai/runtime/permission_mode_wiring.py +0 -112
  350. package/latticeai/runtime/persistence_runtime.py +0 -159
  351. package/latticeai/runtime/platform_runtime_wiring.py +0 -89
  352. package/latticeai/runtime/review_wiring.py +0 -42
  353. package/latticeai/runtime/router_registration.py +0 -693
  354. package/latticeai/runtime/service_singletons.py +0 -55
  355. package/latticeai/runtime/sso_config_runtime.py +0 -128
  356. package/latticeai/runtime/user_key_runtime.py +0 -106
  357. package/latticeai/runtime/web_runtime.py +0 -92
  358. package/latticeai/server_app.py +0 -51
  359. package/latticeai/services/app_context.py +0 -130
  360. package/latticeai/services/automation_execution.py +0 -266
  361. package/latticeai/services/automation_intelligence.py +0 -614
  362. package/latticeai/services/brain_automation.py +0 -191
  363. package/latticeai/services/brain_intelligence/__init__.py +0 -58
  364. package/latticeai/services/brain_intelligence/_contract.py +0 -71
  365. package/latticeai/services/brain_intelligence/consistency.py +0 -193
  366. package/latticeai/services/brain_intelligence/constants.py +0 -47
  367. package/latticeai/services/brain_intelligence/digest.py +0 -258
  368. package/latticeai/services/brain_intelligence/health.py +0 -331
  369. package/latticeai/services/brain_intelligence/proposals.py +0 -259
  370. package/latticeai/services/brain_intelligence/sampling.py +0 -84
  371. package/latticeai/services/brain_intelligence/service.py +0 -48
  372. package/latticeai/services/change_proposals.py +0 -471
  373. package/latticeai/services/chat_service.py +0 -243
  374. package/latticeai/services/chronicle.py +0 -555
  375. package/latticeai/services/cloud_egress_audit.py +0 -85
  376. package/latticeai/services/cloud_extraction.py +0 -129
  377. package/latticeai/services/cloud_streaming.py +0 -268
  378. package/latticeai/services/cloud_token_guard.py +0 -84
  379. package/latticeai/services/command_center.py +0 -548
  380. package/latticeai/services/evidence_actions.py +0 -258
  381. package/latticeai/services/feature_toggles.py +0 -502
  382. package/latticeai/services/folder_watch.py +0 -520
  383. package/latticeai/services/funnel_metrics.py +0 -307
  384. package/latticeai/services/hybrid_chat.py +0 -316
  385. package/latticeai/services/hybrid_context.py +0 -228
  386. package/latticeai/services/hybrid_policy.py +0 -129
  387. package/latticeai/services/interop_bridges.py +0 -978
  388. package/latticeai/services/local_knowledge.py +0 -465
  389. package/latticeai/services/memory_service/__init__.py +0 -52
  390. package/latticeai/services/memory_service/_contract.py +0 -100
  391. package/latticeai/services/memory_service/brief.py +0 -431
  392. package/latticeai/services/memory_service/constants.py +0 -57
  393. package/latticeai/services/memory_service/maintenance.py +0 -138
  394. package/latticeai/services/memory_service/manager.py +0 -186
  395. package/latticeai/services/memory_service/proof.py +0 -136
  396. package/latticeai/services/memory_service/recall.py +0 -225
  397. package/latticeai/services/memory_service/service.py +0 -48
  398. package/latticeai/services/memory_service/stores.py +0 -110
  399. package/latticeai/services/mode_store.py +0 -132
  400. package/latticeai/services/model_recommendation.py +0 -224
  401. package/latticeai/services/model_runtime/cloud.py +0 -87
  402. package/latticeai/services/network_boundary_service.py +0 -117
  403. package/latticeai/services/obsidian_bridge.py +0 -609
  404. package/latticeai/services/openai_compatible_adapter.py +0 -101
  405. package/latticeai/services/permission_mode_service.py +0 -122
  406. package/latticeai/services/platform_runtime.py +0 -366
  407. package/latticeai/services/review_queue.py +0 -380
  408. package/latticeai/services/router_context.py +0 -59
  409. package/latticeai/services/run_executor.py +0 -387
  410. package/latticeai/services/self_model_service.py +0 -171
  411. package/latticeai/services/setup_detection.py +0 -147
  412. package/latticeai/services/triggers.py +0 -378
  413. package/latticeai/services/upload_service.py +0 -172
  414. package/latticeai/services/workspace_service.py +0 -165
  415. package/latticeai/setup/__init__.py +0 -25
  416. package/latticeai/setup/auto_setup.py +0 -846
  417. package/latticeai/setup/demo_corpus.py +0 -98
  418. package/latticeai/setup/wizard/__init__.py +0 -126
  419. package/latticeai/setup/wizard/catalog.py +0 -175
  420. package/latticeai/setup/wizard/detect.py +0 -302
  421. package/latticeai/setup/wizard/install.py +0 -348
  422. package/latticeai/setup/wizard/paths.py +0 -165
  423. package/latticeai/setup/wizard/plans.py +0 -74
  424. package/latticeai/setup/wizard/recommend.py +0 -320
  425. package/scripts/bench_agent_smoke.py +0 -409
  426. package/scripts/bench_models.py +0 -540
  427. package/scripts/bench_vector_index.py +0 -295
  428. package/scripts/funnel_soft_gate.py +0 -192
  429. package/scripts/generate_agent_loop_fixtures.py +0 -994
  430. package/scripts/generate_rust_parity_fixtures.py +0 -908
  431. package/scripts/migrate_brain_storage.py +0 -57
  432. package/scripts/parity_fixture_corpus_context.py +0 -162
  433. package/scripts/parity_fixture_corpus_docgen.py +0 -341
  434. package/scripts/profile_kg.py +0 -355
  435. package/server.py +0 -30
  436. package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
  437. package/static/app/assets/Capture-DiQ219jW.js +0 -1
  438. package/static/app/assets/System-CieofHQa.js +0 -1
  439. package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
  440. package/static/app/assets/circle-check-qqLug9nU.js +0 -1
  441. package/static/app/assets/index-DxmOfNRi.css +0 -2
  442. package/static/app/assets/primitives-SNp0LRJz.js +0 -1
  443. package/static/app/assets/search-BcHqkjoy.js +0 -1
@@ -1,12 +1,21 @@
1
- """Safe local tools for Lattice AI agent mode.
1
+ """The compute tool table — read-only tools, plus pointer actuation.
2
2
 
3
3
  All filesystem operations are confined to LATTICEAI_AGENT_ROOT, defaulting to
4
- ./agent_workspace. Command execution runs without a shell and from inside that
5
- workspace.
6
-
7
- The physical implementation belongs to ``latticeai.tools``. The historical
8
- root ``tools`` package aliases this exact module object, preserving existing
9
- imports and ``tools.AGENT_ROOT`` monkeypatch/state identity.
4
+ ./agent_workspace.
5
+
6
+ v11.6.0 §W4 moved the mutating half of this table into ``lattice-agent``: the
7
+ file writes, the todo write, the project scaffold, the vault saves, the two
8
+ desktop openers, the exec surface and the four document creators all run in the
9
+ Rust loop's own sandbox now, and the creators call ``POST /worker/render/*``
10
+ for the bytes. ``TOOL_HANDLERS`` is what a stateless worker may still be asked
11
+ to do: **twenty-five** read-only / capability handlers, plus the **six** pointer
12
+ tools the loop deliberately keeps sending here (v11.6.0 gateway integration
13
+ §4b) because mouse and keyboard actuation is ``pyautogui`` — a capability of
14
+ this interpreter, not a file write Rust can perform. A native refusal would
15
+ only take working pointer control away from an install that has it.
16
+
17
+ That is the whole seam: ``POST /agent/tool``'s whitelist is
18
+ ``registered_tools()``, and there is no second table.
10
19
  """
11
20
 
12
21
  import base64
@@ -162,20 +171,6 @@ from latticeai.tools.network import * # noqa: E402,F401,F403
162
171
 
163
172
 
164
173
  # ── tool registry: the single name → invocation source of truth ───────────────
165
- def _h_create_xlsx(args: Dict[str, Any]) -> Dict[str, Any]:
166
- rows = args.get("rows", [])
167
- if isinstance(rows, str):
168
- rows = json.loads(rows)
169
- return create_xlsx(rows, args.get("filename", "spreadsheet.xlsx"), args.get("sheet_name", "Sheet1"))
170
-
171
-
172
- def _h_create_pptx(args: Dict[str, Any]) -> Dict[str, Any]:
173
- slides = args.get("slides", [])
174
- if isinstance(slides, str):
175
- slides = json.loads(slides)
176
- return create_pptx(args.get("title", ""), slides, args.get("filename", "presentation.pptx"))
177
-
178
-
179
174
  def _knowledge_scope(args: Dict[str, Any]) -> Dict[str, str]:
180
175
  """Return the authenticated scope injected by server/runtime adapters."""
181
176
  workspace_id = str(args.get("workspace_id") or "").strip()
@@ -197,31 +192,19 @@ TOOL_HANDLERS: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = {
197
192
  "list_dir": lambda a: list_dir(a.get("path", ".")),
198
193
  "workspace_tree": lambda a: workspace_tree(a.get("path", "."), a.get("max_depth", 3)),
199
194
  "read_file": lambda a: read_file(a["path"], offset=a.get("offset", 0), limit=a.get("limit", 0), line_numbers=a.get("line_numbers", True)),
200
- "write_file": lambda a: write_file(a["path"], a.get("content", "")),
201
- "edit_file": lambda a: edit_file(a["path"], a["old_string"], a["new_string"], replace_all=bool(a.get("replace_all", False))),
202
195
  "grep": lambda a: grep(a["pattern"], path=a.get("path", "."), glob=a.get("glob"), max_results=a.get("max_results", 50), case_insensitive=bool(a.get("case_insensitive", False)), context_lines=a.get("context_lines", 0)),
203
196
  "search_files": lambda a: search_files(a["query"], a.get("path", "."), a.get("max_results", 20)),
204
197
  "inspect_html": lambda a: inspect_html(a["path"]),
205
198
  "preview_url": lambda a: preview_url(a.get("path", "index.html")),
206
199
  # planning
207
200
  "todo_read": lambda a: todo_read(),
208
- "todo_write": lambda a: todo_write(a.get("todos") or []),
209
- # documents
210
- "create_docx": lambda a: create_docx(a.get("title", ""), a.get("body", ""), a.get("filename", "document.docx")),
211
- "create_xlsx": _h_create_xlsx,
212
- "create_pptx": _h_create_pptx,
213
- "create_pdf": lambda a: create_pdf(a.get("title", ""), a.get("body", ""), a.get("filename", "document.pdf")),
214
- "create_web_project": lambda a: create_web_project(a.get("path", ""), a.get("framework", "react"), a.get("template", "vite")),
215
201
  # local filesystem
216
202
  "local_list": lambda a: local_list(a["path"]),
217
203
  "local_read": lambda a: local_read(a["path"]),
218
- "local_write": lambda a: local_write(a["path"], a.get("content", "")),
219
204
  "read_document": lambda a: read_document(a["path"]),
220
205
  "network_status": lambda a: network_status(),
221
206
  # computer use
222
207
  "computer_screenshot": lambda a: computer_screenshot(),
223
- "computer_open_app": lambda a: computer_open_app(a.get("app", "Google Chrome")),
224
- "computer_open_url": lambda a: computer_open_url(a["url"], a.get("app", "Google Chrome")),
225
208
  "computer_click": lambda a: computer_click(a.get("x", 0), a.get("y", 0), a.get("button", "left"), a.get("double", False)),
226
209
  "computer_type": lambda a: computer_type(a["text"], a.get("interval", 0.04)),
227
210
  "computer_key": lambda a: computer_key(a["key"]),
@@ -233,10 +216,8 @@ TOOL_HANDLERS: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = {
233
216
  "computer_use_status": lambda a: desktop_bridge_status(),
234
217
  "vision_analyze": lambda a: vision_analyze(a.get("image_b64", ""), a.get("prompt", "Describe this image in detail. Be concise.")),
235
218
  # knowledge / obsidian
236
- "knowledge_save": lambda a: knowledge_save(a["content"], a.get("folder", "00_Raw"), a.get("title"), **_knowledge_scope(a)),
237
219
  "knowledge_search": lambda a: knowledge_search(a["query"], a.get("max_results", 5), **_knowledge_scope(a)),
238
220
  "knowledge_tree": lambda a: knowledge_tree(**_knowledge_scope(a)),
239
- "obsidian_save": lambda a: obsidian_save(a["content"], a.get("folder", "00_Raw"), a.get("title"), **_knowledge_scope(a)),
240
221
  "obsidian_search": lambda a: obsidian_search(a["query"], a.get("max_results", 5), **_knowledge_scope(a)),
241
222
  "obsidian_tree": lambda a: obsidian_tree(**_knowledge_scope(a)),
242
223
  # git (read-only)
@@ -244,10 +225,6 @@ TOOL_HANDLERS: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = {
244
225
  "git_diff": lambda a: git_diff(a.get("path"), a.get("cwd")),
245
226
  "git_log": lambda a: git_log(a.get("max_count", 5), a.get("cwd")),
246
227
  "git_show": lambda a: git_show(a.get("revision", "HEAD"), a.get("cwd")),
247
- # exec
248
- "run_command": lambda a: run_command(a["command"], a.get("cwd")),
249
- "build_project": lambda a: build_project(a.get("cwd"), a.get("script", "build")),
250
- "deploy_project": lambda a: deploy_project(a.get("cwd"), a.get("script", "deploy")),
251
228
  }
252
229
 
253
230
 
@@ -265,19 +242,15 @@ def execute_tool(action: str, args: Dict[str, Any]) -> Dict[str, Any]:
265
242
 
266
243
  __all__ = [
267
244
  "AGENT_ROOT", "ToolError", "ensure_agent_root", "resolve_workspace_path",
268
- "list_dir", "workspace_tree", "read_file", "write_file", "edit_file", "grep",
269
- "search_files", "inspect_html", "preview_url", "create_web_project",
270
- "todo_read", "todo_write",
271
- "create_docx", "create_xlsx", "create_pptx", "create_pdf", "read_document",
245
+ "list_dir", "workspace_tree", "read_file", "grep",
246
+ "search_files", "inspect_html", "preview_url", "todo_read", "read_document",
272
247
  "document_output_target",
273
- "local_list", "local_read", "local_write", "desktop_bridge_status",
274
- "knowledge_save", "knowledge_search", "knowledge_tree", "knowledge_scope_root",
275
- "obsidian_save", "obsidian_search", "obsidian_tree",
248
+ "local_list", "local_read", "desktop_bridge_status",
249
+ "knowledge_search", "knowledge_tree", "knowledge_scope_root",
250
+ "obsidian_search", "obsidian_tree",
276
251
  "network_status",
277
- "computer_screenshot", "computer_open_app", "computer_open_url",
278
- "computer_click", "computer_type", "computer_key", "computer_scroll",
252
+ "computer_screenshot", "computer_click", "computer_type", "computer_key", "computer_scroll",
279
253
  "computer_move", "computer_drag", "computer_status", "vision_analyze",
280
- "run_command", "build_project", "deploy_project",
281
254
  "git_status", "git_diff", "git_log", "git_show",
282
255
  "TOOL_HANDLERS", "DEFAULT_TOOL_REGISTRY", "registered_tools", "execute_tool",
283
256
  "BRAIN_DIR", "STRUCTURE",
@@ -2,28 +2,17 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
- import shlex
7
- import shutil
8
5
  import subprocess
9
- from pathlib import Path
10
6
  from typing import Any, Dict, List, Optional
11
7
 
12
8
  import latticeai.tools as tools
13
9
  from latticeai.tools import (
14
- ALLOWED_COMMANDS,
15
10
  ALLOWED_GIT_SUBCOMMANDS,
16
- BLOCKED_COMMANDS,
17
- BUILD_SCRIPT_NAMES,
18
- DEPLOY_SCRIPT_NAMES,
19
- MAX_BUILD_SECONDS,
20
11
  MAX_COMMAND_OUTPUT,
21
12
  MAX_COMMAND_SECONDS,
22
- MAX_DEPLOY_SECONDS,
23
13
  ToolError,
24
14
  _relative,
25
15
  _resolve_path,
26
- ensure_agent_root,
27
16
  )
28
17
 
29
18
  # find(1) flags that execute or delete; checked in run_command.
@@ -41,158 +30,6 @@ _BLOCKED_RG_FLAGS = {"--pre", "--pre-glob"}
41
30
  _SAFE_EXECUTABLE_PATH = "/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/usr/local/bin"
42
31
 
43
32
 
44
- def _argument_value(argument: str) -> str:
45
- if argument.startswith("-") and "=" in argument:
46
- return argument.split("=", 1)[1]
47
- return argument
48
-
49
-
50
- def _validate_command_paths(parts: List[str], workdir: Path) -> None:
51
- """Reject traversal and symlink escapes hidden in command arguments."""
52
- for argument in parts[1:]:
53
- value = _argument_value(argument)
54
- if not value or value == "/dev/null" or (argument.startswith("-") and "=" not in argument):
55
- continue
56
- candidate_path = Path(value).expanduser()
57
- if candidate_path.is_absolute():
58
- raise ToolError(f"Absolute paths in command arguments are not allowed: {value}")
59
- if ".." in candidate_path.parts:
60
- raise ToolError(f"Path traversal in command arguments is not allowed: {value}")
61
-
62
- candidate = workdir / candidate_path
63
- # Existing arguments (including symlinks) and explicit path-shaped
64
- # values must resolve inside the workspace. Other values can be search
65
- # patterns, counts, or expressions and are not interpreted as paths.
66
- if candidate.exists() or candidate.is_symlink() or "/" in value or "\\" in value:
67
- resolved = candidate.resolve(strict=False)
68
- if resolved != tools.AGENT_ROOT and tools.AGENT_ROOT not in resolved.parents:
69
- raise ToolError(f"Path escapes the agent workspace: {value}")
70
-
71
-
72
- def run_command(command: str, cwd: Optional[str] = None) -> Dict[str, Any]:
73
- ensure_agent_root()
74
- parts = shlex.split(command)
75
- if not parts:
76
- raise ToolError("Command is empty.")
77
-
78
- executable = Path(parts[0]).name
79
- if parts[0] != executable:
80
- raise ToolError("Executable paths are not allowed.")
81
- if executable in BLOCKED_COMMANDS or executable not in ALLOWED_COMMANDS:
82
- raise ToolError(f"Command is not allowed: {executable}")
83
- if executable == "git":
84
- raise ToolError("Use the read-only git_status, git_diff, git_log, or git_show tools.")
85
- if any(token in command for token in ["|", "&&", "||", ";", ">", "<", "$(", "`"]):
86
- raise ToolError("Shell operators are not allowed.")
87
- if executable == "find":
88
- blocked = [f for f in parts[1:] if f in _BLOCKED_FIND_FLAGS]
89
- if blocked:
90
- raise ToolError(f"find flags are not allowed: {', '.join(blocked)}")
91
- if executable == "rg":
92
- blocked = [
93
- flag
94
- for flag in parts[1:]
95
- if any(flag == denied or flag.startswith(f"{denied}=") for denied in _BLOCKED_RG_FLAGS)
96
- ]
97
- if blocked:
98
- raise ToolError(f"rg flags are not allowed: {', '.join(blocked)}")
99
-
100
- workdir = _resolve_path(cwd or ".")
101
- if not workdir.exists() or not workdir.is_dir():
102
- raise ToolError("Working directory does not exist.")
103
- _validate_command_paths(parts, workdir)
104
-
105
- executable_path = shutil.which(executable, path=_SAFE_EXECUTABLE_PATH)
106
- if not executable_path:
107
- raise ToolError(f"Allowed command is not installed: {executable}")
108
- environment = {
109
- "HOME": str(tools.AGENT_ROOT),
110
- "LANG": os.environ.get("LANG", "C.UTF-8"),
111
- "LC_ALL": os.environ.get("LC_ALL", "C.UTF-8"),
112
- "PATH": _SAFE_EXECUTABLE_PATH,
113
- }
114
-
115
- try:
116
- completed = subprocess.run(
117
- [executable_path, *parts[1:]],
118
- cwd=workdir,
119
- capture_output=True,
120
- text=True,
121
- env=environment,
122
- timeout=MAX_COMMAND_SECONDS,
123
- check=False,
124
- )
125
- except subprocess.TimeoutExpired:
126
- raise ToolError(f"Command timed out after {MAX_COMMAND_SECONDS} seconds.")
127
-
128
- stdout = completed.stdout[-MAX_COMMAND_OUTPUT:]
129
- stderr = completed.stderr[-MAX_COMMAND_OUTPUT:]
130
- return {
131
- "command": command,
132
- "cwd": _relative(workdir) if workdir != tools.AGENT_ROOT else ".",
133
- "returncode": completed.returncode,
134
- "stdout": stdout,
135
- "stderr": stderr,
136
- }
137
-
138
-
139
- def _load_package_scripts(workdir: Path) -> Dict[str, str]:
140
- package_json = workdir / "package.json"
141
- if not package_json.exists():
142
- return {}
143
- try:
144
- import json
145
- data = json.loads(package_json.read_text(encoding="utf-8"))
146
- except Exception as exc:
147
- raise ToolError(f"Could not parse package.json: {exc}") from exc
148
- scripts = data.get("scripts") or {}
149
- if not isinstance(scripts, dict):
150
- return {}
151
- return {str(key): str(value) for key, value in scripts.items()}
152
-
153
-
154
- def _run_script(script: str, cwd: Optional[str], allowed: set[str], timeout: int) -> Dict[str, Any]:
155
- ensure_agent_root()
156
- if script not in allowed:
157
- raise ToolError(f"Script is not allowed here: {script}")
158
- workdir = _resolve_path(cwd or ".")
159
- if not workdir.exists() or not workdir.is_dir():
160
- raise ToolError("Working directory does not exist.")
161
-
162
- scripts = _load_package_scripts(workdir)
163
- if script not in scripts:
164
- raise ToolError(f"package.json does not define a '{script}' script.")
165
-
166
- try:
167
- completed = subprocess.run(
168
- ["npm", "run", script],
169
- cwd=workdir,
170
- capture_output=True,
171
- text=True,
172
- timeout=timeout,
173
- check=False,
174
- )
175
- except subprocess.TimeoutExpired:
176
- raise ToolError(f"npm run {script} timed out after {timeout} seconds.")
177
-
178
- return {
179
- "command": f"npm run {script}",
180
- "cwd": _relative(workdir) if workdir != tools.AGENT_ROOT else ".",
181
- "script_body": scripts[script],
182
- "returncode": completed.returncode,
183
- "stdout": completed.stdout[-MAX_COMMAND_OUTPUT:],
184
- "stderr": completed.stderr[-MAX_COMMAND_OUTPUT:],
185
- }
186
-
187
-
188
- def build_project(cwd: Optional[str] = None, script: str = "build") -> Dict[str, Any]:
189
- return _run_script(script, cwd, BUILD_SCRIPT_NAMES, MAX_BUILD_SECONDS)
190
-
191
-
192
- def deploy_project(cwd: Optional[str] = None, script: str = "deploy") -> Dict[str, Any]:
193
- return _run_script(script, cwd, DEPLOY_SCRIPT_NAMES, MAX_DEPLOY_SECONDS)
194
-
195
-
196
33
  def _run_git(args: List[str], cwd: Optional[str] = None) -> Dict[str, Any]:
197
34
  if not args:
198
35
  raise ToolError("Git subcommand is required.")
@@ -72,45 +72,6 @@ def computer_screenshot() -> Dict[str, Any]:
72
72
  quiet()
73
73
 
74
74
 
75
- def computer_open_app(app: str = "Google Chrome") -> Dict[str, Any]:
76
- """앱을 실행하거나 앞으로 가져옵니다 (macOS/Windows/Linux)."""
77
- app = str(app or "Google Chrome").strip()
78
- if not app:
79
- raise ToolError("앱 이름이 필요합니다.")
80
- if _PLATFORM == "Darwin":
81
- cmd = ["open", "-a", app]
82
- elif _PLATFORM == "Windows":
83
- cmd = ["cmd", "/c", "start", "", app]
84
- else:
85
- cmd = ["xdg-open", app]
86
- r = subprocess.run(cmd, capture_output=True, timeout=10, check=False)
87
- if r.returncode != 0:
88
- err = r.stderr.decode("utf-8", errors="replace").strip()
89
- raise ToolError(f"앱 열기 실패: {err or app}")
90
- return {"action": "open_app", "app": app}
91
-
92
-
93
- def computer_open_url(url: str, app: str = "Google Chrome") -> Dict[str, Any]:
94
- """URL을 브라우저로 엽니다 (macOS/Windows/Linux)."""
95
- url = str(url or "").strip()
96
- app = str(app or "").strip()
97
- if not url:
98
- raise ToolError("URL이 필요합니다.")
99
- if "://" not in url and not url.startswith(("localhost", "127.0.0.1")):
100
- url = "https://" + url
101
- if _PLATFORM == "Darwin":
102
- cmd = ["open", "-a", app, url] if app else ["open", url]
103
- elif _PLATFORM == "Windows":
104
- cmd = ["cmd", "/c", "start", "", url]
105
- else:
106
- cmd = ["xdg-open", url]
107
- r = subprocess.run(cmd, capture_output=True, timeout=10, check=False)
108
- if r.returncode != 0:
109
- err = r.stderr.decode("utf-8", errors="replace").strip()
110
- raise ToolError(f"URL 열기 실패: {err or url}")
111
- return {"action": "open_url", "app": app or "default", "url": url}
112
-
113
-
114
75
  def computer_click(x: int, y: int, button: str = "left", double: bool = False) -> Dict[str, Any]:
115
76
  """화면 좌표 (x, y)를 클릭합니다."""
116
77
  if not _CU_AVAILABLE:
@@ -3,11 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
- from typing import Any, Dict, List, Optional, Tuple
6
+ from typing import Any, Dict, Optional, Tuple
7
7
 
8
- from latticeai.core.quiet import quiet
9
8
  from latticeai.tools import (
10
- _CJK_FONT_CANDIDATES,
11
9
  _SUPPORTED_READ_EXTENSIONS,
12
10
  DOCUMENT_MAX_READ_BYTES,
13
11
  DOCUMENT_OUTPUT_DIR,
@@ -15,8 +13,6 @@ from latticeai.tools import (
15
13
  PRESENTATION_OUTPUT_DIR,
16
14
  SPREADSHEET_OUTPUT_DIR,
17
15
  ToolError,
18
- _relative,
19
- _resolve_path,
20
16
  )
21
17
 
22
18
 
@@ -57,141 +53,12 @@ def document_output_target(tool_name: str, filename: str) -> Optional[str]:
57
53
  return f"{output_dir}/{_safe_filename(filename, suffix)}"
58
54
 
59
55
 
60
- def _document_target(tool_name: str, filename: str) -> Path:
61
- """Resolved, workspace-confined target for a document creator."""
62
- relative = document_output_target(tool_name, filename)
63
- if relative is None: # pragma: no cover — every creator is in the table
64
- raise ToolError(f"'{tool_name}' has no document output target.")
65
- target = _resolve_path(relative)
66
- target.parent.mkdir(parents=True, exist_ok=True)
67
- return target
68
-
69
-
70
56
  def _body_to_str(body) -> str:
71
57
  if isinstance(body, list):
72
58
  return "\n\n".join(str(item) for item in body)
73
59
  return str(body or "")
74
60
 
75
61
 
76
- def create_docx(title: str, body, filename: str = "document.docx") -> Dict[str, Any]:
77
- try:
78
- from docx import Document
79
- except Exception as exc:
80
- raise ToolError("python-docx is not installed. Run `pip install -r requirements.txt`.") from exc
81
-
82
- target = _document_target("create_docx", filename)
83
-
84
- document = Document()
85
- if title:
86
- document.add_heading(str(title), level=1)
87
- for block in _body_to_str(body).split("\n\n"):
88
- text = block.strip()
89
- if text:
90
- document.add_paragraph(text)
91
- document.save(str(target))
92
- return {"path": _relative(target), "bytes": target.stat().st_size}
93
-
94
-
95
- def create_xlsx(rows: List[List[Any]], filename: str = "spreadsheet.xlsx", sheet_name: str = "Sheet1") -> Dict[str, Any]:
96
- try:
97
- from openpyxl import Workbook
98
- except Exception as exc:
99
- raise ToolError("openpyxl is not installed. Run `pip install -r requirements.txt`.") from exc
100
-
101
- if not isinstance(rows, list) or not all(isinstance(row, list) for row in rows):
102
- raise ToolError("Rows must be a list of lists.")
103
-
104
- target = _document_target("create_xlsx", filename)
105
-
106
- workbook = Workbook()
107
- sheet = workbook.active
108
- sheet.title = (sheet_name or "Sheet1")[:31]
109
- for row in rows:
110
- sheet.append(row)
111
- workbook.save(target)
112
- return {"path": _relative(target), "rows": len(rows), "bytes": target.stat().st_size}
113
-
114
-
115
- def create_pptx(title: str, slides: List[Dict[str, Any]], filename: str = "presentation.pptx") -> Dict[str, Any]:
116
- try:
117
- from pptx import Presentation
118
- except Exception as exc:
119
- raise ToolError("python-pptx is not installed. Run `pip install -r requirements.txt`.") from exc
120
-
121
- target = _document_target("create_pptx", filename)
122
-
123
- presentation = Presentation()
124
- first_layout = presentation.slide_layouts[0]
125
- first = presentation.slides.add_slide(first_layout)
126
- first.shapes.title.text = title or "Presentation"
127
- first.placeholders[1].text = ""
128
-
129
- content_layout = presentation.slide_layouts[1]
130
- for slide_data in slides or []:
131
- slide = presentation.slides.add_slide(content_layout)
132
- slide.shapes.title.text = str(slide_data.get("title") or "Slide")
133
- body = slide.placeholders[1].text_frame
134
- body.clear()
135
- bullets = slide_data.get("bullets") or []
136
- if isinstance(bullets, str):
137
- bullets = [bullets]
138
- for index, bullet in enumerate(bullets):
139
- paragraph = body.paragraphs[0] if index == 0 else body.add_paragraph()
140
- paragraph.text = str(bullet)
141
- paragraph.level = 0
142
-
143
- presentation.save(str(target))
144
- return {"path": _relative(target), "slides": len(presentation.slides), "bytes": target.stat().st_size}
145
-
146
-
147
-
148
-
149
- def create_pdf(title: str, body, filename: str = "document.pdf") -> Dict[str, Any]:
150
- try:
151
- from reportlab.lib.pagesizes import A4
152
- from reportlab.lib.styles import ParagraphStyle
153
- from reportlab.lib.units import mm
154
- from reportlab.pdfbase import pdfmetrics
155
- from reportlab.pdfbase.ttfonts import TTFont
156
- from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer
157
- except Exception as exc:
158
- raise ToolError("reportlab is not installed. Run `pip install reportlab`.") from exc
159
-
160
- target = _document_target("create_pdf", filename)
161
-
162
- # CJK 폰트 등록
163
- font_name = "Helvetica"
164
- for font_path in _CJK_FONT_CANDIDATES:
165
- if Path(font_path).exists():
166
- try:
167
- pdfmetrics.registerFont(TTFont("KoreanFont", font_path))
168
- font_name = "KoreanFont"
169
- except Exception:
170
- quiet()
171
- break
172
-
173
- title_style = ParagraphStyle("Title", fontName=font_name, fontSize=18, spaceAfter=8, leading=24)
174
- body_style = ParagraphStyle("Body", fontName=font_name, fontSize=11, spaceAfter=6, leading=16)
175
-
176
- story = []
177
- if title:
178
- story.append(Paragraph(str(title), title_style))
179
- story.append(Spacer(1, 4 * mm))
180
-
181
- for block in _body_to_str(body).split("\n\n"):
182
- text = block.strip()
183
- if text:
184
- safe_text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
185
- story.append(Paragraph(safe_text, body_style))
186
- story.append(Spacer(1, 2 * mm))
187
-
188
- doc = SimpleDocTemplate(str(target), pagesize=A4,
189
- leftMargin=20*mm, rightMargin=20*mm,
190
- topMargin=20*mm, bottomMargin=20*mm)
191
- doc.build(story)
192
- return {"path": _relative(target), "bytes": target.stat().st_size}
193
-
194
-
195
62
  def read_document(path: str) -> Dict[str, Any]:
196
63
  """Extract text from PDF, DOCX, XLSX, PPTX, TXT, MD, CSV files."""
197
64
  target = Path(path).expanduser().resolve()