ltcai 4.3.1 → 4.4.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 (131) hide show
  1. package/README.md +191 -278
  2. package/docs/CHANGELOG.md +128 -0
  3. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +174 -0
  4. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +81 -0
  5. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +75 -0
  6. package/docs/V4_3_2_GRAPH_UX_REPORT.md +48 -0
  7. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +209 -0
  8. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +57 -0
  9. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +63 -0
  10. package/docs/V4_3_2_VALIDATION_REPORT.md +97 -0
  11. package/docs/V4_3_3_VALIDATION_REPORT.md +46 -0
  12. package/docs/V4_4_0_EXTRACTION_REPORT.md +239 -0
  13. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +18 -19
  14. package/frontend/openapi.json +1 -1
  15. package/frontend/src/components/primitives.tsx +92 -10
  16. package/frontend/src/pages/Act.tsx +11 -9
  17. package/frontend/src/pages/Ask.tsx +2 -2
  18. package/frontend/src/pages/Brain.tsx +607 -65
  19. package/frontend/src/pages/Capture.tsx +11 -7
  20. package/frontend/src/pages/Library.tsx +3 -3
  21. package/frontend/src/pages/System.tsx +186 -23
  22. package/lattice_brain/__init__.py +38 -23
  23. package/lattice_brain/_kg_common.py +11 -1
  24. package/lattice_brain/context.py +212 -2
  25. package/lattice_brain/conversations.py +234 -1
  26. package/lattice_brain/discovery.py +11 -1
  27. package/lattice_brain/documents.py +11 -1
  28. package/lattice_brain/graph/__init__.py +28 -0
  29. package/lattice_brain/graph/_kg_common.py +1123 -0
  30. package/lattice_brain/graph/curator.py +473 -0
  31. package/lattice_brain/graph/discovery.py +1455 -0
  32. package/lattice_brain/graph/documents.py +218 -0
  33. package/lattice_brain/graph/identity.py +175 -0
  34. package/lattice_brain/graph/ingest.py +644 -0
  35. package/lattice_brain/graph/network.py +205 -0
  36. package/lattice_brain/graph/projection.py +571 -0
  37. package/lattice_brain/graph/provenance.py +401 -0
  38. package/lattice_brain/graph/retrieval.py +1341 -0
  39. package/lattice_brain/graph/schema.py +640 -0
  40. package/lattice_brain/graph/store.py +237 -0
  41. package/lattice_brain/graph/write_master.py +225 -0
  42. package/lattice_brain/identity.py +11 -13
  43. package/lattice_brain/ingest.py +11 -1
  44. package/lattice_brain/ingestion.py +318 -0
  45. package/lattice_brain/memory.py +100 -1
  46. package/lattice_brain/network.py +11 -1
  47. package/lattice_brain/portability.py +431 -0
  48. package/lattice_brain/projection.py +11 -1
  49. package/lattice_brain/provenance.py +11 -1
  50. package/lattice_brain/retrieval.py +11 -1
  51. package/lattice_brain/runtime/__init__.py +32 -0
  52. package/lattice_brain/runtime/agent_runtime.py +569 -0
  53. package/lattice_brain/runtime/hooks.py +754 -0
  54. package/lattice_brain/runtime/multi_agent.py +795 -0
  55. package/lattice_brain/schema.py +11 -1
  56. package/lattice_brain/store.py +10 -2
  57. package/lattice_brain/workflow.py +461 -0
  58. package/lattice_brain/write_master.py +11 -1
  59. package/latticeai/__init__.py +1 -1
  60. package/latticeai/api/agents.py +2 -2
  61. package/latticeai/api/browser.py +1 -1
  62. package/latticeai/api/chat.py +1 -1
  63. package/latticeai/api/computer_use.py +1 -1
  64. package/latticeai/api/hooks.py +2 -2
  65. package/latticeai/api/mcp.py +1 -1
  66. package/latticeai/api/tools.py +1 -1
  67. package/latticeai/api/workflow_designer.py +2 -2
  68. package/latticeai/app_factory.py +4 -4
  69. package/latticeai/brain/__init__.py +24 -6
  70. package/latticeai/brain/_kg_common.py +11 -1117
  71. package/latticeai/brain/context.py +12 -208
  72. package/latticeai/brain/conversations.py +12 -231
  73. package/latticeai/brain/discovery.py +13 -1451
  74. package/latticeai/brain/documents.py +13 -214
  75. package/latticeai/brain/identity.py +11 -169
  76. package/latticeai/brain/ingest.py +13 -640
  77. package/latticeai/brain/memory.py +12 -97
  78. package/latticeai/brain/network.py +12 -200
  79. package/latticeai/brain/projection.py +13 -567
  80. package/latticeai/brain/provenance.py +13 -397
  81. package/latticeai/brain/retrieval.py +13 -1337
  82. package/latticeai/brain/schema.py +12 -635
  83. package/latticeai/brain/store.py +13 -233
  84. package/latticeai/brain/write_master.py +13 -221
  85. package/latticeai/core/agent.py +1 -1
  86. package/latticeai/core/agent_registry.py +2 -2
  87. package/latticeai/core/builtin_hooks.py +2 -2
  88. package/latticeai/core/graph_curator.py +6 -468
  89. package/latticeai/core/hooks.py +6 -749
  90. package/latticeai/core/marketplace.py +1 -1
  91. package/latticeai/core/multi_agent.py +6 -790
  92. package/latticeai/core/workflow_engine.py +6 -456
  93. package/latticeai/core/workspace_os.py +1 -1
  94. package/latticeai/services/agent_runtime.py +6 -564
  95. package/latticeai/services/ingestion.py +6 -313
  96. package/latticeai/services/kg_portability.py +6 -426
  97. package/latticeai/services/platform_runtime.py +3 -3
  98. package/latticeai/services/run_executor.py +1 -1
  99. package/latticeai/services/upload_service.py +1 -1
  100. package/p_reinforce.py +1 -1
  101. package/package.json +3 -6
  102. package/scripts/build_vercel_static.mjs +77 -0
  103. package/scripts/bump_version.py +1 -1
  104. package/scripts/check_markdown_links.mjs +75 -0
  105. package/scripts/wheel_smoke.py +7 -0
  106. package/src-tauri/Cargo.lock +1 -1
  107. package/src-tauri/Cargo.toml +1 -1
  108. package/src-tauri/src/main.rs +12 -2
  109. package/src-tauri/tauri.conf.json +1 -1
  110. package/static/app/asset-manifest.json +5 -5
  111. package/static/app/assets/index-CHHal8Zl.css +2 -0
  112. package/static/app/assets/index-pdzil9ac.js +333 -0
  113. package/static/app/assets/index-pdzil9ac.js.map +1 -0
  114. package/static/app/index.html +2 -2
  115. package/latticeai/api/deps.py +0 -15
  116. package/scripts/capture/README.md +0 -28
  117. package/scripts/capture/capture_enterprise.js +0 -8
  118. package/scripts/capture/capture_graph.js +0 -8
  119. package/scripts/capture/capture_onboarding.js +0 -8
  120. package/scripts/capture/capture_page.js +0 -43
  121. package/scripts/capture/capture_release_media.js +0 -125
  122. package/scripts/capture/capture_skills.js +0 -8
  123. package/scripts/capture/capture_v340.js +0 -88
  124. package/scripts/capture/capture_workspace.js +0 -8
  125. package/scripts/generate_diagrams.py +0 -512
  126. package/scripts/release-0.3.1.sh +0 -105
  127. package/scripts/take_screenshots.js +0 -69
  128. package/static/app/assets/index-BhPuj8rT.js +0 -333
  129. package/static/app/assets/index-BhPuj8rT.js.map +0 -1
  130. package/static/app/assets/index-yZswHE3d.css +0 -2
  131. package/static/css/tokens.3ba22e37.css +0 -260
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { FolderPlus, Globe2, HardDrive, Upload } from "lucide-react";
4
4
  import { latticeApi } from "@/api/client";
