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.
@@ -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 `dist/*` upload mistakes are still
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 `dist/*` glob.
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="Analyze this Mac, recommend a model, install only with consent, validate it, then load it." result={recs.data}>
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
- {runtimeLabel} · {loadId}
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 });
@@ -26,7 +26,7 @@ from .storage import (
26
26
  storage_from_env,
27
27
  )
28
28
 
29
- __version__ = "5.1.0"
29
+ __version__ = "5.2.0"
30
30
 
31
31
  __all__ = [
32
32
  "AgentRuntime",
@@ -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.1.0"
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")
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "5.1.0"
3
+ __version__ = "5.2.0"
@@ -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
@@ -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
- Detects the system profile (OS/RAM/CPU/GPU/disk) and classifies the
498
- ``engine`` catalog into recommended / compatible / not_recommended,
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
- return {"profile": profile, "recommendations": catalog}
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
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "5.1.0"
14
+ MARKETPLACE_VERSION = "5.2.0"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -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.1.0"
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
- def list_template_registry(self) -> Dict[str, Any]:
2313
- return dict(self.load_state().get("template_registry") or {})
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 = f"{kind}:{template_id}"
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,