diff --git a/Cargo.lock b/Cargo.lock index 945796092..13ba889c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8269,6 +8269,7 @@ dependencies = [ "libc", "mime_guess", "minijinja", + "moka", "notify", "open", "opentelemetry", diff --git a/Cargo.toml b/Cargo.toml index fd73aa72b..d11347941 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,6 +145,7 @@ prometheus = { version = "0.13", optional = true } pdf-extract = "0.10.0" open = "5.3.3" urlencoding = "2.1.3" +moka = "0.12.13" [features] metrics = ["dep:prometheus"] diff --git a/interface/src/api/client.ts b/interface/src/api/client.ts index 3a031c0fd..abb4144fb 100644 --- a/interface/src/api/client.ts +++ b/interface/src/api/client.ts @@ -846,12 +846,14 @@ export interface RegistrySkill { skillId: string; name: string; installs: number; + description?: string; id?: string; } export interface RegistryBrowseResponse { skills: RegistrySkill[]; has_more: boolean; + total?: number; } export interface RegistrySearchResponse { diff --git a/interface/src/components/Markdown.tsx b/interface/src/components/Markdown.tsx index 81bbf057e..b903aa96b 100644 --- a/interface/src/components/Markdown.tsx +++ b/interface/src/components/Markdown.tsx @@ -1,9 +1,15 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; -export function Markdown({ children }: { children: string }) { +export function Markdown({ + children, + className, +}: { + children: string; + className?: string; +}) { return ( -
+
{ const existing = current[channelId]; if (!existing) return current; + const existingKeys = new Set(existing.timeline.map(itemKey)); + const olderItems = data.items.filter((item) => !existingKeys.has(itemKey(item))); + const hasMore = olderItems.length === 0 ? false : data.has_more; return { ...current, [channelId]: { ...existing, - timeline: [...data.items, ...existing.timeline], - hasMore: data.has_more, + timeline: [...olderItems, ...existing.timeline], + hasMore, loadingMore: false, }, }; diff --git a/interface/src/routes/AgentDetail.tsx b/interface/src/routes/AgentDetail.tsx index 4ed6f6cda..97528a299 100644 --- a/interface/src/routes/AgentDetail.tsx +++ b/interface/src/routes/AgentDetail.tsx @@ -144,35 +144,51 @@ export function AgentDetail({ agentId, liveStates }: AgentDetailProps) { {/* Memory Donut */}
-

Memory Types

- {overviewData.memory_total} +

Memory

+ + Edit +
{/* Model Routing */} {configData && ( -
+

Model Routing

Edit
- +
+ +
)} {/* Quick Stats */} -
-
+
+

Configuration

+ + Edit +
-
+
@@ -470,8 +486,8 @@ function ActivityHeatmap({ data }: { data: { day: number; hour: number; count: n const maxCount = Math.max(...data.map((d) => d.count), 1); const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const hours = Array.from({ length: 24 }, (_value, index) => index); - // Create a 7x24 grid const getCell = (day: number, hour: number) => { const cell = data.find((d) => d.day === day && d.hour === hour); return cell?.count ?? 0; @@ -483,34 +499,30 @@ function ActivityHeatmap({ data }: { data: { day: number; hour: number; count: n }; return ( -
-
- {/* Hour labels */} -
-
{/* Day label spacer */} - {Array.from({ length: 24 }, (_, h) => ( -
- {h % 6 === 0 ? h : ""} +
+
+
+
+ {hours.map((hour) => ( +
+ {hour % 6 === 0 ? hour : ""}
))}
- {/* Heatmap grid */} {days.map((dayLabel, day) => ( -
-
{dayLabel}
-
- {Array.from({ length: 24 }, (_, hour) => { - const count = getCell(day, hour); - return ( -
- ); - })} -
+
+
{dayLabel}
+ {hours.map((hour) => { + const count = getCell(day, hour); + return ( +
+ ); + })}
))}
@@ -524,14 +536,15 @@ function MemoryDonut({ counts }: { counts: Record }) { value: counts[type] ?? 0, color: MEMORY_TYPE_COLORS[idx % MEMORY_TYPE_COLORS.length], })).filter((d) => d.value > 0); + const total = data.reduce((sum, item) => sum + item.value, 0); if (data.length === 0) { return
No memories
; } return ( -
-
+
+
}) { border: `1px solid ${CHART_COLORS.tooltip.border}`, borderRadius: "6px", fontSize: "12px", + padding: "4px 6px", }} - itemStyle={{ color: CHART_COLORS.tooltip.text }} + wrapperStyle={{ zIndex: 20 }} + labelStyle={{ display: "none" }} + itemStyle={{ color: CHART_COLORS.tooltip.text, margin: 0, lineHeight: 1.2 }} /> +
+ {total} + total +
{data.map((item) => ( @@ -586,7 +606,7 @@ function ModelRoutingList({ config }: { config: { routing: { channel: string; br ]; return ( -
+
{models.map(({ label, model, color }) => (
{label} @@ -628,29 +648,32 @@ function IdentitySection({ if (!hasContent) return null; const files = [ - { label: "SOUL.md", content: identity.soul }, - { label: "IDENTITY.md", content: identity.identity }, - { label: "USER.md", content: identity.user }, + { label: "SOUL.md", tab: "soul", content: identity.soul }, + { label: "IDENTITY.md", tab: "identity", content: identity.identity }, + { label: "USER.md", tab: "user", content: identity.user }, ].filter((f) => f.content && f.content.trim().length > 0 && !f.content.startsWith("