5
- import { ActionButton, DataPanel, EntityList, JsonView, Tabs } from "@/components/primitives";
5
+ import { ActionButton, DataPanel, EntityList, OperationResult, StructuredView, Tabs } from "@/components/primitives";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
8
8
  import { Input } from "@/components/ui/input";
@@ -59,7 +59,11 @@ function FilesPanel() {
59
59
  <span className="text-sm text-muted-foreground">PDF, DOCX, XLSX, PPTX, TXT, MD, CSV according to backend policy.</span>
60
60
  <input type="file" multiple className="sr-only" onChange={(e) => e.target.files && upload.mutate(e.target.files)} />
61
61
  </label>
62
- {upload.data ? <JsonView value={upload.data.map((item) => item.data)} /> : null}
62
+ {upload.data ? (
63
+ <div className="mt-3 space-y-2">
64
+ {upload.data.map((item, index) => <OperationResult key={index} result={item} successLabel="Upload completed" />)}
65
+ </div>
66
+ ) : null}
63
67
  </CardContent>
64
68
  </Card>
65
69
  <DataPanel title="Uploaded documents" result={docs.data}>
@@ -88,7 +92,7 @@ function LocalPanel() {
88
92
  <CardContent className="space-y-3">
89
93
  <Input value={path} onChange={(e) => setPath(e.target.value)} placeholder="/Users/me/Documents/project" />
90
94
  <Button disabled={!path.trim() || connect.isPending} onClick={() => connect.mutate()}>Connect and watch</Button>
91
- {connect.data ? <JsonView value={connect.data.data || connect.data.error} /> : null}
95
+ {connect.data ? <OperationResult result={connect.data} successLabel="Folder connection requested" /> : null}
92
96
  </CardContent>
93
97
  </Card>
94
98
  <DataPanel title="Connected sources" result={local.data}>
@@ -107,7 +111,7 @@ function LocalPanel() {
107
111
  )}
108
112
  </DataPanel>
109
113
  <DataPanel title="Local runtime probe" result={agent.data} className="xl:col-span-2">
110
- {(data) => <JsonView value={data} />}
114
+ {(data) => <StructuredView value={data} />}
111
115
  </DataPanel>
112
116
  </div>
113
117
  );
