ltcai 5.1.0 → 5.2.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 +45 -39
- package/docs/CHANGELOG.md +36 -2355
- package/docs/V4_1_VALIDATION_REPORT.md +1 -1
- package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +2 -2
- package/docs/V4_5_1_VALIDATION_REPORT.md +2 -1
- package/frontend/src/pages/Library.tsx +29 -4
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/marketplace.py +2 -2
- package/latticeai/api/models.py +20 -4
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/workspace_os.py +18 -4
- package/latticeai/services/model_capability_registry.py +483 -0
- package/latticeai/services/model_catalog.py +99 -96
- package/latticeai/services/model_recommendation.py +12 -1
- package/package.json +1 -1
- package/scripts/verify_hf_model_registry.py +308 -0
- 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 +5 -5
- package/static/app/assets/index-CQmHhk8Q.css +2 -0
- package/static/app/assets/{index-DONOJfMn.js → index-DsnfomFs.js} +1 -1
- package/static/app/assets/{index-DONOJfMn.js.map → index-DsnfomFs.js.map} +1 -1
- package/static/app/index.html +2 -2
- package/static/app/assets/index-DuYYT2oh.css +0 -2
|
@@ -43,5 +43,5 @@ FastAPI backend compatibility, release artifacts, and installed-wheel smoke.
|
|
|
43
43
|
`block v0.1.6`; the current Tauri 2.0 build passes on Rust 1.96.
|
|
44
44
|
- Release artifact validation warns that historical artifacts remain in
|
|
45
45
|
`dist/`; this is expected and reinforces the rule to upload only exact
|
|
46
|
-
v4.1.0 filenames, never `dist
|
|
46
|
+
v4.1.0 filenames, never a wildcard from the `dist` directory.
|
|
47
47
|
- No external registry publish was performed.
|
|
@@ -44,8 +44,8 @@ architecture.
|
|
|
44
44
|
- Release artifact validation now checks the exact Tauri DMG path.
|
|
45
45
|
- Release artifact build script cleans only target-version outputs before
|
|
46
46
|
rebuilding.
|
|
47
|
-
- Historical artifacts remain visible so
|
|
48
|
-
detectable.
|
|
47
|
+
- Historical artifacts remain visible so wildcard upload mistakes from `dist`
|
|
48
|
+
are still detectable.
|
|
49
49
|
|
|
50
50
|
## Registry Policy
|
|
51
51
|
|
|
@@ -51,4 +51,5 @@ Expected exact-version artifacts:
|
|
|
51
51
|
|
|
52
52
|
`npm run release:validate` confirmed all exact-version RC artifacts are present.
|
|
53
53
|
It also warned that historical artifacts remain in `dist/`, so publish commands
|
|
54
|
-
must continue to use explicit v4.5.1 filenames rather than a
|
|
54
|
+
must continue to use explicit v4.5.1 filenames rather than a wildcard from the
|
|
55
|
+
`dist` directory.
|
|
@@ -103,7 +103,7 @@ function ModelsPanel() {
|
|
|
103
103
|
return (
|
|
104
104
|
<div className="grid gap-4 xl:grid-cols-[1.2fr_0.8fr]">
|
|
105
105
|
<div className="space-y-4">
|
|
106
|
-
<DataPanel title="Guided model setup" description="
|
|
106
|
+
<DataPanel title="Guided model setup" description="5.2 registry: hardware-fit multimodal models with HF verification, download/load strategies, and clear RAM notes. Analyze, consent, download only on click." result={recs.data}>
|
|
107
107
|
{(data) => {
|
|
108
108
|
const recommendation = (data as Record<string, unknown>).recommendations as Record<string, unknown> | undefined;
|
|
109
109
|
const profile = (data as Record<string, unknown>).profile as Record<string, unknown> | undefined;
|
|
@@ -160,6 +160,13 @@ function ModelsPanel() {
|
|
|
160
160
|
const loadId = String(model.recommended_load_id || id);
|
|
161
161
|
const engine = String(model.recommended_engine || model.engine || "");
|
|
162
162
|
const recommendation = recommendationById.get(id) || recommendationById.get(loadId) || {};
|
|
163
|
+
const modelVerification = asRecord(model.verification);
|
|
164
|
+
const recommendationVerification = asRecord(recommendation.verification);
|
|
165
|
+
const modelHardware = asRecord(model.hardware);
|
|
166
|
+
const recommendationHardware = asRecord(recommendation.hardware);
|
|
167
|
+
const hardwareNote = modelHardware.notes || recommendationHardware.notes || (modelHardware.recommended_ram_gb ? `~${modelHardware.recommended_ram_gb}GB RAM rec` : "");
|
|
168
|
+
const safetyNotes = model.safety_notes || recommendation.safety_notes;
|
|
169
|
+
const licenseText = model.license || recommendation.license;
|
|
163
170
|
const compatibility = (model.runtime_compatibility || recommendation.runtime_compatibility || {}) as Record<string, unknown>;
|
|
164
171
|
const fallbackAvailable = String(compatibility.status || "") === "fallback_available";
|
|
165
172
|
const unsupported = model.load_status === "unsupported" || compatibility.supported === false;
|
|
@@ -176,7 +183,9 @@ function ModelsPanel() {
|
|
|
176
183
|
<div className="min-w-0">
|
|
177
184
|
<div className="flex flex-wrap items-center gap-2">
|
|
178
185
|
<div className="text-base font-semibold">{String(model.name || id)}</div>
|
|
179
|
-
{topPick?.id === id ? <Badge variant="success">recommended</Badge> : null}
|
|
186
|
+
{topPick?.id === id || model.recommended_default ? <Badge variant="success">recommended</Badge> : null}
|
|
187
|
+
{String(model.modality || recommendation.modality || "").includes("multi") || String(model.modality || "") === "multimodal" ? <Badge variant="muted">multimodal</Badge> : null}
|
|
188
|
+
{modelVerification.verified || recommendationVerification.verified ? <Badge variant="success" title="HF verified (config+tokenizer present)">✓ HF</Badge> : null}
|
|
180
189
|
</div>
|
|
181
190
|
<div className="mt-1 text-sm text-muted-foreground">
|
|
182
191
|
{mode === "basic"
|
|
@@ -186,6 +195,11 @@ function ModelsPanel() {
|
|
|
186
195
|
].filter(Boolean).map(String).join(" · ")
|
|
187
196
|
: [model.family || recommendation.family || "local", model.size || recommendation.size].filter(Boolean).map(String).join(" · ")}
|
|
188
197
|
</div>
|
|
198
|
+
{(model.hardware || recommendation.hardware) ? (
|
|
199
|
+
<div className="mt-1 text-[11px] text-muted-foreground/80">
|
|
200
|
+
{String(hardwareNote)}
|
|
201
|
+
</div>
|
|
202
|
+
) : null}
|
|
189
203
|
{unsupported ? (
|
|
190
204
|
<div className="mt-3 rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-sm">
|
|
191
205
|
<div className="font-medium">{mode === "basic" ? "Needs attention before loading" : actionLabel}</div>
|
|
@@ -198,8 +212,15 @@ function ModelsPanel() {
|
|
|
198
212
|
</div>
|
|
199
213
|
) : !loaded && !loadAvailable ? <div className="mt-1 text-xs text-muted-foreground">{unavailableReason}</div> : null}
|
|
200
214
|
{mode !== "basic" ? (
|
|
201
|
-
<div className="mt-2 text-xs text-muted-foreground">
|
|
202
|
-
|
|
215
|
+
<div className="mt-2 space-y-1 text-xs text-muted-foreground">
|
|
216
|
+
<div>
|
|
217
|
+
{runtimeLabel} · {loadId}
|
|
218
|
+
{model.load_strategy || recommendation.load_strategy ? ` · ${String(model.load_strategy || recommendation.load_strategy)}` : ""}
|
|
219
|
+
{modelVerification.notes ? ` · ${String(modelVerification.notes).slice(0,60)}` : ""}
|
|
220
|
+
</div>
|
|
221
|
+
{safetyNotes || licenseText ? (
|
|
222
|
+
<div>{[licenseText ? `License: ${String(licenseText)}` : "", safetyNotes ? String(safetyNotes) : ""].filter(Boolean).join(" · ")}</div>
|
|
223
|
+
) : null}
|
|
203
224
|
</div>
|
|
204
225
|
) : null}
|
|
205
226
|
{unsupported || fallbackAvailable ? <AlternativeModels compatibility={compatibility} /> : null}
|
|
@@ -277,6 +298,10 @@ function ModelRecovery({ error }: { error: Record<string, unknown> }) {
|
|
|
277
298
|
);
|
|
278
299
|
}
|
|
279
300
|
|
|
301
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
302
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
303
|
+
}
|
|
304
|
+
|
|
280
305
|
function SkillsPanel() {
|
|
281
306
|
const qc = useQueryClient();
|
|
282
307
|
const skills = useQuery({ queryKey: ["skills"], queryFn: latticeApi.skills });
|
|
@@ -14,7 +14,7 @@ from datetime import datetime
|
|
|
14
14
|
from typing import Any, Callable, Dict, List, Optional
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
MULTI_AGENT_VERSION = "5.
|
|
17
|
+
MULTI_AGENT_VERSION = "5.2.0"
|
|
18
18
|
|
|
19
19
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
20
20
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
package/latticeai/__init__.py
CHANGED
|
@@ -88,7 +88,7 @@ def create_marketplace_router(
|
|
|
88
88
|
@router.get("/marketplace/templates/registry")
|
|
89
89
|
async def template_registry(request: Request):
|
|
90
90
|
require_user(request)
|
|
91
|
-
gate_read(request)
|
|
92
|
-
return {"registry": store.list_template_registry()}
|
|
91
|
+
scope = gate_read(request)
|
|
92
|
+
return {"registry": store.list_template_registry(workspace_id=scope)}
|
|
93
93
|
|
|
94
94
|
return router
|
package/latticeai/api/models.py
CHANGED
|
@@ -425,6 +425,12 @@ def create_models_router(
|
|
|
425
425
|
loaded_ids=_router.loaded_model_ids,
|
|
426
426
|
current_id=_router.current_model_id,
|
|
427
427
|
)
|
|
428
|
+
# 5.2.0: surface structured registry info (verified status, hf, hardware, strategies) for UX
|
|
429
|
+
try:
|
|
430
|
+
from latticeai.services.model_catalog import get_verified_models
|
|
431
|
+
verified = get_verified_models()
|
|
432
|
+
except Exception:
|
|
433
|
+
verified = []
|
|
428
434
|
return {
|
|
429
435
|
"recommended": recommended,
|
|
430
436
|
"cloud": _router.detected_cloud_models(),
|
|
@@ -433,6 +439,12 @@ def create_models_router(
|
|
|
433
439
|
"current": _router.current_model_id,
|
|
434
440
|
"compat_profiles": _list_compat_profiles(),
|
|
435
441
|
"vision": _vision_capability(_router.current_model_id, engines),
|
|
442
|
+
# 5.2+ transparent model capability registry
|
|
443
|
+
"registry": {
|
|
444
|
+
"version": "5.2.0",
|
|
445
|
+
"verified_count": len(verified),
|
|
446
|
+
"verified": verified[:12], # compact; full via /models/recommendations or future dedicated
|
|
447
|
+
},
|
|
436
448
|
}
|
|
437
449
|
|
|
438
450
|
@router.get("/models/compat-profiles")
|
|
@@ -494,9 +506,8 @@ def create_models_router(
|
|
|
494
506
|
async def model_recommendations(request: Request, engine: str = "local_mlx"):
|
|
495
507
|
"""Hardware-aware tri-state model recommendation for this machine.
|
|
496
508
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
grouped by family. Used by the onboarding and model-picker UIs.
|
|
509
|
+
5.2.0: now includes rich capability fields (hf_repo_id, verification,
|
|
510
|
+
hardware, load_strategy, license, safety_notes) from the structured registry.
|
|
500
511
|
"""
|
|
501
512
|
require_user(request)
|
|
502
513
|
from auto_setup import probe as auto_setup_probe
|
|
@@ -504,6 +515,11 @@ def create_models_router(
|
|
|
504
515
|
|
|
505
516
|
profile = await asyncio.to_thread(lambda: auto_setup_probe().to_json())
|
|
506
517
|
catalog = recommend_catalog(profile, engine=engine)
|
|
507
|
-
|
|
518
|
+
try:
|
|
519
|
+
from latticeai.services.model_catalog import get_verified_models
|
|
520
|
+
reg_meta = {"version": "5.2.0", "verified_total": len(get_verified_models())}
|
|
521
|
+
except Exception:
|
|
522
|
+
reg_meta = {"version": "5.2.0"}
|
|
523
|
+
return {"profile": profile, "recommendations": catalog, "registry": reg_meta}
|
|
508
524
|
|
|
509
525
|
return router
|
|
@@ -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 = "5.
|
|
22
|
+
WORKSPACE_OS_VERSION = "5.2.0"
|
|
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
|
|
@@ -2309,8 +2309,22 @@ class WorkspaceOSStore:
|
|
|
2309
2309
|
# Marketplace template registry (v2.1 foundation)
|
|
2310
2310
|
# ------------------------------------------------------------------
|
|
2311
2311
|
|
|
2312
|
-
|
|
2313
|
-
|
|
2312
|
+
@staticmethod
|
|
2313
|
+
def _template_registry_key(kind: str, template_id: str, workspace_id: str) -> str:
|
|
2314
|
+
base = f"{kind}:{template_id}"
|
|
2315
|
+
return base if workspace_id == DEFAULT_WORKSPACE_ID else f"{workspace_id}:{base}"
|
|
2316
|
+
|
|
2317
|
+
def list_template_registry(self, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
2318
|
+
state = self.load_state()
|
|
2319
|
+
registry = dict(state.get("template_registry") or {})
|
|
2320
|
+
if workspace_id is None:
|
|
2321
|
+
return registry
|
|
2322
|
+
scope = self._resolve_scope(workspace_id, state)
|
|
2323
|
+
return {
|
|
2324
|
+
key: value
|
|
2325
|
+
for key, value in registry.items()
|
|
2326
|
+
if self._record_workspace(value) == scope
|
|
2327
|
+
}
|
|
2314
2328
|
|
|
2315
2329
|
def mark_template_installed(
|
|
2316
2330
|
self,
|
|
@@ -2323,7 +2337,7 @@ class WorkspaceOSStore:
|
|
|
2323
2337
|
) -> Dict[str, Any]:
|
|
2324
2338
|
state = self.load_state()
|
|
2325
2339
|
scope = self._resolve_scope(workspace_id, state)
|
|
2326
|
-
key =
|
|
2340
|
+
key = self._template_registry_key(kind, template_id, scope)
|
|
2327
2341
|
entry = state.setdefault("template_registry", {}).setdefault(key, {"id": template_id, "kind": kind})
|
|
2328
2342
|
entry.update({
|
|
2329
2343
|
"id": template_id,
|