ltcai 7.5.0 → 7.7.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 (61) hide show
  1. package/README.md +48 -44
  2. package/docs/CHANGELOG.md +45 -0
  3. package/frontend/src/App.tsx +1 -1
  4. package/frontend/src/api/client.ts +9 -2
  5. package/frontend/src/components/LivingBrain.tsx +9 -1
  6. package/frontend/src/components/ProductFlow.tsx +64 -1
  7. package/frontend/src/components/onboarding/RecommendationScreen.tsx +36 -28
  8. package/frontend/src/components/onboarding/recommendationModel.ts +1 -1
  9. package/frontend/src/features/brain/BrainConversation.tsx +123 -2
  10. package/frontend/src/features/brain/BrainHome.tsx +34 -0
  11. package/frontend/src/features/review/ReviewInbox.tsx +2 -2
  12. package/frontend/src/features/review/reviewHelpers.ts +11 -11
  13. package/frontend/src/i18n.ts +148 -16
  14. package/frontend/src/pages/Act.tsx +28 -14
  15. package/frontend/src/routes.ts +6 -6
  16. package/frontend/src/styles.css +398 -0
  17. package/lattice_brain/__init__.py +1 -1
  18. package/lattice_brain/runtime/multi_agent.py +1 -1
  19. package/latticeai/__init__.py +1 -1
  20. package/latticeai/core/local_embeddings.py +4 -3
  21. package/latticeai/core/marketplace.py +1 -1
  22. package/latticeai/core/sessions.py +8 -2
  23. package/latticeai/core/workspace_os.py +1 -1
  24. package/latticeai/models/router.py +19 -0
  25. package/latticeai/services/architecture_readiness.py +119 -0
  26. package/latticeai/services/model_runtime.py +8 -3
  27. package/latticeai/services/product_readiness.py +159 -0
  28. package/package.json +1 -1
  29. package/scripts/product_readiness.py +37 -0
  30. package/scripts/pts-claudecode-discord-bridge.mjs +20 -3
  31. package/src-tauri/Cargo.lock +1 -1
  32. package/src-tauri/Cargo.toml +1 -1
  33. package/src-tauri/tauri.conf.json +1 -1
  34. package/static/app/asset-manifest.json +28 -28
  35. package/static/app/assets/Act-CX5hL0Z3.js +2 -0
  36. package/static/app/assets/{Act-Di4tRFWY.js.map → Act-CX5hL0Z3.js.map} +1 -1
  37. package/static/app/assets/{Brain-BZB3Gy9w.js → Brain-m19en5wz.js} +3 -3
  38. package/static/app/assets/{Brain-BZB3Gy9w.js.map → Brain-m19en5wz.js.map} +1 -1
  39. package/static/app/assets/Capture-CdFSrZnA.js +2 -0
  40. package/static/app/assets/Capture-CdFSrZnA.js.map +1 -0
  41. package/static/app/assets/Library-CZFVGSmG.js +2 -0
  42. package/static/app/assets/{Library-DAtDDLdg.js.map → Library-CZFVGSmG.js.map} +1 -1
  43. package/static/app/assets/System-CMoOoUrZ.js +2 -0
  44. package/static/app/assets/{System-DEu0xNUc.js.map → System-CMoOoUrZ.js.map} +1 -1
  45. package/static/app/assets/index-B4_drWel.js +17 -0
  46. package/static/app/assets/index-B4_drWel.js.map +1 -0
  47. package/static/app/assets/index-DjpDeE0c.css +2 -0
  48. package/static/app/assets/{primitives-CdwcE--L.js → primitives-CFhU5Rka.js} +2 -2
  49. package/static/app/assets/{primitives-CdwcE--L.js.map → primitives-CFhU5Rka.js.map} +1 -1
  50. package/static/app/assets/textarea-uBpGDOJM.js +2 -0
  51. package/static/app/assets/{textarea-CqOdBPL1.js.map → textarea-uBpGDOJM.js.map} +1 -1
  52. package/static/app/index.html +2 -2
  53. package/static/app/assets/Act-Di4tRFWY.js +0 -2
  54. package/static/app/assets/Capture-tNyYWxnh.js +0 -2
  55. package/static/app/assets/Capture-tNyYWxnh.js.map +0 -1
  56. package/static/app/assets/Library-DAtDDLdg.js +0 -2
  57. package/static/app/assets/System-DEu0xNUc.js +0 -2
  58. package/static/app/assets/index-Bi_bpigM.css +0 -2
  59. package/static/app/assets/index-COuGp7_5.js +0 -17
  60. package/static/app/assets/index-COuGp7_5.js.map +0 -1
  61. package/static/app/assets/textarea-CqOdBPL1.js +0 -2
