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.
Files changed (51) hide show
  1. package/README.md +29 -17
  2. package/docs/CHANGELOG.md +66 -0
  3. package/frontend/src/components/onboarding/AnalysisScreen.tsx +127 -0
  4. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +29 -0
  5. package/frontend/src/components/onboarding/InstallScreen.tsx +208 -0
  6. package/frontend/src/components/onboarding/LanguageChooser.tsx +23 -0
  7. package/frontend/src/components/onboarding/LoginScreen.tsx +131 -0
  8. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +13 -688
  9. package/frontend/src/components/onboarding/RecommendationScreen.tsx +59 -0
  10. package/frontend/src/components/onboarding/recommendationModel.ts +132 -0
  11. package/frontend/src/features/admin/AdminConsole.tsx +1 -1
  12. package/frontend/src/features/brain/BrainCarePanel.tsx +254 -0
  13. package/frontend/src/features/brain/BrainComposer.tsx +66 -0
  14. package/frontend/src/features/brain/BrainConversation.tsx +131 -0
  15. package/frontend/src/features/brain/BrainGraphLayer.tsx +132 -0
  16. package/frontend/src/features/brain/BrainHome.tsx +28 -795
  17. package/frontend/src/features/brain/BrainMemoryLayer.tsx +38 -0
  18. package/frontend/src/features/brain/BrainOverviewPanel.tsx +74 -0
  19. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +43 -0
  20. package/frontend/src/features/brain/DepthEmergence.tsx +53 -0
  21. package/frontend/src/features/brain/types.ts +6 -6
  22. package/frontend/src/features/review/ReviewCard.tsx +14 -10
  23. package/frontend/src/features/review/ReviewInbox.tsx +24 -9
  24. package/frontend/src/i18n.ts +208 -0
  25. package/frontend/src/pages/Brain.tsx +63 -5
  26. package/frontend/src/pages/Capture.tsx +139 -43
  27. package/frontend/src/pages/Library.tsx +72 -6
  28. package/lattice_brain/__init__.py +1 -1
  29. package/lattice_brain/runtime/multi_agent.py +1 -1
  30. package/latticeai/__init__.py +1 -1
  31. package/latticeai/app_factory.py +48 -106
  32. package/latticeai/core/marketplace.py +1 -1
  33. package/latticeai/core/workspace_os.py +1 -1
  34. package/latticeai/runtime/access_runtime.py +97 -0
  35. package/latticeai/runtime/chat_wiring.py +119 -0
  36. package/latticeai/runtime/model_wiring.py +40 -0
  37. package/latticeai/runtime/platform_runtime_wiring.py +2 -3
  38. package/latticeai/runtime/review_wiring.py +37 -0
  39. package/latticeai/runtime/tail_wiring.py +21 -0
  40. package/package.json +2 -2
  41. package/scripts/check_i18n_literals.mjs +52 -0
  42. package/src-tauri/Cargo.lock +1 -1
  43. package/src-tauri/Cargo.toml +1 -1
  44. package/src-tauri/tauri.conf.json +1 -1
  45. package/static/app/asset-manifest.json +5 -5
  46. package/static/app/assets/index-Div5vMlq.css +2 -0
  47. package/static/app/assets/{index-D91Rz5--.js → index-t1jx1BR9.js} +2 -2
  48. package/static/app/assets/index-t1jx1BR9.js.map +1 -0
  49. package/static/app/index.html +2 -2
  50. package/static/app/assets/index-B2-1Gm0q.css +0 -2
  51. package/static/app/assets/index-D91Rz5--.js.map +0 -1
@@ -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 normalize_role, policy_matrix, require_capability
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
- def get_user_role(email: str, users: Optional[Dict] = None) -> str:
808
- users = users or load_users()
809
- identity = str(email or "")
810
- normalized_email = normalize_email(identity)
811
- user = users.get(normalized_email) or users.get(identity) or next(
812
- (
813
- item for item in users.values()
814
- if isinstance(item, dict) and item.get("id") == identity
815
- ),
816
- {},
817
- )
818
- if isinstance(user, dict) and user.get("role"):
819
- return normalize_role(user["role"])
820
- admin_emails = {normalize_email(item) for item in CONFIG.admin_emails}
821
- if normalized_email in admin_emails:
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
- configure_model_runtime(
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 = None
1240
- if ENABLE_TELEGRAM:
1241
- def _telegram_chat_mirror(role: str, text: str, source: Optional[str] = None) -> None:
1242
- from latticeai.integrations.telegram_bot import broadcast_web_chat
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 = build_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 = build_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
- is_public=IS_PUBLIC_MODE,
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 = ToolRouterContext(
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
- def _run_review_item(item, *, user_email, scope):
1515
- """run_now: re-execute the suggestion's source workflow (preview/regenerate)."""
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=_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,
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "6.2.0"
14
+ MARKETPLACE_VERSION = "6.3.1"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -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.2.0"
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.2.0",
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",