ltcai 9.3.0 → 9.4.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 (42) hide show
  1. package/README.md +28 -22
  2. package/docs/CHANGELOG.md +23 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +4 -3
  4. package/docs/DEVELOPMENT.md +8 -8
  5. package/docs/LEGACY_COMPATIBILITY.md +1 -1
  6. package/docs/ONBOARDING.md +2 -2
  7. package/docs/OPERATIONS.md +1 -1
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/docs/mcp-tools.md +1 -1
  12. package/lattice_brain/__init__.py +1 -1
  13. package/lattice_brain/runtime/multi_agent.py +1 -1
  14. package/latticeai/__init__.py +1 -1
  15. package/latticeai/api/automation_intelligence.py +129 -0
  16. package/latticeai/app_factory.py +13 -0
  17. package/latticeai/core/legacy_compatibility.py +1 -1
  18. package/latticeai/core/marketplace.py +1 -1
  19. package/latticeai/core/workspace_os.py +1 -1
  20. package/latticeai/runtime/persistence_runtime.py +8 -0
  21. package/latticeai/services/architecture_readiness.py +1 -1
  22. package/latticeai/services/automation_intelligence.py +450 -0
  23. package/latticeai/services/product_readiness.py +1 -1
  24. package/package.json +1 -1
  25. package/scripts/check_current_release_docs.mjs +1 -1
  26. package/src-tauri/Cargo.lock +1 -1
  27. package/src-tauri/Cargo.toml +1 -1
  28. package/src-tauri/tauri.conf.json +1 -1
  29. package/static/app/asset-manifest.json +11 -11
  30. package/static/app/assets/Act-t9oveJO7.js +1 -0
  31. package/static/app/assets/{Brain-Jd3V4Obm.js → Brain-DP-gpcEJ.js} +1 -1
  32. package/static/app/assets/{Capture-DLj9Uzed.js → Capture-DYknDKy8.js} +1 -1
  33. package/static/app/assets/{Library-dPbez7Tc.js → Library-DLyc_g8c.js} +1 -1
  34. package/static/app/assets/{System-CkROWBuq.js → System-BZgJ7tGu.js} +1 -1
  35. package/static/app/assets/{index-CAlo2Lm-.js → index-BeQ77vPs.js} +3 -3
  36. package/static/app/assets/index-Cl4S_9Id.css +2 -0
  37. package/static/app/assets/{primitives-DrwaJ7dy.js → primitives-DawfkPR4.js} +1 -1
  38. package/static/app/assets/{textarea-DNvCwmmK.js → textarea-a4Ir3SZS.js} +1 -1
  39. package/static/app/index.html +2 -2
  40. package/static/sw.js +1 -1
  41. package/static/app/assets/Act-ls57ctKH.js +0 -1
  42. package/static/app/assets/index-BRUqy2zk.css +0 -2