@@ -32,6 +32,7 @@ from latticeai.models.router import (
32
32
  HF_MODELS_ROOT,
33
33
  OPENAI_COMPATIBLE_PROVIDERS,
34
34
  ensure_mlx_runtime,
35
+ hf_cache_model_dir,
35
36
  hf_model_dir,
36
37
  parse_model_ref,
37
38
  )
@@ -429,10 +430,12 @@ def engine_support_status(engine: str) -> Dict[str, object]:
429
430
 
430
431
  def hf_model_ready(repo_id: str, provider: str = "local_mlx") -> bool:
431
432
  model_dir = hf_model_dir(repo_id)
432
- if provider == "vllm" and (not model_dir.exists() or not model_dir.is_dir()):
433
+ if provider in {"local_mlx", "vllm"} and (not model_dir.exists() or not model_dir.is_dir()):
433
434
  hf_cache_repo = Path.home() / ".cache" / "huggingface" / "hub" / f"models--{repo_id.replace('/', '--')}"
434
435
  if hf_cache_repo.exists() and any(hf_cache_repo.glob("snapshots/*")):
435
- return True
436
+ if provider == "vllm":
437
+ return True
438
+ return hf_cache_model_dir(repo_id) is not None
436
439
  return False
437
440
  if not model_dir.exists() or not model_dir.is_dir():
438
441
  return False
