ltcai 8.9.0 → 9.1.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 +81 -58
- package/auto_setup.py +7 -904
- package/desktop/electron/README.md +9 -0
- package/desktop/electron/main.cjs +5 -3
- package/docs/CHANGELOG.md +221 -238
- package/docs/COMMUNITY_AND_PLUGINS.md +3 -2
- package/docs/DEVELOPMENT.md +53 -14
- package/docs/LEGACY_COMPATIBILITY.md +4 -4
- package/docs/ONBOARDING.md +10 -2
- package/docs/OPERATIONS.md +8 -4
- package/docs/PRODUCT_DIRECTION_REVIEW.md +4 -0
- package/docs/ROADMAP_RECOMMENDATIONS.md +61 -36
- package/docs/TRUST_MODEL.md +5 -2
- package/docs/WHY_LATTICE.md +15 -10
- package/docs/WORKFLOW_DESIGNER.md +22 -0
- package/docs/kg-schema.md +13 -2
- package/docs/mcp-tools.md +17 -6
- package/docs/privacy.md +19 -3
- package/docs/public-deploy.md +32 -3
- package/docs/security-model.md +46 -11
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/archive.py +4 -14
- package/lattice_brain/context.py +66 -9
- package/lattice_brain/embeddings.py +38 -2
- package/lattice_brain/graph/_kg_common.py +27 -462
- package/lattice_brain/graph/_kg_constants.py +243 -0
- package/lattice_brain/graph/_kg_fsutil.py +293 -0
- package/lattice_brain/graph/discovery.py +0 -948
- package/lattice_brain/graph/discovery_index.py +1126 -0
- package/lattice_brain/graph/documents.py +44 -9
- package/lattice_brain/graph/ingest.py +47 -20
- package/lattice_brain/graph/provenance.py +13 -6
- package/lattice_brain/graph/retrieval.py +141 -610
- package/lattice_brain/graph/retrieval_docgen.py +243 -0
- package/lattice_brain/graph/retrieval_vector.py +460 -0
- package/lattice_brain/graph/store.py +6 -0
- package/lattice_brain/ingestion.py +69 -4
- package/lattice_brain/portability.py +29 -23
- package/lattice_brain/quality.py +98 -4
- package/lattice_brain/runtime/agent_runtime.py +169 -7
- package/lattice_brain/runtime/hooks.py +30 -13
- package/lattice_brain/runtime/multi_agent.py +27 -8
- package/lattice_brain/runtime/statuses.py +10 -0
- package/lattice_brain/utils.py +46 -0
- package/lattice_brain/workflow.py +1 -5
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +1 -1
- package/latticeai/api/agent_registry.py +10 -8
- package/latticeai/api/agents.py +22 -3
- package/latticeai/api/auth.py +78 -16
- package/latticeai/api/browser.py +303 -34
- package/latticeai/api/chat.py +355 -952
- package/latticeai/api/chat_agent_http.py +356 -0
- package/latticeai/api/chat_contracts.py +54 -0
- package/latticeai/api/chat_documents.py +256 -0
- package/latticeai/api/chat_helpers.py +250 -0
- package/latticeai/api/chat_history.py +99 -0
- package/latticeai/api/chat_intents.py +302 -0
- package/latticeai/api/chat_stream.py +115 -0
- package/latticeai/api/computer_use.py +211 -40
- package/latticeai/api/health.py +9 -3
- package/latticeai/api/hooks.py +17 -6
- package/latticeai/api/knowledge_graph.py +78 -14
- package/latticeai/api/local_files.py +7 -2
- package/latticeai/api/marketplace.py +11 -0
- package/latticeai/api/mcp.py +102 -23
- package/latticeai/api/models.py +72 -33
- package/latticeai/api/network.py +5 -5
- package/latticeai/api/permissions.py +70 -29
- package/latticeai/api/plugins.py +21 -7
- package/latticeai/api/portability.py +5 -5
- package/latticeai/api/realtime.py +33 -5
- package/latticeai/api/setup.py +2 -2
- package/latticeai/api/static_routes.py +123 -24
- package/latticeai/api/tools.py +97 -14
- package/latticeai/api/workflow_designer.py +37 -0
- package/latticeai/api/workspace.py +2 -1
- package/latticeai/app_factory.py +185 -405
- package/latticeai/core/agent.py +19 -9
- package/latticeai/core/agent_registry.py +1 -5
- package/latticeai/core/config.py +23 -3
- package/latticeai/core/context_builder.py +21 -3
- package/latticeai/core/invitations.py +1 -4
- package/latticeai/core/io_utils.py +45 -0
- package/latticeai/core/legacy_compatibility.py +24 -3
- package/latticeai/core/local_embeddings.py +2 -4
- package/latticeai/core/marketplace.py +33 -2
- package/latticeai/core/mcp_catalog.py +450 -0
- package/latticeai/core/mcp_registry.py +2 -441
- package/latticeai/core/plugins.py +15 -0
- package/latticeai/core/policy.py +1 -1
- package/latticeai/core/realtime.py +38 -15
- package/latticeai/core/sessions.py +7 -2
- package/latticeai/core/timeutil.py +10 -0
- package/latticeai/core/tool_registry.py +90 -18
- package/latticeai/core/users.py +5 -14
- package/latticeai/core/workspace_graph_trace.py +31 -5
- package/latticeai/core/workspace_memory.py +3 -1
- package/latticeai/core/workspace_os.py +18 -7
- package/latticeai/core/workspace_os_utils.py +2 -21
- package/latticeai/core/workspace_permissions.py +2 -1
- package/latticeai/core/workspace_plugins.py +1 -1
- package/latticeai/core/workspace_runs.py +14 -5
- package/latticeai/core/workspace_skills.py +1 -1
- package/latticeai/core/workspace_snapshots.py +2 -1
- package/latticeai/core/workspace_timeline.py +2 -1
- package/latticeai/integrations/telegram_bot.py +96 -40
- package/latticeai/models/model_providers.py +111 -0
- package/latticeai/models/router.py +189 -173
- package/latticeai/runtime/access_runtime.py +62 -4
- package/latticeai/runtime/audit_runtime.py +27 -16
- package/latticeai/runtime/automation_runtime.py +22 -7
- package/latticeai/runtime/brain_runtime.py +19 -7
- package/latticeai/runtime/chat_wiring.py +2 -0
- package/latticeai/runtime/config_runtime.py +58 -26
- package/latticeai/runtime/context_runtime.py +16 -4
- package/latticeai/runtime/history_runtime.py +163 -0
- package/latticeai/runtime/hooks_runtime.py +1 -1
- package/latticeai/runtime/model_wiring.py +33 -5
- package/latticeai/runtime/namespace_runtime.py +163 -0
- package/latticeai/runtime/network_config_runtime.py +56 -0
- package/latticeai/runtime/platform_runtime_wiring.py +4 -3
- package/latticeai/runtime/review_wiring.py +1 -1
- package/latticeai/runtime/router_registration.py +34 -1
- package/latticeai/runtime/security_runtime.py +110 -13
- package/latticeai/runtime/sso_config_runtime.py +128 -0
- package/latticeai/runtime/stages.py +27 -0
- package/latticeai/runtime/user_key_runtime.py +106 -0
- package/latticeai/server_app.py +5 -1
- package/latticeai/services/architecture_readiness.py +74 -5
- package/latticeai/services/brain_automation.py +3 -26
- package/latticeai/services/chat_service.py +205 -15
- package/latticeai/services/local_knowledge.py +423 -0
- package/latticeai/services/memory_service.py +268 -21
- package/latticeai/services/model_engines.py +48 -33
- package/latticeai/services/model_errors.py +17 -0
- package/latticeai/services/model_loading.py +28 -25
- package/latticeai/services/model_runtime.py +228 -162
- package/latticeai/services/p_reinforce.py +22 -3
- package/latticeai/services/platform_runtime.py +92 -24
- package/latticeai/services/product_readiness.py +19 -17
- package/latticeai/services/review_queue.py +76 -11
- package/latticeai/services/router_context.py +1 -0
- package/latticeai/services/run_executor.py +25 -9
- package/latticeai/services/search_service.py +203 -28
- package/latticeai/services/setup_detection.py +80 -0
- package/latticeai/services/tool_dispatch.py +28 -5
- package/latticeai/services/triggers.py +53 -4
- package/latticeai/services/upload_service.py +13 -2
- package/latticeai/setup/__init__.py +25 -0
- package/latticeai/setup/auto_setup.py +857 -0
- package/latticeai/setup/wizard.py +1264 -0
- package/latticeai/tools/__init__.py +278 -0
- package/{tools → latticeai/tools}/commands.py +67 -7
- package/{tools → latticeai/tools}/computer.py +1 -1
- package/{tools → latticeai/tools}/documents.py +1 -1
- package/{tools → latticeai/tools}/filesystem.py +3 -3
- package/latticeai/tools/knowledge.py +178 -0
- package/{tools → latticeai/tools}/local_files.py +7 -1
- package/{tools → latticeai/tools}/network.py +0 -1
- package/local_knowledge_api.py +4 -342
- package/package.json +22 -2
- package/scripts/brain_quality_eval.py +3 -1
- package/scripts/bump_version.py +3 -0
- package/scripts/capture_release_evidence.mjs +180 -0
- package/scripts/check_current_release_docs.mjs +142 -0
- package/scripts/check_i18n_literals.mjs +107 -31
- package/scripts/check_openapi_drift.mjs +56 -0
- package/scripts/export_openapi.py +67 -7
- package/scripts/i18n_literal_allowlist.json +22 -24
- package/scripts/run_integration_tests.mjs +72 -10
- package/scripts/validate_release_artifacts.py +49 -7
- package/scripts/wheel_smoke.py +5 -0
- package/setup_wizard.py +3 -1304
- 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-Bzz0bUyW.js +1 -0
- package/static/app/assets/Brain-Dj2J20YA.js +321 -0
- package/static/app/assets/Capture-CqlEl1Ga.js +1 -0
- package/static/app/assets/Library-B03FP1Yx.js +1 -0
- package/static/app/assets/System-80lHW0Ux.js +1 -0
- package/static/app/assets/index-BiMofBTM.js +17 -0
- package/static/app/assets/index-Bmx9rzTc.css +2 -0
- package/static/app/assets/primitives-Q1A96_7v.js +1 -0
- package/static/app/assets/textarea-D13RtnTo.js +1 -0
- package/static/app/index.html +2 -2
- package/static/css/tokens.css +4 -2
- package/static/sw.js +1 -1
- package/tools/__init__.py +21 -269
- package/docs/CODE_REVIEW_2026-07-06.md +0 -764
- package/latticeai/runtime/app_context_runtime.py +0 -13
- package/latticeai/runtime/sso_runtime.py +0 -52
- package/latticeai/runtime/tail_wiring.py +0 -21
- package/scripts/com.pts.claudecode.discord.plist +0 -31
- package/scripts/pts-claudecode-discord-bridge.mjs +0 -207
- package/scripts/start-pts-claudecode-discord.sh +0 -51
- package/static/app/assets/Act-fZokUnC0.js +0 -1
- package/static/app/assets/Brain-DtyuWubr.js +0 -321
- package/static/app/assets/Capture-D5KV3Cu7.js +0 -1
- package/static/app/assets/Library-C9kyFkSt.js +0 -1
- package/static/app/assets/System-VbChmX7r.js +0 -1
- package/static/app/assets/index-DCh5AoXt.css +0 -2
- package/static/app/assets/index-DPdcPoF0.js +0 -17
- package/static/app/assets/primitives-DFeanEV6.js +0 -1
- package/static/app/assets/textarea-CD8UNKIy.js +0 -1
- package/tools/knowledge.py +0 -95
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# ruff: noqa: F403,F405
|
|
4
|
+
|
|
5
|
+
from ._kg_common import * # noqa: F403,F401
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KnowledgeGraphDocGenMixin:
|
|
9
|
+
"""Multi-hop retrieval specialised for document generation, split out
|
|
10
|
+
of retrieval. Composed into KnowledgeGraphStore alongside the other
|
|
11
|
+
retrieval mixins; shared instance means sibling helpers resolve via MRO.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def search_for_document_generation(
|
|
15
|
+
self,
|
|
16
|
+
query: str,
|
|
17
|
+
limit: int = 10,
|
|
18
|
+
*,
|
|
19
|
+
allowed_workspaces=None,
|
|
20
|
+
include_legacy_global: bool = False,
|
|
21
|
+
) -> List[Dict[str, Any]]:
|
|
22
|
+
"""Hybrid retrieval optimized for document generation.
|
|
23
|
+
|
|
24
|
+
Scoring: 0.5*text_relevance + 0.3*graph_relationship + 0.2*recency
|
|
25
|
+
Returns nodes with rich context for document generation prompts.
|
|
26
|
+
"""
|
|
27
|
+
query = str(query or "").strip()
|
|
28
|
+
if not query:
|
|
29
|
+
return []
|
|
30
|
+
limit = max(1, min(int(limit or 10), 50))
|
|
31
|
+
terms = _topic_candidates(query, limit=12)
|
|
32
|
+
now = datetime.now()
|
|
33
|
+
nt, et = self._read_tables()
|
|
34
|
+
|
|
35
|
+
with self._connect() as conn:
|
|
36
|
+
candidate_rows = []
|
|
37
|
+
seen_ids = set()
|
|
38
|
+
|
|
39
|
+
if query:
|
|
40
|
+
q = f"%{query}%"
|
|
41
|
+
rows = conn.execute(
|
|
42
|
+
f"""
|
|
43
|
+
SELECT id, type, title, summary, metadata_json, updated_at
|
|
44
|
+
FROM {nt}
|
|
45
|
+
WHERE (title LIKE ? OR summary LIKE ? OR metadata_json LIKE ?)
|
|
46
|
+
AND type IN ('Document', 'File', 'CodeFile', 'SlideDeck',
|
|
47
|
+
'Spreadsheet', 'Image', 'ImageText', 'Chat',
|
|
48
|
+
'Decision', 'Task', 'Concept', 'Feature',
|
|
49
|
+
'Page', 'Slide')
|
|
50
|
+
ORDER BY updated_at DESC, id ASC
|
|
51
|
+
LIMIT ?
|
|
52
|
+
""",
|
|
53
|
+
(q, q, q, limit * 5),
|
|
54
|
+
).fetchall()
|
|
55
|
+
for row in rows:
|
|
56
|
+
if row["id"] not in seen_ids:
|
|
57
|
+
seen_ids.add(row["id"])
|
|
58
|
+
candidate_rows.append(row)
|
|
59
|
+
|
|
60
|
+
for term in terms:
|
|
61
|
+
t = f"%{term}%"
|
|
62
|
+
rows = conn.execute(
|
|
63
|
+
f"""
|
|
64
|
+
SELECT id, type, title, summary, metadata_json, updated_at
|
|
65
|
+
FROM {nt}
|
|
66
|
+
WHERE (title LIKE ? OR summary LIKE ? OR metadata_json LIKE ?)
|
|
67
|
+
AND type IN ('Document', 'File', 'CodeFile', 'SlideDeck',
|
|
68
|
+
'Spreadsheet', 'Image', 'ImageText', 'Chat',
|
|
69
|
+
'Decision', 'Task', 'Concept', 'Feature',
|
|
70
|
+
'Page', 'Slide')
|
|
71
|
+
ORDER BY updated_at DESC, id ASC
|
|
72
|
+
LIMIT ?
|
|
73
|
+
""",
|
|
74
|
+
(t, t, t, limit * 3),
|
|
75
|
+
).fetchall()
|
|
76
|
+
for row in rows:
|
|
77
|
+
if row["id"] not in seen_ids:
|
|
78
|
+
seen_ids.add(row["id"])
|
|
79
|
+
candidate_rows.append(row)
|
|
80
|
+
|
|
81
|
+
scored_results = []
|
|
82
|
+
for row in candidate_rows:
|
|
83
|
+
haystack = (
|
|
84
|
+
f"{row['title']} {row['summary']} {row['metadata_json']}".lower()
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
text_hits = sum(1 for term in terms if term.lower() in haystack)
|
|
88
|
+
text_score = min(1.0, text_hits / max(len(terms), 1))
|
|
89
|
+
|
|
90
|
+
edge_count = conn.execute(
|
|
91
|
+
f"SELECT COUNT(*) AS c FROM {et} WHERE from_node=? OR to_node=?",
|
|
92
|
+
(row["id"], row["id"]),
|
|
93
|
+
).fetchone()["c"]
|
|
94
|
+
graph_score = min(1.0, math.log1p(edge_count) / 4.0)
|
|
95
|
+
|
|
96
|
+
recency = _recency_score(
|
|
97
|
+
row["updated_at"], now=now, half_life_days=14.0
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
doc_type_boost = (
|
|
101
|
+
1.2
|
|
102
|
+
if row["type"]
|
|
103
|
+
in (
|
|
104
|
+
"Document",
|
|
105
|
+
"File",
|
|
106
|
+
"SlideDeck",
|
|
107
|
+
"Decision",
|
|
108
|
+
)
|
|
109
|
+
else 1.0
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
hybrid_score = (
|
|
113
|
+
0.5 * text_score + 0.3 * graph_score + 0.2 * recency
|
|
114
|
+
) * doc_type_boost
|
|
115
|
+
|
|
116
|
+
meta = _safe_loads(row["metadata_json"])
|
|
117
|
+
neighbor_concepts = []
|
|
118
|
+
neighbor_rows = conn.execute(
|
|
119
|
+
f"""
|
|
120
|
+
SELECT n.id, n.title, n.type FROM {et} e
|
|
121
|
+
JOIN {nt} n ON n.id = CASE WHEN e.from_node = ? THEN e.to_node ELSE e.from_node END
|
|
122
|
+
WHERE (e.from_node = ? OR e.to_node = ?)
|
|
123
|
+
AND n.type IN ('Concept', 'Feature', 'Decision', 'Task')
|
|
124
|
+
LIMIT 8
|
|
125
|
+
""",
|
|
126
|
+
(row["id"], row["id"], row["id"]),
|
|
127
|
+
).fetchall()
|
|
128
|
+
for nr in neighbor_rows:
|
|
129
|
+
neighbor_concepts.append({"id": nr["id"], "title": nr["title"], "type": nr["type"]})
|
|
130
|
+
|
|
131
|
+
scored_results.append(
|
|
132
|
+
{
|
|
133
|
+
"id": row["id"],
|
|
134
|
+
"type": row["type"],
|
|
135
|
+
"title": row["title"],
|
|
136
|
+
"summary": row["summary"],
|
|
137
|
+
"metadata": meta,
|
|
138
|
+
"updated_at": row["updated_at"],
|
|
139
|
+
"hybrid_score": round(hybrid_score, 4),
|
|
140
|
+
"scores": {
|
|
141
|
+
"text": round(text_score, 4),
|
|
142
|
+
"graph": round(graph_score, 4),
|
|
143
|
+
"recency": round(recency, 4),
|
|
144
|
+
},
|
|
145
|
+
"related_concepts": neighbor_concepts,
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if allowed_workspaces is not None:
|
|
150
|
+
scored_results = self.filter_scoped_nodes(
|
|
151
|
+
scored_results,
|
|
152
|
+
allowed_workspaces,
|
|
153
|
+
include_legacy_global=include_legacy_global,
|
|
154
|
+
)
|
|
155
|
+
for item in scored_results:
|
|
156
|
+
item["related_concepts"] = self.filter_scoped_nodes(
|
|
157
|
+
item.get("related_concepts", []),
|
|
158
|
+
allowed_workspaces,
|
|
159
|
+
include_legacy_global=include_legacy_global,
|
|
160
|
+
)
|
|
161
|
+
scored_results.sort(key=lambda x: x["hybrid_score"], reverse=True)
|
|
162
|
+
return scored_results[:limit]
|
|
163
|
+
|
|
164
|
+
def multi_hop_context(
|
|
165
|
+
self,
|
|
166
|
+
node_ids: List[str],
|
|
167
|
+
max_hops: int = 2,
|
|
168
|
+
*,
|
|
169
|
+
allowed_workspaces=None,
|
|
170
|
+
include_legacy_global: bool = False,
|
|
171
|
+
) -> Dict[str, Any]:
|
|
172
|
+
"""Multi-hop graph traversal from seed nodes for richer context."""
|
|
173
|
+
visited_nodes = set()
|
|
174
|
+
visited_edges = set()
|
|
175
|
+
all_nodes = []
|
|
176
|
+
all_edges = []
|
|
177
|
+
frontier = set(node_ids)
|
|
178
|
+
nt, et = self._read_tables()
|
|
179
|
+
|
|
180
|
+
with self._connect() as conn:
|
|
181
|
+
for hop in range(max_hops):
|
|
182
|
+
if not frontier:
|
|
183
|
+
break
|
|
184
|
+
next_frontier = set()
|
|
185
|
+
for nid in frontier:
|
|
186
|
+
if nid in visited_nodes:
|
|
187
|
+
continue
|
|
188
|
+
visited_nodes.add(nid)
|
|
189
|
+
row = conn.execute(
|
|
190
|
+
f"SELECT id, type, title, summary, metadata_json, updated_at FROM {nt} WHERE id=?",
|
|
191
|
+
(nid,),
|
|
192
|
+
).fetchone()
|
|
193
|
+
if row:
|
|
194
|
+
all_nodes.append(
|
|
195
|
+
{
|
|
196
|
+
"id": row["id"],
|
|
197
|
+
"type": row["type"],
|
|
198
|
+
"title": row["title"],
|
|
199
|
+
"summary": row["summary"],
|
|
200
|
+
"metadata": _safe_loads(row["metadata_json"]),
|
|
201
|
+
"hop": hop,
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
edge_rows = conn.execute(
|
|
205
|
+
f"""
|
|
206
|
+
SELECT id, from_node, to_node, type, weight
|
|
207
|
+
FROM {et} WHERE from_node=? OR to_node=?
|
|
208
|
+
ORDER BY id ASC
|
|
209
|
+
""",
|
|
210
|
+
(nid, nid),
|
|
211
|
+
).fetchall()
|
|
212
|
+
for er in edge_rows:
|
|
213
|
+
if er["id"] not in visited_edges:
|
|
214
|
+
visited_edges.add(er["id"])
|
|
215
|
+
all_edges.append(
|
|
216
|
+
{
|
|
217
|
+
"from": er["from_node"],
|
|
218
|
+
"to": er["to_node"],
|
|
219
|
+
"type": er["type"],
|
|
220
|
+
"weight": er["weight"],
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
other = (
|
|
224
|
+
er["to_node"]
|
|
225
|
+
if er["from_node"] == nid
|
|
226
|
+
else er["from_node"]
|
|
227
|
+
)
|
|
228
|
+
if other not in visited_nodes:
|
|
229
|
+
next_frontier.add(other)
|
|
230
|
+
frontier = next_frontier
|
|
231
|
+
|
|
232
|
+
if allowed_workspaces is not None:
|
|
233
|
+
all_nodes = self.filter_scoped_nodes(
|
|
234
|
+
all_nodes,
|
|
235
|
+
allowed_workspaces,
|
|
236
|
+
include_legacy_global=include_legacy_global,
|
|
237
|
+
)
|
|
238
|
+
visible_ids = {node.get("id") for node in all_nodes}
|
|
239
|
+
all_edges = [
|
|
240
|
+
edge for edge in all_edges
|
|
241
|
+
if edge.get("from") in visible_ids and edge.get("to") in visible_ids
|
|
242
|
+
]
|
|
243
|
+
return {"nodes": all_nodes, "edges": all_edges}
|