Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENT_0_DEPENDENCY_FIX_REPORT_2025-12-26.md
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,7 @@ b5d5aca - docs(agent-0): Add comprehensive code review report
- Zeit: ~8-10h

6. **Production Deployment Verification**

```bash
ssh root@178.156.178.70
cd /root/cloud-agents
Expand Down
42 changes: 42 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { StatsCard } from "./components/StatsCard";
import { ActivityLog } from "./components/ActivityLog";
import { SettingsPanel } from "./components/SettingsPanel";
import { BrainMemoryPage } from "./components/BrainMemoryPage";
import { UsersPage } from "./components/UsersPage";
import { AuditPage } from "./components/AuditPage";
import { IntegrationsPage } from "./components/IntegrationsPage";
import { Button } from "./components/ui/button";
import { Input } from "./components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs";
Expand All @@ -35,6 +38,9 @@ import {
MessageSquare,
CheckSquare,
Brain,
Users,
FileText,
Link2,
} from "lucide-react";
import { toast } from "sonner";
import {
Expand Down Expand Up @@ -485,6 +491,30 @@ export default function App() {
<Brain className="w-4 h-4 mr-2" />
Brain
</TabsTrigger>
<TabsTrigger
value="users"
data-testid="agents.navigation.users.tab"
data-otop-id="agents.navigation.users.tab"
>
<Users className="w-4 h-4 mr-2" />
Users
</TabsTrigger>
<TabsTrigger
value="audit"
data-testid="agents.navigation.audit.tab"
data-otop-id="agents.navigation.audit.tab"
>
<FileText className="w-4 h-4 mr-2" />
Audit
</TabsTrigger>
<TabsTrigger
value="integrations"
data-testid="agents.navigation.integrations.tab"
data-otop-id="agents.navigation.integrations.tab"
>
<Link2 className="w-4 h-4 mr-2" />
Integrations
</TabsTrigger>
</TabsList>

<TabsContent value="dashboard" className="space-y-8">
Expand Down Expand Up @@ -644,6 +674,18 @@ export default function App() {
<TabsContent value="memory">
<BrainMemoryPage />
</TabsContent>

<TabsContent value="users">
<UsersPage />
</TabsContent>

<TabsContent value="audit">
<AuditPage />
</TabsContent>

<TabsContent value="integrations">
<IntegrationsPage />
</TabsContent>
</Tabs>
</main>

Expand Down
86 changes: 85 additions & 1 deletion src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ import {
revokeToken,
refreshAccessToken,
} from "../auth/jwt.js";
import { verifyUserPassword, getUserById } from "../db/users.js";
import {
verifyUserPassword,
getUserById,
changeUserPassword,
} from "../db/users.js";
import { loginRateLimiter } from "../auth/rate-limiter.js";
import { requireAdmin, type AuthenticatedRequest } from "../auth/middleware.js";

const db = initDatabase();

Expand Down Expand Up @@ -282,5 +287,84 @@ export function createAuthRouter(): Router {
}
});

/**
* POST /api/auth/reset-password
* Reset user password (Admin only)
* Body: { userId: string, newPassword: string }
*/
router.post(
"/reset-password",
requireAdmin,
async (req: AuthenticatedRequest, res: Response) => {
try {
const { userId, newPassword } = req.body;

// Validation
if (!userId || !newPassword) {
return res.status(400).json({
error: "Missing required fields",
message: "userId and newPassword are required",
});
}

// Password strength validation
if (newPassword.length < 8) {
return res.status(400).json({
error: "Password too weak",
message: "Password must be at least 8 characters",
});
}

// Check if target user exists
const rawDb = db.getRawDb();
const targetUser = getUserById(rawDb, userId);

if (!targetUser) {
return res.status(404).json({
error: "User not found",
message: `No user found with ID: ${userId}`,
});
}

// Reset password
const success = await changeUserPassword(rawDb, userId, newPassword);

if (!success) {
return res.status(500).json({
error: "Password reset failed",
message: "Could not update password",
});
}

// Log password reset event
db.audit.log({
kind: "password_reset",
message: `Admin ${req.userId} reset password for user ${targetUser.email}`,
userId: req.userId!,
severity: "warn",
meta: {
targetUserId: userId,
targetEmail: targetUser.email,
adminId: req.userId,
},
});

res.json({
success: true,
message: "Password reset successfully",
user: {
id: targetUser.id,
email: targetUser.email,
},
});
} catch (error) {
console.error("Reset password error:", error);
res.status(500).json({
error: "Internal server error",
});
}
},
);

return router;
}
171 changes: 171 additions & 0 deletions src/brain/core-brain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* Core Brain Client - Connects to central brain-core API
*
* Provides read + append-only write access to the central knowledge base.
* No overwrites - only new entries are added.
*
* brain-core API uses headers for context:
* - x-org-id: Organization ID
* - x-user-id: User ID
* - x-project-id: Project ID
*/

const CORE_BRAIN_ORIGIN =
process.env.CORE_BRAIN_ORIGIN || "http://49.13.158.176:5001";
const DEFAULT_ORG_ID = "activi-dev";
const DEFAULT_PROJECT_ID = "cloud-agents";

export interface CoreBrainMemory {
id: string;
type: string;
content: string;
tags?: string[];
createdAt: string;
}

export interface CoreBrainSearchResult {
id: string;
type: string;
content: string;
tags?: string[];
createdAt?: string;
}

export interface CoreBrainStoreParams {
userId: string;
content: string;
type?: string;
tags?: string[];
}

export interface CoreBrainSearchParams {
userId: string;
query: string;
limit?: number;
}

/**
* Build headers for brain-core API
* User is auto-created in brain-core if not exists (via user-sync middleware)
*/
function buildHeaders(userId: string): Record<string, string> {
return {
"Content-Type": "application/json",
"x-org-id": DEFAULT_ORG_ID,
"x-user-id": userId,
"x-project-id": DEFAULT_PROJECT_ID,
};
}

/**
* Search the central brain for relevant memories
*/
export async function coreBrainSearch(
params: CoreBrainSearchParams,
): Promise<CoreBrainSearchResult[]> {
try {
const response = await fetch(`${CORE_BRAIN_ORIGIN}/api/memory/search`, {
method: "POST",
headers: buildHeaders(params.userId),
body: JSON.stringify({
query: params.query,
limit: params.limit || 5,
}),
});

if (!response.ok) {
console.error(`[core-brain] Search failed: ${response.status}`);
return [];
}

const data = await response.json();
return data.results || [];
} catch (error) {
console.error("[core-brain] Search error:", error);
return [];
}
}

/**
* Store new memory in central brain (append-only, no overwrites)
*/
export async function coreBrainStore(
params: CoreBrainStoreParams,
): Promise<{ success: boolean; id?: string }> {
try {
const response = await fetch(`${CORE_BRAIN_ORIGIN}/api/memory/store`, {
method: "POST",
headers: buildHeaders(params.userId),
body: JSON.stringify({
type: params.type || "chat",
content: params.content,
tags: params.tags || [],
}),
});

if (!response.ok) {
console.error(`[core-brain] Store failed: ${response.status}`);
return { success: false };
}

const data = await response.json();
return { success: true, id: data.id };
} catch (error) {
console.error("[core-brain] Store error:", error);
return { success: false };
}
}

/**
* Get recent memories from central brain
*/
export async function coreBrainRecent(
userId: string,
limit: number = 10,
): Promise<CoreBrainMemory[]> {
try {
const response = await fetch(`${CORE_BRAIN_ORIGIN}/api/memory/recent`, {
method: "POST",
headers: buildHeaders(userId),
body: JSON.stringify({ limit }),
});

if (!response.ok) {
console.error(`[core-brain] Recent failed: ${response.status}`);
return [];
}

const data = await response.json();
return data.results || [];
} catch (error) {
console.error("[core-brain] Recent error:", error);
return [];
}
}

/**
* Build context string from core brain results for prompt injection
*/
export function buildCoreBrainContext(
results: CoreBrainSearchResult[],
): string {
if (results.length === 0) return "";

const lines = results.map((r, i) => `[${i + 1}] ${r.content}`);
return `\n--- Central Knowledge Base ---\n${lines.join("\n")}\n---\n`;
}

/**
* Health check for core brain connection
*/
export async function coreBrainHealthCheck(): Promise<boolean> {
try {
const response = await fetch(`${CORE_BRAIN_ORIGIN}/health`, {
method: "GET",
signal: AbortSignal.timeout(3000),
});
return response.ok;
} catch {
return false;
}
}
15 changes: 15 additions & 0 deletions src/brain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ export type {
export { BrainSearch } from "./search.js";
export type { BrainSearchResult, KeywordSearchResult } from "./search.js";

// Core Brain Client (central knowledge base)
export {
coreBrainSearch,
coreBrainStore,
coreBrainRecent,
buildCoreBrainContext,
coreBrainHealthCheck,
} from "./core-brain.js";
export type {
CoreBrainMemory,
CoreBrainSearchResult,
CoreBrainStoreParams,
CoreBrainSearchParams,
} from "./core-brain.js";

// Re-export types from DB schema
export type {
BrainDoc,
Expand Down
Loading