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.
- package/README.md +85 -53
- package/docs/CHANGELOG.md +54 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/HYBRID_CLOUD_KG_STREAMING.md +8 -4
- package/docs/MULTI_AGENT_RUNTIME.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +1 -1
- package/docs/architecture.md +4 -4
- package/docs/kg-schema.md +1 -1
- package/docs/v11.5.2_TIGHT_SHIP_PLAN.md +144 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/proactive.py +1 -1
- package/lattice_brain/graph/projection/v2_schema.py +0 -28
- package/lattice_brain/graph/vector_index/base.py +0 -3
- package/lattice_brain/graph/vector_index/brute_force.py +0 -4
- package/lattice_brain/graph/vector_index/hnsw.py +0 -3
- package/lattice_brain/graph/vector_index/quantized.py +0 -3
- package/lattice_brain/ingestion_jobs.py +0 -3
- package/lattice_brain/memory.py +0 -27
- package/lattice_brain/portability/sharing.py +0 -4
- package/lattice_brain/quality.py +0 -49
- package/lattice_brain/runtime/agent_runtime.py +1 -1
- package/lattice_brain/runtime/hooks.py +0 -13
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/sealed_box.py +0 -4
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +12 -12
- package/latticeai/api/auth.py +25 -2
- package/latticeai/api/chat.py +3 -3
- package/latticeai/api/chat_agent_http.py +3 -12
- package/latticeai/api/chat_helpers.py +10 -13
- package/latticeai/api/computer_use.py +31 -42
- package/latticeai/api/health.py +21 -1
- package/latticeai/api/local_files.py +14 -0
- package/latticeai/api/permissions.py +38 -12
- package/latticeai/api/search.py +24 -0
- package/latticeai/api/static_routes.py +4 -1
- package/latticeai/cli/runtime.py +7 -3
- package/latticeai/core/config.py +47 -3
- package/latticeai/core/csrf.py +28 -2
- package/latticeai/core/embedding_providers/__init__.py +3 -5
- package/latticeai/core/embedding_providers/base.py +1 -1
- package/latticeai/core/embedding_providers/text.py +1 -1
- package/latticeai/core/enterprise.py +1 -4
- package/latticeai/core/http_origin.py +146 -0
- package/latticeai/core/invitations.py +3 -2
- package/latticeai/core/io_utils.py +2 -11
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/messages.py +5 -0
- package/latticeai/core/model_compat.py +2 -8
- package/latticeai/core/module_probe.py +37 -0
- package/latticeai/core/run_explain.py +5 -2
- package/latticeai/core/run_store.py +2 -2
- package/latticeai/core/security.py +13 -0
- package/latticeai/core/sessions.py +3 -2
- package/latticeai/core/sse.py +25 -0
- package/latticeai/core/users.py +0 -9
- package/latticeai/core/workspace_graph_trace.py +2 -8
- package/latticeai/core/workspace_os.py +0 -10
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/core/workspace_os_utils.py +34 -0
- package/latticeai/core/workspace_review_items.py +2 -8
- package/latticeai/core/workspace_runs.py +2 -8
- package/latticeai/core/workspace_skills.py +2 -7
- package/latticeai/models/router/documents.py +0 -35
- package/latticeai/models/router/registry.py +0 -4
- package/latticeai/runtime/access_runtime.py +16 -4
- package/latticeai/runtime/build_phases/features.py +2 -0
- package/latticeai/runtime/build_phases/web.py +2 -0
- package/latticeai/runtime/feature_toggle_wiring.py +12 -18
- package/latticeai/runtime/network_boundary_wiring.py +8 -19
- package/latticeai/runtime/permission_mode_wiring.py +6 -13
- package/latticeai/runtime/router_registration.py +4 -0
- package/latticeai/runtime/runtime_context.py +1 -13
- package/latticeai/runtime/service_singletons.py +55 -0
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/brain_intelligence/proposals.py +0 -5
- package/latticeai/services/change_proposals.py +2 -36
- package/latticeai/services/chronicle.py +4 -6
- package/latticeai/services/command_center.py +4 -4
- package/latticeai/services/evidence_actions.py +5 -2
- package/latticeai/services/hybrid_chat.py +2 -2
- package/latticeai/services/hybrid_policy.py +8 -57
- package/latticeai/services/mode_store.py +132 -0
- package/latticeai/services/model_capability_registry.py +0 -15
- package/latticeai/services/model_catalog.py +0 -7
- package/latticeai/services/model_loading.py +3 -2
- package/latticeai/services/model_runtime/__init__.py +0 -57
- package/latticeai/services/model_runtime/engines.py +27 -128
- package/latticeai/services/model_runtime/loading.py +2 -2
- package/latticeai/services/network_boundary_service.py +7 -44
- package/latticeai/services/permission_mode_service.py +8 -56
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/setup_detection.py +67 -1
- package/latticeai/services/tool_dispatch.py +0 -4
- package/latticeai/services/upload_service.py +7 -20
- package/latticeai/services/workspace_service.py +0 -6
- package/latticeai/setup/auto_setup.py +15 -26
- package/latticeai/setup/wizard/catalog.py +5 -2
- package/latticeai/setup/wizard/detect.py +6 -27
- package/latticeai/setup/wizard/paths.py +2 -5
- package/latticeai/setup/wizard/plans.py +2 -2
- package/package.json +2 -4
- package/scripts/bump_version.py +14 -1
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_legacy_debt.mjs +1 -1
- package/scripts/generate_agent_loop_fixtures.py +87 -0
- package/scripts/generate_rust_parity_fixtures.py +3 -95
- package/scripts/parity_fixture_corpus_context.py +162 -0
- package/scripts/release_screen_claims.json +10 -0
- package/src-tauri/Cargo.lock +7 -7
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +76 -2
- package/src-tauri/src/main.rs +13 -3
- package/src-tauri/tauri.conf.json +2 -2
- package/static/app/asset-manifest.json +40 -40
- package/static/app/assets/{Act-Drs_jd-O.js → Act-DcQizkl1.js} +1 -1
- package/static/app/assets/{AdminConsole-BqNx5oF6.js → AdminConsole-cf4npybT.js} +1 -1
- package/static/app/assets/{Brain-C7bdNmz-.js → Brain-3VCSHFcn.js} +1 -1
- package/static/app/assets/{BrainHome-DcTnINcI.js → BrainHome-Qm8eaztx.js} +1 -1
- package/static/app/assets/{BrainSignals-Df1MRIPD.js → BrainSignals-DS9BtKOW.js} +1 -1
- package/static/app/assets/{Capture-Dgsxvnbx.js → Capture-DiQ219jW.js} +1 -1
- package/static/app/assets/{Chronicle-BrkTxJK1.js → Chronicle-BGvuAchH.js} +1 -1
- package/static/app/assets/{CommandPalette-DDziw7Oj.js → CommandPalette-Bqhm0Urn.js} +1 -1
- package/static/app/assets/{Library-CYxeuraA.js → Library-BV6NnF0a.js} +1 -1
- package/static/app/assets/{LivingBrain-CNp6mvCm.js → LivingBrain-GzenJchP.js} +1 -1
- package/static/app/assets/{ProductFlow-BVsowu3Z.js → ProductFlow-DEP6-vML.js} +1 -1
- package/static/app/assets/{ReviewCard-D1LbOfJS.js → ReviewCard-CNZ7XjWG.js} +1 -1
- package/static/app/assets/{System-DcRAq7wj.js → System-CieofHQa.js} +1 -1
- package/static/app/assets/arrow-left-kfsrk0mv.js +1 -0
- package/static/app/assets/{bot-BK2xQ9mN.js → bot-B_K1Tdmw.js} +1 -1
- package/static/app/assets/{brain-BZNztFBb.js → brain-DWyaV1L1.js} +1 -1
- package/static/app/assets/{button-CjueubVZ.js → button-aTn4s84A.js} +1 -1
- package/static/app/assets/circle-check-qqLug9nU.js +1 -0
- package/static/app/assets/{circle-pause-HRjSmpyY.js → circle-pause-xKgeGXkT.js} +1 -1
- package/static/app/assets/{circle-play-CMcKDVwu.js → circle-play-DkT6tYPX.js} +1 -1
- package/static/app/assets/{cpu-B_MyTfYr.js → cpu-85xYObUC.js} +1 -1
- package/static/app/assets/{download-CTy0EByV.js → download-B5Fm7YXo.js} +1 -1
- package/static/app/assets/{folder-open-DF6bLhTg.js → folder-open-kk2Xa52u.js} +1 -1
- package/static/app/assets/{hard-drive-CpWHG77C.js → hard-drive-DkA3zBW_.js} +1 -1
- package/static/app/assets/{index-Dd6abJHX.js → index-BMPdTmlY.js} +3 -3
- package/static/app/assets/{input-bVgIRN3s.js → input-B0nRf2jO.js} +1 -1
- package/static/app/assets/{link-2-bg6CG5Vk.js → link-2-Dwb4gnTc.js} +1 -1
- package/static/app/assets/{permissionCopy-DFTf1HDZ.js → permissionCopy-CQDUBrOZ.js} +1 -1
- package/static/app/assets/{primitives-D7D-sQ3T.js → primitives-SNp0LRJz.js} +1 -1
- package/static/app/assets/search-BcHqkjoy.js +1 -0
- package/static/app/assets/{share-2-BLzo7U4L.js → share-2-BsrxFglO.js} +1 -1
- package/static/app/assets/{shield-alert-BmngqnsJ.js → shield-alert-5BStfp2_.js} +1 -1
- package/static/app/assets/{textarea-DOSlAQQ3.js → textarea-Cg8IUA-k.js} +1 -1
- package/static/app/assets/{useFocusTrap-pZhgeee4.js → useFocusTrap-CYKvE46M.js} +1 -1
- package/static/app/assets/{useMutation-BMDwNk4I.js → useMutation-CSn9t1op.js} +1 -1
- package/static/app/assets/{useQuery-CSjKttRo.js → useQuery-CY2OI2uy.js} +1 -1
- package/static/app/assets/{utils-D3u_yv7B.js → utils-Ddol2RWD.js} +1 -1
- package/static/app/assets/{workspace-H3bMjYXC.js → workspace-BqDwOz_p.js} +1 -1
- package/static/app/index.html +3 -3
- package/static/sw.js +1 -1
- package/desktop/electron/README.md +0 -9
- package/desktop/electron/main.cjs +0 -58
- package/desktop/electron/preload.cjs +0 -5
- package/latticeai/core/graph_curator.py +0 -11
- package/latticeai/core/hooks.py +0 -11
- package/latticeai/core/local_embeddings.py +0 -104
- package/latticeai/core/multi_agent.py +0 -11
- package/latticeai/core/workflow_engine.py +0 -11
- package/latticeai/services/ingestion.py +0 -11
- package/latticeai/services/kg_portability.py +0 -11
- package/latticeai/services/multimodal_streaming.py +0 -129
- package/scripts/measure_brain_home_fill.mjs +0 -141
- package/static/app/assets/arrow-left-BHYmOTWU.js +0 -1
- package/static/app/assets/circle-check-CLeWJr67.js +0 -1
- package/static/app/assets/search-BgSVX6NG.js +0 -1
|
@@ -47,6 +47,7 @@ from dataclasses import asdict, dataclass, field
|
|
|
47
47
|
from pathlib import Path
|
|
48
48
|
from typing import Any, Dict, List, Optional, Tuple
|
|
49
49
|
|
|
50
|
+
from latticeai.core.module_probe import module_available
|
|
50
51
|
from latticeai.core.quiet import quiet
|
|
51
52
|
from latticeai.services.process_audit import (
|
|
52
53
|
CommandConfirmationError,
|
|
@@ -59,6 +60,8 @@ from latticeai.services.setup_detection import (
|
|
|
59
60
|
detect_cuda,
|
|
60
61
|
detect_tools,
|
|
61
62
|
detect_wsl_from_text,
|
|
63
|
+
parse_windows_cpu_info,
|
|
64
|
+
windows_processor_features,
|
|
62
65
|
)
|
|
63
66
|
from latticeai.services.setup_detection import (
|
|
64
67
|
parse_windows_video_controllers as _parse_windows_video_controllers,
|
|
@@ -282,37 +285,23 @@ def _detect_cpu_details(prof_os: str) -> Tuple[str, int, int, List[str]]:
|
|
|
282
285
|
flags = line.split(":", 1)[-1].strip().lower().split()
|
|
283
286
|
elif prof_os == "windows":
|
|
284
287
|
raw = _run(["wmic", "cpu", "get", "Name,NumberOfCores,NumberOfLogicalProcessors", "/format:list"])
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
elif key == "NumberOfCores" and value.strip():
|
|
290
|
-
try:
|
|
291
|
-
physical = int(value.strip())
|
|
292
|
-
except ValueError:
|
|
293
|
-
quiet()
|
|
294
|
-
elif key == "NumberOfLogicalProcessors" and value.strip():
|
|
295
|
-
try:
|
|
296
|
-
logical = int(value.strip())
|
|
297
|
-
except ValueError:
|
|
298
|
-
quiet()
|
|
299
|
-
try:
|
|
300
|
-
import ctypes
|
|
301
|
-
kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] # Windows-only
|
|
302
|
-
feature_map = {6: "sse", 10: "sse2", 13: "sse3", 19: "neon", 28: "rdrand"}
|
|
303
|
-
flags.extend(name for code, name in feature_map.items() if kernel32.IsProcessorFeaturePresent(code))
|
|
304
|
-
except Exception:
|
|
305
|
-
quiet()
|
|
288
|
+
model, physical, logical = parse_windows_cpu_info(
|
|
289
|
+
raw, model=model, physical_cores=physical, logical_cores=logical,
|
|
290
|
+
)
|
|
291
|
+
flags.extend(windows_processor_features())
|
|
306
292
|
interesting = {"avx", "avx2", "avx512f", "fma", "neon", "sse4_2", "sse", "sse2", "sse3", "rdrand"}
|
|
307
293
|
return model, physical, logical, sorted({flag for flag in flags if flag in interesting})
|
|
308
294
|
|
|
309
295
|
|
|
310
296
|
def _has_module(name: str) -> bool:
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
297
|
+
"""Importable *and loadable*.
|
|
298
|
+
|
|
299
|
+
``strict=True`` on purpose: this probe drives what the setup wizard offers
|
|
300
|
+
to install, and an ``mlx`` wheel built for another architecture has a
|
|
301
|
+
findable spec right up until it raises on load. Answering from the spec
|
|
302
|
+
alone would plan a runtime that cannot start.
|
|
303
|
+
"""
|
|
304
|
+
return module_available(name, strict=True)
|
|
316
305
|
|
|
317
306
|
|
|
318
307
|
def probe() -> SystemProfile:
|
|
@@ -10,6 +10,8 @@ from __future__ import annotations
|
|
|
10
10
|
import re
|
|
11
11
|
from typing import Dict, List, Tuple
|
|
12
12
|
|
|
13
|
+
from latticeai.services.model_catalog import _version_tuple as _catalog_version_tuple
|
|
14
|
+
|
|
13
15
|
# ── Model Catalog ─────────────────────────────────────────────────────────────
|
|
14
16
|
# (model_id, display_name, size_gb, tag, description, min_ram_gb)
|
|
15
17
|
# 11.2.0: 모든 repo id 를 2026-08-10 Hugging Face API 로 확인했다 — 존재 여부,
|
|
@@ -127,8 +129,9 @@ _BEST_MODEL_TIERS: Dict[str, List[Tuple[int, str]]] = {
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
|
|
133
|
+
#: The engine catalog owns this parse; the wizard compares the same strings.
|
|
134
|
+
_version_tuple = _catalog_version_tuple
|
|
132
135
|
|
|
133
136
|
|
|
134
137
|
def _catalog_row_family_version(row: Tuple[str, str, float, str, str, int]) -> Tuple[str, Tuple[int, ...]] | None:
|
|
@@ -21,6 +21,8 @@ from latticeai.services.setup_detection import (
|
|
|
21
21
|
detect_cuda,
|
|
22
22
|
detect_tools,
|
|
23
23
|
detect_wsl_from_text,
|
|
24
|
+
parse_windows_cpu_info,
|
|
25
|
+
windows_processor_features,
|
|
24
26
|
)
|
|
25
27
|
from latticeai.services.setup_detection import (
|
|
26
28
|
parse_windows_video_controllers as _parse_windows_video_controllers,
|
|
@@ -98,33 +100,10 @@ def _detect_cpu() -> Dict[str, Any]:
|
|
|
98
100
|
quiet()
|
|
99
101
|
elif platform.system() == "Windows":
|
|
100
102
|
raw = _cmd(["wmic", "cpu", "get", "Name,NumberOfCores,NumberOfLogicalProcessors", "/format:list"], timeout=5)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
elif key == "NumberOfCores" and value.strip():
|
|
106
|
-
try:
|
|
107
|
-
physical_cores = int(value.strip())
|
|
108
|
-
except ValueError:
|
|
109
|
-
quiet()
|
|
110
|
-
elif key == "NumberOfLogicalProcessors" and value.strip():
|
|
111
|
-
try:
|
|
112
|
-
logical_cores = int(value.strip())
|
|
113
|
-
except ValueError:
|
|
114
|
-
quiet()
|
|
115
|
-
try:
|
|
116
|
-
import ctypes
|
|
117
|
-
kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] # Windows-only
|
|
118
|
-
feature_map = {
|
|
119
|
-
6: "sse",
|
|
120
|
-
10: "sse2",
|
|
121
|
-
13: "sse3",
|
|
122
|
-
19: "neon",
|
|
123
|
-
28: "rdrand",
|
|
124
|
-
}
|
|
125
|
-
flags.extend(name for code, name in feature_map.items() if kernel32.IsProcessorFeaturePresent(code))
|
|
126
|
-
except Exception:
|
|
127
|
-
quiet()
|
|
103
|
+
model, physical_cores, logical_cores = parse_windows_cpu_info(
|
|
104
|
+
raw, model=model, physical_cores=physical_cores, logical_cores=logical_cores,
|
|
105
|
+
)
|
|
106
|
+
flags.extend(windows_processor_features())
|
|
128
107
|
interesting = {"avx", "avx2", "avx512f", "fma", "neon", "sse4_2"}
|
|
129
108
|
if platform.system() == "Windows":
|
|
130
109
|
interesting.update({"sse", "sse2", "sse3", "rdrand"})
|
|
@@ -20,6 +20,8 @@ import shutil
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Any, Dict, List
|
|
22
22
|
|
|
23
|
+
from latticeai.core.module_probe import module_available as _module_available
|
|
24
|
+
|
|
23
25
|
OFFICIAL_DOWNLOADS: Dict[str, str] = {
|
|
24
26
|
"homebrew": "https://brew.sh",
|
|
25
27
|
"python": "https://www.python.org/downloads/",
|
|
@@ -149,11 +151,6 @@ def _which_detail(binary: str) -> Dict[str, Any]:
|
|
|
149
151
|
return {"installed": path is not None, "path": path}
|
|
150
152
|
|
|
151
153
|
|
|
152
|
-
def _module_available(module_name: str) -> bool:
|
|
153
|
-
import importlib.util
|
|
154
|
-
return importlib.util.find_spec(module_name) is not None
|
|
155
|
-
|
|
156
|
-
|
|
157
154
|
def _package_module(package: str) -> str:
|
|
158
155
|
return PACKAGE_MODULES.get(package, package.replace("-", "_").split("[", 1)[0])
|
|
159
156
|
|
|
@@ -8,15 +8,15 @@ plan and therefore nothing to confirm.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
import json as _json
|
|
12
11
|
import sys
|
|
13
12
|
from typing import Any, Dict, List
|
|
14
13
|
|
|
14
|
+
from latticeai.core.sse import sse_frame
|
|
15
15
|
from latticeai.services.process_audit import command_plan_for_commands
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def _sse(data: Dict) -> str:
|
|
19
|
-
return
|
|
19
|
+
return sse_frame(None, data)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def _action_commands(action: Dict[str, Any]) -> List[List[str]]:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "11.5.
|
|
3
|
+
"version": "11.5.2",
|
|
4
4
|
"description": "Lattice AI — local-first Digital Brain that keeps your knowledge durable across any AI model.",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
@@ -52,7 +52,6 @@
|
|
|
52
52
|
"desktop:tauri:check": "cd src-tauri && cargo check",
|
|
53
53
|
"rust:test": "cd rust && cargo test --workspace",
|
|
54
54
|
"rust:lint": "cd rust && cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings",
|
|
55
|
-
"desktop:electron": "electron desktop/electron/main.cjs",
|
|
56
55
|
"package:vsix": "node scripts/build_vsix.mjs",
|
|
57
56
|
"check:screenshot-delta": "node scripts/run_python.mjs scripts/check_screenshot_pixel_delta.py",
|
|
58
57
|
"release:evidence": "node scripts/capture_release_evidence.mjs && node scripts/prune_release_evidence.mjs && npm run check:screenshot-delta",
|
|
@@ -124,7 +123,6 @@
|
|
|
124
123
|
"!src-tauri/gen/**",
|
|
125
124
|
"!src-tauri/target/",
|
|
126
125
|
"!src-tauri/target/**",
|
|
127
|
-
"desktop/electron/",
|
|
128
126
|
"docs/*.md",
|
|
129
127
|
"!docs/assets/",
|
|
130
128
|
"!docs/images/"
|
|
@@ -142,6 +140,7 @@
|
|
|
142
140
|
"@testing-library/react": "^16.3.2",
|
|
143
141
|
"@testing-library/user-event": "^14.6.1",
|
|
144
142
|
"@types/cytoscape": "^3.21.9",
|
|
143
|
+
"@types/node": "24.13.2",
|
|
145
144
|
"@types/react": "^19.2.17",
|
|
146
145
|
"@types/react-dom": "^19.2.3",
|
|
147
146
|
"@vitejs/plugin-react": "^6.0.2",
|
|
@@ -150,7 +149,6 @@
|
|
|
150
149
|
"class-variance-authority": "^0.7.1",
|
|
151
150
|
"clsx": "^2.1.1",
|
|
152
151
|
"cytoscape": "^3.34.0",
|
|
153
|
-
"electron": "^42.4.0",
|
|
154
152
|
"jsdom": "^29.1.1",
|
|
155
153
|
"lucide-react": "^1.17.0",
|
|
156
154
|
"openapi-fetch": "^0.17.0",
|
package/scripts/bump_version.py
CHANGED
|
@@ -25,7 +25,20 @@ REPO = Path(__file__).resolve().parents[1]
|
|
|
25
25
|
# it per crate — src-tauri's too, since 11.4.0, because the desktop shell now
|
|
26
26
|
# depends on lattice-host by path — and a lockfile left behind is a file the
|
|
27
27
|
# next `cargo build` silently rewrites underneath a tagged release.
|
|
28
|
-
|
|
28
|
+
#
|
|
29
|
+
# 11.5.2: this tuple listed only the three Phase-1 crates, so every bump since
|
|
30
|
+
# 11.5.0 left `lattice-agent`, `lattice-ingest` and `lattice-jobs` at the
|
|
31
|
+
# previous version in both lockfiles — and the first `cargo` invocation of the
|
|
32
|
+
# test suite rewrote them, dirtying the tree mid-release. Every workspace
|
|
33
|
+
# member belongs here.
|
|
34
|
+
RUST_CRATES = (
|
|
35
|
+
"lattice-agent",
|
|
36
|
+
"lattice-core",
|
|
37
|
+
"lattice-host",
|
|
38
|
+
"lattice-ingest",
|
|
39
|
+
"lattice-jobs",
|
|
40
|
+
"lattice-retrieval",
|
|
41
|
+
)
|
|
29
42
|
RUST_LOCKFILES = ("rust/Cargo.lock", "src-tauri/Cargo.lock")
|
|
30
43
|
|
|
31
44
|
# (path, kind, pattern) — pattern groups: (prefix, version)
|
|
@@ -6,7 +6,7 @@ const root = process.cwd();
|
|
|
6
6
|
const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
|
|
7
7
|
const version = pkg.version;
|
|
8
8
|
const releaseDir = `output/release/v${version}`;
|
|
9
|
-
const releaseTheme = "
|
|
9
|
+
const releaseTheme = "Tight Ship";
|
|
10
10
|
const title = `${version} — ${releaseTheme}`;
|
|
11
11
|
const escapedVersion = version.replaceAll(".", "\\.");
|
|
12
12
|
|
|
@@ -31,7 +31,7 @@ const REMOVED_MODULES = [
|
|
|
31
31
|
];
|
|
32
32
|
// Trees that must not import removed shims. static/app and node_modules are
|
|
33
33
|
// build output; src-tauri/target is compiler output.
|
|
34
|
-
const SOURCE_TREES = ["latticeai", "lattice_brain", "tests", "scripts", "bin"
|
|
34
|
+
const SOURCE_TREES = ["latticeai", "lattice_brain", "tests", "scripts", "bin"];
|
|
35
35
|
|
|
36
36
|
const failures = [];
|
|
37
37
|
|
|
@@ -90,6 +90,11 @@ from latticeai.core.agent_helpers import ( # noqa: E402
|
|
|
90
90
|
normalize_plan,
|
|
91
91
|
requirement_coverage,
|
|
92
92
|
)
|
|
93
|
+
from latticeai.core.agent_profiles import ( # noqa: E402
|
|
94
|
+
COMPACT_MAX_PARAMS_B,
|
|
95
|
+
model_size_b,
|
|
96
|
+
profile_for_model,
|
|
97
|
+
)
|
|
93
98
|
from latticeai.core.agent_trace import LoopTrace # noqa: E402
|
|
94
99
|
from latticeai.core.file_generation import ( # noqa: E402
|
|
95
100
|
infer_file_target,
|
|
@@ -107,6 +112,7 @@ from latticeai.core.tool_registry import ( # noqa: E402
|
|
|
107
112
|
TOOL_GOVERNANCE,
|
|
108
113
|
TOOL_GOVERNANCE_DEFAULT,
|
|
109
114
|
)
|
|
115
|
+
from latticeai.tools.documents import document_output_target # noqa: E402
|
|
110
116
|
|
|
111
117
|
FIXTURE_DIR = REPO_ROOT / "rust" / "fixtures" / "agent_loop"
|
|
112
118
|
GOLDEN_DIR = FIXTURE_DIR / "golden"
|
|
@@ -260,6 +266,59 @@ INFERENCE_MESSAGES: List[str] = [
|
|
|
260
266
|
"웹사이트 css js 만들어줘", "마크다운 파일 작성해줘", "yaml 만들어줘",
|
|
261
267
|
]
|
|
262
268
|
|
|
269
|
+
#: ``(tool_name, filename)`` pairs for the document-target resolver. Chosen for
|
|
270
|
+
#: the branches they reach, not for realism: a tool that is not a document
|
|
271
|
+
#: creator, each of the four that are, a name that already carries its suffix,
|
|
272
|
+
#: one that carries the wrong one, a path that must be reduced to its basename,
|
|
273
|
+
#: characters the sanitizer replaces, and the empty names that fall back to
|
|
274
|
+
#: ``artifact<suffix>``. The Rust port takes the basename with its own
|
|
275
|
+
#: ``path_name`` and has its own empty-name fallback, so those two are exactly
|
|
276
|
+
#: where the two implementations could disagree unnoticed.
|
|
277
|
+
DOCUMENT_TARGET_CASES: List[Dict[str, str]] = [
|
|
278
|
+
{"tool": "write_file", "filename": "notes.md"},
|
|
279
|
+
{"tool": "create_docx", "filename": "report.docx"},
|
|
280
|
+
{"tool": "create_docx", "filename": "report"},
|
|
281
|
+
{"tool": "create_docx", "filename": "report.pdf"},
|
|
282
|
+
{"tool": "create_xlsx", "filename": "budget.xlsx"},
|
|
283
|
+
{"tool": "create_pptx", "filename": "deck"},
|
|
284
|
+
{"tool": "create_pdf", "filename": "invoice"},
|
|
285
|
+
{"tool": "create_pdf", "filename": "sub/dir/invoice.pdf"},
|
|
286
|
+
{"tool": "create_pdf", "filename": "../../escape.pdf"},
|
|
287
|
+
{"tool": "create_docx", "filename": "회의 기록.docx"},
|
|
288
|
+
{"tool": "create_docx", "filename": "a/b*c?d.docx"},
|
|
289
|
+
{"tool": "create_docx", "filename": ""},
|
|
290
|
+
{"tool": "create_pptx", "filename": " "},
|
|
291
|
+
{"tool": "create_xlsx", "filename": "REPORT.XLSX"},
|
|
292
|
+
{"tool": "create_pdf", "filename": ".pdf"},
|
|
293
|
+
{"tool": "unknown_tool", "filename": "x.docx"},
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
#: Model ids for the profile dial. The interesting ones are the quantization
|
|
297
|
+
#: suffixes (``4bit`` is not a parameter count), the multi-size ids where the
|
|
298
|
+
#: *smallest* wins, and the boundary at ``COMPACT_MAX_PARAMS_B`` itself.
|
|
299
|
+
PROFILE_MODEL_IDS: List[str] = [
|
|
300
|
+
"mlx-community/gemma-4-12B-it-4bit",
|
|
301
|
+
"qwen2.5-1.5b",
|
|
302
|
+
"llama-3.2-3B",
|
|
303
|
+
"mlx-community/Llama-3.2-3B-Instruct-4bit",
|
|
304
|
+
"phi-4-mini-3.8b-8bit",
|
|
305
|
+
"some-model-4b",
|
|
306
|
+
"some-model-4.0b",
|
|
307
|
+
"some-model-4.1b",
|
|
308
|
+
"gpt-4o",
|
|
309
|
+
"claude-sonnet",
|
|
310
|
+
"",
|
|
311
|
+
" ",
|
|
312
|
+
"model-8bit",
|
|
313
|
+
"abc123b",
|
|
314
|
+
"7b-and-1.5b-mixed",
|
|
315
|
+
"Model-70B-Instruct",
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
#: ``LATTICEAI_AGENT_PROFILE`` values, including the two that must fall through
|
|
319
|
+
#: to the size heuristic rather than failing the run.
|
|
320
|
+
PROFILE_OVERRIDES: List[str] = ["", "standard", "compact", "COMPACT", "nonsense"]
|
|
321
|
+
|
|
263
322
|
#: Transcripts the artifact/coverage helpers are asked about.
|
|
264
323
|
TRANSCRIPT_CASES: Dict[str, Dict[str, Any]] = {
|
|
265
324
|
"empty": {"message": "todo 앱 html css js 만들어줘", "transcript": []},
|
|
@@ -382,6 +441,31 @@ def helper_rows() -> Dict[str, Any]:
|
|
|
382
441
|
{"input": case, "kept": filter_learnings(case)} for case in LEARNING_CASES
|
|
383
442
|
]
|
|
384
443
|
|
|
444
|
+
documents = [
|
|
445
|
+
{**case, "target": document_output_target(case["tool"], case["filename"])}
|
|
446
|
+
for case in DOCUMENT_TARGET_CASES
|
|
447
|
+
]
|
|
448
|
+
|
|
449
|
+
# ``env`` is passed explicitly: the Rust twin reads the ambient process
|
|
450
|
+
# environment, and a golden that inherited this machine's would be a
|
|
451
|
+
# machine-specific value in a committed fixture.
|
|
452
|
+
profiles = []
|
|
453
|
+
for override in PROFILE_OVERRIDES:
|
|
454
|
+
env = {"LATTICEAI_AGENT_PROFILE": override} if override else {}
|
|
455
|
+
for model_id in PROFILE_MODEL_IDS:
|
|
456
|
+
profiles.append({
|
|
457
|
+
"override": override,
|
|
458
|
+
"model_id": model_id,
|
|
459
|
+
"size_b": model_size_b(model_id),
|
|
460
|
+
"profile": profile_for_model(model_id, env=env).__dict__,
|
|
461
|
+
})
|
|
462
|
+
profiles.append({
|
|
463
|
+
"override": "",
|
|
464
|
+
"model_id": None,
|
|
465
|
+
"size_b": model_size_b(""),
|
|
466
|
+
"profile": profile_for_model(None, env={}).__dict__,
|
|
467
|
+
})
|
|
468
|
+
|
|
385
469
|
return normalize({
|
|
386
470
|
"schema": SCHEMA,
|
|
387
471
|
"extract_action_details": actions,
|
|
@@ -390,6 +474,8 @@ def helper_rows() -> Dict[str, Any]:
|
|
|
390
474
|
"transcript_helpers": transcripts,
|
|
391
475
|
"truncate_strings": truncated,
|
|
392
476
|
"filter_learnings": learnings,
|
|
477
|
+
"document_targets": documents,
|
|
478
|
+
"agent_profiles": profiles,
|
|
393
479
|
"budgets": {
|
|
394
480
|
"phase": PhaseBudgets().__dict__,
|
|
395
481
|
"transcript": TranscriptBudget().__dict__,
|
|
@@ -858,6 +944,7 @@ def manifest_payload() -> Dict[str, Any]:
|
|
|
858
944
|
"transcript_budget": TranscriptBudget().__dict__,
|
|
859
945
|
"max_state_history": 200,
|
|
860
946
|
"max_retry": 3,
|
|
947
|
+
"compact_max_params_b": COMPACT_MAX_PARAMS_B,
|
|
861
948
|
},
|
|
862
949
|
}
|
|
863
950
|
|
|
@@ -50,6 +50,7 @@ for _import_root in (REPO_ROOT, Path(__file__).resolve().parent):
|
|
|
50
50
|
if str(_import_root) not in sys.path:
|
|
51
51
|
sys.path.insert(0, str(_import_root))
|
|
52
52
|
|
|
53
|
+
import parity_fixture_corpus_context as context_corpus # noqa: E402
|
|
53
54
|
import parity_fixture_corpus_docgen as docgen # noqa: E402
|
|
54
55
|
|
|
55
56
|
FIXTURE_DIR = REPO_ROOT / "rust" / "fixtures"
|
|
@@ -324,23 +325,6 @@ QUERIES: List[Dict[str, Any]] = [
|
|
|
324
325
|
|
|
325
326
|
#: Every branch of the memories section: a workspace hit, one with no kind and an
|
|
326
327
|
#: empty snippet, a non-workspace row to drop, and one past the limit.
|
|
327
|
-
CONTEXT_MEMORIES: Dict[str, Any] = {
|
|
328
|
-
"results": [
|
|
329
|
-
{"id": "mem-1", "kind": "preference", "snippet": "답변은 한국어로", "score": 0.91, "source": "workspace"},
|
|
330
|
-
{"id": "mem-2", "kind": None, "snippet": "", "score": 0.0, "source": "workspace"},
|
|
331
|
-
{"id": "mem-3", "kind": "decision", "snippet": "ranking keeps alpha fusion", "score": 0.4, "source": "personal"},
|
|
332
|
-
{"id": "mem-4", "kind": "fact", "snippet": "온보딩은 다섯 걸음", "score": 0.3, "source": "workspace"},
|
|
333
|
-
]
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
#: A ledger with a pathless row, a non-dict row, and more than the ten-row cut.
|
|
337
|
-
CONTEXT_ARTIFACTS: List[Any] = [
|
|
338
|
-
{"path": "notes/ranking.md", "at": "2026-07-20T09:00:00", "run_id": "run-1"},
|
|
339
|
-
{"path": "notes/onboarding.md", "run_id": "run-2"},
|
|
340
|
-
{"path": "", "at": "2026-07-20T09:00:01"},
|
|
341
|
-
"not-a-dict",
|
|
342
|
-
] + [{"path": f"out/file-{index}.md", "at": None, "run_id": f"r{index}"} for index in range(10)]
|
|
343
|
-
|
|
344
328
|
#: The Phase-2/3 suites: one spec list per ported entry point.
|
|
345
329
|
SUITES: Dict[str, List[Dict[str, Any]]] = {
|
|
346
330
|
"relationship": [
|
|
@@ -462,21 +446,7 @@ SUITES: Dict[str, List[Dict[str, Any]]] = {
|
|
|
462
446
|
{"key": "limit_one", "query": "ranking", "limit": 1},
|
|
463
447
|
{"key": "scoped_strict", "query": "ranking", "allowed": [WS_BETA], "legacy": False},
|
|
464
448
|
],
|
|
465
|
-
|
|
466
|
-
{"key": "all_seams", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "artifacts": CONTEXT_ARTIFACTS, "notes": "정원 노트: 랭킹은 alpha 융합을 유지한다.", "recent": {"limit": 4}},
|
|
467
|
-
{"key": "knowledge_only", "query": "hybrid retrieval ranking"},
|
|
468
|
-
{"key": "no_seams", "query": "hybrid retrieval ranking", "knowledge": False},
|
|
469
|
-
{"key": "memories_only", "query": "온보딩", "knowledge": False, "memories": CONTEXT_MEMORIES, "memory_limit": 2},
|
|
470
|
-
{"key": "artifacts_only", "query": "온보딩", "knowledge": False, "artifacts": CONTEXT_ARTIFACTS},
|
|
471
|
-
{"key": "notes_blank", "query": "온보딩", "knowledge": False, "notes": " "},
|
|
472
|
-
{"key": "recent_conversation", "query": "온보딩", "knowledge": False, "recent": {"conversation_id": "conv-a", "limit": 10}},
|
|
473
|
-
{"key": "recent_personal_workspace", "query": "온보딩", "knowledge": False, "recent": {"workspace_id": "personal", "limit": 6}},
|
|
474
|
-
{"key": "recent_user_scoped", "query": "온보딩", "knowledge": False, "recent": {"user_email": "jiwon@lattice.ai", "limit": 5}},
|
|
475
|
-
{"key": "budget_tiny", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "artifacts": CONTEXT_ARTIFACTS, "notes": "정원 노트: 랭킹은 alpha 융합을 유지한다.", "recent": {"limit": 4}, "budget": 20},
|
|
476
|
-
{"key": "budget_one", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "artifacts": CONTEXT_ARTIFACTS, "notes": "노트", "recent": {"limit": 4}, "budget": 1},
|
|
477
|
-
{"key": "budget_zero", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "notes": "노트", "budget": 0},
|
|
478
|
-
{"key": "knowledge_limit_one", "query": "hybrid retrieval ranking", "knowledge_limit": 1},
|
|
479
|
-
],
|
|
449
|
+
**context_corpus.CONTEXT_SUITES,
|
|
480
450
|
# v11.5.1: the document-generation ports, specs and runners alike.
|
|
481
451
|
**docgen.DOCGEN_SUITES,
|
|
482
452
|
}
|
|
@@ -799,68 +769,6 @@ def _run_history_search(h: Harness, spec: Dict[str, Any]) -> List[Dict[str, Any]
|
|
|
799
769
|
)
|
|
800
770
|
|
|
801
771
|
|
|
802
|
-
def _context_seams(h: Harness, spec: Dict[str, Any]) -> Dict[str, Any]:
|
|
803
|
-
"""The seam set for one context spec — data seams plus the real engines.
|
|
804
|
-
|
|
805
|
-
``memories`` / ``artifacts`` / ``notes`` are *data* seams: the payload is the
|
|
806
|
-
spec, so both runtimes feed the assembler the same bytes and what is under
|
|
807
|
-
test is the assembler. ``knowledge`` and ``recent`` are real engines — the
|
|
808
|
-
service-layer hybrid search and the durable history reader.
|
|
809
|
-
"""
|
|
810
|
-
from latticeai.api.chat_helpers import build_recent_chat_context
|
|
811
|
-
|
|
812
|
-
# Signatures matter: the assembler inspects them to decide which context
|
|
813
|
-
# fields a seam may be handed, so each one declares exactly what it accepts.
|
|
814
|
-
seams: Dict[str, Any] = {}
|
|
815
|
-
if spec.get("memories") is not None:
|
|
816
|
-
memories = spec["memories"]
|
|
817
|
-
seams["memory_recall"] = (
|
|
818
|
-
lambda query, *, user_email=None, workspace_id=None, limit=5: memories
|
|
819
|
-
)
|
|
820
|
-
if spec.get("artifacts") is not None:
|
|
821
|
-
artifacts = spec["artifacts"]
|
|
822
|
-
seams["recent_artifacts"] = (
|
|
823
|
-
lambda *, user_email=None, conversation_id=None, workspace_id=None: artifacts
|
|
824
|
-
)
|
|
825
|
-
if spec.get("knowledge", True):
|
|
826
|
-
# Loopback trust: no workspace scoping, exactly as on the native route.
|
|
827
|
-
seams["hybrid_search"] = (
|
|
828
|
-
lambda query, *, limit=5, user_email=None, workspace_id=None:
|
|
829
|
-
h.service.hybrid_search(query, limit=limit)
|
|
830
|
-
)
|
|
831
|
-
if spec.get("notes") is not None:
|
|
832
|
-
notes = spec["notes"]
|
|
833
|
-
seams["notes_context"] = lambda query, *, user_email=None, workspace_id=None: notes
|
|
834
|
-
if spec.get("recent") is not None:
|
|
835
|
-
recent = spec["recent"]
|
|
836
|
-
seams["recent_chat"] = (
|
|
837
|
-
lambda *, user_email=None, conversation_id=None, workspace_id=None:
|
|
838
|
-
build_recent_chat_context(
|
|
839
|
-
get_history=h.history_runtime["get_history"],
|
|
840
|
-
limit=recent.get("limit", 10),
|
|
841
|
-
include_image_missing_replies=recent.get("images", True),
|
|
842
|
-
user_email=recent.get("user_email"),
|
|
843
|
-
conversation_id=recent.get("conversation_id"),
|
|
844
|
-
workspace_id=recent.get("workspace_id"),
|
|
845
|
-
)
|
|
846
|
-
)
|
|
847
|
-
return seams
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
def _run_context_assemble(h: Harness, spec: Dict[str, Any]) -> Dict[str, Any]:
|
|
851
|
-
from lattice_brain.context import ContextAssembler
|
|
852
|
-
|
|
853
|
-
assembled = ContextAssembler(**_context_seams(h, spec)).assemble(
|
|
854
|
-
spec["query"], user_email=spec.get("user_email"),
|
|
855
|
-
workspace_id=spec.get("workspace_id"),
|
|
856
|
-
conversation_id=spec.get("conversation_id"), budget=spec.get("budget", 2000),
|
|
857
|
-
memory_limit=spec.get("memory_limit", 5),
|
|
858
|
-
knowledge_limit=spec.get("knowledge_limit", 5),
|
|
859
|
-
)
|
|
860
|
-
return {"text": assembled.text, "approx_tokens": assembled.approx_tokens,
|
|
861
|
-
"trace": assembled.trace()}
|
|
862
|
-
|
|
863
|
-
|
|
864
772
|
SUITE_RUNNERS: Dict[str, Callable[[Harness, Dict[str, Any]], Any]] = {
|
|
865
773
|
"relationship": _run_relationship,
|
|
866
774
|
"traverse": _run_traverse,
|
|
@@ -870,7 +778,7 @@ SUITE_RUNNERS: Dict[str, Callable[[Harness, Dict[str, Any]], Any]] = {
|
|
|
870
778
|
"conversations": _run_conversations,
|
|
871
779
|
"conversation_messages": _run_conversation_messages,
|
|
872
780
|
"history_search": _run_history_search,
|
|
873
|
-
|
|
781
|
+
**context_corpus.CONTEXT_RUNNERS,
|
|
874
782
|
**docgen.DOCGEN_RUNNERS,
|
|
875
783
|
}
|
|
876
784
|
|