ltcai 8.0.0 → 8.2.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 (183) hide show
  1. package/README.md +33 -72
  2. package/docs/CHANGELOG.md +52 -874
  3. package/docs/DEVELOPMENT.md +26 -3
  4. package/docs/LEGACY_COMPATIBILITY.md +3 -1
  5. package/docs/TRUST_MODEL.md +26 -26
  6. package/docs/WHY_LATTICE.md +28 -25
  7. package/docs/kg-schema.md +6 -4
  8. package/lattice_brain/__init__.py +1 -1
  9. package/lattice_brain/embeddings.py +9 -1
  10. package/lattice_brain/graph/schema.py +6 -0
  11. package/lattice_brain/runtime/multi_agent.py +1 -1
  12. package/latticeai/__init__.py +1 -1
  13. package/latticeai/api/computer_use.py +2 -0
  14. package/latticeai/api/memory.py +13 -0
  15. package/latticeai/app_factory.py +43 -162
  16. package/latticeai/brain/__init__.py +1 -1
  17. package/latticeai/core/config.py +10 -0
  18. package/latticeai/core/local_embeddings.py +8 -0
  19. package/latticeai/core/marketplace.py +1 -1
  20. package/latticeai/core/mcp_registry.py +140 -1
  21. package/latticeai/core/tool_registry.py +10 -0
  22. package/latticeai/core/workspace_graph_trace.py +132 -0
  23. package/latticeai/core/workspace_memory.py +75 -0
  24. package/latticeai/core/workspace_os.py +135 -1283
  25. package/latticeai/core/workspace_os_utils.py +132 -0
  26. package/latticeai/core/workspace_permissions.py +99 -0
  27. package/latticeai/core/workspace_plugins.py +97 -0
  28. package/latticeai/core/workspace_runs.py +612 -0
  29. package/latticeai/core/workspace_skills.py +114 -0
  30. package/latticeai/core/workspace_snapshots.py +195 -0
  31. package/latticeai/core/workspace_timeline.py +107 -0
  32. package/latticeai/runtime/audit_runtime.py +64 -0
  33. package/latticeai/runtime/config_runtime.py +4 -0
  34. package/latticeai/runtime/sso_runtime.py +52 -0
  35. package/latticeai/services/architecture_readiness.py +6 -5
  36. package/latticeai/services/memory_service.py +215 -7
  37. package/latticeai/services/model_capability_registry.py +68 -0
  38. package/latticeai/services/model_engines.py +603 -0
  39. package/latticeai/services/model_loading.py +482 -0
  40. package/latticeai/services/model_recommendation.py +5 -0
  41. package/latticeai/services/model_runtime.py +146 -837
  42. package/latticeai/services/product_readiness.py +19 -18
  43. package/package.json +2 -2
  44. package/scripts/build_frontend_assets.mjs +11 -2
  45. package/src-tauri/Cargo.lock +1 -1
  46. package/src-tauri/Cargo.toml +1 -1
  47. package/src-tauri/tauri.conf.json +1 -1
  48. package/static/app/asset-manifest.json +11 -102
  49. package/static/app/assets/Act-D9jIknFd.js +1 -0
  50. package/static/app/assets/Brain-CFOtWbPN.js +321 -0
  51. package/static/app/assets/{Capture-B9Tlhzqr.js → Capture-Q4WYzwr5.js} +1 -2
  52. package/static/app/assets/Library-C5Q2yWee.js +1 -0
  53. package/static/app/assets/System-BLbjdr1_.js +1 -0
  54. package/static/app/assets/core-CwxXejkd.js +1 -2
  55. package/static/app/assets/index-BqammyNu.js +16 -0
  56. package/static/app/assets/index-ty1iGgZu.css +2 -0
  57. package/static/app/assets/primitives-Br8uSfZ4.js +1 -0
  58. package/static/app/assets/{textarea-BZk6ybp5.js → textarea-BnhNs1_X.js} +1 -2
  59. package/static/app/index.html +3 -10
  60. package/static/app/theme-boot.js +8 -0
  61. package/static/sw.js +1 -1
  62. package/tools/__init__.py +2 -1
  63. package/tools/computer.py +21 -0
  64. package/docs/CARRYOVER_AUDIT_v3.6.0.md +0 -61
  65. package/docs/HANDOVER_v3.6.0.md +0 -46
  66. package/docs/RUNTIME_HOOK_COVERAGE_v3.5.0.md +0 -56
  67. package/docs/RUNTIME_HOOK_COVERAGE_v3.6.0.md +0 -49
  68. package/docs/V2_ARCHITECTURE.md +0 -561
  69. package/docs/V3_2_AUDIT.md +0 -82
  70. package/docs/V3_BACKEND_ARCHITECTURE.md +0 -138
  71. package/docs/V3_FRONTEND.md +0 -140
  72. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +0 -65
  73. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +0 -70
  74. package/docs/V4_1_VALIDATION_REPORT.md +0 -47
  75. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +0 -97
  76. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +0 -91
  77. package/docs/V4_2_VALIDATION_REPORT.md +0 -89
  78. package/docs/V4_3_2_DEADCODE_AUDIT_REPORT.md +0 -174
  79. package/docs/V4_3_2_DOCUMENTATION_CLEANUP_REPORT.md +0 -81
  80. package/docs/V4_3_2_GITHUB_VERCEL_CHECK_REPORT.md +0 -75
  81. package/docs/V4_3_2_GRAPH_UX_REPORT.md +0 -48
  82. package/docs/V4_3_2_INDEPENDENT_AUDIT_PACKAGE.md +0 -209
  83. package/docs/V4_3_2_PRODUCT_POLISH_REPORT.md +0 -57
  84. package/docs/V4_3_2_SELF_AUDIT_REPORT.md +0 -63
  85. package/docs/V4_3_2_VALIDATION_REPORT.md +0 -97
  86. package/docs/V4_3_3_VALIDATION_REPORT.md +0 -46
  87. package/docs/V4_3_PORTABILITY_ARCHITECTURE.md +0 -69
  88. package/docs/V4_3_PRIVACY_AUDIT.md +0 -60
  89. package/docs/V4_3_PRODUCT_HARDENING_REPORT.md +0 -53
  90. package/docs/V4_3_VALIDATION_REPORT.md +0 -58
  91. package/docs/V4_4_0_EXTRACTION_REPORT.md +0 -239
  92. package/docs/V4_5_0_GEMMA_RUNTIME_COMPATIBILITY_REPORT.md +0 -49
  93. package/docs/V4_5_0_GRAPH_UX_REPORT.md +0 -34
  94. package/docs/V4_5_0_MODEL_RUNTIME_UX_REPORT.md +0 -40
  95. package/docs/V4_5_0_ONBOARDING_REPORT.md +0 -31
  96. package/docs/V4_5_0_PRODUCT_EXPERIENCE_RECOVERY_REPORT.md +0 -49
  97. package/docs/V4_5_0_VALIDATION_REPORT.md +0 -60
  98. package/docs/V4_5_1_GRAPH_EXPERIENCE_REPORT.md +0 -33
  99. package/docs/V4_5_1_MODEL_EXPERIENCE_REPORT.md +0 -37
  100. package/docs/V4_5_1_NAVIGATION_REPORT.md +0 -37
  101. package/docs/V4_5_1_ONBOARDING_REPORT.md +0 -29
  102. package/docs/V4_5_1_PRODUCT_REIMAGINING_REPORT.md +0 -61
  103. package/docs/V4_5_1_RC_ARTIFACTS.md +0 -44
  104. package/docs/V4_5_1_UX_REPORT.md +0 -45
  105. package/docs/V4_5_1_VALIDATION_REPORT.md +0 -55
  106. package/docs/V4_5_1_VISUAL_DESIGN_REPORT.md +0 -30
  107. package/docs/V4_6_0_LIVING_BRAIN_EXPERIENCE_REPORT.md +0 -72
  108. package/docs/V4_6_1_RELEASE_REFRESH_REPORT.md +0 -42
  109. package/docs/V4_7_0_ADMIN_SEPARATION_REPORT.md +0 -42
  110. package/docs/V4_7_1_ADMIN_OPERATIONS_REPORT.md +0 -49
  111. package/docs/V4_7_2_INTUITIVE_BRAIN_UX_REPORT.md +0 -62
  112. package/docs/V4_BRAIN_ARCHITECTURE.md +0 -322
  113. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +0 -555
  114. package/docs/V4_IMPLEMENTATION_PLAN.md +0 -470
  115. package/frontend/index.html +0 -24
  116. package/frontend/openapi.json +0 -15425
  117. package/frontend/src/App.tsx +0 -243
  118. package/frontend/src/api/client.ts +0 -580
  119. package/frontend/src/api/openapi.ts +0 -17892
  120. package/frontend/src/components/AdminAccessGate.tsx +0 -70
  121. package/frontend/src/components/FeedbackState.tsx +0 -45
  122. package/frontend/src/components/LanguageSwitcher.tsx +0 -23
  123. package/frontend/src/components/LivingBrain.tsx +0 -220
  124. package/frontend/src/components/ProductFlow.tsx +0 -171
  125. package/frontend/src/components/WorkspaceProfileSwitcher.tsx +0 -176
  126. package/frontend/src/components/onboarding/AnalysisScreen.tsx +0 -135
  127. package/frontend/src/components/onboarding/DownloadConsentPanel.tsx +0 -29
  128. package/frontend/src/components/onboarding/InstallScreen.tsx +0 -263
  129. package/frontend/src/components/onboarding/LanguageChooser.tsx +0 -23
  130. package/frontend/src/components/onboarding/LoginScreen.tsx +0 -131
  131. package/frontend/src/components/onboarding/ProductFlowScreens.tsx +0 -13
  132. package/frontend/src/components/onboarding/RecommendationScreen.tsx +0 -116
  133. package/frontend/src/components/onboarding/recommendationModel.ts +0 -189
  134. package/frontend/src/components/primitives.tsx +0 -392
  135. package/frontend/src/components/ui/badge.tsx +0 -27
  136. package/frontend/src/components/ui/button.tsx +0 -37
  137. package/frontend/src/components/ui/card.tsx +0 -22
  138. package/frontend/src/components/ui/input.tsx +0 -16
  139. package/frontend/src/components/ui/textarea.tsx +0 -16
  140. package/frontend/src/features/admin/AdminConsole.tsx +0 -334
  141. package/frontend/src/features/brain/BrainCarePanel.tsx +0 -254
  142. package/frontend/src/features/brain/BrainComposer.tsx +0 -85
  143. package/frontend/src/features/brain/BrainConversation.tsx +0 -688
  144. package/frontend/src/features/brain/BrainGraphLayer.tsx +0 -365
  145. package/frontend/src/features/brain/BrainHome.tsx +0 -624
  146. package/frontend/src/features/brain/BrainMemoryLayer.tsx +0 -45
  147. package/frontend/src/features/brain/BrainOverviewPanel.tsx +0 -149
  148. package/frontend/src/features/brain/BrainRelationshipLayer.tsx +0 -43
  149. package/frontend/src/features/brain/DepthEmergence.tsx +0 -53
  150. package/frontend/src/features/brain/brainData.ts +0 -246
  151. package/frontend/src/features/brain/graphLayout.ts +0 -37
  152. package/frontend/src/features/brain/types.ts +0 -150
  153. package/frontend/src/features/review/ReviewCard.tsx +0 -100
  154. package/frontend/src/features/review/ReviewInbox.tsx +0 -127
  155. package/frontend/src/features/review/reviewHelpers.ts +0 -69
  156. package/frontend/src/i18n.ts +0 -1303
  157. package/frontend/src/lib/utils.ts +0 -33
  158. package/frontend/src/main.tsx +0 -23
  159. package/frontend/src/pages/Act.tsx +0 -458
  160. package/frontend/src/pages/Ask.tsx +0 -14
  161. package/frontend/src/pages/Brain.tsx +0 -914
  162. package/frontend/src/pages/Capture.tsx +0 -258
  163. package/frontend/src/pages/Library.tsx +0 -486
  164. package/frontend/src/pages/System.tsx +0 -621
  165. package/frontend/src/routes.ts +0 -92
  166. package/frontend/src/store/appStore.ts +0 -94
  167. package/frontend/src/styles.css +0 -6579
  168. package/static/app/assets/Act-DOvf59ru.js +0 -2
  169. package/static/app/assets/Act-DOvf59ru.js.map +0 -1
  170. package/static/app/assets/Brain-C7_0mEiI.js +0 -322
  171. package/static/app/assets/Brain-C7_0mEiI.js.map +0 -1
  172. package/static/app/assets/Capture-B9Tlhzqr.js.map +0 -1
  173. package/static/app/assets/Library-BJPEEm5O.js +0 -2
  174. package/static/app/assets/Library-BJPEEm5O.js.map +0 -1
  175. package/static/app/assets/System-D6t9jo9V.js +0 -2
  176. package/static/app/assets/System-D6t9jo9V.js.map +0 -1
  177. package/static/app/assets/core-CwxXejkd.js.map +0 -1
  178. package/static/app/assets/index-C7g26IF6.css +0 -2
  179. package/static/app/assets/index-DbcEYJQ2.js +0 -17
  180. package/static/app/assets/index-DbcEYJQ2.js.map +0 -1
  181. package/static/app/assets/primitives-CD38lt4n.js +0 -2
  182. package/static/app/assets/primitives-CD38lt4n.js.map +0 -1
  183. package/static/app/assets/textarea-BZk6ybp5.js.map +0 -1