@@ -520,6 +523,8 @@ def download_hf_model(
520
523
 
521
524
  target_dir = hf_model_dir(repo_id)
522
525
  if hf_model_ready(repo_id, provider):
526
+ cached_dir = hf_cache_model_dir(repo_id) if provider == "local_mlx" else None
527
+ resolved_dir = cached_dir or target_dir
523
528
  if progress_emit:
524
529
  progress_emit(model_download_progress_payload(
525
530
  "download",
@@ -529,7 +534,7 @@ def download_hf_model(
529
534
  total_bytes=0,
530
535
  eta_seconds=0,
531
536
  ))
532
- return {"model": repo_id, "path": str(target_dir), "cached": True}
537
+ return {"model": repo_id, "path": str(resolved_dir), "cached": True}
533
538
 
534
539
  target_dir.mkdir(parents=True, exist_ok=True)
535
540
  try:
@@ -0,0 +1,159 @@
1
+ """Machine-checkable *product* readiness gates for the 7.7 line.
2
+
3
+ Where ``architecture_readiness`` proves the internal structure is sound, this
4
+ module answers the product question the 7.7 release exists to settle: *would
5
+ anyone looking at this call it a finished product?* It does so honestly — every
6
+ gate is backed by evidence that is probed on disk, so a gate only reports
7
+ ``complete`` when its evidence actually resolves. The same report can be printed
8
+ by ``scripts/product_readiness.py`` and re-run after every change, which is the
9
+ point: completeness is something we keep measuring, not a one-time claim.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List
17
+
18
+ from latticeai.services.architecture_readiness import architecture_readiness
19
+
20
+ PRODUCT_VERSION_TARGET = "7.7.0"
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class ProductGate:
25
+ id: str
26
+ title: str
27
+ # Evidence is either a repo-relative path that must exist, or
28
+ # "path::needle" meaning the file must exist and contain ``needle``.
29
+ evidence: List[str]
30
+
31
+
32
+ PRODUCT_GATES: List[ProductGate] = [
33
+ ProductGate(
34
+ id="first-run",
35
+ title="First five minutes lands without a manual",
36
+ evidence=[
37
+ "frontend/src/components/ProductFlow.tsx::WakeBrainScreen",
38
+ "frontend/src/features/brain/BrainConversation.tsx::ProductCommandCenter",
39
+ "frontend/src/features/brain/BrainHome.tsx",
40
+ "auto_setup.py",
41
+ "setup_wizard.py",
42
+ ],
43
+ ),
44
+ ProductGate(
45
+ id="answer-proof",
46
+ title="Answers carry memory proof and citations",
47
+ evidence=[
48
+ "latticeai/api/memory.py::brain-proof",
49
+ "scripts/brain_quality_eval.py",
50
+ ],
51
+ ),
52
+ ProductGate(
53
+ id="local-first-trust",
54
+ title="Local-first privacy is stated and bounded",
55
+ evidence=[
56
+ "PRIVACY.md",
57
+ "PUBLIC_MODE.md",
58
+ "SECURITY.md",
59
+ ],
60
+ ),
61
+ ProductGate(
62
+ id="packaging",
63
+ title="One command produces shippable artifacts",
64
+ evidence=[
65
+ "package.json::release:artifacts",
66
+ "package.json::release:validate",
67
+ "README.md::dist/ltcai-7.7.0-py3-none-any.whl",
68
+ "README.md::dist/ltcai-7.7.0.tar.gz",
69
+ "README.md::dist/ltcai-7.7.0.vsix",
70
+ "README.md::ltcai-7.7.0.tgz",
71
+ "scripts/validate_release_artifacts.py",
72
+ "scripts/release_smoke.py",
73
+ "Dockerfile",
74
+ ],
75
+ ),
76
+ ProductGate(
77
+ id="architecture-closed",
78
+ title="Architecture readiness is complete",
79
+ evidence=["latticeai/services/architecture_readiness.py"],
80
+ ),
81
+ ProductGate(
82
+ id="trust-docs",
83
+ title="Release story is documented and honest",
84
+ evidence=[
85
+ "README.md",
86
+ "README.md::The current release is **7.7.0",
87
+ "SECURITY.md::7.7.x (latest)",
88
+ "vscode-extension/README.md::**7.7.0",
89
+ "docs/CHANGELOG.md::## [7.7.0]",
90
+ "FEATURE_STATUS.md",
91
+ "RELEASE_NOTES_v7.7.0.md",
92
+ ],
93
+ ),
94
+ ProductGate(
95
+ id="quality-gates",
96
+ title="Quality is guarded by repeatable gates",
97
+ evidence=[
98
+ "scripts/brain_quality_eval.py",
99
+ "scripts/product_readiness.py",
100
+ "tests/unit/test_v77_product_readiness.py",
101
+ "tests/visual/v3.spec.js::제품 상태판",
102
+ ".github/workflows/ci.yml::scripts/product_readiness.py",
103
+ ".github/workflows/release.yml::npm run lint",
104
+ ],
105
+ ),
106
+ ]
107
+
108
+
109
+ def _evidence_resolves(root: Path, evidence: str) -> bool:
110
+ if "::" in evidence:
111
+ rel, needle = evidence.split("::", 1)
112
+ target = root / rel
113
+ if not target.is_file():
114
+ return False
115
+ try:
116
+ return needle in target.read_text(encoding="utf-8", errors="ignore")
117
+ except OSError:
118
+ return False
119
+ return (root / evidence).exists()
120
+
121
+
122
+ def product_readiness(root: Path | None = None) -> Dict[str, Any]:
123
+ if root is None:
124
+ root = Path(__file__).resolve().parents[2]
125
+
126
+ arch = architecture_readiness(root)
127
+
128
+ gate_reports: List[Dict[str, Any]] = []
129
+ for gate in PRODUCT_GATES:
130
+ missing = [e for e in gate.evidence if not _evidence_resolves(root, e)]
131
+ # The architecture gate is satisfied by the composed report, not just
132
+ # the file existing — fold its status in so this score can never claim
133
+ # product completeness while the structure underneath is incomplete.
134
+ if gate.id == "architecture-closed" and arch.get("status") != "complete":
135
+ missing = missing or ["architecture_readiness().status != complete"]
136
+ gate_reports.append(
137
+ {
138
+ "id": gate.id,
139
+ "title": gate.title,
140
+ "status": "complete" if not missing else "incomplete",
141
+ "evidence": gate.evidence,
142
+ "missing": missing,
143
+ }
144
+ )
145
+
146
+ complete = sum(1 for g in gate_reports if g["status"] == "complete")
147
+ total = len(gate_reports)
148
+ return {
149
+ "status": "complete" if complete == total else "incomplete",
150
+ "version_target": PRODUCT_VERSION_TARGET,
151
+ "score": f"{complete}/{total}",
152
+ "gates": gate_reports,
153
+ "architecture": arch["status"],
154
+ "metrics": {
155
+ "product_gates": total,
156
+ "product_gates_complete": complete,
157
+ **arch.get("metrics", {}),
158
+ },
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "7.5.0",
3
+ "version": "7.7.0",
4
4
  "description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
5
5
  "homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
6
6
  "repository": {
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env python3
2
+ """Print the 7.7 product-readiness scorecard and fail CI when incomplete.
3
+
4
+ Run it as often as you like — it re-probes the repo every time, so it is the
5
+ single objective answer to "is this a finished product yet?". Exit code is 0
6
+ only when every product gate resolves its evidence on disk.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ REPO_ROOT = Path(__file__).resolve().parents[1]
15
+ if str(REPO_ROOT) not in sys.path:
16
+ sys.path.insert(0, str(REPO_ROOT))
17
+
18
+ from latticeai.services.product_readiness import product_readiness # noqa: E402
19
+
20
+
21
+ def main() -> int:
22
+ report = product_readiness(REPO_ROOT)
23
+ print(f"Lattice AI product readiness — target {report['version_target']}")
24
+ print(f"Score: {report['score']} (architecture: {report['architecture']})")
25
+ print("-" * 60)
26
+ for gate in report["gates"]:
27
+ mark = "✓" if gate["status"] == "complete" else "✗"
28
+ print(f"{mark} {gate['id']:<22} {gate['title']}")
29
+ for miss in gate["missing"]:
30
+ print(f" missing: {miss}")
31
+ print("-" * 60)
32
+ print(f"STATUS: {report['status'].upper()}")
33
+ return 0 if report["status"] == "complete" else 1
34
+
35
+
36
+ if __name__ == "__main__":
37
+ raise SystemExit(main())
@@ -44,6 +44,18 @@ function stripAnsi(text) {
44
44
  return String(text || "").replace(/\u001b\[[0-9;]*m/g, "").trim();
45
45
  }
46
46
 
47
+ function formatClaudeError(error) {
48
+ const message = stripAnsi(error?.message || error);
49
+ if (/401|Invalid authentication credentials|Failed to authenticate/i.test(message)) {
50
+ return [
51
+ "Claude Code 인증이 끊겼습니다.",
52
+ "로컬에서 `claude`를 열어 다시 로그인한 뒤 `pts_claudecode` 브리지를 재시작해야 합니다.",
53
+ "Discord 토큰 문제는 아니고, `/opt/homebrew/bin/claude -p` 호출이 401로 실패하고 있습니다.",
54
+ ].join(" ");
55
+ }
56
+ return `pts_claudecode 브리지 오류: ${message.slice(0, 800)}`;
57
+ }
58
+
47
59
  function isAllowed(message, botId) {
48
60
  if (message.author.id === botId) return false;
49
61
  if (message.channelId !== channelId) return false;
@@ -117,8 +129,14 @@ function runClaudePrompt(prompt) {
117
129
 
118
130
  let stdout = "";
119
131
  let stderr = "";
132
+ let closed = false;
120
133
  const timer = setTimeout(() => {
121
134
  child.kill("SIGTERM");
135
+ setTimeout(() => {
136
+ if (!closed) {
137
+ child.kill("SIGKILL");
138
+ }
139
+ }, 5000).unref();
122
140
  }, runTimeoutMs);
123
141
 
124
142
  child.stdout.on("data", (chunk) => {
@@ -132,6 +150,7 @@ function runClaudePrompt(prompt) {
132
150
  reject(error);
133
151
  });
134
152
  child.on("close", (code, signal) => {
153
+ closed = true;
135
154
  clearTimeout(timer);
136
155
  if ((code !== 0 || signal) && !stdout.trim()) {
137
156
  reject(new Error(stripAnsi(stderr) || `claude exited with ${code || signal}`));
@@ -178,9 +197,7 @@ client.on("messageCreate", async (message) => {
178
197
  : reply;
179
198
  await message.reply(cleanReply || "pts_claudecode 응답 생성에 실패했습니다.");
180
199
  } catch (error) {
181
- await message.reply(
182
- `pts_claudecode 브리지 오류: ${String(error.message || error).slice(0, 800)}`,
183
- );
200
+ await message.reply(formatClaudeError(error));
184
201
  } finally {
185
202
  busy = false;
186
203
  }
@@ -1584,7 +1584,7 @@ dependencies = [
1584
1584
 
1585
1585
  [[package]]
1586
1586
  name = "lattice-ai-desktop"
1587
- version = "7.5.0"
1587
+ version = "7.7.0"
1588
1588
  dependencies = [
1589
1589
  "plist",
1590
1590
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lattice-ai-desktop"
3
- version = "7.5.0"
3
+ version = "7.7.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": "7.5.0",
4
+ "version": "7.7.0",
5
5
  "identifier": "ai.lattice.desktop",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run frontend:dev",
@@ -1,20 +1,20 @@
1
1
  {
2
- "version": "7.5.0",
2
+ "version": "7.7.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
- "_primitives-CdwcE--L.js": "/static/app/assets/primitives-CdwcE--L.js",
10
- "_textarea-CqOdBPL1.js": "/static/app/assets/textarea-CqOdBPL1.js",
11
- "index.html": "/static/app/assets/index-COuGp7_5.js",
12
- "assets/index-Bi_bpigM.css": "/static/app/assets/index-Bi_bpigM.css",
13
- "src/pages/Act.tsx": "/static/app/assets/Act-Di4tRFWY.js",
14
- "src/pages/Brain.tsx": "/static/app/assets/Brain-BZB3Gy9w.js",
15
- "src/pages/Capture.tsx": "/static/app/assets/Capture-tNyYWxnh.js",
16
- "src/pages/Library.tsx": "/static/app/assets/Library-DAtDDLdg.js",
17
- "src/pages/System.tsx": "/static/app/assets/System-DEu0xNUc.js"
9
+ "_primitives-CFhU5Rka.js": "/static/app/assets/primitives-CFhU5Rka.js",
10
+ "_textarea-uBpGDOJM.js": "/static/app/assets/textarea-uBpGDOJM.js",
11
+ "index.html": "/static/app/assets/index-B4_drWel.js",
12
+ "assets/index-DjpDeE0c.css": "/static/app/assets/index-DjpDeE0c.css",
13
+ "src/pages/Act.tsx": "/static/app/assets/Act-CX5hL0Z3.js",
14
+ "src/pages/Brain.tsx": "/static/app/assets/Brain-m19en5wz.js",
15
+ "src/pages/Capture.tsx": "/static/app/assets/Capture-CdFSrZnA.js",
16
+ "src/pages/Library.tsx": "/static/app/assets/Library-CZFVGSmG.js",
17
+ "src/pages/System.tsx": "/static/app/assets/System-CMoOoUrZ.js"
18
18
  },
19
19
  "vite": {
20
20
  "../node_modules/@tauri-apps/api/core.js": {
@@ -23,22 +23,22 @@
23
23
  "src": "../node_modules/@tauri-apps/api/core.js",
24
24
  "isDynamicEntry": true
25
25
  },
26
- "_primitives-CdwcE--L.js": {
27
- "file": "assets/primitives-CdwcE--L.js",
26
+ "_primitives-CFhU5Rka.js": {
27
+ "file": "assets/primitives-CFhU5Rka.js",
28
28
  "name": "primitives",
29
29
  "imports": [
30
30
  "index.html"
31
31
  ]
32
32
  },
33
- "_textarea-CqOdBPL1.js": {
34
- "file": "assets/textarea-CqOdBPL1.js",
33
+ "_textarea-uBpGDOJM.js": {
34
+ "file": "assets/textarea-uBpGDOJM.js",
35
35
  "name": "textarea",
36
36
  "imports": [
37
37
  "index.html"
38
38
  ]
39
39
  },
40
40
  "index.html": {
41
- "file": "assets/index-COuGp7_5.js",
41
+ "file": "assets/index-B4_drWel.js",
42
42
  "name": "index",
43
43
  "src": "index.html",
44
44
  "isEntry": true,
@@ -51,59 +51,59 @@
51
51
  "src/pages/System.tsx"
52
52
  ],
53
53
  "css": [
54
- "assets/index-Bi_bpigM.css"
54
+ "assets/index-DjpDeE0c.css"
55
55
  ]
56
56
  },
57
57
  "src/pages/Act.tsx": {
58
- "file": "assets/Act-Di4tRFWY.js",
58
+ "file": "assets/Act-CX5hL0Z3.js",
59
59
  "name": "Act",
60
60
  "src": "src/pages/Act.tsx",
61
61
  "isDynamicEntry": true,
62
62
  "imports": [
63
63
  "index.html",
64
- "_primitives-CdwcE--L.js",
65
- "_textarea-CqOdBPL1.js"
64
+ "_primitives-CFhU5Rka.js",
65
+ "_textarea-uBpGDOJM.js"
66
66
  ]
67
67
  },
68
68
  "src/pages/Brain.tsx": {
69
- "file": "assets/Brain-BZB3Gy9w.js",
69
+ "file": "assets/Brain-m19en5wz.js",
70
70
  "name": "Brain",
71
71
  "src": "src/pages/Brain.tsx",
72
72
  "isDynamicEntry": true,
73
73
  "imports": [
74
74
  "index.html",
75
- "_primitives-CdwcE--L.js",
76
- "_textarea-CqOdBPL1.js"
75
+ "_primitives-CFhU5Rka.js",
76
+ "_textarea-uBpGDOJM.js"
77
77
  ]
78
78
  },
79
79
  "src/pages/Capture.tsx": {
80
- "file": "assets/Capture-tNyYWxnh.js",
80
+ "file": "assets/Capture-CdFSrZnA.js",
81
81
  "name": "Capture",
82
82
  "src": "src/pages/Capture.tsx",
83
83
  "isDynamicEntry": true,
84
84
  "imports": [
85
85
  "index.html",
86
- "_primitives-CdwcE--L.js"
86
+ "_primitives-CFhU5Rka.js"
87
87
  ]
88
88
  },
89
89
  "src/pages/Library.tsx": {
90
- "file": "assets/Library-DAtDDLdg.js",
90
+ "file": "assets/Library-CZFVGSmG.js",
91
91
  "name": "Library",
92
92
  "src": "src/pages/Library.tsx",
93
93
  "isDynamicEntry": true,
94
94
  "imports": [
95
95
  "index.html",
96
- "_primitives-CdwcE--L.js"
96
+ "_primitives-CFhU5Rka.js"
97
97
  ]
98
98
  },
99
99
  "src/pages/System.tsx": {
100
- "file": "assets/System-DEu0xNUc.js",
100
+ "file": "assets/System-CMoOoUrZ.js",
101
101
  "name": "System",
102
102
  "src": "src/pages/System.tsx",
103
103
  "isDynamicEntry": true,
104
104
  "imports": [
105
105
  "index.html",
106
- "_primitives-CdwcE--L.js"
106
+ "_primitives-CFhU5Rka.js"
107
107
  ]
108
108
  }
109
109
  }