@tanstack/ai-devtools-core 0.4.24 → 0.5.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.
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/SPYBPW3X.js')
5
+ () => import('./Shell/WD22ACIB.js')
6
6
  );
7
7
 
8
8
  export { AiDevtoolsCore };
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/3ITTEC6O.js')
5
+ () => import('./Shell/DRZSZBFE.js')
6
6
  );
7
7
 
8
8
  export { AiDevtoolsCore };
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/54VAGOHT.js')
5
+ () => import('./Shell/G2SFOZV2.js')
6
6
  );
7
7
 
8
8
  export { AiDevtoolsCore };
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import { constructCoreClass } from '@tanstack/devtools-utils/solid/class';
2
2
 
3
3
  // src/core.ts
4
4
  var [AiDevtoolsCore, AiDevtoolsCoreNoOp] = constructCoreClass(
5
- () => import('./Shell/54VAGOHT.js')
5
+ () => import('./Shell/G2SFOZV2.js')
6
6
  );
7
7
 
8
8
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-devtools-core",
3
- "version": "0.4.24",
3
+ "version": "0.5.0",
4
4
  "description": "Core TanStack AI Devtools plugin for inspecting chat messages, tool calls, streams, and errors.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -84,23 +84,23 @@
84
84
  "@tanstack/devtools-utils": "^0.4.0",
85
85
  "goober": "^2.1.18",
86
86
  "solid-js": "^1.9.10",
87
- "@tanstack/ai": "0.42.0",
88
- "@tanstack/ai-event-client": "0.6.8"
87
+ "@tanstack/ai": "0.43.0",
88
+ "@tanstack/ai-event-client": "0.7.0"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@vitest/coverage-v8": "4.0.14",
92
92
  "jsdom": "^27.2.0",
93
93
  "tsup": "^8.5.1",
94
94
  "tsup-preset-solid": "^2.2.0",
95
- "vite": "^7.3.3",
95
+ "vite": "^8.1.4",
96
96
  "vite-plugin-solid": "^2.11.10"
97
97
  },
