ltcai 6.2.0 → 6.3.1
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 +29 -17
- package/docs/CHANGELOG.md +66 -0
- package/frontend/src/components/onboarding/AnalysisScreen.tsx +127 -0
- package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +29 -0
- package/frontend/src/components/onboarding/InstallScreen.tsx +208 -0
- package/frontend/src/components/onboarding/LanguageChooser.tsx +23 -0
- package/frontend/src/components/onboarding/LoginScreen.tsx +131 -0
- package/frontend/src/components/onboarding/ProductFlowScreens.tsx +13 -688
- package/frontend/src/components/onboarding/RecommendationScreen.tsx +59 -0
- package/frontend/src/components/onboarding/recommendationModel.ts +132 -0
- package/frontend/src/features/admin/AdminConsole.tsx +1 -1
- package/frontend/src/features/brain/BrainCarePanel.tsx +254 -0
- package/frontend/src/features/brain/BrainComposer.tsx +66 -0
- package/frontend/src/features/brain/BrainConversation.tsx +131 -0
- package/frontend/src/features/brain/BrainGraphLayer.tsx +132 -0
- package/frontend/src/features/brain/BrainHome.tsx +28 -795
- package/frontend/src/features/brain/BrainMemoryLayer.tsx +38 -0
- package/frontend/src/features/brain/BrainOverviewPanel.tsx +74 -0
- package/frontend/src/features/brain/BrainRelationshipLayer.tsx +43 -0
- package/frontend/src/features/brain/DepthEmergence.tsx +53 -0
- package/frontend/src/features/brain/types.ts +6 -6
- package/frontend/src/features/review/ReviewCard.tsx +14 -10
- package/frontend/src/features/review/ReviewInbox.tsx +24 -9
- package/frontend/src/i18n.ts +208 -0
- package/frontend/src/pages/Brain.tsx +63 -5
- package/frontend/src/pages/Capture.tsx +139 -43
- package/frontend/src/pages/Library.tsx +72 -6
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/app_factory.py +48 -106
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/runtime/access_runtime.py +97 -0
- package/latticeai/runtime/chat_wiring.py +119 -0
- package/latticeai/runtime/model_wiring.py +40 -0
- package/latticeai/runtime/platform_runtime_wiring.py +2 -3
- package/latticeai/runtime/review_wiring.py +37 -0
- package/latticeai/runtime/tail_wiring.py +21 -0
- package/package.json +2 -2
- package/scripts/check_i18n_literals.mjs +52 -0
- 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 +5 -5
- package/static/app/assets/index-Div5vMlq.css +2 -0
- package/static/app/assets/{index-D91Rz5--.js → index-t1jx1BR9.js} +2 -2
- package/static/app/assets/index-t1jx1BR9.js.map +1 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-B2-1Gm0q.css +0 -2
- package/static/app/assets/index-D91Rz5--.js.map +0 -1
package/latticeai/app_factory.py
CHANGED
|
@@ -18,8 +18,14 @@ import threading
|
|
|
18
18
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
19
19
|
|
|
20
20
|
from latticeai.runtime.app_context_runtime import build_app_context
|
|
21
|
+
from latticeai.runtime.access_runtime import build_access_runtime
|
|
21
22
|
from latticeai.runtime.bootstrap import build_session_runtime
|
|
22
23
|
from latticeai.runtime.brain_runtime import build_brain_runtime
|
|
24
|
+
from latticeai.runtime.chat_wiring import (
|
|
25
|
+
build_chat_agent_runtime_from_context,
|
|
26
|
+
build_interaction_contexts,
|
|
27
|
+
maybe_build_telegram_chat_mirror,
|
|
28
|
+
)
|
|
23
29
|
from latticeai.runtime.config_runtime import build_config_runtime
|
|
24
30
|
from latticeai.runtime.context_runtime import build_context_runtime
|
|
25
31
|
from latticeai.runtime.hooks_runtime import (
|
|
@@ -28,12 +34,16 @@ from latticeai.runtime.hooks_runtime import (
|
|
|
28
34
|
build_hooks_runtime,
|
|
29
35
|
)
|
|
30
36
|
from latticeai.runtime.lifespan_runtime import build_lifespan_runtime
|
|
37
|
+
from latticeai.runtime.model_wiring import (
|
|
38
|
+
configure_model_runtime_from_context,
|
|
39
|
+
register_model_runtime_routers,
|
|
40
|
+
)
|
|
31
41
|
from latticeai.runtime.platform_services_runtime import (
|
|
32
42
|
build_brain_network,
|
|
33
|
-
build_model_service,
|
|
34
43
|
)
|
|
35
44
|
from latticeai.runtime.platform_runtime_wiring import build_platform_automation_runtime
|
|
36
45
|
from latticeai.runtime.persistence_runtime import build_persistence_runtime
|
|
46
|
+
from latticeai.runtime.review_wiring import build_review_run_now_runner
|
|
37
47
|
from latticeai.runtime.router_registration import (
|
|
38
48
|
build_auth_admin_security_router_bundle,
|
|
39
49
|
build_static_routes_bundle,
|
|
@@ -44,8 +54,8 @@ from latticeai.runtime.router_registration import (
|
|
|
44
54
|
register_review_and_brain_tail_routers,
|
|
45
55
|
)
|
|
46
56
|
from latticeai.runtime.security_runtime import build_security_runtime
|
|
57
|
+
from latticeai.runtime.tail_wiring import register_tail_runtime_routers
|
|
47
58
|
from latticeai.runtime.web_runtime import build_web_runtime
|
|
48
|
-
from latticeai.services.router_context import InteractionRouterContext, ToolRouterContext
|
|
49
59
|
|
|
50
60
|
if TYPE_CHECKING: # imports for annotations only — keep module import light
|
|
51
61
|
from fastapi import FastAPI
|
|
@@ -113,12 +123,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
113
123
|
from latticeai.core.enterprise import (
|
|
114
124
|
capability_registry,
|
|
115
125
|
)
|
|
116
|
-
from latticeai.core.policy import
|
|
126
|
+
from latticeai.core.policy import policy_matrix
|
|
117
127
|
from latticeai.core.users import (
|
|
118
128
|
ensure_user_identity,
|
|
119
129
|
load_users_file,
|
|
120
130
|
migrate_knowledge_graph_identity,
|
|
121
|
-
normalize_email,
|
|
122
131
|
save_users_file,
|
|
123
132
|
user_id_for_email as _user_id_for_email,
|
|
124
133
|
)
|
|
@@ -804,42 +813,21 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
804
813
|
def clear_conversation(conversation_id: str, started_at: Optional[str] = None) -> Dict:
|
|
805
814
|
return CONVERSATIONS.clear_conversation(conversation_id, started_at=started_at)
|
|
806
815
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
return "admin"
|
|
823
|
-
first_email = next(iter(users), None)
|
|
824
|
-
return "admin" if first_email == normalized_email else "user"
|
|
825
|
-
|
|
826
|
-
def _extract_bearer_token(request: Request) -> Optional[str]:
|
|
827
|
-
auth = request.headers.get("Authorization", "")
|
|
828
|
-
if auth.startswith("Bearer "):
|
|
829
|
-
return auth[7:].strip()
|
|
830
|
-
return request.cookies.get("session_token")
|
|
831
|
-
|
|
832
|
-
def get_current_user(request: Request) -> Optional[str]:
|
|
833
|
-
token = _extract_bearer_token(request)
|
|
834
|
-
if token:
|
|
835
|
-
return get_session_email(token)
|
|
836
|
-
return None
|
|
837
|
-
|
|
838
|
-
def require_user(request: Request) -> str:
|
|
839
|
-
email = get_current_user(request)
|
|
840
|
-
if REQUIRE_AUTH and not email:
|
|
841
|
-
raise HTTPException(status_code=401, detail="인증이 필요합니다.")
|
|
842
|
-
return email or ""
|
|
816
|
+
_access_runtime = build_access_runtime(
|
|
817
|
+
config=CONFIG,
|
|
818
|
+
require_auth=REQUIRE_AUTH,
|
|
819
|
+
http_exception=HTTPException,
|
|
820
|
+
request_type=Request,
|
|
821
|
+
load_users=load_users,
|
|
822
|
+
get_session_email=get_session_email,
|
|
823
|
+
user_id_for_email=_user_id_for_email,
|
|
824
|
+
)
|
|
825
|
+
get_user_role = _access_runtime["get_user_role"]
|
|
826
|
+
_extract_bearer_token = _access_runtime["_extract_bearer_token"]
|
|
827
|
+
get_current_user = _access_runtime["get_current_user"]
|
|
828
|
+
require_user = _access_runtime["require_user"]
|
|
829
|
+
require_admin = _access_runtime["require_admin"]
|
|
830
|
+
public_user = _access_runtime["public_user"]
|
|
843
831
|
|
|
844
832
|
|
|
845
833
|
# ── Rate limiting & file validation — delegated to latticeai.core.security ────
|
|
@@ -908,35 +896,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
908
896
|
raise HTTPException(status_code=403, detail="승인된 파일 내용과 요청 내용이 다릅니다.")
|
|
909
897
|
|
|
910
898
|
|
|
911
|
-
def require_admin(request: Request) -> tuple[str, Dict]:
|
|
912
|
-
users = load_users()
|
|
913
|
-
if not REQUIRE_AUTH:
|
|
914
|
-
return "", users
|
|
915
|
-
token = _extract_bearer_token(request)
|
|
916
|
-
if token:
|
|
917
|
-
email = get_session_email(token)
|
|
918
|
-
if email:
|
|
919
|
-
role = get_user_role(email, users)
|
|
920
|
-
try:
|
|
921
|
-
require_capability(role, "admin:users")
|
|
922
|
-
return email, users
|
|
923
|
-
except PermissionError:
|
|
924
|
-
pass
|
|
925
|
-
raise HTTPException(status_code=403, detail="관리자 권한이 필요합니다.")
|
|
926
|
-
|
|
927
|
-
def public_user(email: str, user: Dict, users: Dict) -> Dict:
|
|
928
|
-
role = get_user_role(email, users)
|
|
929
|
-
user_id = user.get("id") or _user_id_for_email(users, email)
|
|
930
|
-
return {
|
|
931
|
-
"id": user_id,
|
|
932
|
-
"email": email,
|
|
933
|
-
"identity": user_id,
|
|
934
|
-
"name": user.get("name", ""),
|
|
935
|
-
"nickname": user.get("nickname", ""),
|
|
936
|
-
"role": role,
|
|
937
|
-
"disabled": bool(user.get("disabled", False)),
|
|
938
|
-
}
|
|
939
|
-
|
|
940
899
|
def get_history_user(email: Optional[str], nickname: Optional[str] = None) -> Dict:
|
|
941
900
|
if not email:
|
|
942
901
|
return {"user_email": None, "user_nickname": nickname or None}
|
|
@@ -1089,7 +1048,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1089
1048
|
OPEN_REGISTRATION = CONFIG.open_registration
|
|
1090
1049
|
INVITE_CODE = CONFIG.invite_code
|
|
1091
1050
|
INVITE_GATE_ENABLED = CONFIG.invite_gate_enabled
|
|
1092
|
-
|
|
1051
|
+
configure_model_runtime_from_context(
|
|
1052
|
+
configure_model_runtime=configure_model_runtime,
|
|
1093
1053
|
router=router,
|
|
1094
1054
|
APP_MODE=APP_MODE,
|
|
1095
1055
|
DEFAULT_HOST=DEFAULT_HOST,
|
|
@@ -1236,12 +1196,10 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1236
1196
|
# ── Telegram chat mirror: registered only when ENABLE_TELEGRAM is truthy.
|
|
1237
1197
|
# latticeai.api.chat no longer imports telegram_bot (a 45KB module that
|
|
1238
1198
|
# mutates os.environ at import); it calls this injected callback instead.
|
|
1239
|
-
on_chat_message =
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
_spawn(broadcast_web_chat(role, text), name="telegram_broadcast")
|
|
1244
|
-
on_chat_message = _telegram_chat_mirror
|
|
1199
|
+
on_chat_message = maybe_build_telegram_chat_mirror(
|
|
1200
|
+
enable_telegram=ENABLE_TELEGRAM,
|
|
1201
|
+
spawn=_spawn,
|
|
1202
|
+
)
|
|
1245
1203
|
|
|
1246
1204
|
def _recent_chat_context(
|
|
1247
1205
|
limit: int = 10,
|
|
@@ -1257,7 +1215,8 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1257
1215
|
conversation_id=conversation_id,
|
|
1258
1216
|
)
|
|
1259
1217
|
|
|
1260
|
-
CHAT_AGENT_RUNTIME =
|
|
1218
|
+
CHAT_AGENT_RUNTIME = build_chat_agent_runtime_from_context(
|
|
1219
|
+
build_agent_runtime=build_agent_runtime,
|
|
1261
1220
|
model_router=router,
|
|
1262
1221
|
execute_tool=execute_tool,
|
|
1263
1222
|
recent_chat_context=_recent_chat_context,
|
|
@@ -1404,22 +1363,19 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1404
1363
|
# ── Health / status / engine-summary router (latticeai.api.health, v1.2.0) ───
|
|
1405
1364
|
# /health, /mode, /runtime_features, /engines(GET) now live in the health router.
|
|
1406
1365
|
# Heavier engine mutation endpoints remain below in server_app.
|
|
1407
|
-
MODEL_SERVICE =
|
|
1366
|
+
MODEL_SERVICE = register_model_runtime_routers(
|
|
1367
|
+
app=app,
|
|
1368
|
+
create_health_router=create_health_router,
|
|
1369
|
+
create_models_router=create_models_router,
|
|
1370
|
+
register_health_and_model_routers=register_health_and_model_routers,
|
|
1408
1371
|
model_router=router,
|
|
1409
1372
|
runtime_features=runtime_features,
|
|
1410
|
-
|
|
1411
|
-
)
|
|
1412
|
-
register_health_and_model_routers(
|
|
1413
|
-
app,
|
|
1414
|
-
create_health_router=create_health_router,
|
|
1415
|
-
model_service=MODEL_SERVICE,
|
|
1373
|
+
is_public_mode=IS_PUBLIC_MODE,
|
|
1416
1374
|
engine_status=engine_status,
|
|
1417
1375
|
get_current_user=get_current_user,
|
|
1418
1376
|
require_auth=REQUIRE_AUTH,
|
|
1419
1377
|
app_version=APP_VERSION,
|
|
1420
1378
|
app_mode=APP_MODE,
|
|
1421
|
-
create_models_router=create_models_router,
|
|
1422
|
-
model_router=router,
|
|
1423
1379
|
require_user=require_user,
|
|
1424
1380
|
load_users=load_users,
|
|
1425
1381
|
get_user_role=get_user_role,
|
|
@@ -1438,7 +1394,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1438
1394
|
engine_model_catalog=ENGINE_MODEL_CATALOG,
|
|
1439
1395
|
model_engine_aliases=MODEL_ENGINE_ALIASES,
|
|
1440
1396
|
cloud_verify_ttl_seconds=CLOUD_VERIFY_TTL_SECONDS,
|
|
1441
|
-
is_public_mode=IS_PUBLIC_MODE,
|
|
1442
1397
|
allow_local_models=ALLOW_LOCAL_MODELS,
|
|
1443
1398
|
)
|
|
1444
1399
|
|
|
@@ -1461,7 +1416,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1461
1416
|
return None
|
|
1462
1417
|
return PLATFORM.allowed_scopes(user)
|
|
1463
1418
|
|
|
1464
|
-
tool_router_context =
|
|
1419
|
+
tool_router_context, interaction_router_context = build_interaction_contexts(
|
|
1465
1420
|
config=CONFIG,
|
|
1466
1421
|
ingestion_pipeline=INGESTION_PIPELINE,
|
|
1467
1422
|
data_dir=DATA_DIR,
|
|
@@ -1485,15 +1440,10 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1485
1440
|
install_mcp=install_mcp,
|
|
1486
1441
|
mcp_public_item=mcp_public_item,
|
|
1487
1442
|
hooks=HOOKS_REGISTRY,
|
|
1488
|
-
)
|
|
1489
|
-
interaction_router_context = InteractionRouterContext(
|
|
1490
1443
|
chat_context=context,
|
|
1491
1444
|
search_service=SEARCH_SERVICE,
|
|
1492
1445
|
allowed_workspaces_for=_allowed_workspaces_for,
|
|
1493
|
-
require_user=require_user,
|
|
1494
1446
|
embedding_info=_embedding_info,
|
|
1495
|
-
tool_context=tool_router_context,
|
|
1496
|
-
hooks=HOOKS_REGISTRY,
|
|
1497
1447
|
agent_registry=AGENT_REGISTRY,
|
|
1498
1448
|
memory_service=MEMORY_SERVICE,
|
|
1499
1449
|
platform=PLATFORM,
|
|
@@ -1510,25 +1460,17 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1510
1460
|
)
|
|
1511
1461
|
|
|
1512
1462
|
from latticeai.api.review_queue import create_review_queue_router
|
|
1463
|
+
run_review_item = build_review_run_now_runner(PLATFORM, HTTPException)
|
|
1513
1464
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
wf_id = (item.get("payload") or {}).get("workflow_id") or (item.get("provenance") or {}).get("workflow_id")
|
|
1517
|
-
if not wf_id:
|
|
1518
|
-
raise HTTPException(status_code=409, detail="review item has no workflow to run")
|
|
1519
|
-
return PLATFORM.run_workflow_by_id(
|
|
1520
|
-
wf_id, user_email, scope, with_agent=False,
|
|
1521
|
-
inputs={"__review_item__": item.get("id")},
|
|
1522
|
-
)
|
|
1523
|
-
|
|
1524
|
-
BRAIN_NETWORK = register_review_and_brain_tail_routers(
|
|
1525
|
-
app,
|
|
1465
|
+
BRAIN_NETWORK = register_tail_runtime_routers(
|
|
1466
|
+
app=app,
|
|
1526
1467
|
create_review_queue_router=create_review_queue_router,
|
|
1468
|
+
register_review_and_brain_tail_routers=register_review_and_brain_tail_routers,
|
|
1527
1469
|
review_queue=REVIEW_QUEUE,
|
|
1528
1470
|
require_user=require_user,
|
|
1529
1471
|
gate_read=PLATFORM.gate_read,
|
|
1530
1472
|
gate_write=PLATFORM.gate_write,
|
|
1531
|
-
run_review_item=
|
|
1473
|
+
run_review_item=run_review_item,
|
|
1532
1474
|
append_audit_event=append_audit_event,
|
|
1533
1475
|
create_browser_router=create_browser_router,
|
|
1534
1476
|
ingestion_pipeline=INGESTION_PIPELINE,
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
WORKSPACE_OS_VERSION = "6.
|
|
22
|
+
WORKSPACE_OS_VERSION = "6.3.1"
|
|
23
23
|
|
|
24
24
|
# Workspace types separate single-user Personal workspaces from shared
|
|
25
25
|
# Organization workspaces. Both keep the same local-first JSON store; the type
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Access-control helper closures for the app factory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_access_runtime(
|
|
9
|
+
*,
|
|
10
|
+
config: Any,
|
|
11
|
+
require_auth: bool,
|
|
12
|
+
http_exception: Any,
|
|
13
|
+
request_type: Any,
|
|
14
|
+
load_users: Callable[[], Dict],
|
|
15
|
+
get_session_email: Callable[[str], Optional[str]],
|
|
16
|
+
user_id_for_email: Callable[[Dict, str], str],
|
|
17
|
+
) -> Dict[str, Any]:
|
|
18
|
+
"""Build user/admin access helpers without changing legacy call signatures."""
|
|
19
|
+
|
|
20
|
+
from latticeai.core.policy import normalize_role, require_capability
|
|
21
|
+
from latticeai.core.users import normalize_email
|
|
22
|
+
|
|
23
|
+
def get_user_role(email: str, users: Optional[Dict] = None) -> str:
|
|
24
|
+
users = users or load_users()
|
|
25
|
+
identity = str(email or "")
|
|
26
|
+
normalized_email = normalize_email(identity)
|
|
27
|
+
user = users.get(normalized_email) or users.get(identity) or next(
|
|
28
|
+
(
|
|
29
|
+
item
|
|
30
|
+
for item in users.values()
|
|
31
|
+
if isinstance(item, dict) and item.get("id") == identity
|
|
32
|
+
),
|
|
33
|
+
{},
|
|
34
|
+
)
|
|
35
|
+
if isinstance(user, dict) and user.get("role"):
|
|
36
|
+
return normalize_role(user["role"])
|
|
37
|
+
admin_emails = {normalize_email(item) for item in config.admin_emails}
|
|
38
|
+
if normalized_email in admin_emails:
|
|
39
|
+
return "admin"
|
|
40
|
+
first_email = next(iter(users), None)
|
|
41
|
+
return "admin" if first_email == normalized_email else "user"
|
|
42
|
+
|
|
43
|
+
def extract_bearer_token(request: request_type) -> Optional[str]:
|
|
44
|
+
auth = request.headers.get("Authorization", "")
|
|
45
|
+
if auth.startswith("Bearer "):
|
|
46
|
+
return auth[7:].strip()
|
|
47
|
+
return request.cookies.get("session_token")
|
|
48
|
+
|
|
49
|
+
def get_current_user(request: request_type) -> Optional[str]:
|
|
50
|
+
token = extract_bearer_token(request)
|
|
51
|
+
if token:
|
|
52
|
+
return get_session_email(token)
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
def require_user(request: request_type) -> str:
|
|
56
|
+
email = get_current_user(request)
|
|
57
|
+
if require_auth and not email:
|
|
58
|
+
raise http_exception(status_code=401, detail="인증이 필요합니다.")
|
|
59
|
+
return email or ""
|
|
60
|
+
|
|
61
|
+
def require_admin(request: request_type) -> tuple[str, Dict]:
|
|
62
|
+
users = load_users()
|
|
63
|
+
if not require_auth:
|
|
64
|
+
return "", users
|
|
65
|
+
token = extract_bearer_token(request)
|
|
66
|
+
if token:
|
|
67
|
+
email = get_session_email(token)
|
|
68
|
+
if email:
|
|
69
|
+
role = get_user_role(email, users)
|
|
70
|
+
try:
|
|
71
|
+
require_capability(role, "admin:users")
|
|
72
|
+
return email, users
|
|
73
|
+
except PermissionError:
|
|
74
|
+
pass
|
|
75
|
+
raise http_exception(status_code=403, detail="관리자 권한이 필요합니다.")
|
|
76
|
+
|
|
77
|
+
def public_user(email: str, user: Dict, users: Dict) -> Dict:
|
|
78
|
+
role = get_user_role(email, users)
|
|
79
|
+
user_id = user.get("id") or user_id_for_email(users, email)
|
|
80
|
+
return {
|
|
81
|
+
"id": user_id,
|
|
82
|
+
"email": email,
|
|
83
|
+
"identity": user_id,
|
|
84
|
+
"name": user.get("name", ""),
|
|
85
|
+
"nickname": user.get("nickname", ""),
|
|
86
|
+
"role": role,
|
|
87
|
+
"disabled": bool(user.get("disabled", False)),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"get_user_role": get_user_role,
|
|
92
|
+
"_extract_bearer_token": extract_bearer_token,
|
|
93
|
+
"get_current_user": get_current_user,
|
|
94
|
+
"require_user": require_user,
|
|
95
|
+
"require_admin": require_admin,
|
|
96
|
+
"public_user": public_user,
|
|
97
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Chat and interaction wiring seam for ``latticeai.app_factory``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from latticeai.services.router_context import InteractionRouterContext, ToolRouterContext
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_chat_agent_runtime_from_context(
|
|
11
|
+
*,
|
|
12
|
+
build_agent_runtime: Any,
|
|
13
|
+
model_router: Any,
|
|
14
|
+
execute_tool: Any,
|
|
15
|
+
recent_chat_context: Any,
|
|
16
|
+
clear_history: Any,
|
|
17
|
+
knowledge_save: Any,
|
|
18
|
+
audit: Any,
|
|
19
|
+
hooks: Any,
|
|
20
|
+
brain_memory: Any,
|
|
21
|
+
) -> Any:
|
|
22
|
+
return build_agent_runtime(
|
|
23
|
+
model_router=model_router,
|
|
24
|
+
execute_tool=execute_tool,
|
|
25
|
+
recent_chat_context=recent_chat_context,
|
|
26
|
+
clear_history=clear_history,
|
|
27
|
+
knowledge_save=knowledge_save,
|
|
28
|
+
audit=audit,
|
|
29
|
+
hooks=hooks,
|
|
30
|
+
brain_memory=brain_memory,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_interaction_contexts(
|
|
35
|
+
*,
|
|
36
|
+
config: Any,
|
|
37
|
+
ingestion_pipeline: Any,
|
|
38
|
+
data_dir: Any,
|
|
39
|
+
static_dir: Any,
|
|
40
|
+
model_router: Any,
|
|
41
|
+
require_user: Any,
|
|
42
|
+
require_admin: Any,
|
|
43
|
+
get_current_user: Any,
|
|
44
|
+
clear_history: Any,
|
|
45
|
+
append_audit_event: Any,
|
|
46
|
+
enforce_rate_limit: Any,
|
|
47
|
+
bytes_match_extension: Any,
|
|
48
|
+
classify_sensitive_message: Any,
|
|
49
|
+
save_to_history: Any,
|
|
50
|
+
enable_graph: bool,
|
|
51
|
+
knowledge_graph: Any,
|
|
52
|
+
require_graph: Any,
|
|
53
|
+
local_kg_watcher: Any,
|
|
54
|
+
load_mcp_installs: Any,
|
|
55
|
+
recommend_mcps: Any,
|
|
56
|
+
install_mcp: Any,
|
|
57
|
+
mcp_public_item: Any,
|
|
58
|
+
hooks: Any,
|
|
59
|
+
chat_context: Any,
|
|
60
|
+
search_service: Any,
|
|
61
|
+
allowed_workspaces_for: Any,
|
|
62
|
+
embedding_info: Any,
|
|
63
|
+
agent_registry: Any,
|
|
64
|
+
memory_service: Any,
|
|
65
|
+
platform: Any,
|
|
66
|
+
) -> tuple[ToolRouterContext, InteractionRouterContext]:
|
|
67
|
+
tool_router_context = ToolRouterContext(
|
|
68
|
+
config=config,
|
|
69
|
+
ingestion_pipeline=ingestion_pipeline,
|
|
70
|
+
data_dir=data_dir,
|
|
71
|
+
static_dir=static_dir,
|
|
72
|
+
model_router=model_router,
|
|
73
|
+
require_user=require_user,
|
|
74
|
+
require_admin=require_admin,
|
|
75
|
+
get_current_user=get_current_user,
|
|
76
|
+
clear_history=clear_history,
|
|
77
|
+
append_audit_event=append_audit_event,
|
|
78
|
+
enforce_rate_limit=enforce_rate_limit,
|
|
79
|
+
bytes_match_extension=bytes_match_extension,
|
|
80
|
+
classify_sensitive_message=classify_sensitive_message,
|
|
81
|
+
save_to_history=save_to_history,
|
|
82
|
+
enable_graph=enable_graph,
|
|
83
|
+
knowledge_graph=knowledge_graph,
|
|
84
|
+
require_graph=require_graph,
|
|
85
|
+
local_kg_watcher=local_kg_watcher,
|
|
86
|
+
load_mcp_installs=load_mcp_installs,
|
|
87
|
+
recommend_mcps=recommend_mcps,
|
|
88
|
+
install_mcp=install_mcp,
|
|
89
|
+
mcp_public_item=mcp_public_item,
|
|
90
|
+
hooks=hooks,
|
|
91
|
+
)
|
|
92
|
+
interaction_router_context = InteractionRouterContext(
|
|
93
|
+
chat_context=chat_context,
|
|
94
|
+
search_service=search_service,
|
|
95
|
+
allowed_workspaces_for=allowed_workspaces_for,
|
|
96
|
+
require_user=require_user,
|
|
97
|
+
embedding_info=embedding_info,
|
|
98
|
+
tool_context=tool_router_context,
|
|
99
|
+
hooks=hooks,
|
|
100
|
+
agent_registry=agent_registry,
|
|
101
|
+
memory_service=memory_service,
|
|
102
|
+
platform=platform,
|
|
103
|
+
)
|
|
104
|
+
return tool_router_context, interaction_router_context
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def maybe_build_telegram_chat_mirror(
|
|
108
|
+
*,
|
|
109
|
+
enable_telegram: bool,
|
|
110
|
+
spawn: Any,
|
|
111
|
+
) -> Optional[Any]:
|
|
112
|
+
if not enable_telegram:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def telegram_chat_mirror(role: str, text: str, source: Optional[str] = None) -> None:
|
|
116
|
+
from latticeai.integrations.telegram_bot import broadcast_web_chat
|
|
117
|
+
spawn(broadcast_web_chat(role, text), name="telegram_broadcast")
|
|
118
|
+
|
|
119
|
+
return telegram_chat_mirror
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Model/runtime wiring seam for ``latticeai.app_factory``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from latticeai.runtime.platform_services_runtime import build_model_service
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def configure_model_runtime_from_context(**kwargs: Any) -> None:
|
|
11
|
+
configure_model_runtime = kwargs.pop("configure_model_runtime")
|
|
12
|
+
configure_model_runtime(**kwargs)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_model_runtime_routers(
|
|
16
|
+
*,
|
|
17
|
+
app: Any,
|
|
18
|
+
create_health_router: Any,
|
|
19
|
+
create_models_router: Any,
|
|
20
|
+
register_health_and_model_routers: Any,
|
|
21
|
+
model_router: Any,
|
|
22
|
+
runtime_features: Any,
|
|
23
|
+
is_public_mode: bool,
|
|
24
|
+
**kwargs: Any,
|
|
25
|
+
) -> Any:
|
|
26
|
+
model_service = build_model_service(
|
|
27
|
+
model_router=model_router,
|
|
28
|
+
runtime_features=runtime_features,
|
|
29
|
+
is_public=is_public_mode,
|
|
30
|
+
)
|
|
31
|
+
register_health_and_model_routers(
|
|
32
|
+
app,
|
|
33
|
+
create_health_router=create_health_router,
|
|
34
|
+
model_service=model_service,
|
|
35
|
+
create_models_router=create_models_router,
|
|
36
|
+
model_router=model_router,
|
|
37
|
+
is_public_mode=is_public_mode,
|
|
38
|
+
**kwargs,
|
|
39
|
+
)
|
|
40
|
+
return model_service
|
|
@@ -9,9 +9,6 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from typing import Any, Callable, Dict
|
|
11
11
|
|
|
12
|
-
from latticeai.runtime.automation_runtime import build_automation_runtime
|
|
13
|
-
from latticeai.services.platform_runtime import PlatformRuntime
|
|
14
|
-
|
|
15
12
|
|
|
16
13
|
def build_platform_automation_runtime(
|
|
17
14
|
*,
|
|
@@ -34,6 +31,8 @@ def build_platform_automation_runtime(
|
|
|
34
31
|
``app_factory._build`` so ``dict(locals())`` continues to expose the legacy
|
|
35
32
|
``server_app`` compatibility surface.
|
|
36
33
|
"""
|
|
34
|
+
from latticeai.runtime.automation_runtime import build_automation_runtime
|
|
35
|
+
from latticeai.services.platform_runtime import PlatformRuntime
|
|
37
36
|
|
|
38
37
|
def _llm_generate_sync(
|
|
39
38
|
message: str,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Review Center runtime wiring helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_review_run_now_runner(platform: Any, http_exception: type[Exception]) -> Callable[..., Any]:
|
|
9
|
+
"""Build the Review Center run-now runner used by the API router.
|
|
10
|
+
|
|
11
|
+
The runner preserves the public contract: "Run now" previews/regenerates the
|
|
12
|
+
source workflow, records a fresh run id, and leaves approval status unchanged.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def run_review_item(
|
|
16
|
+
item: Dict[str, Any],
|
|
17
|
+
*,
|
|
18
|
+
user_email: Optional[str],
|
|
19
|
+
scope: Optional[str],
|
|
20
|
+
) -> Any:
|
|
21
|
+
payload = item.get("payload") or {}
|
|
22
|
+
provenance = item.get("provenance") or {}
|
|
23
|
+
workflow_id = payload.get("workflow_id") or provenance.get("workflow_id")
|
|
24
|
+
if not workflow_id:
|
|
25
|
+
raise http_exception(status_code=409, detail="review item has no workflow to run")
|
|
26
|
+
return platform.run_workflow_by_id(
|
|
27
|
+
workflow_id,
|
|
28
|
+
user_email,
|
|
29
|
+
scope,
|
|
30
|
+
with_agent=False,
|
|
31
|
+
inputs={"__review_item__": item.get("id")},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return run_review_item
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = ["build_review_run_now_runner"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Review, browser, portability, network, garden, and setup tail wiring."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def register_tail_runtime_routers(
|
|
9
|
+
*,
|
|
10
|
+
app: Any,
|
|
11
|
+
create_review_queue_router: Any,
|
|
12
|
+
register_review_and_brain_tail_routers: Any,
|
|
13
|
+
build_brain_network: Any,
|
|
14
|
+
**kwargs: Any,
|
|
15
|
+
) -> Any:
|
|
16
|
+
return register_review_and_brain_tail_routers(
|
|
17
|
+
app,
|
|
18
|
+
create_review_queue_router=create_review_queue_router,
|
|
19
|
+
build_brain_network=build_brain_network,
|
|
20
|
+
**kwargs,
|
|
21
|
+
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.1",
|
|
4
4
|
"description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build:assets": "vite build && node scripts/build_frontend_assets.mjs",
|
|
24
24
|
"build:python": "node scripts/run_python.mjs -m build",
|
|
25
25
|
"check:python": "node scripts/run_python.mjs scripts/check_python.py",
|
|
26
|
-
"lint": "node --check tests/visual/mock_server.cjs && node --check tests/visual/v3.spec.js && npm run lint:frontend",
|
|
26
|
+
"lint": "node --check tests/visual/mock_server.cjs && node --check tests/visual/v3.spec.js && npm run lint:frontend && node scripts/check_i18n_literals.mjs",
|
|
27
27
|
"lint:frontend": "node scripts/lint_frontend.mjs",
|
|
28
28
|
"docs:check-links": "node scripts/check_markdown_links.mjs",
|
|
29
29
|
"typecheck": "npm run typecheck:frontend && cd vscode-extension && npm run build",
|