ltcai 4.1.0 → 4.3.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 +33 -24
- package/docs/CHANGELOG.md +84 -0
- package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
- package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
- package/docs/V4_2_VALIDATION_REPORT.md +89 -0
- package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +69 -0
- package/docs/V4_3_PRIVACY_AUDIT.md +60 -0
- package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +53 -0
- package/docs/V4_3_VALIDATION_REPORT.md +58 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -33
- package/frontend/openapi.json +449 -1
- package/frontend/src/api/client.ts +10 -0
- package/frontend/src/api/openapi.ts +542 -0
- package/frontend/src/pages/System.tsx +92 -0
- package/kg_schema.py +1 -1
- package/knowledge_graph.py +4 -4
- package/lattice_brain/__init__.py +70 -0
- package/lattice_brain/_kg_common.py +1 -0
- package/lattice_brain/archive.py +446 -0
- package/lattice_brain/context.py +3 -0
- package/lattice_brain/conversations.py +3 -0
- package/lattice_brain/core.py +82 -0
- package/lattice_brain/discovery.py +1 -0
- package/lattice_brain/documents.py +1 -0
- package/lattice_brain/embeddings.py +82 -0
- package/lattice_brain/identity.py +13 -0
- package/lattice_brain/ingest.py +1 -0
- package/lattice_brain/memory.py +3 -0
- package/lattice_brain/network.py +1 -0
- package/lattice_brain/projection.py +1 -0
- package/lattice_brain/provenance.py +1 -0
- package/lattice_brain/retrieval.py +1 -0
- package/lattice_brain/schema.py +1 -0
- package/lattice_brain/storage/__init__.py +22 -0
- package/lattice_brain/storage/base.py +72 -0
- package/lattice_brain/storage/docker.py +105 -0
- package/lattice_brain/storage/factory.py +31 -0
- package/lattice_brain/storage/migration.py +190 -0
- package/lattice_brain/storage/postgres.py +123 -0
- package/lattice_brain/storage/sqlite.py +128 -0
- package/lattice_brain/store.py +3 -0
- package/lattice_brain/write_master.py +1 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +11 -0
- package/latticeai/api/portability.py +127 -1
- package/latticeai/app_factory.py +26 -10
- package/latticeai/brain/__init__.py +6 -6
- package/latticeai/brain/_kg_common.py +1 -1
- package/latticeai/brain/network.py +1 -1
- package/latticeai/brain/retrieval.py +15 -0
- package/latticeai/brain/store.py +22 -6
- package/latticeai/core/config.py +9 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/product_hardening.py +217 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/kg_portability.py +227 -3
- package/ltcai_cli.py +2 -1
- package/package.json +4 -3
- package/scripts/bump_version.py +3 -0
- package/scripts/clean_release_artifacts.mjs +27 -0
- package/scripts/lint_frontend.mjs +10 -0
- package/scripts/migrate_brain_storage.py +53 -0
- package/scripts/validate_release_artifacts.py +10 -0
- package/scripts/wheel_smoke.py +3 -0
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/main.rs +113 -13
- package/src-tauri/tauri.conf.json +5 -2
- package/static/app/asset-manifest.json +5 -5
- package/static/app/assets/{index-CJRAzNnf.js → index-RiJTJliG.js} +3 -3
- package/static/app/assets/index-RiJTJliG.js.map +1 -0
- package/static/app/assets/index-yZswHE3d.css +2 -0
- package/static/app/index.html +2 -2
- package/static/app/assets/index-CJRAzNnf.js.map +0 -1
- package/static/app/assets/index-CSwBBgf4.css +0 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Brain storage migration utility.
|
|
3
|
+
|
|
4
|
+
Examples:
|
|
5
|
+
python scripts/migrate_brain_storage.py plan --sqlite ~/.ltcai/knowledge_graph.sqlite --dsn postgresql://...
|
|
6
|
+
python scripts/migrate_brain_storage.py migrate --sqlite ~/.ltcai/knowledge_graph.sqlite --dsn postgresql://...
|
|
7
|
+
python scripts/migrate_brain_storage.py docker-plan --data-dir ~/.ltcai/postgres
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from lattice_brain.storage import DockerPostgresWizard, PostgresEngine, SQLiteToPostgresMigrator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _print_json(value: object) -> None:
|
|
20
|
+
print(json.dumps(value, ensure_ascii=False, indent=2))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main() -> int:
|
|
24
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
25
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
26
|
+
|
|
27
|
+
for name in ("plan", "migrate"):
|
|
28
|
+
p = sub.add_parser(name)
|
|
29
|
+
p.add_argument("--sqlite", type=Path, required=True)
|
|
30
|
+
p.add_argument("--dsn", required=True)
|
|
31
|
+
p.add_argument("--schema", default="lattice_brain")
|
|
32
|
+
|
|
33
|
+
docker = sub.add_parser("docker-plan")
|
|
34
|
+
docker.add_argument("--data-dir", type=Path, required=True)
|
|
35
|
+
docker.add_argument("--port", type=int, default=5432)
|
|
36
|
+
|
|
37
|
+
args = parser.parse_args()
|
|
38
|
+
if args.command == "docker-plan":
|
|
39
|
+
wizard = DockerPostgresWizard(args.data_dir, port=args.port)
|
|
40
|
+
result = wizard.start(consent=False)
|
|
41
|
+
_print_json(result)
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
migrator = SQLiteToPostgresMigrator(
|
|
45
|
+
args.sqlite,
|
|
46
|
+
PostgresEngine(args.dsn, schema=args.schema),
|
|
47
|
+
)
|
|
48
|
+
_print_json(migrator.migrate(dry_run=args.command == "plan"))
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
raise SystemExit(main())
|
|
@@ -10,6 +10,7 @@ effort) that the VSIX actually contains the compiled extension entrypoint.
|
|
|
10
10
|
Usage:
|
|
11
11
|
python scripts/validate_release_artifacts.py 1.1.0
|
|
12
12
|
python scripts/validate_release_artifacts.py 1.1.0 --require-vsix
|
|
13
|
+
python scripts/validate_release_artifacts.py 1.1.0 --require-dmg
|
|
13
14
|
python scripts/validate_release_artifacts.py 1.1.0 --dist dist --json
|
|
14
15
|
|
|
15
16
|
Exit code is non-zero on any failure so CI can fail fast.
|
|
@@ -63,6 +64,7 @@ def validate(
|
|
|
63
64
|
*,
|
|
64
65
|
require_vsix: bool,
|
|
65
66
|
require_tgz: bool,
|
|
67
|
+
require_dmg: bool = False,
|
|
66
68
|
) -> Dict[str, object]:
|
|
67
69
|
errors: List[str] = []
|
|
68
70
|
warnings: List[str] = []
|
|
@@ -105,6 +107,12 @@ def validate(
|
|
|
105
107
|
else:
|
|
106
108
|
warnings.append(f"npm tarball not found: {tgz.name} (run `npm pack`)")
|
|
107
109
|
|
|
110
|
+
dmg = dist_dir.parent / "src-tauri" / "target" / "release" / "bundle" / "dmg" / f"Lattice AI_{version}_aarch64.dmg"
|
|
111
|
+
if dmg.is_file():
|
|
112
|
+
found["dmg"] = str(dmg)
|
|
113
|
+
elif require_dmg:
|
|
114
|
+
errors.append(f"missing dmg: {dmg}")
|
|
115
|
+
|
|
108
116
|
# Guard against stale-version mixing: warn loudly about other-version builds
|
|
109
117
|
# so a `dist/*` glob upload is obviously unsafe.
|
|
110
118
|
other_versions = set()
|
|
@@ -137,6 +145,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
137
145
|
parser.add_argument("--dist", default="dist", help="dist directory (default: dist)")
|
|
138
146
|
parser.add_argument("--require-vsix", action="store_true", help="fail if the VSIX is absent")
|
|
139
147
|
parser.add_argument("--require-tgz", action="store_true", help="check for npm pack tarball at repo root")
|
|
148
|
+
parser.add_argument("--require-dmg", action="store_true", help="fail if the Tauri DMG is absent")
|
|
140
149
|
parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
|
|
141
150
|
args = parser.parse_args(argv)
|
|
142
151
|
|
|
@@ -145,6 +154,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
145
154
|
Path(args.dist),
|
|
146
155
|
require_vsix=args.require_vsix,
|
|
147
156
|
require_tgz=args.require_tgz,
|
|
157
|
+
require_dmg=args.require_dmg,
|
|
148
158
|
)
|
|
149
159
|
|
|
150
160
|
if args.json:
|
package/scripts/wheel_smoke.py
CHANGED
|
@@ -33,6 +33,9 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
|
33
33
|
# Every importable module the wheel ships (pyproject py-modules + packages).
|
|
34
34
|
WHEEL_MODULES = [
|
|
35
35
|
"setup_wizard",
|
|
36
|
+
"lattice_brain",
|
|
37
|
+
"lattice_brain.storage",
|
|
38
|
+
"lattice_brain.archive",
|
|
36
39
|
"latticeai",
|
|
37
40
|
"latticeai.server_app",
|
|
38
41
|
"latticeai.app_factory",
|
package/src-tauri/Cargo.lock
CHANGED
package/src-tauri/Cargo.toml
CHANGED
package/src-tauri/src/main.rs
CHANGED
|
@@ -4,11 +4,23 @@ use std::{
|
|
|
4
4
|
sync::Mutex,
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
use serde::Serialize;
|
|
7
8
|
use tauri::{Manager, State};
|
|
8
9
|
|
|
9
10
|
struct BackendState {
|
|
10
11
|
origin: String,
|
|
12
|
+
command: String,
|
|
11
13
|
child: Mutex<Option<Child>>,
|
|
14
|
+
last_error: Mutex<Option<String>>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Serialize)]
|
|
18
|
+
struct BackendStatus {
|
|
19
|
+
origin: String,
|
|
20
|
+
command: String,
|
|
21
|
+
running: bool,
|
|
22
|
+
pid: Option<u32>,
|
|
23
|
+
last_error: Option<String>,
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
#[tauri::command]
|
|
@@ -16,6 +28,32 @@ fn backend_origin(state: State<'_, BackendState>) -> String {
|
|
|
16
28
|
state.origin.clone()
|
|
17
29
|
}
|
|
18
30
|
|
|
31
|
+
#[tauri::command]
|
|
32
|
+
fn backend_status(state: State<'_, BackendState>) -> BackendStatus {
|
|
33
|
+
status_from_state(&state)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[tauri::command]
|
|
37
|
+
fn restart_backend(state: State<'_, BackendState>) -> BackendStatus {
|
|
38
|
+
kill_backend(&state);
|
|
39
|
+
match spawn_backend(&state.origin, &state.command) {
|
|
40
|
+
Ok(child) => {
|
|
41
|
+
if let Ok(mut slot) = state.child.lock() {
|
|
42
|
+
*slot = child;
|
|
43
|
+
}
|
|
44
|
+
set_error(&state, None);
|
|
45
|
+
}
|
|
46
|
+
Err(err) => set_error(&state, Some(err)),
|
|
47
|
+
}
|
|
48
|
+
status_from_state(&state)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[tauri::command]
|
|
52
|
+
fn shutdown_backend(state: State<'_, BackendState>) -> BackendStatus {
|
|
53
|
+
kill_backend(&state);
|
|
54
|
+
status_from_state(&state)
|
|
55
|
+
}
|
|
56
|
+
|
|
19
57
|
fn split_command(command: &str) -> Vec<String> {
|
|
20
58
|
command
|
|
21
59
|
.split_whitespace()
|
|
@@ -23,39 +61,105 @@ fn split_command(command: &str) -> Vec<String> {
|
|
|
23
61
|
.collect()
|
|
24
62
|
}
|
|
25
63
|
|
|
26
|
-
fn
|
|
64
|
+
fn backend_command() -> String {
|
|
65
|
+
env::var("LATTICEAI_DESKTOP_BACKEND_CMD")
|
|
66
|
+
.unwrap_or_else(|_| "python3 ltcai_cli.py --host 127.0.0.1 --port 8765".to_string())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn set_error(state: &BackendState, err: Option<String>) {
|
|
70
|
+
if let Ok(mut last) = state.last_error.lock() {
|
|
71
|
+
*last = err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn spawn_backend(origin: &str, command: &str) -> Result<Option<Child>, String> {
|
|
27
76
|
if env::var("LATTICEAI_DESKTOP_NO_BACKEND").is_ok() {
|
|
28
|
-
return None;
|
|
77
|
+
return Ok(None);
|
|
29
78
|
}
|
|
30
|
-
let command = env::var("LATTICEAI_DESKTOP_BACKEND_CMD")
|
|
31
|
-
.unwrap_or_else(|_| "python3 ltcai_cli.py --host 127.0.0.1 --port 8765".to_string());
|
|
32
79
|
let parts = split_command(&command);
|
|
33
80
|
if parts.is_empty() {
|
|
34
|
-
return
|
|
81
|
+
return Err("Desktop backend command is empty.".to_string());
|
|
35
82
|
}
|
|
36
83
|
let mut cmd = Command::new(&parts[0]);
|
|
37
84
|
cmd.args(&parts[1..])
|
|
38
85
|
.env("LATTICEAI_HOST", "127.0.0.1")
|
|
39
86
|
.env("LATTICEAI_PORT", origin.rsplit(':').next().unwrap_or("8765"))
|
|
87
|
+
.env("LATTICEAI_ENABLE_TELEGRAM", "false")
|
|
88
|
+
.env("LATTICEAI_AUTOLOAD_MODELS", "false")
|
|
89
|
+
.env("LATTICEAI_CORS_ALLOW_NETWORK", "false")
|
|
40
90
|
.env("LATTICEAI_TUNNEL", "false")
|
|
41
91
|
.stdout(Stdio::null())
|
|
42
92
|
.stderr(Stdio::null());
|
|
43
93
|
if let Ok(cwd) = env::var("LATTICEAI_DESKTOP_BACKEND_CWD") {
|
|
44
94
|
cmd.current_dir(cwd);
|
|
45
95
|
}
|
|
46
|
-
cmd.spawn()
|
|
96
|
+
cmd.spawn()
|
|
97
|
+
.map(Some)
|
|
98
|
+
.map_err(|err| format!("Failed to start desktop backend '{}': {}", parts[0], err))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn kill_backend(state: &BackendState) {
|
|
102
|
+
if let Ok(mut child) = state.child.lock() {
|
|
103
|
+
if let Some(mut process) = child.take() {
|
|
104
|
+
let _ = process.kill();
|
|
105
|
+
let _ = process.wait();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn status_from_state(state: &BackendState) -> BackendStatus {
|
|
111
|
+
let mut running = false;
|
|
112
|
+
let mut pid = None;
|
|
113
|
+
if let Ok(mut child_slot) = state.child.lock() {
|
|
114
|
+
if let Some(child) = child_slot.as_mut() {
|
|
115
|
+
match child.try_wait() {
|
|
116
|
+
Ok(Some(status)) => {
|
|
117
|
+
set_error(state, Some(format!("Desktop backend exited with status {}", status)));
|
|
118
|
+
*child_slot = None;
|
|
119
|
+
}
|
|
120
|
+
Ok(None) => {
|
|
121
|
+
running = true;
|
|
122
|
+
pid = Some(child.id());
|
|
123
|
+
}
|
|
124
|
+
Err(err) => set_error(state, Some(format!("Unable to inspect desktop backend: {}", err))),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let last_error = state
|
|
129
|
+
.last_error
|
|
130
|
+
.lock()
|
|
131
|
+
.ok()
|
|
132
|
+
.and_then(|guard| guard.clone());
|
|
133
|
+
BackendStatus {
|
|
134
|
+
origin: state.origin.clone(),
|
|
135
|
+
command: state.command.clone(),
|
|
136
|
+
running,
|
|
137
|
+
pid,
|
|
138
|
+
last_error,
|
|
139
|
+
}
|
|
47
140
|
}
|
|
48
141
|
|
|
49
142
|
fn main() {
|
|
50
143
|
let origin = env::var("LATTICEAI_DESKTOP_BACKEND_ORIGIN")
|
|
51
144
|
.unwrap_or_else(|_| "http://127.0.0.1:8765".to_string());
|
|
52
|
-
let
|
|
145
|
+
let command = backend_command();
|
|
146
|
+
let (child, last_error) = match spawn_backend(&origin, &command) {
|
|
147
|
+
Ok(child) => (child, None),
|
|
148
|
+
Err(err) => (None, Some(err)),
|
|
149
|
+
};
|
|
53
150
|
tauri::Builder::default()
|
|
54
151
|
.manage(BackendState {
|
|
55
152
|
origin,
|
|
153
|
+
command,
|
|
56
154
|
child: Mutex::new(child),
|
|
155
|
+
last_error: Mutex::new(last_error),
|
|
57
156
|
})
|
|
58
|
-
.invoke_handler(tauri::generate_handler![
|
|
157
|
+
.invoke_handler(tauri::generate_handler![
|
|
158
|
+
backend_origin,
|
|
159
|
+
backend_status,
|
|
160
|
+
restart_backend,
|
|
161
|
+
shutdown_backend
|
|
162
|
+
])
|
|
59
163
|
.setup(|app| {
|
|
60
164
|
if let Some(window) = app.get_webview_window("main") {
|
|
61
165
|
let _ = window.set_title("Lattice AI");
|
|
@@ -65,11 +169,7 @@ fn main() {
|
|
|
65
169
|
.on_window_event(|window, event| {
|
|
66
170
|
if matches!(event, tauri::WindowEvent::CloseRequested { .. }) {
|
|
67
171
|
if let Some(state) = window.try_state::<BackendState>() {
|
|
68
|
-
|
|
69
|
-
if let Some(process) = child.as_mut() {
|
|
70
|
-
let _ = process.kill();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
172
|
+
kill_backend(&state);
|
|
73
173
|
}
|
|
74
174
|
}
|
|
75
175
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://schema.tauri.app/config/2",
|
|
3
3
|
"productName": "Lattice AI",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.0",
|
|
5
5
|
"identifier": "ai.lattice.desktop",
|
|
6
6
|
"build": {
|
|
7
7
|
"beforeDevCommand": "npm run frontend:dev",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
},
|
|
28
28
|
"bundle": {
|
|
29
29
|
"active": true,
|
|
30
|
-
"targets": [
|
|
30
|
+
"targets": [
|
|
31
|
+
"dmg",
|
|
32
|
+
"app"
|
|
33
|
+
],
|
|
31
34
|
"icon": [
|
|
32
35
|
"../static/icons/icon-192.png",
|
|
33
36
|
"../static/icons/icon-512.png"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.
|
|
2
|
+
"version": "4.3.0",
|
|
3
3
|
"generated_at": "vite",
|
|
4
4
|
"entrypoints": {
|
|
5
5
|
"app": "/static/app/index.html"
|
|
6
6
|
},
|
|
7
7
|
"assets": {
|
|
8
8
|
"../node_modules/@tauri-apps/api/core.js": "/static/app/assets/core-CwxXejkd.js",
|
|
9
|
-
"index.html": "/static/app/assets/index-
|
|
10
|
-
"assets/index-
|
|
9
|
+
"index.html": "/static/app/assets/index-RiJTJliG.js",
|
|
10
|
+
"assets/index-yZswHE3d.css": "/static/app/assets/index-yZswHE3d.css"
|
|
11
11
|
},
|
|
12
12
|
"vite": {
|
|
13
13
|
"../node_modules/@tauri-apps/api/core.js": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"isDynamicEntry": true
|
|
18
18
|
},
|
|
19
19
|
"index.html": {
|
|
20
|
-
"file": "assets/index-
|
|
20
|
+
"file": "assets/index-RiJTJliG.js",
|
|
21
21
|
"name": "index",
|
|
22
22
|
"src": "index.html",
|
|
23
23
|
"isEntry": true,
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"../node_modules/@tauri-apps/api/core.js"
|
|
26
26
|
],
|
|
27
27
|
"css": [
|
|
28
|
-
"assets/index-
|
|
28
|
+
"assets/index-yZswHE3d.css"
|
|
29
29
|
]
|
|
30
30
|
}
|
|
31
31
|
}
|