ltcai 4.0.0 → 4.1.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 (195) hide show
  1. package/README.md +42 -33
  2. package/desktop/electron/main.cjs +44 -0
  3. package/docs/CHANGELOG.md +106 -0
  4. package/docs/REALTIME_COLLABORATION.md +3 -3
  5. package/docs/V3_FRONTEND.md +9 -8
  6. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
  7. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
  8. package/docs/V4_1_VALIDATION_REPORT.md +47 -0
  9. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +95 -45
  10. package/docs/kg-schema.md +6 -2
  11. package/docs/spec-vs-impl.md +10 -10
  12. package/frontend/index.html +24 -0
  13. package/frontend/openapi.json +14190 -0
  14. package/frontend/src/App.tsx +184 -0
  15. package/frontend/src/api/client.ts +317 -0
  16. package/frontend/src/api/openapi.ts +16637 -0
  17. package/frontend/src/components/primitives.tsx +204 -0
  18. package/frontend/src/components/ui/badge.tsx +27 -0
  19. package/frontend/src/components/ui/button.tsx +37 -0
  20. package/frontend/src/components/ui/card.tsx +22 -0
  21. package/frontend/src/components/ui/input.tsx +16 -0
  22. package/frontend/src/components/ui/textarea.tsx +16 -0
  23. package/frontend/src/lib/utils.ts +33 -0
  24. package/frontend/src/main.tsx +23 -0
  25. package/frontend/src/pages/Act.tsx +245 -0
  26. package/frontend/src/pages/Ask.tsx +200 -0
  27. package/frontend/src/pages/Brain.tsx +267 -0
  28. package/frontend/src/pages/Capture.tsx +158 -0
  29. package/frontend/src/pages/Library.tsx +187 -0
  30. package/frontend/src/pages/System.tsx +344 -0
  31. package/frontend/src/routes.ts +85 -0
  32. package/frontend/src/store/appStore.ts +54 -0
  33. package/frontend/src/styles.css +107 -0
  34. package/kg_schema.py +2 -603
  35. package/knowledge_graph.py +37 -4958
  36. package/latticeai/__init__.py +1 -1
  37. package/latticeai/api/admin.py +15 -16
  38. package/latticeai/api/agents.py +13 -6
  39. package/latticeai/api/auth.py +19 -11
  40. package/latticeai/api/invitations.py +100 -0
  41. package/latticeai/api/knowledge_graph.py +4 -11
  42. package/latticeai/api/plugins.py +3 -6
  43. package/latticeai/api/realtime.py +4 -7
  44. package/latticeai/api/setup.py +5 -4
  45. package/latticeai/api/static_routes.py +13 -16
  46. package/latticeai/api/ui_redirects.py +26 -0
  47. package/latticeai/api/workflow_designer.py +39 -6
  48. package/latticeai/api/workspace.py +24 -10
  49. package/latticeai/app_factory.py +88 -17
  50. package/latticeai/brain/_kg_common.py +1123 -0
  51. package/latticeai/brain/discovery.py +1455 -0
  52. package/latticeai/brain/documents.py +218 -0
  53. package/latticeai/brain/ingest.py +644 -0
  54. package/latticeai/brain/projection.py +561 -0
  55. package/latticeai/brain/provenance.py +401 -0
  56. package/latticeai/brain/retrieval.py +1316 -0
  57. package/latticeai/brain/schema.py +640 -0
  58. package/latticeai/brain/store.py +216 -0
  59. package/latticeai/brain/write_master.py +225 -0
  60. package/latticeai/core/invitations.py +131 -0
  61. package/latticeai/core/marketplace.py +1 -1
  62. package/latticeai/core/multi_agent.py +1 -1
  63. package/latticeai/core/policy.py +54 -0
  64. package/latticeai/core/realtime.py +65 -44
  65. package/latticeai/core/sessions.py +31 -5
  66. package/latticeai/core/users.py +147 -0
  67. package/latticeai/core/workspace_os.py +420 -20
  68. package/latticeai/services/agent_runtime.py +242 -4
  69. package/latticeai/services/run_executor.py +328 -0
  70. package/latticeai/services/workspace_service.py +27 -19
  71. package/package.json +54 -27
  72. package/scripts/build_frontend_assets.mjs +38 -0
  73. package/scripts/bump_version.py +1 -1
  74. package/scripts/export_openapi.py +31 -0
  75. package/scripts/lint_frontend.mjs +86 -0
  76. package/scripts/run_python.mjs +47 -0
  77. package/src-tauri/Cargo.lock +4833 -0
  78. package/src-tauri/Cargo.toml +19 -0
  79. package/src-tauri/build.rs +3 -0
  80. package/src-tauri/capabilities/default.json +7 -0
  81. package/src-tauri/src/main.rs +78 -0
  82. package/src-tauri/tauri.conf.json +36 -0
  83. package/static/app/asset-manifest.json +32 -0
  84. package/static/app/assets/core-CwxXejkd.js +2 -0
  85. package/static/app/assets/core-CwxXejkd.js.map +1 -0
  86. package/static/app/assets/index-CJRAzNnf.js +333 -0
  87. package/static/app/assets/index-CJRAzNnf.js.map +1 -0
  88. package/static/app/assets/index-CSwBBgf4.css +2 -0
  89. package/static/app/index.html +25 -0
  90. package/static/manifest.json +2 -2
  91. package/static/sw.js +4 -4
  92. package/scripts/build_v3_assets.mjs +0 -170
  93. package/scripts/lint_v3.mjs +0 -97
  94. package/static/account.html +0 -113
  95. package/static/activity.html +0 -73
  96. package/static/admin.html +0 -486
  97. package/static/agents.html +0 -139
  98. package/static/chat.html +0 -841
  99. package/static/css/reference/account.css +0 -439
  100. package/static/css/reference/admin.css +0 -610
  101. package/static/css/reference/base.css +0 -1661
  102. package/static/css/reference/chat.css +0 -4623
  103. package/static/css/reference/graph.css +0 -1016
  104. package/static/css/responsive.css +0 -861
  105. package/static/graph.html +0 -122
  106. package/static/platform.css +0 -104
  107. package/static/plugins.html +0 -136
  108. package/static/scripts/account.js +0 -238
  109. package/static/scripts/admin.js +0 -1614
  110. package/static/scripts/chat.js +0 -5081
  111. package/static/scripts/graph.js +0 -1804
  112. package/static/scripts/platform.js +0 -64
  113. package/static/scripts/ux.js +0 -167
  114. package/static/scripts/workspace.js +0 -948
  115. package/static/v3/asset-manifest.json +0 -56
  116. package/static/v3/css/lattice.base.49deefb5.css +0 -128
  117. package/static/v3/css/lattice.base.css +0 -128
  118. package/static/v3/css/lattice.components.cde18231.css +0 -472
  119. package/static/v3/css/lattice.components.css +0 -472
  120. package/static/v3/css/lattice.shell.29d36d85.css +0 -452
  121. package/static/v3/css/lattice.shell.css +0 -452
  122. package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
  123. package/static/v3/css/lattice.tokens.css +0 -135
  124. package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
  125. package/static/v3/css/lattice.views.css +0 -360
  126. package/static/v3/index.html +0 -68
  127. package/static/v3/js/app.356e6452.js +0 -26
  128. package/static/v3/js/app.js +0 -26
  129. package/static/v3/js/core/api.7a308b89.js +0 -568
  130. package/static/v3/js/core/api.js +0 -568
  131. package/static/v3/js/core/components.f25b3b93.js +0 -230
  132. package/static/v3/js/core/components.js +0 -230
  133. package/static/v3/js/core/dom.a2773eb0.js +0 -148
  134. package/static/v3/js/core/dom.js +0 -148
  135. package/static/v3/js/core/router.584570f2.js +0 -37
  136. package/static/v3/js/core/router.js +0 -37
  137. package/static/v3/js/core/routes.7222343d.js +0 -93
  138. package/static/v3/js/core/routes.js +0 -93
  139. package/static/v3/js/core/shell.a1657f20.js +0 -391
  140. package/static/v3/js/core/shell.js +0 -391
  141. package/static/v3/js/core/store.204a08b2.js +0 -113
  142. package/static/v3/js/core/store.js +0 -113
  143. package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
  144. package/static/v3/js/views/admin-audit.js +0 -185
  145. package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
  146. package/static/v3/js/views/admin-permissions.js +0 -177
  147. package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
  148. package/static/v3/js/views/admin-policies.js +0 -102
  149. package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
  150. package/static/v3/js/views/admin-private-vpc.js +0 -135
  151. package/static/v3/js/views/admin-security.07c66b72.js +0 -180
  152. package/static/v3/js/views/admin-security.js +0 -180
  153. package/static/v3/js/views/admin-users.03bac88c.js +0 -168
  154. package/static/v3/js/views/admin-users.js +0 -168
  155. package/static/v3/js/views/agents.014d0b74.js +0 -541
  156. package/static/v3/js/views/agents.js +0 -541
  157. package/static/v3/js/views/chat.e6dd7dd0.js +0 -601
  158. package/static/v3/js/views/chat.js +0 -601
  159. package/static/v3/js/views/files.adad14c1.js +0 -365
  160. package/static/v3/js/views/files.js +0 -365
  161. package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
  162. package/static/v3/js/views/graph-canvas.js +0 -509
  163. package/static/v3/js/views/home.24f8b8ae.js +0 -200
  164. package/static/v3/js/views/home.js +0 -200
  165. package/static/v3/js/views/hooks.37895880.js +0 -220
  166. package/static/v3/js/views/hooks.js +0 -220
  167. package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
  168. package/static/v3/js/views/hybrid-search.js +0 -194
  169. package/static/v3/js/views/knowledge-graph.5e40cbeb.js +0 -509
  170. package/static/v3/js/views/knowledge-graph.js +0 -509
  171. package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
  172. package/static/v3/js/views/marketplace.js +0 -141
  173. package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
  174. package/static/v3/js/views/mcp.js +0 -114
  175. package/static/v3/js/views/memory.4ebdf474.js +0 -147
  176. package/static/v3/js/views/memory.js +0 -147
  177. package/static/v3/js/views/models.a1ffa147.js +0 -256
  178. package/static/v3/js/views/models.js +0 -256
  179. package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
  180. package/static/v3/js/views/my-computer.js +0 -463
  181. package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
  182. package/static/v3/js/views/pipeline.js +0 -157
  183. package/static/v3/js/views/planning.9ac3e313.js +0 -153
  184. package/static/v3/js/views/planning.js +0 -153
  185. package/static/v3/js/views/settings.8631fa5e.js +0 -318
  186. package/static/v3/js/views/settings.js +0 -318
  187. package/static/v3/js/views/skills.c6c2f965.js +0 -109
  188. package/static/v3/js/views/skills.js +0 -109
  189. package/static/v3/js/views/tools.e4f11276.js +0 -108
  190. package/static/v3/js/views/tools.js +0 -108
  191. package/static/v3/js/views/workflows.26c57290.js +0 -128
  192. package/static/v3/js/views/workflows.js +0 -128
  193. package/static/workflows.html +0 -146
  194. package/static/workspace.css +0 -1121
  195. package/static/workspace.html +0 -357