@@ -1,33 +0,0 @@
1
- import { clsx, type ClassValue } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
7
-
8
- export function fmtNumber(value: unknown, fallback = "0") {
9
- const n = Number(value);
10
- if (!Number.isFinite(n)) return fallback;
11
- return new Intl.NumberFormat().format(n);
12
- }
13
-
14
- export function pct(value: unknown) {
15
- const n = Number(value);
16
- if (!Number.isFinite(n)) return "0%";
17
- return `${Math.round(n * 100)}%`;
18
- }
19
-
20
- export function shortId(value: unknown, length = 10) {
21
- const text = String(value || "");
22
- return text.length > length ? `${text.slice(0, length)}...` : text;
23
- }
24
-
25
- export function asArray<T = Record<string, unknown>>(value: unknown): T[] {
26
- return Array.isArray(value) ? (value as T[]) : [];
27
- }
28
-
29
- export function titleize(value: unknown) {
30
- return String(value || "")
31
- .replace(/[_-]+/g, " ")
32
- .replace(/\b\w/g, (m) => m.toUpperCase());
33
- }
@@ -1,23 +0,0 @@
1
- import React from "react";
2
- import ReactDOM from "react-dom/client";
3
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
- import App from "./App";
5
- import "./styles.css";
6
-
7
- const queryClient = new QueryClient({
8
- defaultOptions: {
9
- queries: {
10
- staleTime: 15_000,
11
- refetchOnWindowFocus: false,
12
- retry: 1,
13
- },
14
- },
15
- });
16
-
17
- ReactDOM.createRoot(document.getElementById("root")!).render(
18
- <React.StrictMode>
19
- <QueryClientProvider client={queryClient}>
20
- <App />
21
- </QueryClientProvider>
22
- </React.StrictMode>,
23
- );
@@ -1,458 +0,0 @@
1
- import * as React from "react";
2
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
- import ReactFlow, { Background, Controls, Edge, Node } from "reactflow";
4
- import { Bot, CalendarClock, GitBranch, PauseCircle, Play, ShieldCheck, Workflow } from "lucide-react";
5
- import { latticeApi } from "@/api/client";
6
- import { ActionButton, DataPanel, EntityList, KeyValueList, ModeGate, OperationResult, StructuredView, Tabs } from "@/components/primitives";
7
- import { Badge } from "@/components/ui/badge";
8
- import { Button } from "@/components/ui/button";
9
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
10
- import { Input } from "@/components/ui/input";
11
- import { Textarea } from "@/components/ui/textarea";
12
- import { ReviewInbox } from "@/features/review/ReviewInbox";
13
- import { useAppStore } from "@/store/appStore";
14
- import { asArray, shortId } from "@/lib/utils";
15
- import { t } from "@/i18n";
16
-
17
- type ActTab = "agents" | "runs" | "workflows" | "hooks" | "tools";
18
- type RunsSubTab = "runs" | "review";
19
-
20
- const runsSubTabs: Array<{ id: RunsSubTab; labelKey: string }> = [
21
- { id: "runs", labelKey: "act.tab.runs" },
22
- { id: "review", labelKey: "act.tab.review" },
23
- ];
24
-
25
- const tabs: Array<{ id: ActTab; labelKey: string; advancedLabelKey?: string }> = [
26
- { id: "agents", labelKey: "act.tab.goals" },
27
- { id: "runs", labelKey: "act.tab.runs" },
28
- { id: "workflows", labelKey: "act.tab.recipes" },
29
- { id: "hooks", labelKey: "act.tab.safeguards", advancedLabelKey: "act.tab.hooks" },
30
- { id: "tools", labelKey: "act.tab.permissions", advancedLabelKey: "act.tab.tools" },
31
- ];
32
-
33
- export function ActPage({ initialTab }: { initialTab?: string }) {
34
- const mode = useAppStore((state) => state.mode);
35
- const language = useAppStore((state) => state.language);
36
- const [tab, setTab] = React.useState<ActTab>(() => {
37
- if (initialTab === "review") return "runs";
38
- return (initialTab as ActTab) || "agents";
39
- });
40
- const [runsSubTab, setRunsSubTab] = React.useState<RunsSubTab>(initialTab === "review" ? "review" : "runs");
41
- React.useEffect(() => {
42
- if (initialTab === "review") {
43
- setTab("runs");
44
- setRunsSubTab("review");
45
- return;
46
- }
47
- if (tabs.some((item) => item.id === initialTab)) setTab(initialTab as ActTab);
48
- }, [initialTab]);
49
- return (
50
- <div className="space-y-5">
51
- <header className="page-hero">
52
- <div className="page-kicker"><Workflow className="h-4 w-4" /> {t(language, "act.kicker")}</div>
53
- <h1 className="page-title">{t(language, "act.title")}</h1>
54
- <p className="page-copy">{t(language, "act.copy")}</p>
55
- </header>
56
- <Tabs
57
- tabs={tabs.map((item) => ({
58
- id: item.id,
59
- label: t(language, mode === "basic" || !item.advancedLabelKey ? item.labelKey : item.advancedLabelKey),
60
- }))}
61
- value={tab}
62
- onChange={(id) => setTab(id as ActTab)}
63
- />
64
- {tab === "agents" ? <AgentsPanel /> : null}
65
- {tab === "runs" ? <RunsPanel subTab={runsSubTab} onSubTabChange={setRunsSubTab} /> : null}
66
- {tab === "workflows" ? <WorkflowsPanel /> : null}
67
- {tab === "hooks" ? <HooksPanel /> : null}
68
- {tab === "tools" ? <ToolsPanel /> : null}
69
- </div>
70
- );
71
- }
72
-
73
- function AgentsPanel() {
74
- const qc = useQueryClient();
75
- const mode = useAppStore((state) => state.mode);
76
- const [goal, setGoal] = React.useState("");
77
- const runtime = useQuery({ queryKey: ["agentRuntime"], queryFn: latticeApi.agentRuntime });
78
- const registry = useQuery({ queryKey: ["agentRegistry"], queryFn: latticeApi.agentRegistry });
79
- const caps = useQuery({ queryKey: ["agentCapabilities"], queryFn: latticeApi.agentCapabilities });
80
- const run = useMutation({
81
- mutationFn: () => latticeApi.runAgent(goal, ["planner", "executor", "reviewer"]),
82
- onSuccess: () => qc.invalidateQueries({ queryKey: ["agentRuntime"] }),
83
- });
84
- const [agentName, setAgentName] = React.useState("");
85
- const register = useMutation({
86
- mutationFn: () => latticeApi.registerAgent({ name: agentName, type: "custom", capabilities: [] }),
87
- onSuccess: () => qc.invalidateQueries({ queryKey: ["agentRegistry"] }),
88
- });
89
- const runtimeData = (runtime.data?.data || {}) as Record<string, unknown>;
90
- const runtimeMeta = (runtimeData.runtime || {}) as Record<string, unknown>;
91
- const runtimeReady = Boolean(runtimeMeta.ready);
92
- const runtimeReason = mode === "basic" ? "Load a local model before running agents." : String(runtimeMeta.unavailable_reason || "Load an LLM-backed model before running agents.");
93
- const canRunAgent = Boolean(goal.trim()) && runtimeReady && !run.isPending;
94
- return (
95
- <div className="grid gap-4 xl:grid-cols-[0.9fr_1.1fr]">
96
- <Card>
97
- <CardHeader>
98
- <CardTitle className="flex items-center gap-2"><Bot className="h-4 w-4" /> Start with a goal</CardTitle>
99
- <CardDescription>Lattice will plan, execute, and review only when the local model is ready.</CardDescription>
100
- </CardHeader>
101
- <CardContent className="space-y-3">
102
- <Textarea value={goal} onChange={(e) => setGoal(e.target.value)} placeholder="What should Lattice help you accomplish?" />
103
- {!runtimeReady ? <Badge variant="warning">{runtimeReason}</Badge> : null}
104
- <Button
105
- className="w-full"
106
- variant={runtimeReady ? "default" : "outline"}
107
- disabled={!canRunAgent}
108
- onClick={() => run.mutate()}
109
- >
110
- <Play className="h-4 w-4" /> {runtimeReady ? "Start Run" : "Load a model first"}
111
- </Button>
112
- {run.data ? <OperationResult result={run.data} successLabel="Agent run request completed" /> : null}
113
- </CardContent>
114
- </Card>
115
- <DataPanel title="Readiness" result={runtime.data}>
116
- {(data) => mode === "basic" ? (
117
- <div className="grid gap-3 sm:grid-cols-3">
118
- <div className="rounded-lg border border-border bg-background/55 p-3">
119
- <div className="text-sm font-medium">Model</div>
120
- <Badge variant={runtimeReady ? "success" : "warning"}>{runtimeReady ? "ready" : "needed"}</Badge>
121
- </div>
122
- <div className="rounded-lg border border-border bg-background/55 p-3">
123
- <div className="text-sm font-medium">Planner</div>
124
- <Badge variant="muted">{runtimeReady ? "available" : "waiting"}</Badge>
125
- </div>
126
- <div className="rounded-lg border border-border bg-background/55 p-3">
127
- <div className="text-sm font-medium">Review</div>
128
- <Badge variant="success">approval required</Badge>
129
- </div>
130
- </div>
131
- ) : <StructuredView value={data} />}
132
- </DataPanel>
133
- <DataPanel title="Agent team" result={registry.data}>
134
- {(data) => (
135
- <div className="space-y-3">
136
- <EntityList items={(data as Record<string, unknown>).agents} titleKey="name" metaKey="type" />
137
- <div className="flex gap-2">
138
- <Input value={agentName} onChange={(e) => setAgentName(e.target.value)} placeholder="New custom agent name" />
139
- <Button disabled={!agentName.trim() || register.isPending} onClick={() => register.mutate()}>Register</Button>
140
- </div>
141
- </div>
142
- )}
143
- </DataPanel>
144
- <DataPanel title={mode === "basic" ? "What Lattice can do" : "What agents can do"} result={caps.data}>
145
- {(data) => <StructuredView value={data} />}
146
- </DataPanel>
147
- </div>
148
- );
149
- }
150
-
151
- function RunsPanel({ subTab, onSubTabChange }: { subTab: RunsSubTab; onSubTabChange: (tab: RunsSubTab) => void }) {
152
- const language = useAppStore((state) => state.language);
153
- return (
154
- <div className="space-y-4">
155
- <Tabs
156
- tabs={runsSubTabs.map((item) => ({ id: item.id, label: t(language, item.labelKey) }))}
157
- value={subTab}
158
- onChange={(id) => onSubTabChange(id as RunsSubTab)}
159
- />
160
- {subTab === "runs" ? <RunsListPanel /> : <ReviewInbox />}
161
- </div>
162
- );
163
- }
164
-
165
- function RunsListPanel() {
166
- const mode = useAppStore((state) => state.mode);
167
- const runtime = useQuery({ queryKey: ["agentRuntime"], queryFn: latticeApi.agentRuntime });
168
- const workflows = useQuery({ queryKey: ["workflowRuns"], queryFn: latticeApi.workflowRuns });
169
- const pending = useQuery({ queryKey: ["permissions"], queryFn: latticeApi.permissionsPending });
170
- const agentRuns = asArray<Record<string, unknown>>((runtime.data?.data as Record<string, unknown>)?.runs);
171
- const workflowRuns = asArray<Record<string, unknown>>((workflows.data?.data as Record<string, unknown>)?.runs);
172
- return (
173
- <div className="grid gap-4 xl:grid-cols-2">
174
- <DataPanel title="Agent runs" result={runtime.data}>
175
- {() => <RunList runs={agentRuns} kind="agent" />}
176
- </DataPanel>
177
- <DataPanel title="Workflow runs" result={workflows.data}>
178
- {() => <RunList runs={workflowRuns} kind="workflow" />}
179
- </DataPanel>
180
- <DataPanel title="Approval inbox" result={pending.data} className="xl:col-span-2">
181
- {(data) => {
182
- const pendingMap = ((data as Record<string, unknown>).pending || {}) as Record<string, unknown>;
183
- const rows = Object.entries(pendingMap);
184
- return rows.length ? (
185
- <div className="grid gap-2">
186
- {rows.map(([token, value], index) => (
187
- <div key={token} className="flex flex-wrap items-center justify-between gap-3 rounded-md border border-border p-3">
188
- <div>
189
- <div className="font-medium">{mode === "basic" ? `Approval request ${index + 1}` : shortId(token, 16)}</div>
190
- <div className="mt-2">
191
- <KeyValueList data={(value || {}) as Record<string, unknown>} limit={5} />
192
- </div>
193
- </div>
194
- <div className="flex gap-2">
195
- <ActionButton label="Approve" action={() => latticeApi.approvePermission(token)} invalidate={["permissions"]} />
196
- <ActionButton label="Deny" action={() => latticeApi.denyPermission(token)} invalidate={["permissions"]} variant="destructive" />
197
- </div>
198
- </div>
199
- ))}
200
- </div>
201
- ) : <EntityList items={[]} />;
202
- }}
203
- </DataPanel>
204
- </div>
205
- );
206
- }
207
-
208
- function RunList({ runs, kind }: { runs: Array<Record<string, unknown>>; kind: "agent" | "workflow" }) {
209
- if (!runs.length) return <EntityList items={[]} />;
210
- return (
211
- <div className="grid gap-2">
212
- {runs.slice(0, 10).map((run) => {
213
- const id = String(run.run_id || run.id);
214
- const status = String(run.status || "unknown");
215
- return (
216
- <div key={id} className="rounded-md border border-border bg-background p-3">
217
- <div className="flex flex-wrap items-center justify-between gap-2">
218
- <div className="font-medium">{shortId(id, 18)}</div>
219
- <Badge variant={status === "succeeded" ? "success" : status === "awaiting_approval" ? "warning" : "muted"}>{status}</Badge>
220
- </div>
221
- <div className="mt-2 flex flex-wrap gap-2">
222
- <ActionButton label="Stop" action={() => kind === "agent" ? latticeApi.stopAgentRun(id) : latticeApi.stopWorkflowRun(id)} />
223
- {status === "awaiting_approval" && kind === "workflow" ? (
224
- <>
225
- <ActionButton label="Resume approved" action={() => latticeApi.resumeWorkflowRun(id, true)} />
226
- <ActionButton label="Resume denied" action={() => latticeApi.resumeWorkflowRun(id, false)} variant="destructive" />
227
- </>
228
- ) : null}
229
- </div>
230
- </div>
231
- );
232
- })}
233
- </div>
234
- );
235
- }
236
-
237
- function WorkflowsPanel() {
238
- const qc = useQueryClient();
239
- const defs = useQuery({ queryKey: ["workflowDefinitions"], queryFn: latticeApi.workflowDefinitions });
240
- const triggers = useQuery({ queryKey: ["workflowTriggers"], queryFn: latticeApi.workflowTriggers });
241
- const recipes = useQuery({ queryKey: ["automationRecipes"], queryFn: latticeApi.automationRecipes });
242
- const [name, setName] = React.useState("Manual workflow");
243
- const [importText, setImportText] = React.useState("");
244
- const create = useMutation({
245
- mutationFn: () => latticeApi.createWorkflow({
246
- name: name.trim() || "Manual workflow",
247
- nodes: manualWorkflowNodes(),
248
- metadata: { created_from: "desktop-act-ui" },
249
- }),
250
- onSuccess: () => qc.invalidateQueries({ queryKey: ["workflowDefinitions"] }),
251
- });
252
- const importWorkflow = useMutation({
253
- mutationFn: () => latticeApi.importWorkflow(JSON.parse(importText) as Record<string, unknown>),
254
- onSuccess: () => {
255
- setImportText("");
256
- qc.invalidateQueries({ queryKey: ["workflowDefinitions"] });
257
- },
258
- });
259
- const installRecipe = useMutation({
260
- mutationFn: (recipeId: string) => latticeApi.installAutomationRecipe(recipeId, false),
261
- onSuccess: () => {
262
- qc.invalidateQueries({ queryKey: ["workflowDefinitions"] });
263
- qc.invalidateQueries({ queryKey: ["workflowTriggers"] });
264
- },
265
- });
266
- const workflows = asArray<Record<string, unknown>>((defs.data?.data as Record<string, unknown>)?.workflows);
267
- const installedRecipeIds = new Set(
268
- workflows
269
- .filter((w: any) => w && w.metadata && w.metadata.created_from === "brain_automation_recipe" && w.metadata.recipe_id)
270
- .map((w: any) => String(w.metadata.recipe_id))
271
- );
272
- const nodes: Node[] = workflows.slice(0, 12).map((workflow, index) => ({
273
- id: String(workflow.id || workflow.workflow_id || index),
274
- position: { x: (index % 4) * 190, y: Math.floor(index / 4) * 120 },
275
- data: { label: String(workflow.name || workflow.id || `Workflow ${index + 1}`) },
276
- }));
277
- const edges: Edge[] = nodes.slice(1).map((node, index) => ({ id: `e-${index}`, source: nodes[index].id, target: node.id }));
278
- return (
279
- <div className="grid gap-4 xl:grid-cols-[1.2fr_0.8fr]">
280
- <DataPanel title="Brain automations" result={recipes.data} className="xl:col-span-2">
281
- {(data) => {
282
- const items = asArray<Record<string, unknown>>((data as Record<string, unknown>).recipes);
283
- return (
284
- <div className="grid gap-3 lg:grid-cols-3">
285
- {items.map((recipe) => {
286
- const id = String(recipe.id || "");
287
- const consent = (recipe.consent || {}) as Record<string, unknown>;
288
- const creates = asArray<string>(recipe.creates);
289
- return (
290
- <div key={id} className="rounded-lg border border-border bg-background/70 p-4">
291
- <div className="flex items-start justify-between gap-2">
292
- <div>
293
- <div className="flex items-center gap-2 font-medium">
294
- <CalendarClock className="h-4 w-4" /> {String(recipe.name || id)}
295
- </div>
296
- <p className="mt-2 text-sm text-muted-foreground">{String(recipe.summary || "")}</p>
297
- </div>
298
- <Badge variant="muted">{String(recipe.cadence || "draft")}</Badge>
299
- </div>
300
- <p className="mt-3 text-sm">{String(recipe.user_value || "")}</p>
301
- <div className="mt-3 flex flex-wrap gap-2">
302
- <Badge variant="success"><ShieldCheck className="h-3 w-3" /> local only</Badge>
303
- {consent.requires_user_enable ? <Badge variant="warning">draft first</Badge> : null}
304
- {creates.slice(0, 2).map((item) => <Badge key={item} variant="muted">{item}</Badge>)}
305
- </div>
306
- {(() => {
307
- const isInstalling = installRecipe.isPending && installRecipe.variables === id;
308
- const last = installRecipe.data as any;
309
- const lastRid = last && last.recipe && last.recipe.recipe_id ? String(last.recipe.recipe_id) : "";
310
- const justSucceeded = !installRecipe.isPending && lastRid === id;
311
- const installed = installedRecipeIds.has(id);
312
- const btnLabel = isInstalling
313
- ? "Creating..."
314
- : justSucceeded
315
- ? "✓ Draft created"
316
- : installed
317
- ? "Draft already created"
318
- : "Create reviewable draft";
319
- return (
320
- <>
321
- <Button
322
- className="mt-4 w-full"
323
- variant={installed || justSucceeded ? "secondary" : "outline"}
324
- disabled={!id || isInstalling || installed}
325
- onClick={() => {
326
- if (installed || isInstalling) return;
327
- installRecipe.mutate(id);
328
- }}
329
- >
330
- {btnLabel}
331
- </Button>
332
- {justSucceeded ? (
333
- <p className="mt-1 text-[10px] text-green-600">Reviewable draft ready. Check Definitions below.</p>
334
- ) : null}
335
- {installed && !justSucceeded ? (
336
- <p className="mt-1 text-[10px] text-muted-foreground">Reviewable draft exists — see Definitions.</p>
337
- ) : null}
338
- </>
339
- );
340
- })()}
341
- </div>
342
- );
343
- })}
344
- </div>
345
- );
346
- }}
347
- </DataPanel>
348
- <Card>
349
- <CardHeader>
350
- <CardTitle className="flex items-center gap-2"><GitBranch className="h-4 w-4" /> Workflow graph</CardTitle>
351
- <CardDescription>See your saved workflows as a simple map.</CardDescription>
352
- </CardHeader>
353
- <CardContent>
354
- <div className="h-[440px] rounded-lg border border-border">
355
- <ReactFlow nodes={nodes} edges={edges} fitView>
356
- <Background />
357
- <Controls />
358
- </ReactFlow>
359
- </div>
360
- </CardContent>
361
- </Card>
362
- <DataPanel title="Definitions" result={defs.data}>
363
- {() => (
364
- <div className="space-y-3">
365
- <div className="grid gap-2 rounded-md border border-border p-3">
366
- <div className="flex flex-wrap gap-2">
367
- <Input value={name} onChange={(event) => setName(event.target.value)} placeholder="Workflow name" />
368
- <Button disabled={create.isPending} onClick={() => create.mutate()}>Create</Button>
369
- </div>
370
- <Textarea value={importText} onChange={(event) => setImportText(event.target.value)} placeholder="Paste a workflow export" />
371
- <Button variant="outline" disabled={!importText.trim() || importWorkflow.isPending} onClick={() => importWorkflow.mutate()}>Import</Button>
372
- {create.data ? <OperationResult result={create.data} successLabel="Workflow created" /> : null}
373
- {importWorkflow.data ? <OperationResult result={importWorkflow.data} successLabel="Workflow imported" /> : null}
374
- </div>
375
- {workflows.length ? workflows.map((workflow) => {
376
- const id = String(workflow.id || workflow.workflow_id);
377
- return (
378
- <div key={id} className="rounded-md border border-border p-3">
379
- <div className="font-medium">{String(workflow.name || id)}</div>
380
- <div className="mt-2 flex gap-2">
381
- <ActionButton label="Run" action={() => latticeApi.runWorkflow(id)} invalidate={["workflowRuns"]} />
382
- <ActionButton label="Export" action={() => latticeApi.exportWorkflow(id)} />
383
- </div>
384
- </div>
385
- );
386
- }) : <EntityList items={[]} />}
387
- </div>
388
- )}
389
- </DataPanel>
390
- <DataPanel title="Automation triggers" result={triggers.data} className="xl:col-span-2">
391
- {(data) => <StructuredView value={data} />}
392
- </DataPanel>
393
- </div>
394
- );
395
- }
396
-
397
- function manualWorkflowNodes(): Array<Record<string, unknown>> {
398
- return [
399
- {
400
- id: "trigger",
401
- type: "trigger",
402
- name: "Manual start",
403
- config: { trigger: "manual" },
404
- next: "output",
405
- },
406
- {
407
- id: "output",
408
- type: "output",
409
- name: "Output",
410
- config: { value: "Workflow completed" },
411
- next: null,
412
- },
413
- ];
414
- }
415
-
416
- function HooksPanel() {
417
- const mode = useAppStore((state) => state.mode);
418
- const hooks = useQuery({ queryKey: ["hooks"], queryFn: latticeApi.hooks });
419
- const runs = useQuery({ queryKey: ["hookRuns"], queryFn: latticeApi.hookRuns });
420
- if (mode === "basic") {
421
- return (
422
- <div className="grid gap-4 xl:grid-cols-2">
423
- <DataPanel title="Safeguards" result={hooks.data}>
424
- {(data) => <EntityList items={(data as Record<string, unknown>).hooks} titleKey="name" metaKey="kind" />}
425
- </DataPanel>
426
- <ModeGate title="Detailed hook logs" detail="Switch to Advanced when you need hook run logs and manual diagnostic controls." />
427
- </div>
428
- );
429
- }
430
- return (
431
- <div className="grid gap-4 xl:grid-cols-2">
432
- <DataPanel title="Hooks" result={hooks.data}>
433
- {(data) => <EntityList items={(data as Record<string, unknown>).hooks} titleKey="name" metaKey="kind" />}
434
- </DataPanel>
435
- <DataPanel title="Hook run log" result={runs.data}>
436
- {(data) => <EntityList items={(data as Record<string, unknown>).runs} titleKey="hook_id" metaKey="status" />}
437
- </DataPanel>
438
- <Card className="xl:col-span-2">
439
- <CardHeader>
440
- <CardTitle className="flex items-center gap-2"><PauseCircle className="h-4 w-4" /> Run manual hooks</CardTitle>
441
- <CardDescription>Trigger hooks deliberately and review the recorded result.</CardDescription>
442
- </CardHeader>
443
- <CardContent>
444
- <ActionButton label="Run all manual hooks" action={() => latticeApi.hookRun({ event: "manual" })} invalidate={["hookRuns"]} />
445
- </CardContent>
446
- </Card>
447
- </div>
448
- );
449
- }
450
-
451
- function ToolsPanel() {
452
- const tools = useQuery({ queryKey: ["toolPermissions"], queryFn: latticeApi.toolPermissions });
453
- return (
454
- <DataPanel title="Action permissions" result={tools.data}>
455
- {(data) => <EntityList items={(data as Record<string, unknown>).permissions || data} titleKey="tool" metaKey="risk" />}
456
- </DataPanel>
457
- );
458
- }
@@ -1,14 +0,0 @@
1
- import * as React from "react";
2
- import { type BrainState } from "@/components/LivingBrain";
3
- import { BrainHome } from "@/features/brain/BrainHome";
4
-
5
- export function AskPage() {
6
- const [brainPresence, setBrainPresence] = React.useState<{ state: BrainState; intensity: number }>({
7
- state: "idle",
8
- intensity: 0.58,
9
- });
10
- const setBrain = React.useCallback((state: BrainState, intensity = 0.58) => {
11
- setBrainPresence({ state, intensity });
12
- }, []);
13
- return <BrainHome brainState={brainPresence.state} intensity={brainPresence.intensity} onBrainChange={setBrain} />;
14
- }