ltcai 4.3.1 → 4.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.
- package/README.md +191 -278
- package/docs/CHANGELOG.md +128 -0
- package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +174 -0
- package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +81 -0
- package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +75 -0
- package/docs/V4_3_2_GRAPH_UX_REPORT.md +48 -0
- package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +209 -0
- package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +57 -0
- package/docs/V4_3_2_SELF_AUDIT_REPORT.md +63 -0
- package/docs/V4_3_2_VALIDATION_REPORT.md +97 -0
- package/docs/V4_3_3_VALIDATION_REPORT.md +46 -0
- package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -19
- package/frontend/openapi.json +1 -1
- package/frontend/src/components/primitives.tsx +92 -10
- package/frontend/src/pages/Act.tsx +11 -9
- package/frontend/src/pages/Ask.tsx +2 -2
- package/frontend/src/pages/Brain.tsx +607 -65
- package/frontend/src/pages/Capture.tsx +11 -7
- package/frontend/src/pages/Library.tsx +3 -3
- package/frontend/src/pages/System.tsx +186 -23
- package/lattice_brain/__init__.py +38 -23
- package/lattice_brain/_kg_common.py +11 -1
- package/lattice_brain/context.py +212 -2
- package/lattice_brain/conversations.py +234 -1
- package/lattice_brain/discovery.py +11 -1
- package/lattice_brain/documents.py +11 -1
- package/lattice_brain/graph/__init__.py +28 -0
- package/lattice_brain/graph/_kg_common.py +1123 -0
- package/lattice_brain/graph/curator.py +473 -0
- package/lattice_brain/graph/discovery.py +1455 -0
- package/lattice_brain/graph/documents.py +218 -0
- package/lattice_brain/graph/identity.py +175 -0
- package/lattice_brain/graph/ingest.py +644 -0
- package/lattice_brain/graph/network.py +205 -0
- package/lattice_brain/graph/projection.py +571 -0
- package/lattice_brain/graph/provenance.py +401 -0
- package/lattice_brain/graph/retrieval.py +1341 -0
- package/lattice_brain/graph/schema.py +640 -0
- package/lattice_brain/graph/store.py +237 -0
- package/lattice_brain/graph/write_master.py +225 -0
- package/lattice_brain/identity.py +11 -13
- package/lattice_brain/ingest.py +11 -1
- package/lattice_brain/ingestion.py +318 -0
- package/lattice_brain/memory.py +100 -1
- package/lattice_brain/network.py +11 -1
- package/lattice_brain/portability.py +431 -0
- package/lattice_brain/projection.py +11 -1
- package/lattice_brain/provenance.py +11 -1
- package/lattice_brain/retrieval.py +11 -1
- package/lattice_brain/runtime/__init__.py +32 -0
- package/lattice_brain/runtime/agent_runtime.py +569 -0
- package/lattice_brain/runtime/hooks.py +754 -0
- package/lattice_brain/runtime/multi_agent.py +795 -0
- package/lattice_brain/schema.py +11 -1
- package/lattice_brain/store.py +10 -2
- package/lattice_brain/workflow.py +461 -0
- package/lattice_brain/write_master.py +11 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agents.py +2 -2
- package/latticeai/api/browser.py +1 -1
- package/latticeai/api/chat.py +1 -1
- package/latticeai/api/computer_use.py +1 -1
- package/latticeai/api/hooks.py +2 -2
- package/latticeai/api/mcp.py +1 -1
- package/latticeai/api/tools.py +1 -1
- package/latticeai/api/workflow_designer.py +2 -2
- package/latticeai/app_factory.py +4 -4
- package/latticeai/brain/__init__.py +24 -6
- package/latticeai/brain/_kg_common.py +11 -1117
- package/latticeai/brain/context.py +12 -208
- package/latticeai/brain/conversations.py +12 -231
- package/latticeai/brain/discovery.py +13 -1451
- package/latticeai/brain/documents.py +13 -214
- package/latticeai/brain/identity.py +11 -169
- package/latticeai/brain/ingest.py +13 -640
- package/latticeai/brain/memory.py +12 -97
- package/latticeai/brain/network.py +12 -200
- package/latticeai/brain/projection.py +13 -567
- package/latticeai/brain/provenance.py +13 -397
- package/latticeai/brain/retrieval.py +13 -1337
- package/latticeai/brain/schema.py +12 -635
- package/latticeai/brain/store.py +13 -233
- package/latticeai/brain/write_master.py +13 -221
- package/latticeai/core/agent.py +1 -1
- package/latticeai/core/agent_registry.py +2 -2
- package/latticeai/core/builtin_hooks.py +2 -2
- package/latticeai/core/graph_curator.py +6 -468
- package/latticeai/core/hooks.py +6 -749
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +6 -790
- package/latticeai/core/workflow_engine.py +6 -456
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/agent_runtime.py +6 -564
- package/latticeai/services/ingestion.py +6 -313
- package/latticeai/services/kg_portability.py +6 -426
- package/latticeai/services/platform_runtime.py +3 -3
- package/latticeai/services/run_executor.py +1 -1
- package/latticeai/services/upload_service.py +1 -1
- package/p_reinforce.py +1 -1
- package/package.json +3 -6
- package/scripts/build_vercel_static.mjs +77 -0
- package/scripts/bump_version.py +1 -1
- package/scripts/check_markdown_links.mjs +75 -0
- package/scripts/wheel_smoke.py +7 -0
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/main.rs +12 -2
- package/src-tauri/tauri.conf.json +1 -1
- package/static/app/asset-manifest.json +5 -5
- package/static/app/assets/index-CHHal8Zl.css +2 -0
- package/static/app/assets/index-pdzil9ac.js +333 -0
- package/static/app/assets/index-pdzil9ac.js.map +1 -0
- package/static/app/index.html +2 -2
- package/latticeai/api/deps.py +0 -15
- package/scripts/capture/README.md +0 -28
- package/scripts/capture/capture_enterprise.js +0 -8
- package/scripts/capture/capture_graph.js +0 -8
- package/scripts/capture/capture_onboarding.js +0 -8
- package/scripts/capture/capture_page.js +0 -43
- package/scripts/capture/capture_release_media.js +0 -125
- package/scripts/capture/capture_skills.js +0 -8
- package/scripts/capture/capture_v340.js +0 -88
- package/scripts/capture/capture_workspace.js +0 -8
- package/scripts/generate_diagrams.py +0 -512
- package/scripts/release-0.3.1.sh +0 -105
- package/scripts/take_screenshots.js +0 -69
- package/static/app/assets/index-BhPuj8rT.js +0 -333
- package/static/app/assets/index-BhPuj8rT.js.map +0 -1
- package/static/app/assets/index-yZswHE3d.css +0 -2
- package/static/css/tokens.3ba22e37.css +0 -260
|
@@ -1,318 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Compatibility shim: physically moved to ``lattice_brain.ingestion``.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
connected folders, PDFs/Markdown/text/code, web URLs, browser tabs — is
|
|
6
|
-
normalized into one :class:`IngestionItem` and pushed through one
|
|
7
|
-
:meth:`IngestionPipeline.ingest` entrypoint:
|
|
8
|
-
|
|
9
|
-
Source → normalize → content hash → (file | text) ingest → provenance
|
|
10
|
-
|
|
11
|
-
The pipeline is deliberately thin. It owns normalization, idempotency reporting,
|
|
12
|
-
provenance capture, and — crucially — routing every ingest through the shared
|
|
13
|
-
``dispatch_tool`` lifecycle so ``pre_tool``/``post_tool`` hooks fire on data
|
|
14
|
-
ingestion exactly as they do on tool calls. The heavy graph construction lives in
|
|
15
|
-
:class:`knowledge_graph.KnowledgeGraphStore` (``ingest_document`` for files,
|
|
16
|
-
``ingest_source`` for text/web), which this module composes rather than
|
|
17
|
-
re-implements.
|
|
3
|
+
Aliases itself to the physical module so identity, module-level state, and
|
|
4
|
+
monkeypatching keep working through the old import path.
|
|
18
5
|
"""
|
|
19
6
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import hashlib
|
|
23
|
-
from dataclasses import dataclass, field
|
|
24
|
-
from datetime import datetime, timezone
|
|
25
|
-
from pathlib import Path
|
|
26
|
-
from typing import Any, Dict, List, Optional
|
|
27
|
-
|
|
28
|
-
from latticeai.core.hooks import dispatch_tool
|
|
29
|
-
|
|
30
|
-
# Source types that arrive as a file on disk (read via ingest_document).
|
|
31
|
-
FILE_SOURCE_TYPES = frozenset({"file", "local_file", "upload", "pdf"})
|
|
32
|
-
# Source types that arrive as extracted text (read via ingest_source).
|
|
33
|
-
TEXT_SOURCE_TYPES = frozenset(
|
|
34
|
-
{"web_url", "browser_tab", "text", "markdown", "note", "code", "clipboard"}
|
|
35
|
-
)
|
|
36
|
-
# Conversational exchanges (read via ingest_message — role/content semantics,
|
|
37
|
-
# conversation chaining). v4: chat and MCP messages stop bypassing the
|
|
38
|
-
# pipeline, so they carry provenance and fire the hook lifecycle like every
|
|
39
|
-
# other source.
|
|
40
|
-
CHAT_SOURCE_TYPES = frozenset({"chat_message", "mcp_message"})
|
|
41
|
-
# Typed memory records (read via ingest_event → Decision/Experience/Event
|
|
42
|
-
# nodes). The Memory System writes through the same door as everything else.
|
|
43
|
-
MEMORY_SOURCE_TYPES = frozenset({"decision", "experience", "workspace_event"})
|
|
44
|
-
_MEMORY_NODE_TYPES = {"decision": "Decision", "experience": "Experience", "workspace_event": "Event"}
|
|
45
|
-
|
|
46
|
-
DEFAULT_MAX_TEXT_BYTES = 5 * 1024 * 1024 # 5 MB of extracted text per item
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _now_iso() -> str:
|
|
50
|
-
return datetime.now(timezone.utc).isoformat()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataclass
|
|
54
|
-
class IngestionItem:
|
|
55
|
-
"""A single thing to ingest, normalized across every source type."""
|
|
56
|
-
|
|
57
|
-
source_type: str
|
|
58
|
-
title: Optional[str] = None
|
|
59
|
-
text: Optional[str] = None # text/web sources
|
|
60
|
-
path: Optional[str] = None # file sources
|
|
61
|
-
source_uri: Optional[str] = None
|
|
62
|
-
mime_type: Optional[str] = None
|
|
63
|
-
owner: Optional[str] = None
|
|
64
|
-
workspace_id: Optional[str] = None
|
|
65
|
-
permissions: Optional[Dict[str, Any]] = None
|
|
66
|
-
captured_at: Optional[str] = None
|
|
67
|
-
modified_at: Optional[str] = None
|
|
68
|
-
conversation_id: Optional[str] = None
|
|
69
|
-
agent_used: Optional[str] = None
|
|
70
|
-
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@dataclass
|
|
74
|
-
class IngestionResult:
|
|
75
|
-
"""The outcome of one ingestion, including provenance and idempotency."""
|
|
76
|
-
|
|
77
|
-
status: str # ok | unavailable | blocked | failed
|
|
78
|
-
source_type: str
|
|
79
|
-
node_id: Optional[str] = None
|
|
80
|
-
source_node_id: Optional[str] = None
|
|
81
|
-
content_hash: Optional[str] = None
|
|
82
|
-
title: Optional[str] = None
|
|
83
|
-
chunk_ids: List[str] = field(default_factory=list)
|
|
84
|
-
chunk_count: int = 0
|
|
85
|
-
duplicate: bool = False
|
|
86
|
-
embedded: bool = False
|
|
87
|
-
indexing_status: str = "pending" # indexed | skipped | failed | pending
|
|
88
|
-
provenance_id: Optional[str] = None
|
|
89
|
-
detail: Optional[str] = None
|
|
90
|
-
|
|
91
|
-
def as_dict(self) -> Dict[str, Any]:
|
|
92
|
-
return {
|
|
93
|
-
"status": self.status,
|
|
94
|
-
"source_type": self.source_type,
|
|
95
|
-
"node_id": self.node_id,
|
|
96
|
-
"source_node_id": self.source_node_id,
|
|
97
|
-
"content_hash": self.content_hash,
|
|
98
|
-
"title": self.title,
|
|
99
|
-
"chunk_ids": self.chunk_ids,
|
|
100
|
-
"chunk_count": self.chunk_count,
|
|
101
|
-
"duplicate": self.duplicate,
|
|
102
|
-
"embedded": self.embedded,
|
|
103
|
-
"indexing_status": self.indexing_status,
|
|
104
|
-
"provenance_id": self.provenance_id,
|
|
105
|
-
"detail": self.detail,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class IngestionPipeline:
|
|
110
|
-
"""Single normalized entrypoint that feeds every source into the graph."""
|
|
111
|
-
|
|
112
|
-
def __init__(
|
|
113
|
-
self,
|
|
114
|
-
knowledge_graph: Any,
|
|
115
|
-
*,
|
|
116
|
-
hooks: Any = None,
|
|
117
|
-
enable_graph: bool = True,
|
|
118
|
-
audit: Optional[Any] = None,
|
|
119
|
-
max_text_bytes: int = DEFAULT_MAX_TEXT_BYTES,
|
|
120
|
-
pipeline_name: str = "unified-ingestion",
|
|
121
|
-
) -> None:
|
|
122
|
-
self._kg = knowledge_graph
|
|
123
|
-
self._hooks = hooks
|
|
124
|
-
self._enable = bool(enable_graph)
|
|
125
|
-
self._audit = audit
|
|
126
|
-
self._max_text_bytes = int(max_text_bytes)
|
|
127
|
-
self._pipeline_name = pipeline_name
|
|
128
|
-
|
|
129
|
-
def available(self) -> bool:
|
|
130
|
-
return self._enable and self._kg is not None
|
|
131
|
-
|
|
132
|
-
# ── public API ───────────────────────────────────────────────────────────
|
|
133
|
-
def ingest(self, item: IngestionItem, *, user_email: Optional[str] = None) -> IngestionResult:
|
|
134
|
-
"""Normalize, hash, route through dispatch_tool, and record provenance."""
|
|
135
|
-
source_type = str(item.source_type or "text").strip()
|
|
136
|
-
if not self.available():
|
|
137
|
-
return IngestionResult(
|
|
138
|
-
status="unavailable", source_type=source_type,
|
|
139
|
-
indexing_status="skipped",
|
|
140
|
-
detail="Knowledge Graph is disabled (LATTICEAI_ENABLE_GRAPH).",
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
captured_at = item.captured_at or _now_iso()
|
|
144
|
-
owner = item.owner or user_email
|
|
145
|
-
tool_name = f"kg_ingest.{source_type}"
|
|
146
|
-
# Only the keys are read by the hook payload, so this dict is safe/cheap.
|
|
147
|
-
args = {
|
|
148
|
-
"source_type": source_type,
|
|
149
|
-
"source_uri": item.source_uri,
|
|
150
|
-
"owner": owner,
|
|
151
|
-
"workspace_id": item.workspace_id,
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
def _run() -> Dict[str, Any]:
|
|
155
|
-
if source_type in CHAT_SOURCE_TYPES:
|
|
156
|
-
return self._ingest_chat(item, source_type=source_type, owner=owner)
|
|
157
|
-
if source_type in MEMORY_SOURCE_TYPES:
|
|
158
|
-
return self._ingest_memory_record(item, source_type=source_type, owner=owner)
|
|
159
|
-
if source_type in FILE_SOURCE_TYPES or (item.path and not item.text):
|
|
160
|
-
return self._ingest_file(item, source_type=source_type, owner=owner, captured_at=captured_at)
|
|
161
|
-
return self._ingest_text(item, source_type=source_type, owner=owner, captured_at=captured_at)
|
|
162
|
-
|
|
163
|
-
try:
|
|
164
|
-
raw = dispatch_tool(
|
|
165
|
-
self._hooks, tool_name, args, _run,
|
|
166
|
-
user_email=user_email, workspace_id=item.workspace_id, source="ingestion",
|
|
167
|
-
)
|
|
168
|
-
except PermissionError as exc:
|
|
169
|
-
return IngestionResult(
|
|
170
|
-
status="blocked", source_type=source_type,
|
|
171
|
-
indexing_status="skipped", detail=str(exc),
|
|
172
|
-
)
|
|
173
|
-
except FileNotFoundError as exc:
|
|
174
|
-
return IngestionResult(
|
|
175
|
-
status="failed", source_type=source_type,
|
|
176
|
-
indexing_status="failed", detail=str(exc),
|
|
177
|
-
)
|
|
178
|
-
except Exception as exc: # noqa: BLE001 — surface as a failed result, never crash the caller
|
|
179
|
-
return IngestionResult(
|
|
180
|
-
status="failed", source_type=source_type,
|
|
181
|
-
indexing_status="failed", detail=str(exc),
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
node_id = raw.get("node_id")
|
|
185
|
-
content_hash = raw.get("content_hash") or raw.get("sha256")
|
|
186
|
-
chunk_ids = list(raw.get("chunk_ids") or [])
|
|
187
|
-
embedded = bool(self._kg.node_is_embedded(node_id)) if node_id else False
|
|
188
|
-
title = raw.get("title") or item.title
|
|
189
|
-
|
|
190
|
-
prov = self._kg.record_provenance(
|
|
191
|
-
node_id=node_id,
|
|
192
|
-
source_type=source_type,
|
|
193
|
-
pipeline=self._pipeline_name,
|
|
194
|
-
source_uri=item.source_uri,
|
|
195
|
-
content_hash=content_hash,
|
|
196
|
-
title=title,
|
|
197
|
-
owner=owner,
|
|
198
|
-
workspace_id=item.workspace_id,
|
|
199
|
-
captured_at=captured_at,
|
|
200
|
-
modified_at=item.modified_at,
|
|
201
|
-
embedded=embedded,
|
|
202
|
-
linked=bool(raw.get("source_node_id")),
|
|
203
|
-
duplicate=bool(raw.get("duplicate")),
|
|
204
|
-
agent_used=item.agent_used,
|
|
205
|
-
chunk_count=len(chunk_ids),
|
|
206
|
-
permissions=item.permissions,
|
|
207
|
-
metadata=item.metadata,
|
|
208
|
-
)
|
|
209
|
-
if self._audit is not None:
|
|
210
|
-
try:
|
|
211
|
-
self._audit(
|
|
212
|
-
"kg_ingest",
|
|
213
|
-
{
|
|
214
|
-
"source_type": source_type, "node_id": node_id,
|
|
215
|
-
"content_hash": content_hash, "duplicate": bool(raw.get("duplicate")),
|
|
216
|
-
},
|
|
217
|
-
user_email,
|
|
218
|
-
)
|
|
219
|
-
except Exception: # noqa: BLE001 — audit must never break ingestion
|
|
220
|
-
pass
|
|
221
|
-
|
|
222
|
-
return IngestionResult(
|
|
223
|
-
status="ok",
|
|
224
|
-
source_type=source_type,
|
|
225
|
-
node_id=node_id,
|
|
226
|
-
source_node_id=raw.get("source_node_id"),
|
|
227
|
-
content_hash=content_hash,
|
|
228
|
-
title=title,
|
|
229
|
-
chunk_ids=chunk_ids,
|
|
230
|
-
chunk_count=len(chunk_ids),
|
|
231
|
-
duplicate=bool(raw.get("duplicate")),
|
|
232
|
-
embedded=embedded,
|
|
233
|
-
indexing_status="indexed",
|
|
234
|
-
provenance_id=prov.get("id"),
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
# ── routing helpers ──────────────────────────────────────────────────────
|
|
238
|
-
def _ingest_text(self, item, *, source_type, owner, captured_at) -> Dict[str, Any]:
|
|
239
|
-
text = item.text or ""
|
|
240
|
-
if len(text.encode("utf-8", "ignore")) > self._max_text_bytes:
|
|
241
|
-
raise ValueError(
|
|
242
|
-
f"Text payload exceeds the {self._max_text_bytes // (1024 * 1024)}MB ingestion limit."
|
|
243
|
-
)
|
|
244
|
-
title = item.title or item.source_uri or source_type
|
|
245
|
-
return self._kg.ingest_source(
|
|
246
|
-
source_type=source_type,
|
|
247
|
-
title=title,
|
|
248
|
-
text=text,
|
|
249
|
-
source_uri=item.source_uri,
|
|
250
|
-
owner=owner,
|
|
251
|
-
workspace_id=item.workspace_id,
|
|
252
|
-
permissions=item.permissions,
|
|
253
|
-
captured_at=captured_at,
|
|
254
|
-
modified_at=item.modified_at,
|
|
255
|
-
conversation_id=item.conversation_id,
|
|
256
|
-
metadata={"mime_type": item.mime_type, **(item.metadata or {})},
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
def _ingest_chat(self, item, *, source_type, owner) -> Dict[str, Any]:
|
|
260
|
-
text = item.text or ""
|
|
261
|
-
meta = item.metadata or {}
|
|
262
|
-
role = str(meta.get("role") or "user")
|
|
263
|
-
result = self._kg.ingest_message(
|
|
264
|
-
role,
|
|
265
|
-
text,
|
|
266
|
-
user_email=owner,
|
|
267
|
-
user_nickname=meta.get("user_nickname"),
|
|
268
|
-
source=meta.get("source") or source_type,
|
|
269
|
-
conversation_id=item.conversation_id,
|
|
270
|
-
raw=meta.get("raw"),
|
|
271
|
-
)
|
|
272
|
-
# ingest_message reports message/response node ids; normalize the keys
|
|
273
|
-
# the provenance step expects.
|
|
274
|
-
result.setdefault("node_id", result.get("node_id") or result.get("message_node_id") or result.get("id"))
|
|
275
|
-
result.setdefault("title", item.title or text[:80])
|
|
276
|
-
return result
|
|
277
|
-
|
|
278
|
-
def _ingest_memory_record(self, item, *, source_type, owner) -> Dict[str, Any]:
|
|
279
|
-
node_type = _MEMORY_NODE_TYPES[source_type]
|
|
280
|
-
meta = item.metadata or {}
|
|
281
|
-
result = self._kg.ingest_event(
|
|
282
|
-
node_type,
|
|
283
|
-
item.title or (item.text or node_type)[:120],
|
|
284
|
-
user_email=owner,
|
|
285
|
-
source=meta.get("source") or source_type,
|
|
286
|
-
conversation_id=item.conversation_id,
|
|
287
|
-
metadata={**meta, "detail": (item.text or "")[:2000]},
|
|
288
|
-
)
|
|
289
|
-
result.setdefault("node_id", result.get("node_id") or result.get("id"))
|
|
290
|
-
result.setdefault("title", item.title)
|
|
291
|
-
return result
|
|
292
|
-
|
|
293
|
-
def _ingest_file(self, item, *, source_type, owner, captured_at) -> Dict[str, Any]:
|
|
294
|
-
if not item.path:
|
|
295
|
-
raise ValueError("File ingestion requires a path.")
|
|
296
|
-
path = Path(item.path)
|
|
297
|
-
if not path.exists():
|
|
298
|
-
raise FileNotFoundError(f"File not found: {path}")
|
|
299
|
-
return self._kg.ingest_document(
|
|
300
|
-
path,
|
|
301
|
-
original_filename=item.title or path.name,
|
|
302
|
-
mime_type=item.mime_type,
|
|
303
|
-
uploader=owner,
|
|
304
|
-
conversation_id=item.conversation_id,
|
|
305
|
-
extracted=item.metadata.get("extracted") if item.metadata else None,
|
|
306
|
-
source_type=source_type,
|
|
307
|
-
source_uri=item.source_uri or str(path),
|
|
308
|
-
captured_at=captured_at,
|
|
309
|
-
modified_at=item.modified_at,
|
|
310
|
-
owner=owner,
|
|
311
|
-
workspace_id=item.workspace_id,
|
|
312
|
-
permissions=item.permissions,
|
|
313
|
-
)
|
|
7
|
+
import sys
|
|
314
8
|
|
|
9
|
+
import lattice_brain.ingestion as _impl
|
|
315
10
|
|
|
316
|
-
|
|
317
|
-
"""Canonical content hash for a text payload (matches store hashing scheme)."""
|
|
318
|
-
return hashlib.sha256((text or "").encode("utf-8", "ignore")).hexdigest()
|
|
11
|
+
sys.modules[__name__] = _impl
|