@@ -1,568 +0,0 @@
1
- /* ============================================================================
2
- * Lattice AI v3 — Integration adapter
3
- *
4
- * Every adapter call hits the real endpoint first (including /api/index/status,
5
- * /api/graph, /api/search/hybrid, and /chat). If that endpoint is
6
- * missing/unavailable, it returns an unavailable source with empty data so the
7
- * UI can render a clear unavailable state without inventing counters or health.
8
- *
9
- * Return shape (never throws): { ok, status, data, source, error }
10
- * source: "live" → returned by a real backend endpoint
11
- * "unavailable" → endpoint missing/down; no fake payload
12
- * ========================================================================== */
13
-
14
- import { store } from "./store.js";
15
-
16
- const TIMEOUT_MS = 8000;
17
- const EMPTY_INDEX_STATUS = { generated_at: null, pipelines: {}, sources: [] };
18
- const EMPTY_GRAPH_STATS = { nodes: {}, edges: {}, total_nodes: 0, total_edges: 0 };
19
- const EMPTY_WORKSPACE_OS = { counts: {}, models: {} };
20
- const EMPTY_SYSINFO = { cpu_pct: null, ram_pct: null, gpu_mem_pct: null, gpu_mem_gb: null };
21
- const EMPTY_ADMIN = {
22
- summary: { total_users: null, active_users: null, admin_users: null, total_messages: null },
23
- users: [],
24
- audit: { recent_events: [] },
25
- security: {},
26
- roles: { roles: [] },
27
- policies: { policies: [] },
28
- vpc: {},
29
- };
30
-
31
- async function raw(path, { method = "GET", body, headers } = {}) {
32
- const ctrl = new AbortController();
33
- const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
34
- try {
35
- const ws = store.get().workspaceId;
36
- const res = await fetch(path, {
37
- method,
38
- credentials: "same-origin",
39
- signal: ctrl.signal,
40
- headers: {
41
- "Accept": "application/json",
42
- ...(body ? { "Content-Type": "application/json" } : {}),
43
- ...(ws ? { "X-Workspace-Id": ws } : {}),
44
- ...headers,
45
- },
46
- body: body ? JSON.stringify(body) : undefined,
47
- });
48
- let data = null;
49
- const text = await res.text();
50
- if (text) { try { data = JSON.parse(text); } catch { data = { raw: text }; } }
51
- return { ok: res.ok, status: res.status, data };
52
- } catch (err) {
53
- return { ok: false, status: 0, data: null, error: err && err.name === "AbortError" ? "timeout" : String(err) };
54
- } finally {
55
- clearTimeout(timer);
56
- }
57
- }
58
-
59
- function unavailableData(shape) {
60
- const value = typeof shape === "function" ? shape() : shape;
61
- if (Array.isArray(value)) return [];
62
- if (value && typeof value === "object") return {};
63
- return null;
64
- }
65
-
66
- /** Try the live endpoint; on any non-2xx/transport failure, return empty data. */
67
- async function withFallback(path, opts, shape) {
68
- const res = await raw(path, opts);
69
- if (res.ok && res.data && !res.data.raw) {
70
- return { ...res, source: "live" };
71
- }
72
- return { ok: false, status: res.status, data: unavailableData(shape), source: "unavailable", error: res.error };
73
- }
74
-
75
- export const api = {
76
- raw,
77
-
78
- /** Generic GET with unavailable fallback. */
79
- async get(path, shape = null) {
80
- return withFallback(path, {}, shape);
81
- },
82
-
83
- /* ── Documented future surfaces ─────────────────────────────────────── */
84
-
85
- /** GET /api/index/status — KG + Vector + Hybrid pipeline state.
86
- * The backend endpoint is vector-centric (status/storage/source_items/…); the
87
- * home pillars + topbar chip want a `pipelines` view keyed by
88
- * knowledge_graph / vector_index / hybrid. Synthesize that shape from the real
89
- * index status (vectors) plus the KG stats endpoint (entities). Nothing is
90
- * fabricated: if the index endpoint is unavailable we report unavailable (so
91
- * the UI shows the honest empty state), and a missing graph-stats count yields
92
- * an "unavailable" graph pillar rather than a fake number. */
93
- async indexStatus() {
94
- const res = await raw("/api/index/status");
95
- if (!(res.ok && res.data && !res.data.raw)) {
96
- return { ok: false, status: res.status, data: EMPTY_INDEX_STATUS, source: "unavailable", error: res.error };
97
- }
98
- const idx = res.data;
99
- let entities = null;
100
- const gs = await raw("/knowledge-graph/stats");
101
- if (gs.ok && gs.data && !gs.data.raw) {
102
- const g = gs.data;
103
- const n = g.total_nodes ?? g.nodes_total ?? (g.nodes && (g.nodes.total ?? g.nodes.count));
104
- if (n !== undefined && n !== null) entities = Number(n) || 0;
105
- }
106
- const vectors = Number(idx.indexed_items ?? idx.ready_items) || 0;
107
- const vstate = idx.status === "ready" ? "ready" : "pending";
108
- const pipelines = {
109
- knowledge_graph: { state: entities === null ? "unavailable" : "ready", entities: entities ?? 0 },
110
- vector_index: { state: vstate, vectors },
111
- hybrid: { state: vstate, strategy: vstate === "ready" ? "fused" : "pending" },
112
- };
113
- return { ok: true, status: res.status, data: { ...idx, pipelines }, source: "live" };
114
- },
115
-
116
- /** POST /api/index/rebuild — rebuild the derived vector index (real run). */
117
- rebuildIndex(opts = {}) {
118
- return raw("/api/index/rebuild", { method: "POST", body: { full: false, include_nodes: true, include_chunks: true, ...opts } });
119
- },
120
-
121
- /** GET /api/graph — knowledge graph (nodes + edges). Falls back through the
122
- * current /knowledge-graph/graph route before reporting unavailable. */
123
- async graph(params = {}) {
124
- const qs = new URLSearchParams(params).toString();
125
- const primary = await raw(`/api/graph${qs ? "?" + qs : ""}`);
126
- if (primary.ok && primary.data && Array.isArray(primary.data.nodes)) {
127
- return { ...primary, source: "live" };
128
- }
129
- const legacy = await raw("/knowledge-graph/graph");
130
- if (legacy.ok && legacy.data && Array.isArray(legacy.data.nodes)) {
131
- return { ...legacy, source: "live" };
132
- }
133
- return { ok: false, status: primary.status || legacy.status || 0, data: { nodes: [], edges: [] }, source: "unavailable", error: primary.error || legacy.error };
134
- },
135
-
136
- graphStats() {
137
- return withFallback("/knowledge-graph/stats", {}, EMPTY_GRAPH_STATS);
138
- },
139
-
140
- /** POST /api/search/hybrid — fused KG + vector retrieval.
141
- * The backend returns `{ matches: [...] }` where each match carries
142
- * `source_scores: { keyword, vector, graph }`. Normalize that into the flat
143
- * result shape the view renders (title/path/snippet/score + per-signal). A
144
- * legacy `results` array is also accepted defensively. */
145
- async hybridSearch(query, opts = {}) {
146
- const res = await raw("/api/search/hybrid", { method: "POST", body: { query, ...opts } });
147
- const live = res.ok && res.data
148
- ? (Array.isArray(res.data.matches) ? res.data.matches
149
- : Array.isArray(res.data.results) ? res.data.results
150
- : null)
151
- : null;
152
- if (live) {
153
- const items = live.map((m) => {
154
- const ss = m.source_scores || {};
155
- const meta = m.metadata || {};
156
- return {
157
- id: m.id || m.node_id,
158
- title: m.title || m.id || "Untitled",
159
- path: meta.path || meta.source || m.path || m.type || "",
160
- snippet: m.snippet || m.summary || "",
161
- score: typeof m.score === "number" ? m.score : 0,
162
- vector: Number(ss.vector ?? m.vector) || 0,
163
- lexical: Number(ss.keyword ?? m.lexical) || 0,
164
- graph: Number(ss.graph ?? m.graph) || 0,
165
- };
166
- });
167
- return { ok: true, status: res.status, data: items, source: "live", weights: res.data.weights || null };
168
- }
169
- return { ok: false, status: res.status, data: [], source: "unavailable", error: res.error };
170
- },
171
-
172
- /* ── Existing surfaces (used where helpful, all fallback-safe) ──────── */
173
- workspaceOs() { return withFallback("/workspace/os", {}, EMPTY_WORKSPACE_OS); },
174
- async models() {
175
- const res = await raw("/models");
176
- if (res.ok && res.data && !res.data.raw) {
177
- const data = res.data;
178
- const loadedIds = Array.isArray(data.loaded) ? data.loaded : [];
179
- const recommended = Array.isArray(data.recommended) ? data.recommended.map((m) => ({
180
- ...m,
181
- name: m.name || m.display_name || m.id,
182
- family: m.family || m.modality || "local",
183
- state: loadedIds.includes(m.id) || data.current === m.id ? "loaded" : "available",
184
- })) : [];
185
- const loadedOnly = loadedIds
186
- .filter((id) => !recommended.some((m) => m.id === id))
187
- .map((id) => ({ id, name: id, family: "local", state: data.current === id ? "loaded" : "available" }));
188
- return {
189
- ok: true,
190
- status: res.status,
191
- source: "live",
192
- data: { ...data, catalog: Array.isArray(data.catalog) ? data.catalog : [...recommended, ...loadedOnly] },
193
- };
194
- }
195
- return { ok: false, status: res.status, data: { current: null, catalog: [] }, source: "unavailable", error: res.error };
196
- },
197
- loadModel(modelId, engine) {
198
- return raw("/models/load", { method: "POST", body: { model_id: modelId, engine: engine || null } });
199
- },
200
- unloadModel(modelId) {
201
- return raw(`/models/unload/${encodeURIComponent(modelId)}`, { method: "DELETE" });
202
- },
203
- sysinfo() { return withFallback("/local/sysinfo", {}, EMPTY_SYSINFO); },
204
-
205
- /** POST /upload/document — manual document ingest (multipart/form-data).
206
- * Real backend path: parse → chunk → embed → knowledge-graph ingest
207
- * (latticeai/api/tools.py:/upload/document). Returns { ok, status, data,
208
- * source }; never throws. FormData must NOT carry a JSON Content-Type — the
209
- * browser sets the multipart boundary itself. */
210
- async uploadDocument(file) {
211
- const ws = store.get().workspaceId;
212
- const form = new FormData();
213
- form.append("file", file);
214
- try {
215
- const res = await fetch("/upload/document", {
216
- method: "POST",
217
- credentials: "same-origin",
218
- headers: { "Accept": "application/json", ...(ws ? { "X-Workspace-Id": ws } : {}) },
219
- body: form,
220
- });
221
- let data = null;
222
- const text = await res.text();
223
- if (text) { try { data = JSON.parse(text); } catch { data = { raw: text }; } }
224
- return { ok: res.ok, status: res.status, data, source: res.ok ? "live" : "unavailable" };
225
- } catch (err) {
226
- return { ok: false, status: 0, data: null, source: "unavailable", error: String(err) };
227
- }
228
- },
229
-
230
- adminSummary() { return withFallback("/admin/summary", {}, EMPTY_ADMIN.summary); },
231
- adminUsers() { return withFallback("/admin/users", {}, EMPTY_ADMIN.users); },
232
- adminAudit() { return withFallback("/admin/audit", {}, EMPTY_ADMIN.audit); },
233
- adminSecurity() { return withFallback("/admin/security/overview", {}, EMPTY_ADMIN.security); },
234
- adminRoles() { return withFallback("/admin/roles", {}, EMPTY_ADMIN.roles); },
235
- adminPolicies() { return withFallback("/admin/policies", {}, EMPTY_ADMIN.policies); },
236
- vpcStatus() { return withFallback("/vpc/status", {}, EMPTY_ADMIN.vpc); },
237
-
238
- /* ── Embeddings (real backend: /api/embeddings/*) ───────────────────── */
239
- /** GET /api/embeddings/status — active provider, grade, dimensions, last index. */
240
- async embeddingsStatus() {
241
- const res = await raw("/api/embeddings/status");
242
- if (res.ok && res.data && res.data.provider) {
243
- return { ok: true, status: res.status, data: res.data, source: "live" };
244
- }
245
- // No backend → report unavailable honestly (never fabricate a provider).
246
- return {
247
- ok: false, status: res.status, source: "unavailable",
248
- data: { provider: null, active_provider: null, model: null,
249
- model_id: null, dimensions: null, grade: "unavailable",
250
- state: "unavailable", fell_back: false, health: { status: "unavailable", detail: "backend unavailable" },
251
- last_indexed_at: null },
252
- };
253
- },
254
- embeddingsProviders() { return withFallback("/api/embeddings/providers", {}, { active: "hash", providers: [] }); },
255
-
256
- /* ── Agents (real backend: AgentRuntime /agents/api/runtime/*) ───────── */
257
- /** GET /agents/api/runtime/status — roles, roster, runs, health from the runtime. */
258
- async agentRuntime() {
259
- const res = await raw("/agents/api/runtime/status");
260
- if (res.ok && res.data && res.data.runtime && Array.isArray(res.data.agents)) {
261
- return { ok: true, status: res.status, data: res.data, source: "live" };
262
- }
263
- // Fallback: unavailable roster, no fabricated run ledger.
264
- return {
265
- ok: false, status: res.status, source: "unavailable",
266
- data: { runtime: { ready: false, total_runs: 0, active_runs: 0 },
267
- health: { status: "unknown", checks: {} }, roles: [],
268
- agents: [], runs: [] },
269
- };
270
- },
271
- /** POST /agents/api/run — execute the multi-agent pipeline for a goal. */
272
- runAgent(goal, roles) { return raw("/agents/api/run", { method: "POST", body: { goal, roles: roles || [] } }); },
273
-
274
- /* ── Local computer memory (real backend: /workspace/computer-memory) ── */
275
- computerMemory() { return raw("/workspace/computer-memory"); },
276
- setComputerMemory(enabled) {
277
- return raw("/workspace/computer-memory", { method: "POST", body: { enabled, consent: { approved: !!enabled } } });
278
- },
279
-
280
- /* ── Organization workspaces (real backend: /workspace/orgs) ────────── */
281
- createOrg(name) { return raw("/workspace/orgs", { method: "POST", body: { name } }); },
282
-
283
- /* ── Chat (real backend: SSE /chat + /history/*) ────────────────────── */
284
-
285
- /** GET /history/conversations — conversation list. */
286
- async chatHistory() {
287
- const res = await raw("/history/conversations");
288
- const list = res.ok && Array.isArray(res.data) ? res.data
289
- : res.ok && res.data && Array.isArray(res.data.conversations) ? res.data.conversations
290
- : null;
291
- if (list) return { ok: true, status: res.status, data: list, source: "live" };
292
- return { ok: false, status: res.status, data: [], source: "unavailable" };
293
- },
294
-
295
- /** GET /history/conversations/{id} — messages for one conversation. */
296
- async conversation(id) {
297
- const res = await raw(`/history/conversations/${encodeURIComponent(id)}`);
298
- if (res.ok && res.data && Array.isArray(res.data.messages)) {
299
- return { ok: true, status: res.status, data: res.data.messages, source: "live" };
300
- }
301
- return { ok: false, status: res.status, data: [], source: "unavailable" };
302
- },
303
-
304
- deleteConversation(id) {
305
- return raw(`/history/conversations/${encodeURIComponent(id)}`, { method: "DELETE" });
306
- },
307
-
308
- /**
309
- * POST /chat — streams the assistant reply over SSE.
310
- * Parses `data: {chunk, model, trace}` events (terminator `[DONE]`), calling
311
- * onChunk(delta, fullText) and onTrace(trace). If the endpoint is missing or
312
- * not an event-stream, reports that chat is unavailable (no generated answer is
313
- * invented). Resolves { source, text, trace, model, aborted }.
314
- */
315
- async streamChat(body, { onChunk, onTrace, signal } = {}) {
316
- const ws = store.get().workspaceId;
317
- let res;
318
- try {
319
- res = await fetch("/chat", {
320
- method: "POST",
321
- credentials: "same-origin",
322
- signal,
323
- headers: {
324
- "Content-Type": "application/json",
325
- "Accept": "text/event-stream",
326
- ...(ws ? { "X-Workspace-Id": ws } : {}),
327
- },
328
- body: JSON.stringify({ stream: true, max_tokens: 2048, temperature: 0.2, ...body }),
329
- });
330
- } catch (err) {
331
- if (err && err.name === "AbortError") return { source: "live", text: "", aborted: true };
332
- return simulateChat(body, { onChunk, onTrace, signal });
333
- }
334
- const ctype = res.headers.get("content-type") || "";
335
- if (!res.ok) {
336
- let data = null;
337
- try { data = await res.clone().json(); } catch {}
338
- const detail = data && (data.detail || data.message || data.error);
339
- const noModel = data && (data.error === "no_model_loaded" || /no .*model .*loaded/i.test(String(detail || "")));
340
- if (noModel) {
341
- return { source: "live", text: "", error: "no_model_loaded", errorMessage: String(detail || "No local model is loaded.") };
342
- }
343
- return simulateChat(body, { onChunk, onTrace, signal });
344
- }
345
- if (!res.body || !ctype.includes("text/event-stream")) {
346
- return simulateChat(body, { onChunk, onTrace, signal });
347
- }
348
- const reader = res.body.getReader();
349
- const decoder = new TextDecoder();
350
- let buf = "", text = "", trace = null, model = null;
351
- try {
352
- for (;;) {
353
- const { value, done } = await reader.read();
354
- if (done) break;
355
- buf += decoder.decode(value, { stream: true });
356
- const parts = buf.split("\n\n");
357
- buf = parts.pop();
358
- for (const part of parts) {
359
- const line = part.split("\n").find((l) => l.startsWith("data:"));
360
- if (!line) continue;
361
- const rawData = line.slice(5).trim();
362
- if (rawData === "[DONE]") return { source: "live", text, trace, model };
363
- let data; try { data = JSON.parse(rawData); } catch { continue; }
364
- // Standard chat streams `chunk`; the document-generation path streams
365
- // `text` (report body + footnotes). Accept both so doc requests render
366
- // instead of falsely reporting the backend as unreachable.
367
- const delta = data.chunk || data.text;
368
- if (delta) { text += delta; onChunk && onChunk(delta, text); }
369
- if (data.model) model = data.model;
370
- if (data.trace) { trace = data.trace; onTrace && onTrace(trace); }
371
- }
372
- }
373
- } catch (err) {
374
- if (err && err.name === "AbortError") return { source: "live", text, trace, model, aborted: true };
375
- if (!text) return simulateChat(body, { onChunk, onTrace, signal });
376
- }
377
- return { source: "live", text, trace, model };
378
- },
379
-
380
- /* ── v3.2 platform surfaces (all fallback-safe; never fabricate) ─────── */
381
-
382
- // Agent Registry (Part 2)
383
- agentRegistry(type) { return withFallback(`/agents/api/registry${type ? "?type=" + encodeURIComponent(type) : ""}`, {}, { agents: [], counts: {}, types: [] }); },
384
- agentCapabilities() { return withFallback("/agents/api/registry/capabilities", {}, { capabilities: {} }); },
385
- registerAgent(body) { return raw("/agents/api/registry", { method: "POST", body }); },
386
- updateAgent(id, body) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "PATCH", body }); },
387
- removeAgent(id) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "DELETE" }); },
388
- agentRunDetail(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}`); },
389
- agentRunReplay(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/replay`); },
390
- stopAgentRun(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/stop`, { method: "POST" }); },
391
-
392
- // Marketplace + Templates (Parts 3, 4)
393
- templates(kind) { return withFallback(`/marketplace/templates${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { templates: [], kinds: [] }); },
394
- templateRegistry() { return withFallback("/marketplace/templates/registry", {}, { registry: [] }); },
395
- exportTemplate(kind, id) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/export`); },
396
- importTemplate(data) { return raw("/marketplace/templates/import", { method: "POST", body: { data } }); },
397
- installTemplate(data) { return raw("/marketplace/templates/install", { method: "POST", body: { data } }); },
398
- cloneTemplate(kind, id, name) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/clone`, { method: "POST", body: { name } }); },
399
- pluginsRegistry() { return withFallback("/plugins/registry", {}, { plugins: [] }); },
400
- pluginsDirectory() { return withFallback("/plugins/directory", {}, { plugins: [], categories: [] }); },
401
-
402
- // Workflow Agents (Part 5)
403
- workflowDefinitions() { return withFallback("/workflows/api/definitions", {}, { workflows: [] }); },
404
- createWorkflow(body) { return raw("/workflows/api/definitions", { method: "POST", body }); },
405
- runWorkflow(id, body = {}) { return raw(`/workflows/api/definitions/${encodeURIComponent(id)}/run`, { method: "POST", body }); },
406
- workflowRuns() { return withFallback("/workflows/api/runs", {}, { runs: [] }); },
407
- workflowReplay(runId) { return raw(`/workflows/api/runs/${encodeURIComponent(runId)}/replay`); },
408
-
409
- // Long-Term Memory + Memory Manager (Parts 7, 8)
410
- memoryManager() { return withFallback("/api/memory/manager", {}, { sources: [], tiers: [], usage: {} }); },
411
- memoryTiers() { return withFallback("/api/memory/tiers", {}, { tiers: [], workspace_kinds: [] }); },
412
- memoryInspect(source, limit = 50) { return withFallback(`/api/memory/inspect?source=${encodeURIComponent(source)}&limit=${limit}`, {}, { items: [] }); },
413
- memoryRecall(query, limit = 20) { return raw("/api/memory/recall", { method: "POST", body: { query, limit } }); },
414
- memoryPrune(body) { return raw("/api/memory/prune", { method: "POST", body }); },
415
- memoryCompact() { return raw("/api/memory/compact", { method: "POST", body: {} }); },
416
- memoryRebuild(target = "vector") { return raw("/api/memory/rebuild", { method: "POST", body: { target } }); },
417
- memoryClear(scope, confirm = true) { return raw("/api/memory/clear", { method: "POST", body: { scope, confirm } }); },
418
- workspaceMemories(kind) { return withFallback(`/workspace/memories${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { memories: [] }); },
419
-
420
- // Skills Registry (Part 9)
421
- skills() { return withFallback("/workspace/skills", {}, { skills: [] }); },
422
- skillEnable(skill) { return raw("/workspace/skills/enable", { method: "POST", body: { skill } }); },
423
- skillDisable(skill) { return raw("/workspace/skills/disable", { method: "POST", body: { skill } }); },
424
- skillInstall(skill, plugin) { return raw("/workspace/skills/install", { method: "POST", body: { skill, plugin: plugin || "" } }); },
425
- skillUninstall(skill) { return raw("/workspace/skills/uninstall", { method: "POST", body: { skill } }); },
426
- skillsMarketplace() { return withFallback("/skills/marketplace", {}, { skills: [], categories: [] }); },
427
-
428
- // Hooks Registry (Part 10)
429
- hooks(kind) { return withFallback(`/api/hooks${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { hooks: [], kinds: [], counts: {} }); },
430
- hookEnable(hook_id, enabled = true) { return raw("/api/hooks/enable", { method: "POST", body: { hook_id, enabled } }); },
431
- hookDisable(hook_id) { return raw("/api/hooks/disable", { method: "POST", body: { hook_id, enabled: false } }); },
432
- hookReorder(kind, ordered_ids) { return raw("/api/hooks/reorder", { method: "POST", body: { kind, ordered_ids } }); },
433
- hookRegister(body) { return raw("/api/hooks/register", { method: "POST", body }); },
434
- hookRemove(hook_id) { return raw(`/api/hooks/${encodeURIComponent(hook_id)}`, { method: "DELETE" }); },
435
-
436
- // Tool Registry + MCP (Parts 11, 12)
437
- toolPermissions() { return withFallback("/tools/permissions", {}, { permissions: [] }); },
438
- mcpTools() { return withFallback("/mcp/tools", {}, { tools: [], installed_mcps: [] }); },
439
- mcpInstalled() { return withFallback("/mcp/installed", {}, { installed: [] }); },
440
- mcpClaudeServers() { return withFallback("/mcp/claude-code-servers", {}, { servers: [] }); },
441
- mcpCustom() { return withFallback("/mcp/custom", {}, { custom: [] }); },
442
- mcpRecommend(query, limit = 6) { return raw("/mcp/recommend", { method: "POST", body: { query, limit } }); },
443
-
444
- /* ── v3.4 Platform Completion ───────────────────────────────────────────
445
- * Uploaded documents in Files, Connect Folder + Folder Watch over the real
446
- * on-device runtime, the Local Agent status, and Hooks dispatch/run-log.
447
- * All endpoints are real (latticeai/api + knowledge_graph_api); fallback-safe. */
448
-
449
- /** GET /knowledge-graph/documents — uploaded + indexed docs with index state. */
450
- async documents(limit = 200) {
451
- const res = await raw(`/knowledge-graph/documents?limit=${encodeURIComponent(limit)}`);
452
- if (res.ok && res.data && Array.isArray(res.data.documents)) {
453
- return { ok: true, status: res.status, data: res.data.documents, source: "live", total: res.data.total };
454
- }
455
- return { ok: false, status: res.status, data: [], source: "unavailable", error: res.error };
456
- },
457
-
458
- // Local Agent (the on-device Lattice runtime: real GET /api/local-agent/status)
459
- async localAgent() {
460
- const res = await raw("/api/local-agent/status");
461
- if (res.ok && res.data && res.data.agent) {
462
- return { ok: true, status: res.status, data: res.data, source: "live" };
463
- }
464
- return {
465
- ok: false, status: res.status, source: "unavailable",
466
- data: { agent: { online: false }, health: {}, folders: { connected: 0, watching: 0 }, watch: { available: false, active: {} }, sources: [] },
467
- };
468
- },
469
-
470
- // Connect Folder + Folder Watch (real backend: /knowledge-graph/local/*)
471
- localRoots() { return withFallback("/knowledge-graph/local/roots", {}, { roots: [] }); },
472
- async localSources() {
473
- const res = await raw("/knowledge-graph/local/sources");
474
- if (res.ok && res.data && Array.isArray(res.data.sources)) {
475
- return { ok: true, status: res.status, data: res.data, source: "live" };
476
- }
477
- return { ok: false, status: res.status, data: { sources: [], watch: { available: false, active: {} } }, source: "unavailable" };
478
- },
479
- localWatchStatus() { return raw("/knowledge-graph/local/watch/status"); },
480
- localWatchStop(source_id) { return raw("/knowledge-graph/local/watch/stop", { method: "POST", body: { source_id } }); },
481
- approvePermission(token) { return raw(`/permissions/approve/${encodeURIComponent(token)}`, { method: "POST" }); },
482
- indexFolder(path, opts = {}) {
483
- return raw("/knowledge-graph/local/index", { method: "POST", body: { path, ...opts } });
484
- },
485
- /** One-call Connect Folder: request → self-approve (the click is the consent)
486
- * → index (+ optional watch). Returns { ok, data, error }. */
487
- async connectFolder(path, { watch = true, includeOcr = false } = {}) {
488
- const probe = await raw("/knowledge-graph/local/index", { method: "POST", body: { path, approved: false } });
489
- const token = probe.data && probe.data.approval_token;
490
- if (!token) {
491
- const detail = (probe.data && (probe.data.detail || probe.data.error)) || "the runtime did not return an approval token";
492
- return { ok: false, error: detail, status: probe.status };
493
- }
494
- const approved = await raw(`/permissions/approve/${encodeURIComponent(token)}`, { method: "POST" });
495
- if (!approved.ok) {
496
- const detail = (approved.data && (approved.data.detail || approved.data.error)) || "approval failed";
497
- return { ok: false, error: detail, status: approved.status };
498
- }
499
- const res = await raw("/knowledge-graph/local/index", {
500
- method: "POST",
501
- body: { path, approved: true, approval_token: token, watch_enabled: watch, include_ocr: includeOcr, consent: { approved: true, source: "files-ui" } },
502
- });
503
- if (res.ok && res.data && !res.data.detail) return { ok: true, data: res.data, status: res.status };
504
- return { ok: false, error: (res.data && (res.data.detail || res.data.error)) || "indexing failed", status: res.status, data: res.data };
505
- },
506
-
507
- // Hooks dispatch (real backend: POST /api/hooks/run + GET /api/hooks/runs)
508
- hookRun(body) { return raw("/api/hooks/run", { method: "POST", body }); },
509
- hookRuns(limit = 50, kind) { return withFallback(`/api/hooks/runs?limit=${encodeURIComponent(limit)}${kind ? "&kind=" + encodeURIComponent(kind) : ""}`, {}, { runs: [], total: 0 }); },
510
-
511
- /* ── v3.6 Knowledge Graph First: ingestion provenance + portability ─────
512
- * The graph is the durable asset; these surface its health, where every node
513
- * came from, and local export/import/backup. All fallback-safe; never fake. */
514
-
515
- /** GET /api/knowledge-graph/portability — schema versions + stats + provenance counts. */
516
- async kgPortability() {
517
- const res = await raw("/api/knowledge-graph/portability");
518
- if (res.ok && res.data && res.data.available) {
519
- return { ok: true, status: res.status, data: res.data, source: "live" };
520
- }
521
- return {
522
- ok: false, status: res.status, source: "unavailable",
523
- data: { available: false, graph_schema_version: null, embed_dim: null,
524
- stats: { nodes: {}, edges: {} },
525
- provenance: { total: 0, by_source_type: {}, embedded: 0, duplicates: 0, last_ingested_at: null } },
526
- };
527
- },
528
-
529
- /** GET /api/knowledge-graph/provenance — recent ingestions (newest first). */
530
- kgProvenance(limit = 50, sourceType) {
531
- const qs = `?limit=${encodeURIComponent(limit)}${sourceType ? "&source_type=" + encodeURIComponent(sourceType) : ""}`;
532
- return withFallback(`/api/knowledge-graph/provenance${qs}`, {}, { items: [], count: 0 });
533
- },
534
-
535
- /** POST /api/knowledge-graph/export — logical JSON export of the whole graph. */
536
- graphExport() { return raw("/api/knowledge-graph/export", { method: "POST", body: {} }); },
537
-
538
- /** POST /api/knowledge-graph/import — import an export artifact (merge|replace). */
539
- graphImport(artifact, mode = "merge", dryRun = false) {
540
- return raw("/api/knowledge-graph/import", { method: "POST", body: { artifact, mode, dry_run: dryRun } });
541
- },
542
-
543
- /** POST /api/knowledge-graph/backup — binary backup (sqlite + blobs) to a local zip. */
544
- graphBackup() { return raw("/api/knowledge-graph/backup", { method: "POST", body: {} }); },
545
-
546
- /** POST /api/browser/read-url — fetch a public URL locally into the graph. */
547
- browserReadUrl(url) { return raw("/api/browser/read-url", { method: "POST", body: { url } }); },
548
- };
549
-
550
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
551
-
552
- /** Transparent unavailable stream — used only when no chat backend is available. */
553
- async function simulateChat(body, { onChunk, onTrace, signal } = {}) {
554
- const q = (body && body.message) || "your question";
555
- const reply =
556
- `Chat is unavailable because the Lattice backend or active model is not reachable. ` +
557
- `Start the server, load a model, and rebuild retrieval before sending “${q}”.`;
558
- let text = "";
559
- for (const word of reply.split(" ")) {
560
- if (signal && signal.aborted) return { source: "unavailable", text, aborted: true };
561
- const delta = (text ? " " : "") + word;
562
- text += delta;
563
- onChunk && onChunk(delta, text);
564
- await sleep(16);
565
- }
566
- onTrace && onTrace(null);
567
- return { source: "unavailable", text, trace: null };
568
- }