@@ -127,7 +131,7 @@ function BrowserPanel() {
127
131
  <Input value={url} onChange={(e) => setUrl(e.target.value)} placeholder="https://example.com/article" />
128
132
  <Button disabled={!url.trim() || read.isPending} onClick={() => read.mutate()}>Capture URL</Button>
129
133
  </div>
130
- {read.data ? <JsonView value={read.data.data || read.data.error} /> : null}
134
+ {read.data ? <OperationResult result={read.data} successLabel="URL capture requested" /> : null}
131
135
  </CardContent>
132
136
  </Card>
133
137
  );
@@ -139,10 +143,10 @@ function PipelinePanel() {
139
143
  return (
140
144
  <div className="grid gap-4 xl:grid-cols-2">
141
145
  <DataPanel title="Index pipeline" result={index.data}>
142
- {(data) => <JsonView value={data} />}
146
+ {(data) => <StructuredView value={data} />}
143
147
  </DataPanel>
144
148
  <DataPanel title="Graph totals" result={stats.data}>
145
- {(data) => <JsonView value={data} />}
149
+ {(data) => <StructuredView value={data} />}
146
150
  </DataPanel>
147
151
  <Card className="xl:col-span-2">
148
152
  <CardHeader>
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { Boxes, Cpu, PackagePlus, Plug, Puzzle } from "lucide-react";
4
4
  import { latticeApi } from "@/api/client";
5
- import { ActionButton, DataPanel, EntityList, JsonView, Tabs } from "@/components/primitives";
5
+ import { ActionButton, DataPanel, EntityList, OperationResult, StructuredView, Tabs } from "@/components/primitives";
6
6
  import { Badge } from "@/components/ui/badge";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -82,7 +82,7 @@ function ModelsPanel() {
82
82
  )}
83
83
  </DataPanel>
84
84
  <DataPanel title="Embedding provider" result={emb.data}>
85
- {(data) => <JsonView value={data} />}
85
+ {(data) => <StructuredView value={data} />}
86
86
  </DataPanel>
87
87
  </div>
88
88
  );
