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.
Files changed (109) hide show
  1. package/README.md +44 -39
  2. package/docs/CHANGELOG.md +109 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/ONBOARDING.md +1 -1
  6. package/docs/OPERATIONS.md +1 -1
  7. package/docs/SURFACE_PARITY.md +47 -0
  8. package/docs/TRUST_MODEL.md +1 -1
  9. package/docs/WHY_LATTICE.md +1 -1
  10. package/docs/kg-schema.md +1 -1
  11. package/lattice_brain/__init__.py +1 -1
  12. package/lattice_brain/graph/_kg_common.py +335 -0
  13. package/lattice_brain/graph/curator.py +141 -0
  14. package/lattice_brain/graph/discovery_index.py +8 -1
  15. package/lattice_brain/graph/documents.py +111 -0
  16. package/lattice_brain/graph/fusion.py +179 -0
  17. package/lattice_brain/graph/ingest.py +43 -9
  18. package/lattice_brain/graph/projection.py +382 -37
  19. package/lattice_brain/graph/retrieval.py +71 -4
  20. package/lattice_brain/graph/retrieval_policy.py +174 -0
  21. package/lattice_brain/graph/retrieval_vector.py +122 -2
  22. package/lattice_brain/ingestion.py +66 -0
  23. package/lattice_brain/portability.py +41 -12
  24. package/lattice_brain/runtime/multi_agent.py +1 -1
  25. package/latticeai/__init__.py +1 -1
  26. package/latticeai/api/automation_intelligence.py +159 -0
  27. package/latticeai/api/browser.py +15 -4
  28. package/latticeai/api/chat.py +33 -2
  29. package/latticeai/api/chat_agent_http.py +363 -6
  30. package/latticeai/api/chat_contracts.py +7 -1
  31. package/latticeai/api/chat_helpers.py +147 -0
  32. package/latticeai/api/chat_intents.py +217 -10
  33. package/latticeai/api/chat_stream.py +93 -3
  34. package/latticeai/api/funnel_metrics.py +31 -0
  35. package/latticeai/api/knowledge_graph.py +65 -1
  36. package/latticeai/api/local_files.py +90 -0
  37. package/latticeai/api/setup.py +134 -1
  38. package/latticeai/api/tools.py +33 -3
  39. package/latticeai/app_factory.py +14 -0
  40. package/latticeai/core/agent.py +355 -18
  41. package/latticeai/core/agent_eval.py +107 -0
  42. package/latticeai/core/enterprise.py +5 -0
  43. package/latticeai/core/file_generation.py +404 -2
  44. package/latticeai/core/legacy_compatibility.py +1 -1
  45. package/latticeai/core/marketplace.py +1 -1
  46. package/latticeai/core/run_store.py +243 -0
  47. package/latticeai/core/workspace_os.py +1 -1
  48. package/latticeai/models/router.py +25 -12
  49. package/latticeai/runtime/persistence_runtime.py +19 -1
  50. package/latticeai/runtime/router_registration.py +8 -1
  51. package/latticeai/services/app_context.py +3 -0
  52. package/latticeai/services/architecture_readiness.py +1 -1
  53. package/latticeai/services/automation_execution.py +266 -0
  54. package/latticeai/services/automation_intelligence.py +7 -0
  55. package/latticeai/services/command_center.py +110 -2
  56. package/latticeai/services/folder_watch.py +396 -0
  57. package/latticeai/services/funnel_metrics.py +210 -0
  58. package/latticeai/services/product_readiness.py +1 -1
  59. package/latticeai/services/search_service.py +51 -5
  60. package/latticeai/setup/demo_corpus.py +98 -0
  61. package/latticeai/tools/filesystem.py +38 -1
  62. package/package.json +1 -1
  63. package/scripts/bench_agent_smoke.py +410 -0
  64. package/scripts/bench_models.py +234 -5
  65. package/scripts/check_current_release_docs.mjs +1 -1
  66. package/scripts/funnel_soft_gate.py +192 -0
  67. package/src-tauri/Cargo.lock +1 -1
  68. package/src-tauri/Cargo.toml +1 -1
  69. package/src-tauri/tauri.conf.json +1 -1
  70. package/static/app/asset-manifest.json +29 -27
  71. package/static/app/assets/Act-BCmTU0E2.js +1 -0
  72. package/static/app/assets/{Brain-BnGbytHa.js → Brain-CTnjox7w.js} +2 -2
  73. package/static/app/assets/BrainHome-BJ3sFNX0.js +2 -0
  74. package/static/app/assets/BrainSignals-C52lwZVD.js +1 -0
  75. package/static/app/assets/{Capture-CAKnLcb0.js → Capture-B6vBhFa3.js} +1 -1
  76. package/static/app/assets/CommandPalette-90u9FWFH.js +1 -0
  77. package/static/app/assets/Library-pKCK0_tk.js +1 -0
  78. package/static/app/assets/{LivingBrain-C3VbogVo.js → LivingBrain-Jlf2wFqI.js} +1 -1
  79. package/static/app/assets/{ProductFlow-BLau7c7N.js → ProductFlow-yg1fKP1P.js} +1 -1
  80. package/static/app/assets/ReviewCard-DWvD7n9h.js +3 -0
  81. package/static/app/assets/{System-DRHc_6Su.js → System-BAEuHqNY.js} +1 -1
  82. package/static/app/assets/{bot-DZkDdh-A.js → bot-nB_buEZD.js} +1 -1
  83. package/static/app/assets/circle-pause-DLNw6Ucp.js +1 -0
  84. package/static/app/assets/circle-play-B-IsFL1y.js +1 -0
  85. package/static/app/assets/{cpu-DyvsDBbj.js → cpu-CEPBHaBl.js} +1 -1
  86. package/static/app/assets/{folder-open-CffoIr9t.js → folder-open-hmN0N9cX.js} +1 -1
  87. package/static/app/assets/{hard-drive-C1oojrHb.js → hard-drive-CBV_B_Yd.js} +1 -1
  88. package/static/app/assets/index-7FAfYm4v.css +2 -0
  89. package/static/app/assets/{index-BS_3aN1n.js → index-DrmOCySv.js} +3 -3
  90. package/static/app/assets/{input-B_U7zvbR.js → input-gtVCg-ll.js} +1 -1
  91. package/static/app/assets/{navigation-xG-gyjbe.js → navigation-Bot0hvuv.js} +1 -1
  92. package/static/app/assets/{network-BF13HJJF.js → network-jE42eKfT.js} +1 -1
  93. package/static/app/assets/{primitives-DsuKh3M8.js → primitives-CX2Komon.js} +1 -1
  94. package/static/app/assets/shield-alert-BftATuAA.js +1 -0
  95. package/static/app/assets/{textarea-DsDL4ET8.js → textarea-CiMJfOSI.js} +1 -1
  96. package/static/app/assets/useFocusTrap-B7RPGfFy.js +1 -0
  97. package/static/app/assets/utils-SJUNVOj5.js +7 -0
  98. package/static/app/index.html +4 -4
  99. package/static/sw.js +1 -1
  100. package/static/app/assets/Act-Cx_gZNbE.js +0 -1
  101. package/static/app/assets/BrainHome-CRUMJL93.js +0 -2
  102. package/static/app/assets/BrainSignals-BP8-Qh2g.js +0 -1
  103. package/static/app/assets/CommandPalette-BoUTXBfm.js +0 -1
  104. package/static/app/assets/Library-PcfVUco6.js +0 -1
  105. package/static/app/assets/ReviewCard-CX5RqRqx.js +0 -1
  106. package/static/app/assets/download-C7yxYHvz.js +0 -1
  107. package/static/app/assets/index-DU78Kogz.css +0 -2
  108. package/static/app/assets/sunrise-CIP2ELJ1.js +0 -1
  109. package/static/app/assets/utils-Dh-Dwtkb.js +0 -7
@@ -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
- def create_setup_router(*, model_router, require_user) -> APIRouter:
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)
@@ -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)
@@ -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 ────────────────────────────────────────────────────────────────