ltcai 5.6.0 → 6.1.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.
Files changed (55) hide show
  1. package/README.md +45 -25
  2. package/docs/CHANGELOG.md +74 -0
  3. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +1 -1
  4. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +1 -1
  5. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +1 -1
  6. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +1 -1
  7. package/frontend/openapi.json +39 -0
  8. package/frontend/src/App.tsx +5 -0
  9. package/frontend/src/api/client.ts +104 -23
  10. package/frontend/src/api/openapi.ts +48 -0
  11. package/frontend/src/components/FirstRunGuide.tsx +3 -3
  12. package/frontend/src/components/ProductFlow.tsx +7 -0
  13. package/frontend/src/features/review/ReviewCard.tsx +96 -0
  14. package/frontend/src/features/review/ReviewInbox.tsx +112 -0
  15. package/frontend/src/features/review/reviewHelpers.ts +69 -0
  16. package/frontend/src/i18n.ts +18 -8
  17. package/frontend/src/pages/Act.tsx +5 -177
  18. package/frontend/src/routes.ts +1 -0
  19. package/frontend/src/styles.css +20 -0
  20. package/lattice_brain/__init__.py +1 -1
  21. package/lattice_brain/runtime/multi_agent.py +1 -1
  22. package/latticeai/__init__.py +1 -1
  23. package/latticeai/api/chat.py +52 -33
  24. package/latticeai/api/review_queue.py +7 -3
  25. package/latticeai/app_factory.py +253 -475
  26. package/latticeai/cli/__init__.py +1 -0
  27. package/latticeai/cli/runtime.py +37 -0
  28. package/latticeai/core/marketplace.py +1 -1
  29. package/latticeai/core/workspace_os.py +1 -1
  30. package/latticeai/runtime/app_context_runtime.py +13 -0
  31. package/latticeai/runtime/automation_runtime.py +64 -0
  32. package/latticeai/runtime/bootstrap.py +48 -0
  33. package/latticeai/runtime/context_runtime.py +43 -0
  34. package/latticeai/runtime/hooks_runtime.py +77 -0
  35. package/latticeai/runtime/lifespan_runtime.py +138 -0
  36. package/latticeai/runtime/persistence_runtime.py +87 -0
  37. package/latticeai/runtime/platform_services_runtime.py +39 -0
  38. package/latticeai/runtime/router_registration.py +570 -0
  39. package/latticeai/runtime/web_runtime.py +65 -0
  40. package/latticeai/services/app_context.py +1 -0
  41. package/latticeai/services/review_queue.py +20 -4
  42. package/latticeai/services/tool_dispatch.py +82 -25
  43. package/ltcai_cli.py +5 -31
  44. package/package.json +1 -1
  45. package/src-tauri/Cargo.lock +1 -1
  46. package/src-tauri/Cargo.toml +1 -1
  47. package/src-tauri/tauri.conf.json +1 -1
  48. package/static/app/asset-manifest.json +5 -5
  49. package/static/app/assets/{index-xRn29gI8.css → index-B744yblP.css} +1 -1
  50. package/static/app/assets/index-DYaUKNfl.js +16 -0
  51. package/static/app/assets/index-DYaUKNfl.js.map +1 -0
  52. package/static/app/index.html +2 -2
  53. package/telegram_bot.py +3 -9
  54. package/static/app/assets/index-xMFu94cX.js +0 -16
  55. package/static/app/assets/index-xMFu94cX.js.map +0 -1
@@ -6,8 +6,9 @@ tool-response shaping are owned outside ``server_app``.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ from dataclasses import dataclass, field
9
10
  from pathlib import Path
10
- from typing import Any, Callable, Dict, Optional
11
+ from typing import Any, Callable, Dict, Mapping, Optional
11
12
 
12
13
  from fastapi import HTTPException
13
14
 
@@ -30,9 +31,6 @@ def _default_get_user_role(_email, _users=None) -> str:
30
31
  return "user"
31
32
 
32
33
 
33
- _load_users: Callable[[], Dict[str, Any]] = _default_load_users
34
- _get_user_role: Callable[..., str] = _default_get_user_role
35
-
36
34
  FILE_CREATE_ACTIONS = set(DEFAULT_TOOL_REGISTRY.file_create_actions)
37
35
  TOOL_GOVERNANCE: Dict[str, ToolPolicy] = dict(DEFAULT_TOOL_REGISTRY.governance)
38
36
  TOOL_GOVERNANCE_DEFAULT: ToolPolicy = DEFAULT_TOOL_REGISTRY.default_policy
@@ -41,41 +39,100 @@ LOCAL_WRITE_BLOCKED_PREFIXES = DEFAULT_TOOL_REGISTRY.local_write_blocked_prefixe
41
39
  RISK_LEVEL_MAP = DEFAULT_TOOL_REGISTRY.risk_level_map
42
40
 
43
41
 