@@ -0,0 +1,450 @@
1
+ """Question-driven everyday automation intelligence (v9.4.0).
2
+
3
+ Users repeat themselves: the same "오늘 뭐 해야 하지?", "프로젝트 상태 정리해줘",
4
+ "그 폴더에 새 문서 뭐 들어왔어?" questions come back every day. This service
5
+ watches for those patterns and turns them into one-click, consent-first
6
+ automation suggestions:
7
+
8
+ * **question_patterns** — mines the user's own chat history for recurring
9
+ question intents. Clustering is deterministic and local (token-signature
10
+ similarity, no model call), so the evidence shown to the user is their own
11
+ literal questions.
12
+ * **suggestions** — converts recurring patterns and active local knowledge
13
+ sources (folders the user connected to the Brain) into concrete automation
14
+ proposals: a scheduled answer for a recurring question, a digest for a busy
15
+ knowledge folder, or one of the starter recipes when the intent matches.
16
+ * **build_suggestion_workflow** — turns an accepted suggestion into a
17
+ workflow definition using the same consent-first shape as the starter
18
+ recipes: installed as a disabled draft, review-queue gated, local-only,
19
+ no external actions.
20
+ * **overview** — one payload for the automation surface: suggestions,
21
+ installed automations with their enable state, and pattern evidence.
22
+
23
+ Every suggestion id is deterministic, so install calls are idempotent and the
24
+ UI can safely re-request suggestions without duplicates.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import hashlib
30
+ import logging
31
+ import re
32
+ from dataclasses import dataclass, field
33
+ from typing import Any, Dict, List, Optional
34
+
35
+ from latticeai.core.timeutil import now_iso as _now
36
+
37
+ LOGGER = logging.getLogger(__name__)
38
+
39
+ _MIN_PATTERN_COUNT = 2
40
+ _MAX_HISTORY = 4000
41
+ _SIGNATURE_SIMILARITY = 0.6
42
+
43
+ _QUESTION_HINT_RE = re.compile(
44
+ r"(\?|어때|뭐야|뭐가|뭘까|알려줘|보여줘|정리해|요약해|정리 좀|요약 좀|해줘"
45
+ r"|what|how|why|when|where|status|summar|remind|list|show me|tell me)",
46
+ re.IGNORECASE,
47
+ )
48
+
49
+ _STOPWORDS = {
50
+ "the", "a", "an", "is", "are", "was", "to", "of", "in", "on", "for", "and",
51
+ "or", "me", "my", "you", "please", "can", "could", "would", "it", "this",
52
+ "that", "do", "does", "what", "how", "about",
53
+ "좀", "그", "이", "저", "것", "거", "게", "내", "제", "나", "너", "우리",
54
+ "해줘", "해주세요", "주세요", "합니다", "있어", "있나", "있는지", "어떻게",
55
+ "뭐야", "뭐가", "알려줘", "보여줘",
56
+ }
57
+
58
+ # Intent buckets map recurring question language onto the starter recipes.
59
+ _INTENT_RULES = (
60
+ (
61
+ "digest",
62
+ re.compile(r"(오늘|하루|today|daily).{0,12}(정리|요약|digest|summary|기억|메모)|"
63
+ r"(정리|요약).{0,8}(해줘|해 줘|부탁)|summar(y|ize)", re.IGNORECASE),
64
+ "daily-memory-digest",
65
+ ),
66
+ (
67
+ "project_review",
68
+ re.compile(r"(프로젝트|project|진행|progress|status|상태|주간|weekly|이번 주)", re.IGNORECASE),
69
+ "weekly-project-review",
70
+ ),
71
+ (
72
+ "follow_up",
73
+ re.compile(r"(리마인드|remind|챙겨|잊지|deadline|마감|follow.?up|나중에|까먹)", re.IGNORECASE),
74
+ "follow-up-radar",
75
+ ),
76
+ )
77
+
78
+
79
+ def _tokens(text: str) -> List[str]:
80
+ return [
81
+ token
82
+ for token in re.findall(r"[\w가-힣]+", str(text or "").lower())
83
+ if len(token) > 1 and token not in _STOPWORDS
84
+ ]
85
+
86
+
87
+ def _signature(tokens: List[str]) -> frozenset:
88
+ return frozenset(tokens)
89
+
90
+
91
+ def _similarity(left: frozenset, right: frozenset) -> float:
92
+ if not left or not right:
93
+ return 0.0
94
+ return len(left & right) / len(left | right)
95
+
96
+
97
+ def _stable_id(prefix: str, seed: str) -> str:
98
+ return f"{prefix}-{hashlib.sha256(seed.encode('utf-8')).hexdigest()[:10]}"
99
+
100
+
101
+ @dataclass
102
+ class _Pattern:
103
+ representative: str
104
+ signature: frozenset
105
+ count: int = 1
106
+ last_asked: str = ""
107
+ intent: str = "recurring_question"
108
+ recipe_id: Optional[str] = None
109
+ examples: List[str] = field(default_factory=list)
110
+
111
+ def as_dict(self) -> Dict[str, Any]:
112
+ return {
113
+ "id": _stable_id("pat", " ".join(sorted(self.signature))),
114
+ "representative": self.representative[:160],
115
+ "count": self.count,
116
+ "last_asked": self.last_asked,
117
+ "intent": self.intent,
118
+ "recipe_id": self.recipe_id,
119
+ "examples": self.examples[:3],
120
+ }
121
+
122
+
123
+ class AutomationIntelligenceService:
124
+ def __init__(
125
+ self,
126
+ *,
127
+ conversation_store: Any = None,
128
+ knowledge_graph: Any = None,
129
+ store: Any = None,
130
+ enable_graph: bool = True,
131
+ ) -> None:
132
+ self._conversations = conversation_store
133
+ self._kg = knowledge_graph
134
+ self._store = store
135
+ self._enable_graph = bool(enable_graph and knowledge_graph is not None)
136
+
137
+ # ── question pattern mining ──────────────────────────────────────────
138
+
139
+ def _user_questions(
140
+ self, *, user_email: Optional[str], workspace_id: Optional[str]
141
+ ) -> List[Dict[str, Any]]:
142
+ if self._conversations is None:
143
+ return []
144
+ try:
145
+ items = self._conversations.history(
146
+ user_email=user_email,
147
+ allowed_workspaces={workspace_id} if workspace_id is not None else None,
148
+ include_legacy_global=workspace_id is None,
149
+ limit=_MAX_HISTORY,
150
+ )
151
+ except Exception:
152
+ LOGGER.exception("automation intelligence history read failed")
153
+ return []
154
+ questions = []
155
+ for item in items:
156
+ if item.get("role") != "user":
157
+ continue
158
+ content = str(item.get("content") or "").strip()
159
+ if not content or len(content) < 6 or content.startswith("/"):
160
+ continue
161
+ if not _QUESTION_HINT_RE.search(content):
162
+ continue
163
+ questions.append({"content": content, "timestamp": str(item.get("timestamp") or "")})
164
+ return questions
165
+
166
+ def question_patterns(
167
+ self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
168
+ ) -> Dict[str, Any]:
169
+ questions = self._user_questions(user_email=user_email, workspace_id=workspace_id)
170
+ patterns: List[_Pattern] = []
171
+ for question in questions:
172
+ tokens = _tokens(question["content"])
173
+ if len(tokens) < 2:
174
+ continue
175
+ signature = _signature(tokens)
176
+ match = None
177
+ for pattern in patterns:
178
+ if _similarity(pattern.signature, signature) >= _SIGNATURE_SIMILARITY:
179
+ match = pattern
180
+ break
181
+ if match is None:
182
+ patterns.append(
183
+ _Pattern(
184
+ representative=question["content"],
185
+ signature=signature,
186
+ last_asked=question["timestamp"],
187
+ examples=[question["content"]],
188
+ )
189
+ )
190
+ continue
191
+ match.count += 1
192
+ # Union keeps the cluster stable as phrasing drifts.
193
+ match.signature = match.signature | signature
194
+ if question["timestamp"] >= match.last_asked:
195
+ match.last_asked = question["timestamp"]
196
+ match.representative = question["content"]
197
+ if question["content"] not in match.examples:
198
+ match.examples.append(question["content"])
199
+
200
+ recurring = [p for p in patterns if p.count >= _MIN_PATTERN_COUNT]
201
+ for pattern in recurring:
202
+ for intent, rule, recipe_id in _INTENT_RULES:
203
+ if rule.search(pattern.representative):
204
+ pattern.intent = intent
205
+ pattern.recipe_id = recipe_id
206
+ break
207
+
208
+ recurring.sort(key=lambda p: (p.count, p.last_asked), reverse=True)
209
+ return {
210
+ "questions_scanned": len(questions),
211
+ "patterns": [p.as_dict() for p in recurring[:20]],
212
+ "generated_at": _now(),
213
+ }
214
+
215
+ # ── knowledge sources ────────────────────────────────────────────────
216
+
217
+ def _knowledge_sources(self) -> List[Dict[str, Any]]:
218
+ if not self._enable_graph or not hasattr(self._kg, "local_sources"):
219
+ return []
220
+ try:
221
+ return list(self._kg.local_sources().get("sources") or [])
222
+ except Exception:
223
+ LOGGER.exception("automation intelligence source read failed")
224
+ return []
225
+
226
+ # ── suggestions ──────────────────────────────────────────────────────
227
+
228
+ def _installed_suggestion_ids(self, *, workspace_id: Optional[str]) -> Dict[str, Dict[str, Any]]:
229
+ installed: Dict[str, Dict[str, Any]] = {}
230
+ if self._store is None:
231
+ return installed
232
+ try:
233
+ workflows = self._store.list_workflows(workspace_id=workspace_id).get("workflows") or []
234
+ except Exception:
235
+ LOGGER.exception("automation intelligence workflow read failed")
236
+ return installed
237
+ for workflow in workflows:
238
+ metadata = (workflow or {}).get("metadata") or {}
239
+ suggestion_id = metadata.get("suggestion_id")
240
+ if metadata.get("created_from") == "automation_suggestion" and suggestion_id:
241
+ installed[str(suggestion_id)] = workflow
242
+ return installed
243
+
244
+ def suggestions(
245
+ self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
246
+ ) -> Dict[str, Any]:
247
+ pattern_report = self.question_patterns(user_email=user_email, workspace_id=workspace_id)
248
+ installed = self._installed_suggestion_ids(workspace_id=workspace_id)
249
+
250
+ items: List[Dict[str, Any]] = []
251
+ for pattern in pattern_report["patterns"]:
252
+ suggestion_id = _stable_id("sug-q", pattern["id"])
253
+ items.append({
254
+ "id": suggestion_id,
255
+ "kind": "recurring_question",
256
+ "intent": pattern["intent"],
257
+ "recipe_id": pattern["recipe_id"],
258
+ "title": pattern["representative"],
259
+ "reason": {
260
+ "type": "repeated_question",
261
+ "count": pattern["count"],
262
+ "last_asked": pattern["last_asked"],
263
+ "examples": pattern["examples"],
264
+ },
265
+ "cadence": "daily",
266
+ "installed": suggestion_id in installed,
267
+ "workflow_id": (installed.get(suggestion_id) or {}).get("id"),
268
+ })
269
+
270
+ for source in self._knowledge_sources():
271
+ file_status = source.get("file_status") or {}
272
+ indexed = sum(int(v or 0) for v in file_status.values())
273
+ if indexed <= 0:
274
+ continue
275
+ suggestion_id = _stable_id("sug-src", str(source.get("id") or source.get("root_path") or ""))
276
+ items.append({
277
+ "id": suggestion_id,
278
+ "kind": "knowledge_source",
279
+ "intent": "source_digest",
280
+ "recipe_id": None,
281
+ "title": str(source.get("label") or source.get("root_path") or "knowledge folder"),
282
+ "reason": {
283
+ "type": "connected_source",
284
+ "root_path": source.get("root_path"),
285
+ "indexed_files": indexed,
286
+ "watch_enabled": bool(source.get("watch_enabled")),
287
+ },
288
+ "cadence": "when new knowledge arrives",
289
+ "installed": suggestion_id in installed,
290
+ "workflow_id": (installed.get(suggestion_id) or {}).get("id"),
291
+ })
292
+
293
+ return {
294
+ "suggestions": items,
295
+ "questions_scanned": pattern_report["questions_scanned"],
296
+ "consent": {
297
+ "default_state": "draft_disabled",
298
+ "local_only": True,
299
+ "external_actions": False,
300
+ "requires_user_enable": True,
301
+ "review_before_run": True,
302
+ },
303
+ "generated_at": _now(),
304
+ }
305
+
306
+ def find_suggestion(
307
+ self,
308
+ suggestion_id: str,
309
+ *,
310
+ user_email: Optional[str] = None,
311
+ workspace_id: Optional[str] = None,
312
+ ) -> Optional[Dict[str, Any]]:
313
+ for suggestion in self.suggestions(user_email=user_email, workspace_id=workspace_id)["suggestions"]:
314
+ if suggestion["id"] == suggestion_id:
315
+ return suggestion
316
+ return None
317
+
318
+ # ── workflow building ────────────────────────────────────────────────
319
+
320
+ def build_suggestion_workflow(
321
+ self, suggestion: Dict[str, Any], *, enabled: bool = False
322
+ ) -> Dict[str, Any]:
323
+ """Build a consent-first workflow definition from a suggestion.
324
+
325
+ Mirrors the starter-recipe shape: trigger → draft agent → review
326
+ output, installed disabled by default, review-queue gated, local
327
+ only, and stamped with provenance so installs stay idempotent.
328
+ """
329
+ kind = suggestion.get("kind")
330
+ title = str(suggestion.get("title") or "automation")[:80]
331
+ if kind == "knowledge_source":
332
+ trigger: Dict[str, Any] = {"trigger": "brain_event"}
333
+ trigger_name = "New knowledge in connected folder"
334
+ name = f"Folder digest: {title}"
335
+ reason = suggestion.get("reason") or {}
336
+ prompt = (
337
+ "New knowledge arrived from the connected folder "
338
+ f"'{reason.get('root_path') or title}'. Draft a short digest of what "
339
+ "was added, what changed, and any follow-ups it suggests. Use only "
340
+ "the local Brain; do not contact external services."
341
+ )
342
+ creates = ["folder digest", "change summary", "follow-up suggestions"]
343
+ else:
344
+ trigger = {"trigger": "interval", "interval_seconds": 86_400}
345
+ trigger_name = "User-enabled schedule"
346
+ name = f"Scheduled answer: {title}"
347
+ prompt = (
348
+ "The user repeatedly asks this question:\n"
349
+ f"“{suggestion.get('title')}”\n"
350
+ "Answer it from the current Brain (memories, knowledge graph, "
351
+ "recent activity) as a concise draft the user can review. "
352
+ "Use only local knowledge; do not contact external services."
353
+ )
354
+ creates = ["scheduled answer draft", "supporting evidence", "suggested follow-ups"]
355
+
356
+ trigger_config = {
357
+ **trigger,
358
+ "enabled": bool(enabled),
359
+ "review_queue": True,
360
+ "consent_required": True,
361
+ "local_only": True,
362
+ "external_actions": False,
363
+ }
364
+ return {
365
+ "name": name,
366
+ "nodes": [
367
+ {
368
+ "id": "trigger",
369
+ "type": "trigger",
370
+ "name": trigger_name,
371
+ "config": trigger_config,
372
+ "next": "draft",
373
+ },
374
+ {
375
+ "id": "draft",
376
+ "type": "agent",
377
+ "name": "Draft for review",
378
+ "config": {
379
+ "agent": "agent:planner",
380
+ "goal": prompt,
381
+ "prompt": prompt,
382
+ "roles": ["researcher", "planner", "executor", "reviewer"],
383
+ "mode": "draft",
384
+ "local_only": True,
385
+ "external_actions": False,
386
+ "requires_review": True,
387
+ },
388
+ "next": "output",
389
+ },
390
+ {
391
+ "id": "output",
392
+ "type": "output",
393
+ "name": "Review before saving",
394
+ "config": {
395
+ "value": "Draft ready for review. Save, edit, or discard it before it becomes durable memory.",
396
+ },
397
+ "next": None,
398
+ },
399
+ ],
400
+ "metadata": {
401
+ "created_from": "automation_suggestion",
402
+ "suggestion_id": suggestion.get("id"),
403
+ "suggestion_kind": kind,
404
+ "suggestion_title": title,
405
+ "suggestion_reason": suggestion.get("reason"),
406
+ "automation_state": "enabled" if enabled else "draft_disabled",
407
+ "local_only": True,
408
+ "external_actions": False,
409
+ "requires_user_enable": not enabled,
410
+ "creates": creates,
411
+ },
412
+ }
413
+
414
+ # ── overview for the automation surface ──────────────────────────────
415
+
416
+ def overview(
417
+ self, *, user_email: Optional[str] = None, workspace_id: Optional[str] = None
418
+ ) -> Dict[str, Any]:
419
+ suggestion_report = self.suggestions(user_email=user_email, workspace_id=workspace_id)
420
+ installed: List[Dict[str, Any]] = []
421
+ if self._store is not None:
422
+ try:
423
+ workflows = self._store.list_workflows(workspace_id=workspace_id).get("workflows") or []
424
+ except Exception:
425
+ LOGGER.exception("automation intelligence workflow read failed")
426
+ workflows = []
427
+ for workflow in workflows:
428
+ metadata = (workflow or {}).get("metadata") or {}
429
+ if metadata.get("created_from") not in {"automation_suggestion", "brain_automation_recipe"}:
430
+ continue
431
+ installed.append({
432
+ "id": workflow.get("id"),
433
+ "name": workflow.get("name"),
434
+ "created_from": metadata.get("created_from"),
435
+ "suggestion_id": metadata.get("suggestion_id"),
436
+ "recipe_id": metadata.get("recipe_id"),
437
+ "enabled": metadata.get("automation_state") == "enabled",
438
+ "requires_user_enable": bool(metadata.get("requires_user_enable", True)),
439
+ "creates": metadata.get("creates") or [],
440
+ })
441
+ return {
442
+ "suggestions": suggestion_report["suggestions"],
443
+ "questions_scanned": suggestion_report["questions_scanned"],
444
+ "installed": installed,
445
+ "consent": suggestion_report["consent"],
446
+ "generated_at": _now(),
447
+ }
448
+
449
+
450
+ __all__ = ["AutomationIntelligenceService"]
@@ -18,7 +18,7 @@ from typing import Any, Dict, List
18
18
 
19
19
  from latticeai.services.architecture_readiness import architecture_readiness
20
20
 
21
- PRODUCT_VERSION_TARGET = "9.3.0"
21
+ PRODUCT_VERSION_TARGET = "9.4.0"
22
22
 
23
23
 
24
24
  @dataclass(frozen=True)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcai",
3
- "version": "9.3.0",
3
+ "version": "9.4.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": {
@@ -6,7 +6,7 @@ const root = process.cwd();
6
6
  const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
7
7
  const version = pkg.version;
8
8
  const releaseDir = `output/release/v${version}`;
9
- const releaseTheme = "Proactive Brain Intelligence";
9
+ const releaseTheme = "Question-Driven Everyday Automation";
10
10
  const title = `${version} — ${releaseTheme}`;
11
11
  const escapedVersion = version.replaceAll(".", "\\.");
12
12
 
@@ -1584,7 +1584,7 @@ dependencies = [
1584
1584
 
1585
1585
  [[package]]
1586
1586
  name = "lattice-ai-desktop"
1587
- version = "9.3.0"
1587
+ version = "9.4.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 = "9.3.0"
3
+ version = "9.4.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": "9.3.0",
4
+ "version": "9.4.0",
5
5
  "identifier": "ai.lattice.desktop",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run frontend:dev",
@@ -1,19 +1,19 @@
1
1
  {
2
- "version": "9.3.0",
2
+ "version": "9.4.0",
3
3
  "generated_at": "vite",
4
4
  "entrypoints": {
5
- "app": "/static/app/assets/index-CAlo2Lm-.js"
5
+ "app": "/static/app/assets/index-BeQ77vPs.js"
6
6
  },
7
7
  "assets": {
8
8
  "../node_modules/@tauri-apps/api/core.js": "/static/app/assets/core-CwxXejkd.js",
9
- "_primitives-DrwaJ7dy.js": "/static/app/assets/primitives-DrwaJ7dy.js",
10
- "_textarea-DNvCwmmK.js": "/static/app/assets/textarea-DNvCwmmK.js",
11
- "index.html": "/static/app/assets/index-CAlo2Lm-.js",
12
- "assets/index-BRUqy2zk.css": "/static/app/assets/index-BRUqy2zk.css",
13
- "src/pages/Act.tsx": "/static/app/assets/Act-ls57ctKH.js",
14
- "src/pages/Brain.tsx": "/static/app/assets/Brain-Jd3V4Obm.js",
15
- "src/pages/Capture.tsx": "/static/app/assets/Capture-DLj9Uzed.js",
16
- "src/pages/Library.tsx": "/static/app/assets/Library-dPbez7Tc.js",
17
- "src/pages/System.tsx": "/static/app/assets/System-CkROWBuq.js"
9
+ "_primitives-DawfkPR4.js": "/static/app/assets/primitives-DawfkPR4.js",
10
+ "_textarea-a4Ir3SZS.js": "/static/app/assets/textarea-a4Ir3SZS.js",
11
+ "index.html": "/static/app/assets/index-BeQ77vPs.js",
12
+ "assets/index-Cl4S_9Id.css": "/static/app/assets/index-Cl4S_9Id.css",
13
+ "src/pages/Act.tsx": "/static/app/assets/Act-t9oveJO7.js",
14
+ "src/pages/Brain.tsx": "/static/app/assets/Brain-DP-gpcEJ.js",
15
+ "src/pages/Capture.tsx": "/static/app/assets/Capture-DYknDKy8.js",
16
+ "src/pages/Library.tsx": "/static/app/assets/Library-DLyc_g8c.js",
17
+ "src/pages/System.tsx": "/static/app/assets/System-BZgJ7tGu.js"
18
18
  }
19
19
  }