ltcai 11.5.1 → 11.5.2

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 (176) hide show
  1. package/README.md +85 -53
  2. package/docs/CHANGELOG.md +54 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/HYBRID_CLOUD_KG_STREAMING.md +8 -4
  6. package/docs/MULTI_AGENT_RUNTIME.md +1 -1
  7. package/docs/ONBOARDING.md +1 -1
  8. package/docs/OPERATIONS.md +1 -1
  9. package/docs/TRUST_MODEL.md +1 -1
  10. package/docs/WHY_LATTICE.md +1 -1
  11. package/docs/WORKFLOW_DESIGNER.md +1 -1
  12. package/docs/architecture.md +4 -4
  13. package/docs/kg-schema.md +1 -1
  14. package/docs/v11.5.2_TIGHT_SHIP_PLAN.md +144 -0
  15. package/lattice_brain/__init__.py +1 -1
  16. package/lattice_brain/graph/proactive.py +1 -1
  17. package/lattice_brain/graph/projection/v2_schema.py +0 -28
  18. package/lattice_brain/graph/vector_index/base.py +0 -3
  19. package/lattice_brain/graph/vector_index/brute_force.py +0 -4
  20. package/lattice_brain/graph/vector_index/hnsw.py +0 -3
  21. package/lattice_brain/graph/vector_index/quantized.py +0 -3
  22. package/lattice_brain/ingestion_jobs.py +0 -3
  23. package/lattice_brain/memory.py +0 -27
  24. package/lattice_brain/portability/sharing.py +0 -4
  25. package/lattice_brain/quality.py +0 -49
  26. package/lattice_brain/runtime/agent_runtime.py +1 -1
  27. package/lattice_brain/runtime/hooks.py +0 -13
  28. package/lattice_brain/runtime/multi_agent.py +1 -1
  29. package/lattice_brain/sealed_box.py +0 -4
  30. package/latticeai/__init__.py +1 -1
  31. package/latticeai/api/admin.py +12 -12
  32. package/latticeai/api/auth.py +25 -2
  33. package/latticeai/api/chat.py +3 -3
  34. package/latticeai/api/chat_agent_http.py +3 -12
  35. package/latticeai/api/chat_helpers.py +10 -13
  36. package/latticeai/api/computer_use.py +31 -42
  37. package/latticeai/api/health.py +21 -1
  38. package/latticeai/api/local_files.py +14 -0
  39. package/latticeai/api/permissions.py +38 -12
  40. package/latticeai/api/search.py +24 -0
  41. package/latticeai/api/static_routes.py +4 -1
  42. package/latticeai/cli/runtime.py +7 -3
  43. package/latticeai/core/config.py +47 -3
  44. package/latticeai/core/csrf.py +28 -2
  45. package/latticeai/core/embedding_providers/__init__.py +3 -5
  46. package/latticeai/core/embedding_providers/base.py +1 -1
  47. package/latticeai/core/embedding_providers/text.py +1 -1
  48. package/latticeai/core/enterprise.py +1 -4
  49. package/latticeai/core/http_origin.py +146 -0
  50. package/latticeai/core/invitations.py +3 -2
  51. package/latticeai/core/io_utils.py +2 -11
  52. package/latticeai/core/legacy_compatibility.py +1 -1
  53. package/latticeai/core/marketplace.py +1 -1
  54. package/latticeai/core/messages.py +5 -0
  55. package/latticeai/core/model_compat.py +2 -8
  56. package/latticeai/core/module_probe.py +37 -0
  57. package/latticeai/core/run_explain.py +5 -2
  58. package/latticeai/core/run_store.py +2 -2
  59. package/latticeai/core/security.py +13 -0
  60. package/latticeai/core/sessions.py +3 -2
  61. package/latticeai/core/sse.py +25 -0
  62. package/latticeai/core/users.py +0 -9
  63. package/latticeai/core/workspace_graph_trace.py +2 -8
  64. package/latticeai/core/workspace_os.py +0 -10
  65. package/latticeai/core/workspace_os_constants.py +1 -1
  66. package/latticeai/core/workspace_os_utils.py +34 -0
  67. package/latticeai/core/workspace_review_items.py +2 -8
  68. package/latticeai/core/workspace_runs.py +2 -8
  69. package/latticeai/core/workspace_skills.py +2 -7
  70. package/latticeai/models/router/documents.py +0 -35
  71. package/latticeai/models/router/registry.py +0 -4
  72. package/latticeai/runtime/access_runtime.py +16 -4
  73. package/latticeai/runtime/build_phases/features.py +2 -0
  74. package/latticeai/runtime/build_phases/web.py +2 -0
  75. package/latticeai/runtime/feature_toggle_wiring.py +12 -18
  76. package/latticeai/runtime/network_boundary_wiring.py +8 -19
  77. package/latticeai/runtime/permission_mode_wiring.py +6 -13
  78. package/latticeai/runtime/router_registration.py +4 -0
  79. package/latticeai/runtime/runtime_context.py +1 -13
  80. package/latticeai/runtime/service_singletons.py +55 -0
  81. package/latticeai/services/architecture_readiness.py +1 -1
  82. package/latticeai/services/brain_intelligence/proposals.py +0 -5
  83. package/latticeai/services/change_proposals.py +2 -36
  84. package/latticeai/services/chronicle.py +4 -6
  85. package/latticeai/services/command_center.py +4 -4
  86. package/latticeai/services/evidence_actions.py +5 -2
  87. package/latticeai/services/hybrid_chat.py +2 -2
  88. package/latticeai/services/hybrid_policy.py +8 -57
  89. package/latticeai/services/mode_store.py +132 -0
  90. package/latticeai/services/model_capability_registry.py +0 -15
  91. package/latticeai/services/model_catalog.py +0 -7
  92. package/latticeai/services/model_loading.py +3 -2
  93. package/latticeai/services/model_runtime/__init__.py +0 -57
  94. package/latticeai/services/model_runtime/engines.py +27 -128
  95. package/latticeai/services/model_runtime/loading.py +2 -2
  96. package/latticeai/services/network_boundary_service.py +7 -44
  97. package/latticeai/services/permission_mode_service.py +8 -56
  98. package/latticeai/services/product_readiness.py +1 -1
  99. package/latticeai/services/setup_detection.py +67 -1
  100. package/latticeai/services/tool_dispatch.py +0 -4
  101. package/latticeai/services/upload_service.py +7 -20
  102. package/latticeai/services/workspace_service.py +0 -6
  103. package/latticeai/setup/auto_setup.py +15 -26
  104. package/latticeai/setup/wizard/catalog.py +5 -2
  105. package/latticeai/setup/wizard/detect.py +6 -27
  106. package/latticeai/setup/wizard/paths.py +2 -5
  107. package/latticeai/setup/wizard/plans.py +2 -2
  108. package/package.json +2 -4
  109. package/scripts/bump_version.py +14 -1
  110. package/scripts/check_current_release_docs.mjs +1 -1
  111. package/scripts/check_legacy_debt.mjs +1 -1
  112. package/scripts/generate_agent_loop_fixtures.py +87 -0
  113. package/scripts/generate_rust_parity_fixtures.py +3 -95
  114. package/scripts/parity_fixture_corpus_context.py +162 -0
  115. package/scripts/release_screen_claims.json +10 -0
  116. package/src-tauri/Cargo.lock +7 -7
  117. package/src-tauri/Cargo.toml +1 -1
  118. package/src-tauri/src/backend.rs +76 -2
  119. package/src-tauri/src/main.rs +13 -3
  120. package/src-tauri/tauri.conf.json +2 -2
  121. package/static/app/asset-manifest.json +40 -40
  122. package/static/app/assets/{Act-Drs_jd-O.js → Act-DcQizkl1.js} +1 -1
  123. package/static/app/assets/{AdminConsole-BqNx5oF6.js → AdminConsole-cf4npybT.js} +1 -1
  124. package/static/app/assets/{Brain-C7bdNmz-.js → Brain-3VCSHFcn.js} +1 -1
  125. package/static/app/assets/{BrainHome-DcTnINcI.js → BrainHome-Qm8eaztx.js} +1 -1
  126. package/static/app/assets/{BrainSignals-Df1MRIPD.js → BrainSignals-DS9BtKOW.js} +1 -1
  127. package/static/app/assets/{Capture-Dgsxvnbx.js → Capture-DiQ219jW.js} +1 -1
  128. package/static/app/assets/{Chronicle-BrkTxJK1.js → Chronicle-BGvuAchH.js} +1 -1
  129. package/static/app/assets/{CommandPalette-DDziw7Oj.js → CommandPalette-Bqhm0Urn.js} +1 -1
  130. package/static/app/assets/{Library-CYxeuraA.js → Library-BV6NnF0a.js} +1 -1
  131. package/static/app/assets/{LivingBrain-CNp6mvCm.js → LivingBrain-GzenJchP.js} +1 -1
  132. package/static/app/assets/{ProductFlow-BVsowu3Z.js → ProductFlow-DEP6-vML.js} +1 -1
  133. package/static/app/assets/{ReviewCard-D1LbOfJS.js → ReviewCard-CNZ7XjWG.js} +1 -1
  134. package/static/app/assets/{System-DcRAq7wj.js → System-CieofHQa.js} +1 -1
  135. package/static/app/assets/arrow-left-kfsrk0mv.js +1 -0
  136. package/static/app/assets/{bot-BK2xQ9mN.js → bot-B_K1Tdmw.js} +1 -1
  137. package/static/app/assets/{brain-BZNztFBb.js → brain-DWyaV1L1.js} +1 -1
  138. package/static/app/assets/{button-CjueubVZ.js → button-aTn4s84A.js} +1 -1
  139. package/static/app/assets/circle-check-qqLug9nU.js +1 -0
  140. package/static/app/assets/{circle-pause-HRjSmpyY.js → circle-pause-xKgeGXkT.js} +1 -1
  141. package/static/app/assets/{circle-play-CMcKDVwu.js → circle-play-DkT6tYPX.js} +1 -1
  142. package/static/app/assets/{cpu-B_MyTfYr.js → cpu-85xYObUC.js} +1 -1
  143. package/static/app/assets/{download-CTy0EByV.js → download-B5Fm7YXo.js} +1 -1
  144. package/static/app/assets/{folder-open-DF6bLhTg.js → folder-open-kk2Xa52u.js} +1 -1
  145. package/static/app/assets/{hard-drive-CpWHG77C.js → hard-drive-DkA3zBW_.js} +1 -1
  146. package/static/app/assets/{index-Dd6abJHX.js → index-BMPdTmlY.js} +3 -3
  147. package/static/app/assets/{input-bVgIRN3s.js → input-B0nRf2jO.js} +1 -1
  148. package/static/app/assets/{link-2-bg6CG5Vk.js → link-2-Dwb4gnTc.js} +1 -1
  149. package/static/app/assets/{permissionCopy-DFTf1HDZ.js → permissionCopy-CQDUBrOZ.js} +1 -1
  150. package/static/app/assets/{primitives-D7D-sQ3T.js → primitives-SNp0LRJz.js} +1 -1
  151. package/static/app/assets/search-BcHqkjoy.js +1 -0
  152. package/static/app/assets/{share-2-BLzo7U4L.js → share-2-BsrxFglO.js} +1 -1
  153. package/static/app/assets/{shield-alert-BmngqnsJ.js → shield-alert-5BStfp2_.js} +1 -1
  154. package/static/app/assets/{textarea-DOSlAQQ3.js → textarea-Cg8IUA-k.js} +1 -1
  155. package/static/app/assets/{useFocusTrap-pZhgeee4.js → useFocusTrap-CYKvE46M.js} +1 -1
  156. package/static/app/assets/{useMutation-BMDwNk4I.js → useMutation-CSn9t1op.js} +1 -1
  157. package/static/app/assets/{useQuery-CSjKttRo.js → useQuery-CY2OI2uy.js} +1 -1
  158. package/static/app/assets/{utils-D3u_yv7B.js → utils-Ddol2RWD.js} +1 -1
  159. package/static/app/assets/{workspace-H3bMjYXC.js → workspace-BqDwOz_p.js} +1 -1
  160. package/static/app/index.html +3 -3
  161. package/static/sw.js +1 -1
  162. package/desktop/electron/README.md +0 -9
  163. package/desktop/electron/main.cjs +0 -58
  164. package/desktop/electron/preload.cjs +0 -5
  165. package/latticeai/core/graph_curator.py +0 -11
  166. package/latticeai/core/hooks.py +0 -11
  167. package/latticeai/core/local_embeddings.py +0 -104
  168. package/latticeai/core/multi_agent.py +0 -11
  169. package/latticeai/core/workflow_engine.py +0 -11
  170. package/latticeai/services/ingestion.py +0 -11
  171. package/latticeai/services/kg_portability.py +0 -11
  172. package/latticeai/services/multimodal_streaming.py +0 -129
  173. package/scripts/measure_brain_home_fill.mjs +0 -141
  174. package/static/app/assets/arrow-left-BHYmOTWU.js +0 -1
  175. package/static/app/assets/circle-check-CLeWJr67.js +0 -1
  176. package/static/app/assets/search-BgSVX6NG.js +0 -1
