ltcai 3.4.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -247
- package/docs/CARRYOVER_AUDIT_v3.6.0.md +61 -0
- package/docs/CHANGELOG.md +32 -0
- package/docs/HANDOVER_v3.6.0.md +46 -0
- package/docs/RUNTIME_HOOK_COVERAGE_v3.5.0.md +56 -0
- package/docs/RUNTIME_HOOK_COVERAGE_v3.6.0.md +49 -0
- package/docs/architecture.md +13 -12
- package/docs/kg-schema.md +55 -0
- package/docs/privacy.md +18 -2
- package/docs/security-model.md +17 -0
- package/kg_schema.py +46 -0
- package/knowledge_graph.py +520 -1
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/auth.py +37 -9
- package/latticeai/api/browser.py +217 -0
- package/latticeai/api/chat.py +4 -1
- package/latticeai/api/computer_use.py +21 -8
- package/latticeai/api/portability.py +93 -0
- package/latticeai/api/tools.py +29 -26
- package/latticeai/core/config.py +3 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/oidc.py +205 -0
- package/latticeai/core/security.py +59 -5
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +39 -0
- package/latticeai/services/ingestion.py +271 -0
- package/latticeai/services/kg_portability.py +177 -0
- package/package.json +5 -4
- package/requirements.txt +1 -0
- package/scripts/build_vsix.mjs +72 -0
- package/scripts/check_python.py +87 -0
- package/static/css/reference/account.css +1 -1
- package/static/css/reference/admin.css +1 -1
- package/static/css/reference/base.css +8 -5
- package/static/css/reference/chat.css +8 -8
- package/static/css/reference/graph.css +2 -2
- package/static/css/responsive.css +2 -2
- package/static/v3/asset-manifest.json +9 -9
- package/static/v3/css/{lattice.shell.6ceea7c8.css → lattice.shell.8fcc9d33.css} +2 -1
- package/static/v3/css/lattice.shell.css +2 -1
- package/static/v3/js/{app.d086489d.js → app.c541f955.js} +1 -1
- package/static/v3/js/core/{api.12b568ad.js → api.33d6320e.js} +38 -0
- package/static/v3/js/core/api.js +38 -0
- package/static/v3/js/core/{routes.d214b399.js → routes.2ce3815a.js} +1 -1
- package/static/v3/js/core/routes.js +1 -1
- package/static/v3/js/core/{shell.d05266f5.js → shell.8c163e0e.js} +2 -2
- package/static/v3/js/views/knowledge-graph.a96040a5.js +513 -0
- package/static/v3/js/views/knowledge-graph.js +293 -17
- package/static/workspace.css +1 -1
- package/tools/__init__.py +276 -0
- package/tools/commands.py +188 -0
- package/tools/computer.py +185 -0
- package/tools/documents.py +243 -0
- package/tools/filesystem.py +560 -0
- package/tools/knowledge.py +97 -0
- package/tools/local_files.py +69 -0
- package/tools/network.py +66 -0
- package/static/v3/js/views/knowledge-graph.a14ea7e7.js +0 -237
- package/tools.py +0 -1525
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Knowledge Graph portability — local export / import / backup / restore.
|
|
2
|
+
|
|
3
|
+
The Knowledge Graph is the user's durable asset, so it must be portable without
|
|
4
|
+
any cloud service. Two complementary mechanisms, both fully local:
|
|
5
|
+
|
|
6
|
+
* **Logical export/import** (JSON): nodes/edges/chunks/sources/provenance with a
|
|
7
|
+
versioned header (schema + projection + embed-dim). Re-embeds on import, so it
|
|
8
|
+
is portable across machines.
|
|
9
|
+
* **Binary backup/restore** (ZIP): a faithful snapshot of the SQLite DB (incl.
|
|
10
|
+
vector embeddings) plus the blob directory, integrity-checked, for
|
|
11
|
+
same-machine recovery.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import json
|
|
18
|
+
import shutil
|
|
19
|
+
import tempfile
|
|
20
|
+
import zipfile
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, Optional
|
|
24
|
+
|
|
25
|
+
FORMAT = "latticeai.kg.export"
|
|
26
|
+
FORMAT_VERSION = 1
|
|
27
|
+
BACKUP_FORMAT = "latticeai.kg.backup"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _now_iso() -> str:
|
|
31
|
+
return datetime.now(timezone.utc).isoformat()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _stamp() -> str:
|
|
35
|
+
return _now_iso().replace(":", "").replace("-", "").replace(".", "")[:15]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _sha256_file(path: Path) -> str:
|
|
39
|
+
h = hashlib.sha256()
|
|
40
|
+
with open(path, "rb") as fh:
|
|
41
|
+
for block in iter(lambda: fh.read(65536), b""):
|
|
42
|
+
h.update(block)
|
|
43
|
+
return h.hexdigest()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class KGPortabilityService:
|
|
47
|
+
def __init__(self, *, knowledge_graph: Any, data_dir, enable_graph: bool = True) -> None:
|
|
48
|
+
self._kg = knowledge_graph
|
|
49
|
+
self._data_dir = Path(data_dir)
|
|
50
|
+
self._enable = bool(enable_graph)
|
|
51
|
+
self._exports_dir = self._data_dir / "workspace_exports"
|
|
52
|
+
|
|
53
|
+
def available(self) -> bool:
|
|
54
|
+
return self._enable and self._kg is not None
|
|
55
|
+
|
|
56
|
+
def _require(self) -> None:
|
|
57
|
+
if not self.available():
|
|
58
|
+
raise RuntimeError("Knowledge Graph is disabled (LATTICEAI_ENABLE_GRAPH).")
|
|
59
|
+
|
|
60
|
+
# ── logical export / import ──────────────────────────────────────────────
|
|
61
|
+
def export(self, *, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
62
|
+
self._require()
|
|
63
|
+
data = self._kg.export_graph_data()
|
|
64
|
+
header = {
|
|
65
|
+
"format": FORMAT,
|
|
66
|
+
"format_version": FORMAT_VERSION,
|
|
67
|
+
**self._kg.schema_versions(),
|
|
68
|
+
"exported_at": _now_iso(),
|
|
69
|
+
"workspace_id": workspace_id,
|
|
70
|
+
"counts": data.get("counts"),
|
|
71
|
+
}
|
|
72
|
+
return {"header": header, **data}
|
|
73
|
+
|
|
74
|
+
def export_to_file(self, path=None, *, workspace_id: Optional[str] = None) -> Dict[str, Any]:
|
|
75
|
+
artifact = self.export(workspace_id=workspace_id)
|
|
76
|
+
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
path = Path(path) if path else self._exports_dir / f"kg-export-{_stamp()}.json"
|
|
78
|
+
path.write_text(json.dumps(artifact, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
79
|
+
return {"path": str(path), "header": artifact["header"], "bytes": path.stat().st_size}
|
|
80
|
+
|
|
81
|
+
def import_data(self, artifact: Dict[str, Any], *, mode: str = "merge", dry_run: bool = False) -> Dict[str, Any]:
|
|
82
|
+
self._require()
|
|
83
|
+
if not isinstance(artifact, dict) or "nodes" not in artifact:
|
|
84
|
+
raise ValueError("Invalid Knowledge Graph export artifact.")
|
|
85
|
+
if mode not in ("merge", "replace"):
|
|
86
|
+
raise ValueError("mode must be 'merge' or 'replace'.")
|
|
87
|
+
result = self._kg.import_graph_data(artifact, mode=mode, dry_run=dry_run)
|
|
88
|
+
result["header"] = artifact.get("header")
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
def import_from_file(self, path, *, mode: str = "merge", dry_run: bool = False) -> Dict[str, Any]:
|
|
92
|
+
artifact = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
93
|
+
return self.import_data(artifact, mode=mode, dry_run=dry_run)
|
|
94
|
+
|
|
95
|
+
# ── binary backup / restore ──────────────────────────────────────────────
|
|
96
|
+
def backup(self, dest_path=None) -> Dict[str, Any]:
|
|
97
|
+
self._require()
|
|
98
|
+
self._exports_dir.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
dest = Path(dest_path) if dest_path else self._exports_dir / f"kg-backup-{_stamp()}.zip"
|
|
100
|
+
with tempfile.TemporaryDirectory() as tmp_s:
|
|
101
|
+
tmp = Path(tmp_s)
|
|
102
|
+
db_copy = tmp / "knowledge_graph.sqlite"
|
|
103
|
+
self._kg.backup_database(db_copy)
|
|
104
|
+
manifest = {
|
|
105
|
+
"format": BACKUP_FORMAT,
|
|
106
|
+
"format_version": FORMAT_VERSION,
|
|
107
|
+
**self._kg.schema_versions(),
|
|
108
|
+
"created_at": _now_iso(),
|
|
109
|
+
"db_sha256": _sha256_file(db_copy),
|
|
110
|
+
"has_blobs": Path(self._kg.blob_dir).exists(),
|
|
111
|
+
}
|
|
112
|
+
with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
113
|
+
zf.write(db_copy, "knowledge_graph.sqlite")
|
|
114
|
+
zf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2))
|
|
115
|
+
blob_dir = Path(self._kg.blob_dir)
|
|
116
|
+
if blob_dir.exists():
|
|
117
|
+
for f in blob_dir.rglob("*"):
|
|
118
|
+
if f.is_file():
|
|
119
|
+
zf.write(f, f"blobs/{f.relative_to(blob_dir)}")
|
|
120
|
+
return {"path": str(dest), "bytes": dest.stat().st_size, "manifest": manifest}
|
|
121
|
+
|
|
122
|
+
def restore(self, archive_path, *, verify: bool = True) -> Dict[str, Any]:
|
|
123
|
+
self._require()
|
|
124
|
+
archive = Path(archive_path)
|
|
125
|
+
if not archive.exists():
|
|
126
|
+
raise FileNotFoundError(f"Backup archive not found: {archive}")
|
|
127
|
+
with zipfile.ZipFile(archive) as zf:
|
|
128
|
+
names = zf.namelist()
|
|
129
|
+
if "knowledge_graph.sqlite" not in names:
|
|
130
|
+
raise ValueError("Archive is missing knowledge_graph.sqlite.")
|
|
131
|
+
manifest = json.loads(zf.read("manifest.json")) if "manifest.json" in names else {}
|
|
132
|
+
with tempfile.TemporaryDirectory() as tmp_s:
|
|
133
|
+
tmp = Path(tmp_s)
|
|
134
|
+
zf.extractall(tmp)
|
|
135
|
+
db_src = tmp / "knowledge_graph.sqlite"
|
|
136
|
+
if verify and manifest.get("db_sha256"):
|
|
137
|
+
if _sha256_file(db_src) != manifest["db_sha256"]:
|
|
138
|
+
raise ValueError("Backup integrity check failed (db sha256 mismatch).")
|
|
139
|
+
db_dest = Path(self._kg.db_path)
|
|
140
|
+
blob_dest = Path(self._kg.blob_dir)
|
|
141
|
+
db_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
# Drop the live DB + stale WAL/SHM siblings so the restored copy
|
|
143
|
+
# is authoritative (no stale journal overlaying old pages).
|
|
144
|
+
for sib in (db_dest, Path(str(db_dest) + "-wal"), Path(str(db_dest) + "-shm")):
|
|
145
|
+
if sib.exists():
|
|
146
|
+
sib.unlink()
|
|
147
|
+
shutil.copyfile(db_src, db_dest)
|
|
148
|
+
blob_src = tmp / "blobs"
|
|
149
|
+
if blob_src.exists():
|
|
150
|
+
if blob_dest.exists():
|
|
151
|
+
shutil.rmtree(blob_dest)
|
|
152
|
+
shutil.copytree(blob_src, blob_dest)
|
|
153
|
+
else:
|
|
154
|
+
blob_dest.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
stats = self._kg.stats()
|
|
156
|
+
return {
|
|
157
|
+
"restored": True,
|
|
158
|
+
"manifest": manifest,
|
|
159
|
+
"nodes": sum(stats.get("nodes", {}).values()),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# ── status surface ───────────────────────────────────────────────────────
|
|
163
|
+
def snapshot_metadata(self) -> Dict[str, Any]:
|
|
164
|
+
if not self.available():
|
|
165
|
+
return {"available": False}
|
|
166
|
+
return {
|
|
167
|
+
"available": True,
|
|
168
|
+
**self._kg.schema_versions(),
|
|
169
|
+
"stats": self._kg.stats(),
|
|
170
|
+
"provenance": self._kg.provenance_stats(),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
def recent_ingestions(self, *, limit: int = 50, source_type: Optional[str] = None) -> Dict[str, Any]:
|
|
174
|
+
"""Recent provenance records (newest first) for the ingestion-sources UI."""
|
|
175
|
+
if not self.available():
|
|
176
|
+
return {"items": [], "count": 0}
|
|
177
|
+
return self._kg.list_provenance(limit=limit, source_type=source_type)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Lattice AI v3 local-first AI workspace platform with knowledge graph, vector index, hybrid search, agents, and workspace modes.",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"build": "npm run build:assets && npm run build:python",
|
|
21
21
|
"build:assets": "node scripts/build_v3_assets.mjs",
|
|
22
22
|
"build:python": "python3 -m build",
|
|
23
|
-
"check:python": "python3
|
|
23
|
+
"check:python": "python3 scripts/check_python.py",
|
|
24
24
|
"lint": "node --check static/scripts/account.js && node --check static/scripts/admin.js && node --check static/scripts/chat.js && node --check static/scripts/graph.js && node --check static/scripts/platform.js && node --check static/scripts/ux.js && node --check static/scripts/workspace.js && node --check tests/visual/mock_server.cjs && node --check tests/visual/v3.spec.js && npm run lint:v3",
|
|
25
25
|
"lint:v3": "node scripts/lint_v3.mjs",
|
|
26
26
|
"typecheck": "cd vscode-extension && npm run build",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"capture:skills": "node scripts/capture/capture_skills.js",
|
|
34
34
|
"capture:enterprise": "node scripts/capture/capture_enterprise.js",
|
|
35
35
|
"capture:onboarding": "node scripts/capture/capture_onboarding.js",
|
|
36
|
-
"
|
|
36
|
+
"package:vsix": "node scripts/build_vsix.mjs",
|
|
37
|
+
"release:artifacts": "npm run build:assets && npm run build:python && npm pack && npm run package:vsix",
|
|
37
38
|
"release:validate": "python3 scripts/validate_release_artifacts.py $npm_package_version --require-vsix --require-tgz",
|
|
38
39
|
"publish:npm": "npm pack && npm publish ltcai-$npm_package_version.tgz --access public",
|
|
39
40
|
"publish:pypi": "npm run build:python && python3 -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl",
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
"llm_router.py",
|
|
71
72
|
"p_reinforce.py",
|
|
72
73
|
"telegram_bot.py",
|
|
73
|
-
"tools
|
|
74
|
+
"tools/",
|
|
74
75
|
"codex_telegram_bot.py",
|
|
75
76
|
"mcp_registry.py",
|
|
76
77
|
"latticeai/**/*.py",
|
package/requirements.txt
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build the VS Code extension VSIX into `dist/ltcai-<version>.vsix`.
|
|
4
|
+
*
|
|
5
|
+
* Why a script instead of an inline `cd vscode-extension && npm run package:vsix`:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Single version source.** The output name is derived from the ROOT
|
|
8
|
+
* `package.json` version — the same value `release:validate` checks. The old
|
|
9
|
+
* inline form used the extension's own `$npm_package_version`, so a version
|
|
10
|
+
* drift between root and extension produced a `dist/ltcai-<ext>.vsix` that the
|
|
11
|
+
* validator (run from root) reported as a *missing* `dist/ltcai-<root>.vsix`.
|
|
12
|
+
* 2. **Fresh-checkout / CI safe.** It installs the extension's toolchain
|
|
13
|
+
* (`tsc`, `vsce`) when `node_modules` is absent, so the artifact builds on a
|
|
14
|
+
* clean clone — not only on a warmed-up dev tree.
|
|
15
|
+
* 3. **Fails loudly.** It verifies the compiled entrypoint and the final VSIX
|
|
16
|
+
* exist, exiting non-zero otherwise, so a skipped compile can't yield a
|
|
17
|
+
* silently-empty or missing artifact.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors the tag-driven `.github/workflows/release.yml` VSIX step.
|
|
20
|
+
*/
|
|
21
|
+
import { execFileSync } from "node:child_process";
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
23
|
+
import { dirname, join, resolve } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
27
|
+
const extDir = join(repoRoot, "vscode-extension");
|
|
28
|
+
const distDir = join(repoRoot, "dist");
|
|
29
|
+
|
|
30
|
+
const version = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")).version;
|
|
31
|
+
const extVersion = JSON.parse(readFileSync(join(extDir, "package.json"), "utf8")).version;
|
|
32
|
+
if (extVersion !== version) {
|
|
33
|
+
console.error(
|
|
34
|
+
`build_vsix: version drift — root package.json is ${version} but ` +
|
|
35
|
+
`vscode-extension/package.json is ${extVersion}. Bump both to the same value.`
|
|
36
|
+
);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const outFile = join(distDir, `ltcai-${version}.vsix`);
|
|
41
|
+
const binExt = process.platform === "win32" ? ".cmd" : "";
|
|
42
|
+
|
|
43
|
+
mkdirSync(distDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
function run(cmd, args, cwd) {
|
|
46
|
+
console.log(`$ (cd ${cwd && cwd.replace(repoRoot, ".")}) ${cmd} ${args.join(" ")}`);
|
|
47
|
+
execFileSync(cmd, args, { cwd, stdio: "inherit" });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 1) Ensure the extension toolchain (tsc + vsce) is installed.
|
|
51
|
+
if (!existsSync(join(extDir, "node_modules", ".bin", `vsce${binExt}`))) {
|
|
52
|
+
const installCmd = existsSync(join(extDir, "package-lock.json")) ? "ci" : "install";
|
|
53
|
+
run(`npm${binExt}`, [installCmd, "--no-audit", "--no-fund"], extDir);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2) Compile and assert the entrypoint exists (vsce also runs vscode:prepublish,
|
|
57
|
+
// but we fail fast and explicitly here for a clearer error).
|
|
58
|
+
run(`npm${binExt}`, ["run", "compile"], extDir);
|
|
59
|
+
if (!existsSync(join(extDir, "out", "extension.js"))) {
|
|
60
|
+
console.error("build_vsix: vscode-extension/out/extension.js missing after compile");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 3) Package to the root-version-scoped path. `--no-yarn` matches release.yml.
|
|
65
|
+
run(join(extDir, "node_modules", ".bin", `vsce${binExt}`), ["package", "--no-yarn", "-o", outFile], extDir);
|
|
66
|
+
|
|
67
|
+
// 4) Verify the artifact landed where release:validate expects it.
|
|
68
|
+
if (!existsSync(outFile)) {
|
|
69
|
+
console.error(`build_vsix: expected artifact not found: ${outFile}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
console.log(`build_vsix: wrote ${outFile.replace(repoRoot, ".")}`);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Discover-and-compile every first-party Python module.
|
|
3
|
+
|
|
4
|
+
Replaces the hand-maintained ``py_compile`` enumeration in CI and
|
|
5
|
+
``package.json``: walks the repository, skips vendored / virtualenv / build /
|
|
6
|
+
cache / generated directories, and byte-compiles everything that remains. New
|
|
7
|
+
modules are picked up automatically — there is nothing to update when a file is
|
|
8
|
+
added, so the syntax gate can never silently fall behind the codebase.
|
|
9
|
+
|
|
10
|
+
Usage::
|
|
11
|
+
|
|
12
|
+
python scripts/check_python.py # compile all discovered modules
|
|
13
|
+
python scripts/check_python.py --list # just print what would be compiled
|
|
14
|
+
|
|
15
|
+
Exit code is non-zero if any module fails to compile.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import py_compile
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
25
|
+
|
|
26
|
+
# Directory names excluded anywhere in the tree: virtualenvs, build/cache
|
|
27
|
+
# artifacts, generated agent output, and vendored snapshots of older releases.
|
|
28
|
+
EXCLUDE_DIRS = {
|
|
29
|
+
".git",
|
|
30
|
+
".venv",
|
|
31
|
+
"venv",
|
|
32
|
+
"env",
|
|
33
|
+
".build-venv",
|
|
34
|
+
".npm-cache",
|
|
35
|
+
"build",
|
|
36
|
+
"dist",
|
|
37
|
+
"node_modules",
|
|
38
|
+
"__pycache__",
|
|
39
|
+
".pytest_cache",
|
|
40
|
+
".mypy_cache",
|
|
41
|
+
".ruff_cache",
|
|
42
|
+
"agent_workspace",
|
|
43
|
+
"outputs",
|
|
44
|
+
"playwright-report",
|
|
45
|
+
"test-results",
|
|
46
|
+
"ltcai.egg-info",
|
|
47
|
+
".ltcai",
|
|
48
|
+
".ltcai-brain",
|
|
49
|
+
".ltcai-test",
|
|
50
|
+
# Vendored snapshot of an older packaged release — not part of the build.
|
|
51
|
+
"ltcai-0.3.1",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def iter_modules():
|
|
56
|
+
for path in ROOT.rglob("*.py"):
|
|
57
|
+
parts = path.relative_to(ROOT).parts
|
|
58
|
+
if any(part in EXCLUDE_DIRS for part in parts):
|
|
59
|
+
continue
|
|
60
|
+
yield path
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main(argv: list[str]) -> int:
|
|
64
|
+
modules = sorted(iter_modules())
|
|
65
|
+
if "--list" in argv:
|
|
66
|
+
for path in modules:
|
|
67
|
+
print(path.relative_to(ROOT))
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
failures: list[str] = []
|
|
71
|
+
for path in modules:
|
|
72
|
+
try:
|
|
73
|
+
py_compile.compile(str(path), doraise=True)
|
|
74
|
+
except py_compile.PyCompileError as exc:
|
|
75
|
+
failures.append(str(exc))
|
|
76
|
+
|
|
77
|
+
if failures:
|
|
78
|
+
print("\n".join(failures))
|
|
79
|
+
print(f"check:python FAILED — {len(failures)} of {len(modules)} module(s) did not compile")
|
|
80
|
+
return 1
|
|
81
|
+
|
|
82
|
+
print(f"check:python OK — compiled {len(modules)} modules")
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
background: var(--card);
|
|
47
47
|
border-color: var(--accent-soft);
|
|
48
48
|
box-shadow: 0 20px 64px rgba(102, 82, 168, 0.20), inset 0 1px 0 rgba(255,255,255,0.86);
|
|
49
|
-
backdrop-filter:
|
|
49
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
.lattice-ref-auth .card::before {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
color: var(--ref-ink);
|
|
66
66
|
background: var(--surface);
|
|
67
67
|
border-bottom: 1px solid rgba(111,66,232,0.08);
|
|
68
|
-
backdrop-filter:
|
|
68
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
.auth-window-brand {
|
|
@@ -712,7 +712,10 @@
|
|
|
712
712
|
font-weight: 700;
|
|
713
713
|
background: transparent;
|
|
714
714
|
cursor: pointer;
|
|
715
|
-
transition
|
|
715
|
+
/* Animate surface affordances, but never `color`: a color transition on
|
|
716
|
+
theme switch would briefly render the rail link dark-on-dark (the v226
|
|
717
|
+
dark-mode contrast gate caught this on slow CI). Color flips instantly. */
|
|
718
|
+
transition: background-color 160ms ease, border-color 160ms ease, transform 160ms ease;
|
|
716
719
|
}
|
|
717
720
|
|
|
718
721
|
.reference-rail a.active,
|
|
@@ -842,7 +845,7 @@
|
|
|
842
845
|
background: var(--sidebar);
|
|
843
846
|
border-bottom: 1px solid rgba(111,66,232,0.11);
|
|
844
847
|
box-shadow: 0 2px 12px rgba(88,72,150,0.06);
|
|
845
|
-
backdrop-filter:
|
|
848
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
846
849
|
}
|
|
847
850
|
|
|
848
851
|
.lattice-ref-chat .messages-viewport {
|
|
@@ -1295,7 +1298,7 @@
|
|
|
1295
1298
|
height: 96px;
|
|
1296
1299
|
background: var(--sidebar);
|
|
1297
1300
|
border-bottom: 1px solid rgba(111,66,232,0.10);
|
|
1298
|
-
backdrop-filter:
|
|
1301
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
1299
1302
|
padding: 22px 34px 8px;
|
|
1300
1303
|
position: sticky;
|
|
1301
1304
|
}
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
display: flex;
|
|
82
82
|
flex-direction: column;
|
|
83
83
|
min-width: 240px;
|
|
84
|
-
backdrop-filter:
|
|
85
|
-
-webkit-backdrop-filter:
|
|
84
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
85
|
+
-webkit-backdrop-filter: none; /* glass removed v3.5.0 */
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.sidebar-header {
|
|
@@ -342,8 +342,8 @@
|
|
|
342
342
|
padding: 10px 22px;
|
|
343
343
|
border-bottom: 1px solid rgba(111,66,232,0.10);
|
|
344
344
|
background: var(--sidebar);
|
|
345
|
-
backdrop-filter:
|
|
346
|
-
-webkit-backdrop-filter:
|
|
345
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
346
|
+
-webkit-backdrop-filter: none; /* glass removed v3.5.0 */
|
|
347
347
|
position: relative;
|
|
348
348
|
z-index: 50;
|
|
349
349
|
}
|
|
@@ -636,7 +636,7 @@
|
|
|
636
636
|
justify-content: space-between;
|
|
637
637
|
gap: 12px;
|
|
638
638
|
box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,0.80);
|
|
639
|
-
backdrop-filter:
|
|
639
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
640
640
|
transition: all .2s;
|
|
641
641
|
}
|
|
642
642
|
|
|
@@ -1323,7 +1323,7 @@
|
|
|
1323
1323
|
box-shadow: var(--shadow), 0 0 0 1px rgba(111,66,232,0.05);
|
|
1324
1324
|
position: relative;
|
|
1325
1325
|
z-index: 1;
|
|
1326
|
-
backdrop-filter:
|
|
1326
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
1329
|
.auth-card::before {
|
|
@@ -2754,7 +2754,7 @@
|
|
|
2754
2754
|
.admin-panel {
|
|
2755
2755
|
background: var(--card);
|
|
2756
2756
|
border-left: 1px solid var(--border);
|
|
2757
|
-
backdrop-filter:
|
|
2757
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
2758
2758
|
color: var(--text);
|
|
2759
2759
|
}
|
|
2760
2760
|
.admin-header {
|
|
@@ -4254,7 +4254,7 @@
|
|
|
4254
4254
|
background: var(--sidebar);
|
|
4255
4255
|
border-bottom: 1px solid var(--line);
|
|
4256
4256
|
box-shadow: none;
|
|
4257
|
-
backdrop-filter:
|
|
4257
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
4258
4258
|
}
|
|
4259
4259
|
|
|
4260
4260
|
/* Messages viewport */
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
z-index: 20;
|
|
121
121
|
border: 1px solid var(--line);
|
|
122
122
|
background: var(--panel);
|
|
123
|
-
backdrop-filter:
|
|
123
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
124
124
|
box-shadow: var(--shadow);
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -904,7 +904,7 @@
|
|
|
904
904
|
border-radius: 10px;
|
|
905
905
|
background: var(--surface-2);
|
|
906
906
|
box-shadow: var(--shadow);
|
|
907
|
-
backdrop-filter:
|
|
907
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
908
908
|
}
|
|
909
909
|
|
|
910
910
|
.focus-chip span {
|
|
@@ -319,8 +319,8 @@ select {
|
|
|
319
319
|
z-index: 99;
|
|
320
320
|
display: none;
|
|
321
321
|
background: rgba(15, 12, 30, 0.42);
|
|
322
|
-
-webkit-backdrop-filter:
|
|
323
|
-
backdrop-filter:
|
|
322
|
+
-webkit-backdrop-filter: none; /* glass removed v3.5.0 */
|
|
323
|
+
backdrop-filter: none; /* glass removed v3.5.0 */
|
|
324
324
|
}
|
|
325
325
|
body.sidebar-open .sidebar-overlay { display: block; }
|
|
326
326
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.
|
|
2
|
+
"version": "3.6.0",
|
|
3
3
|
"generated_at": "deterministic",
|
|
4
4
|
"entrypoints": {
|
|
5
|
-
"app": "/static/v3/js/app.
|
|
5
|
+
"app": "/static/v3/js/app.c541f955.js",
|
|
6
6
|
"styles": [
|
|
7
7
|
"/static/css/tokens.3ba22e37.css",
|
|
8
8
|
"/static/v3/css/lattice.tokens.e7018963.css",
|
|
9
9
|
"/static/v3/css/lattice.base.e4cdd05d.css",
|
|
10
10
|
"/static/v3/css/lattice.components.9b49d614.css",
|
|
11
|
-
"/static/v3/css/lattice.shell.
|
|
11
|
+
"/static/v3/css/lattice.shell.8fcc9d33.css",
|
|
12
12
|
"/static/v3/css/lattice.views.22f69117.css"
|
|
13
13
|
]
|
|
14
14
|
},
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"static/v3/css/lattice.tokens.css": "/static/v3/css/lattice.tokens.e7018963.css",
|
|
18
18
|
"static/v3/css/lattice.base.css": "/static/v3/css/lattice.base.e4cdd05d.css",
|
|
19
19
|
"static/v3/css/lattice.components.css": "/static/v3/css/lattice.components.9b49d614.css",
|
|
20
|
-
"static/v3/css/lattice.shell.css": "/static/v3/css/lattice.shell.
|
|
20
|
+
"static/v3/css/lattice.shell.css": "/static/v3/css/lattice.shell.8fcc9d33.css",
|
|
21
21
|
"static/v3/css/lattice.views.css": "/static/v3/css/lattice.views.22f69117.css",
|
|
22
|
-
"static/v3/js/app.js": "/static/v3/js/app.
|
|
23
|
-
"static/v3/js/core/api.js": "/static/v3/js/core/api.
|
|
22
|
+
"static/v3/js/app.js": "/static/v3/js/app.c541f955.js",
|
|
23
|
+
"static/v3/js/core/api.js": "/static/v3/js/core/api.33d6320e.js",
|
|
24
24
|
"static/v3/js/core/components.js": "/static/v3/js/core/components.f25b3b93.js",
|
|
25
25
|
"static/v3/js/core/dom.js": "/static/v3/js/core/dom.a2773eb0.js",
|
|
26
26
|
"static/v3/js/core/router.js": "/static/v3/js/core/router.584570f2.js",
|
|
27
|
-
"static/v3/js/core/routes.js": "/static/v3/js/core/routes.
|
|
28
|
-
"static/v3/js/core/shell.js": "/static/v3/js/core/shell.
|
|
27
|
+
"static/v3/js/core/routes.js": "/static/v3/js/core/routes.2ce3815a.js",
|
|
28
|
+
"static/v3/js/core/shell.js": "/static/v3/js/core/shell.8c163e0e.js",
|
|
29
29
|
"static/v3/js/core/store.js": "/static/v3/js/core/store.34ebd5e6.js",
|
|
30
30
|
"static/v3/js/views/admin-audit.js": "/static/v3/js/views/admin-audit.660a1fb1.js",
|
|
31
31
|
"static/v3/js/views/admin-permissions.js": "/static/v3/js/views/admin-permissions.a7ae5f09.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"static/v3/js/views/home.js": "/static/v3/js/views/home.24f8b8ae.js",
|
|
40
40
|
"static/v3/js/views/hooks.js": "/static/v3/js/views/hooks.37895880.js",
|
|
41
41
|
"static/v3/js/views/hybrid-search.js": "/static/v3/js/views/hybrid-search.b22b97e0.js",
|
|
42
|
-
"static/v3/js/views/knowledge-graph.js": "/static/v3/js/views/knowledge-graph.
|
|
42
|
+
"static/v3/js/views/knowledge-graph.js": "/static/v3/js/views/knowledge-graph.a96040a5.js",
|
|
43
43
|
"static/v3/js/views/marketplace.js": "/static/v3/js/views/marketplace.ab0583d4.js",
|
|
44
44
|
"static/v3/js/views/mcp.js": "/static/v3/js/views/mcp.99b5c6a7.js",
|
|
45
45
|
"static/v3/js/views/memory.js": "/static/v3/js/views/memory.4ebdf474.js",
|
|
@@ -349,8 +349,9 @@
|
|
|
349
349
|
.lt3-scrim {
|
|
350
350
|
position: fixed; inset: 0;
|
|
351
351
|
z-index: var(--lt3-z-scrim);
|
|
352
|
+
/* Solid dim scrim — no backdrop blur (glassmorphism removed in v3.5.0 for a
|
|
353
|
+
crisp, stable surface). */
|
|
352
354
|
background: var(--overlay);
|
|
353
|
-
backdrop-filter: blur(2px);
|
|
354
355
|
opacity: 0;
|
|
355
356
|
animation: lt3-fade var(--lt3-dur-2) var(--lt3-ease) forwards;
|
|
356
357
|
}
|
|
@@ -349,8 +349,9 @@
|
|
|
349
349
|
.lt3-scrim {
|
|
350
350
|
position: fixed; inset: 0;
|
|
351
351
|
z-index: var(--lt3-z-scrim);
|
|
352
|
+
/* Solid dim scrim — no backdrop blur (glassmorphism removed in v3.5.0 for a
|
|
353
|
+
crisp, stable surface). */
|
|
352
354
|
background: var(--overlay);
|
|
353
|
-
backdrop-filter: blur(2px);
|
|
354
355
|
opacity: 0;
|
|
355
356
|
animation: lt3-fade var(--lt3-dur-2) var(--lt3-ease) forwards;
|
|
356
357
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Boots the shell. Views are lazy-loaded by the router (see core/routes.js).
|
|
4
4
|
* ========================================================================== */
|
|
5
5
|
|
|
6
|
-
import { boot } from "./core/shell.
|
|
6
|
+
import { boot } from "./core/shell.8c163e0e.js";
|
|
7
7
|
|
|
8
8
|
const root = document.getElementById("app");
|
|
9
9
|
if (root) boot(root);
|