ltcai 4.0.0 → 4.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.
- package/README.md +42 -33
- package/desktop/electron/main.cjs +44 -0
- package/docs/CHANGELOG.md +106 -0
- package/docs/REALTIME_COLLABORATION.md +3 -3
- package/docs/V3_FRONTEND.md +9 -8
- package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
- package/docs/V4_1_VALIDATION_REPORT.md +47 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +95 -45
- package/docs/kg-schema.md +6 -2
- package/docs/spec-vs-impl.md +10 -10
- package/frontend/index.html +24 -0
- package/frontend/openapi.json +14190 -0
- package/frontend/src/App.tsx +184 -0
- package/frontend/src/api/client.ts +317 -0
- package/frontend/src/api/openapi.ts +16637 -0
- package/frontend/src/components/primitives.tsx +204 -0
- package/frontend/src/components/ui/badge.tsx +27 -0
- package/frontend/src/components/ui/button.tsx +37 -0
- package/frontend/src/components/ui/card.tsx +22 -0
- package/frontend/src/components/ui/input.tsx +16 -0
- package/frontend/src/components/ui/textarea.tsx +16 -0
- package/frontend/src/lib/utils.ts +33 -0
- package/frontend/src/main.tsx +23 -0
- package/frontend/src/pages/Act.tsx +245 -0
- package/frontend/src/pages/Ask.tsx +200 -0
- package/frontend/src/pages/Brain.tsx +267 -0
- package/frontend/src/pages/Capture.tsx +158 -0
- package/frontend/src/pages/Library.tsx +187 -0
- package/frontend/src/pages/System.tsx +344 -0
- package/frontend/src/routes.ts +85 -0
- package/frontend/src/store/appStore.ts +54 -0
- package/frontend/src/styles.css +107 -0
- package/kg_schema.py +2 -603
- package/knowledge_graph.py +37 -4958
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +15 -16
- package/latticeai/api/agents.py +13 -6
- package/latticeai/api/auth.py +19 -11
- package/latticeai/api/invitations.py +100 -0
- package/latticeai/api/knowledge_graph.py +4 -11
- package/latticeai/api/plugins.py +3 -6
- package/latticeai/api/realtime.py +4 -7
- package/latticeai/api/setup.py +5 -4
- package/latticeai/api/static_routes.py +13 -16
- package/latticeai/api/ui_redirects.py +26 -0
- package/latticeai/api/workflow_designer.py +39 -6
- package/latticeai/api/workspace.py +24 -10
- package/latticeai/app_factory.py +88 -17
- package/latticeai/brain/_kg_common.py +1123 -0
- package/latticeai/brain/discovery.py +1455 -0
- package/latticeai/brain/documents.py +218 -0
- package/latticeai/brain/ingest.py +644 -0
- package/latticeai/brain/projection.py +561 -0
- package/latticeai/brain/provenance.py +401 -0
- package/latticeai/brain/retrieval.py +1316 -0
- package/latticeai/brain/schema.py +640 -0
- package/latticeai/brain/store.py +216 -0
- package/latticeai/brain/write_master.py +225 -0
- package/latticeai/core/invitations.py +131 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/policy.py +54 -0
- package/latticeai/core/realtime.py +65 -44
- package/latticeai/core/sessions.py +31 -5
- package/latticeai/core/users.py +147 -0
- package/latticeai/core/workspace_os.py +420 -20
- package/latticeai/services/agent_runtime.py +242 -4
- package/latticeai/services/run_executor.py +328 -0
- package/latticeai/services/workspace_service.py +27 -19
- package/package.json +54 -27
- package/scripts/build_frontend_assets.mjs +38 -0
- package/scripts/bump_version.py +1 -1
- package/scripts/export_openapi.py +31 -0
- package/scripts/lint_frontend.mjs +86 -0
- package/scripts/run_python.mjs +47 -0
- package/src-tauri/Cargo.lock +4833 -0
- package/src-tauri/Cargo.toml +19 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +7 -0
- package/src-tauri/src/main.rs +78 -0
- package/src-tauri/tauri.conf.json +36 -0
- package/static/app/asset-manifest.json +32 -0
- package/static/app/assets/core-CwxXejkd.js +2 -0
- package/static/app/assets/core-CwxXejkd.js.map +1 -0
- package/static/app/assets/index-CJRAzNnf.js +333 -0
- package/static/app/assets/index-CJRAzNnf.js.map +1 -0
- package/static/app/assets/index-CSwBBgf4.css +2 -0
- package/static/app/index.html +25 -0
- package/static/manifest.json +2 -2
- package/static/sw.js +4 -4
- package/scripts/build_v3_assets.mjs +0 -170
- package/scripts/lint_v3.mjs +0 -97
- package/static/account.html +0 -113
- package/static/activity.html +0 -73
- package/static/admin.html +0 -486
- package/static/agents.html +0 -139
- package/static/chat.html +0 -841
- package/static/css/reference/account.css +0 -439
- package/static/css/reference/admin.css +0 -610
- package/static/css/reference/base.css +0 -1661
- package/static/css/reference/chat.css +0 -4623
- package/static/css/reference/graph.css +0 -1016
- package/static/css/responsive.css +0 -861
- package/static/graph.html +0 -122
- package/static/platform.css +0 -104
- package/static/plugins.html +0 -136
- package/static/scripts/account.js +0 -238
- package/static/scripts/admin.js +0 -1614
- package/static/scripts/chat.js +0 -5081
- package/static/scripts/graph.js +0 -1804
- package/static/scripts/platform.js +0 -64
- package/static/scripts/ux.js +0 -167
- package/static/scripts/workspace.js +0 -948
- package/static/v3/asset-manifest.json +0 -56
- package/static/v3/css/lattice.base.49deefb5.css +0 -128
- package/static/v3/css/lattice.base.css +0 -128
- package/static/v3/css/lattice.components.cde18231.css +0 -472
- package/static/v3/css/lattice.components.css +0 -472
- package/static/v3/css/lattice.shell.29d36d85.css +0 -452
- package/static/v3/css/lattice.shell.css +0 -452
- package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
- package/static/v3/css/lattice.tokens.css +0 -135
- package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
- package/static/v3/css/lattice.views.css +0 -360
- package/static/v3/index.html +0 -68
- package/static/v3/js/app.356e6452.js +0 -26
- package/static/v3/js/app.js +0 -26
- package/static/v3/js/core/api.7a308b89.js +0 -568
- package/static/v3/js/core/api.js +0 -568
- package/static/v3/js/core/components.f25b3b93.js +0 -230
- package/static/v3/js/core/components.js +0 -230
- package/static/v3/js/core/dom.a2773eb0.js +0 -148
- package/static/v3/js/core/dom.js +0 -148
- package/static/v3/js/core/router.584570f2.js +0 -37
- package/static/v3/js/core/router.js +0 -37
- package/static/v3/js/core/routes.7222343d.js +0 -93
- package/static/v3/js/core/routes.js +0 -93
- package/static/v3/js/core/shell.a1657f20.js +0 -391
- package/static/v3/js/core/shell.js +0 -391
- package/static/v3/js/core/store.204a08b2.js +0 -113
- package/static/v3/js/core/store.js +0 -113
- package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
- package/static/v3/js/views/admin-audit.js +0 -185
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
- package/static/v3/js/views/admin-permissions.js +0 -177
- package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
- package/static/v3/js/views/admin-policies.js +0 -102
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
- package/static/v3/js/views/admin-private-vpc.js +0 -135
- package/static/v3/js/views/admin-security.07c66b72.js +0 -180
- package/static/v3/js/views/admin-security.js +0 -180
- package/static/v3/js/views/admin-users.03bac88c.js +0 -168
- package/static/v3/js/views/admin-users.js +0 -168
- package/static/v3/js/views/agents.014d0b74.js +0 -541
- package/static/v3/js/views/agents.js +0 -541
- package/static/v3/js/views/chat.e6dd7dd0.js +0 -601
- package/static/v3/js/views/chat.js +0 -601
- package/static/v3/js/views/files.adad14c1.js +0 -365
- package/static/v3/js/views/files.js +0 -365
- package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
- package/static/v3/js/views/graph-canvas.js +0 -509
- package/static/v3/js/views/home.24f8b8ae.js +0 -200
- package/static/v3/js/views/home.js +0 -200
- package/static/v3/js/views/hooks.37895880.js +0 -220
- package/static/v3/js/views/hooks.js +0 -220
- package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
- package/static/v3/js/views/hybrid-search.js +0 -194
- package/static/v3/js/views/knowledge-graph.5e40cbeb.js +0 -509
- package/static/v3/js/views/knowledge-graph.js +0 -509
- package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
- package/static/v3/js/views/marketplace.js +0 -141
- package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
- package/static/v3/js/views/mcp.js +0 -114
- package/static/v3/js/views/memory.4ebdf474.js +0 -147
- package/static/v3/js/views/memory.js +0 -147
- package/static/v3/js/views/models.a1ffa147.js +0 -256
- package/static/v3/js/views/models.js +0 -256
- package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
- package/static/v3/js/views/my-computer.js +0 -463
- package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
- package/static/v3/js/views/pipeline.js +0 -157
- package/static/v3/js/views/planning.9ac3e313.js +0 -153
- package/static/v3/js/views/planning.js +0 -153
- package/static/v3/js/views/settings.8631fa5e.js +0 -318
- package/static/v3/js/views/settings.js +0 -318
- package/static/v3/js/views/skills.c6c2f965.js +0 -109
- package/static/v3/js/views/skills.js +0 -109
- package/static/v3/js/views/tools.e4f11276.js +0 -108
- package/static/v3/js/views/tools.js +0 -108
- package/static/v3/js/views/workflows.26c57290.js +0 -128
- package/static/v3/js/views/workflows.js +0 -128
- package/static/workflows.html +0 -146
- package/static/workspace.css +0 -1121
- package/static/workspace.html +0 -357
|
@@ -26,7 +26,7 @@ Guardrail summary (v1.2.0):
|
|
|
26
26
|
|
|
27
27
|
from __future__ import annotations
|
|
28
28
|
|
|
29
|
-
from typing import Any, Dict, Optional
|
|
29
|
+
from typing import Any, Callable, Dict, Optional
|
|
30
30
|
|
|
31
31
|
from latticeai.core.workspace_os import WorkspaceOSStore
|
|
32
32
|
|
|
@@ -38,13 +38,20 @@ class WorkspaceService:
|
|
|
38
38
|
# partitioned per workspace. Surfaced so the UI / docs can be explicit.
|
|
39
39
|
SHARED_GLOBAL_AREAS = ("graph", "skills")
|
|
40
40
|
|
|
41
|
-
def __init__(self, store: WorkspaceOSStore):
|
|
41
|
+
def __init__(self, store: WorkspaceOSStore, *, resolve_user_id: Optional[Callable[[Optional[str]], Optional[str]]] = None):
|
|
42
42
|
self.store = store
|
|
43
|
+
self._resolve_user_id = resolve_user_id or (lambda user_id: user_id)
|
|
44
|
+
|
|
45
|
+
def _identity(self, user_id: Optional[str]) -> Optional[str]:
|
|
46
|
+
if isinstance(user_id, str) and user_id.startswith("user:"):
|
|
47
|
+
return user_id
|
|
48
|
+
return self._resolve_user_id(user_id)
|
|
43
49
|
|
|
44
50
|
# ── scope resolution + gating ────────────────────────────────────────
|
|
45
51
|
|
|
46
52
|
def _ensure_permission(self, workspace_id: str, user_id: Optional[str], permission: str) -> None:
|
|
47
|
-
|
|
53
|
+
resolved_user = self._identity(user_id)
|
|
54
|
+
if not self.store.has_permission(workspace_id, resolved_user, permission):
|
|
48
55
|
raise PermissionError(
|
|
49
56
|
f"'{user_id or 'anonymous'}' lacks '{permission}' on workspace '{workspace_id}'"
|
|
50
57
|
)
|
|
@@ -67,10 +74,10 @@ class WorkspaceService:
|
|
|
67
74
|
return workspace_id
|
|
68
75
|
|
|
69
76
|
def can_read(self, workspace_id: str, user_id: Optional[str]) -> bool:
|
|
70
|
-
return self.store.has_permission(workspace_id, user_id, "read")
|
|
77
|
+
return self.store.has_permission(workspace_id, self._identity(user_id), "read")
|
|
71
78
|
|
|
72
79
|
def can_write(self, workspace_id: str, user_id: Optional[str]) -> bool:
|
|
73
|
-
return self.store.has_permission(workspace_id, user_id, "write")
|
|
80
|
+
return self.store.has_permission(workspace_id, self._identity(user_id), "write")
|
|
74
81
|
|
|
75
82
|
# ── record-level authorization (by-id access must not bypass gating) ──
|
|
76
83
|
|
|
@@ -93,12 +100,13 @@ class WorkspaceService:
|
|
|
93
100
|
"""
|
|
94
101
|
owner = (record or {}).get("user_email")
|
|
95
102
|
workspace_id = (record or {}).get("workspace_id")
|
|
96
|
-
|
|
103
|
+
resolved_user = self._identity(user_id)
|
|
104
|
+
if owner and owner in {user_id, resolved_user}:
|
|
97
105
|
return
|
|
98
106
|
if workspace_id:
|
|
99
|
-
self._ensure_permission(workspace_id,
|
|
107
|
+
self._ensure_permission(workspace_id, resolved_user, "write")
|
|
100
108
|
return
|
|
101
|
-
if owner and owner
|
|
109
|
+
if owner and owner not in {user_id, resolved_user}:
|
|
102
110
|
raise PermissionError(
|
|
103
111
|
f"'{user_id or 'anonymous'}' is not the owner of memory '{record.get('id')}'"
|
|
104
112
|
)
|
|
@@ -107,42 +115,42 @@ class WorkspaceService:
|
|
|
107
115
|
|
|
108
116
|
def summary(self, user_id: Optional[str]) -> Dict[str, Any]:
|
|
109
117
|
data = self.store.summary()
|
|
110
|
-
data["workspace_registry"] = self.store.list_workspaces(user_id=user_id)
|
|
118
|
+
data["workspace_registry"] = self.store.list_workspaces(user_id=self._identity(user_id))
|
|
111
119
|
data["shared_global_areas"] = list(self.SHARED_GLOBAL_AREAS)
|
|
112
120
|
return data
|
|
113
121
|
|
|
114
122
|
def list_workspaces(self, user_id: Optional[str]) -> Dict[str, Any]:
|
|
115
|
-
return self.store.list_workspaces(user_id=user_id)
|
|
123
|
+
return self.store.list_workspaces(user_id=self._identity(user_id))
|
|
116
124
|
|
|
117
125
|
def get_workspace(self, workspace_id: str, user_id: Optional[str]) -> Dict[str, Any]:
|
|
118
126
|
# Reading workspace metadata requires read access to that workspace.
|
|
119
127
|
self._ensure_permission(workspace_id, user_id, "read")
|
|
120
|
-
return self.store.get_workspace(workspace_id, user_id=user_id)
|
|
128
|
+
return self.store.get_workspace(workspace_id, user_id=self._identity(user_id))
|
|
121
129
|
|
|
122
130
|
def workspace_summary(self, workspace_id: str, user_id: Optional[str]) -> Dict[str, Any]:
|
|
123
131
|
self._ensure_permission(workspace_id, user_id, "read")
|
|
124
|
-
return self.store.workspace_summary(workspace_id, user_id=user_id)
|
|
132
|
+
return self.store.workspace_summary(workspace_id, user_id=self._identity(user_id))
|
|
125
133
|
|
|
126
134
|
# ── organization workspace management (delegates with actor) ─────────
|
|
127
135
|
|
|
128
136
|
def create_organization_workspace(self, *, name: str, owner_user_id: Optional[str], settings: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
129
|
-
return self.store.create_organization_workspace(name=name, owner_user_id=owner_user_id, settings=settings)
|
|
137
|
+
return self.store.create_organization_workspace(name=name, owner_user_id=self._identity(owner_user_id), settings=settings)
|
|
130
138
|
|
|
131
139
|
def update_workspace(self, workspace_id: str, *, name=None, settings=None, actor=None) -> Dict[str, Any]:
|
|
132
|
-
return self.store.update_workspace(workspace_id, name=name, settings=settings, actor=actor)
|
|
140
|
+
return self.store.update_workspace(workspace_id, name=name, settings=settings, actor=self._identity(actor))
|
|
133
141
|
|
|
134
142
|
def archive_workspace(self, workspace_id: str, *, actor=None) -> Dict[str, Any]:
|
|
135
|
-
return self.store.archive_workspace(workspace_id, actor=actor)
|
|
143
|
+
return self.store.archive_workspace(workspace_id, actor=self._identity(actor))
|
|
136
144
|
|
|
137
145
|
def add_member(self, workspace_id: str, *, user_id: str, role: str = "member", actor=None) -> Dict[str, Any]:
|
|
138
|
-
return self.store.add_member(workspace_id, user_id=user_id, role=role, actor=actor)
|
|
146
|
+
return self.store.add_member(workspace_id, user_id=self._identity(user_id) or user_id, role=role, actor=self._identity(actor))
|
|
139
147
|
|
|
140
148
|
def update_member_role(self, workspace_id: str, *, user_id: str, role: str, actor=None) -> Dict[str, Any]:
|
|
141
|
-
return self.store.update_member_role(workspace_id, user_id=user_id, role=role, actor=actor)
|
|
149
|
+
return self.store.update_member_role(workspace_id, user_id=self._identity(user_id) or user_id, role=role, actor=self._identity(actor))
|
|
142
150
|
|
|
143
151
|
def remove_member(self, workspace_id: str, *, user_id: str, actor=None) -> Dict[str, Any]:
|
|
144
|
-
return self.store.remove_member(workspace_id, user_id=user_id, actor=actor)
|
|
152
|
+
return self.store.remove_member(workspace_id, user_id=self._identity(user_id) or user_id, actor=self._identity(actor))
|
|
145
153
|
|
|
146
154
|
def set_active_workspace(self, workspace_id: str, user_id: Optional[str]) -> Dict[str, Any]:
|
|
147
155
|
# Membership is enforced inside the store for organization workspaces.
|
|
148
|
-
return self.store.set_active_workspace(workspace_id, user_id=user_id)
|
|
156
|
+
return self.store.set_active_workspace(workspace_id, user_id=self._identity(user_id))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Lattice AI — local-first Digital Brain Platform (knowledge graph, durable memory, hybrid search, agents, signed brain exchange)",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
@@ -18,29 +18,36 @@
|
|
|
18
18
|
"start": "LTCAI",
|
|
19
19
|
"dev": "python3 ltcai_cli.py --reload",
|
|
20
20
|
"build": "npm run build:assets && npm run build:python",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
21
|
+
"frontend:dev": "vite --host 127.0.0.1",
|
|
22
|
+
"frontend:openapi": "node scripts/run_python.mjs scripts/export_openapi.py frontend/openapi.json && npx openapi-typescript frontend/openapi.json -o frontend/src/api/openapi.ts",
|
|
23
|
+
"build:assets": "vite build && node scripts/build_frontend_assets.mjs",
|
|
24
|
+
"build:python": "node scripts/run_python.mjs -m build",
|
|
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",
|
|
27
|
+
"lint:frontend": "node scripts/lint_frontend.mjs",
|
|
28
|
+
"typecheck": "npm run typecheck:frontend && cd vscode-extension && npm run build",
|
|
29
|
+
"typecheck:frontend": "npx tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"test": "node scripts/run_python.mjs -m pytest tests/ -v",
|
|
31
|
+
"test:unit": "node scripts/run_python.mjs -m pytest tests/unit/ -v",
|
|
32
|
+
"test:integration": "node scripts/run_python.mjs -m pytest tests/integration/ -v",
|
|
30
33
|
"test:visual": "playwright test",
|
|
31
34
|
"capture:workspace": "node scripts/capture/capture_workspace.js",
|
|
32
35
|
"capture:graph": "node scripts/capture/capture_graph.js",
|
|
33
36
|
"capture:skills": "node scripts/capture/capture_skills.js",
|
|
34
37
|
"capture:enterprise": "node scripts/capture/capture_enterprise.js",
|
|
35
38
|
"capture:onboarding": "node scripts/capture/capture_onboarding.js",
|
|
39
|
+
"desktop:tauri": "tauri dev",
|
|
40
|
+
"desktop:tauri:build": "tauri build",
|
|
41
|
+
"desktop:tauri:check": "cd src-tauri && cargo check",
|
|
42
|
+
"desktop:electron": "electron desktop/electron/main.cjs",
|
|
36
43
|
"package:vsix": "node scripts/build_vsix.mjs",
|
|
37
44
|
"release:artifacts": "npm run build:assets && npm run build:python && npm pack && npm run package:vsix",
|
|
38
|
-
"release:validate": "
|
|
45
|
+
"release:validate": "node scripts/run_python.mjs scripts/validate_release_artifacts.py $npm_package_version --require-vsix --require-tgz",
|
|
39
46
|
"publish:npm": "npm pack && npm publish ltcai-$npm_package_version.tgz --access public",
|
|
40
|
-
"publish:pypi": "npm run build:python &&
|
|
47
|
+
"publish:pypi": "npm run build:python && node scripts/run_python.mjs -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl",
|
|
41
48
|
"publish:vscode": "cd vscode-extension && npm run package:vsix && npm run publish:vscode",
|
|
42
49
|
"publish:openvsx": "cd vscode-extension && npm run package:vsix && npm run publish:openvsx",
|
|
43
|
-
"publish:all": "npm run release:artifacts && npm run release:validate && npm publish ltcai-$npm_package_version.tgz --access public &&
|
|
50
|
+
"publish:all": "npm run release:artifacts && npm run release:validate && npm publish ltcai-$npm_package_version.tgz --access public && node scripts/run_python.mjs -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl && cd vscode-extension && npm run publish:vscode && npm run publish:openvsx"
|
|
44
51
|
},
|
|
45
52
|
"keywords": [
|
|
46
53
|
"ltcai",
|
|
@@ -74,23 +81,12 @@
|
|
|
74
81
|
"mcp_registry.py",
|
|
75
82
|
"latticeai/**/*.py",
|
|
76
83
|
"skills/",
|
|
77
|
-
"static/account.html",
|
|
78
|
-
"static/chat.html",
|
|
79
|
-
"static/admin.html",
|
|
80
|
-
"static/graph.html",
|
|
81
|
-
"static/workspace.html",
|
|
82
|
-
"static/plugins.html",
|
|
83
|
-
"static/workflows.html",
|
|
84
|
-
"static/agents.html",
|
|
85
|
-
"static/activity.html",
|
|
86
84
|
"static/favicon.ico",
|
|
87
85
|
"static/manifest.json",
|
|
88
86
|
"static/sw.js",
|
|
89
|
-
"static/workspace.css",
|
|
90
|
-
"static/platform.css",
|
|
91
|
-
"static/scripts/",
|
|
92
87
|
"static/css/",
|
|
93
|
-
"
|
|
88
|
+
"frontend/",
|
|
89
|
+
"static/app/",
|
|
94
90
|
"static/icons/",
|
|
95
91
|
"plugins/",
|
|
96
92
|
"scripts/",
|
|
@@ -101,6 +97,12 @@
|
|
|
101
97
|
"setup_wizard.py",
|
|
102
98
|
"knowledge_graph_api.py",
|
|
103
99
|
"static/vendor/",
|
|
100
|
+
"src-tauri/",
|
|
101
|
+
"!src-tauri/gen/",
|
|
102
|
+
"!src-tauri/gen/**",
|
|
103
|
+
"!src-tauri/target/",
|
|
104
|
+
"!src-tauri/target/**",
|
|
105
|
+
"desktop/electron/",
|
|
104
106
|
"docs/*.md",
|
|
105
107
|
"!docs/assets/",
|
|
106
108
|
"!docs/images/"
|
|
@@ -109,6 +111,31 @@
|
|
|
109
111
|
"access": "public"
|
|
110
112
|
},
|
|
111
113
|
"devDependencies": {
|
|
112
|
-
"@playwright/test": "^1.60.0"
|
|
114
|
+
"@playwright/test": "^1.60.0",
|
|
115
|
+
"@tailwindcss/postcss": "^4.3.0",
|
|
116
|
+
"@tanstack/react-query": "^5.101.0",
|
|
117
|
+
"@tauri-apps/api": "^2.0.0",
|
|
118
|
+
"@tauri-apps/cli": "^2.0.0",
|
|
119
|
+
"@types/cytoscape": "^3.21.9",
|
|
120
|
+
"@types/react": "^19.2.17",
|
|
121
|
+
"@types/react-dom": "^19.2.3",
|
|
122
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
123
|
+
"autoprefixer": "^10.5.0",
|
|
124
|
+
"class-variance-authority": "^0.7.1",
|
|
125
|
+
"clsx": "^2.1.1",
|
|
126
|
+
"cytoscape": "^3.34.0",
|
|
127
|
+
"electron": "^42.4.0",
|
|
128
|
+
"lucide-react": "^1.17.0",
|
|
129
|
+
"openapi-fetch": "^0.17.0",
|
|
130
|
+
"openapi-typescript": "^7.13.0",
|
|
131
|
+
"postcss": "^8.5.15",
|
|
132
|
+
"react": "^19.2.7",
|
|
133
|
+
"react-dom": "^19.2.7",
|
|
134
|
+
"reactflow": "^11.11.4",
|
|
135
|
+
"tailwind-merge": "^3.6.0",
|
|
136
|
+
"tailwindcss": "^4.3.0",
|
|
137
|
+
"typescript": "^5.9.3",
|
|
138
|
+
"vite": "^8.0.16",
|
|
139
|
+
"zustand": "^5.0.14"
|
|
113
140
|
}
|
|
114
141
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const repo = join(import.meta.dirname, "..");
|
|
6
|
+
const appDir = join(repo, "static", "app");
|
|
7
|
+
const nestedViteManifest = join(appDir, ".vite", "asset-manifest.json");
|
|
8
|
+
const publicManifest = join(appDir, "asset-manifest.json");
|
|
9
|
+
const pkg = JSON.parse(readFileSync(join(repo, "package.json"), "utf8"));
|
|
10
|
+
|
|
11
|
+
const viteManifest = existsSync(nestedViteManifest) ? nestedViteManifest : publicManifest;
|
|
12
|
+
if (!existsSync(viteManifest)) {
|
|
13
|
+
console.error("Vite manifest missing. Run `vite build` before build_frontend_assets.mjs.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const raw = JSON.parse(readFileSync(viteManifest, "utf8"));
|
|
18
|
+
const assets = {};
|
|
19
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
20
|
+
if (value && typeof value === "object") {
|
|
21
|
+
if (value.file) assets[key] = `/static/app/${value.file}`;
|
|
22
|
+
for (const css of value.css || []) assets[css] = `/static/app/${css}`;
|
|
23
|
+
for (const asset of value.assets || []) assets[asset] = `/static/app/${asset}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const manifest = {
|
|
28
|
+
version: pkg.version,
|
|
29
|
+
generated_at: "vite",
|
|
30
|
+
entrypoints: {
|
|
31
|
+
app: assets["frontend/index.html"] || "/static/app/index.html",
|
|
32
|
+
},
|
|
33
|
+
assets,
|
|
34
|
+
vite: raw,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
writeFileSync(publicManifest, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
38
|
+
console.log(`wrote static/app/asset-manifest.json with ${Object.keys(assets).length} assets`);
|
package/scripts/bump_version.py
CHANGED
|
@@ -31,7 +31,7 @@ TARGETS = [
|
|
|
31
31
|
("package-lock.json", "package-lock", None),
|
|
32
32
|
("vscode-extension/package.json", "json", "version"),
|
|
33
33
|
("vscode-extension/package-lock.json", "package-lock", None),
|
|
34
|
-
("static/
|
|
34
|
+
("static/app/asset-manifest.json", "json", "version"),
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Export the FastAPI OpenAPI schema used by the desktop frontend client."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
REPO = Path(__file__).resolve().parents[1]
|
|
12
|
+
sys.path.insert(0, str(REPO))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> int:
|
|
16
|
+
from latticeai.app_factory import create_app
|
|
17
|
+
|
|
18
|
+
target = Path(sys.argv[1] if len(sys.argv) > 1 else "frontend/openapi.json")
|
|
19
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
os.environ.setdefault("LATTICEAI_REQUIRE_AUTH", "false")
|
|
21
|
+
os.environ.setdefault("LATTICEAI_TUNNEL", "false")
|
|
22
|
+
os.environ.setdefault("LATTICEAI_AUTOLOAD_MODELS", "false")
|
|
23
|
+
app = create_app()
|
|
24
|
+
schema = app.openapi()
|
|
25
|
+
target.write_text(json.dumps(schema, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
|
26
|
+
print(f"wrote {target} with {len(schema.get('paths', {}))} paths")
|
|
27
|
+
return 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
const repo = join(import.meta.dirname, "..");
|
|
7
|
+
const frontend = join(repo, "frontend");
|
|
8
|
+
const staticRoot = join(repo, "static");
|
|
9
|
+
let failures = 0;
|
|
10
|
+
|
|
11
|
+
function fail(message) {
|
|
12
|
+
failures += 1;
|
|
13
|
+
console.error(`FAIL ${message}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function walk(dir, exts) {
|
|
17
|
+
if (!existsSync(dir)) return [];
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const name of readdirSync(dir)) {
|
|
20
|
+
const path = join(dir, name);
|
|
21
|
+
const stat = statSync(path);
|
|
22
|
+
if (stat.isDirectory()) {
|
|
23
|
+
if (name === "node_modules") continue;
|
|
24
|
+
out.push(...walk(path, exts));
|
|
25
|
+
} else if (exts.some((ext) => name.endsWith(ext))) {
|
|
26
|
+
out.push(path);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tsc = spawnSync("npx", ["tsc", "-p", "tsconfig.json", "--noEmit"], { cwd: repo, encoding: "utf8" });
|
|
33
|
+
if (tsc.status !== 0) fail(`frontend typecheck\n${tsc.stdout}${tsc.stderr}`);
|
|
34
|
+
else console.log("typecheck: frontend TS passes");
|
|
35
|
+
|
|
36
|
+
const files = [
|
|
37
|
+
...walk(frontend, [".ts", ".tsx", ".css", ".html"]),
|
|
38
|
+
...walk(join(staticRoot, "app"), [".js", ".css", ".html", ".json"]),
|
|
39
|
+
join(staticRoot, "sw.js"),
|
|
40
|
+
join(staticRoot, "manifest.json"),
|
|
41
|
+
].filter(existsSync);
|
|
42
|
+
|
|
43
|
+
const external = /https?:\/\/(fonts\.googleapis\.com|fonts\.gstatic\.com|cdn\.jsdelivr\.net|unpkg\.com|cdnjs\.cloudflare\.com)/;
|
|
44
|
+
const stale = /static\/v3|lint_v3|build_v3_assets/;
|
|
45
|
+
let scanned = 0;
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
scanned += 1;
|
|
48
|
+
const text = readFileSync(file, "utf8");
|
|
49
|
+
if (external.test(text)) fail(`${relative(repo, file)}: CDN reference`);
|
|
50
|
+
if (stale.test(text)) fail(`${relative(repo, file)}: stale v3 frontend reference`);
|
|
51
|
+
}
|
|
52
|
+
console.log(`privacy: ${scanned} frontend/static files scanned`);
|
|
53
|
+
|
|
54
|
+
const client = readFileSync(join(frontend, "src", "api", "client.ts"), "utf8");
|
|
55
|
+
if (!client.includes("openapi-fetch") || !client.includes("./openapi")) {
|
|
56
|
+
fail("frontend/src/api/client.ts must use the generated OpenAPI client");
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(join(frontend, "src", "api", "openapi.ts"))) {
|
|
59
|
+
fail("generated OpenAPI types missing");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const openapi = JSON.parse(readFileSync(join(frontend, "openapi.json"), "utf8"));
|
|
63
|
+
const openapiPaths = Object.keys(openapi.paths || {});
|
|
64
|
+
const requiredPaths = [
|
|
65
|
+
"/health",
|
|
66
|
+
"/chat",
|
|
67
|
+
"/api/graph",
|
|
68
|
+
"/api/search/hybrid",
|
|
69
|
+
"/api/knowledge-graph/export",
|
|
70
|
+
"/api/memory/recall",
|
|
71
|
+
"/agents/api/run",
|
|
72
|
+
"/workflows/api/definitions",
|
|
73
|
+
"/workspace/os",
|
|
74
|
+
"/models",
|
|
75
|
+
];
|
|
76
|
+
if (openapiPaths.length < 300) fail(`OpenAPI path count too low: ${openapiPaths.length}`);
|
|
77
|
+
for (const path of requiredPaths) {
|
|
78
|
+
if (!openapiPaths.includes(path)) fail(`OpenAPI path missing: ${path}`);
|
|
79
|
+
}
|
|
80
|
+
console.log(`api: generated OpenAPI schema exposes ${openapiPaths.length} paths`);
|
|
81
|
+
|
|
82
|
+
if (failures) {
|
|
83
|
+
console.error(`\nfrontend lint: ${failures} failure(s)`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
console.log("\nfrontend lint: all checks pass");
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
if (args.length === 0) {
|
|
9
|
+
console.error("usage: node scripts/run_python.mjs <python-args...>");
|
|
10
|
+
process.exit(2);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const candidates = [
|
|
14
|
+
process.env.LTCAI_PYTHON,
|
|
15
|
+
path.join(process.cwd(), ".venv", "bin", "python"),
|
|
16
|
+
path.join(process.cwd(), ".venv", "Scripts", "python.exe"),
|
|
17
|
+
"python3",
|
|
18
|
+
"python",
|
|
19
|
+
].filter(Boolean);
|
|
20
|
+
|
|
21
|
+
let lastError = null;
|
|
22
|
+
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
const isPath = candidate.includes(path.sep);
|
|
25
|
+
if (isPath && !existsSync(candidate)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result = spawnSync(candidate, args, {
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
env: process.env,
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (result.error) {
|
|
36
|
+
if (result.error.code === "ENOENT") {
|
|
37
|
+
lastError = result.error;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
throw result.error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process.exit(result.status ?? 1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.error(`No usable Python executable found. Last error: ${lastError?.message ?? "none"}`);
|
|
47
|
+
process.exit(127);
|