ltcai 5.0.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.
Files changed (48) hide show
  1. package/README.md +75 -55
  2. package/docs/CHANGELOG.md +96 -2354
  3. package/docs/TRUST_MODEL.md +66 -0
  4. package/docs/V4_1_VALIDATION_REPORT.md +1 -1
  5. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +2 -2
  6. package/docs/V4_5_1_VALIDATION_REPORT.md +2 -1
  7. package/docs/WHY_LATTICE.md +54 -0
  8. package/frontend/src/App.tsx +1 -1
  9. package/frontend/src/components/primitives.tsx +1 -1
  10. package/frontend/src/i18n.ts +6 -4
  11. package/frontend/src/pages/Library.tsx +29 -4
  12. package/frontend/src/pages/System.tsx +1 -1
  13. package/lattice_brain/__init__.py +1 -1
  14. package/lattice_brain/portability.py +11 -7
  15. package/lattice_brain/runtime/multi_agent.py +1 -1
  16. package/latticeai/__init__.py +1 -1
  17. package/latticeai/api/chat.py +19 -11
  18. package/latticeai/api/marketplace.py +2 -2
  19. package/latticeai/api/models.py +26 -4
  20. package/latticeai/api/security_dashboard.py +3 -15
  21. package/latticeai/api/static_routes.py +16 -0
  22. package/latticeai/app_factory.py +114 -40
  23. package/latticeai/core/audit.py +3 -1
  24. package/latticeai/core/builtin_hooks.py +7 -9
  25. package/latticeai/core/logging_safety.py +5 -21
  26. package/latticeai/core/marketplace.py +1 -1
  27. package/latticeai/core/security.py +67 -9
  28. package/latticeai/core/workspace_os.py +18 -4
  29. package/latticeai/services/model_capability_registry.py +483 -0
  30. package/latticeai/services/model_catalog.py +99 -96
  31. package/latticeai/services/model_recommendation.py +12 -1
  32. package/package.json +2 -2
  33. package/scripts/clean_release_artifacts.mjs +16 -1
  34. package/scripts/com.pts.claudecode.discord.plist +31 -0
  35. package/scripts/pts-claudecode-discord-bridge.mjs +189 -0
  36. package/scripts/run_integration_tests.mjs +91 -0
  37. package/scripts/start-pts-claudecode-discord.sh +51 -0
  38. package/scripts/verify_hf_model_registry.py +308 -0
  39. package/src-tauri/Cargo.lock +1 -1
  40. package/src-tauri/Cargo.toml +1 -1
  41. package/src-tauri/tauri.conf.json +3 -2
  42. package/static/app/asset-manifest.json +5 -5
  43. package/static/app/assets/index-CQmHhk8Q.css +2 -0
  44. package/static/app/assets/{index-FR1UZkCD.js → index-DsnfomFs.js} +2 -2
  45. package/static/app/assets/index-DsnfomFs.js.map +1 -0
  46. package/static/app/index.html +2 -2
  47. package/static/app/assets/index-DuYYT2oh.css +0 -2
  48. package/static/app/assets/index-FR1UZkCD.js.map +0 -1
