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.
Files changed (76) hide show
  1. package/README.md +33 -24
  2. package/docs/CHANGELOG.md +84 -0
  3. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
  4. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
  5. package/docs/V4_2_VALIDATION_REPORT.md +89 -0
  6. package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +69 -0
  7. package/docs/V4_3_PRIVACY_AUDIT.md +60 -0
  8. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +53 -0
  9. package/docs/V4_3_VALIDATION_REPORT.md +58 -0
  10. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -33
  11. package/frontend/openapi.json +449 -1
  12. package/frontend/src/api/client.ts +10 -0
  13. package/frontend/src/api/openapi.ts +542 -0
  14. package/frontend/src/pages/System.tsx +92 -0
  15. package/kg_schema.py +1 -1
  16. package/knowledge_graph.py +4 -4
  17. package/lattice_brain/__init__.py +70 -0
  18. package/lattice_brain/_kg_common.py +1 -0
  19. package/lattice_brain/archive.py +446 -0
  20. package/lattice_brain/context.py +3 -0
  21. package/lattice_brain/conversations.py +3 -0
  22. package/lattice_brain/core.py +82 -0
  23. package/lattice_brain/discovery.py +1 -0
  24. package/lattice_brain/documents.py +1 -0
  25. package/lattice_brain/embeddings.py +82 -0
  26. package/lattice_brain/identity.py +13 -0
  27. package/lattice_brain/ingest.py +1 -0
  28. package/lattice_brain/memory.py +3 -0
  29. package/lattice_brain/network.py +1 -0
  30. package/lattice_brain/projection.py +1 -0
  31. package/lattice_brain/provenance.py +1 -0
  32. package/lattice_brain/retrieval.py +1 -0
  33. package/lattice_brain/schema.py +1 -0
  34. package/lattice_brain/storage/__init__.py +22 -0
  35. package/lattice_brain/storage/base.py +72 -0
  36. package/lattice_brain/storage/docker.py +105 -0
  37. package/lattice_brain/storage/factory.py +31 -0
  38. package/lattice_brain/storage/migration.py +190 -0
  39. package/lattice_brain/storage/postgres.py +123 -0
  40. package/lattice_brain/storage/sqlite.py +128 -0
  41. package/lattice_brain/store.py +3 -0
  42. package/lattice_brain/write_master.py +1 -0
  43. package/latticeai/__init__.py +1 -1
  44. package/latticeai/api/admin.py +11 -0
  45. package/latticeai/api/portability.py +127 -1
  46. package/latticeai/app_factory.py +26 -10
  47. package/latticeai/brain/__init__.py +6 -6
  48. package/latticeai/brain/_kg_common.py +1 -1
  49. package/latticeai/brain/network.py +1 -1
  50. package/latticeai/brain/retrieval.py +15 -0
  51. package/latticeai/brain/store.py +22 -6
  52. package/latticeai/core/config.py +9 -1
  53. package/latticeai/core/marketplace.py +1 -1
  54. package/latticeai/core/multi_agent.py +1 -1
  55. package/latticeai/core/product_hardening.py +217 -0
  56. package/latticeai/core/workspace_os.py +1 -1
  57. package/latticeai/services/kg_portability.py +227 -3
  58. package/ltcai_cli.py +2 -1
  59. package/package.json +4 -3
  60. package/scripts/bump_version.py +3 -0
  61. package/scripts/clean_release_artifacts.mjs +27 -0
  62. package/scripts/lint_frontend.mjs +10 -0
  63. package/scripts/migrate_brain_storage.py +53 -0
  64. package/scripts/validate_release_artifacts.py +10 -0
  65. package/scripts/wheel_smoke.py +3 -0
  66. package/src-tauri/Cargo.lock +1 -1
  67. package/src-tauri/Cargo.toml +1 -1
  68. package/src-tauri/src/main.rs +113 -13
  69. package/src-tauri/tauri.conf.json +5 -2
  70. package/static/app/asset-manifest.json +5 -5
  71. package/static/app/assets/{index-CJRAzNnf.js → index-RiJTJliG.js} +3 -3
  72. package/static/app/assets/index-RiJTJliG.js.map +1 -0
  73. package/static/app/assets/index-yZswHE3d.css +2 -0
  74. package/static/app/index.html +2 -2
  75. package/static/app/assets/index-CJRAzNnf.js.map +0 -1
  76. 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:
@@ -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",
@@ -1654,7 +1654,7 @@ dependencies = [
1654
1654
 
1655
1655
  [[package]]
1656
1656
  name = "lattice-ai-desktop"
1657
- version = "4.1.0"
1657
+ version = "4.3.0"
1658
1658
  dependencies = [
1659
1659
  "plist",
1660
1660
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lattice-ai-desktop"
3
- version = "4.1.0"
3
+ version = "4.3.0"
4
4
  description = "Lattice AI Digital Brain desktop shell"
5
5
  authors = ["TaeSoo Park"]
6
6
  edition = "2021"
@@ -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 spawn_backend(origin: &str) -> Option<Child> {
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 None;
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().ok()
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 child = spawn_backend(&origin);
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![backend_origin])
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
- if let Ok(mut child) = state.child.lock() {
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.1.0",
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": ["dmg", "app"],
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.1.0",
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-CJRAzNnf.js",
10
- "assets/index-CSwBBgf4.css": "/static/app/assets/index-CSwBBgf4.css"
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-CJRAzNnf.js",
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-CSwBBgf4.css"
28
+ "assets/index-yZswHE3d.css"
29
29
  ]
30
30
  }
31
31
  }