42
+ @dataclass
43
+ class ToolDispatchService:
44
+ """Runtime-facing tool policy and authorization boundary.
45
+
46
+ ``ToolRegistry`` owns the catalog and governance facts; this service owns
47
+ request/runtime authorization callbacks that must be configured during app
48
+ construction. Keeping those callbacks on an instance gives future runtime
49
+ assembly code an injectable seam while the module-level functions below
50
+ preserve the historical ``server_app`` surface.
51
+ """
52
+
53
+ registry: Any = field(default_factory=lambda: DEFAULT_TOOL_REGISTRY)
54
+ load_users: Callable[[], Dict[str, Any]] = field(default=_default_load_users)
55
+ get_user_role: Callable[..., str] = field(default=_default_get_user_role)
56
+
57
+ @property
58
+ def file_create_actions(self) -> frozenset[str]:
59
+ return self.registry.file_create_actions
60
+
61
+ @property
62
+ def tool_governance(self) -> Mapping[str, ToolPolicy]:
63
+ return self.registry.governance
64
+
65
+ @property
66
+ def risk_level_map(self) -> Mapping[str, str]:
67
+ return self.registry.risk_level_map
68
+
69
+ def configure(
70
+ self,
71
+ *,
72
+ load_users: Callable[[], Dict[str, Any]],
73
+ get_user_role: Callable[..., str],
74
+ ) -> None:
75
+ self.load_users = load_users
76
+ self.get_user_role = get_user_role
77
+
78
+ def policy_for(self, action_name: str, args: dict) -> ToolPolicy:
79
+ return self.registry.policy_for(action_name, args)
80
+
81
+ def risk_level(self, action_name: str, args: dict) -> str:
82
+ return self.registry.risk_level(action_name, args)
83
+
84
+ def risk_level_for_policy(self, policy: ToolPolicy) -> str:
85
+ return self.risk_level_map.get(policy["risk"], "medium")
86
+
87
+ def permission(self, name: str, args: Optional[dict] = None) -> ToolPermission:
88
+ return self.registry.permission(name, args or {})
89
+
90
+ def permissions(self) -> list[ToolPermission]:
91
+ return self.registry.permissions()
92
+
93
+ def check_role(self, tool_name: str, current_user: str) -> None:
94
+ if tool_name not in self.registry.admin_only_tools:
95
+ return
96
+ users = self.load_users()
97
+ if self.get_user_role(current_user, users) != "admin":
98
+ raise HTTPException(
99
+ status_code=403,
100
+ detail=f"'{tool_name}' 툴은 관리자 전용입니다.",
101
+ )
102
+
103
+
104
+ DEFAULT_TOOL_DISPATCH_SERVICE = ToolDispatchService()
105
+
106
+
44
107
  def configure_tool_dispatch(
45
108
  *,
46
109
  load_users: Callable[[], Dict[str, Any]],
47
110
  get_user_role: Callable[..., str],
48
111
  ) -> None:
49
- global _load_users, _get_user_role
50
- _load_users = load_users
51
- _get_user_role = get_user_role
112
+ DEFAULT_TOOL_DISPATCH_SERVICE.configure(
113
+ load_users=load_users,
114
+ get_user_role=get_user_role,
115
+ )
52
116
 
53
117
 
54
118
  def agent_policy(action_name: str, args: dict) -> ToolPolicy:
55
- return DEFAULT_TOOL_REGISTRY.policy_for(action_name, args)
119
+ return DEFAULT_TOOL_DISPATCH_SERVICE.policy_for(action_name, args)
56
120
 
57
121
 
58
122
  def agent_risk(action_name: str, args: dict) -> str:
59
- return DEFAULT_TOOL_REGISTRY.risk_level(action_name, args)
123
+ return DEFAULT_TOOL_DISPATCH_SERVICE.risk_level(action_name, args)
60
124
 
61
125
 
62
126
  def get_tool_permission(name: str, args: Optional[dict] = None) -> ToolPermission:
63
- return DEFAULT_TOOL_REGISTRY.permission(name, args or {})
127
+ return DEFAULT_TOOL_DISPATCH_SERVICE.permission(name, args or {})
64
128
 
65
129
 
66
130
  def list_tool_permissions() -> list:
67
- return DEFAULT_TOOL_REGISTRY.permissions()
131
+ return DEFAULT_TOOL_DISPATCH_SERVICE.permissions()
68
132
 
69
133
 
70
134
  def check_tool_role(tool_name: str, current_user: str) -> None:
71
- if tool_name not in ADMIN_ONLY_TOOLS:
72
- return
73
- users = _load_users()
74
- if _get_user_role(current_user, users) != "admin":
75
- raise HTTPException(
76
- status_code=403,
77
- detail=f"'{tool_name}' 툴은 관리자 전용입니다.",
78
- )
135
+ DEFAULT_TOOL_DISPATCH_SERVICE.check_role(tool_name, current_user)
79
136
 
80
137
 
81
138
  def collect_created_files(transcript: list) -> list:
@@ -113,17 +170,18 @@ def build_agent_runtime(
113
170
  audit: Callable[..., None],
114
171
  hooks: Any = None,
115
172
  brain_memory: Any = None,
173
+ dispatch_service: ToolDispatchService = DEFAULT_TOOL_DISPATCH_SERVICE,
116
174
  ) -> AgentRuntime:
117
175
  ensure_agent_root()
118
176
  deps = AgentDeps(
119
177
  generate_as=model_router.generate_as,
120
178
  generate=model_router.generate,
121
179
  execute_tool=execute_tool,
122
- policy_for=agent_policy,
123
- risk_level=lambda policy: RISK_LEVEL_MAP.get(policy["risk"], "medium"),
124
- check_role=check_tool_role,
125
- tool_governance=TOOL_GOVERNANCE,
126
- file_create_actions=frozenset(FILE_CREATE_ACTIONS),
180
+ policy_for=dispatch_service.policy_for,
181
+ risk_level=dispatch_service.risk_level_for_policy,
182
+ check_role=dispatch_service.check_role,
183
+ tool_governance=dispatch_service.tool_governance,
184
+ file_create_actions=dispatch_service.file_create_actions,
127
185
  recent_chat_context=recent_chat_context,
128
186
  clear_history=clear_history,
129
187
  knowledge_save=knowledge_save,
@@ -144,4 +202,3 @@ def tool_response(fn, *args):
144
202
  return {"status": "ok", "workspace": str(AGENT_ROOT), "result": fn(*args)}
145
203
  except ToolError as exc:
146
204
  raise HTTPException(status_code=400, detail=str(exc))
147
-
package/ltcai_cli.py CHANGED
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
- import importlib.util
7
6
  import os
8
7
  import platform
9
8
  import re
@@ -17,36 +16,11 @@ import time
17
16
  import urllib.request
18
17
  from pathlib import Path
19
18
 
20
-
21
- def _load_env_file(path: Path) -> None:
22
- if not path.exists():
23
- return
24
- for raw_line in path.read_text(encoding="utf-8").splitlines():
25
- line = raw_line.strip()
26
- if not line or line.startswith("#") or "=" not in line:
27
- continue
28
- key, value = line.split("=", 1)
29
- key = key.strip()
30
- value = value.strip().strip('"').strip("'")
31
- if key and key not in os.environ:
32
- os.environ[key] = value
33
-
34
-
35
- def _apply_extra_path() -> None:
36
- extra = os.getenv("LATTICEAI_EXTRA_PATH", "")
37
- if not extra:
38
- return
39
- current = [p for p in os.environ.get("PATH", "").split(os.pathsep) if p]
40
- for item in reversed([p for p in extra.split(os.pathsep) if p]):
41
- expanded = str(Path(item).expanduser())
42
- if Path(expanded).exists() and expanded not in current:
43
- current.insert(0, expanded)
44
- os.environ["PATH"] = os.pathsep.join(current)
45
-
46
-
47
- def _has_module(name: str) -> bool:
48
- return importlib.util.find_spec(name) is not None
49
-
19
+ from latticeai.cli.runtime import (
20
+ _apply_extra_path,
21
+ _has_module,
22
+ _load_env_file,
23
+ )
50
24
 
51
25
  def _local_ips() -> list[str]:
52
26
  ips: list[str] = []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "5.6.0",
3
+ "version": "6.1.0",
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": {
@@ -1654,7 +1654,7 @@ dependencies = [
1654
1654
 
1655
1655
  [[package]]
1656
1656
  name = "lattice-ai-desktop"
1657
- version = "5.6.0"
1657
+ version = "6.1.0"
1658
1658
  dependencies = [
1659
1659
  "plist",
1660
1660
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lattice-ai-desktop"
3
- version = "5.6.0"
3
+ version = "6.1.0"
4
4
  description = "Lattice AI Digital Brain desktop shell"
5
5
  authors = ["TaeSoo Park"]
6
6
  edition = "2021"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schema.tauri.app/config/2",
3
3
  "productName": "Lattice AI",
4
- "version": "5.6.0",
4
+ "version": "6.1.0",
5
5
  "identifier": "ai.lattice.desktop",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run frontend:dev",
@@ -1,13 +1,13 @@
1
1
  {
2
- "version": "5.6.0",
2
+ "version": "6.1.0",
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-xMFu94cX.js",
10
- "assets/index-xRn29gI8.css": "/static/app/assets/index-xRn29gI8.css"
9
+ "index.html": "/static/app/assets/index-DYaUKNfl.js",
10
+ "assets/index-B744yblP.css": "/static/app/assets/index-B744yblP.css"
11
11
  },
12
12
  "vite": {
13
13
  "../node_modules/@tauri-apps/api/core.js": {
@@ -17,7 +17,7 @@
17
17
  "isDynamicEntry": true
18
18
  },
19
19
  "index.html": {
20
- "file": "assets/index-xMFu94cX.js",
20
+ "file": "assets/index-DYaUKNfl.js",
21
21
  "name": "index",
22
22
  "src": "index.html",
23
23
  "isEntry": true,
@@ -25,7 +25,7 @@
25
25
  "../node_modules/@tauri-apps/api/core.js"
26
26
  ],
27
27
  "css": [
28
- "assets/index-xRn29gI8.css"
28
+ "assets/index-B744yblP.css"
29
29
  ]
30
30
  }
31
31
  }