ltcai 8.1.0 → 8.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 (110) hide show
  1. package/README.md +20 -41
  2. package/docs/CHANGELOG.md +38 -874
  3. package/docs/DEVELOPMENT.md +26 -3
  4. package/docs/LEGACY_COMPATIBILITY.md +3 -1
  5. package/docs/TRUST_MODEL.md +26 -26
  6. package/docs/WHY_LATTICE.md +28 -25
  7. package/docs/kg-schema.md +6 -4
  8. package/lattice_brain/__init__.py +1 -1
  9. package/lattice_brain/graph/schema.py +6 -0
  10. package/lattice_brain/runtime/multi_agent.py +1 -1
  11. package/latticeai/__init__.py +1 -1
  12. package/latticeai/api/computer_use.py +2 -0
  13. package/latticeai/api/memory.py +13 -0
  14. package/latticeai/app_factory.py +30 -28
  15. package/latticeai/brain/__init__.py +1 -1
  16. package/latticeai/core/config.py +10 -0
  17. package/latticeai/core/marketplace.py +1 -1
  18. package/latticeai/core/tool_registry.py +10 -0
  19. package/latticeai/core/workspace_graph_trace.py +132 -0
  20. package/latticeai/core/workspace_memory.py +75 -0
  21. package/latticeai/core/workspace_os.py +135 -1283
  22. package/latticeai/core/workspace_os_utils.py +132 -0
  23. package/latticeai/core/workspace_permissions.py +99 -0
  24. package/latticeai/core/workspace_plugins.py +97 -0
  25. package/latticeai/core/workspace_runs.py +612 -0
  26. package/latticeai/core/workspace_skills.py +114 -0
  27. package/latticeai/core/workspace_snapshots.py +195 -0
  28. package/latticeai/core/workspace_timeline.py +107 -0
  29. package/latticeai/runtime/audit_runtime.py +64 -0
  30. package/latticeai/runtime/config_runtime.py +4 -0
  31. package/latticeai/runtime/sso_runtime.py +52 -0
  32. package/latticeai/services/architecture_readiness.py +3 -3
  33. package/latticeai/services/memory_service.py +215 -7
  34. package/latticeai/services/model_capability_registry.py +68 -0
  35. package/latticeai/services/model_engines.py +603 -0
  36. package/latticeai/services/model_loading.py +482 -0
  37. package/latticeai/services/model_recommendation.py +5 -0
  38. package/latticeai/services/model_runtime.py +141 -866
  39. package/latticeai/services/product_readiness.py +19 -18
  40. package/package.json +1 -1
  41. package/src-tauri/Cargo.lock +1 -1
  42. package/src-tauri/Cargo.toml +1 -1
  43. package/src-tauri/tauri.conf.json +1 -1
  44. package/static/app/asset-manifest.json +11 -11
  45. package/static/app/assets/{Act-BOO66G-c.js → Act-D9jIknFd.js} +1 -1
  46. package/static/app/assets/{Brain-C6lEYiD7.js → Brain-CFOtWbPN.js} +1 -1
  47. package/static/app/assets/{Capture-TATXBRDw.js → Capture-Q4WYzwr5.js} +1 -1
  48. package/static/app/assets/{Library-DK4FIp8a.js → Library-C5Q2yWee.js} +1 -1
  49. package/static/app/assets/{System-Bgs6Ql7x.js → System-BLbjdr1_.js} +1 -1
  50. package/static/app/assets/index-BqammyNu.js +16 -0
  51. package/static/app/assets/index-ty1iGgZu.css +2 -0
  52. package/static/app/assets/{primitives-B70WOra0.js → primitives-Br8uSfZ4.js} +1 -1
  53. package/static/app/assets/{textarea-Czrd9gwM.js → textarea-BnhNs1_X.js} +1 -1
  54. package/static/app/index.html +2 -2
  55. package/static/sw.js +1 -1
  56. package/tools/__init__.py +2 -1
  57. package/tools/computer.py +21 -0
  58. package/docs/CARRYOVER_AUDIT_v3.6.0.md +0 -61
  59. package/docs/HANDOVER_v3.6.0.md +0 -46
  60. package/docs/RUNTIME_HOOK_COVERAGE_v3.5.0.md +0 -56
  61. package/docs/RUNTIME_HOOK_COVERAGE_v3.6.0.md +0 -49
  62. package/docs/V2_ARCHITECTURE.md +0 -561
  63. package/docs/V3_2_AUDIT.md +0 -82
  64. package/docs/V3_BACKEND_ARCHITECTURE.md +0 -138
  65. package/docs/V3_FRONTEND.md +0 -146
  66. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +0 -65
  67. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +0 -70
  68. package/docs/V4_1_VALIDATION_REPORT.md +0 -47
  69. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +0 -97
  70. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +0 -91
  71. package/docs/V4_2_VALIDATION_REPORT.md +0 -89
  72. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +0 -174
  73. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +0 -81
  74. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +0 -75
  75. package/docs/V4_3_2_GRAPH_UX_REPORT.md +0 -48
  76. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +0 -209
  77. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +0 -57
  78. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +0 -63
  79. package/docs/V4_3_2_VALIDATION_REPORT.md +0 -97
  80. package/docs/V4_3_3_VALIDATION_REPORT.md +0 -46
  81. package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +0 -69
  82. package/docs/V4_3_PRIVACY_AUDIT.md +0 -60
  83. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +0 -53
  84. package/docs/V4_3_VALIDATION_REPORT.md +0 -58
  85. package/docs/V4_4_0_EXTRACTION_REPORT.md +0 -239
  86. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +0 -49
  87. package/docs/V4_5_0_GRAPH_UX_REPORT.md +0 -34
  88. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +0 -40
  89. package/docs/V4_5_0_ONBOARDING_REPORT.md +0 -31
  90. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +0 -49
  91. package/docs/V4_5_0_VALIDATION_REPORT.md +0 -60
  92. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +0 -33
  93. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +0 -37
  94. package/docs/V4_5_1_NAVIGATION_REPORT.md +0 -37
  95. package/docs/V4_5_1_ONBOARDING_REPORT.md +0 -29
  96. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +0 -61
  97. package/docs/V4_5_1_RC_ARTIFACTS.md +0 -44
  98. package/docs/V4_5_1_UX_REPORT.md +0 -45
  99. package/docs/V4_5_1_VALIDATION_REPORT.md +0 -55
  100. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +0 -30
  101. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +0 -72
  102. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +0 -42
  103. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +0 -42
  104. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +0 -49
  105. package/docs/V4_7_2_INTUITIVE_BRAIN_UX_REPORT.md +0 -62
  106. package/docs/V4_BRAIN_ARCHITECTURE.md +0 -322
  107. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +0 -555
  108. package/docs/V4_IMPLEMENTATION_PLAN.md +0 -470
  109. package/static/app/assets/index-Bvv79nre.js +0 -16
  110. package/static/app/assets/index-Dslqglia.css +0 -2
