ltcai 10.3.0 → 10.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.
Files changed (143) hide show
  1. package/README.md +37 -36
  2. package/docs/CHANGELOG.md +77 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/MYPY_BACKLOG.md +59 -92
  6. package/docs/ONBOARDING.md +1 -1
  7. package/docs/OPERATIONS.md +1 -1
  8. package/docs/SURFACE_PARITY.md +33 -6
  9. package/docs/TRUST_MODEL.md +1 -1
  10. package/docs/WHY_LATTICE.md +1 -1
  11. package/docs/kg-schema.md +1 -1
  12. package/lattice_brain/__init__.py +1 -1
  13. package/lattice_brain/context.py +23 -3
  14. package/lattice_brain/graph/_kg_common.py +164 -9
  15. package/lattice_brain/graph/_kg_contract.py +223 -0
  16. package/lattice_brain/graph/_kg_fsutil.py +1 -1
  17. package/lattice_brain/graph/curator.py +4 -4
  18. package/lattice_brain/graph/discovery.py +12 -1
  19. package/lattice_brain/graph/discovery_index.py +12 -1
  20. package/lattice_brain/graph/documents.py +17 -2
  21. package/lattice_brain/graph/ingest.py +12 -1
  22. package/lattice_brain/graph/proactive.py +9 -5
  23. package/lattice_brain/graph/projection.py +12 -1
  24. package/lattice_brain/graph/provenance.py +12 -1
  25. package/lattice_brain/graph/retrieval.py +12 -1
  26. package/lattice_brain/graph/retrieval_docgen.py +12 -1
  27. package/lattice_brain/graph/retrieval_reads.py +12 -1
  28. package/lattice_brain/graph/retrieval_vector.py +12 -1
  29. package/lattice_brain/graph/schema.py +6 -2
  30. package/lattice_brain/graph/write_master.py +12 -1
  31. package/lattice_brain/ingestion.py +1 -1
  32. package/lattice_brain/quality.py +5 -5
  33. package/lattice_brain/runtime/contracts.py +2 -1
  34. package/lattice_brain/runtime/multi_agent.py +1 -1
  35. package/lattice_brain/workflow.py +10 -7
  36. package/latticeai/__init__.py +1 -1
  37. package/latticeai/api/admin.py +2 -2
  38. package/latticeai/api/chat.py +19 -15
  39. package/latticeai/api/chat_agent_http.py +19 -7
  40. package/latticeai/api/chat_helpers.py +9 -8
  41. package/latticeai/api/chat_stream.py +2 -2
  42. package/latticeai/api/computer_use.py +2 -2
  43. package/latticeai/api/hooks.py +4 -2
  44. package/latticeai/api/local_files.py +8 -4
  45. package/latticeai/api/mcp.py +2 -2
  46. package/latticeai/api/models.py +5 -5
  47. package/latticeai/api/permissions.py +2 -2
  48. package/latticeai/api/search.py +5 -3
  49. package/latticeai/api/security_dashboard.py +3 -5
  50. package/latticeai/api/setup.py +3 -3
  51. package/latticeai/api/static_routes.py +7 -2
  52. package/latticeai/api/tools.py +9 -4
  53. package/latticeai/api/workspace.py +25 -22
  54. package/latticeai/app_factory.py +132 -1322
  55. package/latticeai/cli/entrypoint.py +1 -1
  56. package/latticeai/core/agent.py +28 -14
  57. package/latticeai/core/audit.py +5 -3
  58. package/latticeai/core/embedding_providers.py +19 -6
  59. package/latticeai/core/legacy_compatibility.py +1 -1
  60. package/latticeai/core/marketplace.py +2 -2
  61. package/latticeai/core/mcp_catalog.py +3 -1
  62. package/latticeai/core/model_compat.py +1 -1
  63. package/latticeai/core/plugins.py +1 -1
  64. package/latticeai/core/project_sessions.py +1 -1
  65. package/latticeai/core/run_explain.py +6 -4
  66. package/latticeai/core/workspace_os.py +0 -4
  67. package/latticeai/core/workspace_os_constants.py +1 -1
  68. package/latticeai/core/workspace_os_state.py +1 -1
  69. package/latticeai/core/workspace_snapshots.py +3 -3
  70. package/latticeai/integrations/telegram_bot.py +52 -3
  71. package/latticeai/models/router.py +61 -34
  72. package/latticeai/runtime/access_runtime.py +3 -1
  73. package/latticeai/runtime/build_phases.py +1417 -0
  74. package/latticeai/runtime/review_wiring.py +6 -1
  75. package/latticeai/runtime/runtime_context.py +276 -0
  76. package/latticeai/runtime/sso_config_runtime.py +1 -1
  77. package/latticeai/runtime/stages.py +8 -2
  78. package/latticeai/services/app_context.py +23 -0
  79. package/latticeai/services/architecture_readiness.py +1 -1
  80. package/latticeai/services/brain_intelligence.py +4 -2
  81. package/latticeai/services/cloud_streaming.py +5 -3
  82. package/latticeai/services/command_center.py +1 -1
  83. package/latticeai/services/evidence_actions.py +2 -1
  84. package/latticeai/services/hybrid_chat.py +1 -1
  85. package/latticeai/services/local_knowledge.py +25 -9
  86. package/latticeai/services/memory_service.py +8 -4
  87. package/latticeai/services/model_catalog.py +12 -12
  88. package/latticeai/services/model_engines.py +15 -17
  89. package/latticeai/services/model_loading.py +1 -1
  90. package/latticeai/services/model_runtime.py +31 -33
  91. package/latticeai/services/multimodal_streaming.py +10 -4
  92. package/latticeai/services/openai_compatible_adapter.py +4 -3
  93. package/latticeai/services/product_readiness.py +1 -1
  94. package/latticeai/services/run_executor.py +12 -3
  95. package/latticeai/services/search_service.py +1 -1
  96. package/latticeai/services/triggers.py +1 -1
  97. package/latticeai/setup/auto_setup.py +1 -1
  98. package/latticeai/setup/wizard.py +12 -3
  99. package/latticeai/tools/computer.py +3 -1
  100. package/latticeai/tools/documents.py +2 -2
  101. package/package.json +1 -1
  102. package/scripts/check_current_release_docs.mjs +1 -1
  103. package/src-tauri/Cargo.lock +1 -1
  104. package/src-tauri/Cargo.toml +1 -1
  105. package/src-tauri/tauri.conf.json +1 -1
  106. package/static/app/asset-manifest.json +36 -36
  107. package/static/app/assets/{Act-DrKIGZeJ.js → Act-BK__Y5VE.js} +1 -1
  108. package/static/app/assets/{AdminConsole-CEV1VefA.js → AdminConsole-ZrTyN8_F.js} +1 -1
  109. package/static/app/assets/{Brain-BZSXnwWU.js → Brain-CYHDXpPB.js} +1 -1
  110. package/static/app/assets/{BrainHome-CEBd86k6.js → BrainHome-bbJulb5g.js} +1 -1
  111. package/static/app/assets/{BrainSignals-O4s6R7av.js → BrainSignals-CesSPtUP.js} +1 -1
  112. package/static/app/assets/{Capture-AGbOxsg-.js → Capture-p5upXmz2.js} +1 -1
  113. package/static/app/assets/{CommandPalette-CeqBjCn2.js → CommandPalette-0zkyC0RY.js} +1 -1
  114. package/static/app/assets/{Library-CXnbNiQW.js → Library-Dt-vABI2.js} +1 -1
  115. package/static/app/assets/{LivingBrain-D9Iei2ZJ.js → LivingBrain-Bz-h6LBH.js} +1 -1
  116. package/static/app/assets/{ProductFlow-JlXiDfMM.js → ProductFlow-DAZ7DJCE.js} +1 -1
  117. package/static/app/assets/{ReviewCard-QQly2bTV.js → ReviewCard-Bv0WGbY6.js} +1 -1
  118. package/static/app/assets/{System-Bp9ZY4ER.js → System-BhrJQSD_.js} +1 -1
  119. package/static/app/assets/{activity-CxRPOQ2F.js → activity-AolpZJCH.js} +1 -1
  120. package/static/app/assets/{bot-1bcsWB2s.js → bot-CsVsT2nd.js} +1 -1
  121. package/static/app/assets/{brain-CxaeKCRM.js → brain-vuppco5y.js} +1 -1
  122. package/static/app/assets/{button-ZYFqGan7.js → button-1BP1PJZ3.js} +1 -1
  123. package/static/app/assets/{circle-pause-B5NOIqvk.js → circle-pause-5paeCOtq.js} +1 -1
  124. package/static/app/assets/{circle-play-BiJVFXf7.js → circle-play-By8mFYRV.js} +1 -1
  125. package/static/app/assets/{cpu-Vvq0OKTr.js → cpu-DvUQxdKe.js} +1 -1
  126. package/static/app/assets/{download-SEcXzkx1.js → download-CFqYG90a.js} +1 -1
  127. package/static/app/assets/{folder-open-Dw1ff_Qa.js → folder-open-CVSnp-Dp.js} +1 -1
  128. package/static/app/assets/{hard-drive-B00fR7_r.js → hard-drive-Ckr4Vd6-.js} +1 -1
  129. package/static/app/assets/{index-Dm9VXl1o.js → index--0zKF5Ow.js} +3 -3
  130. package/static/app/assets/{input-DtdWYQEa.js → input-D-JKquXU.js} +1 -1
  131. package/static/app/assets/{network-CpjBu0kI.js → network-DB88FZ2T.js} +1 -1
  132. package/static/app/assets/{primitives-CwsudBwc.js → primitives-D8U9OaIo.js} +1 -1
  133. package/static/app/assets/search-B8jsVTw_.js +1 -0
  134. package/static/app/assets/{shield-alert-CpjBc-gQ.js → shield-alert-DnsvO79P.js} +1 -1
  135. package/static/app/assets/{textarea-DUPyjHQs.js → textarea-4RchwbTa.js} +1 -1
  136. package/static/app/assets/{useFocusTrap-BWcMtCWH.js → useFocusTrap-CFJjy7cS.js} +1 -1
  137. package/static/app/assets/{useQuery-Dg5fPqTS.js → useQuery-3Reu-A4a.js} +1 -1
  138. package/static/app/assets/{users-BgcdTQGM.js → users-BTxYksxa.js} +1 -1
  139. package/static/app/assets/{utils-a3YXFtEO.js → utils-D02Vf-fw.js} +1 -1
  140. package/static/app/assets/{workspace-BMUEiXUm.js → workspace-CSJNgX-C.js} +1 -1
  141. package/static/app/index.html +3 -3
  142. package/static/sw.js +1 -1
  143. package/static/app/assets/search-CwEJWBEA.js +0 -1
