ltcai 8.1.0 → 8.3.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.
- package/README.md +29 -48
- package/docs/CHANGELOG.md +70 -874
- package/docs/COMMUNITY_AND_PLUGINS.md +43 -0
- package/docs/DEVELOPMENT.md +26 -3
- package/docs/LEGACY_COMPATIBILITY.md +23 -1
- package/docs/ONBOARDING.md +38 -0
- package/docs/TRUST_MODEL.md +26 -26
- package/docs/WHY_LATTICE.md +29 -25
- package/docs/kg-schema.md +7 -5
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/ingest.py +40 -8
- package/lattice_brain/graph/schema.py +6 -0
- package/lattice_brain/runtime/agent_runtime.py +22 -37
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/workflow.py +26 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/computer_use.py +2 -0
- package/latticeai/api/knowledge_graph.py +33 -0
- package/latticeai/api/local_files.py +2 -0
- package/latticeai/api/memory.py +13 -0
- package/latticeai/api/tools.py +1 -0
- package/latticeai/api/workflow_designer.py +5 -4
- package/latticeai/app_factory.py +30 -28
- package/latticeai/brain/__init__.py +1 -1
- package/latticeai/core/config.py +10 -0
- package/latticeai/core/legacy_compatibility.py +174 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/tool_registry.py +10 -0
- package/latticeai/core/workspace_graph_trace.py +132 -0
- package/latticeai/core/workspace_memory.py +75 -0
- package/latticeai/core/workspace_os.py +135 -1283
- package/latticeai/core/workspace_os_utils.py +132 -0
- package/latticeai/core/workspace_permissions.py +99 -0
- package/latticeai/core/workspace_plugins.py +97 -0
- package/latticeai/core/workspace_runs.py +612 -0
- package/latticeai/core/workspace_skills.py +114 -0
- package/latticeai/core/workspace_snapshots.py +195 -0
- package/latticeai/core/workspace_timeline.py +107 -0
- package/latticeai/runtime/audit_runtime.py +64 -0
- package/latticeai/runtime/config_runtime.py +4 -0
- package/latticeai/runtime/sso_runtime.py +52 -0
- package/latticeai/services/architecture_readiness.py +37 -3
- package/latticeai/services/memory_service.py +215 -7
- package/latticeai/services/model_capability_registry.py +68 -0
- package/latticeai/services/model_engines.py +594 -0
- package/latticeai/services/model_loading.py +482 -0
- package/latticeai/services/model_recommendation.py +5 -0
- package/latticeai/services/model_runtime.py +131 -875
- package/latticeai/services/product_readiness.py +41 -18
- package/llm_router.py +7 -28
- package/mcp_registry.py +7 -24
- package/package.json +1 -1
- package/scripts/pts-claudecode-discord-bridge.mjs +2 -1
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +11 -11
- package/static/app/assets/{Act-BOO66G-c.js → Act-D5mo4tE4.js} +1 -1
- package/static/app/assets/{Brain-C6lEYiD7.js → Brain-BVWyQw8A.js} +1 -1
- package/static/app/assets/{Capture-TATXBRDw.js → Capture-C1R6GT0t.js} +1 -1
- package/static/app/assets/{Library-DK4FIp8a.js → Library-C2wIxpTs.js} +1 -1
- package/static/app/assets/{System-Bgs6Ql7x.js → System-DE5GRyQR.js} +1 -1
- package/static/app/assets/index-CoiuIFFP.js +16 -0
- package/static/app/assets/index-ty1iGgZu.css +2 -0
- package/static/app/assets/{primitives-B70WOra0.js → primitives-BdsUNXa6.js} +1 -1
- package/static/app/assets/{textarea-Czrd9gwM.js → textarea-e7qaj6Hm.js} +1 -1
- package/static/app/index.html +2 -2
- package/static/sw.js +1 -1
- package/tools/__init__.py +2 -1
- package/tools/computer.py +21 -0
- package/docs/CARRYOVER_AUDIT_v3.6.0.md +0 -61
- package/docs/HANDOVER_v3.6.0.md +0 -46
- package/docs/RUNTIME_HOOK_COVERAGE_v3.5.0.md +0 -56
- package/docs/RUNTIME_HOOK_COVERAGE_v3.6.0.md +0 -49
- package/docs/V2_ARCHITECTURE.md +0 -561
- package/docs/V3_2_AUDIT.md +0 -82
- package/docs/V3_BACKEND_ARCHITECTURE.md +0 -138
- package/docs/V3_FRONTEND.md +0 -146
- package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +0 -65
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +0 -70
- package/docs/V4_1_VALIDATION_REPORT.md +0 -47
- package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +0 -97
- package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +0 -91
- package/docs/V4_2_VALIDATION_REPORT.md +0 -89
- package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +0 -174
- package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +0 -81
- package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +0 -75
- package/docs/V4_3_2_GRAPH_UX_REPORT.md +0 -48
- package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +0 -209
- package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +0 -57
- package/docs/V4_3_2_SELF_AUDIT_REPORT.md +0 -63
- package/docs/V4_3_2_VALIDATION_REPORT.md +0 -97
- package/docs/V4_3_3_VALIDATION_REPORT.md +0 -46
- package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +0 -69
- package/docs/V4_3_PRIVACY_AUDIT.md +0 -60
- package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +0 -53
- package/docs/V4_3_VALIDATION_REPORT.md +0 -58
- package/docs/V4_4_0_EXTRACTION_REPORT.md +0 -239
- package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +0 -49
- package/docs/V4_5_0_GRAPH_UX_REPORT.md +0 -34
- package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +0 -40
- package/docs/V4_5_0_ONBOARDING_REPORT.md +0 -31
- package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +0 -49
- package/docs/V4_5_0_VALIDATION_REPORT.md +0 -60
- package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +0 -33
- package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +0 -37
- package/docs/V4_5_1_NAVIGATION_REPORT.md +0 -37
- package/docs/V4_5_1_ONBOARDING_REPORT.md +0 -29
- package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +0 -61
- package/docs/V4_5_1_RC_ARTIFACTS.md +0 -44
- package/docs/V4_5_1_UX_REPORT.md +0 -45
- package/docs/V4_5_1_VALIDATION_REPORT.md +0 -55
- package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +0 -30
- package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +0 -72
- package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +0 -42
- package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +0 -42
- package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +0 -49
- package/docs/V4_7_2_INTUITIVE_BRAIN_UX_REPORT.md +0 -62
- package/docs/V4_BRAIN_ARCHITECTURE.md +0 -322
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +0 -555
- package/docs/V4_IMPLEMENTATION_PLAN.md +0 -470
- package/static/app/assets/index-Bvv79nre.js +0 -16
- 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
|