@@ -104,81 +104,24 @@ from latticeai.services.model_runtime.engines import (
104
104
  from latticeai.services.model_runtime.engines import (
105
105
  _LOCAL_SERVER_PROCESSES as _LOCAL_SERVER_PROCESSES,
106
106
  )
107
- from latticeai.services.model_runtime.engines import (
108
- LMSTUDIO_BUNDLED_CLI as LMSTUDIO_BUNDLED_CLI,
109
- )
110
107
  from latticeai.services.model_runtime.engines import (
111
108
  LOCAL_SERVER_PROCESSES as LOCAL_SERVER_PROCESSES,
112
109
  )
113
- from latticeai.services.model_runtime.engines import (
114
- VLLM_METAL_BIN as VLLM_METAL_BIN,
115
- )
116
- from latticeai.services.model_runtime.engines import (
117
- VLLM_METAL_ENV as VLLM_METAL_ENV,
118
- )
119
- from latticeai.services.model_runtime.engines import (
120
- VLLM_METAL_PYTHON as VLLM_METAL_PYTHON,
121
- )
122
110
  from latticeai.services.model_runtime.engines import (
123
111
  _engine_install_plan as _engine_install_plan,
124
112
  )
125
- from latticeai.services.model_runtime.engines import (
126
- _engine_support_status as _engine_support_status,
127
- )
128
- from latticeai.services.model_runtime.engines import (
129
- _ensure_llamacpp_server as _ensure_llamacpp_server,
130
- )
131
- from latticeai.services.model_runtime.engines import (
132
- _ensure_lmstudio_server as _ensure_lmstudio_server,
133
- )
134
- from latticeai.services.model_runtime.engines import (
135
- _ensure_ollama_server as _ensure_ollama_server,
136
- )
137
- from latticeai.services.model_runtime.engines import (
138
- _ensure_vllm_server as _ensure_vllm_server,
139
- )
140
- from latticeai.services.model_runtime.engines import (
141
- _find_lmstudio_cli as _find_lmstudio_cli,
142
- )
143
113
  from latticeai.services.model_runtime.engines import (
144
114
  _find_lmstudio_model_key as _find_lmstudio_model_key,
145
115
  )
146
- from latticeai.services.model_runtime.engines import (
147
- _get_ollama_pulled_models as _get_ollama_pulled_models,
148
- )
149
- from latticeai.services.model_runtime.engines import (
150
- _get_openai_compatible_server_models as _get_openai_compatible_server_models,
151
- )
152
116
  from latticeai.services.model_runtime.engines import (
153
117
  _json_request as _json_request,
154
118
  )
155
119
  from latticeai.services.model_runtime.engines import (
156
120
  _lmstudio_candidate_keys as _lmstudio_candidate_keys,
157
121
  )
158
- from latticeai.services.model_runtime.engines import (
159
- _local_binary as _local_binary,
160
- )
161
- from latticeai.services.model_runtime.engines import (
162
- _pull_ollama_model_with_progress as _pull_ollama_model_with_progress,
163
- )
164
122
  from latticeai.services.model_runtime.engines import (
165
123
  _safe_engine_install_plan as _safe_engine_install_plan,
166
124
  )
167
- from latticeai.services.model_runtime.engines import (
168
- _update_env_file as _update_env_file,
169
- )
170
- from latticeai.services.model_runtime.engines import (
171
- _vllm_executable as _vllm_executable,
172
- )
173
- from latticeai.services.model_runtime.engines import (
174
- _vllm_metal_python as _vllm_metal_python,
175
- )
176
- from latticeai.services.model_runtime.engines import (
177
- _wait_for_openai_compatible_server as _wait_for_openai_compatible_server,
178
- )
179
- from latticeai.services.model_runtime.engines import (
180
- _windows_binary_candidates as _windows_binary_candidates,
181
- )
182
125
  from latticeai.services.model_runtime.engines import (
183
126
  engine_installed as engine_installed,
184
127
  )
@@ -1,10 +1,17 @@
1
1
  """Local engine discovery, server hand-off, and the LM Studio HTTP client.
2
2
 
3
- Thin wrappers over ``latticeai.services.model_engines`` that keep the historical
3
+ Re-exports from ``latticeai.services.model_engines`` that keep the historical
4
4
  ``model_runtime`` import path alive, plus the one piece of real logic that never
5
5
  belonged in the engine layer: the LM Studio native API (list / download / load)
6
6
  and its short-lived model cache.
7
7
 
8
+ Until 11.5.2 the re-exports were fifteen hand-written one-line functions that
9
+ forwarded their arguments, next to second copies of ``_json_request`` and the
10
+ LM Studio base-URL pair. A forwarding wrapper is a place two implementations
11
+ can drift — one of the copies had already grown a different fallback — so the
12
+ names are now bound directly to the engine layer's and there is nothing left to
13
+ disagree with.
14
+
8
15
  ``_LMSTUDIO_MODELS_CACHE`` and ``_LMSTUDIO_MODELS_CACHE_TS`` are rebindable
9
16
  module state and therefore live here and nowhere else — the package
10
17
  ``__init__`` deliberately does not re-export them, because a
@@ -15,7 +22,6 @@ the live value.
15
22
  from __future__ import annotations
16
23
 
17
24
  import importlib.util
18
- import json
19
25
  import os
20
26
  import shutil
21
27
  import time
@@ -24,131 +30,55 @@ import urllib.request
24
30
  from pathlib import Path
25
31
  from typing import Any, Dict, List, Optional
26
32
 
27
- from latticeai.models.router import OPENAI_COMPATIBLE_PROVIDERS, AsyncOpenAI
33
+ from latticeai.models.router import AsyncOpenAI
28
34
  from latticeai.services.model_engines import (
29
35
  LOCAL_SERVER_PROCESSES as _LOCAL_SERVER_PROCESSES,
30
36
  )
31
37
  from latticeai.services.model_engines import (
32
- engine_install_plan as _engine_install_plan,
33
- )
34
- from latticeai.services.model_engines import (
35
- engine_support_status as _engine_support_status,
36
- )
37
- from latticeai.services.model_engines import (
38
- ensure_llamacpp_server as _ensure_llamacpp_server,
38
+ _json_request as _json_request,
39
39
  )
40
40
  from latticeai.services.model_engines import (
41
- ensure_lmstudio_server as _ensure_lmstudio_server,
42
- )
43
- from latticeai.services.model_engines import (
44
- ensure_ollama_server as _ensure_ollama_server,
41
+ engine_install_plan as _engine_install_plan,
45
42
  )
46
43
  from latticeai.services.model_engines import (
47
- ensure_vllm_server as _ensure_vllm_server,
44
+ engine_support_status as engine_support_status,
48
45
  )
49
46
  from latticeai.services.model_engines import (
50
- find_lmstudio_cli as _find_lmstudio_cli,
47
+ ensure_llamacpp_server as ensure_llamacpp_server,
51
48
  )
52
49
  from latticeai.services.model_engines import (
53
- get_ollama_pulled_models as _get_ollama_pulled_models,
50
+ ensure_lmstudio_server as ensure_lmstudio_server,
54
51
  )
55
52
  from latticeai.services.model_engines import (
56
- get_openai_compatible_server_models as _get_openai_compatible_server_models,
53
+ ensure_ollama_server as ensure_ollama_server,
57
54
  )
55
+ from latticeai.services.model_engines import ensure_vllm_server as ensure_vllm_server
56
+ from latticeai.services.model_engines import find_lmstudio_cli as find_lmstudio_cli
58
57
  from latticeai.services.model_engines import (
59
- local_binary as _local_binary,
58
+ get_ollama_pulled_models as get_ollama_pulled_models,
60
59
  )
61
60
  from latticeai.services.model_engines import (
62
- pull_ollama_model_with_progress as _pull_ollama_model_with_progress,
61
+ get_openai_compatible_server_models as get_openai_compatible_server_models,
63
62
  )
63
+ from latticeai.services.model_engines import lmstudio_api_base as lmstudio_api_base
64
64
  from latticeai.services.model_engines import (
65
- vllm_executable as _vllm_executable,
65
+ lmstudio_native_api_base as lmstudio_native_api_base,
66
66
  )
67
+ from latticeai.services.model_engines import local_binary as local_binary
67
68
  from latticeai.services.model_engines import (
68
- vllm_metal_python as _vllm_metal_python,
69
+ pull_ollama_model_with_progress as pull_ollama_model_with_progress,
69
70
  )
71
+ from latticeai.services.model_engines import vllm_executable as vllm_executable
72
+ from latticeai.services.model_engines import vllm_metal_python as vllm_metal_python
70
73
  from latticeai.services.model_engines import (
71
- wait_for_openai_compatible_server as _wait_for_openai_compatible_server,
74
+ wait_for_openai_compatible_server as wait_for_openai_compatible_server,
72
75
  )
73
76
  from latticeai.services.model_engines import (
74
- windows_binary_candidates as _windows_binary_candidates,
77
+ windows_binary_candidates as windows_binary_candidates,
75
78
  )
76
79
  from latticeai.services.model_errors import ModelRuntimeError
77
80
 
78
-
79
- def _update_env_file(env_file: Path, key: str, value: str) -> None:
80
- lines = []
81
- found = False
82
- if env_file.exists():
83
- for line in env_file.read_text(encoding="utf-8").splitlines():
84
- if line.startswith(f"{key}="):
85
- lines.append(f"{key}={value}")
86
- found = True
87
- else:
88
- lines.append(line)
89
- if not found:
90
- lines.append(f"{key}={value}")
91
- env_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
92
-
93
-
94
81
  LOCAL_SERVER_PROCESSES = _LOCAL_SERVER_PROCESSES
95
- VLLM_METAL_ENV = Path.home() / ".venv-vllm-metal"
96
- VLLM_METAL_BIN = VLLM_METAL_ENV / "bin" / "vllm"
97
- VLLM_METAL_PYTHON = VLLM_METAL_ENV / "bin" / "python"
98
- LMSTUDIO_BUNDLED_CLI = Path("/Applications/LM Studio.app/Contents/Resources/app/.webpack/lms")
99
-
100
- def windows_binary_candidates(binary: str) -> List[Path]:
101
- return _windows_binary_candidates(binary)
102
-
103
-
104
- def local_binary(binary: str) -> Optional[str]:
105
- return _local_binary(binary)
106
-
107
-
108
- def find_lmstudio_cli() -> Optional[str]:
109
- return _find_lmstudio_cli()
110
-
111
-
112
- def vllm_executable() -> Optional[str]:
113
- return _vllm_executable()
114
-
115
-
116
- def vllm_metal_python() -> Optional[str]:
117
- return _vllm_metal_python()
118
-
119
-
120
- def _json_request(
121
- url: str,
122
- *,
123
- method: str = "GET",
124
- payload: Optional[Dict[str, Any]] = None,
125
- headers: Optional[Dict[str, str]] = None,
126
- timeout: float = 10.0,
127
- ) -> Dict[str, Any]:
128
- data = None
129
- req_headers = dict(headers or {})
130
- if payload is not None:
131
- data = json.dumps(payload).encode("utf-8")
132
- req_headers.setdefault("Content-Type", "application/json")
133
- req = urllib.request.Request(url, data=data, headers=req_headers, method=method)
134
- with urllib.request.urlopen(req, timeout=timeout) as res:
135
- raw = res.read().decode("utf-8", errors="replace")
136
- if not raw.strip():
137
- return {}
138
- return json.loads(raw)
139
-
140
-
141
- def lmstudio_api_base() -> str:
142
- return (os.getenv("LMSTUDIO_BASE_URL") or OPENAI_COMPATIBLE_PROVIDERS["lmstudio"]["base_url"]).rstrip("/")
143
-
144
-
145
- def lmstudio_native_api_base() -> str:
146
- base = lmstudio_api_base()
147
- return base[:-3] if base.endswith("/v1") else base
148
-
149
-
150
- def ensure_lmstudio_server() -> None:
151
- return _ensure_lmstudio_server()
152
82
 
153
83
 
154
84
  _LMSTUDIO_MODELS_CACHE: List[Dict[str, Any]] = []
@@ -280,37 +210,6 @@ def ensure_lmstudio_model(model_name: str) -> Dict[str, Any]:
280
210
  "cached": False,
281
211
  }
282
212
 
283
- def engine_support_status(engine: str) -> Dict[str, Any]:
284
- return _engine_support_status(engine)
285
-
286
- def pull_ollama_model_with_progress(model_name: str, progress_emit=None) -> Dict[str, Any]:
287
- return _pull_ollama_model_with_progress(model_name, progress_emit)
288
-
289
-
290
- def get_ollama_pulled_models() -> set:
291
- return _get_ollama_pulled_models()
292
-
293
-
294
- def get_openai_compatible_server_models(provider: str) -> List[str]:
295
- return _get_openai_compatible_server_models(provider)
296
-
297
-
298
- def ensure_ollama_server() -> None:
299
- return _ensure_ollama_server()
300
-
301
-
302
- def wait_for_openai_compatible_server(provider: str, model_name: Optional[str] = None, timeout: int = 45) -> bool:
303
- return _wait_for_openai_compatible_server(provider, model_name=model_name, timeout=timeout)
304
-
305
-
306
- def ensure_vllm_server(model_name: str) -> None:
307
- return _ensure_vllm_server(model_name)
308
-
309
-
310
- def ensure_llamacpp_server(model_name: str) -> None:
311
- return _ensure_llamacpp_server(model_name)
312
-
313
-
314
213
  def _safe_engine_install_plan(
315
214
  engine: str,
316
215
  *,
@@ -9,10 +9,10 @@ load itself (blocking and streaming forms) and to
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import json
13
12
  from typing import Any, AsyncIterator, Dict, Optional
14
13
 
15
14
  from latticeai.core.model_resolution import ModelResolution as _ModelResolution
15
+ from latticeai.core.sse import sse_frame
16
16
  from latticeai.models.router import OPENAI_COMPATIBLE_PROVIDERS, ensure_mlx_runtime
17
17
  from latticeai.services.model_catalog import ENGINE_INSTALLERS, MODEL_ENGINE_ALIASES
18
18
  from latticeai.services.model_errors import ModelRuntimeError
@@ -151,7 +151,7 @@ async def prepare_and_load_model(
151
151
 
152
152
 
153
153
  def sse_event(event: str, data: Dict[str, Any]) -> str:
154
- return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
154
+ return sse_frame(event, data)
155
155
 
156
156
 
157
157
  async def prepare_and_load_model_stream(
@@ -12,12 +12,9 @@ ack is audited. Hard node filters remain in ``latticeai.core.network_boundary``.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- import json
16
- import threading
17
15
  from pathlib import Path
18
16
  from typing import Any, Callable, Dict, Optional
19
17
 
20
- from latticeai.core.io_utils import atomic_write_json
21
18
  from latticeai.core.network_boundary import (
22
19
  DEFAULT_NETWORK_MODE,
23
20
  NetworkBoundaryMode,
@@ -25,11 +22,14 @@ from latticeai.core.network_boundary import (
25
22
  network_mode_contract,
26
23
  normalize_network_mode,
27
24
  )
25
+ from latticeai.services.mode_store import JsonBackedModeService
28
26
 
29
27
 
30
- class NetworkBoundaryService:
28
+ class NetworkBoundaryService(JsonBackedModeService[NetworkBoundaryMode]):
31
29
  """Load / save the network boundary dial."""
32
30
 
31
+ FILENAME = "network_boundary.json"
32
+
33
33
  def __init__(
34
34
  self,
35
35
  *,
@@ -37,36 +37,11 @@ class NetworkBoundaryService:
37
37
  default_mode: NetworkBoundaryMode | str = DEFAULT_NETWORK_MODE,
38
38
  audit: Optional[Callable[..., None]] = None,
39
39
  ) -> None:
40
- self._path = Path(data_dir) / "network_boundary.json"
40
+ super().__init__(data_dir=data_dir, audit=audit)
41
41
  self._default = normalize_network_mode(default_mode)
42
- self._audit = audit or (lambda *a, **kw: None)
43
- self._lock = threading.Lock()
44
-
45
- def rebind_data_dir(self, data_dir: Path) -> None:
46
- with self._lock:
47
- self._path = Path(data_dir) / "network_boundary.json"
48
42
 
49
- def rebind_audit(self, audit: Callable[..., None]) -> None:
50
- with self._lock:
51
- self._audit = audit
52
-
53
- def _read(self) -> Dict[str, Any]:
54
- if not self._path.exists():
55
- return {"default": self._default.value, "users": {}, "workspaces": {}}
56
- try:
57
- data = json.loads(self._path.read_text(encoding="utf-8"))
58
- except Exception:
59
- return {"default": self._default.value, "users": {}, "workspaces": {}}
60
- if not isinstance(data, dict):
61
- return {"default": self._default.value, "users": {}, "workspaces": {}}
62
- data.setdefault("default", self._default.value)
63
- data.setdefault("users", {})
64
- data.setdefault("workspaces", {})
65
- return data
66
-
67
- def _write(self, data: Dict[str, Any]) -> None:
68
- self._path.parent.mkdir(parents=True, exist_ok=True)
69
- atomic_write_json(self._path, data)
43
+ def _default_entry(self) -> Any:
44
+ return self._default.value
70
45
 
71
46
  def _resolve_from(
72
47
  self,
@@ -85,18 +60,6 @@ class NetworkBoundaryService:
85
60
  return normalize_network_mode(user)
86
61
  return normalize_network_mode(data.get("default") or self._default)
87
62
 
88
- def resolve(
89
- self,
90
- *,
91
- user_email: Optional[str] = None,
92
- workspace_id: Optional[str] = None,
93
- ) -> NetworkBoundaryMode:
94
- with self._lock:
95
- data = self._read()
96
- return self._resolve_from(
97
- data, user_email=user_email, workspace_id=workspace_id,
98
- )
99
-
100
63
  def get(
101
64
  self,
102
65
  *,
@@ -14,12 +14,9 @@ not by this store.
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- import json
18
- import threading
19
17
  from pathlib import Path
20
18
  from typing import Any, Callable, Dict, Optional
21
19
 
22
- from latticeai.core.io_utils import atomic_write_json
23
20
  from latticeai.core.permission_mode import (
24
21
  DEFAULT_MODE,
25
22
  PermissionMode,
@@ -27,11 +24,14 @@ from latticeai.core.permission_mode import (
27
24
  mode_contract,
28
25
  normalize_mode,
29
26
  )
27
+ from latticeai.services.mode_store import JsonBackedModeService
30
28
 
31
29
 
32
- class PermissionModeService:
30
+ class PermissionModeService(JsonBackedModeService[PermissionMode]):
33
31
  """Load / save the autonomy dial."""
34
32
 
33
+ FILENAME = "permission_mode.json"
34
+
35
35
  def __init__(
36
36
  self,
37
37
  *,
@@ -39,43 +39,11 @@ class PermissionModeService:
39
39
  default_mode: PermissionMode | str = DEFAULT_MODE,
40
40
  audit: Optional[Callable[..., None]] = None,
41
41
  ) -> None:
42
- self._path = Path(data_dir) / "permission_mode.json"
42
+ super().__init__(data_dir=data_dir, audit=audit)
43
43
  self._default = normalize_mode(default_mode)
44
- self._audit = audit or (lambda *a, **kw: None)
45
- self._lock = threading.Lock()
46
-
47
- def rebind_data_dir(self, data_dir: Path) -> None:
48
- """Point the store at the app's real data dir.
49
-
50
- The wiring may instantiate this service lazily before routers know the
51
- configured data dir; rebinding keeps one file of record instead of
52
- stranding writes under the fallback path.
53
- """
54
- with self._lock:
55
- self._path = Path(data_dir) / "permission_mode.json"
56
44
 
57
- def rebind_audit(self, audit: Callable[..., None]) -> None:
58
- """Attach the real audit sink once app wiring provides one."""
59
- with self._lock:
60
- self._audit = audit
61
-
62
- def _read(self) -> Dict[str, Any]:
63
- if not self._path.exists():
64
- return {"default": self._default.value, "users": {}, "workspaces": {}}
65
- try:
66
- data = json.loads(self._path.read_text(encoding="utf-8"))
67
- except Exception:
68
- return {"default": self._default.value, "users": {}, "workspaces": {}}
69
- if not isinstance(data, dict):
70
- return {"default": self._default.value, "users": {}, "workspaces": {}}
71
- data.setdefault("default", self._default.value)
72
- data.setdefault("users", {})
73
- data.setdefault("workspaces", {})
74
- return data
75
-
76
- def _write(self, data: Dict[str, Any]) -> None:
77
- self._path.parent.mkdir(parents=True, exist_ok=True)
78
- atomic_write_json(self._path, data)
45
+ def _default_entry(self) -> Any:
46
+ return self._default.value
79
47
 
80
48
  def _resolve_from(
81
49
  self,
@@ -84,11 +52,7 @@ class PermissionModeService:
84
52
  user_email: Optional[str],
85
53
  workspace_id: Optional[str],
86
54
  ) -> PermissionMode:
87
- """Scope precedence: workspace → user → process default.
88
-
89
- Pure over ``data`` and lock-free, so holders of ``_lock`` can reuse it
90
- without re-entering a non-reentrant lock.
91
- """
55
+ """Scope precedence: workspace → user → process default."""
92
56
  if workspace_id:
93
57
  ws = (data.get("workspaces") or {}).get(str(workspace_id))
94
58
  if ws:
@@ -99,18 +63,6 @@ class PermissionModeService:
99
63
  return normalize_mode(user)
100
64
  return normalize_mode(data.get("default") or self._default)
101
65
 
102
- def resolve(
103
- self,
104
- *,
105
- user_email: Optional[str] = None,
106
- workspace_id: Optional[str] = None,
107
- ) -> PermissionMode:
108
- with self._lock:
109
- data = self._read()
110
- return self._resolve_from(
111
- data, user_email=user_email, workspace_id=workspace_id,
112
- )
113
-
114
66
  def get(
115
67
  self,
116
68
  *,
@@ -18,7 +18,7 @@ from typing import Any, Dict, List
18
18
 
19
19
  from latticeai.services.architecture_readiness import architecture_readiness
20
20
 
21
- PRODUCT_VERSION_TARGET = "11.5.1"
21
+ PRODUCT_VERSION_TARGET = "11.5.2"
22
22
 
23
23
 
24
24
  @dataclass(frozen=True)
@@ -50,6 +50,64 @@ def parse_windows_video_controllers(raw: str) -> List[Dict[str, Any]]:
50
50
  return controllers
51
51
 
52
52
 
53
+
54
+ def parse_windows_cpu_info(
55
+ raw: str,
56
+ *,
57
+ model: str,
58
+ physical_cores: int,
59
+ logical_cores: int,
60
+ ) -> Tuple[str, int, int]:
61
+ """Read ``wmic cpu get Name,NumberOfCores,NumberOfLogicalProcessors``.
62
+
63
+ Whatever the output does not say keeps the value the caller already had —
64
+ ``wmic`` is absent on modern Windows images and prints nothing there, and
65
+ "0 cores" would be a worse answer than ``os.cpu_count()``'s guess.
66
+ """
67
+ for line in raw.splitlines():
68
+ key, _, value = line.partition("=")
69
+ if key == "Name" and value.strip():
70
+ model = value.strip()
71
+ elif key == "NumberOfCores" and value.strip():
72
+ try:
73
+ physical_cores = int(value.strip())
74
+ except ValueError:
75
+ quiet()
76
+ elif key == "NumberOfLogicalProcessors" and value.strip():
77
+ try:
78
+ logical_cores = int(value.strip())
79
+ except ValueError:
80
+ quiet()
81
+ return model, physical_cores, logical_cores
82
+
83
+
84
+ #: ``IsProcessorFeaturePresent`` codes worth reporting, by instruction name.
85
+ WINDOWS_PROCESSOR_FEATURES: Dict[int, str] = {
86
+ 6: "sse", 10: "sse2", 13: "sse3", 19: "neon", 28: "rdrand",
87
+ }
88
+
89
+
90
+ def windows_processor_features() -> List[str]:
91
+ """Instruction-set flags from the Win32 API, or none anywhere else.
92
+
93
+ ``ctypes.windll`` exists only on Windows, so on every other platform this
94
+ raises and answers "nothing known" — which is the honest reading, since the
95
+ flags are unobtainable rather than absent.
96
+ """
97
+ try:
98
+ import ctypes
99
+
100
+ kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] # Windows-only
101
+ return [
102
+ name
103
+ for code, name in WINDOWS_PROCESSOR_FEATURES.items()
104
+ if kernel32.IsProcessorFeaturePresent(code)
105
+ ]
106
+ except Exception:
107
+ quiet()
108
+ return []
109
+
110
+
53
111
  def detect_cuda(which: WhichFn, run: RunFn) -> Tuple[bool, str, Optional[str], Optional[str]]:
54
112
  nvidia_smi = which("nvidia-smi")
55
113
  nvcc = which("nvcc")
@@ -78,4 +136,12 @@ def detect_tools(which: WhichFn, binaries: Iterable[str]) -> Dict[str, Optional[
78
136
  return {binary: which(binary) for binary in binaries}
79
137
 
80
138
 
81
- __all__ = ["detect_cuda", "detect_tools", "detect_wsl_from_text", "parse_windows_video_controllers"]
139
+ __all__ = [
140
+ "WINDOWS_PROCESSOR_FEATURES",
141
+ "detect_cuda",
142
+ "detect_tools",
143
+ "detect_wsl_from_text",
144
+ "parse_windows_cpu_info",
145
+ "parse_windows_video_controllers",
146
+ "windows_processor_features",
147
+ ]
@@ -362,10 +362,6 @@ def configure_tool_dispatch(
362
362
  )
363
363
 
364
364
 
365
- def agent_policy(action_name: str, args: dict) -> ToolPolicy:
366
- return DEFAULT_TOOL_DISPATCH_SERVICE.policy_for(action_name, args)
367
-
368
-
369
365
  def agent_risk(action_name: str, args: dict) -> str:
370
366
  return DEFAULT_TOOL_DISPATCH_SERVICE.risk_level(action_name, args)
371
367
 
@@ -6,23 +6,15 @@ import logging
6
6
  import tempfile
7
7
  from datetime import datetime
8
8
  from pathlib import Path
9
- from typing import Optional
10
9
 
11
10
  from fastapi import HTTPException, Request, UploadFile
12
11
 
13
12
  from lattice_brain.ingestion import IngestionItem
13
+ from latticeai.api.workspace_scope import resolve_workspace_scope
14
14
  from latticeai.core.quiet import quiet
15
15
  from latticeai.tools import ToolError, read_document
16
16
 
17
17
 
18
- def _workspace_scope_from_request(request: Request) -> Optional[str]:
19
- header = request.headers.get("X-Workspace-Id")
20
- if header and header.strip():
21
- return header.strip()
22
- query = request.query_params.get("workspace_id")
23
- return query.strip() if query and query.strip() else None
24
-
25
-
26
18
  async def process_uploaded_document(
27
19
  *,
28
20
  request: Request,
@@ -39,17 +31,12 @@ async def process_uploaded_document(
39
31
  workspace_service=None,
40
32
  ) -> dict:
41
33
  enforce_rate_limit(current_user, "upload")
42
- requested_workspace = _workspace_scope_from_request(request)
43
- if workspace_service is not None:
44
- try:
45
- workspace_id = workspace_service.resolve_write_scope(
46
- requested_workspace,
47
- current_user or None,
48
- )
49
- except PermissionError as exc:
50
- raise HTTPException(status_code=403, detail=str(exc)) from exc
51
- else:
52
- workspace_id = requested_workspace
34
+ workspace_id = resolve_workspace_scope(
35
+ request,
36
+ user=current_user,
37
+ workspace_service=workspace_service,
38
+ write=True,
39
+ )
53
40
  suffix = Path(file.filename or "upload").suffix.lower()
54
41
  allowed = {".pdf", ".docx", ".xlsx", ".pptx", ".txt", ".md", ".csv"}
55
42
  if suffix not in allowed:
@@ -73,12 +73,6 @@ class WorkspaceService:
73
73
  self._ensure_permission(workspace_id, user_id, "write")
74
74
  return workspace_id
75
75
 
76
- def can_read(self, workspace_id: str, user_id: Optional[str]) -> bool:
77
- return self.store.has_permission(workspace_id, self._identity(user_id), "read")
78
-
79
- def can_write(self, workspace_id: str, user_id: Optional[str]) -> bool:
80
- return self.store.has_permission(workspace_id, self._identity(user_id), "write")
81
-
82
76
  def readable_workspaces(self, user_id: Optional[str]) -> list[str]:
83
77
  """Return workspace ids the caller can read.
84
78