ltcai 6.3.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 +24 -15
- package/docs/CHANGELOG.md +24 -0
- package/frontend/src/features/review/ReviewCard.tsx +12 -10
- package/frontend/src/features/review/ReviewInbox.tsx +15 -10
- package/frontend/src/i18n.ts +116 -0
- package/frontend/src/pages/Capture.tsx +48 -41
- 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 +17 -67
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/runtime/access_runtime.py +97 -0
- package/package.json +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 +3 -3
- package/static/app/assets/{index-D76dWuQk.js → index-t1jx1BR9.js} +2 -2
- package/static/app/assets/index-t1jx1BR9.js.map +1 -0
- package/static/app/index.html +1 -1
- package/static/app/assets/index-D76dWuQk.js.map +0 -1
|
@@ -19,7 +19,7 @@ from datetime import datetime
|
|
|
19
19
|
from typing import Any, Callable, Dict, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
MULTI_AGENT_VERSION = "6.3.
|
|
22
|
+
MULTI_AGENT_VERSION = "6.3.1"
|
|
23
23
|
|
|
24
24
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
25
25
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
package/latticeai/app_factory.py
CHANGED
|
@@ -18,6 +18,7 @@ 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
|
|
23
24
|
from latticeai.runtime.chat_wiring import (
|
|
@@ -122,12 +123,11 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
122
123
|
from latticeai.core.enterprise import (
|
|
123
124
|
capability_registry,
|
|
124
125
|
)
|
|
125
|
-
from latticeai.core.policy import
|
|
126
|
+
from latticeai.core.policy import policy_matrix
|
|
126
127
|
from latticeai.core.users import (
|
|
127
128
|
ensure_user_identity,
|
|
128
129
|
load_users_file,
|
|
129
130
|
migrate_knowledge_graph_identity,
|
|
130
|
-
normalize_email,
|
|
131
131
|
save_users_file,
|
|
132
132
|
user_id_for_email as _user_id_for_email,
|
|
133
133
|
)
|
|
@@ -813,42 +813,21 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
813
813
|
def clear_conversation(conversation_id: str, started_at: Optional[str] = None) -> Dict:
|
|
814
814
|
return CONVERSATIONS.clear_conversation(conversation_id, started_at=started_at)
|
|
815
815
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
return "admin"
|
|
832
|
-
first_email = next(iter(users), None)
|
|
833
|
-
return "admin" if first_email == normalized_email else "user"
|
|
834
|
-
|
|
835
|
-
def _extract_bearer_token(request: Request) -> Optional[str]:
|
|
836
|
-
auth = request.headers.get("Authorization", "")
|
|
837
|
-
if auth.startswith("Bearer "):
|
|
838
|
-
return auth[7:].strip()
|
|
839
|
-
return request.cookies.get("session_token")
|
|
840
|
-
|
|
841
|
-
def get_current_user(request: Request) -> Optional[str]:
|
|
842
|
-
token = _extract_bearer_token(request)
|
|
843
|
-
if token:
|
|
844
|
-
return get_session_email(token)
|
|
845
|
-
return None
|
|
846
|
-
|
|
847
|
-
def require_user(request: Request) -> str:
|
|
848
|
-
email = get_current_user(request)
|
|
849
|
-
if REQUIRE_AUTH and not email:
|
|
850
|
-
raise HTTPException(status_code=401, detail="인증이 필요합니다.")
|
|
851
|
-
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"]
|
|
852
831
|
|
|
853
832
|
|
|
854
833
|
# ── Rate limiting & file validation — delegated to latticeai.core.security ────
|
|
@@ -917,35 +896,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
917
896
|
raise HTTPException(status_code=403, detail="승인된 파일 내용과 요청 내용이 다릅니다.")
|
|
918
897
|
|
|
919
898
|
|
|
920
|
-
def require_admin(request: Request) -> tuple[str, Dict]:
|
|
921
|
-
users = load_users()
|
|
922
|
-
if not REQUIRE_AUTH:
|
|
923
|
-
return "", users
|
|
924
|
-
token = _extract_bearer_token(request)
|
|
925
|
-
if token:
|
|
926
|
-
email = get_session_email(token)
|
|
927
|
-
if email:
|
|
928
|
-
role = get_user_role(email, users)
|
|
929
|
-
try:
|
|
930
|
-
require_capability(role, "admin:users")
|
|
931
|
-
return email, users
|
|
932
|
-
except PermissionError:
|
|
933
|
-
pass
|
|
934
|
-
raise HTTPException(status_code=403, detail="관리자 권한이 필요합니다.")
|
|
935
|
-
|
|
936
|
-
def public_user(email: str, user: Dict, users: Dict) -> Dict:
|
|
937
|
-
role = get_user_role(email, users)
|
|
938
|
-
user_id = user.get("id") or _user_id_for_email(users, email)
|
|
939
|
-
return {
|
|
940
|
-
"id": user_id,
|
|
941
|
-
"email": email,
|
|
942
|
-
"identity": user_id,
|
|
943
|
-
"name": user.get("name", ""),
|
|
944
|
-
"nickname": user.get("nickname", ""),
|
|
945
|
-
"role": role,
|
|
946
|
-
"disabled": bool(user.get("disabled", False)),
|
|
947
|
-
}
|
|
948
|
-
|
|
949
899
|
def get_history_user(email: Optional[str], nickname: Optional[str] = None) -> Dict:
|
|
950
900
|
if not email:
|
|
951
901
|
return {"user_email": None, "user_nickname": nickname or None}
|
|
@@ -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.3.
|
|
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
|
+
}
|
package/package.json
CHANGED
package/src-tauri/Cargo.lock
CHANGED
package/src-tauri/Cargo.toml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "6.3.
|
|
2
|
+
"version": "6.3.1",
|
|
3
3
|
"generated_at": "vite",
|
|
4
4
|
"entrypoints": {
|
|
5
5
|
"app": "/static/app/index.html"
|
|
6
6
|
},
|
|
7
7
|
"assets": {
|
|
8
8
|
"../node_modules/@tauri-apps/api/core.js": "/static/app/assets/core-CwxXejkd.js",
|
|
9
|
-
"index.html": "/static/app/assets/index-
|
|
9
|
+
"index.html": "/static/app/assets/index-t1jx1BR9.js",
|
|
10
10
|
"assets/index-Div5vMlq.css": "/static/app/assets/index-Div5vMlq.css"
|
|
11
11
|
},
|
|
12
12
|
"vite": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"isDynamicEntry": true
|
|
18
18
|
},
|
|
19
19
|
"index.html": {
|
|
20
|
-
"file": "assets/index-
|
|
20
|
+
"file": "assets/index-t1jx1BR9.js",
|
|
21
21
|
"name": "index",
|
|
22
22
|
"src": "index.html",
|
|
23
23
|
"isEntry": true,
|