ltcai 10.2.0 → 10.4.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 +40 -44
- package/docs/CHANGELOG.md +125 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/MYPY_BACKLOG.md +65 -0
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/SURFACE_PARITY.md +33 -6
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/context.py +23 -3
- package/lattice_brain/graph/_kg_common.py +164 -9
- package/lattice_brain/graph/_kg_contract.py +223 -0
- package/lattice_brain/graph/_kg_fsutil.py +2 -2
- package/lattice_brain/graph/curator.py +4 -4
- package/lattice_brain/graph/discovery.py +12 -1
- package/lattice_brain/graph/discovery_index.py +12 -1
- package/lattice_brain/graph/documents.py +17 -2
- package/lattice_brain/graph/ingest.py +12 -1
- package/lattice_brain/graph/proactive.py +9 -5
- package/lattice_brain/graph/projection.py +12 -1
- package/lattice_brain/graph/provenance.py +12 -1
- package/lattice_brain/graph/retrieval.py +12 -1
- package/lattice_brain/graph/retrieval_docgen.py +12 -1
- package/lattice_brain/graph/retrieval_reads.py +12 -1
- package/lattice_brain/graph/retrieval_vector.py +12 -1
- package/lattice_brain/graph/schema.py +6 -2
- package/lattice_brain/graph/write_master.py +12 -1
- package/lattice_brain/ingestion.py +1 -1
- package/lattice_brain/quality.py +5 -5
- package/lattice_brain/runtime/agent_runtime.py +7 -2
- package/lattice_brain/runtime/contracts.py +2 -1
- package/lattice_brain/runtime/hooks.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/workflow.py +10 -7
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +2 -2
- package/latticeai/api/chat.py +19 -15
- package/latticeai/api/chat_agent_http.py +19 -7
- package/latticeai/api/chat_helpers.py +9 -8
- package/latticeai/api/chat_stream.py +2 -2
- package/latticeai/api/computer_use.py +2 -2
- package/latticeai/api/hooks.py +4 -2
- package/latticeai/api/local_files.py +8 -4
- package/latticeai/api/mcp.py +2 -2
- package/latticeai/api/models.py +5 -5
- package/latticeai/api/permissions.py +2 -2
- package/latticeai/api/search.py +5 -3
- package/latticeai/api/security_dashboard.py +3 -5
- package/latticeai/api/setup.py +3 -3
- package/latticeai/api/static_routes.py +7 -2
- package/latticeai/api/tools.py +9 -4
- package/latticeai/api/workspace.py +25 -22
- package/latticeai/app_factory.py +132 -1346
- package/latticeai/cli/entrypoint.py +1 -1
- package/latticeai/core/agent.py +28 -14
- package/latticeai/core/audit.py +5 -3
- package/latticeai/core/embedding_providers.py +19 -6
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +2 -2
- package/latticeai/core/mcp_catalog.py +3 -1
- package/latticeai/core/model_compat.py +1 -1
- package/latticeai/core/plugins.py +1 -1
- package/latticeai/core/project_sessions.py +1 -1
- package/latticeai/core/run_explain.py +6 -4
- package/latticeai/core/workspace_os.py +0 -4
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/core/workspace_os_state.py +1 -1
- package/latticeai/core/workspace_snapshots.py +3 -3
- package/latticeai/integrations/telegram_bot.py +52 -3
- package/latticeai/models/router.py +61 -34
- package/latticeai/runtime/access_runtime.py +3 -1
- package/latticeai/runtime/build_phases.py +1417 -0
- package/latticeai/runtime/history_writer.py +138 -0
- package/latticeai/runtime/review_wiring.py +6 -1
- package/latticeai/runtime/runtime_context.py +276 -0
- package/latticeai/runtime/sso_config_runtime.py +1 -1
- package/latticeai/runtime/stages.py +8 -2
- package/latticeai/services/app_context.py +23 -0
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/brain_intelligence.py +4 -2
- package/latticeai/services/cloud_streaming.py +5 -3
- package/latticeai/services/command_center.py +1 -1
- package/latticeai/services/evidence_actions.py +2 -1
- package/latticeai/services/hybrid_chat.py +1 -1
- package/latticeai/services/local_knowledge.py +25 -9
- package/latticeai/services/memory_service.py +8 -4
- package/latticeai/services/model_catalog.py +12 -12
- package/latticeai/services/model_engines.py +15 -17
- package/latticeai/services/model_loading.py +1 -1
- package/latticeai/services/model_runtime.py +31 -33
- package/latticeai/services/multimodal_streaming.py +10 -4
- package/latticeai/services/openai_compatible_adapter.py +4 -3
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/run_executor.py +12 -3
- package/latticeai/services/search_service.py +1 -1
- package/latticeai/services/triggers.py +1 -1
- package/latticeai/setup/auto_setup.py +1 -1
- package/latticeai/setup/wizard.py +12 -3
- package/latticeai/tools/computer.py +3 -1
- package/latticeai/tools/documents.py +2 -2
- package/package.json +4 -2
- package/scripts/check_current_release_docs.mjs +1 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +37 -37
- package/static/app/assets/{Act-CbdGD-2i.js → Act-BK__Y5VE.js} +1 -1
- package/static/app/assets/{AdminConsole-LgCkXpnf.js → AdminConsole-ZrTyN8_F.js} +1 -1
- package/static/app/assets/{Brain-CoPGJI1L.js → Brain-CYHDXpPB.js} +1 -1
- package/static/app/assets/{BrainHome-gVnaxSwp.js → BrainHome-bbJulb5g.js} +1 -1
- package/static/app/assets/{BrainSignals-ChxAYHtj.js → BrainSignals-CesSPtUP.js} +1 -1
- package/static/app/assets/{Capture-DBtgkHZg.js → Capture-p5upXmz2.js} +1 -1
- package/static/app/assets/{CommandPalette-Ovtv5I0Y.js → CommandPalette-0zkyC0RY.js} +1 -1
- package/static/app/assets/Library-Dt-vABI2.js +1 -0
- package/static/app/assets/{LivingBrain-CNz-6NSd.js → LivingBrain-Bz-h6LBH.js} +1 -1
- package/static/app/assets/{ProductFlow-ePX-Y73J.js → ProductFlow-DAZ7DJCE.js} +1 -1
- package/static/app/assets/{ReviewCard-C4mpvkwH.js → ReviewCard-Bv0WGbY6.js} +1 -1
- package/static/app/assets/{System-BvWNK1zc.js → System-BhrJQSD_.js} +1 -1
- package/static/app/assets/{activity-CiauPV_3.js → activity-AolpZJCH.js} +1 -1
- package/static/app/assets/{bot-Dwct-Q1x.js → bot-CsVsT2nd.js} +1 -1
- package/static/app/assets/{brain-BKDW1F0T.js → brain-vuppco5y.js} +1 -1
- package/static/app/assets/{button-CCB9Kuw7.js → button-1BP1PJZ3.js} +1 -1
- package/static/app/assets/{circle-pause-DNa8WHC5.js → circle-pause-5paeCOtq.js} +1 -1
- package/static/app/assets/{circle-play-8jmxn5mf.js → circle-play-By8mFYRV.js} +1 -1
- package/static/app/assets/{cpu-mxvF3V1I.js → cpu-DvUQxdKe.js} +1 -1
- package/static/app/assets/{download--SmCcx0p.js → download-CFqYG90a.js} +1 -1
- package/static/app/assets/{folder-open-D4Wy5roo.js → folder-open-CVSnp-Dp.js} +1 -1
- package/static/app/assets/{hard-drive-BwQcSPlS.js → hard-drive-Ckr4Vd6-.js} +1 -1
- package/static/app/assets/{index-DDV2YZwM.js → index--0zKF5Ow.js} +3 -3
- package/static/app/assets/index-CEkL4_M-.css +2 -0
- package/static/app/assets/{input-DFhSjmLS.js → input-D-JKquXU.js} +1 -1
- package/static/app/assets/{network-Bts7VO94.js → network-DB88FZ2T.js} +1 -1
- package/static/app/assets/{primitives-BuSMEJY8.js → primitives-D8U9OaIo.js} +1 -1
- package/static/app/assets/search-B8jsVTw_.js +1 -0
- package/static/app/assets/{shield-alert-BfTO6X_G.js → shield-alert-DnsvO79P.js} +1 -1
- package/static/app/assets/{textarea-BjctW1oh.js → textarea-4RchwbTa.js} +1 -1
- package/static/app/assets/{useFocusTrap-CE43-LrA.js → useFocusTrap-CFJjy7cS.js} +1 -1
- package/static/app/assets/{useQuery-Bv3ejLL5.js → useQuery-3Reu-A4a.js} +1 -1
- package/static/app/assets/{users-CsNqLZAj.js → users-BTxYksxa.js} +1 -1
- package/static/app/assets/{utils-KFFdVG_t.js → utils-D02Vf-fw.js} +1 -1
- package/static/app/assets/{workspace-wdCvdyPF.js → workspace-CSJNgX-C.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/static/app/assets/Library-CBia2Fvm.js +0 -1
- package/static/app/assets/index-CQWdDU3z.css +0 -2
- package/static/app/assets/search-DZzxhWaQ.js +0 -1
package/latticeai/app_factory.py
CHANGED
|
@@ -10,59 +10,25 @@ and no file is created until ``create_app``/``build_runtime`` is called.
|
|
|
10
10
|
``build_runtime`` returns the full constructed namespace (every name the
|
|
11
11
|
legacy module-level assembly exposed); ``latticeai.server_app`` proxies it
|
|
12
12
|
lazily via module ``__getattr__`` for backwards compatibility.
|
|
13
|
+
|
|
14
|
+
The assembly itself lives in :mod:`latticeai.runtime.build_phases` as ten
|
|
15
|
+
ordered phases sharing a :class:`~latticeai.runtime.runtime_context.RuntimeContext`.
|
|
16
|
+
This module is now only the *orchestrator*: run the phases, then select the
|
|
17
|
+
explicit compatibility surface. It used to be a single 1,300-line ``_build``
|
|
18
|
+
closure, which worked because closures resolve free variables at call time —
|
|
19
|
+
the RuntimeContext preserves exactly that property while naming the state.
|
|
13
20
|
"""
|
|
14
21
|
|
|
15
22
|
from __future__ import annotations
|
|
16
23
|
|
|
17
24
|
import threading
|
|
18
25
|
from dataclasses import dataclass
|
|
19
|
-
from typing import TYPE_CHECKING, Any, Dict,
|
|
26
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
20
27
|
|
|
21
|
-
from latticeai.
|
|
22
|
-
from latticeai.runtime.access_runtime import build_access_runtime
|
|
23
|
-
from latticeai.runtime.audit_runtime import build_audit_runtime
|
|
24
|
-
from latticeai.runtime.bootstrap import build_session_runtime
|
|
25
|
-
from latticeai.runtime.brain_runtime import build_brain_runtime
|
|
26
|
-
from latticeai.runtime.chat_wiring import (
|
|
27
|
-
build_chat_agent_runtime_from_context,
|
|
28
|
-
build_interaction_contexts,
|
|
29
|
-
maybe_build_telegram_chat_mirror,
|
|
30
|
-
)
|
|
31
|
-
from latticeai.runtime.config_runtime import build_config_runtime
|
|
32
|
-
from latticeai.runtime.context_runtime import build_context_runtime
|
|
33
|
-
from latticeai.runtime.history_runtime import build_history_query_runtime
|
|
34
|
-
from latticeai.runtime.hooks_runtime import (
|
|
35
|
-
bind_builtin_hook_runners,
|
|
36
|
-
bind_trigger_hook_runner,
|
|
37
|
-
build_hooks_runtime,
|
|
38
|
-
)
|
|
39
|
-
from latticeai.runtime.lifespan_runtime import build_lifespan_runtime
|
|
40
|
-
from latticeai.runtime.model_wiring import (
|
|
41
|
-
configure_model_runtime_from_context,
|
|
42
|
-
register_model_runtime_routers,
|
|
43
|
-
)
|
|
28
|
+
from latticeai.runtime.build_phases import BUILD_PHASES
|
|
44
29
|
from latticeai.runtime.namespace_runtime import RuntimeBundle, build_runtime_namespace
|
|
45
|
-
from latticeai.runtime.
|
|
46
|
-
from latticeai.runtime.
|
|
47
|
-
from latticeai.runtime.platform_runtime_wiring import build_platform_automation_runtime
|
|
48
|
-
from latticeai.runtime.platform_services_runtime import (
|
|
49
|
-
build_brain_network,
|
|
50
|
-
)
|
|
51
|
-
from latticeai.runtime.review_wiring import build_review_run_now_runner
|
|
52
|
-
from latticeai.runtime.router_registration import (
|
|
53
|
-
build_auth_admin_security_router_bundle,
|
|
54
|
-
build_router_bundle,
|
|
55
|
-
build_static_routes_bundle,
|
|
56
|
-
register_foundation_routers,
|
|
57
|
-
register_health_and_model_routers,
|
|
58
|
-
register_interaction_routers,
|
|
59
|
-
register_platform_feature_routers,
|
|
60
|
-
register_review_and_brain_tail_routers,
|
|
61
|
-
)
|
|
62
|
-
from latticeai.runtime.security_runtime import build_security_runtime
|
|
63
|
-
from latticeai.runtime.sso_config_runtime import build_sso_config_runtime
|
|
64
|
-
from latticeai.runtime.user_key_runtime import build_user_key_runtime
|
|
65
|
-
from latticeai.runtime.web_runtime import build_web_runtime
|
|
30
|
+
from latticeai.runtime.router_registration import build_router_bundle
|
|
31
|
+
from latticeai.runtime.runtime_context import RuntimeContext
|
|
66
32
|
|
|
67
33
|
if TYPE_CHECKING: # imports for annotations only — keep module import light
|
|
68
34
|
from fastapi import FastAPI
|
|
@@ -70,70 +36,19 @@ if TYPE_CHECKING: # imports for annotations only — keep module import light
|
|
|
70
36
|
from latticeai.core.config import Config
|
|
71
37
|
|
|
72
38
|
|
|
73
|
-
def
|
|
74
|
-
"""The
|
|
39
|
+
def _legacy_exports(ctx: RuntimeContext) -> Dict[str, Any]:
|
|
40
|
+
"""The explicit, allowlisted compatibility surface for ``server_app``.
|
|
75
41
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
42
|
+
Two kinds of name appear here: values the phases produced (read off the
|
|
43
|
+
context) and pass-through re-exports of module functions that historical
|
|
44
|
+
callers reached through ``server_app``. Keeping them in one function makes
|
|
45
|
+
the surface countable — ``namespace_runtime`` rejects anything not on its
|
|
46
|
+
allowlist.
|
|
79
47
|
"""
|
|
80
|
-
import logging
|
|
81
|
-
import os
|
|
82
|
-
import threading
|
|
83
|
-
from pathlib import Path
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
import mlx.core as mx
|
|
87
|
-
mx.set_default_device(mx.gpu)
|
|
88
|
-
print("✅ MLX Metal context initialized in main thread.")
|
|
89
|
-
except Exception as e:
|
|
90
|
-
print(f"⚠️ MLX Metal context unavailable: {e}")
|
|
91
|
-
mx = None
|
|
92
|
-
import uvicorn
|
|
93
|
-
from fastapi import HTTPException, Request
|
|
94
|
-
from pydantic import BaseModel
|
|
95
|
-
|
|
96
|
-
from lattice_brain.graph.runtime import set_llm_router
|
|
97
|
-
from lattice_brain.graph.schema import set_embed_dim
|
|
98
|
-
from lattice_brain.ingestion import IngestionItem
|
|
99
|
-
from lattice_brain.storage import storage_from_env
|
|
100
|
-
from latticeai.api.admin import create_admin_router
|
|
101
|
-
from latticeai.api.agent_registry import create_agent_registry_router
|
|
102
|
-
from latticeai.api.agents import create_agents_router
|
|
103
|
-
from latticeai.api.auth import create_auth_router
|
|
104
|
-
from latticeai.api.automation_intelligence import (
|
|
105
|
-
create_automation_intelligence_router,
|
|
106
|
-
)
|
|
107
|
-
from latticeai.api.brain_intelligence import create_brain_intelligence_router
|
|
108
|
-
from latticeai.api.browser import create_browser_router
|
|
109
|
-
from latticeai.api.change_proposals import create_change_proposals_router
|
|
110
|
-
from latticeai.api.chat import build_recent_chat_context, create_chat_router
|
|
111
|
-
from latticeai.api.command_center import create_command_center_router
|
|
112
|
-
from latticeai.api.garden import create_garden_router
|
|
113
|
-
from latticeai.api.health import create_health_router
|
|
114
|
-
from latticeai.api.hooks import create_hooks_router
|
|
115
|
-
from latticeai.api.invitations import create_invitations_router
|
|
116
|
-
from latticeai.api.marketplace import create_marketplace_router
|
|
117
|
-
from latticeai.api.memory import create_memory_router
|
|
118
|
-
from latticeai.api.models import create_models_router
|
|
119
|
-
from latticeai.api.network import create_network_router
|
|
120
|
-
|
|
121
|
-
# ── v2 Agentic Workspace Platform layers ─────────────────────────────────────
|
|
122
|
-
from latticeai.api.plugins import create_plugins_router
|
|
123
|
-
from latticeai.api.portability import create_portability_router
|
|
124
|
-
from latticeai.api.realtime import create_realtime_router
|
|
125
|
-
from latticeai.api.search import create_search_router
|
|
126
48
|
from latticeai.api.security_dashboard import (
|
|
127
49
|
create_security_router as _create_security_router,
|
|
128
50
|
)
|
|
129
|
-
from latticeai.api.
|
|
130
|
-
from latticeai.api.static_routes import create_static_routes_router
|
|
131
|
-
from latticeai.api.tools import create_tools_router
|
|
132
|
-
from latticeai.api.workflow_designer import create_workflow_designer_router
|
|
133
|
-
from latticeai.api.workspace import (
|
|
134
|
-
_workspace_scope_from_request,
|
|
135
|
-
create_workspace_router,
|
|
136
|
-
)
|
|
51
|
+
from latticeai.api.workspace import _workspace_scope_from_request
|
|
137
52
|
from latticeai.core.audit import (
|
|
138
53
|
build_admin_audit_report as _build_admin_audit_report,
|
|
139
54
|
)
|
|
@@ -143,1275 +58,125 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
143
58
|
from latticeai.core.audit import (
|
|
144
59
|
classify_sensitive_message as _classify_sensitive_message,
|
|
145
60
|
)
|
|
146
|
-
from latticeai.core.audit import
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
from latticeai.core.embedding_providers import (
|
|
150
|
-
resolve_embedder,
|
|
151
|
-
resolve_embedding_profile,
|
|
152
|
-
)
|
|
153
|
-
from latticeai.core.enterprise import (
|
|
154
|
-
capability_registry,
|
|
155
|
-
)
|
|
156
|
-
from latticeai.core.mcp_registry import (
|
|
157
|
-
SKILLS_DIR,
|
|
158
|
-
_fetch_skills_marketplace,
|
|
159
|
-
create_mcp_install_state,
|
|
160
|
-
install_skill,
|
|
161
|
-
)
|
|
61
|
+
from latticeai.core.audit import get_audit_log as _get_audit_log
|
|
62
|
+
from latticeai.core.mcp_registry import _fetch_skills_marketplace
|
|
162
63
|
from latticeai.core.model_compat import (
|
|
163
64
|
list_cached_profiles as _list_compat_profiles,
|
|
164
65
|
)
|
|
165
|
-
from latticeai.core.policy import policy_matrix
|
|
166
|
-
from latticeai.core.product_hardening import build_product_hardening_status
|
|
167
|
-
from latticeai.core.security import (
|
|
168
|
-
bytes_match_extension as _bytes_match_extension_impl,
|
|
169
|
-
)
|
|
170
66
|
from latticeai.core.security import (
|
|
171
67
|
check_ip_rate_limit as _check_ip_rate_limit,
|
|
172
68
|
)
|
|
173
|
-
from latticeai.core.security import
|
|
174
|
-
client_ip as _client_ip_impl,
|
|
175
|
-
)
|
|
176
|
-
from latticeai.core.security import (
|
|
177
|
-
enforce_rate_limit as _enforce_rate_limit,
|
|
178
|
-
)
|
|
179
|
-
from latticeai.core.security import (
|
|
180
|
-
hash_password,
|
|
181
|
-
verify_password,
|
|
182
|
-
)
|
|
183
|
-
from latticeai.core.security import (
|
|
184
|
-
host_is_loopback as _host_is_loopback_impl,
|
|
185
|
-
)
|
|
69
|
+
from latticeai.core.security import hash_password, verify_password
|
|
186
70
|
from latticeai.core.security import (
|
|
187
71
|
redact_secret_text as _redact_secret_text,
|
|
188
72
|
)
|
|
189
|
-
from latticeai.core.tool_registry import
|
|
190
|
-
|
|
191
|
-
)
|
|
192
|
-
from latticeai.core.users import (
|
|
193
|
-
ensure_user_identity,
|
|
194
|
-
load_users_file,
|
|
195
|
-
migrate_knowledge_graph_identity,
|
|
196
|
-
save_users_file,
|
|
197
|
-
)
|
|
198
|
-
from latticeai.core.users import (
|
|
199
|
-
user_id_for_email as _user_id_for_email,
|
|
200
|
-
)
|
|
201
|
-
from latticeai.core.workspace_os import (
|
|
202
|
-
WORKSPACE_OS_VERSION,
|
|
203
|
-
remove_skill_directory,
|
|
204
|
-
)
|
|
205
|
-
from latticeai.models.router import LLMRouter, normalize_branding
|
|
206
|
-
from latticeai.services.app_context import AppContext
|
|
207
|
-
from latticeai.services.change_proposals import ChangeProposalService
|
|
208
|
-
from latticeai.services.chat_service import ChatService
|
|
209
|
-
from latticeai.services.command_center import CommandCenterService
|
|
73
|
+
from latticeai.core.tool_registry import TOOL_CATALOG_BRIEF as _TOOL_CATALOG_BRIEF
|
|
74
|
+
from latticeai.core.users import user_id_for_email as _user_id_for_email
|
|
210
75
|
from latticeai.services.model_runtime import (
|
|
211
|
-
CLOUD_VERIFY_TTL_SECONDS,
|
|
212
76
|
ENGINE_MODEL_CATALOG,
|
|
213
|
-
LOCAL_SERVER_PROCESSES,
|
|
214
|
-
MODEL_ENGINE_ALIASES,
|
|
215
|
-
build_model_runtime,
|
|
216
|
-
download_hf_model,
|
|
217
|
-
ensure_ollama_server,
|
|
218
77
|
filter_lower_family_versions,
|
|
219
|
-
local_binary,
|
|
220
78
|
normalize_local_model_request,
|
|
221
|
-
sse_event,
|
|
222
|
-
)
|
|
223
|
-
from latticeai.services.p_reinforce import PReinforceGardener
|
|
224
|
-
|
|
225
|
-
# The aliased names below form the explicit, allowlisted compatibility
|
|
226
|
-
# surface consumed by historical ``server_app`` callers.
|
|
227
|
-
from latticeai.services.tool_dispatch import ( # noqa: F401
|
|
228
|
-
LOCAL_WRITE_BLOCKED_PREFIXES as _LOCAL_WRITE_BLOCKED_PREFIXES,
|
|
229
79
|
)
|
|
230
80
|
from latticeai.services.tool_dispatch import (
|
|
231
|
-
|
|
232
|
-
build_agent_runtime,
|
|
233
|
-
configure_tool_dispatch,
|
|
234
|
-
get_tool_permission,
|
|
81
|
+
LOCAL_WRITE_BLOCKED_PREFIXES as _LOCAL_WRITE_BLOCKED_PREFIXES,
|
|
235
82
|
)
|
|
83
|
+
from latticeai.services.tool_dispatch import TOOL_GOVERNANCE
|
|
236
84
|
from latticeai.services.tool_dispatch import (
|
|
237
85
|
TOOL_GOVERNANCE_DEFAULT as _TOOL_GOVERNANCE_DEFAULT,
|
|
238
86
|
)
|
|
239
|
-
from latticeai.services.tool_dispatch import
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
INVITE_COOKIE_SECRET = _security_runtime["INVITE_COOKIE_SECRET"]
|
|
306
|
-
INVITE_GATE_ENABLED = _security_runtime["INVITE_GATE_ENABLED"]
|
|
307
|
-
SECURE_COOKIES = _security_runtime["SECURE_COOKIES"]
|
|
308
|
-
|
|
309
|
-
# SSO config + discovery seam is built below, after SSO_FILE is known
|
|
310
|
-
# (latticeai.runtime.sso_config_runtime).
|
|
311
|
-
|
|
312
|
-
# ── Password hashing — used directly from latticeai.core.security ──────────────
|
|
313
|
-
# (hash_password / verify_password are imported above; no local wrapper needed)
|
|
314
|
-
def verify_and_migrate_password(email: str, plain: str, stored: str, users: Dict) -> bool:
|
|
315
|
-
"""평문 비밀번호를 투명하게 해시로 마이그레이션. 마이그레이션 발생 시 audit log 남김."""
|
|
316
|
-
if ":" in stored and len(stored) > 64:
|
|
317
|
-
return verify_password(plain, stored)
|
|
318
|
-
if plain == stored:
|
|
319
|
-
users[email]["password"] = hash_password(plain)
|
|
320
|
-
save_users(users)
|
|
321
|
-
try:
|
|
322
|
-
append_audit_event("password_migrated_from_plaintext", user_email=email)
|
|
323
|
-
except Exception as e:
|
|
324
|
-
logging.warning("audit log failed on password migration: %s", e)
|
|
325
|
-
logging.info("Migrated plaintext password to scrypt hash for %s", email)
|
|
326
|
-
return True
|
|
327
|
-
return False
|
|
328
|
-
|
|
329
|
-
# ── Session store — delegated to latticeai.runtime.bootstrap ──────────────────
|
|
330
|
-
def _check_rate_limit(ip: str, action: str, max_calls: int, window_secs: float) -> None:
|
|
331
|
-
_check_ip_rate_limit(ip, action, max_calls=max_calls, window_secs=window_secs)
|
|
332
|
-
|
|
333
|
-
def _client_ip(request: Request) -> str:
|
|
334
|
-
return _client_ip_impl(request)
|
|
335
|
-
|
|
336
|
-
def user_id_for_email(email: Optional[str]) -> Optional[str]:
|
|
337
|
-
return _user_id_for_email(load_users(), email)
|
|
338
|
-
|
|
339
|
-
# Session token lifecycle (store + create/get/invalidate closures) lives in
|
|
340
|
-
# the bootstrap seam; user_id_for_email is injected as the subject resolver.
|
|
341
|
-
_session_runtime = build_session_runtime(user_id_resolver=user_id_for_email)
|
|
342
|
-
_SESSION_TTL = _session_runtime["_SESSION_TTL"]
|
|
343
|
-
_session_store = _session_runtime["_session_store"]
|
|
344
|
-
create_session = _session_runtime["create_session"]
|
|
345
|
-
get_session_email = _session_runtime["get_session_email"]
|
|
346
|
-
invalidate_session = _session_runtime["invalidate_session"]
|
|
347
|
-
|
|
348
|
-
# ── User Management Logic ──────────────────────────────────────────────────
|
|
349
|
-
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
350
|
-
DATA_DIR = CONFIG.data_dir
|
|
351
|
-
DATA_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
352
|
-
try:
|
|
353
|
-
DATA_DIR.chmod(0o700)
|
|
354
|
-
except OSError:
|
|
355
|
-
quiet()
|
|
356
|
-
STATIC_DIR = CONFIG.static_dir
|
|
357
|
-
|
|
358
|
-
USERS_FILE = DATA_DIR / "users.json"
|
|
359
|
-
HISTORY_FILE = DATA_DIR / "chat_history.json"
|
|
360
|
-
VPC_FILE = DATA_DIR / "vpc_config.json"
|
|
361
|
-
AUDIT_FILE = DATA_DIR / "audit_log.json"
|
|
362
|
-
SSO_FILE = DATA_DIR / "sso_config.json"
|
|
363
|
-
|
|
364
|
-
# MCP state extracted to mcp_registry.create_mcp_install_state (server decomp)
|
|
365
|
-
_mcp_state = create_mcp_install_state(DATA_DIR)
|
|
366
|
-
load_mcp_installs = _mcp_state["load_mcp_installs"]
|
|
367
|
-
mcp_public_item = _mcp_state["mcp_public_item"]
|
|
368
|
-
recommend_mcps = _mcp_state["recommend_mcps"]
|
|
369
|
-
install_mcp = _mcp_state["install_mcp"]
|
|
370
|
-
|
|
371
|
-
# Resolve the configured embedding provider once at startup. Degrades to the
|
|
372
|
-
# offline hash fallback when the requested provider is unavailable, while
|
|
373
|
-
# recording the requested-vs-active provider for the Embeddings status surface.
|
|
374
|
-
try:
|
|
375
|
-
EMBEDDING_PROFILE = resolve_embedding_profile(CONFIG.embedding_profile)
|
|
376
|
-
except ValueError as exc:
|
|
377
|
-
logging.warning("Embedding profile ignored: %s", exc)
|
|
378
|
-
EMBEDDING_PROFILE = {}
|
|
379
|
-
_embedding_provider = CONFIG.embedding_provider
|
|
380
|
-
_embedding_model = CONFIG.embedding_model or str(EMBEDDING_PROFILE.get("model") or "")
|
|
381
|
-
_embedding_dim = CONFIG.embedding_dim or int(EMBEDDING_PROFILE.get("dimensions") or 0)
|
|
382
|
-
if CONFIG.embedding_profile and CONFIG.embedding_provider in {"", "hash", "local", "fallback"}:
|
|
383
|
-
_embedding_provider = str(EMBEDDING_PROFILE.get("provider") or CONFIG.embedding_provider)
|
|
384
|
-
|
|
385
|
-
EMBEDDER = resolve_embedder(
|
|
386
|
-
_embedding_provider,
|
|
387
|
-
model=_embedding_model,
|
|
388
|
-
base_url=CONFIG.embedding_base_url,
|
|
389
|
-
api_key=CONFIG.embedding_api_key,
|
|
390
|
-
dim=_embedding_dim,
|
|
391
|
-
timeout=CONFIG.embedding_timeout,
|
|
392
|
-
extra={"target": CONFIG.embedding_custom_target},
|
|
393
|
-
probe=_embedding_provider not in {"", "hash", "local", "fallback"},
|
|
394
|
-
)
|
|
395
|
-
set_embed_dim(int(getattr(EMBEDDER, "dim", None) or _embedding_dim or 384))
|
|
396
|
-
if EMBEDDER.fell_back:
|
|
397
|
-
logging.warning("Embedding provider %s unavailable: %s", EMBEDDER.requested, EMBEDDER.detail)
|
|
398
|
-
STORAGE_ENGINE = storage_from_env(os.environ, data_dir=DATA_DIR) if ENABLE_GRAPH else None
|
|
399
|
-
_brain_runtime = build_brain_runtime(
|
|
400
|
-
data_dir=DATA_DIR,
|
|
401
|
-
history_file=HISTORY_FILE,
|
|
402
|
-
enable_graph=ENABLE_GRAPH,
|
|
403
|
-
embedder=EMBEDDER,
|
|
404
|
-
storage_engine=STORAGE_ENGINE,
|
|
405
|
-
)
|
|
406
|
-
KNOWLEDGE_GRAPH = _brain_runtime["KNOWLEDGE_GRAPH"]
|
|
407
|
-
# ── v4 durable conversation store: unbounded episodic memory in the same
|
|
408
|
-
# SQLite file as the graph (kg_portability backup/restore covers it for
|
|
409
|
-
# free). Legacy chat_history.json is imported once, idempotently, and the
|
|
410
|
-
# file is left untouched on disk as the import source.
|
|
411
|
-
CONVERSATIONS = _brain_runtime["CONVERSATIONS"]
|
|
412
|
-
# Hooks registry is constructed here (ahead of the watcher) so folder-watch
|
|
413
|
-
# reindexes can fire the pre_index/post_index lifecycle hooks. The registry
|
|
414
|
-
# + watcher pair is assembled behind the hooks_runtime seam.
|
|
415
|
-
_hooks_runtime = build_hooks_runtime(
|
|
416
|
-
data_dir=DATA_DIR,
|
|
417
|
-
enable_graph=ENABLE_GRAPH,
|
|
418
|
-
knowledge_graph_getter=lambda: KNOWLEDGE_GRAPH,
|
|
419
|
-
)
|
|
420
|
-
HOOKS_REGISTRY = _hooks_runtime["HOOKS_REGISTRY"]
|
|
421
|
-
LOCAL_KG_WATCHER = _hooks_runtime["LOCAL_KG_WATCHER"]
|
|
422
|
-
# ── Persistence/service graph: workspace store, realtime feed, plugin/memory
|
|
423
|
-
# registries, ingestion pipeline, device identity, and portability services.
|
|
424
|
-
_persistence_runtime = build_persistence_runtime(
|
|
425
|
-
data_dir=DATA_DIR,
|
|
426
|
-
base_dir=BASE_DIR,
|
|
427
|
-
enable_graph=ENABLE_GRAPH,
|
|
428
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
429
|
-
hooks_registry=HOOKS_REGISTRY,
|
|
430
|
-
history_file=HISTORY_FILE,
|
|
431
|
-
conversations=CONVERSATIONS,
|
|
432
|
-
user_id_for_email=user_id_for_email,
|
|
433
|
-
audit=lambda action, detail, user: append_audit_event(action, user_email=user, **detail),
|
|
434
|
-
)
|
|
435
|
-
REALTIME_BUS = _persistence_runtime["REALTIME_BUS"]
|
|
436
|
-
WORKSPACE_OS = _persistence_runtime["WORKSPACE_OS"]
|
|
437
|
-
WORKSPACE_SERVICE = _persistence_runtime["WORKSPACE_SERVICE"]
|
|
438
|
-
INVITATION_STORE = _persistence_runtime["INVITATION_STORE"]
|
|
439
|
-
PLUGIN_REGISTRY = _persistence_runtime["PLUGIN_REGISTRY"]
|
|
440
|
-
TEMPLATE_CATALOG = _persistence_runtime["TEMPLATE_CATALOG"]
|
|
441
|
-
AGENT_REGISTRY = _persistence_runtime["AGENT_REGISTRY"]
|
|
442
|
-
MEMORY_SERVICE = _persistence_runtime["MEMORY_SERVICE"]
|
|
443
|
-
BRAIN_INTELLIGENCE = _persistence_runtime["BRAIN_INTELLIGENCE"]
|
|
444
|
-
AUTOMATION_INTELLIGENCE = _persistence_runtime["AUTOMATION_INTELLIGENCE"]
|
|
445
|
-
INGESTION_PIPELINE = _persistence_runtime["INGESTION_PIPELINE"]
|
|
446
|
-
DEVICE_IDENTITY = _persistence_runtime["DEVICE_IDENTITY"]
|
|
447
|
-
KG_PORTABILITY = _persistence_runtime["KG_PORTABILITY"]
|
|
448
|
-
FUNNEL_METRICS = _persistence_runtime["FUNNEL_METRICS"]
|
|
449
|
-
|
|
450
|
-
def _require_graph():
|
|
451
|
-
if not ENABLE_GRAPH or KNOWLEDGE_GRAPH is None:
|
|
452
|
-
raise HTTPException(status_code=404, detail="지식 그래프가 비활성화되어 있습니다. LATTICEAI_ENABLE_GRAPH=true 설정 후 다시 시도해 주세요.")
|
|
453
|
-
|
|
454
|
-
class UserRegister(BaseModel):
|
|
455
|
-
email: str
|
|
456
|
-
password: str
|
|
457
|
-
name: str
|
|
458
|
-
nickname: str
|
|
459
|
-
|
|
460
|
-
class UserLogin(BaseModel):
|
|
461
|
-
email: str
|
|
462
|
-
password: str
|
|
463
|
-
|
|
464
|
-
class AdminUserUpdate(BaseModel):
|
|
465
|
-
role: Optional[str] = None
|
|
466
|
-
disabled: Optional[bool] = None
|
|
467
|
-
|
|
468
|
-
class VpcConfigUpdate(BaseModel):
|
|
469
|
-
provider: Optional[str] = None
|
|
470
|
-
region: Optional[str] = None
|
|
471
|
-
cidr_block: Optional[str] = None
|
|
472
|
-
private_subnets: Optional[List[str]] = None
|
|
473
|
-
endpoint: Optional[str] = None
|
|
474
|
-
vpn_status: Optional[str] = None
|
|
475
|
-
peering_status: Optional[str] = None
|
|
476
|
-
notes: Optional[str] = None
|
|
477
|
-
|
|
478
|
-
class SsoConfigUpdate(BaseModel):
|
|
479
|
-
enabled: Optional[bool] = None
|
|
480
|
-
provider_name: Optional[str] = None
|
|
481
|
-
discovery_url: Optional[str] = None
|
|
482
|
-
client_id: Optional[str] = None
|
|
483
|
-
client_secret: Optional[str] = None
|
|
484
|
-
redirect_uri: Optional[str] = None
|
|
485
|
-
scopes: Optional[str] = None
|
|
486
|
-
|
|
487
|
-
# SSO config + OIDC discovery seam. One closure owns config load/save and the
|
|
488
|
-
# discovery cache, so save_sso_config now actually invalidates discovery.
|
|
489
|
-
_sso_runtime = build_sso_config_runtime(
|
|
490
|
-
sso_file=SSO_FILE,
|
|
491
|
-
discovery_url=SSO_DISCOVERY_URL,
|
|
492
|
-
client_id=SSO_CLIENT_ID,
|
|
493
|
-
client_secret=SSO_CLIENT_SECRET,
|
|
494
|
-
redirect_uri=SSO_REDIRECT_URI,
|
|
495
|
-
provider_name=SSO_PROVIDER_NAME,
|
|
496
|
-
logging=logging,
|
|
497
|
-
)
|
|
498
|
-
_sso_env_defaults = _sso_runtime["_sso_env_defaults"]
|
|
499
|
-
get_sso_settings = _sso_runtime["get_sso_settings"]
|
|
500
|
-
public_sso_config = _sso_runtime["public_sso_config"]
|
|
501
|
-
save_sso_config = _sso_runtime["save_sso_config"]
|
|
502
|
-
_get_sso_discovery = _sso_runtime["_get_sso_discovery"]
|
|
503
|
-
_sso_states = _sso_runtime["_sso_states"]
|
|
504
|
-
|
|
505
|
-
# MCP/skill request models moved to latticeai.api.mcp (v1.3.0).
|
|
506
|
-
# VPC network-profile config → latticeai.runtime.network_config_runtime.
|
|
507
|
-
_vpc_runtime = build_vpc_runtime(vpc_file=VPC_FILE, logging=logging)
|
|
508
|
-
load_vpc_config = _vpc_runtime["load_vpc_config"]
|
|
509
|
-
save_vpc_config = _vpc_runtime["save_vpc_config"]
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
def load_users():
|
|
513
|
-
users = load_users_file(USERS_FILE)
|
|
514
|
-
email_to_id = {
|
|
515
|
-
email: user.get("id")
|
|
516
|
-
for email, user in users.items()
|
|
517
|
-
if isinstance(user, dict) and user.get("id")
|
|
518
|
-
}
|
|
519
|
-
try:
|
|
520
|
-
migrate_knowledge_graph_identity(DATA_DIR / "knowledge_graph.sqlite", email_to_id)
|
|
521
|
-
except Exception as exc:
|
|
522
|
-
logging.warning("knowledge graph identity migration skipped: %s", exc)
|
|
523
|
-
try:
|
|
524
|
-
WORKSPACE_OS.migrate_workspace_identities(email_to_id)
|
|
525
|
-
except Exception as exc:
|
|
526
|
-
logging.warning("workspace identity migration skipped: %s", exc)
|
|
527
|
-
return users
|
|
528
|
-
|
|
529
|
-
def save_users(users):
|
|
530
|
-
save_users_file(USERS_FILE, users)
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
_history_lock = threading.Lock()
|
|
534
|
-
|
|
535
|
-
# audit build moved after redact_secret_text is defined (see below)
|
|
536
|
-
|
|
537
|
-
def save_to_history(
|
|
538
|
-
role: str,
|
|
539
|
-
message: str,
|
|
540
|
-
user_email: Optional[str] = None,
|
|
541
|
-
user_nickname: Optional[str] = None,
|
|
542
|
-
source: Optional[str] = None,
|
|
543
|
-
conversation_id: Optional[str] = None,
|
|
544
|
-
workspace_id: Optional[str] = None,
|
|
545
|
-
):
|
|
546
|
-
try:
|
|
547
|
-
message = redact_secret_text(message)
|
|
548
|
-
if role == "assistant":
|
|
549
|
-
message = normalize_branding(message)
|
|
550
|
-
item = {"role": role, "content": message, "timestamp": datetime.now().isoformat()}
|
|
551
|
-
if user_email:
|
|
552
|
-
item["user_email"] = user_email
|
|
553
|
-
if user_nickname:
|
|
554
|
-
item["user_nickname"] = user_nickname
|
|
555
|
-
if source:
|
|
556
|
-
item["source"] = source
|
|
557
|
-
if conversation_id:
|
|
558
|
-
item["conversation_id"] = conversation_id
|
|
559
|
-
if workspace_id:
|
|
560
|
-
item["workspace_id"] = workspace_id
|
|
561
|
-
sensitive = classify_sensitive_message(item, -1)
|
|
562
|
-
append_audit_event(
|
|
563
|
-
"chat_message",
|
|
564
|
-
role=role,
|
|
565
|
-
user_email=user_email,
|
|
566
|
-
user_nickname=user_nickname,
|
|
567
|
-
source=source,
|
|
568
|
-
conversation_id=conversation_id,
|
|
569
|
-
workspace_id=workspace_id,
|
|
570
|
-
content_preview=sensitive.get("preview"),
|
|
571
|
-
content_chars=len(message or ""),
|
|
572
|
-
sensitivity=sensitive.get("sensitivity"),
|
|
573
|
-
sensitive_labels=sensitive.get("labels") or [],
|
|
574
|
-
)
|
|
575
|
-
# v4: conversations are durable episodic memory — unbounded SQLite
|
|
576
|
-
# store (the 50-message chat_history.json cap is dead).
|
|
577
|
-
CONVERSATIONS.append(item)
|
|
578
|
-
try:
|
|
579
|
-
if ENABLE_GRAPH and KNOWLEDGE_GRAPH:
|
|
580
|
-
# v4: chat messages enter the brain through the unified
|
|
581
|
-
# ingestion pipeline (provenance + hook lifecycle), not by
|
|
582
|
-
# bypassing it with a direct store call.
|
|
583
|
-
INGESTION_PIPELINE.ingest(
|
|
584
|
-
IngestionItem(
|
|
585
|
-
source_type="chat_message",
|
|
586
|
-
text=message,
|
|
587
|
-
owner=user_email,
|
|
588
|
-
workspace_id=workspace_id,
|
|
589
|
-
conversation_id=conversation_id,
|
|
590
|
-
metadata={
|
|
591
|
-
"role": role,
|
|
592
|
-
"user_nickname": user_nickname,
|
|
593
|
-
"source": source,
|
|
594
|
-
"raw": item,
|
|
595
|
-
},
|
|
596
|
-
),
|
|
597
|
-
user_email=user_email,
|
|
598
|
-
)
|
|
599
|
-
except Exception as graph_error:
|
|
600
|
-
logging.warning("knowledge graph message ingest failed: %s", graph_error)
|
|
601
|
-
except Exception as e:
|
|
602
|
-
logging.warning("save_to_history failed: %s", e)
|
|
603
|
-
|
|
604
|
-
def redact_secret_text(text: str) -> str:
|
|
605
|
-
return _redact_secret_text(text)
|
|
606
|
-
|
|
607
|
-
# Build audit now that redact is available
|
|
608
|
-
_audit_rt = build_audit_runtime(
|
|
609
|
-
audit_file=AUDIT_FILE,
|
|
610
|
-
logging=logging,
|
|
611
|
-
redact_fn=redact_secret_text,
|
|
612
|
-
)
|
|
613
|
-
get_audit_log = _audit_rt["get_audit_log"]
|
|
614
|
-
append_audit_event = _audit_rt["append_audit_event"]
|
|
615
|
-
|
|
616
|
-
# History query/clear seam (scope resolution, get_history, grouping, clears)
|
|
617
|
-
# → latticeai.runtime.history_runtime. save_to_history (the write path) stays
|
|
618
|
-
# inline above because it is bound to redaction/audit/ingestion.
|
|
619
|
-
_history_query_runtime = build_history_query_runtime(
|
|
620
|
-
conversations=CONVERSATIONS,
|
|
621
|
-
workspace_service=WORKSPACE_SERVICE,
|
|
622
|
-
require_auth=REQUIRE_AUTH,
|
|
623
|
-
logging=logging,
|
|
624
|
-
)
|
|
625
|
-
_history_allowed_workspaces_for = _history_query_runtime["_history_allowed_workspaces_for"]
|
|
626
|
-
_history_include_legacy_global = _history_query_runtime["_history_include_legacy_global"]
|
|
627
|
-
get_history = _history_query_runtime["get_history"]
|
|
628
|
-
conversation_title = _history_query_runtime["conversation_title"]
|
|
629
|
-
group_history_conversations = _history_query_runtime["group_history_conversations"]
|
|
630
|
-
get_conversation_messages = _history_query_runtime["get_conversation_messages"]
|
|
631
|
-
clear_history = _history_query_runtime["clear_history"]
|
|
632
|
-
clear_conversation = _history_query_runtime["clear_conversation"]
|
|
633
|
-
|
|
634
|
-
_access_runtime = build_access_runtime(
|
|
635
|
-
config=CONFIG,
|
|
636
|
-
require_auth=REQUIRE_AUTH,
|
|
637
|
-
http_exception=HTTPException,
|
|
638
|
-
request_type=Request,
|
|
639
|
-
load_users=load_users,
|
|
640
|
-
get_session_email=get_session_email,
|
|
641
|
-
user_id_for_email=_user_id_for_email,
|
|
642
|
-
)
|
|
643
|
-
get_user_role = _access_runtime["get_user_role"]
|
|
644
|
-
_extract_bearer_token = _access_runtime["_extract_bearer_token"]
|
|
645
|
-
get_current_user = _access_runtime["get_current_user"]
|
|
646
|
-
require_user = _access_runtime["require_user"]
|
|
647
|
-
require_admin = _access_runtime["require_admin"]
|
|
648
|
-
public_user = _access_runtime["public_user"]
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
# ── Rate limiting & file validation — delegated to latticeai.core.security ────
|
|
652
|
-
_RATE_LIMIT_ENABLED = CONFIG.rate_limit_enabled
|
|
653
|
-
|
|
654
|
-
def enforce_rate_limit(email: str, bucket_key: str) -> None:
|
|
655
|
-
_enforce_rate_limit(email, bucket_key, enabled=_RATE_LIMIT_ENABLED)
|
|
656
|
-
|
|
657
|
-
def _bytes_match_extension(data: bytes, ext: str) -> bool:
|
|
658
|
-
return _bytes_match_extension_impl(data, ext)
|
|
659
|
-
|
|
660
|
-
# Local file-access approval lives entirely in
|
|
661
|
-
# ``latticeai.api.permissions.PermissionGateway`` (token hash-at-rest,
|
|
662
|
-
# persisted approval queue, Discord notifications). The historical inline
|
|
663
|
-
# copy that used to sit here was app-dead — no route referenced it — so it
|
|
664
|
-
# was removed to keep this factory a single wiring path.
|
|
665
|
-
|
|
666
|
-
_user_key_runtime = build_user_key_runtime(
|
|
667
|
-
load_users=load_users,
|
|
668
|
-
save_users=save_users,
|
|
669
|
-
ensure_user_identity=ensure_user_identity,
|
|
670
|
-
keyring=keyring,
|
|
671
|
-
allow_plaintext_api_keys=ALLOW_PLAINTEXT_API_KEYS,
|
|
672
|
-
logging=logging,
|
|
673
|
-
http_exception=HTTPException,
|
|
674
|
-
)
|
|
675
|
-
get_history_user = _user_key_runtime["get_history_user"]
|
|
676
|
-
get_user_api_key = _user_key_runtime["get_user_api_key"]
|
|
677
|
-
set_user_api_key = _user_key_runtime["set_user_api_key"]
|
|
678
|
-
|
|
679
|
-
# Chat service owns persistence and trace behavior after its user-key
|
|
680
|
-
# dependencies are available.
|
|
681
|
-
CHAT_SERVICE = ChatService(
|
|
682
|
-
store=WORKSPACE_OS,
|
|
683
|
-
get_history=get_history,
|
|
684
|
-
save_to_history=save_to_history,
|
|
685
|
-
get_history_user=get_history_user,
|
|
686
|
-
)
|
|
687
|
-
|
|
688
|
-
# ── Sensitivity analysis — delegated to latticeai.core.audit ──────────────────
|
|
689
|
-
def classify_sensitive_message(item: Dict, index: int) -> Dict:
|
|
690
|
-
return _classify_sensitive_message(item, index)
|
|
691
|
-
|
|
692
|
-
def build_sensitivity_report(history: List[Dict]) -> Dict:
|
|
693
|
-
return _build_sensitivity_report(history)
|
|
694
|
-
|
|
695
|
-
# ── Admin audit report — delegated to latticeai.core.audit ───────────────────
|
|
696
|
-
def build_admin_audit_report(users: Dict, audit_events: Optional[List[Dict]] = None) -> Dict:
|
|
697
|
-
graph_stats = None
|
|
698
|
-
try:
|
|
699
|
-
if ENABLE_GRAPH and KNOWLEDGE_GRAPH:
|
|
700
|
-
graph_stats = KNOWLEDGE_GRAPH.stats()
|
|
701
|
-
except Exception:
|
|
702
|
-
quiet()
|
|
703
|
-
return _build_admin_audit_report(
|
|
704
|
-
AUDIT_FILE, users,
|
|
705
|
-
get_user_role=get_user_role,
|
|
706
|
-
graph_stats=graph_stats,
|
|
707
|
-
audit_events=audit_events,
|
|
708
|
-
)
|
|
709
|
-
|
|
710
|
-
router = LLMRouter()
|
|
711
|
-
set_llm_router(router)
|
|
712
|
-
configure_tool_dispatch(load_users=load_users, get_user_role=get_user_role)
|
|
713
|
-
# v4 garden absorption: the vault is the user-owned markdown mirror; the
|
|
714
|
-
# brain is authoritative. Existing notes import idempotently at startup
|
|
715
|
-
# (content-hash dedup — re-runs are no-ops), and garden context queries
|
|
716
|
-
# the brain instead of rescanning the vault per chat message.
|
|
717
|
-
gardener = PReinforceGardener(
|
|
718
|
-
ingestion_pipeline=INGESTION_PIPELINE if ENABLE_GRAPH else None,
|
|
719
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
720
|
-
)
|
|
721
|
-
if ENABLE_GRAPH:
|
|
722
|
-
try:
|
|
723
|
-
_garden_import = gardener.import_vault()
|
|
724
|
-
if _garden_import.get("failed"):
|
|
725
|
-
logging.warning("garden vault import: %s notes failed to ingest", _garden_import["failed"])
|
|
726
|
-
except Exception as exc:
|
|
727
|
-
logging.warning("garden vault import skipped: %s", exc)
|
|
728
|
-
|
|
729
|
-
_lifespan_runtime = build_lifespan_runtime(
|
|
730
|
-
app_mode=APP_MODE,
|
|
731
|
-
enable_telegram=ENABLE_TELEGRAM,
|
|
732
|
-
autoload_models=AUTOLOAD_MODELS,
|
|
733
|
-
is_public_mode=IS_PUBLIC_MODE,
|
|
734
|
-
public_model=PUBLIC_MODEL,
|
|
735
|
-
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
736
|
-
local_model=LOCAL_MODEL,
|
|
737
|
-
local_draft_model=LOCAL_DRAFT_MODEL,
|
|
738
|
-
model_idle_unload_seconds=MODEL_IDLE_UNLOAD_SECONDS,
|
|
739
|
-
model_router=router,
|
|
740
|
-
local_kg_watcher=LOCAL_KG_WATCHER,
|
|
741
|
-
local_server_processes=LOCAL_SERVER_PROCESSES,
|
|
742
|
-
logger=logging,
|
|
743
|
-
)
|
|
744
|
-
_spawn = _lifespan_runtime["_spawn"]
|
|
745
|
-
lifespan = _lifespan_runtime["lifespan"]
|
|
746
|
-
|
|
747
|
-
_web_runtime = build_web_runtime(
|
|
748
|
-
app_mode=APP_MODE,
|
|
749
|
-
app_version=APP_VERSION,
|
|
750
|
-
lifespan=lifespan,
|
|
751
|
-
default_host=DEFAULT_HOST,
|
|
752
|
-
default_port=DEFAULT_PORT,
|
|
753
|
-
cors_extra_origins=CORS_EXTRA_ORIGINS,
|
|
754
|
-
cors_allow_network=CORS_ALLOW_NETWORK,
|
|
755
|
-
static_dir=STATIC_DIR,
|
|
756
|
-
)
|
|
757
|
-
app = _web_runtime["app"]
|
|
758
|
-
ensure_agent_root()
|
|
759
|
-
|
|
760
|
-
OPEN_REGISTRATION = CONFIG.open_registration
|
|
761
|
-
_model_runtime_service = configure_model_runtime_from_context(
|
|
762
|
-
build_model_runtime=build_model_runtime,
|
|
763
|
-
router=router,
|
|
764
|
-
APP_MODE=APP_MODE,
|
|
765
|
-
DEFAULT_HOST=DEFAULT_HOST,
|
|
766
|
-
DEFAULT_PORT=DEFAULT_PORT,
|
|
767
|
-
DATA_DIR=DATA_DIR,
|
|
768
|
-
BASE_DIR=BASE_DIR,
|
|
769
|
-
ENABLE_TELEGRAM=ENABLE_TELEGRAM,
|
|
770
|
-
ENABLE_GRAPH=ENABLE_GRAPH,
|
|
771
|
-
AUTOLOAD_MODELS=AUTOLOAD_MODELS,
|
|
772
|
-
MODEL_IDLE_UNLOAD_SECONDS=MODEL_IDLE_UNLOAD_SECONDS,
|
|
773
|
-
ALLOW_MODEL_DOWNLOADS=ALLOW_MODEL_DOWNLOADS,
|
|
774
|
-
MODEL_DOWNLOAD_TIMEOUT=MODEL_DOWNLOAD_TIMEOUT,
|
|
775
|
-
ALLOW_LOCAL_MODELS=ALLOW_LOCAL_MODELS,
|
|
776
|
-
REQUIRE_AUTH=REQUIRE_AUTH,
|
|
777
|
-
INVITE_GATE_ENABLED=INVITE_GATE_ENABLED,
|
|
778
|
-
ALLOW_PLAINTEXT_API_KEYS=ALLOW_PLAINTEXT_API_KEYS,
|
|
779
|
-
CORS_ALLOW_NETWORK=CORS_ALLOW_NETWORK,
|
|
780
|
-
PUBLIC_MODEL=PUBLIC_MODEL,
|
|
781
|
-
LOCAL_MODEL=LOCAL_MODEL,
|
|
782
|
-
IS_PUBLIC_MODE=IS_PUBLIC_MODE,
|
|
783
|
-
keyring=keyring,
|
|
784
|
-
get_current_user=get_current_user,
|
|
785
|
-
get_user_api_key=get_user_api_key,
|
|
786
|
-
)
|
|
787
|
-
_static_routes_bundle = build_static_routes_bundle(
|
|
788
|
-
create_static_routes_router=create_static_routes_router,
|
|
789
|
-
static_dir=STATIC_DIR,
|
|
790
|
-
invite_gate_enabled=INVITE_GATE_ENABLED,
|
|
791
|
-
invite_code=INVITE_CODE,
|
|
792
|
-
invite_cookie_secret=INVITE_COOKIE_SECRET,
|
|
793
|
-
secure_cookies=SECURE_COOKIES,
|
|
794
|
-
app_mode=APP_MODE,
|
|
795
|
-
model_router=router,
|
|
796
|
-
require_user=require_user,
|
|
797
|
-
)
|
|
798
|
-
STATIC_ROUTES = _static_routes_bundle["STATIC_ROUTES"]
|
|
799
|
-
ui_file_response = _static_routes_bundle["ui_file_response"]
|
|
800
|
-
local_sysinfo = _static_routes_bundle["local_sysinfo"]
|
|
801
|
-
invite_authorized = _static_routes_bundle["invite_authorized"]
|
|
802
|
-
|
|
803
|
-
# ── Auth & Admin routers (latticeai.api) ─────────────────────────────────────
|
|
804
|
-
_foundation_router_bundle = build_auth_admin_security_router_bundle(
|
|
805
|
-
create_auth_router=create_auth_router,
|
|
806
|
-
load_users=load_users,
|
|
807
|
-
save_users=save_users,
|
|
808
|
-
hash_password=hash_password,
|
|
809
|
-
verify_and_migrate_password=verify_and_migrate_password,
|
|
810
|
-
create_session=create_session,
|
|
811
|
-
get_session_email=get_session_email,
|
|
812
|
-
invalidate_session=invalidate_session,
|
|
813
|
-
extract_bearer_token=_extract_bearer_token,
|
|
814
|
-
get_user_role=get_user_role,
|
|
815
|
-
require_user=require_user,
|
|
816
|
-
check_ip_rate_limit=_check_rate_limit,
|
|
817
|
-
client_ip=_client_ip,
|
|
818
|
-
get_sso_settings=get_sso_settings,
|
|
819
|
-
get_sso_discovery=_get_sso_discovery,
|
|
820
|
-
public_sso_config=public_sso_config,
|
|
821
|
-
open_registration=OPEN_REGISTRATION,
|
|
822
|
-
session_ttl=_SESSION_TTL,
|
|
823
|
-
require_auth=REQUIRE_AUTH,
|
|
824
|
-
secure_cookies=SECURE_COOKIES,
|
|
825
|
-
invite_authorized=invite_authorized,
|
|
826
|
-
ensure_identity=ensure_user_identity,
|
|
827
|
-
create_admin_router=create_admin_router,
|
|
828
|
-
require_admin=require_admin,
|
|
829
|
-
get_history=get_history,
|
|
830
|
-
get_audit_log=get_audit_log,
|
|
831
|
-
audit_file=AUDIT_FILE,
|
|
832
|
-
public_user=public_user,
|
|
833
|
-
load_vpc_config=load_vpc_config,
|
|
834
|
-
save_vpc_config=save_vpc_config,
|
|
835
|
-
build_admin_audit_report=build_admin_audit_report,
|
|
836
|
-
build_sensitivity_report=build_sensitivity_report,
|
|
837
|
-
append_audit_event=append_audit_event,
|
|
838
|
-
save_sso_config=save_sso_config,
|
|
839
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
840
|
-
enable_graph=ENABLE_GRAPH,
|
|
841
|
-
logger=logging,
|
|
842
|
-
invite_code=INVITE_CODE,
|
|
843
|
-
invite_gate_enabled=INVITE_GATE_ENABLED,
|
|
844
|
-
default_port=DEFAULT_PORT,
|
|
845
|
-
policy_matrix=policy_matrix,
|
|
846
|
-
build_product_hardening_status=build_product_hardening_status,
|
|
847
|
-
config=CONFIG,
|
|
848
|
-
kg_portability=KG_PORTABILITY,
|
|
849
|
-
device_identity=DEVICE_IDENTITY,
|
|
850
|
-
create_invitations_router=create_invitations_router,
|
|
851
|
-
invitation_store=INVITATION_STORE,
|
|
852
|
-
workspace_service=WORKSPACE_SERVICE,
|
|
853
|
-
user_id_for_email=user_id_for_email,
|
|
854
|
-
create_security_router=_create_security_router,
|
|
855
|
-
classify_sensitive_message=classify_sensitive_message,
|
|
856
|
-
)
|
|
857
|
-
auth_router = _foundation_router_bundle["auth_router"]
|
|
858
|
-
admin_router = _foundation_router_bundle["admin_router"]
|
|
859
|
-
invitations_router = _foundation_router_bundle["invitations_router"]
|
|
860
|
-
security_router = _foundation_router_bundle["security_router"]
|
|
861
|
-
_graph_stats_safe = _foundation_router_bundle["_graph_stats_safe"]
|
|
862
|
-
_product_hardening_status = _foundation_router_bundle["_product_hardening_status"]
|
|
863
|
-
_security_audit_events_safe = _foundation_router_bundle["_security_audit_events_safe"]
|
|
864
|
-
_security_list_uploaded_files = _foundation_router_bundle["_security_list_uploaded_files"]
|
|
865
|
-
|
|
866
|
-
# ── Static UI/status routes moved to latticeai.api.static_routes ──
|
|
867
|
-
|
|
868
|
-
# ── Request / Response Models ──────────────────────────────────────────────────
|
|
869
|
-
|
|
870
|
-
# ── Workspace OS API ──────────────────────────────────────────────────────────
|
|
871
|
-
|
|
872
|
-
def _workspace_settings_payload() -> Dict:
|
|
873
|
-
return {
|
|
874
|
-
"mode": APP_MODE,
|
|
875
|
-
"host": DEFAULT_HOST,
|
|
876
|
-
"port": DEFAULT_PORT,
|
|
877
|
-
"require_auth": REQUIRE_AUTH,
|
|
878
|
-
"enable_graph": ENABLE_GRAPH,
|
|
879
|
-
"allow_local_models": ALLOW_LOCAL_MODELS,
|
|
880
|
-
"static_dir": str(STATIC_DIR),
|
|
881
|
-
"data_dir": str(DATA_DIR),
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
def _workspace_models_payload() -> Dict:
|
|
886
|
-
return {
|
|
887
|
-
"current_model": router.current_model_id,
|
|
888
|
-
"loaded_models": router.loaded_model_ids,
|
|
889
|
-
"public_model": PUBLIC_MODEL,
|
|
890
|
-
"local_model": LOCAL_MODEL,
|
|
891
|
-
"local_draft_model": LOCAL_DRAFT_MODEL,
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
def _workspace_graph():
|
|
896
|
-
return KNOWLEDGE_GRAPH if (ENABLE_GRAPH and KNOWLEDGE_GRAPH) else None
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
_context_runtime = build_context_runtime(
|
|
900
|
-
graph_store=_workspace_graph(),
|
|
901
|
-
ingestion_pipeline=INGESTION_PIPELINE,
|
|
902
|
-
memory_service=MEMORY_SERVICE,
|
|
903
|
-
gardener=gardener,
|
|
904
|
-
require_auth=REQUIRE_AUTH,
|
|
905
|
-
allowed_scopes_for_user=lambda user_email: PLATFORM.allowed_scopes(user_email),
|
|
906
|
-
)
|
|
907
|
-
SEARCH_SERVICE = _context_runtime["SEARCH_SERVICE"]
|
|
908
|
-
BRAIN_MEMORY = _context_runtime["BRAIN_MEMORY"]
|
|
909
|
-
CONTEXT_ASSEMBLER = _context_runtime["CONTEXT_ASSEMBLER"]
|
|
910
|
-
ARTIFACT_LEDGER = _context_runtime["ARTIFACT_LEDGER"]
|
|
911
|
-
_scoped_hybrid_search = _context_runtime["_scoped_hybrid_search"]
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
# ── Telegram chat mirror: registered only when ENABLE_TELEGRAM is truthy.
|
|
915
|
-
# latticeai.api.chat no longer imports telegram_bot (a 45KB module that
|
|
916
|
-
# mutates os.environ at import); it calls this injected callback instead.
|
|
917
|
-
on_chat_message = maybe_build_telegram_chat_mirror(
|
|
918
|
-
enable_telegram=ENABLE_TELEGRAM,
|
|
919
|
-
spawn=_spawn,
|
|
920
|
-
)
|
|
921
|
-
|
|
922
|
-
def _recent_chat_context(
|
|
923
|
-
limit: int = 10,
|
|
924
|
-
include_image_missing_replies: bool = True,
|
|
925
|
-
user_email: Optional[str] = None,
|
|
926
|
-
conversation_id: Optional[str] = None,
|
|
927
|
-
workspace_id: Optional[str] = None,
|
|
928
|
-
) -> str:
|
|
929
|
-
return build_recent_chat_context(
|
|
930
|
-
get_history=get_history,
|
|
931
|
-
limit=limit,
|
|
932
|
-
include_image_missing_replies=include_image_missing_replies,
|
|
933
|
-
user_email=user_email,
|
|
934
|
-
conversation_id=conversation_id,
|
|
935
|
-
workspace_id=workspace_id,
|
|
936
|
-
)
|
|
937
|
-
|
|
938
|
-
CHAT_AGENT_RUNTIME = build_chat_agent_runtime_from_context(
|
|
939
|
-
build_agent_runtime=build_agent_runtime,
|
|
940
|
-
model_router=router,
|
|
941
|
-
execute_tool=execute_tool,
|
|
942
|
-
recent_chat_context=_recent_chat_context,
|
|
943
|
-
clear_history=clear_history,
|
|
944
|
-
knowledge_save=knowledge_save,
|
|
945
|
-
audit=append_audit_event,
|
|
946
|
-
hooks=HOOKS_REGISTRY,
|
|
947
|
-
brain_memory=BRAIN_MEMORY,
|
|
948
|
-
)
|
|
949
|
-
|
|
950
|
-
# ── Typed dependency context (latticeai.services.app_context) ────────────────
|
|
951
|
-
# One context object replaces the historical 25-30-kwarg router wiring.
|
|
952
|
-
context = AppContext(
|
|
953
|
-
config=CONFIG,
|
|
954
|
-
data_dir=DATA_DIR,
|
|
955
|
-
static_dir=STATIC_DIR,
|
|
956
|
-
base_dir=BASE_DIR,
|
|
957
|
-
skills_dir=SKILLS_DIR,
|
|
958
|
-
model_router=router,
|
|
959
|
-
workspace_store=WORKSPACE_OS,
|
|
960
|
-
workspace_service=WORKSPACE_SERVICE,
|
|
961
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
962
|
-
local_kg_watcher=LOCAL_KG_WATCHER,
|
|
963
|
-
chat_service=CHAT_SERVICE,
|
|
964
|
-
context_assembler=CONTEXT_ASSEMBLER,
|
|
965
|
-
artifact_ledger=ARTIFACT_LEDGER,
|
|
966
|
-
brain_memory=BRAIN_MEMORY,
|
|
967
|
-
ingestion_pipeline=INGESTION_PIPELINE if ENABLE_GRAPH else None,
|
|
968
|
-
chat_agent_runtime=CHAT_AGENT_RUNTIME,
|
|
969
|
-
gardener=gardener,
|
|
970
|
-
hooks=HOOKS_REGISTRY,
|
|
971
|
-
realtime_bus=REALTIME_BUS,
|
|
972
|
-
capability_registry=capability_registry,
|
|
973
|
-
require_user=require_user,
|
|
974
|
-
require_admin=require_admin,
|
|
975
|
-
get_current_user=get_current_user,
|
|
976
|
-
load_users=load_users,
|
|
977
|
-
get_user_role=get_user_role,
|
|
978
|
-
enforce_rate_limit=enforce_rate_limit,
|
|
979
|
-
allowed_workspaces_for=_history_allowed_workspaces_for,
|
|
980
|
-
append_audit_event=append_audit_event,
|
|
981
|
-
get_audit_log=get_audit_log,
|
|
982
|
-
get_history=get_history,
|
|
983
|
-
get_history_user=get_history_user,
|
|
984
|
-
save_to_history=save_to_history,
|
|
985
|
-
clear_history=clear_history,
|
|
986
|
-
clear_conversation=clear_conversation,
|
|
987
|
-
group_history_conversations=group_history_conversations,
|
|
988
|
-
get_conversation_messages=get_conversation_messages,
|
|
989
|
-
conversation_title=conversation_title,
|
|
990
|
-
enable_graph=ENABLE_GRAPH,
|
|
991
|
-
require_graph=_require_graph,
|
|
992
|
-
workspace_graph=_workspace_graph,
|
|
993
|
-
graph_stats=_graph_stats_safe,
|
|
994
|
-
workspace_models=_workspace_models_payload,
|
|
995
|
-
workspace_settings=_workspace_settings_payload,
|
|
996
|
-
scan_environment=scan_environment,
|
|
997
|
-
local_sysinfo=local_sysinfo,
|
|
998
|
-
get_recommendations=get_recommendations,
|
|
999
|
-
fetch_skills_marketplace=_fetch_skills_marketplace,
|
|
1000
|
-
install_skill=install_skill,
|
|
1001
|
-
remove_skill_directory=remove_skill_directory,
|
|
1002
|
-
redact_secret_text=redact_secret_text,
|
|
1003
|
-
ui_file_response=ui_file_response,
|
|
1004
|
-
public_model=PUBLIC_MODEL,
|
|
1005
|
-
local_model=LOCAL_MODEL or "",
|
|
1006
|
-
on_chat_message=on_chat_message,
|
|
1007
|
-
funnel_metrics=FUNNEL_METRICS,
|
|
1008
|
-
)
|
|
1009
|
-
app.state.context = context
|
|
1010
|
-
|
|
1011
|
-
register_foundation_routers(
|
|
1012
|
-
app,
|
|
1013
|
-
static_router=STATIC_ROUTES.router,
|
|
1014
|
-
auth_router=auth_router,
|
|
1015
|
-
admin_router=admin_router,
|
|
1016
|
-
invitations_router=invitations_router,
|
|
1017
|
-
security_router=security_router,
|
|
1018
|
-
create_workspace_router=create_workspace_router,
|
|
1019
|
-
context=context,
|
|
1020
|
-
)
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
# ── v2 Agentic Workspace Platform: cross-system wiring ───────────────────────
|
|
1024
|
-
_platform_automation_runtime = build_platform_automation_runtime(
|
|
1025
|
-
model_router=router,
|
|
1026
|
-
workspace_store=WORKSPACE_OS,
|
|
1027
|
-
workspace_service=WORKSPACE_SERVICE,
|
|
1028
|
-
plugin_registry=PLUGIN_REGISTRY,
|
|
1029
|
-
get_current_user=get_current_user,
|
|
1030
|
-
workspace_graph=_workspace_graph,
|
|
1031
|
-
workspace_scope_from_request=_workspace_scope_from_request,
|
|
1032
|
-
get_tool_permission=get_tool_permission,
|
|
1033
|
-
hooks=HOOKS_REGISTRY,
|
|
1034
|
-
agent_registry=AGENT_REGISTRY,
|
|
1035
|
-
data_dir=DATA_DIR,
|
|
1036
|
-
append_audit_event=append_audit_event,
|
|
1037
|
-
memory_service=MEMORY_SERVICE,
|
|
1038
|
-
tz_name=getattr(CONFIG, "timezone", None),
|
|
1039
|
-
)
|
|
1040
|
-
_llm_generate_sync = _platform_automation_runtime["_llm_generate_sync"]
|
|
1041
|
-
PLATFORM = _platform_automation_runtime["PLATFORM"]
|
|
1042
|
-
_automation_runtime = _platform_automation_runtime["_automation_runtime"]
|
|
1043
|
-
REVIEW_QUEUE = _platform_automation_runtime["REVIEW_QUEUE"]
|
|
1044
|
-
TRIGGER_SERVICE = _platform_automation_runtime["TRIGGER_SERVICE"]
|
|
1045
|
-
AGENT_RUNTIME = _platform_automation_runtime["AGENT_RUNTIME"]
|
|
1046
|
-
RUN_EXECUTOR = _platform_automation_runtime["RUN_EXECUTOR"]
|
|
1047
|
-
bind_trigger_hook_runner(registry=HOOKS_REGISTRY, trigger_service=TRIGGER_SERVICE)
|
|
1048
|
-
app.state.run_executor = RUN_EXECUTOR
|
|
1049
|
-
app.state.run_reconciliation = RUN_EXECUTOR.reconcile_startup()
|
|
1050
|
-
TRIGGER_SERVICE.start()
|
|
1051
|
-
|
|
1052
|
-
# ── Hooks dispatch: bind real built-in runners ───────────────────────────────
|
|
1053
|
-
bind_builtin_hook_runners(
|
|
1054
|
-
registry=HOOKS_REGISTRY,
|
|
1055
|
-
append_audit_event=append_audit_event,
|
|
1056
|
-
get_tool_permission=get_tool_permission,
|
|
1057
|
-
classify_sensitive_message=classify_sensitive_message,
|
|
1058
|
-
)
|
|
1059
|
-
|
|
1060
|
-
register_platform_feature_routers(
|
|
1061
|
-
app,
|
|
1062
|
-
create_plugins_router=create_plugins_router,
|
|
1063
|
-
plugin_registry=PLUGIN_REGISTRY,
|
|
1064
|
-
require_user=require_user,
|
|
1065
|
-
require_admin=require_admin,
|
|
1066
|
-
append_audit_event=append_audit_event,
|
|
1067
|
-
platform=PLATFORM,
|
|
1068
|
-
ui_file_response=ui_file_response,
|
|
1069
|
-
static_dir=STATIC_DIR,
|
|
1070
|
-
create_workflow_designer_router=create_workflow_designer_router,
|
|
1071
|
-
store=WORKSPACE_OS,
|
|
1072
|
-
get_current_user=get_current_user,
|
|
1073
|
-
workspace_graph=_workspace_graph,
|
|
1074
|
-
hooks=HOOKS_REGISTRY,
|
|
1075
|
-
run_executor=RUN_EXECUTOR,
|
|
1076
|
-
trigger_service=TRIGGER_SERVICE,
|
|
1077
|
-
create_agents_router=create_agents_router,
|
|
1078
|
-
agent_runtime=AGENT_RUNTIME,
|
|
1079
|
-
create_marketplace_router=create_marketplace_router,
|
|
1080
|
-
template_catalog=TEMPLATE_CATALOG,
|
|
1081
|
-
create_realtime_router=create_realtime_router,
|
|
1082
|
-
realtime_bus=REALTIME_BUS,
|
|
1083
|
-
)
|
|
1084
|
-
app.include_router(
|
|
1085
|
-
create_automation_intelligence_router(
|
|
1086
|
-
service=AUTOMATION_INTELLIGENCE,
|
|
1087
|
-
store=WORKSPACE_OS,
|
|
1088
|
-
require_user=require_user,
|
|
1089
|
-
gate_read=PLATFORM.gate_read,
|
|
1090
|
-
gate_write=PLATFORM.gate_write,
|
|
1091
|
-
append_audit_event=append_audit_event,
|
|
1092
|
-
workspace_graph=_workspace_graph,
|
|
1093
|
-
run_executor=RUN_EXECUTOR,
|
|
1094
|
-
review_queue=REVIEW_QUEUE,
|
|
1095
|
-
)
|
|
1096
|
-
)
|
|
1097
|
-
# UX funnel metrics (backlog #16): admin-only runtime counters.
|
|
1098
|
-
from latticeai.api.funnel_metrics import create_funnel_metrics_router
|
|
1099
|
-
|
|
1100
|
-
app.include_router(
|
|
1101
|
-
create_funnel_metrics_router(
|
|
1102
|
-
service=FUNNEL_METRICS,
|
|
1103
|
-
require_admin=require_admin,
|
|
1104
|
-
)
|
|
1105
|
-
)
|
|
1106
|
-
COMMAND_CENTER = CommandCenterService(
|
|
1107
|
-
conversation_store=CONVERSATIONS,
|
|
1108
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
1109
|
-
store=WORKSPACE_OS,
|
|
1110
|
-
search_service=SEARCH_SERVICE,
|
|
1111
|
-
brain_intelligence=BRAIN_INTELLIGENCE,
|
|
1112
|
-
automation_intelligence=AUTOMATION_INTELLIGENCE,
|
|
1113
|
-
review_queue=REVIEW_QUEUE,
|
|
1114
|
-
enable_graph=ENABLE_GRAPH,
|
|
1115
|
-
)
|
|
1116
|
-
app.include_router(
|
|
1117
|
-
create_command_center_router(
|
|
1118
|
-
service=COMMAND_CENTER,
|
|
1119
|
-
require_user=require_user,
|
|
1120
|
-
gate_read=PLATFORM.gate_read,
|
|
1121
|
-
)
|
|
1122
|
-
)
|
|
1123
|
-
CHANGE_PROPOSALS = ChangeProposalService(
|
|
1124
|
-
review_queue=REVIEW_QUEUE,
|
|
1125
|
-
resolve_path=resolve_workspace_path,
|
|
1126
|
-
audit=append_audit_event,
|
|
1127
|
-
)
|
|
1128
|
-
# Proposal-first mutations: the agent loop consults the governor so
|
|
1129
|
-
# additive creates run with minimal friction while changes/deletions of
|
|
1130
|
-
# existing files are staged for review instead of applied.
|
|
1131
|
-
CHAT_AGENT_RUNTIME.deps.change_governor = CHANGE_PROPOSALS
|
|
1132
|
-
app.include_router(
|
|
1133
|
-
create_change_proposals_router(
|
|
1134
|
-
service=CHANGE_PROPOSALS,
|
|
1135
|
-
require_user=require_user,
|
|
1136
|
-
gate_read=PLATFORM.gate_read,
|
|
1137
|
-
gate_write=PLATFORM.gate_write,
|
|
1138
|
-
)
|
|
1139
|
-
)
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
# ── Health & Info ──────────────────────────────────────────────────────────────
|
|
1143
|
-
|
|
1144
|
-
# ── Model runtime/provider helpers moved to latticeai.services.model_runtime ──
|
|
1145
|
-
# ── Health / status / engine-summary router (latticeai.api.health, v1.2.0) ───
|
|
1146
|
-
# /health, /mode, /runtime_features, /engines(GET) now live in the health router.
|
|
1147
|
-
# Heavier engine mutation endpoints remain below in server_app.
|
|
1148
|
-
_model_runtime = register_model_runtime_routers(
|
|
1149
|
-
app=app,
|
|
1150
|
-
create_health_router=create_health_router,
|
|
1151
|
-
create_models_router=create_models_router,
|
|
1152
|
-
register_health_and_model_routers=register_health_and_model_routers,
|
|
1153
|
-
model_router=router,
|
|
1154
|
-
runtime_service=_model_runtime_service,
|
|
1155
|
-
runtime_features=_model_runtime_service.runtime_features,
|
|
1156
|
-
is_public_mode=IS_PUBLIC_MODE,
|
|
1157
|
-
engine_status=_model_runtime_service.engine_status,
|
|
1158
|
-
get_current_user=get_current_user,
|
|
1159
|
-
require_auth=REQUIRE_AUTH,
|
|
1160
|
-
app_version=APP_VERSION,
|
|
1161
|
-
app_mode=APP_MODE,
|
|
1162
|
-
require_user=require_user,
|
|
1163
|
-
require_admin=require_admin,
|
|
1164
|
-
load_users=load_users,
|
|
1165
|
-
get_user_role=get_user_role,
|
|
1166
|
-
install_engine=_model_runtime_service.install_engine,
|
|
1167
|
-
verify_cloud_models=_model_runtime_service.verify_cloud_models,
|
|
1168
|
-
normalize_local_model_request=normalize_local_model_request,
|
|
1169
|
-
download_hf_model=download_hf_model,
|
|
1170
|
-
prepare_and_load_model=_model_runtime_service.prepare_and_load_model,
|
|
1171
|
-
prepare_and_load_model_stream=_model_runtime_service.prepare_and_load_model_stream,
|
|
1172
|
-
sse_event=sse_event,
|
|
1173
|
-
ensure_ollama_server=ensure_ollama_server,
|
|
1174
|
-
local_binary=local_binary,
|
|
1175
|
-
filter_lower_family_versions=filter_lower_family_versions,
|
|
1176
|
-
list_compat_profiles=_list_compat_profiles,
|
|
1177
|
-
set_user_api_key=set_user_api_key,
|
|
1178
|
-
engine_model_catalog=ENGINE_MODEL_CATALOG,
|
|
1179
|
-
model_engine_aliases=MODEL_ENGINE_ALIASES,
|
|
1180
|
-
cloud_verify_ttl_seconds=CLOUD_VERIFY_TTL_SECONDS,
|
|
1181
|
-
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
1182
|
-
)
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
# ── Chat / Completion ──────────────────────────────────────────────────────────
|
|
1186
|
-
|
|
1187
|
-
def _embedding_info() -> dict:
|
|
1188
|
-
from latticeai.core.embedding_providers import (
|
|
1189
|
-
PROVIDER_TYPES,
|
|
1190
|
-
embedding_provider_profiles,
|
|
1191
|
-
)
|
|
1192
|
-
info = EMBEDDER.as_dict()
|
|
1193
|
-
info["available_providers"] = list(PROVIDER_TYPES)
|
|
1194
|
-
info["profile"] = CONFIG.embedding_profile or ""
|
|
1195
|
-
info["profiles"] = embedding_provider_profiles()
|
|
1196
|
-
return info
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
def _allowed_workspaces_for(user):
|
|
1200
|
-
# No-auth local mode is single-user: no scoping. With auth, scope
|
|
1201
|
-
# reads to the caller's memberships (legacy-global rows require an
|
|
1202
|
-
# explicit maintenance-only compatibility opt-in).
|
|
1203
|
-
if not REQUIRE_AUTH or not user:
|
|
1204
|
-
return None
|
|
1205
|
-
return PLATFORM.allowed_scopes(user)
|
|
1206
|
-
|
|
1207
|
-
# Evidence → action (v9.9.6): an answer's citations become ready-to-send,
|
|
1208
|
-
# evidence-scoped follow-ups. Deterministic composition only — execution
|
|
1209
|
-
# stays on the chat/file-generation path.
|
|
1210
|
-
from latticeai.api.evidence_actions import create_evidence_actions_router
|
|
1211
|
-
from latticeai.services.evidence_actions import EvidenceActionService
|
|
1212
|
-
|
|
1213
|
-
EVIDENCE_ACTIONS_SERVICE = EvidenceActionService(
|
|
1214
|
-
node_reader=getattr(KNOWLEDGE_GRAPH, "get_node", None)
|
|
1215
|
-
)
|
|
1216
|
-
app.include_router(
|
|
1217
|
-
create_evidence_actions_router(
|
|
1218
|
-
service=EVIDENCE_ACTIONS_SERVICE,
|
|
1219
|
-
require_user=require_user,
|
|
1220
|
-
allowed_workspaces_for=_allowed_workspaces_for,
|
|
1221
|
-
)
|
|
1222
|
-
)
|
|
1223
|
-
|
|
1224
|
-
# Voice memo capture (v9.9.7): the shortest path from a thought to the
|
|
1225
|
-
# Brain. Transcription is an optional local port — absent, the memo is
|
|
1226
|
-
# still stored and the response says it is not searchable.
|
|
1227
|
-
from latticeai.api.voice_capture import create_voice_capture_router
|
|
1228
|
-
from latticeai.services.voice_capture import VoiceCaptureService
|
|
1229
|
-
|
|
1230
|
-
VOICE_CAPTURE = VoiceCaptureService(
|
|
1231
|
-
pipeline=INGESTION_PIPELINE if ENABLE_GRAPH else None,
|
|
1232
|
-
transcriber=None,
|
|
1233
|
-
)
|
|
1234
|
-
app.include_router(
|
|
1235
|
-
create_voice_capture_router(
|
|
1236
|
-
service=VOICE_CAPTURE,
|
|
1237
|
-
require_user=require_user,
|
|
1238
|
-
gate_write=PLATFORM.gate_write,
|
|
1239
|
-
append_audit_event=append_audit_event,
|
|
1240
|
-
)
|
|
1241
|
-
)
|
|
1242
|
-
|
|
1243
|
-
# Multi-turn project loop (v9.9.6): work that spans several runs keeps its
|
|
1244
|
-
# files, open TODOs, and last honest verification in one project session.
|
|
1245
|
-
from latticeai.api.project_sessions import create_project_sessions_router
|
|
1246
|
-
from latticeai.core.project_sessions import ProjectSessionStore
|
|
1247
|
-
|
|
1248
|
-
PROJECT_SESSIONS = ProjectSessionStore(DATA_DIR / "project_sessions")
|
|
1249
|
-
app.include_router(
|
|
1250
|
-
create_project_sessions_router(
|
|
1251
|
-
store=PROJECT_SESSIONS,
|
|
1252
|
-
require_user=require_user,
|
|
1253
|
-
gate_read=PLATFORM.gate_read,
|
|
1254
|
-
gate_write=PLATFORM.gate_write,
|
|
1255
|
-
)
|
|
1256
|
-
)
|
|
1257
|
-
|
|
1258
|
-
tool_router_context, interaction_router_context = build_interaction_contexts(
|
|
1259
|
-
config=CONFIG,
|
|
1260
|
-
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1261
|
-
data_dir=DATA_DIR,
|
|
1262
|
-
static_dir=STATIC_DIR,
|
|
1263
|
-
model_router=router,
|
|
1264
|
-
require_user=require_user,
|
|
1265
|
-
require_admin=require_admin,
|
|
1266
|
-
get_current_user=get_current_user,
|
|
1267
|
-
clear_history=clear_history,
|
|
1268
|
-
append_audit_event=append_audit_event,
|
|
1269
|
-
enforce_rate_limit=enforce_rate_limit,
|
|
1270
|
-
bytes_match_extension=_bytes_match_extension,
|
|
1271
|
-
classify_sensitive_message=classify_sensitive_message,
|
|
1272
|
-
save_to_history=save_to_history,
|
|
1273
|
-
enable_graph=ENABLE_GRAPH,
|
|
1274
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
1275
|
-
require_graph=_require_graph,
|
|
1276
|
-
local_kg_watcher=LOCAL_KG_WATCHER,
|
|
1277
|
-
load_mcp_installs=load_mcp_installs,
|
|
1278
|
-
recommend_mcps=recommend_mcps,
|
|
1279
|
-
install_mcp=install_mcp,
|
|
1280
|
-
mcp_public_item=mcp_public_item,
|
|
1281
|
-
hooks=HOOKS_REGISTRY,
|
|
1282
|
-
workspace_service=WORKSPACE_SERVICE,
|
|
1283
|
-
chat_context=context,
|
|
1284
|
-
search_service=SEARCH_SERVICE,
|
|
1285
|
-
allowed_workspaces_for=_allowed_workspaces_for,
|
|
1286
|
-
embedding_info=_embedding_info,
|
|
1287
|
-
agent_registry=AGENT_REGISTRY,
|
|
1288
|
-
memory_service=MEMORY_SERVICE,
|
|
1289
|
-
platform=PLATFORM,
|
|
1290
|
-
active_model_getter=lambda: router.current_model_id or "",
|
|
1291
|
-
brain_intelligence=BRAIN_INTELLIGENCE,
|
|
1292
|
-
)
|
|
1293
|
-
register_interaction_routers(
|
|
1294
|
-
app,
|
|
1295
|
-
interaction_context=interaction_router_context,
|
|
1296
|
-
create_chat_router=create_chat_router,
|
|
1297
|
-
create_search_router=create_search_router,
|
|
1298
|
-
create_tools_router=create_tools_router,
|
|
1299
|
-
create_hooks_router=create_hooks_router,
|
|
1300
|
-
create_agent_registry_router=create_agent_registry_router,
|
|
1301
|
-
create_memory_router=create_memory_router,
|
|
1302
|
-
create_brain_intelligence_router=create_brain_intelligence_router,
|
|
1303
|
-
)
|
|
1304
|
-
|
|
1305
|
-
from latticeai.api.review_queue import create_review_queue_router
|
|
1306
|
-
run_review_item = build_review_run_now_runner(PLATFORM, HTTPException)
|
|
1307
|
-
|
|
1308
|
-
register_review_and_brain_tail_routers(
|
|
1309
|
-
app,
|
|
1310
|
-
create_review_queue_router=create_review_queue_router,
|
|
1311
|
-
review_queue=REVIEW_QUEUE,
|
|
1312
|
-
require_user=require_user,
|
|
1313
|
-
gate_read=PLATFORM.gate_read,
|
|
1314
|
-
gate_write=PLATFORM.gate_write,
|
|
1315
|
-
run_review_item=run_review_item,
|
|
1316
|
-
append_audit_event=append_audit_event,
|
|
1317
|
-
# Approving a change_proposal from the Review Center applies the
|
|
1318
|
-
# staged content through the same service the agent governor uses.
|
|
1319
|
-
change_proposals=CHANGE_PROPOSALS,
|
|
1320
|
-
create_browser_router=create_browser_router,
|
|
1321
|
-
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1322
|
-
workspace_service=WORKSPACE_SERVICE,
|
|
1323
|
-
create_portability_router=create_portability_router,
|
|
1324
|
-
kg_portability=KG_PORTABILITY,
|
|
1325
|
-
require_admin=require_admin,
|
|
1326
|
-
build_brain_network=build_brain_network,
|
|
1327
|
-
device_identity=DEVICE_IDENTITY,
|
|
1328
|
-
data_dir=DATA_DIR,
|
|
1329
|
-
create_network_router=create_network_router,
|
|
1330
|
-
create_garden_router=create_garden_router,
|
|
1331
|
-
gardener=gardener,
|
|
1332
|
-
create_setup_router=create_setup_router,
|
|
1333
|
-
model_router=router,
|
|
1334
|
-
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
1335
|
-
)
|
|
1336
|
-
|
|
1337
|
-
# ── Entry Point ────────────────────────────────────────────────────────────────
|
|
87
|
+
from latticeai.services.tool_dispatch import agent_risk as _agent_risk
|
|
88
|
+
from latticeai.services.tool_dispatch import check_tool_role as _check_tool_role
|
|
89
|
+
from latticeai.services.tool_dispatch import tool_response as _tool_response
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
# ── pass-through re-exports ──────────────────────────────────────
|
|
93
|
+
"ENGINE_MODEL_CATALOG": ENGINE_MODEL_CATALOG,
|
|
94
|
+
"TOOL_GOVERNANCE": TOOL_GOVERNANCE,
|
|
95
|
+
"filter_lower_family_versions": filter_lower_family_versions,
|
|
96
|
+
"hash_password": hash_password,
|
|
97
|
+
"normalize_local_model_request": normalize_local_model_request,
|
|
98
|
+
"verify_password": verify_password,
|
|
99
|
+
"_LOCAL_WRITE_BLOCKED_PREFIXES": _LOCAL_WRITE_BLOCKED_PREFIXES,
|
|
100
|
+
"_TOOL_CATALOG_BRIEF": _TOOL_CATALOG_BRIEF,
|
|
101
|
+
"_TOOL_GOVERNANCE_DEFAULT": _TOOL_GOVERNANCE_DEFAULT,
|
|
102
|
+
"_agent_risk": _agent_risk,
|
|
103
|
+
"_build_admin_audit_report": _build_admin_audit_report,
|
|
104
|
+
"_build_sensitivity_report": _build_sensitivity_report,
|
|
105
|
+
"_check_ip_rate_limit": _check_ip_rate_limit,
|
|
106
|
+
"_check_tool_role": _check_tool_role,
|
|
107
|
+
"_classify_sensitive_message": _classify_sensitive_message,
|
|
108
|
+
"_create_security_router": _create_security_router,
|
|
109
|
+
"_fetch_skills_marketplace": _fetch_skills_marketplace,
|
|
110
|
+
"_get_audit_log": _get_audit_log,
|
|
111
|
+
"_list_compat_profiles": _list_compat_profiles,
|
|
112
|
+
"_redact_secret_text": _redact_secret_text,
|
|
113
|
+
"_tool_response": _tool_response,
|
|
114
|
+
"_user_id_for_email": _user_id_for_email,
|
|
115
|
+
"_workspace_scope_from_request": _workspace_scope_from_request,
|
|
116
|
+
# ── values the phases produced ───────────────────────────────────
|
|
117
|
+
"enforce_rate_limit": ctx.enforce_rate_limit,
|
|
118
|
+
"_RATE_LIMIT_ENABLED": ctx._RATE_LIMIT_ENABLED,
|
|
119
|
+
"_SESSION_TTL": ctx._SESSION_TTL,
|
|
120
|
+
"_allowed_workspaces_for": ctx._allowed_workspaces_for,
|
|
121
|
+
"_bytes_match_extension": ctx._bytes_match_extension,
|
|
122
|
+
"_check_rate_limit": ctx._check_rate_limit,
|
|
123
|
+
"_client_ip": ctx._client_ip,
|
|
124
|
+
"_embedding_info": ctx._embedding_info,
|
|
125
|
+
"_get_sso_discovery": ctx._get_sso_discovery,
|
|
126
|
+
"_graph_stats_safe": ctx._graph_stats_safe,
|
|
127
|
+
"_host_is_loopback": ctx._host_is_loopback,
|
|
128
|
+
"_llm_generate_sync": ctx._llm_generate_sync,
|
|
129
|
+
"_product_hardening_status": ctx._product_hardening_status,
|
|
130
|
+
"_recent_chat_context": ctx._recent_chat_context,
|
|
131
|
+
"_require_graph": ctx._require_graph,
|
|
132
|
+
"_scoped_hybrid_search": ctx._scoped_hybrid_search,
|
|
133
|
+
"_security_audit_events_safe": ctx._security_audit_events_safe,
|
|
134
|
+
"_security_list_uploaded_files": ctx._security_list_uploaded_files,
|
|
135
|
+
"_spawn": ctx._spawn,
|
|
136
|
+
"_workspace_graph": ctx._workspace_graph,
|
|
137
|
+
"_workspace_models_payload": ctx._workspace_models_payload,
|
|
138
|
+
"_workspace_settings_payload": ctx._workspace_settings_payload,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def build_context(config: "Optional[Config]" = None) -> RuntimeContext:
|
|
143
|
+
"""Run every build phase in order and return the populated context.
|
|
144
|
+
|
|
145
|
+
Exposed separately from :func:`build_runtime` so tests can inspect the
|
|
146
|
+
assembly (which phase produced what) without going through the legacy
|
|
147
|
+
namespace adapter.
|
|
148
|
+
"""
|
|
149
|
+
ctx = RuntimeContext(config)
|
|
150
|
+
for phase in BUILD_PHASES:
|
|
151
|
+
phase(ctx)
|
|
152
|
+
return ctx
|
|
1338
153
|
|
|
1339
|
-
def main() -> None:
|
|
1340
|
-
print(f"🧠 Lattice AI Server starting in {APP_MODE} mode on http://{DEFAULT_HOST}:{DEFAULT_PORT}")
|
|
1341
|
-
uvicorn.run(app, host=DEFAULT_HOST, port=DEFAULT_PORT, log_level="info")
|
|
1342
154
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
router_bundle = build_router_bundle(app, context)
|
|
155
|
+
def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
156
|
+
"""Assemble the application and return its explicit export namespace."""
|
|
157
|
+
ctx = build_context(config)
|
|
1347
158
|
runtime_bundle = RuntimeBundle(
|
|
1348
|
-
app=app,
|
|
1349
|
-
CONFIG=CONFIG,
|
|
1350
|
-
KNOWLEDGE_GRAPH=KNOWLEDGE_GRAPH,
|
|
1351
|
-
INGESTION_PIPELINE=INGESTION_PIPELINE,
|
|
1352
|
-
AGENT_RUNTIME=AGENT_RUNTIME,
|
|
1353
|
-
HOOKS_REGISTRY=HOOKS_REGISTRY,
|
|
1354
|
-
REVIEW_QUEUE=REVIEW_QUEUE,
|
|
1355
|
-
AGENT_REGISTRY=AGENT_REGISTRY,
|
|
1356
|
-
model_router=
|
|
159
|
+
app=ctx.app,
|
|
160
|
+
CONFIG=ctx.CONFIG,
|
|
161
|
+
KNOWLEDGE_GRAPH=ctx.KNOWLEDGE_GRAPH,
|
|
162
|
+
INGESTION_PIPELINE=ctx.INGESTION_PIPELINE,
|
|
163
|
+
AGENT_RUNTIME=ctx.AGENT_RUNTIME,
|
|
164
|
+
HOOKS_REGISTRY=ctx.HOOKS_REGISTRY,
|
|
165
|
+
REVIEW_QUEUE=ctx.REVIEW_QUEUE,
|
|
166
|
+
AGENT_REGISTRY=ctx.AGENT_REGISTRY,
|
|
167
|
+
model_router=ctx.model_router,
|
|
1357
168
|
build_runtime=build_runtime,
|
|
1358
169
|
get_shared_runtime=get_shared_runtime,
|
|
1359
170
|
create_app=create_app,
|
|
1360
|
-
config_runtime=
|
|
1361
|
-
security_runtime=
|
|
1362
|
-
brain_runtime=
|
|
1363
|
-
model_runtime=
|
|
1364
|
-
router_bundle=
|
|
171
|
+
config_runtime=ctx.config_runtime,
|
|
172
|
+
security_runtime=ctx.security_runtime,
|
|
173
|
+
brain_runtime=ctx.brain_runtime,
|
|
174
|
+
model_runtime=ctx.model_runtime,
|
|
175
|
+
router_bundle=build_router_bundle(ctx.app, ctx.app_context),
|
|
1365
176
|
)
|
|
1366
177
|
return build_runtime_namespace(
|
|
1367
178
|
runtime_bundle=runtime_bundle,
|
|
1368
|
-
legacy_exports=
|
|
1369
|
-
"ENGINE_MODEL_CATALOG": ENGINE_MODEL_CATALOG,
|
|
1370
|
-
"TOOL_GOVERNANCE": TOOL_GOVERNANCE,
|
|
1371
|
-
"enforce_rate_limit": enforce_rate_limit,
|
|
1372
|
-
"filter_lower_family_versions": filter_lower_family_versions,
|
|
1373
|
-
"hash_password": hash_password,
|
|
1374
|
-
"normalize_local_model_request": normalize_local_model_request,
|
|
1375
|
-
"verify_password": verify_password,
|
|
1376
|
-
"_LOCAL_WRITE_BLOCKED_PREFIXES": _LOCAL_WRITE_BLOCKED_PREFIXES,
|
|
1377
|
-
"_RATE_LIMIT_ENABLED": _RATE_LIMIT_ENABLED,
|
|
1378
|
-
"_SESSION_TTL": _SESSION_TTL,
|
|
1379
|
-
"_TOOL_CATALOG_BRIEF": _TOOL_CATALOG_BRIEF,
|
|
1380
|
-
"_TOOL_GOVERNANCE_DEFAULT": _TOOL_GOVERNANCE_DEFAULT,
|
|
1381
|
-
"_agent_risk": _agent_risk,
|
|
1382
|
-
"_allowed_workspaces_for": _allowed_workspaces_for,
|
|
1383
|
-
"_build_admin_audit_report": _build_admin_audit_report,
|
|
1384
|
-
"_build_sensitivity_report": _build_sensitivity_report,
|
|
1385
|
-
"_bytes_match_extension": _bytes_match_extension,
|
|
1386
|
-
"_check_ip_rate_limit": _check_ip_rate_limit,
|
|
1387
|
-
"_check_rate_limit": _check_rate_limit,
|
|
1388
|
-
"_check_tool_role": _check_tool_role,
|
|
1389
|
-
"_classify_sensitive_message": _classify_sensitive_message,
|
|
1390
|
-
"_client_ip": _client_ip,
|
|
1391
|
-
"_create_security_router": _create_security_router,
|
|
1392
|
-
"_embedding_info": _embedding_info,
|
|
1393
|
-
"_fetch_skills_marketplace": _fetch_skills_marketplace,
|
|
1394
|
-
"_get_audit_log": _get_audit_log,
|
|
1395
|
-
"_get_sso_discovery": _get_sso_discovery,
|
|
1396
|
-
"_graph_stats_safe": _graph_stats_safe,
|
|
1397
|
-
"_host_is_loopback": _host_is_loopback,
|
|
1398
|
-
"_list_compat_profiles": _list_compat_profiles,
|
|
1399
|
-
"_llm_generate_sync": _llm_generate_sync,
|
|
1400
|
-
"_product_hardening_status": _product_hardening_status,
|
|
1401
|
-
"_recent_chat_context": _recent_chat_context,
|
|
1402
|
-
"_redact_secret_text": _redact_secret_text,
|
|
1403
|
-
"_require_graph": _require_graph,
|
|
1404
|
-
"_scoped_hybrid_search": _scoped_hybrid_search,
|
|
1405
|
-
"_security_audit_events_safe": _security_audit_events_safe,
|
|
1406
|
-
"_security_list_uploaded_files": _security_list_uploaded_files,
|
|
1407
|
-
"_spawn": _spawn,
|
|
1408
|
-
"_tool_response": _tool_response,
|
|
1409
|
-
"_user_id_for_email": _user_id_for_email,
|
|
1410
|
-
"_workspace_graph": _workspace_graph,
|
|
1411
|
-
"_workspace_models_payload": _workspace_models_payload,
|
|
1412
|
-
"_workspace_scope_from_request": _workspace_scope_from_request,
|
|
1413
|
-
"_workspace_settings_payload": _workspace_settings_payload,
|
|
1414
|
-
},
|
|
179
|
+
legacy_exports=_legacy_exports(ctx),
|
|
1415
180
|
)
|
|
1416
181
|
|
|
1417
182
|
|
|
@@ -1432,6 +197,11 @@ class AppRuntime:
|
|
|
1432
197
|
(``app``, ``KNOWLEDGE_GRAPH``, ``load_users``, …) as attributes.
|
|
1433
198
|
"""
|
|
1434
199
|
|
|
200
|
+
# Declared because they are bound dynamically from the namespace dict;
|
|
201
|
+
# these two are the ones this module itself reads back.
|
|
202
|
+
app: Any
|
|
203
|
+
CONFIG: Any
|
|
204
|
+
|
|
1435
205
|
def __init__(self, namespace: Dict[str, Any]) -> None:
|
|
1436
206
|
self._legacy_namespace = LegacyRuntimeNamespace(namespace)
|
|
1437
207
|
self._legacy_namespace.bind(self)
|
|
@@ -1465,4 +235,20 @@ def create_app(config: "Optional[Config]" = None) -> "FastAPI":
|
|
|
1465
235
|
|
|
1466
236
|
|
|
1467
237
|
def main() -> None:
|
|
1468
|
-
|
|
238
|
+
"""Serve the shared runtime (``python -m latticeai.server_app``).
|
|
239
|
+
|
|
240
|
+
This used to call ``get_shared_runtime().main()``, but ``main`` was a local
|
|
241
|
+
inside the old ``_build`` closure and was never on the export allowlist, so
|
|
242
|
+
the call raised ``AttributeError`` — the module entrypoint was broken.
|
|
243
|
+
Serving from here needs no export at all.
|
|
244
|
+
"""
|
|
245
|
+
import uvicorn
|
|
246
|
+
|
|
247
|
+
runtime = get_shared_runtime()
|
|
248
|
+
config = runtime.CONFIG
|
|
249
|
+
host, port = config.host, config.port
|
|
250
|
+
print(
|
|
251
|
+
f"🧠 Lattice AI Server starting in {config.app_mode} mode "
|
|
252
|
+
f"on http://{host}:{port}"
|
|
253
|
+
)
|
|
254
|
+
uvicorn.run(runtime.app, host=host, port=port, log_level="info")
|