ltcai 11.5.2 → 11.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -148
- package/bin/ltcai.js +234 -24
- package/docs/CHANGELOG.md +75 -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 +21 -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-CToZnOHz.js} +1 -1
- package/static/app/assets/{AdminConsole-cf4npybT.js → AdminConsole-D5kbrBu8.js} +1 -1
- package/static/app/assets/{Brain-3VCSHFcn.js → Brain-C6zCdv4S.js} +1 -1
- package/static/app/assets/{BrainHome-Qm8eaztx.js → BrainHome-D4n3EDUt.js} +1 -1
- package/static/app/assets/{BrainSignals-DS9BtKOW.js → BrainSignals-CzeZ-vQ9.js} +1 -1
- package/static/app/assets/{Capture-DiQ219jW.js → Capture-DPcyTczq.js} +1 -1
- package/static/app/assets/{Chronicle-BGvuAchH.js → Chronicle-D3Nx-oWS.js} +1 -1
- package/static/app/assets/{CommandPalette-Bqhm0Urn.js → CommandPalette-DPPvpp28.js} +1 -1
- package/static/app/assets/{Library-BV6NnF0a.js → Library-Co2qTu3I.js} +1 -1
- package/static/app/assets/{LivingBrain-GzenJchP.js → LivingBrain-Bsmzwy8_.js} +1 -1
- package/static/app/assets/{ProductFlow-DEP6-vML.js → ProductFlow-CBLdmesT.js} +1 -1
- package/static/app/assets/{ReviewCard-CNZ7XjWG.js → ReviewCard-CfR6DLs7.js} +1 -1
- package/static/app/assets/{System-CieofHQa.js → System-Ce-P2YL6.js} +1 -1
- package/static/app/assets/arrow-left-DD5jFGHV.js +1 -0
- package/static/app/assets/{bot-B_K1Tdmw.js → bot-C5-XIww2.js} +1 -1
- package/static/app/assets/{brain-DWyaV1L1.js → brain-CFRJUvdX.js} +1 -1
- package/static/app/assets/{button-aTn4s84A.js → button-DI1KcQP-.js} +1 -1
- package/static/app/assets/circle-check-CY20XMCh.js +1 -0
- package/static/app/assets/{circle-pause-xKgeGXkT.js → circle-pause-C8HHyood.js} +1 -1
- package/static/app/assets/{circle-play-DkT6tYPX.js → circle-play-Cufc235E.js} +1 -1
- package/static/app/assets/{cpu-85xYObUC.js → cpu-DW2_JVsv.js} +1 -1
- package/static/app/assets/{download-B5Fm7YXo.js → download-gbdKTBb8.js} +1 -1
- package/static/app/assets/{folder-open-kk2Xa52u.js → folder-open-C9ydyX33.js} +1 -1
- package/static/app/assets/{hard-drive-DkA3zBW_.js → hard-drive-BxNMzBM3.js} +1 -1
- package/static/app/assets/index-BovSWRmQ.css +2 -0
- package/static/app/assets/{index-BMPdTmlY.js → index-q9XyDY7A.js} +3 -3
- package/static/app/assets/{input-B0nRf2jO.js → input-GLD94Y_K.js} +1 -1
- package/static/app/assets/{link-2-Dwb4gnTc.js → link-2-B4Y0gz1S.js} +1 -1
- package/static/app/assets/{permissionCopy-CQDUBrOZ.js → permissionCopy-CxrV0fvU.js} +1 -1
- package/static/app/assets/{primitives-SNp0LRJz.js → primitives-2ANdAsu2.js} +1 -1
- package/static/app/assets/search-DTxtjQ07.js +1 -0
- package/static/app/assets/{share-2-BsrxFglO.js → share-2-DxArfiuI.js} +1 -1
- package/static/app/assets/{shield-alert-5BStfp2_.js → shield-alert-CgftuFFs.js} +1 -1
- package/static/app/assets/{textarea-Cg8IUA-k.js → textarea-BHVBAoCr.js} +1 -1
- package/static/app/assets/{useFocusTrap-CYKvE46M.js → useFocusTrap-Dzq2j_OG.js} +1 -1
- package/static/app/assets/{useMutation-CSn9t1op.js → useMutation-BcqfMLNq.js} +1 -1
- package/static/app/assets/{useQuery-CY2OI2uy.js → useQuery-DBzLKCOv.js} +1 -1
- package/static/app/assets/{utils-Ddol2RWD.js → utils-B-Aah1bO.js} +1 -1
- package/static/app/assets/{workspace-BqDwOz_p.js → workspace-B6fFhoko.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/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/search-BcHqkjoy.js +0 -1
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
"""What the user asked for, when they never said a filename.
|
|
2
|
-
|
|
3
|
-
Two deliberately narrow, fully deterministic inferences (weak local models
|
|
4
|
-
never see either decision): a single filename for "html 파일 만들어줘", and a
|
|
5
|
-
multi-file project manifest for "todo 앱 html+css+js로 만들어줘". Both require
|
|
6
|
-
a creation verb and an explicit type keyword, so anything less specific keeps
|
|
7
|
-
flowing to the paths that handled it before.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import re
|
|
13
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
14
|
-
|
|
15
|
-
_CREATE_VERB_RE = re.compile(
|
|
16
|
-
r"(만들|생성|작성|써\s*줘|저장|create|make|write|generate|build|save)",
|
|
17
|
-
re.IGNORECASE,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# Explicit type keyword → default filename. Ordered: first match wins.
|
|
22
|
-
_TYPE_KEYWORDS: Tuple[Tuple[str, str], ...] = (
|
|
23
|
-
(r"\bhtml\b|웹\s*페이지|웹페이지|홈페이지|landing\s*page|web\s*page", "generated_page.html"),
|
|
24
|
-
(r"\bcss\b|스타일\s*시트", "styles.css"),
|
|
25
|
-
(r"\bjavascript\b|\bjs\b\s*(파일|file)|자바스크립트", "script.js"),
|
|
26
|
-
(r"\bpython\b|파이썬", "script.py"),
|
|
27
|
-
(r"\bjson\b", "data.json"),
|
|
28
|
-
(r"\bcsv\b", "data.csv"),
|
|
29
|
-
(r"\byaml\b|\byml\b", "config.yaml"),
|
|
30
|
-
(r"\bxml\b", "data.xml"),
|
|
31
|
-
(r"\bsql\b", "query.sql"),
|
|
32
|
-
(r"마크다운|\bmarkdown\b|\bmd\b\s*(파일|file)", "notes.md"),
|
|
33
|
-
(r"텍스트\s*파일|\btext\s*file\b|\btxt\b", "notes.txt"),
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def infer_file_target(message: str) -> Optional[str]:
|
|
38
|
-
"""Infer a filename for creation requests that name a type but no path.
|
|
39
|
-
|
|
40
|
-
"html 파일 만들어줘" previously fell through to the agent JSON loop, which
|
|
41
|
-
small models fail at. Inference keeps such requests on the deterministic
|
|
42
|
-
direct-write path. Deliberately narrow: requires a creation verb and an
|
|
43
|
-
explicit file-type keyword — report/document prose requests keep flowing
|
|
44
|
-
to the document generator.
|
|
45
|
-
"""
|
|
46
|
-
text = (message or "").strip()
|
|
47
|
-
if not text or not _CREATE_VERB_RE.search(text):
|
|
48
|
-
return None
|
|
49
|
-
lower = text.lower()
|
|
50
|
-
for pattern, filename in _TYPE_KEYWORDS:
|
|
51
|
-
if re.search(pattern, lower):
|
|
52
|
-
return filename
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# ``\b`` fails against Korean particles ("js로") because Hangul is ``\w`` —
|
|
57
|
-
# use ASCII lookarounds so type keywords match with or without a particle.
|
|
58
|
-
_HTML_HINT_RE = re.compile(
|
|
59
|
-
r"(?<![a-z0-9])html(?![a-z0-9])"
|
|
60
|
-
r"|웹\s*페이지|웹페이지|홈페이지|웹\s*사이트|웹사이트|website|web\s*page|landing\s*page",
|
|
61
|
-
)
|
|
62
|
-
_CSS_HINT_RE = re.compile(r"(?<![a-z0-9])css(?![a-z0-9])|스타일\s*시트|stylesheet")
|
|
63
|
-
_JS_HINT_RE = re.compile(
|
|
64
|
-
r"(?<![a-z0-9])js(?![a-z0-9])|javascript|자바스크립트|자바\s*스크립트"
|
|
65
|
-
)
|
|
66
|
-
# An explicit filename means the user is managing paths — keep the
|
|
67
|
-
# deterministic single-file flow untouched.
|
|
68
|
-
_EXPLICIT_FILENAME_RE = re.compile(
|
|
69
|
-
r"[\w-]+\.(?:html?|css|js|jsx|ts|tsx|py|json|md|txt|csv|vue|svelte)\b",
|
|
70
|
-
re.IGNORECASE,
|
|
71
|
-
)
|
|
72
|
-
_PROJECT_NAME_RE = re.compile(r"([A-Za-z][A-Za-z0-9_-]{1,30})\s*(?:앱|app\b)", re.IGNORECASE)
|
|
73
|
-
# React/Vite intent: the react keyword is specific enough on its own.
|
|
74
|
-
_REACT_HINT_RE = re.compile(r"(?<![a-z0-9])react(?![a-z0-9])|리액트")
|
|
75
|
-
_VITE_HINT_RE = re.compile(r"(?<![a-z0-9])vite(?![a-z0-9])")
|
|
76
|
-
# Python package intent: language + package word, both required.
|
|
77
|
-
_PYTHON_HINT_RE = re.compile(r"(?<![a-z0-9])python(?![a-z0-9])|파이썬")
|
|
78
|
-
_PACKAGE_HINT_RE = re.compile(r"패키지|(?<![a-z0-9])package(?![a-z0-9])")
|
|
79
|
-
_PKG_NAME_RE = re.compile(
|
|
80
|
-
r"([A-Za-z][A-Za-z0-9_-]{1,30})\s*(?:패키지|package\b)", re.IGNORECASE
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _react_manifest(text: str) -> Dict[str, Any]:
|
|
85
|
-
"""Vite + React starter manifest (review Wave 4: manifest 확장)."""
|
|
86
|
-
name_match = _PROJECT_NAME_RE.search(text)
|
|
87
|
-
name = f"{name_match.group(1).lower()}-app" if name_match else "react-app"
|
|
88
|
-
return {
|
|
89
|
-
"name": name,
|
|
90
|
-
"kind": "react",
|
|
91
|
-
"files": [
|
|
92
|
-
{
|
|
93
|
-
"path": "package.json",
|
|
94
|
-
"brief": (
|
|
95
|
-
f'Vite React app manifest: strictly valid JSON with "name": "{name}", '
|
|
96
|
-
'"private": true, "type": "module", "scripts" {"dev": "vite", '
|
|
97
|
-
'"build": "vite build", "preview": "vite preview"}, "dependencies" '
|
|
98
|
-
'with react and react-dom (^18), and "devDependencies" with vite '
|
|
99
|
-
"and @vitejs/plugin-react."
|
|
100
|
-
),
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
"path": "index.html",
|
|
104
|
-
"brief": (
|
|
105
|
-
"The Vite entry HTML: <div id=\"root\"></div> in <body> and "
|
|
106
|
-
"<script type=\"module\" src=\"/src/main.jsx\"></script> just "
|
|
107
|
-
"before </body>. No inline styles or scripts."
|
|
108
|
-
),
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
"path": "src/main.jsx",
|
|
112
|
-
"brief": (
|
|
113
|
-
"React entry: createRoot from react-dom/client rendering <App /> "
|
|
114
|
-
"into #root; imports ./App.jsx and ./App.css."
|
|
115
|
-
),
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"path": "src/App.jsx",
|
|
119
|
-
"brief": (
|
|
120
|
-
"The main App component implementing the user's request as one "
|
|
121
|
-
"self-contained React component (hooks allowed, no extra deps)."
|
|
122
|
-
),
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
"path": "src/App.css",
|
|
126
|
-
"brief": "All visual styles for the App component.",
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _python_package_manifest(text: str) -> Dict[str, Any]:
|
|
133
|
-
"""Multi-file Python package manifest (review Wave 4: manifest 확장)."""
|
|
134
|
-
name_match = _PKG_NAME_RE.search(text)
|
|
135
|
-
raw_name = name_match.group(1).lower() if name_match else "my_package"
|
|
136
|
-
module = re.sub(r"[^a-z0-9_]", "_", raw_name)
|
|
137
|
-
if not re.match(r"[a-z_]", module):
|
|
138
|
-
module = f"pkg_{module}"
|
|
139
|
-
return {
|
|
140
|
-
"name": module,
|
|
141
|
-
"kind": "python",
|
|
142
|
-
"files": [
|
|
143
|
-
{
|
|
144
|
-
"path": f"{module}/__init__.py",
|
|
145
|
-
"brief": (
|
|
146
|
-
f"Package init for {module}: import and re-export the public "
|
|
147
|
-
"API from .core with an explicit __all__."
|
|
148
|
-
),
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"path": f"{module}/core.py",
|
|
152
|
-
"brief": (
|
|
153
|
-
"Implement the user's request as clean, documented functions/"
|
|
154
|
-
"classes with type hints. Standard library only."
|
|
155
|
-
),
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"path": f"{module}/cli.py",
|
|
159
|
-
"brief": (
|
|
160
|
-
"argparse CLI wrapping the core API: a main() function and an "
|
|
161
|
-
'if __name__ == "__main__": main() guard.'
|
|
162
|
-
),
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
"path": "README.md",
|
|
166
|
-
"brief": (
|
|
167
|
-
f"Usage documentation for the {module} package: install, import "
|
|
168
|
-
"example, and CLI example."
|
|
169
|
-
),
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def infer_project_manifest(message: str) -> Optional[Dict[str, Any]]:
|
|
176
|
-
"""Infer a multi-file project manifest from a creation request.
|
|
177
|
-
|
|
178
|
-
"todo 앱 html+css+js로 만들어줘" should yield real linked files, not one
|
|
179
|
-
inlined page. Deliberately narrow and deterministic (weak local models
|
|
180
|
-
never see this decision): requires a creation verb, a recognized project
|
|
181
|
-
intent (web page + css/js, React/Vite app, or Python package), and no
|
|
182
|
-
explicit filename. Single-type requests return ``None`` so the existing
|
|
183
|
-
single-file flow is completely unchanged.
|
|
184
|
-
"""
|
|
185
|
-
text = (message or "").strip()
|
|
186
|
-
if not text or not _CREATE_VERB_RE.search(text):
|
|
187
|
-
return None
|
|
188
|
-
if _EXPLICIT_FILENAME_RE.search(text):
|
|
189
|
-
return None
|
|
190
|
-
lower = text.lower()
|
|
191
|
-
|
|
192
|
-
# Most-specific first: React (its own structure), then Python package,
|
|
193
|
-
# then the classic html+css/js web bundle.
|
|
194
|
-
if _REACT_HINT_RE.search(lower) or _VITE_HINT_RE.search(lower):
|
|
195
|
-
return _react_manifest(text)
|
|
196
|
-
if _PYTHON_HINT_RE.search(lower) and _PACKAGE_HINT_RE.search(lower):
|
|
197
|
-
return _python_package_manifest(text)
|
|
198
|
-
|
|
199
|
-
wants_html = bool(_HTML_HINT_RE.search(lower))
|
|
200
|
-
wants_css = bool(_CSS_HINT_RE.search(lower))
|
|
201
|
-
wants_js = bool(_JS_HINT_RE.search(lower))
|
|
202
|
-
if not wants_html or not (wants_css or wants_js):
|
|
203
|
-
return None
|
|
204
|
-
|
|
205
|
-
name_match = _PROJECT_NAME_RE.search(text)
|
|
206
|
-
name = f"{name_match.group(1).lower()}-app" if name_match else "web-project"
|
|
207
|
-
|
|
208
|
-
files: List[Dict[str, str]] = []
|
|
209
|
-
html_refs: List[str] = []
|
|
210
|
-
if wants_css:
|
|
211
|
-
html_refs.append('<link rel="stylesheet" href="style.css"> in <head>')
|
|
212
|
-
if wants_js:
|
|
213
|
-
html_refs.append('<script src="app.js"></script> just before </body>')
|
|
214
|
-
files.append({
|
|
215
|
-
"path": "index.html",
|
|
216
|
-
"brief": (
|
|
217
|
-
"The main HTML page of the project. Reference the sibling files: "
|
|
218
|
-
+ " and ".join(html_refs)
|
|
219
|
-
+ ". Do not inline styles or behavior scripts."
|
|
220
|
-
),
|
|
221
|
-
})
|
|
222
|
-
if wants_css:
|
|
223
|
-
files.append({
|
|
224
|
-
"path": "style.css",
|
|
225
|
-
"brief": "All visual styles for index.html (layout, colors, typography).",
|
|
226
|
-
})
|
|
227
|
-
if wants_js:
|
|
228
|
-
files.append({
|
|
229
|
-
"path": "app.js",
|
|
230
|
-
"brief": (
|
|
231
|
-
"All page behavior for index.html as plain browser JavaScript "
|
|
232
|
-
"(no build step, no imports of missing files)."
|
|
233
|
-
),
|
|
234
|
-
})
|
|
235
|
-
return {"name": name, "kind": "web", "files": files}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
"""Prompt → extract → validate → retry → repair, in that order.
|
|
2
|
-
|
|
3
|
-
The pipeline's driver. ``generate_file_content`` is the only place the model
|
|
4
|
-
is actually called; every other module here is pure. The salvage score decides
|
|
5
|
-
which rejected candidate repair gets to work from — closest to a file, not
|
|
6
|
-
longest.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import ast
|
|
12
|
-
from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple
|
|
13
|
-
|
|
14
|
-
from .extraction import _ext, _slice_json_document, extract_file_content
|
|
15
|
-
from .prompting import build_file_generation_context
|
|
16
|
-
from .repair import repair_file_content
|
|
17
|
-
from .validation import (
|
|
18
|
-
_BRACED_CODE_EXTENSIONS,
|
|
19
|
-
_COMPONENT_EXTENSIONS,
|
|
20
|
-
_check_balanced_delimiters,
|
|
21
|
-
_check_component_blocks,
|
|
22
|
-
looks_like_refusal,
|
|
23
|
-
validate_file_content,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
async def generate_file_content(
|
|
28
|
-
generate: Callable[[str], Awaitable[Any]],
|
|
29
|
-
*,
|
|
30
|
-
target_path: str,
|
|
31
|
-
user_request: str,
|
|
32
|
-
max_attempts: int = 2,
|
|
33
|
-
bundle_files: Optional[List[str]] = None,
|
|
34
|
-
) -> Tuple[str, Dict[str, Any]]:
|
|
35
|
-
"""Generate validated file content with any LLM.
|
|
36
|
-
|
|
37
|
-
``generate`` is an async callable ``context -> raw model text``. Runs up
|
|
38
|
-
to ``max_attempts`` model calls (each retry carrying corrective feedback),
|
|
39
|
-
then falls back to deterministic repair, so the returned content is
|
|
40
|
-
always non-empty and structurally valid for the target type.
|
|
41
|
-
|
|
42
|
-
One extra call beyond ``max_attempts`` is spent — at most once per
|
|
43
|
-
request — when the model has returned a byte-identical rejected reply.
|
|
44
|
-
That is the one case where the ordinary retry is known to be dead on
|
|
45
|
-
arrival: the corrective feedback did not change the reply, so the budget
|
|
46
|
-
is better spent on a prompt that names the repetition than on a third
|
|
47
|
-
identical round trip. Small local models hit this constantly; large ones
|
|
48
|
-
never do, so the extra call is not charged to models that do not need it.
|
|
49
|
-
"""
|
|
50
|
-
attempts: List[Dict[str, Any]] = []
|
|
51
|
-
feedback: Optional[str] = None
|
|
52
|
-
best_candidate = ""
|
|
53
|
-
best_score = (-1, -1)
|
|
54
|
-
seen: set[str] = set()
|
|
55
|
-
escalations_left = 1
|
|
56
|
-
attempt = 0
|
|
57
|
-
budget = max_attempts
|
|
58
|
-
while attempt < budget:
|
|
59
|
-
attempt += 1
|
|
60
|
-
context = build_file_generation_context(
|
|
61
|
-
target_path, user_request, feedback=feedback, bundle_files=bundle_files,
|
|
62
|
-
)
|
|
63
|
-
try:
|
|
64
|
-
raw = await generate(context)
|
|
65
|
-
except Exception as exc: # model backend hiccup — repair still delivers
|
|
66
|
-
attempts.append({"attempt": attempt, "valid": False, "reason": f"generation error: {exc}"})
|
|
67
|
-
feedback = "the model call failed"
|
|
68
|
-
continue
|
|
69
|
-
candidate = extract_file_content(str(raw or ""), target_path)
|
|
70
|
-
ok, reason = validate_file_content(candidate, target_path)
|
|
71
|
-
record: Dict[str, Any] = {"attempt": attempt, "valid": ok, "reason": reason}
|
|
72
|
-
if ok:
|
|
73
|
-
attempts.append(record)
|
|
74
|
-
return candidate, {"attempts": attempts, "repaired": False}
|
|
75
|
-
|
|
76
|
-
# A small model handed the same corrective feedback often replays the
|
|
77
|
-
# same reply verbatim. Saying "you sent this before" is the only signal
|
|
78
|
-
# left that has any chance of moving it, and it makes the wasted retry
|
|
79
|
-
# visible in the trace instead of looking like two genuine tries.
|
|
80
|
-
fingerprint = candidate.strip()
|
|
81
|
-
repeated = fingerprint in seen and bool(fingerprint)
|
|
82
|
-
record["repeated"] = repeated
|
|
83
|
-
seen.add(fingerprint)
|
|
84
|
-
if repeated and escalations_left and attempt >= budget:
|
|
85
|
-
# The retry budget is exhausted and the last thing it bought was a
|
|
86
|
-
# duplicate. Buy one more, but only with a prompt that says so.
|
|
87
|
-
escalations_left -= 1
|
|
88
|
-
budget += 1
|
|
89
|
-
record["escalated"] = True
|
|
90
|
-
attempts.append(record)
|
|
91
|
-
|
|
92
|
-
# Keep the candidate that is *closest to a file*, not the longest one.
|
|
93
|
-
# Longest-wins handed repair a 900-character apology in preference to a
|
|
94
|
-
# 300-character HTML document that only needed its </html> closing —
|
|
95
|
-
# and repair can finish the document but can only bury the apology.
|
|
96
|
-
score = _salvage_score(candidate, target_path)
|
|
97
|
-
if score > best_score:
|
|
98
|
-
best_score, best_candidate = score, candidate
|
|
99
|
-
|
|
100
|
-
feedback = (
|
|
101
|
-
f"{reason}. You already sent exactly this reply and it was rejected "
|
|
102
|
-
"for the same reason — do not repeat it. Output the file itself, "
|
|
103
|
-
"starting at its first character."
|
|
104
|
-
if repeated else reason
|
|
105
|
-
)
|
|
106
|
-
repaired = repair_file_content(best_candidate, target_path, user_request)
|
|
107
|
-
return repaired, {"attempts": attempts, "repaired": True}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _salvage_score(candidate: str, target_path: str) -> Tuple[int, int]:
|
|
111
|
-
"""How useful an invalid candidate is as raw material for repair.
|
|
112
|
-
|
|
113
|
-
``(tier, length)`` — tier first, so a short real document always beats a
|
|
114
|
-
long non-document; length breaks ties within a tier.
|
|
115
|
-
|
|
116
|
-
Tier 2 something of the right shape that repair can finish (an HTML
|
|
117
|
-
document missing its close tag, parseable-ish JSON, Python that
|
|
118
|
-
at least tokenises).
|
|
119
|
-
Tier 1 ordinary text: no structure, but the words may be the content.
|
|
120
|
-
Tier 0 a refusal — repair should prefer literally anything else, because
|
|
121
|
-
an apology written into the file is worse than an empty stub.
|
|
122
|
-
"""
|
|
123
|
-
text = candidate.strip()
|
|
124
|
-
if not text:
|
|
125
|
-
return (0, 0)
|
|
126
|
-
if looks_like_refusal(text):
|
|
127
|
-
return (0, len(text))
|
|
128
|
-
|
|
129
|
-
ext = _ext(target_path)
|
|
130
|
-
lower = text.lower()
|
|
131
|
-
if ext in (".html", ".htm"):
|
|
132
|
-
if lower.startswith("<!doctype") or lower.startswith("<html"):
|
|
133
|
-
return (2, len(text))
|
|
134
|
-
elif ext == ".json":
|
|
135
|
-
if _slice_json_document(text) is not None:
|
|
136
|
-
return (2, len(text))
|
|
137
|
-
elif ext == ".py":
|
|
138
|
-
try:
|
|
139
|
-
ast.parse(text)
|
|
140
|
-
except SyntaxError:
|
|
141
|
-
pass
|
|
142
|
-
else:
|
|
143
|
-
return (2, len(text))
|
|
144
|
-
elif ext in _BRACED_CODE_EXTENSIONS:
|
|
145
|
-
if _check_balanced_delimiters(text)[0]:
|
|
146
|
-
return (2, len(text))
|
|
147
|
-
elif ext in _COMPONENT_EXTENSIONS:
|
|
148
|
-
if _check_component_blocks(text)[0]:
|
|
149
|
-
return (2, len(text))
|
|
150
|
-
elif ext == ".css" and "{" in text and "}" in text:
|
|
151
|
-
return (2, len(text))
|
|
152
|
-
return (1, len(text))
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
"""Extension-aware generation instructions — step 1 of the pipeline.
|
|
2
|
-
|
|
3
|
-
Small models ignore abstract rules but reliably imitate concrete anchors, so
|
|
4
|
-
the prompt pins the exact first line of the expected output and the structural
|
|
5
|
-
rule for the target type. Multi-file bundles swap in the rule that links
|
|
6
|
-
sibling files instead of inlining everything.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
from typing import Dict, List, Optional
|
|
12
|
-
|
|
13
|
-
from .extraction import _ext
|
|
14
|
-
|
|
15
|
-
_FIRST_LINE_HINTS: Dict[str, str] = {
|
|
16
|
-
".html": "<!DOCTYPE html>",
|
|
17
|
-
".htm": "<!DOCTYPE html>",
|
|
18
|
-
".py": "# (python code — imports or code on the first line)",
|
|
19
|
-
".sh": "#!/bin/sh",
|
|
20
|
-
".json": "{",
|
|
21
|
-
".xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
_TYPE_RULES: Dict[str, str] = {
|
|
26
|
-
".html": (
|
|
27
|
-
"Produce ONE complete standalone HTML5 document: <!DOCTYPE html>, <html>, "
|
|
28
|
-
"<head> with <meta charset=\"utf-8\"> and a <title>, inline <style> for CSS, "
|
|
29
|
-
"and a closed </html> tag. Do not reference external files."
|
|
30
|
-
),
|
|
31
|
-
".htm": (
|
|
32
|
-
"Produce ONE complete standalone HTML5 document ending with </html>."
|
|
33
|
-
),
|
|
34
|
-
".json": "Produce strictly valid JSON (double quotes, no comments, no trailing commas).",
|
|
35
|
-
".css": "Produce valid CSS rules only.",
|
|
36
|
-
".md": "Produce well-structured Markdown with headings.",
|
|
37
|
-
".markdown": "Produce well-structured Markdown with headings.",
|
|
38
|
-
".csv": "Produce CSV with a header row; comma-separated, one record per line.",
|
|
39
|
-
".py": "Produce complete runnable Python source code.",
|
|
40
|
-
".js": "Produce complete valid JavaScript source code.",
|
|
41
|
-
".jsx": "Produce one complete React component file in JSX.",
|
|
42
|
-
".ts": "Produce complete valid TypeScript source code.",
|
|
43
|
-
".tsx": "Produce one complete React component file in TSX (TypeScript).",
|
|
44
|
-
".vue": "Produce ONE complete Vue single-file component with closed <template>/<script>/<style> blocks.",
|
|
45
|
-
".svelte": "Produce ONE complete Svelte component; every <script>/<style> block must be closed.",
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# Multi-file bundles override the standalone-HTML rule: the page must link
|
|
50
|
-
# its sibling files instead of inlining everything.
|
|
51
|
-
_BUNDLE_HTML_RULE = (
|
|
52
|
-
"Produce ONE complete HTML5 document: <!DOCTYPE html>, <html>, <head> with "
|
|
53
|
-
"<meta charset=\"utf-8\"> and a <title>, and a closed </html> tag. "
|
|
54
|
-
"This page is part of a multi-file project: link the project stylesheet(s) "
|
|
55
|
-
"with <link rel=\"stylesheet\" href=\"...\"> and load the project script(s) "
|
|
56
|
-
"with <script src=\"...\"></script> just before </body>. Reference ONLY the "
|
|
57
|
-
"project files listed below — no other external files, no inline <style> "
|
|
58
|
-
"blocks, no inline behavior scripts."
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# Vite/React bundles need a module entry point, not classic script tags.
|
|
63
|
-
_BUNDLE_HTML_MODULE_RULE = (
|
|
64
|
-
"Produce ONE complete HTML5 document: <!DOCTYPE html>, <html>, <head> with "
|
|
65
|
-
"<meta charset=\"utf-8\"> and a <title>, and a closed </html> tag. "
|
|
66
|
-
"This page is the Vite entry of a React project: the <body> must contain "
|
|
67
|
-
"<div id=\"root\"></div> and load the app with "
|
|
68
|
-
"<script type=\"module\" src=\"/src/main.jsx\"></script> just before "
|
|
69
|
-
"</body>. No inline <style> blocks, no other scripts, no external files."
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _bundle_html_rule(bundle_files: List[str]) -> str:
|
|
74
|
-
"""Pick the HTML bundle rule that matches the bundle's technology."""
|
|
75
|
-
if any(str(path).lower().endswith((".jsx", ".tsx")) for path in bundle_files):
|
|
76
|
-
return _BUNDLE_HTML_MODULE_RULE
|
|
77
|
-
return _BUNDLE_HTML_RULE
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def build_file_generation_context(
|
|
81
|
-
target_path: str,
|
|
82
|
-
user_request: str,
|
|
83
|
-
feedback: Optional[str] = None,
|
|
84
|
-
bundle_files: Optional[List[str]] = None,
|
|
85
|
-
) -> str:
|
|
86
|
-
"""Strict, extension-aware generation instructions.
|
|
87
|
-
|
|
88
|
-
Small models ignore abstract rules but reliably imitate concrete anchors,
|
|
89
|
-
so the prompt pins the exact first line of the expected output.
|
|
90
|
-
"""
|
|
91
|
-
ext = _ext(target_path)
|
|
92
|
-
parts = [
|
|
93
|
-
"You are a file content generator. Your entire reply is saved verbatim "
|
|
94
|
-
f"as the file `{target_path}` — it is NOT shown in a chat.",
|
|
95
|
-
"Rules:",
|
|
96
|
-
"- Output ONLY the raw file content.",
|
|
97
|
-
"- No Markdown code fences (```), no explanations, no greetings, "
|
|
98
|
-
"no text before or after the content.",
|
|
99
|
-
]
|
|
100
|
-
type_rule = _TYPE_RULES.get(ext)
|
|
101
|
-
if bundle_files and ext in (".html", ".htm"):
|
|
102
|
-
type_rule = _bundle_html_rule(bundle_files)
|
|
103
|
-
if type_rule:
|
|
104
|
-
parts.append(f"- {type_rule}")
|
|
105
|
-
if bundle_files:
|
|
106
|
-
listed = ", ".join(bundle_files)
|
|
107
|
-
parts.append(f"- Project files in this bundle: {listed}")
|
|
108
|
-
first_line = _FIRST_LINE_HINTS.get(ext)
|
|
109
|
-
if first_line:
|
|
110
|
-
parts.append(f"- The very first line of your reply must be: {first_line}")
|
|
111
|
-
if feedback:
|
|
112
|
-
parts.append(
|
|
113
|
-
"Your previous attempt was rejected: "
|
|
114
|
-
f"{feedback}. Fix that and output only the corrected file content."
|
|
115
|
-
)
|
|
116
|
-
parts.append(f"\nUser request: {user_request}")
|
|
117
|
-
return "\n".join(parts)
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
"""Deterministic scaffolds — step 5, the last resort after the retries.
|
|
2
|
-
|
|
3
|
-
The user asked for a file, so the request must still end in a well-formed file
|
|
4
|
-
of the target type, never an error. Nothing here calls a model: whatever the
|
|
5
|
-
model did produce is salvaged where it can be, and honestly labelled where it
|
|
6
|
-
cannot.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import ast
|
|
12
|
-
import html as html_lib
|
|
13
|
-
import json
|
|
14
|
-
import re
|
|
15
|
-
|
|
16
|
-
from .extraction import _ext, _slice_html_document, _slice_json_document
|
|
17
|
-
from .validation import looks_like_refusal
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def repair_file_content(content: str, target_path: str, user_request: str) -> str:
|
|
21
|
-
"""Turn whatever the model produced into a valid file of the target type.
|
|
22
|
-
|
|
23
|
-
This is the last resort after retries: the user asked for a file, so the
|
|
24
|
-
request must still end in a well-formed file, never an error.
|
|
25
|
-
"""
|
|
26
|
-
ext = _ext(target_path)
|
|
27
|
-
salvage = content.strip()
|
|
28
|
-
if looks_like_refusal(salvage):
|
|
29
|
-
salvage = ""
|
|
30
|
-
|
|
31
|
-
if ext in (".html", ".htm"):
|
|
32
|
-
return _repair_html(salvage, user_request)
|
|
33
|
-
if ext == ".json":
|
|
34
|
-
sliced = _slice_json_document(salvage)
|
|
35
|
-
if sliced is not None:
|
|
36
|
-
return sliced
|
|
37
|
-
return json.dumps(
|
|
38
|
-
{"request": user_request, "content": salvage},
|
|
39
|
-
ensure_ascii=False,
|
|
40
|
-
indent=2,
|
|
41
|
-
)
|
|
42
|
-
if ext == ".py" and salvage:
|
|
43
|
-
# The repair guarantee for Python is parseability: unparseable output
|
|
44
|
-
# is preserved honestly as a commented-out draft, never as a broken
|
|
45
|
-
# module the user has to debug.
|
|
46
|
-
try:
|
|
47
|
-
ast.parse(salvage)
|
|
48
|
-
return salvage
|
|
49
|
-
except SyntaxError:
|
|
50
|
-
commented = "\n".join(f"# {line}" for line in salvage.splitlines())
|
|
51
|
-
return (
|
|
52
|
-
f"# TODO: model produced invalid Python for: {user_request}\n"
|
|
53
|
-
"# The draft below is preserved as comments — fix and uncomment.\n"
|
|
54
|
-
f"{commented}\n"
|
|
55
|
-
)
|
|
56
|
-
if salvage:
|
|
57
|
-
return salvage
|
|
58
|
-
# Nothing usable at all — leave an honest placeholder in the right format.
|
|
59
|
-
comment = {
|
|
60
|
-
".py": "# TODO: model produced no usable content for: ",
|
|
61
|
-
".js": "// TODO: model produced no usable content for: ",
|
|
62
|
-
".jsx": "// TODO: model produced no usable content for: ",
|
|
63
|
-
".ts": "// TODO: model produced no usable content for: ",
|
|
64
|
-
".tsx": "// TODO: model produced no usable content for: ",
|
|
65
|
-
".css": "/* TODO: model produced no usable content for: ",
|
|
66
|
-
".sh": "# TODO: model produced no usable content for: ",
|
|
67
|
-
".sql": "-- TODO: model produced no usable content for: ",
|
|
68
|
-
}.get(ext, "")
|
|
69
|
-
if ext == ".css":
|
|
70
|
-
return f"{comment}{user_request} */\n"
|
|
71
|
-
if comment:
|
|
72
|
-
return f"{comment}{user_request}\n"
|
|
73
|
-
return f"{user_request}\n"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _repair_html(salvage: str, user_request: str) -> str:
|
|
77
|
-
lower = salvage.lower()
|
|
78
|
-
if "<html" in lower or "<!doctype" in lower:
|
|
79
|
-
# A real document that is merely truncated — close it.
|
|
80
|
-
doc = _slice_html_document(salvage)
|
|
81
|
-
low = doc.lower()
|
|
82
|
-
if "</body>" not in low and "<body" in low:
|
|
83
|
-
doc += "\n</body>"
|
|
84
|
-
if "</html>" not in low:
|
|
85
|
-
doc += "\n</html>"
|
|
86
|
-
return doc
|
|
87
|
-
if re.search(r"<\w+[^>]*>", salvage):
|
|
88
|
-
body = salvage # an HTML fragment — embed as-is
|
|
89
|
-
elif salvage:
|
|
90
|
-
body = "\n".join(
|
|
91
|
-
f" <p>{html_lib.escape(line)}</p>"
|
|
92
|
-
for line in salvage.splitlines()
|
|
93
|
-
if line.strip()
|
|
94
|
-
)
|
|
95
|
-
else:
|
|
96
|
-
body = f" <p>{html_lib.escape(user_request)}</p>"
|
|
97
|
-
title = html_lib.escape(user_request[:60] or "Generated page")
|
|
98
|
-
return (
|
|
99
|
-
"<!DOCTYPE html>\n"
|
|
100
|
-
"<html lang=\"ko\">\n"
|
|
101
|
-
"<head>\n"
|
|
102
|
-
" <meta charset=\"utf-8\">\n"
|
|
103
|
-
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
|
|
104
|
-
f" <title>{title}</title>\n"
|
|
105
|
-
" <style>\n"
|
|
106
|
-
" body { font-family: system-ui, sans-serif; margin: 2rem auto; "
|
|
107
|
-
"max-width: 720px; line-height: 1.6; padding: 0 1rem; }\n"
|
|
108
|
-
" </style>\n"
|
|
109
|
-
"</head>\n"
|
|
110
|
-
"<body>\n"
|
|
111
|
-
f"{body}\n"
|
|
112
|
-
"</body>\n"
|
|
113
|
-
"</html>"
|
|
114
|
-
)
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"""The single write-side guarantee for model-produced file content.
|
|
2
|
-
|
|
3
|
-
``sanitize_write_content`` is the one door every write entry point goes
|
|
4
|
-
through — the direct chat path, the agent JSON loop, the tools layer — so a
|
|
5
|
-
fenced or chatty payload can never be persisted verbatim no matter which
|
|
6
|
-
surface produced it.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
from typing import Any, Dict, Tuple
|
|
12
|
-
|
|
13
|
-
from .extraction import extract_file_content
|
|
14
|
-
from .repair import repair_file_content
|
|
15
|
-
from .validation import validate_file_content
|
|
16
|
-
|
|
17
|
-
# Extensions the Brain UI can render inline (preview) after creation.
|
|
18
|
-
PREVIEWABLE_EXTENSIONS = frozenset({
|
|
19
|
-
".html", ".htm", ".md", ".markdown", ".txt", ".json", ".css", ".js",
|
|
20
|
-
".csv", ".py", ".yaml", ".yml", ".xml", ".sql", ".sh",
|
|
21
|
-
".jsx", ".ts", ".tsx", ".vue", ".svelte",
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def sanitize_write_content(
|
|
26
|
-
target_path: str,
|
|
27
|
-
content: Any,
|
|
28
|
-
user_request: str = "",
|
|
29
|
-
) -> Tuple[str, Dict[str, Any]]:
|
|
30
|
-
"""Single write-side guarantee for model-produced file content.
|
|
31
|
-
|
|
32
|
-
The direct chat path already runs the full generate→validate→repair
|
|
33
|
-
pipeline, but the agent JSON loop historically wrote ``args.content``
|
|
34
|
-
verbatim — weak models routinely put fenced/chatty payloads there. This
|
|
35
|
-
conservative sanitizer closes that gap for *any* write entry point:
|
|
36
|
-
|
|
37
|
-
1. content that already validates is returned byte-for-byte unchanged
|
|
38
|
-
(trusted/user-authored content is never mangled);
|
|
39
|
-
2. otherwise the extraction pass strips fences/think-blocks/chat noise
|
|
40
|
-
and is used only when the extracted payload validates;
|
|
41
|
-
3. otherwise deterministic repair guarantees a structurally valid file.
|
|
42
|
-
|
|
43
|
-
Empty content is left untouched (creating an empty file is a legitimate,
|
|
44
|
-
intentional action — e.g. ``__init__.py``). Returns ``(content, meta)``
|
|
45
|
-
where meta is ``{"sanitized": bool, "repaired": bool, "reason": str}``.
|
|
46
|
-
"""
|
|
47
|
-
raw = str(content or "")
|
|
48
|
-
if not raw.strip():
|
|
49
|
-
return raw, {"sanitized": False, "repaired": False, "reason": "empty"}
|
|
50
|
-
ok, reason = validate_file_content(raw, target_path)
|
|
51
|
-
if ok:
|
|
52
|
-
return raw, {"sanitized": False, "repaired": False, "reason": "ok"}
|
|
53
|
-
extracted = extract_file_content(raw, target_path)
|
|
54
|
-
if extracted:
|
|
55
|
-
extracted_ok, _ = validate_file_content(extracted, target_path)
|
|
56
|
-
if extracted_ok:
|
|
57
|
-
return extracted, {"sanitized": True, "repaired": False, "reason": reason}
|
|
58
|
-
repaired = repair_file_content(
|
|
59
|
-
extracted or raw, target_path, user_request or f"content for {target_path}"
|
|
60
|
-
)
|
|
61
|
-
return repaired, {"sanitized": True, "repaired": True, "reason": reason}
|