ltcai 9.9.2 → 9.9.4
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 +44 -39
- package/docs/CHANGELOG.md +109 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/SURFACE_PARITY.md +47 -0
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/kg-schema.md +1 -1
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/_kg_common.py +335 -0
- package/lattice_brain/graph/curator.py +141 -0
- package/lattice_brain/graph/discovery_index.py +8 -1
- package/lattice_brain/graph/documents.py +111 -0
- package/lattice_brain/graph/fusion.py +179 -0
- package/lattice_brain/graph/ingest.py +43 -9
- package/lattice_brain/graph/projection.py +382 -37
- package/lattice_brain/graph/retrieval.py +71 -4
- package/lattice_brain/graph/retrieval_policy.py +174 -0
- package/lattice_brain/graph/retrieval_vector.py +122 -2
- package/lattice_brain/ingestion.py +66 -0
- package/lattice_brain/portability.py +41 -12
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/automation_intelligence.py +159 -0
- package/latticeai/api/browser.py +15 -4
- package/latticeai/api/chat.py +33 -2
- package/latticeai/api/chat_agent_http.py +363 -6
- package/latticeai/api/chat_contracts.py +7 -1
- package/latticeai/api/chat_helpers.py +147 -0
- package/latticeai/api/chat_intents.py +217 -10
- package/latticeai/api/chat_stream.py +93 -3
- package/latticeai/api/funnel_metrics.py +31 -0
- package/latticeai/api/knowledge_graph.py +65 -1
- package/latticeai/api/local_files.py +90 -0
- package/latticeai/api/setup.py +134 -1
- package/latticeai/api/tools.py +33 -3
- package/latticeai/app_factory.py +14 -0
- package/latticeai/core/agent.py +355 -18
- package/latticeai/core/agent_eval.py +107 -0
- package/latticeai/core/enterprise.py +5 -0
- package/latticeai/core/file_generation.py +404 -2
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/run_store.py +243 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/models/router.py +25 -12
- package/latticeai/runtime/persistence_runtime.py +19 -1
- package/latticeai/runtime/router_registration.py +8 -1
- package/latticeai/services/app_context.py +3 -0
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/automation_execution.py +266 -0
- package/latticeai/services/automation_intelligence.py +7 -0
- package/latticeai/services/command_center.py +110 -2
- package/latticeai/services/folder_watch.py +396 -0
- package/latticeai/services/funnel_metrics.py +210 -0
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/search_service.py +51 -5
- package/latticeai/setup/demo_corpus.py +98 -0
- package/latticeai/tools/filesystem.py +38 -1
- package/package.json +1 -1
- package/scripts/bench_agent_smoke.py +410 -0
- package/scripts/bench_models.py +234 -5
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/funnel_soft_gate.py +192 -0
- 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 +29 -27
- package/static/app/assets/Act-BCmTU0E2.js +1 -0
- package/static/app/assets/{Brain-BnGbytHa.js → Brain-CTnjox7w.js} +2 -2
- package/static/app/assets/BrainHome-BJ3sFNX0.js +2 -0
- package/static/app/assets/BrainSignals-C52lwZVD.js +1 -0
- package/static/app/assets/{Capture-CAKnLcb0.js → Capture-B6vBhFa3.js} +1 -1
- package/static/app/assets/CommandPalette-90u9FWFH.js +1 -0
- package/static/app/assets/Library-pKCK0_tk.js +1 -0
- package/static/app/assets/{LivingBrain-C3VbogVo.js → LivingBrain-Jlf2wFqI.js} +1 -1
- package/static/app/assets/{ProductFlow-BLau7c7N.js → ProductFlow-yg1fKP1P.js} +1 -1
- package/static/app/assets/ReviewCard-DWvD7n9h.js +3 -0
- package/static/app/assets/{System-DRHc_6Su.js → System-BAEuHqNY.js} +1 -1
- package/static/app/assets/{bot-DZkDdh-A.js → bot-nB_buEZD.js} +1 -1
- package/static/app/assets/circle-pause-DLNw6Ucp.js +1 -0
- package/static/app/assets/circle-play-B-IsFL1y.js +1 -0
- package/static/app/assets/{cpu-DyvsDBbj.js → cpu-CEPBHaBl.js} +1 -1
- package/static/app/assets/{folder-open-CffoIr9t.js → folder-open-hmN0N9cX.js} +1 -1
- package/static/app/assets/{hard-drive-C1oojrHb.js → hard-drive-CBV_B_Yd.js} +1 -1
- package/static/app/assets/index-7FAfYm4v.css +2 -0
- package/static/app/assets/{index-BS_3aN1n.js → index-DrmOCySv.js} +3 -3
- package/static/app/assets/{input-B_U7zvbR.js → input-gtVCg-ll.js} +1 -1
- package/static/app/assets/{navigation-xG-gyjbe.js → navigation-Bot0hvuv.js} +1 -1
- package/static/app/assets/{network-BF13HJJF.js → network-jE42eKfT.js} +1 -1
- package/static/app/assets/{primitives-DsuKh3M8.js → primitives-CX2Komon.js} +1 -1
- package/static/app/assets/shield-alert-BftATuAA.js +1 -0
- package/static/app/assets/{textarea-DsDL4ET8.js → textarea-CiMJfOSI.js} +1 -1
- package/static/app/assets/useFocusTrap-B7RPGfFy.js +1 -0
- package/static/app/assets/utils-SJUNVOj5.js +7 -0
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/static/app/assets/Act-Cx_gZNbE.js +0 -1
- package/static/app/assets/BrainHome-CRUMJL93.js +0 -2
- package/static/app/assets/BrainSignals-BP8-Qh2g.js +0 -1
- package/static/app/assets/CommandPalette-BoUTXBfm.js +0 -1
- package/static/app/assets/Library-PcfVUco6.js +0 -1
- package/static/app/assets/ReviewCard-CX5RqRqx.js +0 -1
- package/static/app/assets/download-C7yxYHvz.js +0 -1
- package/static/app/assets/index-DU78Kogz.css +0 -2
- package/static/app/assets/sunrise-CIP2ELJ1.js +0 -1
- package/static/app/assets/utils-Dh-Dwtkb.js +0 -7
package/latticeai/api/setup.py
CHANGED
|
@@ -17,7 +17,15 @@ from latticeai.setup.auto_setup import (
|
|
|
17
17
|
verify as auto_setup_verify,
|
|
18
18
|
)
|
|
19
19
|
from latticeai.models.router import parse_model_ref
|
|
20
|
+
from latticeai.setup.demo_corpus import (
|
|
21
|
+
DEMO_DOCUMENTS,
|
|
22
|
+
DEMO_METADATA_FLAG,
|
|
23
|
+
DEMO_URI_PREFIX,
|
|
24
|
+
demo_source_uri,
|
|
25
|
+
suggested_questions,
|
|
26
|
+
)
|
|
20
27
|
from latticeai.setup.wizard import get_recommendations, install_stream, open_url, scan_environment
|
|
28
|
+
from lattice_brain.ingestion import IngestionItem
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
class SetupInstallRequest(BaseModel):
|
|
@@ -25,7 +33,18 @@ class SetupInstallRequest(BaseModel):
|
|
|
25
33
|
confirmation_token: Optional[str] = None
|
|
26
34
|
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
class DemoCorpusRequest(BaseModel):
|
|
37
|
+
workspace_id: Optional[str] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def create_setup_router(
|
|
41
|
+
*,
|
|
42
|
+
model_router,
|
|
43
|
+
require_user,
|
|
44
|
+
ingestion_pipeline=None,
|
|
45
|
+
knowledge_graph=None,
|
|
46
|
+
workspace_service=None,
|
|
47
|
+
) -> APIRouter:
|
|
29
48
|
api_router = APIRouter()
|
|
30
49
|
router = model_router
|
|
31
50
|
|
|
@@ -157,6 +176,120 @@ def create_setup_router(*, model_router, require_user) -> APIRouter:
|
|
|
157
176
|
return {"status": "ok", "opened": url, "mcp_id": mcp_id}
|
|
158
177
|
|
|
159
178
|
|
|
179
|
+
# ── First Value Loop demo corpus (backlog #3, review §3.3 P0) ────────────
|
|
180
|
+
|
|
181
|
+
def _require_demo_pipeline():
|
|
182
|
+
if ingestion_pipeline is None or not ingestion_pipeline.available():
|
|
183
|
+
raise HTTPException(
|
|
184
|
+
status_code=503, detail="Knowledge Graph ingestion is disabled.",
|
|
185
|
+
)
|
|
186
|
+
if knowledge_graph is None:
|
|
187
|
+
raise HTTPException(status_code=503, detail="Knowledge Graph is disabled.")
|
|
188
|
+
|
|
189
|
+
def _demo_workspace(request: Request, body_workspace: Optional[str], user: str) -> Optional[str]:
|
|
190
|
+
header = request.headers.get("X-Workspace-Id")
|
|
191
|
+
header = header.strip() if header and header.strip() else None
|
|
192
|
+
supplied = [value for value in (body_workspace, header) if value]
|
|
193
|
+
if len(set(supplied)) > 1:
|
|
194
|
+
raise HTTPException(status_code=403, detail="Workspace selectors must match.")
|
|
195
|
+
requested = supplied[0] if supplied else None
|
|
196
|
+
if workspace_service is None:
|
|
197
|
+
return requested
|
|
198
|
+
try:
|
|
199
|
+
return workspace_service.resolve_write_scope(requested, user or None)
|
|
200
|
+
except PermissionError as exc:
|
|
201
|
+
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
|
202
|
+
|
|
203
|
+
@api_router.get("/api/setup/demo-corpus")
|
|
204
|
+
async def demo_corpus_status(request: Request):
|
|
205
|
+
"""Whether the demo corpus is installed + the suggestion chips."""
|
|
206
|
+
require_user(request)
|
|
207
|
+
_require_demo_pipeline()
|
|
208
|
+
installed = knowledge_graph.find_documents_by_uri_prefix(DEMO_URI_PREFIX)
|
|
209
|
+
return {
|
|
210
|
+
"installed": bool(installed),
|
|
211
|
+
"documents": installed,
|
|
212
|
+
"document_count": len(installed),
|
|
213
|
+
"suggested_questions": suggested_questions(),
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@api_router.post("/api/setup/demo-corpus")
|
|
217
|
+
async def demo_corpus_install(request: Request, req: Optional[DemoCorpusRequest] = None):
|
|
218
|
+
"""One-click ingest of the 3 built-in demo documents (idempotent).
|
|
219
|
+
|
|
220
|
+
Every document goes through the normal IngestionPipeline door with
|
|
221
|
+
``demo://`` provenance so it is honest (real nodes, real chunks, real
|
|
222
|
+
provenance) and removable via DELETE. Re-POSTing reports duplicates
|
|
223
|
+
instead of duplicating.
|
|
224
|
+
"""
|
|
225
|
+
user = require_user(request)
|
|
226
|
+
_require_demo_pipeline()
|
|
227
|
+
workspace_id = _demo_workspace(request, req.workspace_id if req else None, user)
|
|
228
|
+
results = []
|
|
229
|
+
ingested = 0
|
|
230
|
+
duplicates = 0
|
|
231
|
+
failed = 0
|
|
232
|
+
for doc in DEMO_DOCUMENTS:
|
|
233
|
+
result = ingestion_pipeline.ingest(
|
|
234
|
+
IngestionItem(
|
|
235
|
+
source_type="note",
|
|
236
|
+
title=doc["title"],
|
|
237
|
+
text=doc["text"],
|
|
238
|
+
source_uri=demo_source_uri(doc["id"]),
|
|
239
|
+
owner=user or None,
|
|
240
|
+
workspace_id=workspace_id,
|
|
241
|
+
metadata={DEMO_METADATA_FLAG: True, "demo_id": doc["id"]},
|
|
242
|
+
),
|
|
243
|
+
user_email=user or None,
|
|
244
|
+
)
|
|
245
|
+
if result.status != "ok":
|
|
246
|
+
failed += 1
|
|
247
|
+
elif result.duplicate:
|
|
248
|
+
duplicates += 1
|
|
249
|
+
else:
|
|
250
|
+
ingested += 1
|
|
251
|
+
results.append({
|
|
252
|
+
"demo_id": doc["id"],
|
|
253
|
+
"title": doc["title"],
|
|
254
|
+
"source_uri": demo_source_uri(doc["id"]),
|
|
255
|
+
"status": result.status,
|
|
256
|
+
"node_id": result.node_id,
|
|
257
|
+
"duplicate": result.duplicate,
|
|
258
|
+
"chunk_count": result.chunk_count,
|
|
259
|
+
"detail": result.detail,
|
|
260
|
+
})
|
|
261
|
+
status = "ok" if failed == 0 else ("partial" if (ingested + duplicates) else "failed")
|
|
262
|
+
return {
|
|
263
|
+
"status": status,
|
|
264
|
+
"ingested": ingested,
|
|
265
|
+
"duplicates": duplicates,
|
|
266
|
+
"failed": failed,
|
|
267
|
+
"documents": results,
|
|
268
|
+
"suggested_questions": suggested_questions(),
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@api_router.delete("/api/setup/demo-corpus")
|
|
272
|
+
async def demo_corpus_remove(request: Request):
|
|
273
|
+
"""Remove every demo document (node + chunks + edges + orphan source)."""
|
|
274
|
+
require_user(request)
|
|
275
|
+
_require_demo_pipeline()
|
|
276
|
+
installed = knowledge_graph.find_documents_by_uri_prefix(DEMO_URI_PREFIX)
|
|
277
|
+
removed = []
|
|
278
|
+
for doc in installed:
|
|
279
|
+
outcome = knowledge_graph.delete_document_tree(doc["id"])
|
|
280
|
+
removed.append({
|
|
281
|
+
"node_id": doc["id"],
|
|
282
|
+
"title": doc.get("title"),
|
|
283
|
+
"source_uri": doc.get("source_uri"),
|
|
284
|
+
"status": outcome.get("status"),
|
|
285
|
+
"removed_nodes": outcome.get("removed_nodes", 0),
|
|
286
|
+
})
|
|
287
|
+
return {
|
|
288
|
+
"status": "ok",
|
|
289
|
+
"removed_count": len(removed),
|
|
290
|
+
"removed": removed,
|
|
291
|
+
}
|
|
292
|
+
|
|
160
293
|
@api_router.post("/permissions/open/{permission_id}")
|
|
161
294
|
async def open_permission_settings(permission_id: str, request: Request):
|
|
162
295
|
require_user(request)
|
package/latticeai/api/tools.py
CHANGED
|
@@ -11,7 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Any, Dict, List, Optional
|
|
12
12
|
|
|
13
13
|
from fastapi import APIRouter, File, HTTPException, Request, UploadFile
|
|
14
|
-
from fastapi.responses import FileResponse
|
|
14
|
+
from fastapi.responses import FileResponse, Response
|
|
15
15
|
from pydantic import BaseModel
|
|
16
16
|
|
|
17
17
|
from latticeai.api.computer_use import create_computer_use_router
|
|
@@ -543,8 +543,38 @@ def create_tools_router(
|
|
|
543
543
|
filename=target.name,
|
|
544
544
|
media_type="application/octet-stream",
|
|
545
545
|
)
|
|
546
|
-
|
|
547
|
-
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
@api_router.get("/tools/download_zip")
|
|
549
|
+
async def tools_download_zip(path: str, request: Request):
|
|
550
|
+
"""Serve a generated project directory from the agent workspace as a zip.
|
|
551
|
+
|
|
552
|
+
The multi-file artifact loop writes bundles like ``todo-app/`` —
|
|
553
|
+
this is their one-click download. Path confinement mirrors
|
|
554
|
+
``/tools/download``: anything outside the workspace is refused.
|
|
555
|
+
"""
|
|
556
|
+
require_user(request)
|
|
557
|
+
from urllib.parse import unquote
|
|
558
|
+
|
|
559
|
+
from latticeai.tools.filesystem import zip_workspace_dir
|
|
560
|
+
|
|
561
|
+
rel = unquote(path).lstrip("/")
|
|
562
|
+
target = (AGENT_ROOT / rel).resolve()
|
|
563
|
+
if AGENT_ROOT not in target.parents and target != AGENT_ROOT:
|
|
564
|
+
raise HTTPException(status_code=403, detail="경로가 작업 공간 밖입니다.")
|
|
565
|
+
if not target.exists() or not target.is_dir():
|
|
566
|
+
raise HTTPException(status_code=404, detail="디렉터리가 없습니다.")
|
|
567
|
+
try:
|
|
568
|
+
payload, filename = zip_workspace_dir(rel)
|
|
569
|
+
except ToolError as exc:
|
|
570
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
571
|
+
return Response(
|
|
572
|
+
content=payload,
|
|
573
|
+
media_type="application/zip",
|
|
574
|
+
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
548
578
|
@api_router.post("/upload/document")
|
|
549
579
|
async def upload_document(request: Request, file: UploadFile = File(...)):
|
|
550
580
|
current_user = require_user(request)
|
package/latticeai/app_factory.py
CHANGED
|
@@ -395,6 +395,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
395
395
|
INGESTION_PIPELINE = _persistence_runtime["INGESTION_PIPELINE"]
|
|
396
396
|
DEVICE_IDENTITY = _persistence_runtime["DEVICE_IDENTITY"]
|
|
397
397
|
KG_PORTABILITY = _persistence_runtime["KG_PORTABILITY"]
|
|
398
|
+
FUNNEL_METRICS = _persistence_runtime["FUNNEL_METRICS"]
|
|
398
399
|
|
|
399
400
|
def _require_graph():
|
|
400
401
|
if not ENABLE_GRAPH or KNOWLEDGE_GRAPH is None:
|
|
@@ -951,6 +952,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
951
952
|
public_model=PUBLIC_MODEL,
|
|
952
953
|
local_model=LOCAL_MODEL or "",
|
|
953
954
|
on_chat_message=on_chat_message,
|
|
955
|
+
funnel_metrics=FUNNEL_METRICS,
|
|
954
956
|
)
|
|
955
957
|
app.state.context = context
|
|
956
958
|
|
|
@@ -1036,6 +1038,17 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1036
1038
|
gate_write=PLATFORM.gate_write,
|
|
1037
1039
|
append_audit_event=append_audit_event,
|
|
1038
1040
|
workspace_graph=_workspace_graph,
|
|
1041
|
+
run_executor=RUN_EXECUTOR,
|
|
1042
|
+
review_queue=REVIEW_QUEUE,
|
|
1043
|
+
)
|
|
1044
|
+
)
|
|
1045
|
+
# UX funnel metrics (backlog #16): admin-only runtime counters.
|
|
1046
|
+
from latticeai.api.funnel_metrics import create_funnel_metrics_router
|
|
1047
|
+
|
|
1048
|
+
app.include_router(
|
|
1049
|
+
create_funnel_metrics_router(
|
|
1050
|
+
service=FUNNEL_METRICS,
|
|
1051
|
+
require_admin=require_admin,
|
|
1039
1052
|
)
|
|
1040
1053
|
)
|
|
1041
1054
|
COMMAND_CENTER = CommandCenterService(
|
|
@@ -1212,6 +1225,7 @@ def _build(config: "Optional[Config]" = None) -> Dict[str, Any]:
|
|
|
1212
1225
|
gardener=gardener,
|
|
1213
1226
|
create_setup_router=create_setup_router,
|
|
1214
1227
|
model_router=router,
|
|
1228
|
+
knowledge_graph=KNOWLEDGE_GRAPH,
|
|
1215
1229
|
)
|
|
1216
1230
|
|
|
1217
1231
|
# ── Entry Point ────────────────────────────────────────────────────────────────
|