@swarmclawai/swarmclaw 0.6.7 → 0.7.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 (203) hide show
  1. package/README.md +82 -39
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +19 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  8. package/src/app/api/clawhub/install/route.ts +2 -2
  9. package/src/app/api/eval/run/route.ts +37 -0
  10. package/src/app/api/eval/scenarios/route.ts +24 -0
  11. package/src/app/api/eval/suite/route.ts +29 -0
  12. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  13. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  14. package/src/app/api/memory/graph/route.ts +46 -0
  15. package/src/app/api/memory/route.ts +36 -5
  16. package/src/app/api/notifications/route.ts +3 -0
  17. package/src/app/api/plugins/install/route.ts +57 -5
  18. package/src/app/api/plugins/marketplace/route.ts +73 -22
  19. package/src/app/api/plugins/route.ts +61 -1
  20. package/src/app/api/plugins/ui/route.ts +34 -0
  21. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  22. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  23. package/src/app/api/settings/route.ts +62 -0
  24. package/src/app/api/setup/doctor/route.ts +22 -5
  25. package/src/app/api/souls/[id]/route.ts +65 -0
  26. package/src/app/api/souls/route.ts +70 -0
  27. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  28. package/src/app/api/tasks/[id]/route.ts +16 -3
  29. package/src/app/api/tasks/route.ts +10 -2
  30. package/src/app/api/usage/route.ts +9 -2
  31. package/src/app/globals.css +27 -0
  32. package/src/app/page.tsx +10 -5
  33. package/src/cli/index.js +37 -0
  34. package/src/components/activity/activity-feed.tsx +9 -2
  35. package/src/components/agents/agent-avatar.tsx +5 -1
  36. package/src/components/agents/agent-card.tsx +55 -9
  37. package/src/components/agents/agent-sheet.tsx +112 -34
  38. package/src/components/agents/inspector-panel.tsx +1 -1
  39. package/src/components/agents/soul-library-picker.tsx +84 -13
  40. package/src/components/auth/access-key-gate.tsx +63 -54
  41. package/src/components/auth/user-picker.tsx +37 -32
  42. package/src/components/chat/activity-moment.tsx +2 -0
  43. package/src/components/chat/chat-area.tsx +11 -0
  44. package/src/components/chat/chat-header.tsx +69 -25
  45. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  46. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  47. package/src/components/chat/code-block.tsx +3 -1
  48. package/src/components/chat/exec-approval-card.tsx +8 -1
  49. package/src/components/chat/message-bubble.tsx +164 -4
  50. package/src/components/chat/message-list.tsx +46 -4
  51. package/src/components/chat/session-approval-card.tsx +80 -0
  52. package/src/components/chat/session-debug-panel.tsx +106 -84
  53. package/src/components/chat/streaming-bubble.tsx +6 -5
  54. package/src/components/chat/task-approval-card.tsx +78 -0
  55. package/src/components/chat/thinking-indicator.tsx +48 -12
  56. package/src/components/chat/tool-call-bubble.tsx +3 -0
  57. package/src/components/chat/tool-request-banner.tsx +39 -20
  58. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  59. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  60. package/src/components/connectors/connector-list.tsx +33 -11
  61. package/src/components/connectors/connector-sheet.tsx +37 -7
  62. package/src/components/home/home-view.tsx +54 -24
  63. package/src/components/input/chat-input.tsx +22 -1
  64. package/src/components/knowledge/knowledge-list.tsx +17 -18
  65. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  66. package/src/components/layout/app-layout.tsx +87 -19
  67. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  68. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  69. package/src/components/memory/memory-browser.tsx +73 -45
  70. package/src/components/memory/memory-graph-view.tsx +203 -0
  71. package/src/components/memory/memory-list.tsx +20 -13
  72. package/src/components/plugins/plugin-list.tsx +214 -60
  73. package/src/components/plugins/plugin-sheet.tsx +119 -24
  74. package/src/components/projects/project-list.tsx +17 -9
  75. package/src/components/providers/provider-list.tsx +21 -6
  76. package/src/components/providers/provider-sheet.tsx +42 -25
  77. package/src/components/runs/run-list.tsx +17 -13
  78. package/src/components/schedules/schedule-card.tsx +10 -3
  79. package/src/components/schedules/schedule-list.tsx +2 -2
  80. package/src/components/schedules/schedule-sheet.tsx +28 -9
  81. package/src/components/secrets/secret-sheet.tsx +7 -2
  82. package/src/components/secrets/secrets-list.tsx +18 -5
  83. package/src/components/sessions/new-session-sheet.tsx +183 -376
  84. package/src/components/sessions/session-card.tsx +10 -2
  85. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  86. package/src/components/shared/command-palette.tsx +13 -5
  87. package/src/components/shared/empty-state.tsx +20 -8
  88. package/src/components/shared/hint-tip.tsx +31 -0
  89. package/src/components/shared/notification-center.tsx +134 -86
  90. package/src/components/shared/profile-sheet.tsx +4 -0
  91. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  92. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  93. package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
  94. package/src/components/skills/clawhub-browser.tsx +1 -0
  95. package/src/components/skills/skill-list.tsx +31 -12
  96. package/src/components/skills/skill-sheet.tsx +20 -7
  97. package/src/components/tasks/approvals-panel.tsx +224 -0
  98. package/src/components/tasks/task-board.tsx +20 -12
  99. package/src/components/tasks/task-card.tsx +21 -7
  100. package/src/components/tasks/task-column.tsx +4 -3
  101. package/src/components/tasks/task-list.tsx +1 -1
  102. package/src/components/tasks/task-sheet.tsx +130 -1
  103. package/src/components/ui/dialog.tsx +1 -0
  104. package/src/components/ui/sheet.tsx +1 -0
  105. package/src/components/usage/metrics-dashboard.tsx +72 -48
  106. package/src/components/wallets/wallet-panel.tsx +65 -41
  107. package/src/components/wallets/wallet-section.tsx +9 -3
  108. package/src/components/webhooks/webhook-list.tsx +21 -12
  109. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  110. package/src/lib/approval-display.test.ts +45 -0
  111. package/src/lib/approval-display.ts +62 -0
  112. package/src/lib/clipboard.ts +38 -0
  113. package/src/lib/memory.ts +8 -0
  114. package/src/lib/providers/claude-cli.ts +5 -3
  115. package/src/lib/providers/index.ts +67 -21
  116. package/src/lib/runtime-loop.ts +3 -2
  117. package/src/lib/server/approvals.ts +150 -0
  118. package/src/lib/server/chat-execution.ts +319 -74
  119. package/src/lib/server/chatroom-helpers.ts +63 -5
  120. package/src/lib/server/chatroom-orchestration.ts +74 -0
  121. package/src/lib/server/clawhub-client.ts +82 -6
  122. package/src/lib/server/connectors/manager.ts +27 -1
  123. package/src/lib/server/context-manager.ts +132 -50
  124. package/src/lib/server/cost.test.ts +73 -0
  125. package/src/lib/server/cost.ts +165 -34
  126. package/src/lib/server/daemon-state.ts +112 -1
  127. package/src/lib/server/data-dir.ts +18 -1
  128. package/src/lib/server/eval/runner.ts +126 -0
  129. package/src/lib/server/eval/scenarios.ts +218 -0
  130. package/src/lib/server/eval/scorer.ts +96 -0
  131. package/src/lib/server/eval/store.ts +37 -0
  132. package/src/lib/server/eval/types.ts +48 -0
  133. package/src/lib/server/execution-log.ts +12 -8
  134. package/src/lib/server/guardian.ts +34 -0
  135. package/src/lib/server/heartbeat-service.ts +53 -1
  136. package/src/lib/server/integrity-monitor.ts +208 -0
  137. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  138. package/src/lib/server/link-understanding.ts +55 -0
  139. package/src/lib/server/llm-response-cache.test.ts +102 -0
  140. package/src/lib/server/llm-response-cache.ts +227 -0
  141. package/src/lib/server/main-agent-loop.ts +115 -16
  142. package/src/lib/server/main-session.ts +6 -3
  143. package/src/lib/server/mcp-conformance.test.ts +18 -0
  144. package/src/lib/server/mcp-conformance.ts +233 -0
  145. package/src/lib/server/memory-db.ts +193 -19
  146. package/src/lib/server/memory-retrieval.test.ts +56 -0
  147. package/src/lib/server/mmr.ts +73 -0
  148. package/src/lib/server/orchestrator-lg.ts +7 -1
  149. package/src/lib/server/orchestrator.ts +4 -3
  150. package/src/lib/server/plugins.ts +662 -132
  151. package/src/lib/server/process-manager.ts +18 -0
  152. package/src/lib/server/query-expansion.ts +57 -0
  153. package/src/lib/server/queue.ts +280 -11
  154. package/src/lib/server/runtime-settings.ts +9 -0
  155. package/src/lib/server/session-run-manager.test.ts +23 -0
  156. package/src/lib/server/session-run-manager.ts +32 -2
  157. package/src/lib/server/session-tools/canvas.ts +85 -50
  158. package/src/lib/server/session-tools/chatroom.ts +130 -127
  159. package/src/lib/server/session-tools/connector.ts +233 -454
  160. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  161. package/src/lib/server/session-tools/crud.ts +84 -7
  162. package/src/lib/server/session-tools/delegate.ts +351 -752
  163. package/src/lib/server/session-tools/discovery.ts +198 -0
  164. package/src/lib/server/session-tools/edit_file.ts +82 -0
  165. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  166. package/src/lib/server/session-tools/file.ts +257 -425
  167. package/src/lib/server/session-tools/git.ts +87 -47
  168. package/src/lib/server/session-tools/http.ts +95 -33
  169. package/src/lib/server/session-tools/index.ts +217 -138
  170. package/src/lib/server/session-tools/memory.ts +154 -239
  171. package/src/lib/server/session-tools/monitor.ts +126 -0
  172. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  173. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  174. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  175. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  176. package/src/lib/server/session-tools/platform.ts +86 -0
  177. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  178. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  179. package/src/lib/server/session-tools/sandbox.ts +175 -148
  180. package/src/lib/server/session-tools/schedule.ts +78 -0
  181. package/src/lib/server/session-tools/session-info.ts +104 -410
  182. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  183. package/src/lib/server/session-tools/shell.ts +171 -143
  184. package/src/lib/server/session-tools/subagent.ts +77 -77
  185. package/src/lib/server/session-tools/wallet.ts +182 -106
  186. package/src/lib/server/session-tools/web.ts +181 -327
  187. package/src/lib/server/storage.ts +36 -0
  188. package/src/lib/server/stream-agent-chat.ts +348 -242
  189. package/src/lib/server/task-quality-gate.test.ts +44 -0
  190. package/src/lib/server/task-quality-gate.ts +67 -0
  191. package/src/lib/server/task-validation.test.ts +78 -0
  192. package/src/lib/server/task-validation.ts +67 -2
  193. package/src/lib/server/tool-aliases.ts +68 -0
  194. package/src/lib/server/tool-capability-policy.ts +24 -5
  195. package/src/lib/server/tool-retry.ts +62 -0
  196. package/src/lib/server/transcript-repair.ts +72 -0
  197. package/src/lib/setup-defaults.ts +1 -0
  198. package/src/lib/tasks.ts +7 -1
  199. package/src/lib/tool-definitions.ts +24 -23
  200. package/src/lib/validation/schemas.ts +13 -0
  201. package/src/lib/view-routes.ts +2 -23
  202. package/src/stores/use-app-store.ts +23 -1
  203. package/src/types/index.ts +155 -10
