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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The context-assembly half of the Python↔Rust parity corpus (v11.5.2).
|
|
3
|
+
|
|
4
|
+
``scripts/generate_rust_parity_fixtures.py`` owns the store, the harness and the
|
|
5
|
+
goldens; this module owns the specs and runners for the two context ports, so
|
|
6
|
+
neither file grows past the tree's file-size ceiling. The split follows the
|
|
7
|
+
``parity_fixture_corpus_docgen`` precedent: specs and their runners live side by
|
|
8
|
+
side, because a spec list that arrives without the runner that answers it is a
|
|
9
|
+
suite that silently checks nothing.
|
|
10
|
+
|
|
11
|
+
Two ports, deliberately separate:
|
|
12
|
+
|
|
13
|
+
* **``context_assemble``** pins :class:`~lattice_brain.context.ContextAssembler`
|
|
14
|
+
over the seams the *production* assembler supplies
|
|
15
|
+
(``latticeai/runtime/context_runtime.py``): memories, artifacts, hybrid
|
|
16
|
+
knowledge and garden notes. It does **not** supply ``recent_chat`` — that seam
|
|
17
|
+
has never been wired live, and a golden that pinned it claimed cross-runtime
|
|
18
|
+
agreement about a code path the product does not execute.
|
|
19
|
+
* **``recent_chat``** pins the recent-conversation transcript as the product
|
|
20
|
+
really builds it: ``chat.py`` prepends ``build_recent_chat_context(...)`` on
|
|
21
|
+
every non-hybrid turn and the agent loop injects the same text each executor
|
|
22
|
+
step. Its Rust twin is ``lattice_retrieval::history::recent_chat``.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any, Callable, Dict, List
|
|
28
|
+
|
|
29
|
+
#: ``h`` is the generator's ``Harness``. It is typed ``Any`` rather than
|
|
30
|
+
#: imported: the harness owns the live store and the service layer, so importing
|
|
31
|
+
#: it here would make the corpus depend on the generator that imports *it*.
|
|
32
|
+
|
|
33
|
+
#: The organization workspace the corpus seeds; the same literal the generator
|
|
34
|
+
#: and the docgen corpus use.
|
|
35
|
+
WS_ALPHA = "ws-alpha"
|
|
36
|
+
|
|
37
|
+
CONTEXT_MEMORIES: Dict[str, Any] = {
|
|
38
|
+
"results": [
|
|
39
|
+
{"id": "mem-1", "kind": "preference", "snippet": "답변은 한국어로", "score": 0.91, "source": "workspace"},
|
|
40
|
+
{"id": "mem-2", "kind": None, "snippet": "", "score": 0.0, "source": "workspace"},
|
|
41
|
+
{"id": "mem-3", "kind": "decision", "snippet": "ranking keeps alpha fusion", "score": 0.4, "source": "personal"},
|
|
42
|
+
{"id": "mem-4", "kind": "fact", "snippet": "온보딩은 다섯 걸음", "score": 0.3, "source": "workspace"},
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#: A ledger with a pathless row, a non-dict row, and more than the ten-row cut.
|
|
47
|
+
CONTEXT_ARTIFACTS: List[Any] = [
|
|
48
|
+
{"path": "notes/ranking.md", "at": "2026-07-20T09:00:00", "run_id": "run-1"},
|
|
49
|
+
{"path": "notes/onboarding.md", "run_id": "run-2"},
|
|
50
|
+
{"path": "", "at": "2026-07-20T09:00:01"},
|
|
51
|
+
"not-a-dict",
|
|
52
|
+
] + [{"path": f"out/file-{index}.md", "at": None, "run_id": f"r{index}"} for index in range(10)]
|
|
53
|
+
|
|
54
|
+
def _context_seams(h: Any, spec: Dict[str, Any]) -> Dict[str, Any]:
|
|
55
|
+
"""The seam set for one context spec — data seams plus the real engines.
|
|
56
|
+
|
|
57
|
+
``memories`` / ``artifacts`` / ``notes`` are *data* seams: the payload is the
|
|
58
|
+
spec, so both runtimes feed the assembler the same bytes and what is under
|
|
59
|
+
test is the assembler. ``knowledge`` is a real engine — the service-layer
|
|
60
|
+
hybrid search.
|
|
61
|
+
|
|
62
|
+
There is deliberately no ``recent_chat`` seam here: the production
|
|
63
|
+
assembler does not supply one, so a golden that did would pin agreement
|
|
64
|
+
about a path neither runtime reaches in the product. The recent transcript
|
|
65
|
+
is pinned by the ``recent_chat`` suite instead.
|
|
66
|
+
"""
|
|
67
|
+
# Signatures matter: the assembler inspects them to decide which context
|
|
68
|
+
# fields a seam may be handed, so each one declares exactly what it accepts.
|
|
69
|
+
seams: Dict[str, Any] = {}
|
|
70
|
+
if spec.get("memories") is not None:
|
|
71
|
+
memories = spec["memories"]
|
|
72
|
+
seams["memory_recall"] = (
|
|
73
|
+
lambda query, *, user_email=None, workspace_id=None, limit=5: memories
|
|
74
|
+
)
|
|
75
|
+
if spec.get("artifacts") is not None:
|
|
76
|
+
artifacts = spec["artifacts"]
|
|
77
|
+
seams["recent_artifacts"] = (
|
|
78
|
+
lambda *, user_email=None, conversation_id=None, workspace_id=None: artifacts
|
|
79
|
+
)
|
|
80
|
+
if spec.get("knowledge", True):
|
|
81
|
+
# Loopback trust: no workspace scoping, exactly as on the native route.
|
|
82
|
+
seams["hybrid_search"] = (
|
|
83
|
+
lambda query, *, limit=5, user_email=None, workspace_id=None:
|
|
84
|
+
h.service.hybrid_search(query, limit=limit)
|
|
85
|
+
)
|
|
86
|
+
if spec.get("notes") is not None:
|
|
87
|
+
notes = spec["notes"]
|
|
88
|
+
seams["notes_context"] = lambda query, *, user_email=None, workspace_id=None: notes
|
|
89
|
+
return seams
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _run_context_assemble(h: Any, spec: Dict[str, Any]) -> Dict[str, Any]:
|
|
93
|
+
from lattice_brain.context import ContextAssembler
|
|
94
|
+
|
|
95
|
+
assembled = ContextAssembler(**_context_seams(h, spec)).assemble(
|
|
96
|
+
spec["query"], user_email=spec.get("user_email"),
|
|
97
|
+
workspace_id=spec.get("workspace_id"),
|
|
98
|
+
conversation_id=spec.get("conversation_id"), budget=spec.get("budget", 2000),
|
|
99
|
+
memory_limit=spec.get("memory_limit", 5),
|
|
100
|
+
knowledge_limit=spec.get("knowledge_limit", 5),
|
|
101
|
+
)
|
|
102
|
+
return {"text": assembled.text, "approx_tokens": assembled.approx_tokens,
|
|
103
|
+
"trace": assembled.trace()}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _run_recent_chat(h: Any, spec: Dict[str, Any]) -> str:
|
|
107
|
+
"""The recent-conversation transcript, exactly as ``/chat`` builds it."""
|
|
108
|
+
from latticeai.api.chat_helpers import build_recent_chat_context
|
|
109
|
+
|
|
110
|
+
return build_recent_chat_context(
|
|
111
|
+
get_history=h.history_runtime["get_history"],
|
|
112
|
+
limit=spec.get("limit", 10),
|
|
113
|
+
include_image_missing_replies=spec.get("images", True),
|
|
114
|
+
user_email=spec.get("user_email"),
|
|
115
|
+
conversation_id=spec.get("conversation_id"),
|
|
116
|
+
workspace_id=spec.get("workspace_id"),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
#: The two context suites, as the generator merges them into :data:`SUITES` /
|
|
121
|
+
#: :data:`SUITE_RUNNERS`.
|
|
122
|
+
CONTEXT_SUITES: Dict[str, List[Dict[str, Any]]] = {
|
|
123
|
+
# 11.5.2: these pin the assembler over the seams the *production* assembler
|
|
124
|
+
# supplies (`latticeai/runtime/context_runtime.py`). The `recent_chat` seam
|
|
125
|
+
# is not one of them — it has never been wired live — so pinning it here
|
|
126
|
+
# claimed cross-runtime agreement about a code path the product does not
|
|
127
|
+
# run. The recent-conversation text itself is still pinned, in the
|
|
128
|
+
# `recent_chat` suite below, against the function chat really calls.
|
|
129
|
+
"context_assemble": [
|
|
130
|
+
{"key": "all_seams", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "artifacts": CONTEXT_ARTIFACTS, "notes": "정원 노트: 랭킹은 alpha 융합을 유지한다."},
|
|
131
|
+
{"key": "knowledge_only", "query": "hybrid retrieval ranking"},
|
|
132
|
+
{"key": "no_seams", "query": "hybrid retrieval ranking", "knowledge": False},
|
|
133
|
+
{"key": "memories_only", "query": "온보딩", "knowledge": False, "memories": CONTEXT_MEMORIES, "memory_limit": 2},
|
|
134
|
+
{"key": "artifacts_only", "query": "온보딩", "knowledge": False, "artifacts": CONTEXT_ARTIFACTS},
|
|
135
|
+
{"key": "notes_blank", "query": "온보딩", "knowledge": False, "notes": " "},
|
|
136
|
+
{"key": "budget_tiny", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "artifacts": CONTEXT_ARTIFACTS, "notes": "정원 노트: 랭킹은 alpha 융합을 유지한다.", "budget": 20},
|
|
137
|
+
{"key": "budget_one", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "artifacts": CONTEXT_ARTIFACTS, "notes": "노트", "budget": 1},
|
|
138
|
+
{"key": "budget_zero", "query": "회의 결정 사항", "memories": CONTEXT_MEMORIES, "notes": "노트", "budget": 0},
|
|
139
|
+
{"key": "knowledge_limit_one", "query": "hybrid retrieval ranking", "knowledge_limit": 1},
|
|
140
|
+
],
|
|
141
|
+
# The recent-conversation transcript as the product actually builds it:
|
|
142
|
+
# `chat.py` prepends `build_recent_chat_context(...)` on every non-hybrid
|
|
143
|
+
# turn, and the agent loop injects the same text each executor step. This
|
|
144
|
+
# is the live twin of `lattice_retrieval::history::recent_chat`.
|
|
145
|
+
"recent_chat": [
|
|
146
|
+
{"key": "everything"},
|
|
147
|
+
{"key": "conversation", "conversation_id": "conv-a"},
|
|
148
|
+
{"key": "conversation_missing", "conversation_id": "nope"},
|
|
149
|
+
{"key": "personal_workspace", "workspace_id": "personal", "limit": 6},
|
|
150
|
+
{"key": "workspace_alpha", "workspace_id": WS_ALPHA},
|
|
151
|
+
{"key": "user_scoped", "user_email": "jiwon@lattice.ai", "limit": 5},
|
|
152
|
+
{"key": "user_and_conversation", "user_email": "jiwon@lattice.ai", "conversation_id": "conv-a"},
|
|
153
|
+
{"key": "limit_one", "limit": 1},
|
|
154
|
+
{"key": "limit_zero", "limit": 0},
|
|
155
|
+
{"key": "image_replies_dropped", "images": False},
|
|
156
|
+
],
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
CONTEXT_RUNNERS: Dict[str, Callable[..., Any]] = {
|
|
160
|
+
"context_assemble": _run_context_assemble,
|
|
161
|
+
"recent_chat": _run_recent_chat,
|
|
162
|
+
}
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
"specific, not less."
|
|
22
22
|
],
|
|
23
23
|
|
|
24
|
+
"11.5.2": {
|
|
25
|
+
"_why": [
|
|
26
|
+
"Tight Ship release: deletions, consolidation and front-door fixes —",
|
|
27
|
+
"backend only. The frontend bundle is untouched; no captured screen",
|
|
28
|
+
"changes on purpose. All thirteen measured but not gated. Known",
|
|
29
|
+
"jitter: 05-memory-graph moves ~2-3% between captures from the",
|
|
30
|
+
"cytoscape layout (11.3.0-11.5.1 observed)."
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
|
|
24
34
|
"11.5.1": {
|
|
25
35
|
"_why": [
|
|
26
36
|
"Rust Full Loop release: agent-loop orchestrator port, worker seams,",
|
package/src-tauri/Cargo.lock
CHANGED
|
@@ -1730,7 +1730,7 @@ dependencies = [
|
|
|
1730
1730
|
|
|
1731
1731
|
[[package]]
|
|
1732
1732
|
name = "lattice-agent"
|
|
1733
|
-
version = "11.5.
|
|
1733
|
+
version = "11.5.2"
|
|
1734
1734
|
dependencies = [
|
|
1735
1735
|
"axum",
|
|
1736
1736
|
"base64 0.22.1",
|
|
@@ -1746,7 +1746,7 @@ dependencies = [
|
|
|
1746
1746
|
|
|
1747
1747
|
[[package]]
|
|
1748
1748
|
name = "lattice-ai-desktop"
|
|
1749
|
-
version = "11.5.
|
|
1749
|
+
version = "11.5.2"
|
|
1750
1750
|
dependencies = [
|
|
1751
1751
|
"lattice-host",
|
|
1752
1752
|
"lattice-jobs",
|
|
@@ -1761,7 +1761,7 @@ dependencies = [
|
|
|
1761
1761
|
|
|
1762
1762
|
[[package]]
|
|
1763
1763
|
name = "lattice-core"
|
|
1764
|
-
version = "11.5.
|
|
1764
|
+
version = "11.5.2"
|
|
1765
1765
|
dependencies = [
|
|
1766
1766
|
"blake2",
|
|
1767
1767
|
"rusqlite",
|
|
@@ -1771,7 +1771,7 @@ dependencies = [
|
|
|
1771
1771
|
|
|
1772
1772
|
[[package]]
|
|
1773
1773
|
name = "lattice-host"
|
|
1774
|
-
version = "11.5.
|
|
1774
|
+
version = "11.5.2"
|
|
1775
1775
|
dependencies = [
|
|
1776
1776
|
"axum",
|
|
1777
1777
|
"lattice-agent",
|
|
@@ -1788,7 +1788,7 @@ dependencies = [
|
|
|
1788
1788
|
|
|
1789
1789
|
[[package]]
|
|
1790
1790
|
name = "lattice-ingest"
|
|
1791
|
-
version = "11.5.
|
|
1791
|
+
version = "11.5.2"
|
|
1792
1792
|
dependencies = [
|
|
1793
1793
|
"axum",
|
|
1794
1794
|
"lattice-core",
|
|
@@ -1801,7 +1801,7 @@ dependencies = [
|
|
|
1801
1801
|
|
|
1802
1802
|
[[package]]
|
|
1803
1803
|
name = "lattice-jobs"
|
|
1804
|
-
version = "11.5.
|
|
1804
|
+
version = "11.5.2"
|
|
1805
1805
|
dependencies = [
|
|
1806
1806
|
"axum",
|
|
1807
1807
|
"lattice-core",
|
|
@@ -1813,7 +1813,7 @@ dependencies = [
|
|
|
1813
1813
|
|
|
1814
1814
|
[[package]]
|
|
1815
1815
|
name = "lattice-retrieval"
|
|
1816
|
-
version = "11.5.
|
|
1816
|
+
version = "11.5.2"
|
|
1817
1817
|
dependencies = [
|
|
1818
1818
|
"axum",
|
|
1819
1819
|
"fancy-regex",
|
package/src-tauri/Cargo.toml
CHANGED
package/src-tauri/src/backend.rs
CHANGED
|
@@ -72,6 +72,9 @@ pub struct BackendStatus {
|
|
|
72
72
|
/// Whether the background jobs timer is running in this process
|
|
73
73
|
/// (11.5.0, additive).
|
|
74
74
|
pub jobs_running: bool,
|
|
75
|
+
/// Why the front door is not there, when this shell was supposed to serve
|
|
76
|
+
/// one and could not bind it (11.5.2, additive). `null` normally.
|
|
77
|
+
pub gateway_error: Option<String>,
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
/// The gateway this shell is serving, and how to stop it.
|
|
@@ -89,6 +92,11 @@ pub struct DesktopBackend {
|
|
|
89
92
|
gateway: Mutex<Option<RunningGateway>>,
|
|
90
93
|
/// The jobs scheduler, mounted on `/host/jobs` in the gateway topology.
|
|
91
94
|
jobs: Option<Arc<lattice_jobs::Scheduler>>,
|
|
95
|
+
/// Why the front door is not there, when it could not be bound.
|
|
96
|
+
///
|
|
97
|
+
/// `None` means "nothing went wrong" — including in the topologies that
|
|
98
|
+
/// serve no gateway at all, where the worker *is* the front door.
|
|
99
|
+
gateway_error: Mutex<Option<String>>,
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
impl DesktopBackend {
|
|
@@ -116,6 +124,7 @@ impl DesktopBackend {
|
|
|
116
124
|
health_deadline,
|
|
117
125
|
gateway: Mutex::new(None),
|
|
118
126
|
jobs,
|
|
127
|
+
gateway_error: Mutex::new(None),
|
|
119
128
|
})
|
|
120
129
|
}
|
|
121
130
|
|
|
@@ -139,6 +148,18 @@ impl DesktopBackend {
|
|
|
139
148
|
self.plan.topology
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
/// Why the front door is unavailable, when it is.
|
|
152
|
+
///
|
|
153
|
+
/// `Some` only when this shell was supposed to serve the gateway and could
|
|
154
|
+
/// not bind it — in which case [`Self::app_url`] names a port nothing will
|
|
155
|
+
/// ever listen on, and `main::boot` must not navigate the window into it.
|
|
156
|
+
pub fn gateway_error(&self) -> Option<String> {
|
|
157
|
+
self.gateway_error
|
|
158
|
+
.lock()
|
|
159
|
+
.expect("gateway error lock")
|
|
160
|
+
.clone()
|
|
161
|
+
}
|
|
162
|
+
|
|
142
163
|
/// Start the front door, then the worker.
|
|
143
164
|
///
|
|
144
165
|
/// The gateway comes up first on purpose: it is what the webview polls, and
|
|
@@ -171,10 +192,16 @@ impl DesktopBackend {
|
|
|
171
192
|
let listener = match bind_loopback(addr).await {
|
|
172
193
|
Ok(listener) => listener,
|
|
173
194
|
Err(err) => {
|
|
174
|
-
|
|
175
|
-
"
|
|
195
|
+
let message = format!(
|
|
196
|
+
"the gateway could not bind {addr}: {err} — \
|
|
176
197
|
set LATTICEAI_DESKTOP_DIRECT=1 to run the worker as the front door"
|
|
177
198
|
);
|
|
199
|
+
eprintln!("lattice-ai-desktop: {message}");
|
|
200
|
+
// Recorded, not only logged: `plan.origin` names the port that
|
|
201
|
+
// just failed to bind, so navigating the window there would
|
|
202
|
+
// land on a connection refused with no explanation. The window
|
|
203
|
+
// stays on the bundled shell and reads this instead.
|
|
204
|
+
*self.gateway_error.lock().expect("gateway error lock") = Some(message);
|
|
178
205
|
return;
|
|
179
206
|
}
|
|
180
207
|
};
|
|
@@ -310,6 +337,7 @@ impl DesktopBackend {
|
|
|
310
337
|
.as_ref()
|
|
311
338
|
.map(|scheduler| scheduler.is_running())
|
|
312
339
|
.unwrap_or(false),
|
|
340
|
+
gateway_error: self.gateway_error(),
|
|
313
341
|
}
|
|
314
342
|
}
|
|
315
343
|
|
|
@@ -457,4 +485,50 @@ mod tests {
|
|
|
457
485
|
shell.stop_gateway();
|
|
458
486
|
shell.stop_gateway();
|
|
459
487
|
}
|
|
488
|
+
|
|
489
|
+
/// A shell that never had to bind a front door has nothing to report, and
|
|
490
|
+
/// its origin is navigable.
|
|
491
|
+
#[test]
|
|
492
|
+
fn a_healthy_shell_names_no_gateway_error() {
|
|
493
|
+
for probe in [
|
|
494
|
+
StaticProbe::new().with_env(PORT_ENV, "41844"),
|
|
495
|
+
StaticProbe::new().with_env(DIRECT_ENV, "1"),
|
|
496
|
+
] {
|
|
497
|
+
let shell = backend(probe);
|
|
498
|
+
assert_eq!(shell.gateway_error(), None);
|
|
499
|
+
let value =
|
|
500
|
+
serde_json::to_value(shell.render(&WorkerStatus::idle(4825, true))).expect("json");
|
|
501
|
+
assert!(value["gateway_error"].is_null());
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/// Binding the front door is the one failure that makes `app_url` name a
|
|
506
|
+
/// port nothing will ever answer on. The window must read the reason rather
|
|
507
|
+
/// than navigate into it.
|
|
508
|
+
#[tokio::test]
|
|
509
|
+
async fn a_gateway_that_cannot_bind_is_reported_and_not_navigated_to() {
|
|
510
|
+
// Hold the port the shell will try to bind.
|
|
511
|
+
let occupied = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
|
|
512
|
+
let port = occupied.local_addr().expect("addr").port();
|
|
513
|
+
let shell = backend(StaticProbe::new().with_env(PORT_ENV, &port.to_string()));
|
|
514
|
+
assert_eq!(shell.topology(), Topology::Gateway);
|
|
515
|
+
|
|
516
|
+
shell.start_gateway().await;
|
|
517
|
+
|
|
518
|
+
let reason = shell.gateway_error().expect("the failure is recorded");
|
|
519
|
+
assert!(reason.contains("could not bind"), "{reason}");
|
|
520
|
+
assert!(
|
|
521
|
+
reason.contains("LATTICEAI_DESKTOP_DIRECT"),
|
|
522
|
+
"the way out is named: {reason}"
|
|
523
|
+
);
|
|
524
|
+
assert!(
|
|
525
|
+
shell.app_url().contains(&port.to_string()),
|
|
526
|
+
"app_url still names the port nothing bound — which is why boot must \
|
|
527
|
+
read gateway_error instead of navigating"
|
|
528
|
+
);
|
|
529
|
+
let value =
|
|
530
|
+
serde_json::to_value(shell.render(&WorkerStatus::idle(port, true))).expect("serialise");
|
|
531
|
+
assert_eq!(value["gateway_error"], reason);
|
|
532
|
+
drop(occupied);
|
|
533
|
+
}
|
|
460
534
|
}
|
package/src-tauri/src/main.rs
CHANGED
|
@@ -82,9 +82,19 @@ fn boot(app: &tauri::App) {
|
|
|
82
82
|
// Waiting in both places would double the worst case.
|
|
83
83
|
shell.wait_until_serving().await;
|
|
84
84
|
}
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
85
|
+
// One reason not to navigate, and it is not "the worker is slow": if
|
|
86
|
+
// this shell was supposed to serve the front door and could not bind
|
|
87
|
+
// it, `app_url` names a port nothing will ever listen on, and sending
|
|
88
|
+
// the window there replaces a readable failure with a blank "cannot
|
|
89
|
+
// connect". The bundled shell stays up and reads `gateway_error` out of
|
|
90
|
+
// `desktopBackendStatus` instead.
|
|
91
|
+
if let Some(reason) = shell.gateway_error() {
|
|
92
|
+
eprintln!("lattice-ai-desktop: staying on the bundled shell — {reason}");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Otherwise navigate either way, as the shell always has: a window
|
|
96
|
+
// showing the browser's own "cannot connect" is more use than a window
|
|
97
|
+
// showing nothing while a worker that will never arrive is waited for.
|
|
88
98
|
if let Ok(url) = tauri::Url::parse(&shell.app_url()) {
|
|
89
99
|
let _ = window.navigate(url);
|
|
90
100
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://schema.tauri.app/config/2",
|
|
3
3
|
"productName": "Lattice AI",
|
|
4
|
-
"version": "11.5.
|
|
4
|
+
"version": "11.5.2",
|
|
5
5
|
"identifier": "ai.lattice.desktop",
|
|
6
6
|
"build": {
|
|
7
7
|
"beforeDevCommand": "npm run frontend:dev",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
"security": {
|
|
25
|
-
"csp": "default-src 'self' asset: tauri: http://127.0.0.1:*; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' asset: data: blob: http://127.0.0.1:*; font-src 'self' data:; connect-src 'self' ipc: http://127.0.0.1
|
|
25
|
+
"csp": "default-src 'self' asset: tauri: http://127.0.0.1:*; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' asset: data: blob: http://127.0.0.1:*; font-src 'self' data:; connect-src 'self' ipc: http://127.0.0.1:*; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'self'",
|
|
26
26
|
"devCsp": "default-src 'self' http://127.0.0.1:* ws://127.0.0.1:*; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http://127.0.0.1:*; font-src 'self' data:; connect-src 'self' http://127.0.0.1:* ws://127.0.0.1:*; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'self'"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "11.5.
|
|
2
|
+
"version": "11.5.2",
|
|
3
3
|
"generated_at": "vite",
|
|
4
4
|
"entrypoints": {
|
|
5
|
-
"app": "/static/app/assets/index-
|
|
5
|
+
"app": "/static/app/assets/index-BMPdTmlY.js"
|
|
6
6
|
},
|
|
7
7
|
"assets": {
|
|
8
8
|
"../node_modules/@tauri-apps/api/core.js": "/static/app/assets/core-CwxXejkd.js",
|
|
9
|
-
"_BrainSignals-
|
|
10
|
-
"_LivingBrain-
|
|
11
|
-
"_ReviewCard-
|
|
12
|
-
"_arrow-left-
|
|
13
|
-
"_bot-
|
|
14
|
-
"_brain-
|
|
15
|
-
"_button-
|
|
16
|
-
"_circle-check-
|
|
17
|
-
"_circle-pause-
|
|
18
|
-
"_circle-play-
|
|
19
|
-
"_cpu-
|
|
20
|
-
"_download-
|
|
21
|
-
"_folder-open-
|
|
22
|
-
"_hard-drive-
|
|
23
|
-
"_input-
|
|
24
|
-
"_link-2-
|
|
25
|
-
"_permissionCopy-
|
|
26
|
-
"_primitives-
|
|
27
|
-
"_search-
|
|
28
|
-
"_share-2-
|
|
29
|
-
"_shield-alert-
|
|
30
|
-
"_textarea-
|
|
31
|
-
"_useFocusTrap-
|
|
32
|
-
"_useMutation-
|
|
33
|
-
"_useQuery-
|
|
34
|
-
"_utils-
|
|
35
|
-
"_workspace-
|
|
36
|
-
"index.html": "/static/app/assets/index-
|
|
9
|
+
"_BrainSignals-DS9BtKOW.js": "/static/app/assets/BrainSignals-DS9BtKOW.js",
|
|
10
|
+
"_LivingBrain-GzenJchP.js": "/static/app/assets/LivingBrain-GzenJchP.js",
|
|
11
|
+
"_ReviewCard-CNZ7XjWG.js": "/static/app/assets/ReviewCard-CNZ7XjWG.js",
|
|
12
|
+
"_arrow-left-kfsrk0mv.js": "/static/app/assets/arrow-left-kfsrk0mv.js",
|
|
13
|
+
"_bot-B_K1Tdmw.js": "/static/app/assets/bot-B_K1Tdmw.js",
|
|
14
|
+
"_brain-DWyaV1L1.js": "/static/app/assets/brain-DWyaV1L1.js",
|
|
15
|
+
"_button-aTn4s84A.js": "/static/app/assets/button-aTn4s84A.js",
|
|
16
|
+
"_circle-check-qqLug9nU.js": "/static/app/assets/circle-check-qqLug9nU.js",
|
|
17
|
+
"_circle-pause-xKgeGXkT.js": "/static/app/assets/circle-pause-xKgeGXkT.js",
|
|
18
|
+
"_circle-play-DkT6tYPX.js": "/static/app/assets/circle-play-DkT6tYPX.js",
|
|
19
|
+
"_cpu-85xYObUC.js": "/static/app/assets/cpu-85xYObUC.js",
|
|
20
|
+
"_download-B5Fm7YXo.js": "/static/app/assets/download-B5Fm7YXo.js",
|
|
21
|
+
"_folder-open-kk2Xa52u.js": "/static/app/assets/folder-open-kk2Xa52u.js",
|
|
22
|
+
"_hard-drive-DkA3zBW_.js": "/static/app/assets/hard-drive-DkA3zBW_.js",
|
|
23
|
+
"_input-B0nRf2jO.js": "/static/app/assets/input-B0nRf2jO.js",
|
|
24
|
+
"_link-2-Dwb4gnTc.js": "/static/app/assets/link-2-Dwb4gnTc.js",
|
|
25
|
+
"_permissionCopy-CQDUBrOZ.js": "/static/app/assets/permissionCopy-CQDUBrOZ.js",
|
|
26
|
+
"_primitives-SNp0LRJz.js": "/static/app/assets/primitives-SNp0LRJz.js",
|
|
27
|
+
"_search-BcHqkjoy.js": "/static/app/assets/search-BcHqkjoy.js",
|
|
28
|
+
"_share-2-BsrxFglO.js": "/static/app/assets/share-2-BsrxFglO.js",
|
|
29
|
+
"_shield-alert-5BStfp2_.js": "/static/app/assets/shield-alert-5BStfp2_.js",
|
|
30
|
+
"_textarea-Cg8IUA-k.js": "/static/app/assets/textarea-Cg8IUA-k.js",
|
|
31
|
+
"_useFocusTrap-CYKvE46M.js": "/static/app/assets/useFocusTrap-CYKvE46M.js",
|
|
32
|
+
"_useMutation-CSn9t1op.js": "/static/app/assets/useMutation-CSn9t1op.js",
|
|
33
|
+
"_useQuery-CY2OI2uy.js": "/static/app/assets/useQuery-CY2OI2uy.js",
|
|
34
|
+
"_utils-Ddol2RWD.js": "/static/app/assets/utils-Ddol2RWD.js",
|
|
35
|
+
"_workspace-BqDwOz_p.js": "/static/app/assets/workspace-BqDwOz_p.js",
|
|
36
|
+
"index.html": "/static/app/assets/index-BMPdTmlY.js",
|
|
37
37
|
"assets/index-DxmOfNRi.css": "/static/app/assets/index-DxmOfNRi.css",
|
|
38
|
-
"src/components/ProductFlow.tsx": "/static/app/assets/ProductFlow-
|
|
39
|
-
"src/features/admin/AdminConsole.tsx": "/static/app/assets/AdminConsole-
|
|
40
|
-
"src/features/brain/BrainHome.tsx": "/static/app/assets/BrainHome-
|
|
41
|
-
"src/features/command/CommandPalette.tsx": "/static/app/assets/CommandPalette-
|
|
42
|
-
"src/pages/Act.tsx": "/static/app/assets/Act-
|
|
43
|
-
"src/pages/Brain.tsx": "/static/app/assets/Brain-
|
|
44
|
-
"src/pages/Capture.tsx": "/static/app/assets/Capture-
|
|
45
|
-
"src/pages/Chronicle.tsx": "/static/app/assets/Chronicle-
|
|
46
|
-
"src/pages/Library.tsx": "/static/app/assets/Library-
|
|
47
|
-
"src/pages/System.tsx": "/static/app/assets/System-
|
|
38
|
+
"src/components/ProductFlow.tsx": "/static/app/assets/ProductFlow-DEP6-vML.js",
|
|
39
|
+
"src/features/admin/AdminConsole.tsx": "/static/app/assets/AdminConsole-cf4npybT.js",
|
|
40
|
+
"src/features/brain/BrainHome.tsx": "/static/app/assets/BrainHome-Qm8eaztx.js",
|
|
41
|
+
"src/features/command/CommandPalette.tsx": "/static/app/assets/CommandPalette-Bqhm0Urn.js",
|
|
42
|
+
"src/pages/Act.tsx": "/static/app/assets/Act-DcQizkl1.js",
|
|
43
|
+
"src/pages/Brain.tsx": "/static/app/assets/Brain-3VCSHFcn.js",
|
|
44
|
+
"src/pages/Capture.tsx": "/static/app/assets/Capture-DiQ219jW.js",
|
|
45
|
+
"src/pages/Chronicle.tsx": "/static/app/assets/Chronicle-BGvuAchH.js",
|
|
46
|
+
"src/pages/Library.tsx": "/static/app/assets/Library-BV6NnF0a.js",
|
|
47
|
+
"src/pages/System.tsx": "/static/app/assets/System-CieofHQa.js"
|
|
48
48
|
}
|
|
49
49
|
}
|