@@ -0,0 +1,132 @@
1
+ """Graph answer trace persistence extracted from WorkspaceOSStore."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from .workspace_os_utils import _json_hash, _listify, _now
7
+
8
+
9
+ class WorkspaceGraphTrace:
10
+ def __init__(self, store: Any):
11
+ self._store = store
12
+
13
+ def __getattr__(self, name: str) -> Any:
14
+ return getattr(self._store, name)
15
+
16
+ def build_graph_trace(self, question: str, graph: Any, context: str = "", *, limit: int = 8) -> Dict[str, Any]:
17
+ if graph is None:
18
+ return {
19
+ "source_files": [],
20
+ "graph_nodes": [],
21
+ "graph_edges": [],
22
+ "confidence": 0.0,
23
+ "retrieval_metadata": {
24
+ "query": question,
25
+ "matched_nodes": 0,
26
+ "graph_enabled": False,
27
+ "context_chars": len(context or ""),
28
+ },
29
+ }
30
+
31
+ matches: List[Dict[str, Any]] = []
32
+ search_error = ""
33
+ try:
34
+ matches = graph.search(question, limit=limit).get("matches", [])
35
+ except Exception as exc:
36
+ search_error = str(exc)
37
+ matches = []
38
+
39
+ source_files: List[Dict[str, Any]] = []
40
+ seen_sources = set()
41
+ for match in matches:
42
+ meta = match.get("metadata") or {}
43
+ source = (
44
+ meta.get("relative_path")
45
+ or meta.get("file_path")
46
+ or meta.get("filename")
47
+ or meta.get("blob_path")
48
+ or meta.get("source")
49
+ )
50
+ if source and source not in seen_sources:
51
+ seen_sources.add(source)
52
+ source_files.append({
53
+ "source": source,
54
+ "node_id": match.get("id"),
55
+ "node_title": match.get("title"),
56
+ "node_type": match.get("type"),
57
+ "jump": {
58
+ "graph": f"/graph?node={match.get('id')}",
59
+ "source": source,
60
+ },
61
+ })
62
+
63
+ edges: List[Dict[str, Any]] = []
64
+ edge_seen = set()
65
+ for match in matches[:5]:
66
+ node_id = match.get("id")
67
+ if not node_id:
68
+ continue
69
+ try:
70
+ for edge in graph.neighbors(node_id).get("edges", []):
71
+ key = (edge.get("from"), edge.get("to"), edge.get("type"))
72
+ if key in edge_seen:
73
+ continue
74
+ edge_seen.add(key)
75
+ edges.append(edge)
76
+ if len(edges) >= 24:
77
+ break
78
+ except Exception:
79
+ continue
80
+
81
+ if matches:
82
+ confidence = min(0.95, 0.35 + min(len(matches), limit) / max(limit, 1) * 0.45 + (0.10 if edges else 0.0))
83
+ else:
84
+ confidence = 0.05 if context else 0.0
85
+
86
+ return {
87
+ "source_files": source_files,
88
+ "graph_nodes": matches,
89
+ "graph_edges": edges,
90
+ "confidence": round(confidence, 4),
91
+ "retrieval_metadata": {
92
+ "query": question,
93
+ "matched_nodes": len(matches),
94
+ "matched_edges": len(edges),
95
+ "graph_enabled": True,
96
+ "context_chars": len(context or ""),
97
+ "search_error": search_error,
98
+ },
99
+ }
100
+
101
+ def record_trace(
102
+ self,
103
+ *,
104
+ question: str,
105
+ response: str,
106
+ conversation_id: Optional[str],
107
+ user_email: Optional[str],
108
+ trace: Dict[str, Any],
109
+ workspace_id: Optional[str] = None,
110
+ ) -> Dict[str, Any]:
111
+ state = self.load_state()
112
+ trace_id = f"trace-{_json_hash([question, response, conversation_id, _now()])[:16]}"
113
+ record = {
114
+ "id": trace_id,
115
+ "question": question,
116
+ "response_preview": str(response or "")[:700],
117
+ "conversation_id": conversation_id,
118
+ "user_email": user_email,
119
+ "workspace_id": self._resolve_scope(workspace_id, state),
120
+ "created_at": _now(),
121
+ **trace,
122
+ }
123
+ state.setdefault("traces", []).append(record)
124
+ self.save_state(state)
125
+ self.record_timeline_event("graph", "answer_trace", {"trace_id": trace_id, "conversation_id": conversation_id})
126
+ return record
127
+
128
+ def list_traces(self, conversation_id: Optional[str] = None, limit: int = 50, workspace_id: Optional[str] = None) -> Dict[str, Any]:
129
+ traces = self._scoped(_listify(self.load_state().get("traces")), workspace_id)
130
+ if conversation_id:
131
+ traces = [trace for trace in traces if trace.get("conversation_id") == conversation_id]
132
+ return {"traces": list(reversed(traces[-max(1, min(limit, 200)):]))}
@@ -0,0 +1,75 @@
1
+ """Memory methods extracted from WorkspaceOSStore for decomposition.
2
+
3
+ WorkspaceMemory manager.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from .workspace_os_utils import _json_hash, _listify, _now
10
+
11
+
12
+ class WorkspaceMemory:
13
+ def __init__(self, store: Any):
14
+ self._store = store
15
+
16
+ def upsert_memory(
17
+ self,
18
+ *,
19
+ kind: str,
20
+ content: str,
21
+ user_email: Optional[str],
22
+ tags: Optional[List[str]] = None,
23
+ memory_id: Optional[str] = None,
24
+ metadata: Optional[Dict[str, Any]] = None,
25
+ graph: Any = None,
26
+ workspace_id: Optional[str] = None,
27
+ ) -> Dict[str, Any]:
28
+ MEMORY_KINDS = {"short_term", "workspace", "preferences", "decisions", "working_style", "frequently_used_tools", "long_term"}
29
+ if kind not in MEMORY_KINDS:
30
+ raise ValueError(f"unknown memory kind: {kind}")
31
+ if not str(content or "").strip():
32
+ raise ValueError("content is required")
33
+ state = self._store.load_state()
34
+ memories = _listify(state.get("memories"))
35
+ now = _now()
36
+ memory_id = memory_id or f"memory-{_json_hash([kind, content, user_email, now])[:16]}"
37
+ existing = next((item for item in memories if item.get("id") == memory_id), None)
38
+ record = existing or {"id": memory_id, "created_at": now}
39
+ record.update({
40
+ "kind": kind,
41
+ "content": content,
42
+ "user_email": user_email,
43
+ "tags": tags or [],
44
+ "metadata": {**(metadata or {}), "memory_scope": kind},
45
+ "workspace_id": self._store._resolve_scope(workspace_id, state) if existing is None else self._store._record_workspace(record),
46
+ "updated_at": now,
47
+ })
48
+ if graph is not None:
49
+ try:
50
+ ingested = graph.ingest_event(
51
+ "Memory",
52
+ f"{kind}: {content[:80]}",
53
+ user_email=user_email,
54
+ source="workspace_os",
55
+ metadata={"memory_id": memory_id, "kind": kind, "tags": tags or []},
56
+ )
57
+ record["graph_node_id"] = ingested.get("node_id")
58
+ except Exception as exc:
59
+ record["graph_error"] = str(exc)
60
+ if existing is None:
61
+ memories.append(record)
62
+ state["memories"] = memories
63
+ self._store.save_state(state)
64
+ self._store.record_timeline_event("memory", "memory_upserted", {"memory_id": memory_id, "kind": kind}, workspace_id=record.get("workspace_id"))
65
+ return record
66
+
67
+ def list_memories(self, user_email: Optional[str] = None, kind: Optional[str] = None, workspace_id: Optional[str] = None) -> Dict[str, Any]:
68
+ memories = self._store._scoped(_listify(self._store.load_state().get("memories")), workspace_id)
69
+ if user_email:
70
+ memories = [item for item in memories if item.get("user_email") in {None, user_email}]
71
+ if kind:
72
+ memories = [item for item in memories if item.get("kind") == kind]
73
+ return {"memories": list(reversed(memories))}
74
+
75
+ # search_memories can be added similarly if needed