@@ -11,7 +11,7 @@ import threading
11
11
  import time
12
12
  import urllib.request
13
13
  from pathlib import Path
14
- from typing import Dict, Optional, Tuple
14
+ from typing import Any, Dict, Optional, Tuple
15
15
 
16
16
  from fastapi import APIRouter, HTTPException, Request
17
17
 
@@ -33,7 +33,7 @@ class PermissionGateway:
33
33
  self.local_approval_ttl_seconds = 5 * 60
34
34
  self.local_approval_lock = threading.Lock()
35
35
  self.perm_queue_lock = threading.Lock()
36
- self.local_approvals: Dict[str, Dict[str, object]] = {}
36
+ self.local_approvals: Dict[str, Dict[str, Any]] = {}
37
37
  self.discord_permission_webhook_url = config.discord_permission_webhook
38
38
  self.discord_bot_token = config.discord_bot_token
39
39
  self.discord_permission_channel = config.discord_permission_channel
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, Callable, Dict, Optional
5
+ from typing import Any, Callable, Dict, NoReturn, Optional
6
6
 
7
7
  from fastapi import APIRouter, HTTPException, Request
8
8
  from pydantic import BaseModel, Field
@@ -84,14 +84,16 @@ def create_search_router(
84
84
  ) -> APIRouter:
85
85
  router = APIRouter()
86
86
 
87
- def _guarded(request: Request) -> SearchService:
87
+ def _guarded(request: Request) -> "_ScopedSearchService":
88
88
  user = require_user(request)
89
89
  allowed = None
90
90
  if allowed_workspaces_for is not None and user:
91
91
  allowed = allowed_workspaces_for(user)
92
92
  return _ScopedSearchService(service, allowed)
93
93
 
94
- def _raise_graph_error(exc: Exception) -> None:
94
+ def _raise_graph_error(exc: Exception) -> NoReturn:
95
+ # NoReturn, not None: every handler ends with this in its except
96
+ # branch, and without it each one reads as a missing return.
95
97
  raise HTTPException(status_code=404, detail=str(exc)) from exc
96
98
 
97
99
  @router.post("/api/search/hybrid")
@@ -515,8 +515,6 @@ def create_security_router(
515
515
  else:
516
516
  raise HTTPException(status_code=400, detail="지원하지 않는 scope입니다.")
517
517
 
518
- if not isinstance(rows, list):
519
- rows = []
520
518
 
521
519
  _log_view(admin_email, "export", f"{scope}:{fmt}", reason="export")
522
520
 
@@ -530,7 +528,7 @@ def create_security_router(
530
528
  headers={"Content-Disposition": f"attachment; filename={filename}"},
531
529
  )
532
530
  if fmt == "csv":
533
- body = _csv_dump(rows)
531
+ body = _csv_dump(rows) # type: ignore[assignment]
534
532
  filename = f"security_{scope}.csv"
535
533
  return Response(
536
534
  content=body,
@@ -538,7 +536,7 @@ def create_security_router(
538
536
  headers={"Content-Disposition": f"attachment; filename={filename}"},
539
537
  )
540
538
  if fmt in {"excel", "xlsx"}:
541
- body = _excel_dump(rows)
539
+ body = _excel_dump(rows) # type: ignore[assignment]
542
540
  filename = f"security_{scope}.xlsx"
543
541
  return Response(
544
542
  content=body,
@@ -547,7 +545,7 @@ def create_security_router(
547
545
  )
548
546
  if fmt == "pdf":
549
547
  overview = build_sensitivity_report(get_history() or []).get("summary", {}) or {}
550
- body = _pdf_report("Lattice AI Security Report", rows, overview)
548
+ body = _pdf_report("Lattice AI Security Report", rows, overview) # type: ignore[assignment]
551
549
  filename = f"security_{scope}.pdf"
552
550
  return Response(
553
551
  content=body,
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Dict, List, Optional
5
+ from typing import Any, Dict, List, Optional
6
6
 
7
7
  from fastapi import APIRouter, HTTPException, Request
8
8
  from fastapi.responses import StreamingResponse
@@ -63,7 +63,7 @@ def create_setup_router(
63
63
 
64
64
  # ── Setup Wizard ─────────────────────────────────────────────────────────────
65
65
 
66
- def setup_auto_state() -> Dict[str, object]:
66
+ def setup_auto_state() -> Dict[str, Any]:
67
67
  """Return the PPT-aligned zero-config setup state used by setup UI/API."""
68
68
  profile = auto_setup_probe()
69
69
  recommendation = auto_setup_recommend(profile)
@@ -77,7 +77,7 @@ def create_setup_router(
77
77
  }
78
78
 
79
79
 
80
- def primary_setup_model(recs: Dict[str, object]) -> Optional[Dict[str, object]]:
80
+ def primary_setup_model(recs: Dict[str, Any]) -> Optional[Dict[str, Any]]:
81
81
  models = recs.get("models") if isinstance(recs, dict) else None
82
82
  if not isinstance(models, list):
83
83
  return None
@@ -9,7 +9,7 @@ import subprocess
9
9
  import time
10
10
  from dataclasses import dataclass
11
11
  from pathlib import Path
12
- from typing import Callable, Optional
12
+ from typing import Any, Callable, Dict, Optional
13
13
 
14
14
  from fastapi import APIRouter, Cookie, HTTPException, Request
15
15
  from fastapi.responses import FileResponse, HTMLResponse
@@ -253,7 +253,12 @@ def create_static_routes_router(
253
253
  """CPU / RAM / GPU(MLX) 사용량을 반환합니다."""
254
254
  require_user(request)
255
255
  import re as _re
256
- result = {"cpu_pct": 0.0, "ram_pct": 0.0, "gpu_mem_pct": 0.0, "gpu_mem_gb": 0.0}
256
+ result: Dict[str, Any] = {
257
+ "cpu_pct": 0.0,
258
+ "ram_pct": 0.0,
259
+ "gpu_mem_pct": 0.0,
260
+ "gpu_mem_gb": 0.0,
261
+ }
257
262
  try:
258
263
  # CPU
259
264
  top_out = subprocess.run(["top", "-l", "1", "-n", "0"], capture_output=True, text=True, timeout=4).stdout
@@ -243,10 +243,15 @@ def create_tools_router(
243
243
  api_router = APIRouter()
244
244
  HOOKS = hooks
245
245
  CONFIG = config
246
- DATA_DIR = data_dir
247
- STATIC_DIR = static_dir
246
+ # These three are Optional in the signature only because the legacy
247
+ # kwarg form predates AppContext; the router cannot be built without
248
+ # them, so resolve once here rather than at every use.
249
+ if data_dir is None or static_dir is None:
250
+ raise RuntimeError("create_tools_router requires data_dir and static_dir")
251
+ DATA_DIR: Path = data_dir
252
+ STATIC_DIR: Path = static_dir
248
253
  router = model_router
249
- ENABLE_GRAPH = enable_graph
254
+ ENABLE_GRAPH: bool = bool(enable_graph)
250
255
  KNOWLEDGE_GRAPH = knowledge_graph
251
256
  LOCAL_KG_WATCHER = local_kg_watcher
252
257
  _require_graph = require_graph
@@ -327,7 +332,7 @@ def create_tools_router(
327
332
  or None
328
333
  )
329
334
 
330
- def _knowledge_scope(request: Request, current_user: str, *, write: bool) -> Dict[str, str]:
335
+ def _knowledge_scope(request: Request, current_user: str, *, write: bool) -> Dict[str, Any]:
331
336
  # Preserve the historical shared vault only for explicit single-user,
332
337
  # no-auth local mode. Authenticated deployments always partition by
333
338
  # both authorized workspace and account.
@@ -165,35 +165,38 @@ def create_workspace_router(context: AppContext) -> APIRouter:
165
165
  router = APIRouter()
166
166
 
167
167
  # Bind injected deps to the names the moved handler bodies expect.
168
- service = context.workspace_service
169
- require_user = context.require_user
170
- require_admin = context.require_admin
171
- get_current_user = context.get_current_user
172
- append_audit_event = context.append_audit_event
173
- get_history = context.get_history
174
- get_audit_log = context.get_audit_log
175
- load_users = context.load_users
176
- scan_environment = context.scan_environment
177
- local_sysinfo = context.local_sysinfo
178
- get_recommendations = context.get_recommendations
179
- install_skill = context.install_skill
180
- remove_skill_directory = context.remove_skill_directory
181
- redact_secret_text = context.redact_secret_text
182
- capability_registry = context.capability_registry
168
+ # `context.require(...)` for anything this router calls: an absent
169
+ # dependency now names itself here instead of surfacing as
170
+ # "'NoneType' object is not callable" inside a request handler.
171
+ service = context.require("workspace_service")
172
+ require_user = context.require("require_user")
173
+ require_admin = context.require("require_admin")
174
+ get_current_user = context.require("get_current_user")
175
+ append_audit_event = context.require("append_audit_event")
176
+ get_history = context.require("get_history")
177
+ get_audit_log = context.require("get_audit_log")
178
+ load_users = context.require("load_users")
179
+ scan_environment = context.require("scan_environment")
180
+ local_sysinfo = context.require("local_sysinfo")
181
+ get_recommendations = context.require("get_recommendations")
182
+ install_skill = context.require("install_skill")
183
+ remove_skill_directory = context.require("remove_skill_directory")
184
+ redact_secret_text = context.require("redact_secret_text")
185
+ capability_registry = context.require("capability_registry")
183
186
 
184
187
  svc = service
185
188
  WORKSPACE_OS = service.store
186
- _workspace_graph = context.workspace_graph
187
- _graph_stats_safe = context.graph_stats
188
- _workspace_models_payload = context.workspace_models
189
- _workspace_settings_payload = context.workspace_settings
190
- _require_graph = context.require_graph
189
+ _workspace_graph = context.require("workspace_graph")
190
+ _graph_stats_safe = context.require("graph_stats")
191
+ _workspace_models_payload = context.require("workspace_models")
192
+ _workspace_settings_payload = context.require("workspace_settings")
193
+ _require_graph = context.require("require_graph")
191
194
  KNOWLEDGE_GRAPH = context.knowledge_graph
192
195
  LOCAL_KG_WATCHER = context.local_kg_watcher
193
196
  SKILLS_DIR = context.skills_dir
194
197
  LOCAL_MODEL = context.local_model
195
198
  PUBLIC_MODEL = context.public_model
196
- _fetch_skills_marketplace = context.fetch_skills_marketplace
199
+ _fetch_skills_marketplace = context.require("fetch_skills_marketplace")
197
200
  _workspace_scope = _workspace_scope_from_request
198
201
 
199
202
  def _gate_read(request: Request):
@@ -651,7 +654,7 @@ def create_workspace_router(context: AppContext) -> APIRouter:
651
654
  @router.get("/workspace/vscode/status")
652
655
  async def workspace_vscode_status(request: Request):
653
656
  require_user(request)
654
- last_seen = int(_VSCODE_STATUS.get("last_seen_ms") or 0)
657
+ last_seen = int(str(_VSCODE_STATUS.get("last_seen_ms") or 0))
655
658
  now_ms = int(datetime.utcnow().timestamp() * 1000)
656
659
  connected = last_seen > 0 and (now_ms - last_seen) < 60000
657
660
  return {