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.
- package/README.md +93 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +119 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +8 -6
- package/docs/ENTERPRISE.md +2 -1
- package/docs/MULTI_AGENT_RUNTIME.md +12 -5
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +6 -3
- package/docs/REALTIME_COLLABORATION.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +3 -2
- package/docs/kg-schema.md +1 -1
- package/docs/v11.6.0_ONE_DOOR_PLAN.md +170 -0
- package/lattice_brain/__init__.py +42 -79
- package/lattice_brain/graph/__init__.py +9 -24
- package/lattice_brain/graph/_kg_common/__init__.py +13 -24
- package/lattice_brain/ingestion/__init__.py +19 -58
- package/lattice_brain/ingestion/pipeline.py +20 -398
- package/lattice_brain/multimodal/__init__.py +7 -18
- package/lattice_brain/multimodal/images.py +6 -264
- package/lattice_brain/multimodal/video.py +8 -247
- package/lattice_brain/runtime/__init__.py +8 -79
- package/lattice_brain/runtime/hooks.py +22 -584
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_worker_seam.py +7 -80
- package/latticeai/api/health.py +6 -20
- package/latticeai/api/local_files.py +16 -613
- package/latticeai/api/models.py +12 -87
- package/latticeai/api/search.py +17 -255
- package/latticeai/api/tools.py +69 -750
- package/latticeai/api/voice_capture.py +8 -70
- package/latticeai/api/worker_compute.py +842 -0
- package/latticeai/api/worker_seams.py +216 -0
- package/latticeai/app_factory.py +23 -232
- package/latticeai/cli/entrypoint.py +36 -249
- package/latticeai/core/agent_permission.py +16 -91
- package/latticeai/core/messages.py +89 -531
- package/latticeai/runtime/access_runtime.py +0 -14
- package/latticeai/runtime/bootstrap.py +10 -21
- package/latticeai/runtime/brain_runtime.py +35 -43
- package/latticeai/runtime/build_phases/__init__.py +21 -37
- package/latticeai/runtime/build_phases/features.py +99 -389
- package/latticeai/runtime/build_phases/foundation.py +82 -392
- package/latticeai/runtime/build_phases/web.py +91 -408
- package/latticeai/runtime/build_phases/worker_profile.py +244 -0
- package/latticeai/runtime/lifespan_runtime.py +7 -16
- package/latticeai/runtime/platform_services_runtime.py +1 -15
- package/latticeai/runtime/runtime_context.py +13 -130
- package/latticeai/runtime/security_runtime.py +10 -103
- package/latticeai/services/architecture_readiness.py +82 -43
- package/latticeai/services/model_runtime/__init__.py +2 -11
- package/latticeai/services/model_runtime/service.py +4 -28
- package/latticeai/services/p_reinforce.py +10 -261
- package/latticeai/services/product_readiness.py +31 -38
- package/latticeai/services/search_service.py +37 -795
- package/latticeai/services/tool_dispatch.py +30 -386
- package/latticeai/services/voice_capture.py +13 -107
- package/latticeai/tools/__init__.py +22 -49
- package/latticeai/tools/commands.py +0 -163
- package/latticeai/tools/computer.py +0 -39
- package/latticeai/tools/documents.py +1 -134
- package/latticeai/tools/filesystem.py +1 -247
- package/latticeai/tools/knowledge.py +1 -52
- package/latticeai/tools/local_files.py +0 -20
- package/latticeai/worker_app.py +75 -0
- package/package.json +2 -3
- package/requirements.txt +0 -5
- package/scripts/agent_eval.py +16 -28
- package/scripts/brain_quality_eval.py +20 -183
- package/scripts/bump_version.py +5 -5
- package/scripts/check_current_release_docs.mjs +7 -4
- package/scripts/check_openapi_drift.mjs +10 -0
- package/scripts/check_server_i18n.mjs +2 -18
- package/scripts/compose_openapi.py +377 -0
- package/scripts/export_openapi.py +32 -4
- package/scripts/gen_messages_catalog_fixture.py +302 -0
- package/scripts/gen_openapi_fragments.py +365 -0
- package/scripts/gen_redact_fixture.py +196 -0
- package/scripts/gen_worker_allowlist_fixture.py +115 -0
- package/scripts/generate_agent_parity_fixtures.py +33 -14
- package/scripts/openapi_route_families.json +2161 -0
- package/scripts/release_screen_claims.json +54 -0
- package/scripts/run_integration_tests.mjs +134 -29
- package/scripts/run_sidecar_e2e.mjs +104 -11
- package/scripts/wheel_smoke.py +32 -22
- package/src-tauri/Cargo.lock +375 -9
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +151 -45
- package/src-tauri/src/main.rs +18 -18
- package/src-tauri/src/topology.rs +58 -88
- package/src-tauri/tauri.conf.json +1 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-DcQizkl1.js → Act-BPcVAbOL.js} +1 -1
- package/static/app/assets/AdminConsole-Bw1ATQL0.js +1 -0
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-CT92Kos0.js} +2 -2
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-CFBkt1K_.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-ReLWF2H8.js} +1 -1
- package/static/app/assets/Capture-BsTokYkk.js +1 -0
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-B6f0T9id.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-CuvjTv1u.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-BGJbG9Hd.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-DGYK_Jsa.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-DXBC6brE.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-HXRle3qq.js} +2 -2
- package/static/app/assets/System-CMHSO9qM.js +1 -0
- package/static/app/assets/arrow-left-BfmkskWx.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-Cn8bWRuq.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CQJberbE.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-Ct9f2_oT.js} +1 -1
- package/static/app/assets/circle-check-DruOxB-4.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-CmzC_apg.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-D8mW2aQ7.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DZcdd0PZ.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-bv1KEPGQ.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-d-Pip5gr.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-D20iavUb.js} +1 -1
- package/static/app/assets/index-D9x-kSNy.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-Do83hDzJ.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-BLXVNmj1.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-BPJOFlAy.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-ChdJd493.js} +1 -1
- package/static/app/assets/primitives-Cv5tbZBY.js +1 -0
- package/static/app/assets/search-CT9aho2j.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-YNX_NtMU.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-DuQ3zrVL.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-DqwLnli4.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-ZVI98jaW.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-CVC4qv_D.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-C7BeG4HU.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-CiFtIdZq.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-DQz9vIId.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/lattice_brain/archive.py +0 -522
- package/lattice_brain/context.py +0 -325
- package/lattice_brain/conversations.py +0 -382
- package/lattice_brain/core.py +0 -82
- package/lattice_brain/graph/_kg_contract.py +0 -249
- package/lattice_brain/graph/curator.py +0 -676
- package/lattice_brain/graph/discovery.py +0 -595
- package/lattice_brain/graph/discovery_index/__init__.py +0 -35
- package/lattice_brain/graph/discovery_index/cleanup.py +0 -182
- package/lattice_brain/graph/discovery_index/extract.py +0 -137
- package/lattice_brain/graph/discovery_index/scan.py +0 -411
- package/lattice_brain/graph/discovery_index/upsert.py +0 -495
- package/lattice_brain/graph/documents.py +0 -380
- package/lattice_brain/graph/fusion.py +0 -395
- package/lattice_brain/graph/identity.py +0 -175
- package/lattice_brain/graph/image_vectors.py +0 -230
- package/lattice_brain/graph/ingest.py +0 -829
- package/lattice_brain/graph/network.py +0 -205
- package/lattice_brain/graph/proactive.py +0 -724
- package/lattice_brain/graph/projection/__init__.py +0 -42
- package/lattice_brain/graph/projection/curation.py +0 -500
- package/lattice_brain/graph/projection/v2_schema.py +0 -518
- package/lattice_brain/graph/provenance.py +0 -524
- package/lattice_brain/graph/rerank.py +0 -163
- package/lattice_brain/graph/retrieval/__init__.py +0 -54
- package/lattice_brain/graph/retrieval/context.py +0 -197
- package/lattice_brain/graph/retrieval/graph_view.py +0 -319
- package/lattice_brain/graph/retrieval/hybrid.py +0 -488
- package/lattice_brain/graph/retrieval/maintenance.py +0 -121
- package/lattice_brain/graph/retrieval/signals.py +0 -95
- package/lattice_brain/graph/retrieval_docgen.py +0 -253
- package/lattice_brain/graph/retrieval_policy.py +0 -180
- package/lattice_brain/graph/retrieval_reads.py +0 -769
- package/lattice_brain/graph/retrieval_vector/__init__.py +0 -42
- package/lattice_brain/graph/retrieval_vector/fingerprint.py +0 -97
- package/lattice_brain/graph/retrieval_vector/indexing.py +0 -347
- package/lattice_brain/graph/retrieval_vector/search.py +0 -560
- package/lattice_brain/graph/retrieval_vector/status.py +0 -374
- package/lattice_brain/graph/schema.py +0 -792
- package/lattice_brain/graph/store.py +0 -268
- package/lattice_brain/graph/vector_index/__init__.py +0 -85
- package/lattice_brain/graph/vector_index/base.py +0 -167
- package/lattice_brain/graph/vector_index/brute_force.py +0 -110
- package/lattice_brain/graph/vector_index/hnsw.py +0 -290
- package/lattice_brain/graph/vector_index/jobs.py +0 -287
- package/lattice_brain/graph/vector_index/quantized.py +0 -148
- package/lattice_brain/graph/vector_index/selector.py +0 -161
- package/lattice_brain/graph/write_master.py +0 -308
- package/lattice_brain/ingestion/_contract.py +0 -90
- package/lattice_brain/ingestion/folder_scan.py +0 -57
- package/lattice_brain/ingestion/folders.py +0 -258
- package/lattice_brain/ingestion/jobs_api.py +0 -107
- package/lattice_brain/ingestion/routing.py +0 -295
- package/lattice_brain/ingestion_jobs.py +0 -380
- package/lattice_brain/memory.py +0 -75
- package/lattice_brain/portability/__init__.py +0 -90
- package/lattice_brain/portability/_contract.py +0 -42
- package/lattice_brain/portability/backups.py +0 -338
- package/lattice_brain/portability/bundles.py +0 -136
- package/lattice_brain/portability/constants.py +0 -93
- package/lattice_brain/portability/fsops.py +0 -138
- package/lattice_brain/portability/service.py +0 -41
- package/lattice_brain/portability/sharing.py +0 -710
- package/lattice_brain/quality.py +0 -543
- package/lattice_brain/retrieval_benchmark_fixtures.py +0 -92
- package/lattice_brain/runtime/agent_runtime.py +0 -859
- package/lattice_brain/runtime/contracts.py +0 -460
- package/lattice_brain/runtime/multi_agent.py +0 -942
- package/lattice_brain/runtime/statuses.py +0 -10
- package/lattice_brain/sealed_box.py +0 -240
- package/lattice_brain/self_model.py +0 -675
- package/lattice_brain/sensitivity.py +0 -94
- package/lattice_brain/storage/__init__.py +0 -22
- package/lattice_brain/storage/base.py +0 -100
- package/lattice_brain/storage/docker.py +0 -105
- package/lattice_brain/storage/factory.py +0 -31
- package/lattice_brain/storage/migration.py +0 -191
- package/lattice_brain/storage/postgres.py +0 -123
- package/lattice_brain/storage/sqlite.py +0 -143
- package/lattice_brain/synthesis.py +0 -824
- package/lattice_brain/workflow.py +0 -497
- package/latticeai/api/admin.py +0 -471
- package/latticeai/api/agent_registry.py +0 -105
- package/latticeai/api/agents.py +0 -228
- package/latticeai/api/auth.py +0 -383
- package/latticeai/api/automation_intelligence.py +0 -401
- package/latticeai/api/brain_intelligence.py +0 -199
- package/latticeai/api/browser.py +0 -493
- package/latticeai/api/change_proposals.py +0 -89
- package/latticeai/api/chat.py +0 -572
- package/latticeai/api/chat_agent_http.py +0 -892
- package/latticeai/api/chat_contracts.py +0 -73
- package/latticeai/api/chat_documents.py +0 -276
- package/latticeai/api/chat_helpers.py +0 -460
- package/latticeai/api/chat_history.py +0 -101
- package/latticeai/api/chat_hybrid.py +0 -113
- package/latticeai/api/chat_intents.py +0 -646
- package/latticeai/api/chat_stream.py +0 -216
- package/latticeai/api/chronicle.py +0 -63
- package/latticeai/api/command_center.py +0 -51
- package/latticeai/api/computer_use.py +0 -474
- package/latticeai/api/evidence_actions.py +0 -48
- package/latticeai/api/features.py +0 -70
- package/latticeai/api/funnel_metrics.py +0 -31
- package/latticeai/api/garden.py +0 -34
- package/latticeai/api/hooks.py +0 -165
- package/latticeai/api/index_jobs.py +0 -145
- package/latticeai/api/invitations.py +0 -100
- package/latticeai/api/knowledge_graph.py +0 -536
- package/latticeai/api/marketplace.py +0 -105
- package/latticeai/api/mcp.py +0 -482
- package/latticeai/api/memory.py +0 -270
- package/latticeai/api/network.py +0 -81
- package/latticeai/api/network_boundary.py +0 -225
- package/latticeai/api/permission_mode.py +0 -61
- package/latticeai/api/permissions.py +0 -436
- package/latticeai/api/plugins.py +0 -126
- package/latticeai/api/portability.py +0 -391
- package/latticeai/api/project_sessions.py +0 -114
- package/latticeai/api/realtime.py +0 -118
- package/latticeai/api/review_queue.py +0 -364
- package/latticeai/api/security_dashboard.py +0 -604
- package/latticeai/api/setup.py +0 -319
- package/latticeai/api/static_routes.py +0 -354
- package/latticeai/api/ui_redirects.py +0 -26
- package/latticeai/api/workflow_designer.py +0 -394
- package/latticeai/api/workspace.py +0 -856
- package/latticeai/api/workspace_scope.py +0 -125
- package/latticeai/core/agent/__init__.py +0 -93
- package/latticeai/core/agent/_contract.py +0 -79
- package/latticeai/core/agent/context.py +0 -57
- package/latticeai/core/agent/deps.py +0 -125
- package/latticeai/core/agent/execution.py +0 -622
- package/latticeai/core/agent/planning.py +0 -145
- package/latticeai/core/agent/recovery.py +0 -157
- package/latticeai/core/agent/runtime.py +0 -210
- package/latticeai/core/agent/verification.py +0 -231
- package/latticeai/core/agent_eval.py +0 -739
- package/latticeai/core/agent_helpers.py +0 -493
- package/latticeai/core/agent_profiles.py +0 -110
- package/latticeai/core/agent_prompts.py +0 -171
- package/latticeai/core/agent_registry.py +0 -232
- package/latticeai/core/agent_state.py +0 -41
- package/latticeai/core/agent_trace.py +0 -104
- package/latticeai/core/artifact_ledger.py +0 -109
- package/latticeai/core/audit.py +0 -260
- package/latticeai/core/builtin_hooks.py +0 -105
- package/latticeai/core/context_builder.py +0 -394
- package/latticeai/core/document_generator.py +0 -103
- package/latticeai/core/enterprise.py +0 -154
- package/latticeai/core/enterprise_admin.py +0 -158
- package/latticeai/core/file_generation/__init__.py +0 -115
- package/latticeai/core/file_generation/bundles.py +0 -76
- package/latticeai/core/file_generation/extraction.py +0 -154
- package/latticeai/core/file_generation/inference.py +0 -235
- package/latticeai/core/file_generation/orchestration.py +0 -152
- package/latticeai/core/file_generation/prompting.py +0 -117
- package/latticeai/core/file_generation/repair.py +0 -114
- package/latticeai/core/file_generation/sanitize.py +0 -61
- package/latticeai/core/file_generation/validation.py +0 -201
- package/latticeai/core/invitations.py +0 -132
- package/latticeai/core/legacy_compatibility.py +0 -243
- package/latticeai/core/logging_safety.py +0 -46
- package/latticeai/core/marketplace.py +0 -293
- package/latticeai/core/mcp_catalog.py +0 -452
- package/latticeai/core/mcp_registry.py +0 -506
- package/latticeai/core/network_boundary.py +0 -168
- package/latticeai/core/oidc.py +0 -208
- package/latticeai/core/plugins.py +0 -432
- package/latticeai/core/product_hardening.py +0 -218
- package/latticeai/core/project_sessions.py +0 -337
- package/latticeai/core/realtime.py +0 -238
- package/latticeai/core/run_explain.py +0 -426
- package/latticeai/core/run_store.py +0 -252
- package/latticeai/core/timezones.py +0 -80
- package/latticeai/core/workspace_computer_memory.py +0 -84
- package/latticeai/core/workspace_graph_trace.py +0 -155
- package/latticeai/core/workspace_indexing.py +0 -102
- package/latticeai/core/workspace_memory.py +0 -77
- package/latticeai/core/workspace_onboarding.py +0 -104
- package/latticeai/core/workspace_os.py +0 -978
- package/latticeai/core/workspace_os_constants.py +0 -126
- package/latticeai/core/workspace_os_state.py +0 -180
- package/latticeai/core/workspace_os_utils.py +0 -103
- package/latticeai/core/workspace_permissions.py +0 -101
- package/latticeai/core/workspace_plugins.py +0 -97
- package/latticeai/core/workspace_relationships.py +0 -99
- package/latticeai/core/workspace_reorganization.py +0 -335
- package/latticeai/core/workspace_review_items.py +0 -112
- package/latticeai/core/workspace_runs.py +0 -726
- package/latticeai/core/workspace_skills.py +0 -109
- package/latticeai/core/workspace_snapshots.py +0 -198
- package/latticeai/core/workspace_timeline.py +0 -110
- package/latticeai/integrations/__init__.py +0 -0
- package/latticeai/integrations/telegram_bot/__init__.py +0 -123
- package/latticeai/integrations/telegram_bot/__main__.py +0 -17
- package/latticeai/integrations/telegram_bot/config.py +0 -86
- package/latticeai/integrations/telegram_bot/dispatch.py +0 -311
- package/latticeai/integrations/telegram_bot/flows.py +0 -478
- package/latticeai/integrations/telegram_bot/helpers.py +0 -322
- package/latticeai/integrations/telegram_bot/screens.py +0 -394
- package/latticeai/runtime/audit_runtime.py +0 -76
- package/latticeai/runtime/automation_runtime.py +0 -81
- package/latticeai/runtime/chat_wiring.py +0 -141
- package/latticeai/runtime/context_runtime.py +0 -66
- package/latticeai/runtime/feature_toggle_wiring.py +0 -157
- package/latticeai/runtime/history_runtime.py +0 -163
- package/latticeai/runtime/history_writer.py +0 -138
- package/latticeai/runtime/hooks_runtime.py +0 -77
- package/latticeai/runtime/model_wiring.py +0 -68
- package/latticeai/runtime/namespace_runtime.py +0 -163
- package/latticeai/runtime/network_boundary_wiring.py +0 -117
- package/latticeai/runtime/network_config_runtime.py +0 -56
- package/latticeai/runtime/permission_mode_wiring.py +0 -112
- package/latticeai/runtime/persistence_runtime.py +0 -159
- package/latticeai/runtime/platform_runtime_wiring.py +0 -89
- package/latticeai/runtime/review_wiring.py +0 -42
- package/latticeai/runtime/router_registration.py +0 -693
- package/latticeai/runtime/service_singletons.py +0 -55
- package/latticeai/runtime/sso_config_runtime.py +0 -128
- package/latticeai/runtime/user_key_runtime.py +0 -106
- package/latticeai/runtime/web_runtime.py +0 -92
- package/latticeai/server_app.py +0 -51
- package/latticeai/services/app_context.py +0 -130
- package/latticeai/services/automation_execution.py +0 -266
- package/latticeai/services/automation_intelligence.py +0 -614
- package/latticeai/services/brain_automation.py +0 -191
- package/latticeai/services/brain_intelligence/__init__.py +0 -58
- package/latticeai/services/brain_intelligence/_contract.py +0 -71
- package/latticeai/services/brain_intelligence/consistency.py +0 -193
- package/latticeai/services/brain_intelligence/constants.py +0 -47
- package/latticeai/services/brain_intelligence/digest.py +0 -258
- package/latticeai/services/brain_intelligence/health.py +0 -331
- package/latticeai/services/brain_intelligence/proposals.py +0 -259
- package/latticeai/services/brain_intelligence/sampling.py +0 -84
- package/latticeai/services/brain_intelligence/service.py +0 -48
- package/latticeai/services/change_proposals.py +0 -471
- package/latticeai/services/chat_service.py +0 -243
- package/latticeai/services/chronicle.py +0 -555
- package/latticeai/services/cloud_egress_audit.py +0 -85
- package/latticeai/services/cloud_extraction.py +0 -129
- package/latticeai/services/cloud_streaming.py +0 -268
- package/latticeai/services/cloud_token_guard.py +0 -84
- package/latticeai/services/command_center.py +0 -548
- package/latticeai/services/evidence_actions.py +0 -258
- package/latticeai/services/feature_toggles.py +0 -502
- package/latticeai/services/folder_watch.py +0 -520
- package/latticeai/services/funnel_metrics.py +0 -307
- package/latticeai/services/hybrid_chat.py +0 -316
- package/latticeai/services/hybrid_context.py +0 -228
- package/latticeai/services/hybrid_policy.py +0 -129
- package/latticeai/services/interop_bridges.py +0 -978
- package/latticeai/services/local_knowledge.py +0 -465
- package/latticeai/services/memory_service/__init__.py +0 -52
- package/latticeai/services/memory_service/_contract.py +0 -100
- package/latticeai/services/memory_service/brief.py +0 -431
- package/latticeai/services/memory_service/constants.py +0 -57
- package/latticeai/services/memory_service/maintenance.py +0 -138
- package/latticeai/services/memory_service/manager.py +0 -186
- package/latticeai/services/memory_service/proof.py +0 -136
- package/latticeai/services/memory_service/recall.py +0 -225
- package/latticeai/services/memory_service/service.py +0 -48
- package/latticeai/services/memory_service/stores.py +0 -110
- package/latticeai/services/mode_store.py +0 -132
- package/latticeai/services/model_recommendation.py +0 -224
- package/latticeai/services/model_runtime/cloud.py +0 -87
- package/latticeai/services/network_boundary_service.py +0 -117
- package/latticeai/services/obsidian_bridge.py +0 -609
- package/latticeai/services/openai_compatible_adapter.py +0 -101
- package/latticeai/services/permission_mode_service.py +0 -122
- package/latticeai/services/platform_runtime.py +0 -366
- package/latticeai/services/review_queue.py +0 -380
- package/latticeai/services/router_context.py +0 -59
- package/latticeai/services/run_executor.py +0 -387
- package/latticeai/services/self_model_service.py +0 -171
- package/latticeai/services/setup_detection.py +0 -147
- package/latticeai/services/triggers.py +0 -378
- package/latticeai/services/upload_service.py +0 -172
- package/latticeai/services/workspace_service.py +0 -165
- package/latticeai/setup/__init__.py +0 -25
- package/latticeai/setup/auto_setup.py +0 -846
- package/latticeai/setup/demo_corpus.py +0 -98
- package/latticeai/setup/wizard/__init__.py +0 -126
- package/latticeai/setup/wizard/catalog.py +0 -175
- package/latticeai/setup/wizard/detect.py +0 -302
- package/latticeai/setup/wizard/install.py +0 -348
- package/latticeai/setup/wizard/paths.py +0 -165
- package/latticeai/setup/wizard/plans.py +0 -74
- package/latticeai/setup/wizard/recommend.py +0 -320
- package/scripts/bench_agent_smoke.py +0 -409
- package/scripts/bench_models.py +0 -540
- package/scripts/bench_vector_index.py +0 -295
- package/scripts/funnel_soft_gate.py +0 -192
- package/scripts/generate_agent_loop_fixtures.py +0 -994
- package/scripts/generate_rust_parity_fixtures.py +0 -908
- package/scripts/migrate_brain_storage.py +0 -57
- package/scripts/parity_fixture_corpus_context.py +0 -162
- package/scripts/parity_fixture_corpus_docgen.py +0 -341
- package/scripts/profile_kg.py +0 -355
- package/server.py +0 -30
- package/static/app/assets/AdminConsole-cf4npybT.js +0 -1
- package/static/app/assets/Capture-DiQ219jW.js +0 -1
- package/static/app/assets/System-CieofHQa.js +0 -1
- package/static/app/assets/arrow-left-kfsrk0mv.js +0 -1
- package/static/app/assets/circle-check-qqLug9nU.js +0 -1
- package/static/app/assets/index-DxmOfNRi.css +0 -2
- package/static/app/assets/primitives-SNp0LRJz.js +0 -1
- package/static/app/assets/search-BcHqkjoy.js +0 -1
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
"""
|
|
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.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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", "
|
|
269
|
-
"search_files", "inspect_html", "preview_url", "
|
|
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", "
|
|
274
|
-
"
|
|
275
|
-
"
|
|
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", "
|
|
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,
|
|
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("&", "&").replace("<", "<").replace(">", ">")
|
|
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()
|