ltcai 7.9.0 → 8.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 +35 -49
- package/docs/CHANGELOG.md +35 -0
- package/docs/V3_FRONTEND.md +11 -5
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/embeddings.py +9 -1
- package/lattice_brain/graph/projection.py +19 -5
- package/lattice_brain/graph/provenance.py +26 -4
- package/lattice_brain/graph/write_master.py +30 -3
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/app_factory.py +13 -134
- package/latticeai/core/local_embeddings.py +8 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/mcp_registry.py +140 -1
- package/latticeai/core/tool_registry.py +7 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/architecture_readiness.py +50 -6
- package/latticeai/services/model_runtime.py +36 -2
- package/latticeai/services/product_readiness.py +19 -17
- package/package.json +2 -2
- package/scripts/build_frontend_assets.mjs +11 -2
- 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 +11 -102
- package/static/app/assets/Act-BOO66G-c.js +1 -0
- package/static/app/assets/Brain-C6lEYiD7.js +321 -0
- package/static/app/assets/{Capture-B9Tlhzqr.js → Capture-TATXBRDw.js} +1 -2
- package/static/app/assets/Library-DK4FIp8a.js +1 -0
- package/static/app/assets/System-Bgs6Ql7x.js +1 -0
- package/static/app/assets/core-CwxXejkd.js +1 -2
- package/static/app/assets/index-Bvv79nre.js +16 -0
- package/static/app/assets/index-Dslqglia.css +2 -0
- package/static/app/assets/primitives-B70WOra0.js +1 -0
- package/static/app/assets/{textarea-BZk6ybp5.js → textarea-Czrd9gwM.js} +1 -2
- package/static/app/index.html +3 -10
- package/static/app/theme-boot.js +8 -0
- package/static/sw.js +1 -1
- package/frontend/index.html +0 -24
- package/frontend/openapi.json +0 -15425
- package/frontend/src/App.tsx +0 -243
- package/frontend/src/api/client.ts +0 -580
- package/frontend/src/api/openapi.ts +0 -17892
- package/frontend/src/components/AdminAccessGate.tsx +0 -70
- package/frontend/src/components/FeedbackState.tsx +0 -45
- package/frontend/src/components/LanguageSwitcher.tsx +0 -23
- package/frontend/src/components/LivingBrain.tsx +0 -220
- package/frontend/src/components/ProductFlow.tsx +0 -171
- package/frontend/src/components/WorkspaceProfileSwitcher.tsx +0 -176
- package/frontend/src/components/onboarding/AnalysisScreen.tsx +0 -135
- package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +0 -29
- package/frontend/src/components/onboarding/InstallScreen.tsx +0 -263
- package/frontend/src/components/onboarding/LanguageChooser.tsx +0 -23
- package/frontend/src/components/onboarding/LoginScreen.tsx +0 -131
- package/frontend/src/components/onboarding/ProductFlowScreens.tsx +0 -13
- package/frontend/src/components/onboarding/RecommendationScreen.tsx +0 -116
- package/frontend/src/components/onboarding/recommendationModel.ts +0 -189
- package/frontend/src/components/primitives.tsx +0 -392
- package/frontend/src/components/ui/badge.tsx +0 -27
- package/frontend/src/components/ui/button.tsx +0 -37
- package/frontend/src/components/ui/card.tsx +0 -22
- package/frontend/src/components/ui/input.tsx +0 -16
- package/frontend/src/components/ui/textarea.tsx +0 -16
- package/frontend/src/features/admin/AdminConsole.tsx +0 -334
- package/frontend/src/features/brain/BrainCarePanel.tsx +0 -254
- package/frontend/src/features/brain/BrainComposer.tsx +0 -85
- package/frontend/src/features/brain/BrainConversation.tsx +0 -688
- package/frontend/src/features/brain/BrainGraphLayer.tsx +0 -365
- package/frontend/src/features/brain/BrainHome.tsx +0 -624
- package/frontend/src/features/brain/BrainMemoryLayer.tsx +0 -45
- package/frontend/src/features/brain/BrainOverviewPanel.tsx +0 -149
- package/frontend/src/features/brain/BrainRelationshipLayer.tsx +0 -43
- package/frontend/src/features/brain/DepthEmergence.tsx +0 -53
- package/frontend/src/features/brain/brainData.ts +0 -246
- package/frontend/src/features/brain/graphLayout.ts +0 -37
- package/frontend/src/features/brain/types.ts +0 -150
- package/frontend/src/features/review/ReviewCard.tsx +0 -100
- package/frontend/src/features/review/ReviewInbox.tsx +0 -127
- package/frontend/src/features/review/reviewHelpers.ts +0 -69
- package/frontend/src/i18n.ts +0 -1303
- package/frontend/src/lib/utils.ts +0 -33
- package/frontend/src/main.tsx +0 -23
- package/frontend/src/pages/Act.tsx +0 -458
- package/frontend/src/pages/Ask.tsx +0 -14
- package/frontend/src/pages/Brain.tsx +0 -914
- package/frontend/src/pages/Capture.tsx +0 -258
- package/frontend/src/pages/Library.tsx +0 -486
- package/frontend/src/pages/System.tsx +0 -621
- package/frontend/src/routes.ts +0 -92
- package/frontend/src/store/appStore.ts +0 -94
- package/frontend/src/styles.css +0 -6579
- package/static/app/assets/Act-DOvf59ru.js +0 -2
- package/static/app/assets/Act-DOvf59ru.js.map +0 -1
- package/static/app/assets/Brain-C7_0mEiI.js +0 -322
- package/static/app/assets/Brain-C7_0mEiI.js.map +0 -1
- package/static/app/assets/Capture-B9Tlhzqr.js.map +0 -1
- package/static/app/assets/Library-BJPEEm5O.js +0 -2
- package/static/app/assets/Library-BJPEEm5O.js.map +0 -1
- package/static/app/assets/System-D6t9jo9V.js +0 -2
- package/static/app/assets/System-D6t9jo9V.js.map +0 -1
- package/static/app/assets/core-CwxXejkd.js.map +0 -1
- package/static/app/assets/index-C7g26IF6.css +0 -2
- package/static/app/assets/index-DbcEYJQ2.js +0 -17
- package/static/app/assets/index-DbcEYJQ2.js.map +0 -1
- package/static/app/assets/primitives-CD38lt4n.js +0 -2
- package/static/app/assets/primitives-CD38lt4n.js.map +0 -1
- package/static/app/assets/textarea-BZk6ybp5.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lattice AI
|
|
2
2
|
|
|
3
|
-
**Lattice AI
|
|
3
|
+
**Lattice AI 8.0 is the local-first Digital Brain platform with hardened runtime architecture. It keeps your knowledge durable across any AI model, with AgentRuntime, ToolRegistry, central Config, and Knowledge Graph stability tracked by explicit release contracts.**
|
|
4
4
|
|
|
5
5
|
**Lattice AI는 모델이 바뀌어도 내 지식과 맥락을 보존하는 로컬 우선 AI 브레인입니다.**
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ downloads, update checks, and other external communication happen only after
|
|
|
11
11
|
explicit consent.
|
|
12
12
|
|
|
13
13
|
It is not a ChatGPT clone, a model launcher, a graph database, or a note app.
|
|
14
|
-
It is the finished private AI memory layer wrapped in a Living Brain experience — now with
|
|
14
|
+
It is the finished private AI memory layer wrapped in a Living Brain experience — now with the 8.0 runtime architecture contract behind it.
|
|
15
15
|
|
|
16
16
|
[](https://pypi.org/project/ltcai/)
|
|
17
17
|
[](https://www.npmjs.com/package/ltcai)
|
|
@@ -64,9 +64,9 @@ You need Lattice AI when:
|
|
|
64
64
|
|
|
65
65
|
## Living Brain Flow
|
|
66
66
|
|
|
67
|
-
The screenshots below are release evidence captures. The
|
|
68
|
-
|
|
69
|
-
composer are
|
|
67
|
+
The screenshots below are release evidence captures. The 8.1 line keeps the
|
|
68
|
+
first-run flow and refreshes Brain Home so the living Brain, recent memory,
|
|
69
|
+
next action, and composer are understandable in one glance.
|
|
70
70
|
|
|
71
71
|
### 1. Wake Brain
|
|
72
72
|
|
|
@@ -78,43 +78,36 @@ confirm owner, check the computer, choose the Brain voice.
|
|
|
78
78
|
Choose the owner of the Brain. The profile is not a SaaS account by default; it
|
|
79
79
|
is the local identity for the knowledge you keep.
|
|
80
80
|
|
|
81
|
-

|
|
82
82
|
|
|
83
|
-
### 3.
|
|
84
|
-
|
|
85
|
-
See what kind of local AI experience this computer can support before choosing a
|
|
86
|
-
model.
|
|
87
|
-
|
|
88
|
-

|
|
89
|
-
|
|
90
|
-
### 4. Recommended Models
|
|
83
|
+
### 3. Recommended Models
|
|
91
84
|
|
|
92
85
|
Start with a short list: safest recommendation, faster model, stronger model.
|
|
93
86
|
Advanced details stay available without overwhelming first-time users.
|
|
94
87
|
|
|
95
|
-

|
|
96
89
|
|
|
97
|
-
###
|
|
90
|
+
### 4. Install And Load
|
|
98
91
|
|
|
99
92
|
Download and load only after consent. Lattice explains model size, local
|
|
100
93
|
execution, and network use before work starts.
|
|
101
94
|
|
|
102
|
-

|
|
103
96
|
|
|
104
|
-
###
|
|
97
|
+
### 5. Brain Chat
|
|
105
98
|
|
|
106
99
|
Talk normally. Useful decisions and context become memory, then appear later as
|
|
107
100
|
topics, relationships, graph structure, and the concentric memory rings around
|
|
108
101
|
the Brain.
|
|
109
102
|
|
|
110
|
-

|
|
111
104
|
|
|
112
|
-
###
|
|
105
|
+
### 6. Review Center
|
|
113
106
|
|
|
114
107
|
Automation results are staged for review before they become durable decisions.
|
|
115
108
|
Snooze, unsnooze, run now, approve, and dismiss actions stay explicit.
|
|
116
109
|
|
|
117
|
-

|
|
118
111
|
|
|
119
112
|
## Brain Depths
|
|
120
113
|
|
|
@@ -130,10 +123,10 @@ The user travels inward from everyday memory to deeper structure:
|
|
|
130
123
|
|
|
131
124
|
Walkthrough:
|
|
132
125
|
|
|
133
|
-

|
|
134
127
|
|
|
135
128
|
Screenshot index and capture notes:
|
|
136
|
-
[output/release/
|
|
129
|
+
[output/release/v8.1.0/SCREENSHOT_INDEX.md](output/release/v8.1.0/SCREENSHOT_INDEX.md)
|
|
137
130
|
|
|
138
131
|
## Install
|
|
139
132
|
|
|
@@ -210,36 +203,27 @@ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for developer workflow details.
|
|
|
210
203
|
|
|
211
204
|
## Current Release
|
|
212
205
|
|
|
213
|
-
The current release is **
|
|
214
|
-
|
|
215
|
-
-
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
|
|
219
|
-
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
-
|
|
229
|
-
7.9 product readiness score before a tag can be treated as release-safe.
|
|
230
|
-
|
|
231
|
-
Expected artifacts for 7.9.0 release must use exact filenames:
|
|
232
|
-
|
|
233
|
-
- `dist/ltcai-7.9.0-py3-none-any.whl`
|
|
234
|
-
- `dist/ltcai-7.9.0.tar.gz`
|
|
235
|
-
- `ltcai-7.9.0.tgz`
|
|
236
|
-
- `dist/ltcai-7.9.0.vsix`
|
|
237
|
-
- `src-tauri/target/release/bundle/dmg/Lattice AI_7.9.0_aarch64.dmg`
|
|
206
|
+
The current release is **8.1.0 — Intuitive Brain Home**:
|
|
207
|
+
|
|
208
|
+
- Brain Home now opens with the living Brain directly above the conversation composer, so the primary action is visible before users read any utility panels.
|
|
209
|
+
- The previous dashboard-style growth strip is replaced by a product narrative: Brain state, what was just remembered, the strongest connected topic, and the next best action.
|
|
210
|
+
- Primary actions are explicit and click-tested: talk to Brain, add sources, or open the memory graph.
|
|
211
|
+
- Mobile and narrow layouts keep the Brain and composer in the first viewport, avoiding the previous clipped/overcrowded first screen.
|
|
212
|
+
- The 8.0 runtime architecture contract remains active: AgentRuntime, ToolRegistry, Config, server decomposition, and KG hardening stay machine-checkable through readiness gates.
|
|
213
|
+
- CI and release workflows continue to run frontend lint/typecheck/build gates plus product readiness before a tag can be treated as release-safe.
|
|
214
|
+
|
|
215
|
+
Expected artifacts for 8.1.0 release must use exact filenames:
|
|
216
|
+
|
|
217
|
+
- `dist/ltcai-8.1.0-py3-none-any.whl`
|
|
218
|
+
- `dist/ltcai-8.1.0.tar.gz`
|
|
219
|
+
- `ltcai-8.1.0.tgz`
|
|
220
|
+
- `dist/ltcai-8.1.0.vsix`
|
|
221
|
+
- `src-tauri/target/release/bundle/dmg/Lattice AI_8.1.0_aarch64.dmg`
|
|
238
222
|
|
|
239
223
|
Do not use wildcard artifact uploads. Package registry publishing remains owner-run.
|
|
240
224
|
|
|
241
225
|
See [docs/ROADMAP_RECOMMENDATIONS.md](docs/ROADMAP_RECOMMENDATIONS.md) for the
|
|
242
|
-
strategic roadmap slices applied through
|
|
226
|
+
strategic roadmap slices applied through 8.1.0 and the follow-up tracks.
|
|
243
227
|
|
|
244
228
|
## Known Limitations
|
|
245
229
|
|
|
@@ -256,6 +240,8 @@ strategic roadmap slices applied through 7.9.0 and the follow-up tracks.
|
|
|
256
240
|
|
|
257
241
|
| Version | Theme |
|
|
258
242
|
| --- | --- |
|
|
243
|
+
| 8.1.0 | Intuitive Brain Home: living Brain, recent memory, connected topic, next action, and composer are visible in one product-first screen with refreshed 8.1.0 evidence and artifacts |
|
|
244
|
+
| 8.0.0 | Runtime Architecture Contract: AgentRuntime, ToolRegistry, central Config, server decomposition, and KG hardening are captured as machine-checkable release boundaries with exact 8.0.0 artifacts |
|
|
259
245
|
| 7.9.0 | Agent Runtime Boundary Hardening: explicit `SingleAgentRuntime`, compatibility alias preservation, injected rollback port, and release/readiness docs aligned to the product AgentRuntime facade |
|
|
260
246
|
| 7.8.0 | Brain Chat Home UX Simplification: chat-first first viewport, visible workspace navigation, collapsed source/status utilities, hidden default depth controls, and removal of obsolete Brain UX components |
|
|
261
247
|
| 7.7.0 | Complete Product Polish: command-center Brain Home, repaired Review Center evidence, exact 7.7 docs/artifacts, product readiness gate, and stronger CI/release checks |
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,41 @@ existed at that release.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [8.1.0] - 2026-06-27
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Rebuilt Brain Home around an intuitive first screen with LivingBrain, recent
|
|
13
|
+
memory, connected topic, next-best action, and the composer visible together.
|
|
14
|
+
- Replaced dashboard-style Brain growth metrics with narrative, product-facing
|
|
15
|
+
copy and focused primary actions.
|
|
16
|
+
- Tightened mobile and narrow viewport behavior so the Brain and composer remain
|
|
17
|
+
visible without horizontal overflow.
|
|
18
|
+
- Refreshed release screenshots, walkthrough GIF/WebM, static app assets, and
|
|
19
|
+
exact 8.1.0 artifact metadata.
|
|
20
|
+
- Synchronized Python, npm, VS Code extension, Tauri, workspace, readiness, and
|
|
21
|
+
current-release documentation versions to 8.1.0.
|
|
22
|
+
|
|
23
|
+
## [8.0.0] - 2026-06-24
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Added `lattice-architecture-contract/v1` to make the AgentRuntime,
|
|
27
|
+
ToolRegistry, Config, server decomposition, and Knowledge Graph stabilization
|
|
28
|
+
boundaries explicit and testable for the major architecture line.
|
|
29
|
+
- Added `tool-registry-contract/v1` to the ToolRegistry manifest, including
|
|
30
|
+
dispatch, policy, and permission ownership.
|
|
31
|
+
- Updated architecture and product readiness targets to 8.0.0.
|
|
32
|
+
- Synchronized package/runtime/static/Tauri metadata to 8.0.0.
|
|
33
|
+
- Updated current-release docs and exact artifact names to 8.0.0 while
|
|
34
|
+
preserving historical 7.x entries.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- Made logical Knowledge Graph `replace` imports transactional so malformed
|
|
38
|
+
imports roll back without clearing the existing graph.
|
|
39
|
+
- Locked v2 read-equivalence coverage for `list_documents`, `get_node`,
|
|
40
|
+
`relationship_search`, and `traverse`.
|
|
41
|
+
- Preserved colliding legacy edge labels during logical import/backfill while
|
|
42
|
+
keeping native write-door synonym dedupe canonical.
|
|
43
|
+
|
|
9
44
|
## [7.9.0] - 2026-06-23
|
|
10
45
|
|
|
11
46
|
### Changed
|
package/docs/V3_FRONTEND.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# Lattice AI v3 — Frontend Product Shell
|
|
2
2
|
|
|
3
|
+
> Historical v3 shell notes. The current product shell is the React/Vite app
|
|
4
|
+
> built from `frontend/` into `static/app/` with `npm run build:assets`; current
|
|
5
|
+
> linting uses `scripts/lint_frontend.mjs`.
|
|
6
|
+
|
|
3
7
|
> A token-native single-page workspace shell for Lattice AI v3. It ships as the
|
|
4
8
|
> primary product experience and calls the real v3 retrieval/chat APIs while
|
|
5
9
|
> rendering clear unavailable states for local services that are not running.
|
|
6
10
|
|
|
7
11
|
Entry point: **`/app`** (served by `latticeai/api/static_routes.py` →
|
|
12
|
+
the current `static/app/index.html` bundle; the historical v3 shell used
|
|
8
13
|
`static/v3/index.html`). Login, auto-login after registration, SSO callback, and
|
|
9
14
|
the PWA manifest land on `/app`. Legacy multi-page static screens have been
|
|
10
15
|
deleted; compatibility routes (`/workspace`, `/chat`, `/graph`, `/admin`, …)
|
|
@@ -27,7 +32,8 @@ Deep-linking into an `admin/*` route auto-promotes the shell to Admin mode.
|
|
|
27
32
|
|
|
28
33
|
## Information architecture
|
|
29
34
|
|
|
30
|
-
One declarative table (`static/v3/js/core/routes.js
|
|
35
|
+
One declarative table (historically `static/v3/js/core/routes.js`, now
|
|
36
|
+
represented by the React routes under `frontend/src/`) drives the nav rail, the
|
|
31
37
|
command palette, the router, breadcrumbs, and lazy view loading.
|
|
32
38
|
|
|
33
39
|
```
|
|
@@ -50,8 +56,8 @@ Administration Users · Permissions · Audit Logs · Security · Policies · Pri
|
|
|
50
56
|
Token-native, layered on the existing **single color source**
|
|
51
57
|
(`static/css/tokens.css`, `data-lt-theme` light/dark). No legacy override layers
|
|
52
58
|
(`responsive.css` / `workspace.css` / `platform.css`) are loaded by v3.
|
|
53
|
-
Runtime assets are loaded through `static/
|
|
54
|
-
step writes hashed CSS/JS
|
|
59
|
+
Runtime assets are loaded through `static/app/asset-manifest.json`; the build
|
|
60
|
+
step writes hashed CSS/JS assets and `/app` reads the manifest instead of
|
|
55
61
|
using manual `?v=` query strings.
|
|
56
62
|
|
|
57
63
|
| File | Responsibility |
|
|
@@ -133,8 +139,8 @@ focus rings, skip link, keyboard command palette (⌘K / Ctrl-K).
|
|
|
133
139
|
|
|
134
140
|
## Validation
|
|
135
141
|
|
|
136
|
-
- `npm run lint` (
|
|
137
|
-
- `npm run build:assets` (writes hashed
|
|
142
|
+
- `npm run lint` (covers the current frontend through `scripts/lint_frontend.mjs`).
|
|
143
|
+
- `npm run build:assets` (writes hashed `static/app` assets and `asset-manifest.json`).
|
|
138
144
|
- `npm run test:visual` (`tests/visual/v3.spec.js` against the mock server, which
|
|
139
145
|
serves `/app` and mocks the future API surfaces).
|
|
140
146
|
- Browser-rendered smoke checks of every route in light and dark themes.
|
|
@@ -15,6 +15,10 @@ DEFAULT_EMBEDDING_DIM = int(os.getenv("LATTICEAI_VECTOR_DIM", "384"))
|
|
|
15
15
|
EMBEDDING_MODEL_ID = f"lattice-local-hash-v1:{DEFAULT_EMBEDDING_DIM}"
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def embedding_model_id(dim: int) -> str:
|
|
19
|
+
return f"lattice-local-hash-v1:{int(dim)}"
|
|
20
|
+
|
|
21
|
+
|
|
18
22
|
def _tokenize(text: str) -> List[str]:
|
|
19
23
|
raw = str(text or "").lower()
|
|
20
24
|
tokens = re.findall(r"[a-z0-9][a-z0-9_.:/+-]{1,}|[가-힣]{2,}", raw)
|
|
@@ -49,6 +53,10 @@ class LocalEmbeddingModel:
|
|
|
49
53
|
dim: int = DEFAULT_EMBEDDING_DIM
|
|
50
54
|
model_id: str = EMBEDDING_MODEL_ID
|
|
51
55
|
|
|
56
|
+
def __post_init__(self) -> None:
|
|
57
|
+
if self.model_id == EMBEDDING_MODEL_ID and self.dim != DEFAULT_EMBEDDING_DIM:
|
|
58
|
+
object.__setattr__(self, "model_id", embedding_model_id(self.dim))
|
|
59
|
+
|
|
52
60
|
def embed(self, text: str) -> List[float]:
|
|
53
61
|
vector = [0.0] * self.dim
|
|
54
62
|
features = _tokenize(text)
|
|
@@ -79,4 +87,4 @@ class LocalEmbeddingModel:
|
|
|
79
87
|
return list(struct.unpack(f"<{count}f", payload[: count * 4]))
|
|
80
88
|
|
|
81
89
|
|
|
82
|
-
__all__ = ["DEFAULT_EMBEDDING_DIM", "EMBEDDING_MODEL_ID", "LocalEmbeddingModel"]
|
|
90
|
+
__all__ = ["DEFAULT_EMBEDDING_DIM", "EMBEDDING_MODEL_ID", "LocalEmbeddingModel", "embedding_model_id"]
|
|
@@ -331,17 +331,31 @@ class KnowledgeGraphProjectionMixin:
|
|
|
331
331
|
edge_id: Optional[str] = None,
|
|
332
332
|
created_at: Optional[str] = None,
|
|
333
333
|
strict: bool = False,
|
|
334
|
+
legacy_type: Optional[str] = None,
|
|
334
335
|
) -> None:
|
|
335
336
|
if KGStoreV2 is None:
|
|
336
337
|
if strict:
|
|
337
338
|
raise RuntimeError("Knowledge Graph v2 schema is unavailable")
|
|
338
339
|
return
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
)
|
|
340
|
+
explicit_legacy_type = legacy_type is not None
|
|
341
|
+
leg_type = legacy_type if explicit_legacy_type else edge_type
|
|
342
|
+
# Native canonical writes (and write-door dedupes) use legacy_type=''
|
|
343
|
+
# so (source,target,type) is the effective key.
|
|
344
|
+
# Import paths can pass distinct legacy_type to keep colliding legacy
|
|
345
|
+
# labels as separate rows (lossless for old data).
|
|
346
|
+
if leg_type and EdgeType is not None:
|
|
347
|
+
try:
|
|
348
|
+
if EdgeType.from_legacy(leg_type).value == leg_type:
|
|
349
|
+
leg_type = ""
|
|
350
|
+
except ValueError:
|
|
351
|
+
pass
|
|
342
352
|
norm_type = (
|
|
343
353
|
EdgeType.from_legacy(edge_type).value if EdgeType is not None else edge_type
|
|
344
354
|
)
|
|
355
|
+
if explicit_legacy_type and leg_type:
|
|
356
|
+
eid = f"edge:{_sha256_text(f'{from_node}|{norm_type}|{to_node}|{leg_type}')[:24]}"
|
|
357
|
+
else:
|
|
358
|
+
eid = edge_id or f"edge:{_sha256_text(f'{from_node}|{norm_type}|{to_node}')[:24]}"
|
|
345
359
|
meta_str = metadata_json if metadata_json is not None else "{}"
|
|
346
360
|
confidence = float(_safe_loads(meta_str).get("confidence", 1.0))
|
|
347
361
|
try:
|
|
@@ -360,7 +374,7 @@ class KnowledgeGraphProjectionMixin:
|
|
|
360
374
|
from_node,
|
|
361
375
|
to_node,
|
|
362
376
|
norm_type,
|
|
363
|
-
|
|
377
|
+
leg_type,
|
|
364
378
|
float(weight),
|
|
365
379
|
confidence,
|
|
366
380
|
meta_str,
|
|
@@ -371,7 +385,7 @@ class KnowledgeGraphProjectionMixin:
|
|
|
371
385
|
# (the UNIQUE upsert + weight=max alone would erase recurrence).
|
|
372
386
|
row = conn.execute(
|
|
373
387
|
"SELECT id FROM edges_v2 WHERE source=? AND target=? AND type=? AND legacy_type=?",
|
|
374
|
-
(from_node, to_node, norm_type,
|
|
388
|
+
(from_node, to_node, norm_type, leg_type),
|
|
375
389
|
).fetchone()
|
|
376
390
|
if row is not None:
|
|
377
391
|
conn.execute(
|
|
@@ -292,10 +292,24 @@ class KnowledgeGraphProvenanceMixin:
|
|
|
292
292
|
plan["dry_run"] = True
|
|
293
293
|
return plan
|
|
294
294
|
|
|
295
|
-
if mode == "replace":
|
|
296
|
-
self.clear_all()
|
|
297
|
-
|
|
298
295
|
with self._connect() as conn:
|
|
296
|
+
if mode == "replace":
|
|
297
|
+
# Keep replacement imports transactional. The old clear_all()
|
|
298
|
+
# path committed before the import started, so a malformed
|
|
299
|
+
# artifact could leave a cleared or partially rebuilt graph.
|
|
300
|
+
# These deletes roll back with the rest of the import.
|
|
301
|
+
for table in (
|
|
302
|
+
"local_file_index",
|
|
303
|
+
"knowledge_sources",
|
|
304
|
+
"chunks",
|
|
305
|
+
"edges",
|
|
306
|
+
"nodes",
|
|
307
|
+
"vector_embeddings",
|
|
308
|
+
):
|
|
309
|
+
conn.execute(f"DELETE FROM {table}")
|
|
310
|
+
if KGStoreV2 is not None:
|
|
311
|
+
conn.execute("DELETE FROM edges_v2")
|
|
312
|
+
conn.execute("DELETE FROM nodes_v2")
|
|
299
313
|
for n in nodes:
|
|
300
314
|
self._upsert_node(
|
|
301
315
|
conn,
|
|
@@ -315,13 +329,21 @@ class KnowledgeGraphProvenanceMixin:
|
|
|
315
329
|
metadata=_safe_loads(c.get("metadata_json")),
|
|
316
330
|
)
|
|
317
331
|
for e in edges:
|
|
332
|
+
e_meta = _safe_loads(e.get("metadata_json")) or {}
|
|
333
|
+
leg_label = e_meta.get("legacy_label")
|
|
334
|
+
if not leg_label:
|
|
335
|
+
orig = e.get("type") or ""
|
|
336
|
+
if orig:
|
|
337
|
+
# preserve whatever label came from export (legacy or canon)
|
|
338
|
+
leg_label = orig
|
|
318
339
|
self._upsert_edge(
|
|
319
340
|
conn,
|
|
320
341
|
e["from_node"],
|
|
321
342
|
e["to_node"],
|
|
322
343
|
e["type"],
|
|
323
344
|
weight=float(e.get("weight") or 1.0),
|
|
324
|
-
metadata=
|
|
345
|
+
metadata=e_meta,
|
|
346
|
+
legacy_label=leg_label,
|
|
325
347
|
)
|
|
326
348
|
for s in sources:
|
|
327
349
|
conn.execute(
|
|
@@ -73,20 +73,46 @@ class KnowledgeGraphWriteMixin:
|
|
|
73
73
|
edge_type: str,
|
|
74
74
|
weight: float = 1.0,
|
|
75
75
|
metadata: Optional[Dict[str, Any]] = None,
|
|
76
|
+
*,
|
|
77
|
+
legacy_label: Optional[str] = None,
|
|
76
78
|
) -> str:
|
|
77
79
|
# v4 write door: every new edge stores the canonical EdgeType value —
|
|
78
80
|
# free-string types (e.g. '포함함', '언급함') are normalized here, so no
|
|
79
81
|
# caller can mint new legacy taxonomy. The original label survives in
|
|
80
82
|
# metadata.legacy_label for traceability.
|
|
83
|
+
#
|
|
84
|
+
# legacy_type in edges_v2:
|
|
85
|
+
# - normal write door: synonyms always dedupe to ONE row with legacy_type=''
|
|
86
|
+
# - import (passes legacy_label=) : preserves distinct legacy labels as
|
|
87
|
+
# separate v2 rows (lossless collision for old artifacts / backfill)
|
|
88
|
+
passed_for_legacy = legacy_label or edge_type
|
|
81
89
|
if EdgeType is not None:
|
|
82
90
|
canonical = EdgeType.from_legacy(edge_type).value
|
|
83
|
-
if canonical != edge_type:
|
|
91
|
+
if canonical != edge_type or (legacy_label and legacy_label != canonical):
|
|
84
92
|
metadata = dict(metadata or {})
|
|
85
|
-
|
|
93
|
+
ll = legacy_label or edge_type
|
|
94
|
+
if ll != canonical:
|
|
95
|
+
metadata.setdefault("legacy_label", ll)
|
|
86
96
|
edge_type = canonical
|
|
87
97
|
edge_id = f"edge:{_sha256_text(f'{from_node}|{edge_type}|{to_node}')[:24]}"
|
|
88
98
|
now = _now()
|
|
89
99
|
meta_json = _json(metadata) # canonical string shared with the projection
|
|
100
|
+
v2_legacy = ""
|
|
101
|
+
if passed_for_legacy and passed_for_legacy != edge_type:
|
|
102
|
+
if legacy_label is not None:
|
|
103
|
+
# explicit legacy_label from import path forces distinct legacy_type
|
|
104
|
+
# rows in v2 for collision preservation
|
|
105
|
+
v2_legacy = passed_for_legacy
|
|
106
|
+
else:
|
|
107
|
+
# normal write-door dedupes even for synonym labels (legacy_type='')
|
|
108
|
+
v2_legacy = ""
|
|
109
|
+
# v2 may use distinct eid when preserving legacy collisions (different from
|
|
110
|
+
# the canon-based edge_id used for legacy edges table which always dedupes)
|
|
111
|
+
v2_eid = (
|
|
112
|
+
f"edge:{_sha256_text(f'{from_node}|{edge_type}|{to_node}|{v2_legacy}')[:24]}"
|
|
113
|
+
if v2_legacy
|
|
114
|
+
else edge_id
|
|
115
|
+
)
|
|
90
116
|
self._v2_project_edge(
|
|
91
117
|
conn,
|
|
92
118
|
from_node,
|
|
@@ -94,9 +120,10 @@ class KnowledgeGraphWriteMixin:
|
|
|
94
120
|
edge_type,
|
|
95
121
|
float(weight),
|
|
96
122
|
meta_json,
|
|
97
|
-
edge_id=
|
|
123
|
+
edge_id=v2_eid,
|
|
98
124
|
created_at=now,
|
|
99
125
|
strict=True,
|
|
126
|
+
legacy_type=v2_legacy,
|
|
100
127
|
)
|
|
101
128
|
conn.execute(
|
|
102
129
|
"""
|
|
@@ -21,7 +21,7 @@ from typing import Any, Callable, Dict, List, Optional
|
|
|
21
21
|
from .contracts import multi_agent_contract
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
MULTI_AGENT_VERSION = "
|
|
24
|
+
MULTI_AGENT_VERSION = "8.1.0"
|
|
25
25
|
|
|
26
26
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
27
27
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
package/latticeai/app_factory.py
CHANGED
|
@@ -77,8 +77,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
77
77
|
import re
|
|
78
78
|
import secrets
|
|
79
79
|
import threading
|
|
80
|
-
import subprocess
|
|
81
|
-
import sys
|
|
82
80
|
import time
|
|
83
81
|
from pathlib import Path
|
|
84
82
|
|
|
@@ -195,8 +193,10 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
195
193
|
)
|
|
196
194
|
from latticeai.core.tool_registry import TOOL_CATALOG_BRIEF as _TOOL_CATALOG_BRIEF # noqa: F401
|
|
197
195
|
from latticeai.core.mcp_registry import (
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
_fetch_skills_marketplace,
|
|
197
|
+
install_skill,
|
|
198
|
+
SKILLS_DIR,
|
|
199
|
+
create_mcp_install_state,
|
|
200
200
|
)
|
|
201
201
|
from latticeai.services.p_reinforce import PReinforceGardener
|
|
202
202
|
from setup_wizard import get_recommendations, scan_environment
|
|
@@ -322,6 +322,15 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
322
322
|
MCP_FILE = DATA_DIR / "mcp_installs.json"
|
|
323
323
|
AUDIT_FILE = DATA_DIR / "audit_log.json"
|
|
324
324
|
SSO_FILE = DATA_DIR / "sso_config.json"
|
|
325
|
+
|
|
326
|
+
# MCP state extracted to mcp_registry.create_mcp_install_state (server decomp)
|
|
327
|
+
_mcp_state = create_mcp_install_state(DATA_DIR)
|
|
328
|
+
load_mcp_installs = _mcp_state["load_mcp_installs"]
|
|
329
|
+
save_mcp_installs = _mcp_state["save_mcp_installs"]
|
|
330
|
+
mcp_public_item = _mcp_state["mcp_public_item"]
|
|
331
|
+
recommend_mcps = _mcp_state["recommend_mcps"]
|
|
332
|
+
install_mcp = _mcp_state["install_mcp"]
|
|
333
|
+
|
|
325
334
|
# Resolve the configured embedding provider once at startup. Degrades to the
|
|
326
335
|
# offline hash fallback when the requested provider is unavailable, while
|
|
327
336
|
# recording the requested-vs-active provider for the Embeddings status surface.
|
|
@@ -546,136 +555,6 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
546
555
|
with open(VPC_FILE, "w", encoding="utf-8") as f:
|
|
547
556
|
json.dump(config, f, ensure_ascii=False, indent=2)
|
|
548
557
|
|
|
549
|
-
def load_mcp_installs() -> Dict:
|
|
550
|
-
if not os.path.exists(MCP_FILE):
|
|
551
|
-
return {"installed": {}, "updated_at": None}
|
|
552
|
-
try:
|
|
553
|
-
with open(MCP_FILE, "r", encoding="utf-8") as f:
|
|
554
|
-
data = json.load(f)
|
|
555
|
-
if "installed" not in data:
|
|
556
|
-
data["installed"] = {}
|
|
557
|
-
return data
|
|
558
|
-
except Exception as e:
|
|
559
|
-
logging.warning("load_mcp_installs failed: %s", e)
|
|
560
|
-
return {"installed": {}, "updated_at": None}
|
|
561
|
-
|
|
562
|
-
def save_mcp_installs(data: Dict):
|
|
563
|
-
data["updated_at"] = datetime.now().isoformat()
|
|
564
|
-
with open(MCP_FILE, "w", encoding="utf-8") as f:
|
|
565
|
-
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
566
|
-
|
|
567
|
-
def mcp_public_item(item: Dict, installed_state: Dict) -> Dict:
|
|
568
|
-
state = installed_state.get(item["id"]) or {}
|
|
569
|
-
installed = item["install_mode"] in {"builtin", "bundled"} or bool(state.get("installed"))
|
|
570
|
-
connector_pending = item["install_mode"] == "connector" and not state.get("authenticated")
|
|
571
|
-
authenticated = item["install_mode"] != "connector" or bool(state.get("authenticated"))
|
|
572
|
-
return {
|
|
573
|
-
"id": item["id"],
|
|
574
|
-
"name": item["name"],
|
|
575
|
-
"category": item.get("category", ""),
|
|
576
|
-
"install_mode": item["install_mode"],
|
|
577
|
-
"description": item.get("description", ""),
|
|
578
|
-
"capabilities": item.get("capabilities", []),
|
|
579
|
-
"connector_url": item.get("connector_url"),
|
|
580
|
-
"external_url": item.get("external_url"),
|
|
581
|
-
"package": item.get("package"),
|
|
582
|
-
"homepage": item.get("homepage"),
|
|
583
|
-
"source": item.get("source", "local"),
|
|
584
|
-
"installed": installed,
|
|
585
|
-
"status": state.get("status") or ("active" if installed and not connector_pending else "needs_auth" if connector_pending else "available"),
|
|
586
|
-
"authenticated": authenticated,
|
|
587
|
-
"updated_at": state.get("updated_at"),
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
async def recommend_mcps(query: str, limit: int = 5) -> List[Dict]:
|
|
591
|
-
text = (query or "").lower()
|
|
592
|
-
installed = load_mcp_installs().get("installed", {})
|
|
593
|
-
registry = await _get_combined_registry()
|
|
594
|
-
scored = []
|
|
595
|
-
for item in registry:
|
|
596
|
-
score = 0
|
|
597
|
-
hits = []
|
|
598
|
-
for keyword in item.get("keywords", []):
|
|
599
|
-
if keyword.lower() in text:
|
|
600
|
-
score += 3 if len(keyword) > 2 else 1
|
|
601
|
-
hits.append(keyword)
|
|
602
|
-
# description 키워드 매칭 (remote 항목 보완)
|
|
603
|
-
if not hits and text:
|
|
604
|
-
desc_words = item.get("description", "").lower().split()
|
|
605
|
-
for word in text.split():
|
|
606
|
-
if len(word) > 2 and word in desc_words:
|
|
607
|
-
score += 1
|
|
608
|
-
hits.append(word)
|
|
609
|
-
if item["id"] == "filesystem" and any(word in text for word in ["만들", "구현", "build", "deploy", "코드", "앱"]):
|
|
610
|
-
score += 2
|
|
611
|
-
if score:
|
|
612
|
-
public = mcp_public_item(item, installed)
|
|
613
|
-
public["score"] = score
|
|
614
|
-
public["matched_keywords"] = hits[:6]
|
|
615
|
-
scored.append(public)
|
|
616
|
-
if not scored:
|
|
617
|
-
fallback_ids = ["filesystem", "browser", "documents"]
|
|
618
|
-
scored = [
|
|
619
|
-
{**mcp_public_item(item, installed), "score": 1, "matched_keywords": []}
|
|
620
|
-
for item in registry
|
|
621
|
-
if item["id"] in fallback_ids
|
|
622
|
-
]
|
|
623
|
-
return sorted(scored, key=lambda item: item["score"], reverse=True)[: max(1, min(limit, 24))]
|
|
624
|
-
|
|
625
|
-
async def install_mcp(mcp_id: str) -> Dict:
|
|
626
|
-
registry = await _get_combined_registry()
|
|
627
|
-
item = next((entry for entry in registry if entry["id"] == mcp_id), None)
|
|
628
|
-
if not item:
|
|
629
|
-
raise HTTPException(status_code=404, detail="MCP를 찾을 수 없습니다.")
|
|
630
|
-
data = load_mcp_installs()
|
|
631
|
-
state = data.setdefault("installed", {})
|
|
632
|
-
status = "active"
|
|
633
|
-
message = "MCP가 활성화되었습니다."
|
|
634
|
-
if item["install_mode"] == "connector":
|
|
635
|
-
status = "needs_auth"
|
|
636
|
-
message = "커넥터 인증이 필요합니다. Codex 앱의 connector 설정에서 계정을 연결하면 바로 사용할 수 있습니다."
|
|
637
|
-
elif item["install_mode"] == "pip":
|
|
638
|
-
packages = item.get("pip_packages") or []
|
|
639
|
-
for pkg in packages:
|
|
640
|
-
completed = subprocess.run(
|
|
641
|
-
[sys.executable, "-m", "pip", "install", "--upgrade", pkg],
|
|
642
|
-
capture_output=True, text=True, timeout=900, check=False,
|
|
643
|
-
)
|
|
644
|
-
if completed.returncode != 0:
|
|
645
|
-
raise HTTPException(status_code=500, detail=completed.stderr[-2000:] or f"{pkg} 설치 실패")
|
|
646
|
-
message = f"필수 패키지 설치 완료: {', '.join(packages)}"
|
|
647
|
-
elif item["install_mode"] == "pypi":
|
|
648
|
-
pkg = item.get("package", "")
|
|
649
|
-
version = item.get("package_version")
|
|
650
|
-
pkg_str = f"{pkg}=={version}" if version else pkg
|
|
651
|
-
completed = subprocess.run(
|
|
652
|
-
[sys.executable, "-m", "pip", "install", pkg_str],
|
|
653
|
-
capture_output=True, text=True, timeout=300, check=False,
|
|
654
|
-
)
|
|
655
|
-
if completed.returncode != 0:
|
|
656
|
-
raise HTTPException(status_code=500, detail=completed.stderr[-2000:] or f"{pkg} 설치 실패")
|
|
657
|
-
message = f"pip 패키지 설치 완료: {pkg_str}"
|
|
658
|
-
elif item["install_mode"] == "npm":
|
|
659
|
-
pkg = item.get("package", "")
|
|
660
|
-
version = item.get("package_version")
|
|
661
|
-
pkg_str = f"{pkg}@{version}" if version else pkg
|
|
662
|
-
completed = subprocess.run(
|
|
663
|
-
["npm", "install", "-g", pkg_str],
|
|
664
|
-
capture_output=True, text=True, timeout=300, check=False,
|
|
665
|
-
)
|
|
666
|
-
if completed.returncode != 0:
|
|
667
|
-
raise HTTPException(status_code=500, detail=completed.stderr[-2000:] or f"{pkg} 설치 실패")
|
|
668
|
-
message = f"npm 패키지 설치 완료: {pkg_str}"
|
|
669
|
-
state[mcp_id] = {
|
|
670
|
-
"installed": True,
|
|
671
|
-
"status": status,
|
|
672
|
-
"authenticated": item["install_mode"] != "connector",
|
|
673
|
-
"updated_at": datetime.now().isoformat(),
|
|
674
|
-
}
|
|
675
|
-
save_mcp_installs(data)
|
|
676
|
-
public = mcp_public_item(item, state)
|
|
677
|
-
public["message"] = message
|
|
678
|
-
return public
|
|
679
558
|
|
|
680
559
|
_history_lock = threading.Lock()
|
|
681
560
|
|