98
98
  "scripts": {
99
99
  "build": "tsup",
100
100
  "clean": "premove ./build ./dist",
101
- "lint:fix": "eslint ./src --fix",
101
+ "lint:fix": "oxlint src --type-aware --fix",
102
102
  "test:build": "publint --strict",
103
- "test:eslint": "eslint ./src",
103
+ "test:oxlint": "oxlint src --type-aware",
104
104
  "test:lib": "vitest --passWithNoTests",
105
105
  "test:lib:dev": "pnpm test:lib --watch",
106
106
  "test:types": "tsc"
@@ -4,6 +4,7 @@ import { useStyles } from '../../styles/use-styles'
4
4
  import { getHookUnseenEventCount } from '../../store/hook-registry'
5
5
  import {
6
6
  getHookDisplayName,
7
+ getHookIdentitySubtitle,
7
8
  groupHooksByCategory,
8
9
  isHookRunning,
9
10
  visibleHooks,
@@ -244,9 +245,7 @@ export const HookDashboard: Component = () => {
244
245
  </Show>
245
246
  </div>
246
247
  <span class={styles().hookDashboard.rowId}>
247
- <Show when={hook.displayName} fallback={hook.id}>
248
- {hook.hookName} - {hook.id}
249
- </Show>
248
+ {getHookIdentitySubtitle(hook)}
250
249
  </span>
251
250
  </div>
252
251
  <div class={styles().hookDashboard.rowMeta}>
@@ -12,6 +12,8 @@ import { ToolFixtureForm } from './ToolFixtureForm'
12
12
  import {
13
13
  createHookDashboardSummary,
14
14
  getHookDisplayName,
15
+ getHookIdentityLabel,
16
+ getHookIdentitySubtitle,
15
17
  groupHooksByCategory,
16
18
  isHookRunning,
17
19
  visibleHooks,
@@ -36,6 +38,7 @@ import {
36
38
  visiblePreviewPartsForMessage,
37
39
  } from './preview-messages'
38
40
  import { GenerationPanel, GenerationPreview } from './GenerationPanel'
41
+ import { MemoryPanel } from './MemoryPanel'
39
42
  import type { HoverOrigin, HoverTarget, PreviewJsonItem } from './preview-model'
40
43
  import type {
41
44
  HookRecord,
@@ -46,7 +49,7 @@ import type {
46
49
  import type { Conversation, Message, ToolCall } from '../../store/ai-store'
47
50
  import type { Component, Setter } from 'solid-js'
48
51
 
49
- type DetailTab = 'conversation' | 'tools' | 'state'
52
+ type DetailTab = 'conversation' | 'tools' | 'state' | 'memory'
50
53
  type MessagePart = NonNullable<Message['parts']>[number]
51
54
  const scrollAnimations = new WeakMap<HTMLElement, number>()
52
55
 
@@ -144,7 +147,12 @@ export const HookDetails: Component = () => {
144
147
  })
145
148
 
146
149
  createEffect(() => {
147
- if (isGenerationHook() && activeTab() === 'tools') {
150
+ // Tools and Memory are chat-only tabs; if a generation hook becomes active
151
+ // while one of them is selected, fall back to the conversation view.
152
+ if (
153
+ isGenerationHook() &&
154
+ (activeTab() === 'tools' || activeTab() === 'memory')
155
+ ) {
148
156
  setActiveTab('conversation')
149
157
  }
150
158
  })
@@ -240,6 +248,14 @@ export const HookDetails: Component = () => {
240
248
  activeTab={activeTab()}
241
249
  onSelect={setActiveTab}
242
250
  />
251
+ <Show when={!isGenerationHook()}>
252
+ <TabButton
253
+ label="Memory"
254
+ tab="memory"
255
+ activeTab={activeTab()}
256
+ onSelect={setActiveTab}
257
+ />
258
+ </Show>
243
259
  </nav>
244
260
 
245
261
  <div
@@ -283,6 +299,9 @@ export const HookDetails: Component = () => {
283
299
  <Show when={activeTab() === 'state'}>
284
300
  <JsonPanel value={activeHook().state} />
285
301
  </Show>
302
+ <Show when={activeTab() === 'memory'}>
303
+ <MemoryPanel />
304
+ </Show>
286
305
  </main>
287
306
 
288
307
  <Show when={showSecondaryPane()}>
@@ -429,9 +448,7 @@ const HookOverview: Component<{
429
448
  {getHookDisplayName(hook)}
430
449
  </span>
431
450
  <span class={styles().hookDetails.overviewHookId}>
432
- <Show when={hook.displayName} fallback={hook.id}>
433
- {hook.hookName} - {hook.id}
434
- </Show>
451
+ {getHookIdentitySubtitle(hook)}
435
452
  </span>
436
453
  </div>
437
454
  <div class={styles().hookDetails.overviewHookMeta}>
@@ -513,9 +530,21 @@ const HookHeader: Component<{
513
530
  {props.hook.hookName}
514
531
  </span>
515
532
  </Show>
516
- <span>{props.hook.id}</span>
517
- <Show when={props.hook.threadId}>
518
- <span>thread {props.hook.threadId}</span>
533
+ {/* Prefer threadId as the user-facing identity (generation scope /
534
+ chat thread). Only show the raw registry id when it differs —
535
+ after the client prefers threadId over deprecated `id`, the two
536
+ often match and duplicating them is noise. */}
537
+ <span data-testid="ai-devtools-hook-identity">
538
+ {props.hook.threadId
539
+ ? `thread ${props.hook.threadId}`
540
+ : getHookIdentityLabel(props.hook)}
541
+ </span>
542
+ <Show
543
+ when={props.hook.threadId && props.hook.threadId !== props.hook.id}
544
+ >
545
+ <span data-testid="ai-devtools-hook-registry-id">
546
+ id {props.hook.id}
547
+ </span>
519
548
  </Show>
520
549
  <Show when={props.hook.framework}>
521
550
  <span>{props.hook.framework}</span>
@@ -0,0 +1,296 @@
1
+ import { For, Show, createMemo, createSignal } from 'solid-js'
2
+ import { useAIStore } from '../../store/ai-context'
3
+ import { useStyles } from '../../styles/use-styles'
4
+ import type { Component } from 'solid-js'
5
+ import { memoryScopeLabel } from '../../store/memory-registry'
6
+ import type {
7
+ MemoryEventRecord,
8
+ MemoryScopeState,
9
+ } from '../../store/memory-registry'
10
+
11
+ /**
12
+ * DevTools "Memory" tab. Memory is per composite scope (tenant/user/thread),
13
+ * not per-hook, so this panel reads the whole `state.memory` registry and lets
14
+ * the user pick a scope (defaulting to the most recently active). It renders
15
+ * two things:
16
+ * 1. Live contents — the latest `inspect()` records + `listFacts()` facts,
17
+ * pushed via `memory:snapshot` (only for adapters that support inspection).
18
+ * 2. Operations timeline — the `memory:*` recall/save/error events (always
19
+ * available, even when the adapter has no introspection).
20
+ */
21
+
22
+ /** Shape of a record inside the built-in adapters' `inspect()` payload. */
23
+ interface MemoryRecordRow {
24
+ id: string
25
+ text: string
26
+ kind: string
27
+ role?: string
28
+ createdAt?: number
29
+ importance?: number
30
+ }
31
+
32
+ /** Best-effort extraction of `{ records: [...] }` from the opaque snapshot data. */
33
+ function extractRecords(data: unknown): Array<MemoryRecordRow> {
34
+ if (!data || typeof data !== 'object') return []
35
+ const records = (data as { records?: unknown }).records
36
+ if (!Array.isArray(records)) return []
37
+ return records.filter(
38
+ (r): r is MemoryRecordRow =>
39
+ Boolean(r) &&
40
+ typeof r === 'object' &&
41
+ typeof (r as MemoryRecordRow).id === 'string' &&
42
+ typeof (r as MemoryRecordRow).text === 'string',
43
+ )
44
+ }
45
+
46
+ function formatTime(value: number | string | undefined): string {
47
+ if (value === undefined) return ''
48
+ const date = new Date(value)
49
+ if (Number.isNaN(date.getTime())) return ''
50
+ return date.toLocaleTimeString()
51
+ }
52
+
53
+ function eventSummary(event: MemoryEventRecord): string {
54
+ switch (event.type) {
55
+ case 'retrieve:started':
56
+ return `recall "${event.query ?? ''}"`
57
+ case 'retrieve:completed':
58
+ return `recalled ${event.fragmentCount ?? 0} fragment(s), ${
59
+ event.systemPromptChars ?? 0
60
+ } prompt chars${event.hasTools ? ', +tools' : ''} (${event.durationMs ?? 0}ms)`
61
+ case 'persist:started':
62
+ return 'save started'
63
+ case 'persist:completed':
64
+ return `saved ${event.okCount ?? 0}/${event.receiptCount ?? 0} receipt(s) (${
65
+ event.durationMs ?? 0
66
+ }ms)`
67
+ case 'error':
68
+ return `${event.phase ?? ''} error: ${event.error?.message ?? 'unknown'}`
69
+ default:
70
+ return event.type
71
+ }
72
+ }
73
+
74
+ export const MemoryPanel: Component = () => {
75
+ const { state, clearMemory } = useAIStore()
76
+ const styles = useStyles()
77
+ const [override, setOverride] = createSignal<string | null>(null)
78
+
79
+ // Scope keys sorted most-recently-active first.
80
+ const scopeKeys = createMemo(() =>
81
+ Object.values(state.memory.scopes)
82
+ .slice()
83
+ .sort((a, b) => b.lastActivity - a.lastActivity)
84
+ .map((s) => s.key),
85
+ )
86
+
87
+ const selectedKey = createMemo(() => {
88
+ const chosen = override()
89
+ if (chosen && state.memory.scopes[chosen]) return chosen
90
+ return scopeKeys()[0] ?? null
91
+ })
92
+
93
+ const scope = createMemo((): MemoryScopeState | undefined => {
94
+ const key = selectedKey()
95
+ return key ? state.memory.scopes[key] : undefined
96
+ })
97
+
98
+ const records = createMemo(() => extractRecords(scope()?.snapshot?.data))
99
+ const facts = createMemo(() => scope()?.snapshot?.facts ?? [])
100
+ // Timeline newest-first.
101
+ const events = createMemo(() =>
102
+ (scope()?.events ?? []).slice().sort((a, b) => b.timestamp - a.timestamp),
103
+ )
104
+
105
+ return (
106
+ <div
107
+ class={styles().memoryPanel.container}
108
+ data-testid="ai-devtools-memory-panel"
109
+ >
110
+ <Show
111
+ when={scope()}
112
+ fallback={
113
+ <div class={styles().memoryPanel.empty}>
114
+ No memory activity yet. Send a message through a chat wired with
115
+ <code> memoryMiddleware</code> and recall/save events will appear
116
+ here.
117
+ </div>
118
+ }
119
+ >
120
+ {(activeScope) => (
121
+ <>
122
+ <div class={styles().memoryPanel.toolbar}>
123
+ <div class={styles().memoryPanel.scopeControls}>
124
+ <Show
125
+ when={scopeKeys().length > 1}
126
+ fallback={
127
+ <span class={styles().memoryPanel.badge}>
128
+ {activeScope().adapter ?? 'memory'}
129
+ </span>
130
+ }
131
+ >
132
+ <select
133
+ class={styles().memoryPanel.scopeSelect}
134
+ value={selectedKey() ?? ''}
135
+ onChange={(e) => setOverride(e.currentTarget.value)}
136
+ data-testid="ai-devtools-memory-scope-select"
137
+ >
138
+ <For each={scopeKeys()}>
139
+ {(key) => {
140
+ const entry = state.memory.scopes[key]
141
+ const label = entry ? memoryScopeLabel(entry) : key
142
+ return (
143
+ <option value={key}>
144
+ {entry?.adapter ? `${entry.adapter} · ` : ''}
145
+ {label.length > 40
146
+ ? `${label.slice(0, 40)}…`
147
+ : label}
148
+ </option>
149
+ )
150
+ }}
151
+ </For>
152
+ </select>
153
+ </Show>
154
+ </div>
155
+ <button
156
+ type="button"
157
+ class={styles().memoryPanel.clearButton}
158
+ onClick={() => {
159
+ setOverride(null)
160
+ clearMemory()
161
+ }}
162
+ >
163
+ Clear
164
+ </button>
165
+ </div>
166
+
167
+ {/* Live contents (inspect + listFacts) */}
168
+ <section class={styles().memoryPanel.section}>
169
+ <div class={styles().memoryPanel.sectionTitle}>
170
+ Stored records ({records().length})
171
+ <Show when={activeScope().snapshot}>
172
+ {(snap) => (
173
+ <span class={styles().memoryPanel.time}>
174
+ snapshot {formatTime(snap().takenAt)}
175
+ </span>
176
+ )}
177
+ </Show>
178
+ </div>
179
+ <Show
180
+ when={records().length > 0}
181
+ fallback={
182
+ <div class={styles().memoryPanel.sectionEmpty}>
183
+ {activeScope().snapshot
184
+ ? 'Snapshot is empty.'
185
+ : 'This adapter does not expose inspect() — see the timeline below for activity.'}
186
+ </div>
187
+ }
188
+ >
189
+ <div class={styles().memoryPanel.list}>
190
+ <For each={records()}>
191
+ {(rec) => (
192
+ <div
193
+ class={styles().memoryPanel.row}
194
+ data-testid="ai-devtools-memory-record"
195
+ >
196
+ <div class={styles().memoryPanel.rowHeader}>
197
+ <span class={styles().memoryPanel.badge}>
198
+ {rec.kind}
199
+ </span>
200
+ <Show when={rec.role}>
201
+ <span>{rec.role}</span>
202
+ </Show>
203
+ <Show when={rec.importance !== undefined}>
204
+ <span>importance {rec.importance?.toFixed(2)}</span>
205
+ </Show>
206
+ <span class={styles().memoryPanel.time}>
207
+ {formatTime(rec.createdAt)}
208
+ </span>
209
+ </div>
210
+ <div class={styles().memoryPanel.rowText}>
211
+ {rec.text}
212
+ </div>
213
+ </div>
214
+ )}
215
+ </For>
216
+ </div>
217
+ </Show>
218
+ </section>
219
+
220
+ {/* Facts */}
221
+ <section class={styles().memoryPanel.section}>
222
+ <div class={styles().memoryPanel.sectionTitle}>
223
+ listFacts() ({facts().length})
224
+ </div>
225
+ <Show
226
+ when={facts().length > 0}
227
+ fallback={
228
+ <div class={styles().memoryPanel.sectionEmpty}>No facts.</div>
229
+ }
230
+ >
231
+ <div class={styles().memoryPanel.list}>
232
+ <For each={facts()}>
233
+ {(fact) => (
234
+ <div class={styles().memoryPanel.row}>
235
+ <div class={styles().memoryPanel.rowText}>
236
+ <Show when={fact.source}>
237
+ <span class={styles().memoryPanel.rowHeader}>
238
+ {fact.source}:{' '}
239
+ </span>
240
+ </Show>
241
+ {fact.text}
242
+ </div>
243
+ </div>
244
+ )}
245
+ </For>
246
+ </div>
247
+ </Show>
248
+ </section>
249
+
250
+ {/* Operations timeline */}
251
+ <section class={styles().memoryPanel.section}>
252
+ <div class={styles().memoryPanel.sectionTitle}>
253
+ Operations ({events().length})
254
+ </div>
255
+ <Show
256
+ when={events().length > 0}
257
+ fallback={
258
+ <div class={styles().memoryPanel.sectionEmpty}>
259
+ No operations recorded.
260
+ </div>
261
+ }
262
+ >
263
+ <div class={styles().memoryPanel.list}>
264
+ <For each={events()}>
265
+ {(event) => (
266
+ <div
267
+ class={`${styles().memoryPanel.row} ${
268
+ event.type === 'error'
269
+ ? styles().memoryPanel.rowError
270
+ : ''
271
+ }`}
272
+ data-testid="ai-devtools-memory-event"
273
+ >
274
+ <div class={styles().memoryPanel.rowHeader}>
275
+ <span class={styles().memoryPanel.badge}>
276
+ {event.type}
277
+ </span>
278
+ <span class={styles().memoryPanel.time}>
279
+ {formatTime(event.timestamp)}
280
+ </span>
281
+ </div>
282
+ <div class={styles().memoryPanel.rowText}>
283
+ {eventSummary(event)}
284
+ </div>
285
+ </div>
286
+ )}
287
+ </For>
288
+ </div>
289
+ </Show>
290
+ </section>
291
+ </>
292
+ )}
293
+ </Show>
294
+ </div>
295
+ )
296
+ }
@@ -61,6 +61,32 @@ export function getHookDisplayName(hook: HookRecord): string {
61
61
  return hook.displayName ?? hook.hookName
62
62
  }
63
63
 
64
+ /**
65
+ * Stable identity string shown under the hook title.
66
+ *
67
+ * Prefer `threadId` when present — for generation hooks that is the single
68
+ * app-chosen scope (and, after the client prefers `threadId` over deprecated
69
+ * `id`, often equals the registry key too). Fall back to the registry `id`
70
+ * (devtools hook id / legacy client id) for ephemeral hooks that never named
71
+ * a thread.
72
+ */
73
+ export function getHookIdentityLabel(hook: HookRecord): string {
74
+ return hook.threadId ?? hook.id
75
+ }
76
+
77
+ /**
78
+ * Secondary subtitle under the title: either just the identity, or
79
+ * `hookName - identity` when a custom display name is set so the technical
80
+ * name stays visible.
81
+ */
82
+ export function getHookIdentitySubtitle(hook: HookRecord): string {
83
+ const identity = getHookIdentityLabel(hook)
84
+ if (hook.displayName) {
85
+ return `${hook.hookName} - ${identity}`
86
+ }
87
+ return identity
88
+ }
89
+
64
90
  export function groupHooksByCategory(
65
91
  hooks: Array<HookRecord>,
66
92
  ): Array<HookCategoryGroup> {
@@ -1,4 +1,5 @@
1
1
  export { HookDashboard } from './HookDashboard'
2
2
  export { HookDetails } from './HookDetails'
3
3
  export { GenerationPanel, GenerationPreview } from './GenerationPanel'
4
+ export { MemoryPanel } from './MemoryPanel'
4
5
  export { ToolFixtureForm } from './ToolFixtureForm'
@@ -14,12 +14,19 @@ import {
14
14
  createClientToolCallMessage,
15
15
  shouldSkipClientAssistantPlaceholder,
16
16
  } from './message-event-utils'
17
+ import {
18
+ applyMemoryEvent,
19
+ applyMemorySnapshot,
20
+ clearMemoryRegistry,
21
+ createMemoryRegistryState,
22
+ } from './memory-registry'
17
23
  import type { ContentPartSource, TokenUsage } from '@tanstack/ai'
18
24
  import type {
19
25
  DevtoolsToolFixtureApplyEvent,
20
26
  RunLifecycleEvent,
21
27
  } from '@tanstack/ai-event-client'
22
28
  import type { HookRegistryState, ToolFixtureRecord } from './hook-registry'
29
+ import type { MemoryRegistryState } from './memory-registry'
23
30
  import type { ParentComponent } from 'solid-js'
24
31
 
25
32
  interface MessagePart {
@@ -227,6 +234,7 @@ interface AIStoreState {
227
234
  conversations: Record<string, Conversation>
228
235
  activeConversationId: string | null
229
236
  hooks: HookRegistryState
237
+ memory: MemoryRegistryState
230
238
  }
231
239
 
232
240
  interface AIContextValue {
@@ -234,6 +242,7 @@ interface AIContextValue {
234
242
  clearAllConversations: () => void
235
243
  selectConversation: (id: string) => void
236
244
  clearHooks: () => void
245
+ clearMemory: () => void
237
246
  selectHook: (id: string | null) => void
238
247
  saveToolFixture: (fixture: ToolFixtureRecord) => void
239
248
  deleteToolFixture: (fixtureId: string) => void
@@ -255,6 +264,7 @@ export const AIProvider: ParentComponent = (props) => {
255
264
  conversations: {},
256
265
  activeConversationId: null,
257
266
  hooks: createHookRegistryState(),
267
+ memory: createMemoryRegistryState(),
258
268
  })
259
269
 
260
270
  const streamToConversation = new Map<string, string>()
@@ -641,6 +651,15 @@ export const AIProvider: ParentComponent = (props) => {
641
651
  )
642
652
  }
643
653
 
654
+ function clearMemory() {
655
+ setState(
656
+ 'memory',
657
+ produce((memory: MemoryRegistryState) => {
658
+ clearMemoryRegistry(memory)
659
+ }),
660
+ )
661
+ }
662
+
644
663
  function selectHook(id: string | null) {
645
664
  setState(
646
665
  'hooks',
@@ -1188,6 +1207,48 @@ export const AIProvider: ParentComponent = (props) => {
1188
1207
  }),
1189
1208
  )
1190
1209
 
1210
+ // Memory: the 5 `memory:*` operation events feed the timeline; `memory:snapshot`
1211
+ // replaces the per-scope stored-state view. Keyed by composite scope
1212
+ // (tenantId/userId/threadId).
1213
+ type MemoryEventInput = Parameters<typeof applyMemoryEvent>[1]
1214
+ const recordMemoryEvent = (
1215
+ type: MemoryEventInput['type'],
1216
+ payload: object,
1217
+ ) => {
1218
+ setState(
1219
+ 'memory',
1220
+ produce((memory: MemoryRegistryState) => {
1221
+ applyMemoryEvent(memory, { ...payload, type } as MemoryEventInput)
1222
+ }),
1223
+ )
1224
+ }
1225
+
1226
+ cleanupFns.push(
1227
+ aiEventClient.on('memory:retrieve:started', (e) => {
1228
+ recordMemoryEvent('retrieve:started', e.payload)
1229
+ }),
1230
+ aiEventClient.on('memory:retrieve:completed', (e) => {
1231
+ recordMemoryEvent('retrieve:completed', e.payload)
1232
+ }),
1233
+ aiEventClient.on('memory:persist:started', (e) => {
1234
+ recordMemoryEvent('persist:started', e.payload)
1235
+ }),
1236
+ aiEventClient.on('memory:persist:completed', (e) => {
1237
+ recordMemoryEvent('persist:completed', e.payload)
1238
+ }),
1239
+ aiEventClient.on('memory:error', (e) => {
1240
+ recordMemoryEvent('error', e.payload)
1241
+ }),
1242
+ aiEventClient.on('memory:snapshot', (e) => {
1243
+ setState(
1244
+ 'memory',
1245
+ produce((memory: MemoryRegistryState) => {
1246
+ applyMemorySnapshot(memory, e.payload)
1247
+ }),
1248
+ )
1249
+ }),
1250
+ )
1251
+
1191
1252
  const recordRunEvent = (
1192
1253
  eventName:
1193
1254
  | 'run:created'
@@ -3402,6 +3463,7 @@ export const AIProvider: ParentComponent = (props) => {
3402
3463
  clearAllConversations,
3403
3464
  selectConversation,
3404
3465
  clearHooks,
3466
+ clearMemory,
3405
3467
  selectHook,
3406
3468
  saveToolFixture,
3407
3469
  deleteToolFixture,