@@ -151,7 +151,7 @@ function McpPanel() {
151
151
  <Input value={query} onChange={(e) => setQuery(e.target.value)} />
152
152
  <Button onClick={() => rec.mutate()} disabled={!query.trim() || rec.isPending}>Recommend</Button>
153
153
  </div>
154
- {rec.data ? <JsonView value={rec.data.data || rec.data.error} /> : null}
154
+ {rec.data ? <OperationResult result={rec.data} successLabel="Recommendation completed" /> : null}
155
155
  </CardContent>
156
156
  </Card>
157
157
  </div>
@@ -1,14 +1,14 @@
1
1
  import * as React from "react";
2
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
- import { Activity, KeyRound, Network, ShieldCheck, UserCircle, Users } from "lucide-react";
3
+ import { Network, ShieldCheck, UserCircle, Users } from "lucide-react";
4
4
  import { latticeApi } from "@/api/client";
5
- import { ActionButton, DataPanel, EntityList, JsonView, KeyValueList, Tabs } from "@/components/primitives";
5
+ import { ActionButton, DataPanel, EmptyState, EntityList, KeyValueList, OperationResult, StatGrid, StructuredView, Tabs } from "@/components/primitives";
6
6
  import { Badge } from "@/components/ui/badge";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
9
9
  import { Input } from "@/components/ui/input";
10
10
  import { useAppStore } from "@/store/appStore";
11
- import { asArray, titleize } from "@/lib/utils";
11
+ import { asArray, shortId, titleize } from "@/lib/utils";
12
12
 
13
13
  type SystemTab = "account" | "workspaces" | "snapshots" | "activity" | "network" | "settings" | "admin";
14
14
 
@@ -84,11 +84,13 @@ function AccountPanel() {
84
84
  <Button variant="outline" onClick={() => changePassword.mutate()} disabled={!password || !newPassword || changePassword.isPending}>Change password</Button>
85
85
  <ActionButton label="Logout" action={() => latticeApi.logout()} invalidate={["profile"]} />
86
86
  </div>
87
- {[login.data, register.data, saveProfile.data, changePassword.data].filter(Boolean).map((item, i) => <JsonView key={i} value={item?.data || item?.error} />)}
87
+ {[login.data, register.data, saveProfile.data, changePassword.data].filter(Boolean).map((item, i) => (
88
+ <OperationResult key={i} result={item} successLabel="Account request completed" />
89
+ ))}
88
90
  </CardContent>
89
91
  </Card>
90
92
  <DataPanel title="SSO config" result={sso.data} className="xl:col-span-2">
91
- {(data) => <JsonView value={data} />}
93
+ {(data) => <StructuredView value={data} />}
92
94
  </DataPanel>
93
95
  </div>
94
96
  );
@@ -195,7 +197,7 @@ function SnapshotsPanel() {
195
197
  <Input value={after} onChange={(e) => setAfter(e.target.value)} placeholder="after id" />
196
198
  </div>
197
199
  <Button variant="outline" onClick={() => compare.mutate()} disabled={!before || !after || compare.isPending}>Compare</Button>
198
- {compare.data ? <JsonView value={compare.data.data || compare.data.error} /> : null}
200
+ {compare.data ? <OperationResult result={compare.data} successLabel="Snapshot comparison completed" /> : null}
199
201
  </CardContent>
200
202
  </Card>
201
203
  <DataPanel title="Time machine" result={timeline.data} className="xl:col-span-2">
@@ -214,7 +216,7 @@ function ActivityPanel() {
214
216
  {(data) => <EntityList items={(data as Record<string, unknown>).events} titleKey="event_type" metaKey="area" limit={14} />}
215
217
  </DataPanel>
216
218
  <DataPanel title="Presence" result={presence.data}>
217
- {(data) => <JsonView value={data} />}
219
+ {(data) => <PresenceView data={data as Record<string, unknown>} />}
218
220
  </DataPanel>
219
221
  </div>
220
222
  );
@@ -234,7 +236,7 @@ function NetworkPanel() {
234
236
  return (
235
237
  <div className="grid gap-4 xl:grid-cols-[0.8fr_1.2fr]">
236
238
  <DataPanel title="Device identity" result={identity.data}>
237
- {(data) => <JsonView value={data} />}
239
+ {(data) => <DeviceIdentityView data={data as Record<string, unknown>} />}
238
240
  </DataPanel>
239
241
  <Card>
240
242
  <CardHeader>
@@ -246,7 +248,7 @@ function NetworkPanel() {
246
248
  <Input value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder="http://peer.local:8765" />
247
249
  <Input value={publicKey} onChange={(e) => setPublicKey(e.target.value)} placeholder="trusted public key" />
248
250
  <Button disabled={!name || !baseUrl || !publicKey || pair.isPending} onClick={() => pair.mutate()}>Pair</Button>
249
- {pair.data ? <JsonView value={pair.data.data || pair.data.error} /> : null}
251
+ {pair.data ? <OperationResult result={pair.data} successLabel="Peer pairing request completed" /> : null}
250
252
  </CardContent>
251
253
  </Card>
252
254
  <DataPanel title="Peers" result={peers.data} className="xl:col-span-2">
@@ -289,6 +291,7 @@ function SettingsPanel() {
289
291
  const [restorePath, setRestorePath] = React.useState("");
290
292
  const [archivePassphrase, setArchivePassphrase] = React.useState("");
291
293
  const [restoreConfirm, setRestoreConfirm] = React.useState(false);
294
+ const [importConfirm, setImportConfirm] = React.useState(false);
292
295
  const docker = useMutation({ mutationFn: (consent: boolean) => latticeApi.dockerPostgres({ consent, dry_run: !consent, port: 5432 }) });
293
296
  const migration = useMutation({
294
297
  mutationFn: () => latticeApi.migratePostgres({ dsn, schema_name: schema || "lattice_brain", dry_run: true }),
@@ -313,6 +316,16 @@ function SettingsPanel() {
313
316
  qc.invalidateQueries({ queryKey: ["backupHealth"] });
314
317
  },
315
318
  });
319
+ const archiveImportDryRun = useMutation({
320
+ mutationFn: () => latticeApi.brainArchiveImport({ path: restorePath, passphrase: archivePassphrase, dry_run: true, confirm: false }),
321
+ });
322
+ const archiveImport = useMutation({
323
+ mutationFn: () => latticeApi.brainArchiveImport({ path: restorePath, passphrase: archivePassphrase, dry_run: false, confirm: importConfirm }),
324
+ onSuccess: () => {
325
+ qc.invalidateQueries({ queryKey: ["brainStorage"] });
326
+ qc.invalidateQueries({ queryKey: ["backupHealth"] });
327
+ },
328
+ });
316
329
  return (
317
330
  <div className="grid gap-4 xl:grid-cols-3">
318
331
  <Card>
@@ -329,16 +342,16 @@ function SettingsPanel() {
329
342
  </CardContent>
330
343
  </Card>
331
344
  <DataPanel title="Server health" result={health.data}>
332
- {(data) => <JsonView value={data} />}
345
+ {(data) => <HealthView data={data as Record<string, unknown>} />}
333
346
  </DataPanel>
334
347
  <DataPanel title="Host telemetry" result={sys.data}>
335
- {(data) => <JsonView value={data} />}
348
+ {(data) => <StructuredView value={data} />}
336
349
  </DataPanel>
337
350
  <DataPanel title="Brain storage" result={storage.data} className="xl:col-span-3">
338
- {(data) => <JsonView value={data} />}
351
+ {(data) => <StorageView data={data as Record<string, unknown>} />}
339
352
  </DataPanel>
340
353
  <DataPanel title="Backup health" result={backupHealth.data} className="xl:col-span-3">
341
- {(data) => <JsonView value={data} />}
354
+ {(data) => <BackupHealthView data={data as Record<string, unknown>} />}
342
355
  </DataPanel>
343
356
  <Card className="xl:col-span-3">
344
357
  <CardHeader>
@@ -356,14 +369,20 @@ function SettingsPanel() {
356
369
  <Button variant="outline" onClick={() => archiveInspect.mutate()} disabled={!restorePath || archiveInspect.isPending}>Inspect</Button>
357
370
  <Button variant="outline" onClick={() => archiveVerify.mutate()} disabled={!restorePath || !archivePassphrase || archiveVerify.isPending}>Verify</Button>
358
371
  <Button variant="outline" onClick={() => archiveDryRun.mutate()} disabled={!restorePath || !archivePassphrase || archiveDryRun.isPending}>Restore dry run</Button>
372
+ <Button variant="outline" onClick={() => archiveImportDryRun.mutate()} disabled={!restorePath || !archivePassphrase || archiveImportDryRun.isPending}>Import dry run</Button>
359
373
  <label className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm">
360
374
  <input type="checkbox" checked={restoreConfirm} onChange={(e) => setRestoreConfirm(e.target.checked)} />
361
375
  Confirm restore
362
376
  </label>
363
377
  <Button variant="destructive" onClick={() => archiveRestore.mutate()} disabled={!restorePath || !archivePassphrase || !restoreConfirm || archiveRestore.isPending}>Restore</Button>
378
+ <label className="flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm">
379
+ <input type="checkbox" checked={importConfirm} onChange={(e) => setImportConfirm(e.target.checked)} />
380
+ Confirm import
381
+ </label>
382
+ <Button variant="outline" onClick={() => archiveImport.mutate()} disabled={!restorePath || !archivePassphrase || !importConfirm || archiveImport.isPending}>Import</Button>
364
383
  </div>
365
- {[archiveCreate.data, archiveInspect.data, archiveVerify.data, archiveDryRun.data, archiveRestore.data].filter(Boolean).map((item, i) => (
366
- <JsonView key={i} value={item?.data || item?.error} />
384
+ {[archiveCreate.data, archiveInspect.data, archiveVerify.data, archiveDryRun.data, archiveRestore.data, archiveImportDryRun.data, archiveImport.data].filter(Boolean).map((item, i) => (
385
+ <OperationResult key={i} result={item} successLabel="Archive request completed" />
367
386
  ))}
368
387
  </CardContent>
369
388
  </Card>
@@ -386,14 +405,14 @@ function SettingsPanel() {
386
405
  <Button onClick={() => docker.mutate(true)} disabled={!dockerConsent || docker.isPending}>Start Docker</Button>
387
406
  <Button variant="outline" onClick={() => migration.mutate()} disabled={!dsn || migration.isPending}>Plan migration</Button>
388
407
  </div>
389
- {docker.data ? <JsonView value={docker.data.data || docker.data.error} /> : null}
390
- {migration.data ? <JsonView value={migration.data.data || migration.data.error} /> : null}
408
+ {docker.data ? <OperationResult result={docker.data} successLabel="Docker setup request completed" /> : null}
409
+ {migration.data ? <OperationResult result={migration.data} successLabel="Migration plan completed" /> : null}
391
410
  </CardContent>
392
411
  </Card>
393
412
  <DataPanel title="Computer memory" result={comp.data} className="xl:col-span-3">
394
413
  {(data) => (
395
414
  <div className="space-y-3">
396
- <JsonView value={data} />
415
+ <StructuredView value={data} />
397
416
  <div className="flex gap-2">
398
417
  <ActionButton label="Enable memory" action={() => latticeApi.setComputerMemory(true)} invalidate={["computerMemory"]} />
399
418
  <ActionButton label="Disable memory" action={() => latticeApi.setComputerMemory(false)} invalidate={["computerMemory"]} variant="destructive" />
@@ -405,6 +424,150 @@ function SettingsPanel() {
405
424
  );
406
425
  }
407
426
 
427
+ function isRecord(value: unknown): value is Record<string, unknown> {
428
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
429
+ }
430
+
431
+ function textValue(value: unknown, fallback = "not reported") {
432
+ if (value === null || value === undefined || value === "") return fallback;
433
+ if (typeof value === "boolean") return value ? "enabled" : "disabled";
434
+ return String(value);
435
+ }
436
+
437
+ function PresenceView({ data }: { data: Record<string, unknown> }) {
438
+ const rows = asArray<Record<string, unknown>>(data.presence || data.clients || data);
439
+ if (!rows.length) return <EmptyState title="No active presence" detail="No live collaborators or realtime clients are currently reported." />;
440
+ return <EntityList items={rows} titleKey="user" metaKey="workspace_id" />;
441
+ }
442
+
443
+ function DeviceIdentityView({ data }: { data: Record<string, unknown> }) {
444
+ const publicKey = textValue(data.public_key, "");
445
+ return (
446
+ <div className="space-y-3">
447
+ <div className="flex flex-wrap items-center gap-2">
448
+ <Badge variant="success">local device</Badge>
449
+ <Badge variant="muted">{textValue(data.algorithm, "identity key")}</Badge>
450
+ </div>
451
+ <KeyValueList data={{
452
+ device_id: data.device_id || data.id || "not reported",
453
+ fingerprint: data.fingerprint || "not reported",
454
+ public_key: publicKey ? shortId(publicKey.replace(/\s+/g, " "), 72) : "not reported",
455
+ }} />
456
+ </div>
457
+ );
458
+ }
459
+
460
+ function HealthView({ data }: { data: Record<string, unknown> }) {
461
+ return (
462
+ <div className="space-y-3">
463
+ <StatGrid stats={[
464
+ { label: "Status", value: data.status || data.ok || "reported" },
465
+ { label: "Version", value: data.version || "not reported" },
466
+ { label: "Mode", value: data.mode || data.environment || "local" },
467
+ { label: "Port", value: data.port || data.backend_port || "configured" },
468
+ ]} />
469
+ <StructuredView value={data} />
470
+ </div>
471
+ );
472
+ }
473
+
474
+ function StorageView({ data }: { data: Record<string, unknown> }) {
475
+ const active = isRecord(data.active) ? data.active : data;
476
+ const postgres = isRecord(data.postgres) ? data.postgres : {};
477
+ const backup = isRecord(data.backup_health) ? data.backup_health : {};
478
+ const vector = active.vector_search || active.vector || data.vector_search || data.sqlite_vec;
479
+ const postgresAvailable = Boolean(postgres.available || postgres.connected || postgres.enabled);
480
+ return (
481
+ <div className="space-y-4">
482
+ <StatGrid stats={[
483
+ { label: "Active engine", value: active.engine || data.engine || "sqlite" },
484
+ { label: "SQLite default", value: active.engine === "postgres" ? "scale mode" : "enabled" },
485
+ { label: "Vector search", value: vector || "not reported" },
486
+ { label: "Postgres", value: postgresAvailable ? "available" : "optional" },
487
+ ]} />
488
+ <div className="grid gap-3 md:grid-cols-3">
489
+ <StatusCard title="SQLite" status={active.available === false ? "unavailable" : "default"} detail={textValue(active.reason || active.path || data.path, "Local brain storage is active by default.")} />
490
+ <StatusCard title="Vector search" status={textValue(vector, "reported")} detail={textValue(active.vector_reason || active.sqlite_vec_reason || data.vector_reason, "Uses the configured local vector capability or reports why it is unavailable.")} />
491
+ <StatusCard title="Postgres" status={postgresAvailable ? "available" : "not enabled"} detail={textValue(postgres.reason || postgres.dsn || postgres.status, "Postgres scale mode is opt-in and never required for local use.")} />
492
+ </div>
493
+ {Object.keys(backup).length ? <StructuredView value={{ backup_health: backup }} /> : null}
494
+ </div>
495
+ );
496
+ }
497
+
498
+ function BackupHealthView({ data }: { data: Record<string, unknown> }) {
499
+ return (
500
+ <div className="space-y-3">
501
+ <StatGrid stats={[
502
+ { label: "Available", value: data.available === false ? "no" : "yes" },
503
+ { label: "Backups", value: data.count || data.backups || 0 },
504
+ { label: "Encrypted", value: data.encrypted_archives || 0 },
505
+ { label: "Zip backups", value: data.zip_backups || 0 },
506
+ ]} />
507
+ <KeyValueList data={{
508
+ directory: data.directory || "not reported",
509
+ latest: data.latest || "none reported",
510
+ last_verified: data.last_verified || data.verified_at || "not reported",
511
+ failure: data.error || data.reason || "none reported",
512
+ }} />
513
+ </div>
514
+ );
515
+ }
516
+
517
+ function StatusCard({ title, status, detail }: { title: string; status: string; detail: string }) {
518
+ const variant = /unavailable|failed|denied|disabled|not enabled/i.test(status) ? "warning" : "success";
519
+ return (
520
+ <div className="rounded-md border border-border bg-background p-3">
521
+ <div className="flex items-center justify-between gap-2">
522
+ <div className="font-medium">{title}</div>
523
+ <Badge variant={variant}>{status}</Badge>
524
+ </div>
525
+ <p className="mt-2 text-sm text-muted-foreground">{detail}</p>
526
+ </div>
527
+ );
528
+ }
529
+
530
+ function HardeningView({ data }: { data: Record<string, unknown> }) {
531
+ const startup = isRecord(data.startup) ? data.startup : {};
532
+ const privacy = isRecord(data.privacy) ? data.privacy : {};
533
+ const storage = isRecord(data.storage) ? data.storage : {};
534
+ const backup = isRecord(data.backup) ? data.backup : {};
535
+ const identity = isRecord(data.device_identity) ? data.device_identity : {};
536
+ const permissions = isRecord(data.permissions) ? data.permissions : {};
537
+ return (
538
+ <div className="space-y-3">
539
+ <StatGrid stats={[
540
+ { label: "Version", value: data.version || "reported" },
541
+ { label: "Local only", value: privacy.local_only_default ?? startup.local_only_default ?? "reported" },
542
+ { label: "Storage", value: isRecord(storage.active) ? (storage.active as Record<string, unknown>).engine : "reported" },
543
+ { label: "Backups", value: backup.count || backup.available || "reported" },
544
+ ]} />
545
+ <div className="grid gap-3 md:grid-cols-2">
546
+ <StatusCard title="Startup" status={startup.network_exposed ? "network exposed" : "local-only"} detail={`Host ${textValue(startup.host, "127.0.0.1")} on port ${textValue(startup.port, "configured")}.`} />
547
+ <StatusCard title="Integrations" status={privacy.local_only_default === false ? "review required" : "opt-in"} detail="External integrations remain disabled until the user explicitly enables them." />
548
+ <StatusCard title="Device identity" status={textValue(identity.algorithm || identity.fingerprint, "reported")} detail={textValue(identity.storage, "Stored locally and used for signed bundle exchange.")} />
549
+ <StatusCard title="Permissions" status={permissions.destructive_restore_requires_confirmation === false ? "review required" : "guarded"} detail="Export, import, and destructive restore permissions are surfaced through admin status." />
550
+ </div>
551
+ </div>
552
+ );
553
+ }
554
+
555
+ function SecurityView({ data }: { data: Record<string, unknown> }) {
556
+ const cards = isRecord(data.cards) ? data.cards : {};
557
+ const severities = isRecord(data.severity_counts) ? data.severity_counts : {};
558
+ return (
559
+ <div className="space-y-3">
560
+ <StatGrid stats={[
561
+ { label: "Events today", value: cards.events_today || 0 },
562
+ { label: "High risk", value: cards.high_risk_events || severities.high || 0 },
563
+ { label: "Review", value: cards.review_required || 0 },
564
+ { label: "Risk rate", value: data.risk_rate || 0 },
565
+ ]} />
566
+ <StructuredView value={{ severity_counts: severities, sensitive_fields: data.field_counts || {} }} />
567
+ </div>
568
+ );
569
+ }
570
+
408
571
  function AdminPanel() {
409
572
  const summary = useQuery({ queryKey: ["adminSummary"], queryFn: latticeApi.adminSummary });
410
573
  const users = useQuery({ queryKey: ["adminUsers"], queryFn: latticeApi.adminUsers });
@@ -419,15 +582,15 @@ function AdminPanel() {
419
582
  <DataPanel title="Admin summary" result={summary.data}>{(data) => <KeyValueList data={data as Record<string, unknown>} />}</DataPanel>
420
583
  <DataPanel title="Users" result={users.data}>{(data) => <EntityList items={data} titleKey="email" metaKey="role" />}</DataPanel>
421
584
  <DataPanel title="Audit" result={audit.data}>{(data) => <EntityList items={(data as Record<string, unknown>).recent_events || data} titleKey="act" metaKey="sev" />}</DataPanel>
422
- <DataPanel title="Roles" result={roles.data}>{(data) => <JsonView value={data} />}</DataPanel>
423
- <DataPanel title="Policies" result={policies.data}>{(data) => <JsonView value={data} />}</DataPanel>
424
- <DataPanel title="Product hardening" result={hardening.data}>{(data) => <JsonView value={data} />}</DataPanel>
425
- <DataPanel title="Security overview" result={security.data}>{(data) => <JsonView value={data} />}</DataPanel>
585
+ <DataPanel title="Roles" result={roles.data}>{(data) => <EntityList items={(data as Record<string, unknown>).roles || data} titleKey="role" metaKey="members" />}</DataPanel>
586
+ <DataPanel title="Policies" result={policies.data}>{(data) => <EntityList items={(data as Record<string, unknown>).policies || data} titleKey="label" metaKey="enforced" />}</DataPanel>
587
+ <DataPanel title="Product hardening" result={hardening.data}>{(data) => <HardeningView data={data as Record<string, unknown>} />}</DataPanel>
588
+ <DataPanel title="Security overview" result={security.data}>{(data) => <SecurityView data={data as Record<string, unknown>} />}</DataPanel>
426
589
  <DataPanel title="Private VPC" result={vpc.data} className="xl:col-span-2">
427
590
  {(data) => (
428
591
  <div className="space-y-2">
429
592
  <Badge variant="muted">Community-disabled features remain honest unavailable states.</Badge>
430
- <JsonView value={data} />
593
+ <StructuredView value={data} />
431
594
  </div>
432
595
  )}
433
596
  </DataPanel>
@@ -1,7 +1,14 @@
1
1
  """lattice-brain — independent Brain Core package for Lattice AI.
2
2
 
3
- Heavy graph modules are lazy-loaded so storage and archive utilities remain
4
- usable without importing the FastAPI application or creating runtime globals.
3
+ Physically hosts the knowledge graph (``lattice_brain.graph``), memory,
4
+ context assembly, conversations, ingestion, agent/hook runtime
5
+ (``lattice_brain.runtime``), workflow engine, portability (backup/restore and
6
+ ``.latticebrain`` archives), and the storage abstraction.
7
+
8
+ The package never imports ``latticeai``; FastAPI and the desktop product
9
+ import this package, not the other way around. Heavy graph modules are
10
+ lazy-loaded so storage and archive utilities remain usable without creating
11
+ runtime globals.
5
12
  """
6
13
 
7
14
  from .archive import BrainArchivePaths, EncryptedBrainArchive
@@ -19,9 +26,10 @@ from .storage import (
19
26
  storage_from_env,
20
27
  )
21
28
 
22
- __version__ = "4.3.1"
29
+ __version__ = "4.4.0"
23
30
 
24
31
  __all__ = [
32
+ "AgentRuntime",
25
33
  "AssembledContext",
26
34
  "BrainArchivePaths",
27
35
  "BrainCore",
@@ -33,7 +41,11 @@ __all__ = [
33
41
  "DockerPostgresPlan",
34
42
  "DockerPostgresWizard",
35
43
  "EncryptedBrainArchive",
44
+ "IngestionItem",
45
+ "IngestionPipeline",
46
+ "KGPortabilityService",
36
47
  "KnowledgeGraphStore",
48
+ "MultiAgentOrchestrator",
37
49
  "PostgresConfig",
38
50
  "PostgresEngine",
39
51
  "SQLiteEngine",
@@ -41,30 +53,33 @@ __all__ = [
41
53
  "StorageCapabilities",
42
54
  "StorageEngine",
43
55
  "StorageUnavailable",
56
+ "WorkflowEngine",
44
57
  "storage_from_env",
45
58
  "__version__",
46
59
  ]
47
60
 
61
+ _LAZY = {
62
+ "AssembledContext": ("context", "AssembledContext"),
63
+ "ContextAssembler": ("context", "ContextAssembler"),
64
+ "ContextSection": ("context", "ContextSection"),
65
+ "ConversationStore": ("conversations", "ConversationStore"),
66
+ "BrainMemory": ("memory", "BrainMemory"),
67
+ "KnowledgeGraphStore": ("graph.store", "KnowledgeGraphStore"),
68
+ "IngestionItem": ("ingestion", "IngestionItem"),
69
+ "IngestionPipeline": ("ingestion", "IngestionPipeline"),
70
+ "KGPortabilityService": ("portability", "KGPortabilityService"),
71
+ "WorkflowEngine": ("workflow", "WorkflowEngine"),
72
+ "AgentRuntime": ("runtime.agent_runtime", "AgentRuntime"),
73
+ "MultiAgentOrchestrator": ("runtime.multi_agent", "MultiAgentOrchestrator"),
74
+ }
48
75
 
49
- def __getattr__(name: str):
50
- if name in {"AssembledContext", "ContextAssembler", "ContextSection"}:
51
- from .context import AssembledContext, ContextAssembler, ContextSection
52
-
53
- return {
54
- "AssembledContext": AssembledContext,
55
- "ContextAssembler": ContextAssembler,
56
- "ContextSection": ContextSection,
57
- }[name]
58
- if name == "ConversationStore":
59
- from .conversations import ConversationStore
60
76
 
61
- return ConversationStore
62
- if name == "BrainMemory":
63
- from .memory import BrainMemory
64
-
65
- return BrainMemory
66
- if name == "KnowledgeGraphStore":
67
- from .store import KnowledgeGraphStore
77
+ def __getattr__(name: str):
78
+ target = _LAZY.get(name)
79
+ if target is None:
80
+ raise AttributeError(name)
81
+ module_path, attr = target
82
+ import importlib
68
83
 
69
- return KnowledgeGraphStore
70
- raise AttributeError(name)
84
+ module = importlib.import_module(f".{module_path}", __name__)
85
+ return getattr(module, attr)
@@ -1 +1,11 @@
1
- from latticeai.brain._kg_common import * # noqa: F401,F403
1
+ """Compatibility shim: implementation moved to lattice_brain.graph._kg_common.
2
+
3
+ This module aliases itself to the physical module so identity, singletons,
4
+ and monkeypatching behave as if the old flat path were the real module.
5
+ """
6
+
7
+ import sys
8
+
9
+ from .graph import _kg_common as _impl
10
+
11
+ sys.modules[__name__] = _impl