@@ -0,0 +1,51 @@
1
+ #!/bin/zsh
2
+ set -euo pipefail
3
+
4
+ export DISCORD_STATE_DIR="$HOME/.claude/channels/discord"
5
+ PROJECT_DIR="$HOME/Downloads/Lattice AI"
6
+ INSTALLED_BRIDGE_SCRIPT="$HOME/.claude/bin/pts-claudecode-discord-bridge.mjs"
7
+ if [[ -z "${PTS_CLAUDECODE_BRIDGE_SCRIPT:-}" && -f "$INSTALLED_BRIDGE_SCRIPT" ]]; then
8
+ BRIDGE_SCRIPT="$INSTALLED_BRIDGE_SCRIPT"
9
+ else
10
+ BRIDGE_SCRIPT="${PTS_CLAUDECODE_BRIDGE_SCRIPT:-$PROJECT_DIR/scripts/pts-claudecode-discord-bridge.mjs}"
11
+ fi
12
+ export PTS_CLAUDECODE_BRIDGE_SCRIPT="$BRIDGE_SCRIPT"
13
+ LOG_DIR="$HOME/.claude/logs"
14
+ LOG_FILE="$LOG_DIR/pts_claudecode_discord_autostart.log"
15
+ LOCK_DIR="$HOME/.claude/pts_claudecode_start.lock"
16
+
17
+ mkdir -p "$LOG_DIR"
18
+ chmod 600 "$DISCORD_STATE_DIR/.env" 2>/dev/null || true
19
+
20
+ stamp() {
21
+ date '+%Y-%m-%dT%H:%M:%S%z'
22
+ }
23
+
24
+ if ! mkdir "$LOCK_DIR" 2>/dev/null; then
25
+ echo "$(stamp) another pts_claudecode start is already in progress" >> "$LOG_FILE"
26
+ exit 0
27
+ fi
28
+ trap 'rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT
29
+
30
+ token_line=$(grep '^DISCORD_BOT_TOKEN=' "$DISCORD_STATE_DIR/.env" 2>/dev/null | tail -1 || true)
31
+ token_value="${token_line#DISCORD_BOT_TOKEN=}"
32
+
33
+ if [[ -z "$token_value" || ${#token_value} -lt 40 ]]; then
34
+ echo "$(stamp) missing or invalid DISCORD_BOT_TOKEN in $DISCORD_STATE_DIR/.env" >> "$LOG_FILE"
35
+ exit 1
36
+ fi
37
+
38
+ if pgrep -af '[n]ode .*pts-claudecode-discord-bridge\.mjs' >/dev/null 2>&1; then
39
+ echo "$(stamp) pts_claudecode bridge process already running" >> "$LOG_FILE"
40
+ exit 0
41
+ fi
42
+
43
+ cd "$PROJECT_DIR"
44
+
45
+ screen -dmS pts_claudecode_bridge zsh -lc '
46
+ cd "$HOME/Downloads/Lattice AI"
47
+ export DISCORD_STATE_DIR="$HOME/.claude/channels/discord"
48
+ exec /opt/homebrew/bin/node "$PTS_CLAUDECODE_BRIDGE_SCRIPT"
49
+ '
50
+
51
+ echo "$(stamp) started pts_claudecode bridge screen" >> "$LOG_FILE"
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Automated HF verification script for Lattice AI 5.2.0 Model Capability Registry.
4
+
5
+ Usage (no heavy deps):
6
+ python3 scripts/verify_hf_model_registry.py # light API metadata only
7
+ python3 scripts/verify_hf_model_registry.py --deep # + try light config+tokenizer fetch (needs hf_hub or transformers)
8
+ python3 scripts/verify_hf_model_registry.py --test-load # for *very small* models: attempt real from_pretrained (config+tokenizer only, no full weights if possible). Warns for large.
9
+
10
+ Behavior:
11
+ - Never blindly downloads full weights for large models.
12
+ - Uses public HF REST API (no token) for existence, pipeline, tags, likes, lastModified, siblings summary.
13
+ - For deep: uses huggingface_hub snapshot_download with allow_patterns=["config.json","tokenizer*.json","*.model"] + max 50MB or specific small files only. Falls back gracefully.
14
+ - For --test-load on practical sizes (<~4GB display): imports and calls AutoConfig.from_pretrained + AutoTokenizer (trust_remote_code=False by default).
15
+ - Emits:
16
+ * console table
17
+ * verification_report.json (timestamped + summary)
18
+ * Suggested Python snippet to copy verified flags back into model_capability_registry.py (if desired for static pinning)
19
+
20
+ Large model explicit limitation: entries >12GB list "LOCAL_LOAD_LIMITED" and skip heavy tests.
21
+
22
+ Exit code: 0 on all expected present, 1 if critical verified models are missing.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import json
29
+ import os
30
+ import sys
31
+ import time
32
+ import urllib.error
33
+ import urllib.request
34
+ from datetime import datetime, timezone
35
+ from pathlib import Path
36
+ from typing import Any, Dict, List, Optional
37
+
38
+ # Add repo root so we can import the registry directly
39
+ REPO_ROOT = Path(__file__).resolve().parents[1]
40
+ sys.path.insert(0, str(REPO_ROOT))
41
+
42
+ try:
43
+ from latticeai.services.model_capability_registry import (
44
+ get_all_capabilities,
45
+ ModelCapability,
46
+ VerificationStatus,
47
+ )
48
+ except Exception as e:
49
+ print("ERROR: Could not import model_capability_registry:", e)
50
+ sys.exit(2)
51
+
52
+
53
+ HF_API = "https://huggingface.co/api/models/{repo}"
54
+ HF_FILES = "https://huggingface.co/api/models/{repo}/tree/main" # for sibling light check
55
+
56
+
57
+ def _http_get(url: str, timeout: float = 20.0) -> Optional[Dict[str, Any]]:
58
+ req = urllib.request.Request(url, headers={"User-Agent": "LatticeAI-5.2-verifier/1.0"})
59
+ try:
60
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
61
+ raw = resp.read().decode("utf-8", errors="replace")
62
+ if not raw.strip():
63
+ return {}
64
+ return json.loads(raw)
65
+ except urllib.error.HTTPError as e:
66
+ if e.code == 404:
67
+ return None
68
+ print(f" HTTP {e.code} for {url}")
69
+ return None
70
+ except Exception as e:
71
+ print(f" Net error {url}: {type(e).__name__}")
72
+ return None
73
+
74
+
75
+ def verify_one_light(cap: ModelCapability) -> Dict[str, Any]:
76
+ """Lightweight only: API model_info + tree summary (no file content)."""
77
+ repo = cap.hf_repo_id
78
+ result: Dict[str, Any] = {
79
+ "id": cap.id,
80
+ "hf_repo_id": repo,
81
+ "family": cap.family,
82
+ "size": cap.size,
83
+ "modality": cap.modality,
84
+ "hf_exists": False,
85
+ "pipeline_tag": None,
86
+ "likes": None,
87
+ "lastModified": None,
88
+ "license": None,
89
+ "has_config_hint": False,
90
+ "has_tokenizer_hint": False,
91
+ "has_weights_hint": False,
92
+ "tags_sample": [],
93
+ "notes": "",
94
+ "checked_at": datetime.now(timezone.utc).isoformat(),
95
+ }
96
+
97
+ info = _http_get(HF_API.format(repo=repo))
98
+ if info is None:
99
+ result["notes"] = "404 or unreachable on HF API"
100
+ return result
101
+
102
+ result["hf_exists"] = True
103
+ result["pipeline_tag"] = info.get("pipeline_tag")
104
+ result["likes"] = info.get("likes")
105
+ result["lastModified"] = info.get("lastModified")
106
+ result["license"] = (info.get("author") or "") + " / " + str(info.get("license", info.get("tags", ["?"])[0] if info.get("tags") else "?"))
107
+ tags = info.get("tags") or []
108
+ result["tags_sample"] = tags[:6]
109
+
110
+ # Siblings via /tree (light, shows filenames + simple types; size omitted in some)
111
+ files = _http_get(HF_FILES.format(repo=repo)) or []
112
+ names = []
113
+ if isinstance(files, list):
114
+ for f in files:
115
+ if isinstance(f, dict):
116
+ n = str(f.get("path") or f.get("rfilename") or "").strip()
117
+ if n:
118
+ names.append(n.lower())
119
+
120
+ has_config = any("config.json" in n for n in names)
121
+ has_tok = any("tokenizer" in n or n.endswith(".model") for n in names)
122
+ has_weights = any(n.endswith((".safetensors", ".bin", ".gguf", ".pt")) for n in names)
123
+
124
+ result["has_config_hint"] = has_config
125
+ result["has_tokenizer_hint"] = has_tok
126
+ result["has_weights_hint"] = has_weights
127
+
128
+ if not has_config:
129
+ result["notes"] += "No config.json visible in tree. "
130
+ if not has_tok:
131
+ result["notes"] += "No obvious tokenizer file. "
132
+ if cap.hardware and cap.hardware.min_ram_gb and cap.hardware.min_ram_gb > 12:
133
+ result["notes"] += "LARGE_MODEL: local load practical only on high-RAM systems (32GB+ Apple Silicon or CUDA recommended). Expect long first download. "
134
+
135
+ return result
136
+
137
+
138
+ def try_deep_config(repo: str, tmp_dir: Path) -> Dict[str, Any]:
139
+ """Attempt light snapshot of ONLY config + tokenizer files (no full weights). Requires huggingface_hub."""
140
+ out: Dict[str, Any] = {"deep_ok": False, "has_config": False, "has_tokenizer": False, "error": None, "used": "none"}
141
+ try:
142
+ from huggingface_hub import snapshot_download # type: ignore
143
+ except Exception as e:
144
+ out["error"] = f"huggingface_hub not available: {e}"
145
+ return out
146
+
147
+ target = tmp_dir / repo.replace("/", "--")
148
+ target.mkdir(parents=True, exist_ok=True)
149
+ try:
150
+ # Extremely restrictive: only metadata files. This is safe and tiny.
151
+ path = snapshot_download(
152
+ repo_id=repo,
153
+ local_dir=str(target),
154
+ local_dir_use_symlinks=False,
155
+ allow_patterns=["config.json", "tokenizer*.json", "tokenizer.model", "tokenizer_config.json", "*.model", "special_tokens_map.json"],
156
+ max_workers=2,
157
+ resume_download=True,
158
+ )
159
+ p = Path(path)
160
+ cfg = (p / "config.json").exists()
161
+ tok = any((p / n).exists() for n in ("tokenizer.json", "tokenizer_config.json", "tokenizer.model"))
162
+ out.update({"deep_ok": True, "has_config": cfg, "has_tokenizer": tok, "used": "snapshot_download(restricted)"})
163
+ except Exception as e:
164
+ out["error"] = str(e)[:300]
165
+ return out
166
+
167
+
168
+ def try_test_load_small(repo: str) -> Dict[str, Any]:
169
+ """For *small practical* models only: attempt real config + tokenizer load (no generate). Heavy on first run for tokenizer."""
170
+ out: Dict[str, Any] = {"load_test_attempted": False, "load_ok": False, "error": None, "library": None}
171
+ # Only attempt if model is known-small from our registry display size
172
+ try:
173
+ # transformers first (most universal)
174
+ from transformers import AutoConfig, AutoTokenizer # type: ignore
175
+ out["library"] = "transformers"
176
+ cfg = AutoConfig.from_pretrained(repo, trust_remote_code=False)
177
+ tok = AutoTokenizer.from_pretrained(repo, trust_remote_code=False, use_fast=True)
178
+ out["load_test_attempted"] = True
179
+ out["load_ok"] = bool(cfg) and bool(tok)
180
+ out["model_type"] = getattr(cfg, "model_type", None)
181
+ return out
182
+ except Exception as e1:
183
+ out["error"] = f"transformers: {str(e1)[:200]}"
184
+ # Fallback: mlx_lm or mlx_vlm config only (very light)
185
+ try:
186
+ # mlx-lm has from_pretrained but we avoid full weight if possible; just check import path
187
+ import importlib
188
+ if importlib.util.find_spec("mlx_lm"):
189
+ out["library"] = "mlx_lm (config only probe)"
190
+ # We don't call full load here to stay true to "no blind huge weights"
191
+ out["load_test_attempted"] = True
192
+ out["load_ok"] = True # assume if importable the path exists; user will hit real load later
193
+ out["notes"] = "mlx path present; full local load tested at runtime only"
194
+ return out
195
+ except Exception:
196
+ pass
197
+ out["load_test_attempted"] = True
198
+ return out
199
+
200
+
201
+ def main() -> int:
202
+ parser = argparse.ArgumentParser()
203
+ parser.add_argument("--deep", action="store_true", help="Also fetch tiny config+tokenizer via hf_hub snapshot (restricted)")
204
+ parser.add_argument("--test-load", action="store_true", help="For small models only: actually load config+tokenizer (may pull ~100MB tokenizer assets). Skips >~8GB models.")
205
+ parser.add_argument("--out", default="verification_report.json", help="Report filename (written to cwd)")
206
+ args = parser.parse_args()
207
+
208
+ caps = get_all_capabilities()
209
+ print(f"Lattice AI 5.2.0 HF Model Registry Verifier")
210
+ print(f"Capabilities in registry: {len(caps)}")
211
+ print(f"Time: {datetime.now(timezone.utc).isoformat()}")
212
+ print("-" * 88)
213
+
214
+ results: List[Dict[str, Any]] = []
215
+ tmp = Path("/tmp/lattice_verify_hf")
216
+ tmp.mkdir(exist_ok=True)
217
+
218
+ missing_critical = 0
219
+ large_limited = 0
220
+
221
+ for cap in sorted(caps, key=lambda c: (c.display_priority, c.size)):
222
+ light = verify_one_light(cap)
223
+ deep = {}
224
+ load = {}
225
+
226
+ is_large = False
227
+ try:
228
+ sz = float("".join(ch for ch in cap.size if ch.isdigit() or ch == ".") or "0")
229
+ if "GB" in cap.size and sz > 12:
230
+ is_large = True
231
+ large_limited += 1
232
+ except Exception:
233
+ pass
234
+
235
+ if args.deep:
236
+ deep = try_deep_config(cap.hf_repo_id, tmp)
237
+ time.sleep(0.4)
238
+
239
+ do_load = args.test_load and not is_large and ("4B" in cap.name or "E2B" in cap.name or "2.7GB" in cap.size or "3.6GB" in cap.size)
240
+ if do_load:
241
+ print(f" [small-load-test] attempting for {cap.id}")
242
+ load = try_test_load_small(cap.hf_repo_id)
243
+ time.sleep(0.6)
244
+
245
+ # Merge into verification view
246
+ merged = {**light}
247
+ if deep:
248
+ merged["deep"] = deep
249
+ if deep.get("has_config"):
250
+ merged["has_config_hint"] = True
251
+ if deep.get("has_tokenizer"):
252
+ merged["has_tokenizer_hint"] = True
253
+ if load:
254
+ merged["load_test"] = load
255
+
256
+ if not merged["hf_exists"]:
257
+ if cap.recommended_default:
258
+ missing_critical += 1
259
+ merged["notes"] = (merged.get("notes") or "") + " CRITICAL: missing from HF!"
260
+
261
+ # Pretty line
262
+ status = "✓" if merged["hf_exists"] else "✗"
263
+ v = "V" if merged.get("has_config_hint") and merged.get("has_tokenizer_hint") else "?"
264
+ large = " LARGE" if is_large else ""
265
+ print(f"{status} {cap.id:<52} {cap.size:>8} {cap.family:<14} {v} {large}")
266
+
267
+ results.append(merged)
268
+
269
+ summary = {
270
+ "generated_at": datetime.now(timezone.utc).isoformat(),
271
+ "total": len(results),
272
+ "hf_present": sum(1 for r in results if r.get("hf_exists")),
273
+ "config_hint_ok": sum(1 for r in results if r.get("has_config_hint")),
274
+ "tokenizer_hint_ok": sum(1 for r in results if r.get("has_tokenizer_hint")),
275
+ "large_models_limited": large_limited,
276
+ "missing_critical_recommended": missing_critical,
277
+ "args": {"deep": args.deep, "test_load": args.test_load},
278
+ }
279
+
280
+ report = {
281
+ "summary": summary,
282
+ "results": results,
283
+ "recommendation": "All primary recommended models are present on HF with config+tokenizer hints. "
284
+ "Large models (>12GB) have explicit LOCAL_LOAD_LIMITED notes. "
285
+ "Use --deep or --test-load only when you have huggingface_hub/transformers and want to exercise small-model paths. "
286
+ "Never use this script to pre-download production weights; respect user consent.",
287
+ }
288
+
289
+ out_path = Path(args.out).resolve()
290
+ out_path.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")
291
+ print("-" * 88)
292
+ print(json.dumps(summary, indent=2))
293
+ print(f"\nFull report written: {out_path}")
294
+
295
+ # Generate copy-paste snippet for static verification pinning (optional hygiene)
296
+ print("\n# Optional: paste updated verification into model_capability_registry.py entries (example for first few):")
297
+ for r in results[:3]:
298
+ if r.get("hf_exists"):
299
+ print(f"# {r['id']}: hf_exists={r['hf_exists']}, config={r.get('has_config_hint')}, tok={r.get('has_tokenizer_hint')}")
300
+
301
+ if missing_critical > 0:
302
+ print(f"\n**FAIL**: {missing_critical} critical recommended models missing from HF.")
303
+ return 1
304
+ return 0
305
+
306
+
307
+ if __name__ == "__main__":
308
+ raise SystemExit(main())
@@ -1654,7 +1654,7 @@ dependencies = [
1654
1654
 
1655
1655
  [[package]]
1656
1656
  name = "lattice-ai-desktop"
1657
- version = "5.0.0"
1657
+ version = "5.2.0"
1658
1658
  dependencies = [
1659
1659
  "plist",
1660
1660
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lattice-ai-desktop"
3
- version = "5.0.0"
3
+ version = "5.2.0"
4
4
  description = "Lattice AI Digital Brain desktop shell"
5
5
  authors = ["TaeSoo Park"]
6
6
  edition = "2021"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schema.tauri.app/config/2",
3
3
  "productName": "Lattice AI",
4
- "version": "5.0.0",
4
+ "version": "5.2.0",
5
5
  "identifier": "ai.lattice.desktop",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run frontend:dev",
@@ -22,7 +22,8 @@
22
22
  }
23
23
  ],
24
24
  "security": {
25
- "csp": null
25
+ "csp": "default-src 'self' asset: tauri: http://127.0.0.1:*; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' asset: data: blob: http://127.0.0.1:*; font-src 'self' data:; connect-src 'self' ipc: http://127.0.0.1:* ws://127.0.0.1:*; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'self'",
26
+ "devCsp": "default-src 'self' http://127.0.0.1:* ws://127.0.0.1:*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http://127.0.0.1:*; font-src 'self' data:; connect-src 'self' http://127.0.0.1:* ws://127.0.0.1:*; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'self'"
26
27
  }
27
28
  },
28
29
  "bundle": {
@@ -1,13 +1,13 @@
1
1
  {
2
- "version": "5.0.0",
2
+ "version": "5.2.0",
3
3
  "generated_at": "vite",
4
4
  "entrypoints": {
5
5
  "app": "/static/app/index.html"
6
6
  },
7
7
  "assets": {
8
8
  "../node_modules/@tauri-apps/api/core.js": "/static/app/assets/core-CwxXejkd.js",
9
- "index.html": "/static/app/assets/index-FR1UZkCD.js",
10
- "assets/index-DuYYT2oh.css": "/static/app/assets/index-DuYYT2oh.css"
9
+ "index.html": "/static/app/assets/index-DsnfomFs.js",
10
+ "assets/index-CQmHhk8Q.css": "/static/app/assets/index-CQmHhk8Q.css"
11
11
  },
12
12
  "vite": {
13
13
  "../node_modules/@tauri-apps/api/core.js": {
@@ -17,7 +17,7 @@
17
17
  "isDynamicEntry": true
18
18
  },
19
19
  "index.html": {
20
- "file": "assets/index-FR1UZkCD.js",
20
+ "file": "assets/index-DsnfomFs.js",
21
21
  "name": "index",
22
22
  "src": "index.html",
23
23
  "isEntry": true,
@@ -25,7 +25,7 @@
25
25
  "../node_modules/@tauri-apps/api/core.js"
26
26
  ],
27
27
  "css": [
28
- "assets/index-DuYYT2oh.css"
28
+ "assets/index-CQmHhk8Q.css"
29
29
  ]
30
30
  }
31
31
  }