@@ -5,6 +5,7 @@ import { searchMemory } from '@/lib/memory'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
6
  import { MemoryCard } from './memory-card'
7
7
  import { MemoryDetail } from './memory-detail'
8
+ import { MemoryGraphView } from './memory-graph-view'
8
9
  import type { MemoryEntry } from '@/types'
9
10
 
10
11
  export function MemoryBrowser() {
@@ -19,6 +20,7 @@ export function MemoryBrowser() {
19
20
  const [loaded, setLoaded] = useState(false)
20
21
  const [error, setError] = useState<string | null>(null)
21
22
  const [categoryFilter, setCategoryFilter] = useState<string>('')
23
+ const [viewMode, setViewMode] = useState<'list' | 'graph'>('list')
22
24
  const searchRef = useRef(search)
23
25
 
24
26
  // Derive the API agentId from the filter
@@ -102,12 +104,26 @@ export function MemoryBrowser() {
102
104
 
103
105
  return (
104
106
  <div className="flex-1 flex h-full min-w-0">
105
- {/* Left: Memory card list */}
107
+ {/* Left: Memory card list or Graph Toggle */}
106
108
  <div className="w-[360px] shrink-0 border-r border-white/[0.06] flex flex-col overflow-hidden">
107
109
  {/* Header + search */}
108
110
  <div className="px-3 pt-3 pb-1 shrink-0">
109
111
  <div className="flex items-center gap-2 mb-2">
110
112
  <h3 className="font-display text-[13px] font-600 text-text-2 tracking-[-0.01em] flex-1 truncate">{filterLabel}</h3>
113
+ <div className="flex bg-white/[0.04] p-0.5 rounded-[8px]">
114
+ <button
115
+ onClick={() => setViewMode('list')}
116
+ className={`p-1 rounded-[6px] transition-colors ${viewMode === 'list' ? 'bg-white/[0.08] text-text' : 'text-text-3 hover:text-text-2'}`}
117
+ >
118
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
119
+ </button>
120
+ <button
121
+ onClick={() => setViewMode('graph')}
122
+ className={`p-1 rounded-[6px] transition-colors ${viewMode === 'graph' ? 'bg-white/[0.08] text-text' : 'text-text-3 hover:text-text-2'}`}
123
+ >
124
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
125
+ </button>
126
+ </div>
111
127
  <span className="text-[10px] font-mono tabular-nums text-text-3/50">{filtered.length}</span>
112
128
  </div>
113
129
  <input
@@ -150,56 +166,68 @@ export function MemoryBrowser() {
150
166
 
151
167
  {/* Cards */}
152
168
  <div className="flex-1 overflow-y-auto">
153
- {filtered.length > 0 ? (
154
- <div className="flex flex-col gap-0.5 px-2 pb-4">
155
- {filtered.map((e) => {
156
- // Show agent info on cards when in "All Memories" view
157
- const showAgent = !memoryAgentFilter
158
- const agent = showAgent && e.agentId ? agents[e.agentId] : null
159
- return (
160
- <MemoryCard
161
- key={e.id}
162
- entry={e}
163
- active={e.id === selectedMemoryId}
164
- agentName={showAgent ? (agent?.name || null) : undefined}
165
- agentAvatarSeed={showAgent ? (agent?.avatarSeed || null) : undefined}
166
- agentAvatarUrl={showAgent ? (agent?.avatarUrl || null) : undefined}
167
- onClick={() => setSelectedMemoryId(e.id)}
168
- />
169
- )
170
- })}
171
- </div>
172
- ) : error ? (
173
- <div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
174
- <p className="font-display text-[14px] font-600 text-text-2">Couldn&apos;t load memories</p>
175
- <p className="text-[12px] text-text-3/60">{error}</p>
176
- <button
177
- onClick={() => { void load(search) }}
178
- className="px-3 py-1.5 rounded-[8px] bg-accent-soft text-accent-bright text-[12px] font-600 cursor-pointer border-none"
179
- style={{ fontFamily: 'inherit' }}
180
- >
181
- Retry
182
- </button>
183
- </div>
184
- ) : loaded ? (
185
- <div className="flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
186
- <div className="w-10 h-10 rounded-[12px] bg-accent-soft flex items-center justify-center">
187
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
188
- <ellipse cx="12" cy="5" rx="9" ry="3" />
189
- <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
190
- <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
191
- </svg>
169
+ {viewMode === 'list' ? (
170
+ filtered.length > 0 ? (
171
+ <div className="flex flex-col gap-0.5 px-2 pb-4">
172
+ {filtered.map((e) => {
173
+ // Show agent info on cards when in "All Memories" view
174
+ const showAgent = !memoryAgentFilter
175
+ const agent = showAgent && e.agentId ? agents[e.agentId] : null
176
+ return (
177
+ <MemoryCard
178
+ key={e.id}
179
+ entry={e}
180
+ active={e.id === selectedMemoryId}
181
+ agentName={showAgent ? (agent?.name || null) : undefined}
182
+ agentAvatarSeed={showAgent ? (agent?.avatarSeed || null) : undefined}
183
+ agentAvatarUrl={showAgent ? (agent?.avatarUrl || null) : undefined}
184
+ onClick={() => setSelectedMemoryId(e.id)}
185
+ />
186
+ )
187
+ })}
192
188
  </div>
193
- <p className="font-display text-[14px] font-600 text-text-2">No memories yet</p>
194
- <p className="text-[12px] text-text-3/50">Agents store knowledge here as they learn</p>
189
+ ) : error ? (
190
+ <div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
191
+ <p className="font-display text-[14px] font-600 text-text-2">Couldn&apos;t load memories</p>
192
+ <p className="text-[12px] text-text-3/60">{error}</p>
193
+ <button
194
+ onClick={() => { void load(search) }}
195
+ className="px-3 py-1.5 rounded-[8px] bg-accent-soft text-accent-bright text-[12px] font-600 cursor-pointer border-none"
196
+ style={{ fontFamily: 'inherit' }}
197
+ >
198
+ Retry
199
+ </button>
200
+ </div>
201
+ ) : loaded ? (
202
+ <div className="flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
203
+ <div className="w-10 h-10 rounded-[12px] bg-accent-soft flex items-center justify-center">
204
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
205
+ <ellipse cx="12" cy="5" rx="9" ry="3" />
206
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
207
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
208
+ </svg>
209
+ </div>
210
+ <p className="font-display text-[14px] font-600 text-text-2">No memories yet</p>
211
+ <p className="text-[12px] text-text-3/50">Agents store knowledge here as they learn</p>
212
+ </div>
213
+ ) : null
214
+ ) : (
215
+ <div className="p-4 text-[12px] text-text-3 italic">
216
+ Graph view enabled in main area.
195
217
  </div>
196
- ) : null}
218
+ )}
197
219
  </div>
198
220
  </div>
199
221
 
200
- {/* Right: Detail */}
222
+ {/* Right: Detail or Graph */}
201
223
  <div className="flex-1 flex flex-col min-w-0">
202
- <MemoryDetail />
224
+ {viewMode === 'graph' ? (
225
+ <div className="flex-1 p-4 flex flex-col">
226
+ <MemoryGraphView />
227
+ </div>
228
+ ) : (
229
+ <MemoryDetail />
230
+ )}
203
231
  </div>
204
232
  </div>
205
233
  )
@@ -0,0 +1,203 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef, useState } from 'react'
4
+ import { api } from '@/lib/api-client'
5
+ import { useAppStore } from '@/stores/use-app-store'
6
+
7
+ interface Node {
8
+ id: string
9
+ title: string
10
+ category: string
11
+ agentId?: string | null
12
+ x: number
13
+ y: number
14
+ vx: number
15
+ vy: number
16
+ }
17
+
18
+ interface Link {
19
+ source: string
20
+ target: string
21
+ type: string
22
+ }
23
+
24
+ export function MemoryGraphView() {
25
+ const [data, setData] = useState<{ nodes: Node[]; links: Link[] }>({ nodes: [], links: [] })
26
+ const [loading, setLoading] = useState(true)
27
+ const [hoveredNode, setHoveredNode] = useState<string | null>(null)
28
+ const containerRef = useRef<HTMLDivElement>(null)
29
+ const requestRef = useRef<number>(null)
30
+
31
+ const selectedMemoryId = useAppStore((s) => s.selectedMemoryId)
32
+ const setSelectedMemoryId = useAppStore((s) => s.setSelectedMemoryId)
33
+ const memoryAgentFilter = useAppStore((s) => s.memoryAgentFilter)
34
+
35
+ useEffect(() => {
36
+ async function load() {
37
+ setLoading(true)
38
+ try {
39
+ const url = `/memory/graph${memoryAgentFilter ? `?agentId=${memoryAgentFilter}` : ''}`
40
+ const res = await api<{ nodes: Node[]; links: Link[] }>('GET', url)
41
+
42
+ // Initialize positions
43
+ const nodes = res.nodes.map(n => ({
44
+ ...n,
45
+ x: Math.random() * 800,
46
+ y: Math.random() * 600,
47
+ vx: 0,
48
+ vy: 0
49
+ }))
50
+
51
+ setData({ nodes, links: res.links })
52
+ } catch (err) {
53
+ console.error('Failed to load memory graph', err)
54
+ } finally {
55
+ setLoading(false)
56
+ }
57
+ }
58
+ load()
59
+ }, [memoryAgentFilter])
60
+
61
+ // Simple Force-Directed Simulation
62
+ useEffect(() => {
63
+ if (data.nodes.length === 0) return
64
+
65
+ const animate = () => {
66
+ setData(prev => {
67
+ const nodes = [...prev.nodes]
68
+ const links = prev.links
69
+
70
+ // 1. Repulsion between all nodes
71
+ for (let i = 0; i < nodes.length; i++) {
72
+ for (let j = i + 1; j < nodes.length; j++) {
73
+ const dx = nodes[i].x - nodes[j].x
74
+ const dy = nodes[i].y - nodes[j].y
75
+ const distSq = dx * dx + dy * dy + 0.1
76
+ const force = 400 / distSq
77
+ const fx = dx * force
78
+ const fy = dy * force
79
+ nodes[i].vx += fx
80
+ nodes[i].vy += fy
81
+ nodes[j].vx -= fx
82
+ nodes[j].vy -= fy
83
+ }
84
+ }
85
+
86
+ // 2. Attraction along links
87
+ for (const link of links) {
88
+ const source = nodes.find(n => n.id === link.source)
89
+ const target = nodes.find(n => n.id === link.target)
90
+ if (source && target) {
91
+ const dx = target.x - source.x
92
+ const dy = target.y - source.y
93
+ const dist = Math.sqrt(dx * dx + dy * dy) + 0.1
94
+ const force = (dist - 100) * 0.02
95
+ const fx = (dx / dist) * force
96
+ const fy = (dy / dist) * force
97
+ source.vx += fx
98
+ source.vy += fy
99
+ target.vx -= fx
100
+ target.vy -= fy
101
+ }
102
+ }
103
+
104
+ // 3. Centering force
105
+ const cx = 400
106
+ const cy = 300
107
+ for (const node of nodes) {
108
+ node.vx += (cx - node.x) * 0.01
109
+ node.vy += (cy - node.y) * 0.01
110
+ }
111
+
112
+ // 4. Update positions with damping
113
+ for (const node of nodes) {
114
+ node.x += node.vx
115
+ node.y += node.vy
116
+ node.vx *= 0.8
117
+ node.vy *= 0.8
118
+ }
119
+
120
+ return { nodes, links }
121
+ })
122
+ requestRef.current = requestAnimationFrame(animate)
123
+ }
124
+
125
+ requestRef.current = requestAnimationFrame(animate)
126
+ return () => {
127
+ if (requestRef.current) cancelAnimationFrame(requestRef.current)
128
+ }
129
+ }, [data.nodes.length])
130
+
131
+ if (loading) {
132
+ return (
133
+ <div className="flex-1 flex items-center justify-center">
134
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-accent-bright"></div>
135
+ </div>
136
+ )
137
+ }
138
+
139
+ return (
140
+ <div ref={containerRef} className="flex-1 relative overflow-hidden bg-black/20 rounded-[16px] border border-white/[0.06]">
141
+ <svg width="100%" height="100%" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet">
142
+ {/* Links */}
143
+ {data.links.map((link, i) => {
144
+ const s = data.nodes.find(n => n.id === link.source)
145
+ const t = data.nodes.find(n => n.id === link.target)
146
+ if (!s || !t) return null
147
+ return (
148
+ <line
149
+ key={i}
150
+ x1={s.x} y1={s.y}
151
+ x2={t.x} y2={t.y}
152
+ stroke="white"
153
+ strokeOpacity="0.1"
154
+ strokeWidth="1"
155
+ />
156
+ )
157
+ })}
158
+
159
+ {/* Nodes */}
160
+ {data.nodes.map(node => (
161
+ <g
162
+ key={node.id}
163
+ transform={`translate(${node.x},${node.y})`}
164
+ onMouseEnter={() => setHoveredNode(node.id)}
165
+ onMouseLeave={() => setHoveredNode(null)}
166
+ onClick={() => setSelectedMemoryId(node.id)}
167
+ className="cursor-pointer"
168
+ >
169
+ <circle
170
+ r={selectedMemoryId === node.id ? 8 : 5}
171
+ fill={node.category === 'knowledge' ? '#10B981' : '#6366F1'}
172
+ stroke="white"
173
+ strokeWidth={selectedMemoryId === node.id ? 2 : 0}
174
+ className="transition-all"
175
+ />
176
+ {(hoveredNode === node.id || selectedMemoryId === node.id) && (
177
+ <text
178
+ y="-12"
179
+ textAnchor="middle"
180
+ className="text-[10px] fill-text font-600 pointer-events-none"
181
+ style={{ filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.5))' }}
182
+ >
183
+ {node.title}
184
+ </text>
185
+ )}
186
+ </g>
187
+ ))}
188
+ </svg>
189
+
190
+ {/* Legend */}
191
+ <div className="absolute bottom-4 left-4 p-3 bg-surface/80 backdrop-blur rounded-[12px] border border-white/[0.06] flex flex-col gap-2">
192
+ <div className="flex items-center gap-2">
193
+ <div className="w-3 h-3 rounded-full bg-[#10B981]" />
194
+ <span className="text-[11px] text-text-3">Knowledge</span>
195
+ </div>
196
+ <div className="flex items-center gap-2">
197
+ <div className="w-3 h-3 rounded-full bg-[#6366F1]" />
198
+ <span className="text-[11px] text-text-3">Note / Working</span>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ )
203
+ }
@@ -80,7 +80,7 @@ export function MemoryList({ inSidebar: _inSidebar, onSelect }: Props) {
80
80
  return (
81
81
  <div className="flex-1 flex flex-col overflow-y-auto">
82
82
  {/* Search */}
83
- <div className="px-3 py-2 shrink-0">
83
+ <div className="px-3 py-2 shrink-0" style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
84
84
  <input
85
85
  type="text"
86
86
  value={search}
@@ -94,7 +94,7 @@ export function MemoryList({ inSidebar: _inSidebar, onSelect }: Props) {
94
94
 
95
95
  {/* Agent filter tabs */}
96
96
  {entries.length > 0 && hasMultipleAgents && (
97
- <div className="px-3 pb-1.5 shrink-0">
97
+ <div className="px-3 pb-1.5 shrink-0" style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.05s both' }}>
98
98
  <div className="flex gap-1 flex-wrap">
99
99
  <button
100
100
  onClick={() => setMemoryAgentFilter(null)}
@@ -125,7 +125,7 @@ export function MemoryList({ inSidebar: _inSidebar, onSelect }: Props) {
125
125
 
126
126
  {/* Category filter */}
127
127
  {entries.length > 0 && uniqueCategories.length > 1 && (
128
- <div className="px-3 pb-1.5 shrink-0">
128
+ <div className="px-3 pb-1.5 shrink-0" style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.1s both' }}>
129
129
  <div className="flex gap-1 flex-wrap">
130
130
  <button
131
131
  onClick={() => setCategoryFilter('')}
@@ -153,21 +153,28 @@ export function MemoryList({ inSidebar: _inSidebar, onSelect }: Props) {
153
153
  {/* Memory cards */}
154
154
  {filtered.length > 0 ? (
155
155
  <div className="flex flex-col gap-0.5 px-2 pb-4">
156
- {filtered.map((e) => (
157
- <MemoryCard
156
+ {filtered.map((e, idx) => (
157
+ <div
158
158
  key={e.id}
159
- entry={e}
160
- active={e.id === selectedMemoryId}
161
- agentName={e.agentId ? (agents[e.agentId]?.name || null) : null}
162
- onClick={() => {
163
- setSelectedMemoryId(e.id)
164
- onSelect?.()
159
+ style={{
160
+ animation: 'fade-up 0.4s var(--ease-spring) both',
161
+ animationDelay: `${0.15 + idx * 0.02}s`
165
162
  }}
166
- />
163
+ >
164
+ <MemoryCard
165
+ entry={e}
166
+ active={e.id === selectedMemoryId}
167
+ agentName={e.agentId ? (agents[e.agentId]?.name || null) : null}
168
+ onClick={() => {
169
+ setSelectedMemoryId(e.id)
170
+ onSelect?.()
171
+ }}
172
+ />
173
+ </div>
167
174
  ))}
168
175
  </div>
169
176
  ) : error ? (
170
- <div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
177
+ <div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
171
178
  <p className="font-display text-[14px] font-600 text-text-2">Couldn&apos;t load memories</p>
172
179
  <p className="text-[12px] text-text-3/60">{error}</p>
173
180
  <button