@stigmer/react 3.12.6 → 3.12.8

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 (219) hide show
  1. package/composer/ComposerToolbar.d.ts.map +1 -1
  2. package/composer/ComposerToolbar.js.map +1 -1
  3. package/execution/MessageThread.d.ts.map +1 -1
  4. package/execution/MessageThread.js +8 -11
  5. package/execution/MessageThread.js.map +1 -1
  6. package/execution/ResultView.d.ts.map +1 -1
  7. package/execution/ResultView.js +6 -0
  8. package/execution/ResultView.js.map +1 -1
  9. package/execution/ToolCallItem.d.ts.map +1 -1
  10. package/execution/ToolCallItem.js +13 -2
  11. package/execution/ToolCallItem.js.map +1 -1
  12. package/execution/tool-categories.d.ts +1 -1
  13. package/execution/tool-categories.d.ts.map +1 -1
  14. package/execution/tool-categories.js +6 -0
  15. package/execution/tool-categories.js.map +1 -1
  16. package/execution/useFileChangeContent.d.ts +2 -6
  17. package/execution/useFileChangeContent.d.ts.map +1 -1
  18. package/execution/useFileChangeContent.js +4 -12
  19. package/execution/useFileChangeContent.js.map +1 -1
  20. package/identity-account/AccountPreferencesPanel.d.ts +2 -1
  21. package/identity-account/AccountPreferencesPanel.d.ts.map +1 -1
  22. package/identity-account/AccountPreferencesPanel.js +41 -3
  23. package/identity-account/AccountPreferencesPanel.js.map +1 -1
  24. package/identity-account/useAccountExecutionDefaults.d.ts +7 -0
  25. package/identity-account/useAccountExecutionDefaults.d.ts.map +1 -1
  26. package/identity-account/useAccountExecutionDefaults.js +5 -2
  27. package/identity-account/useAccountExecutionDefaults.js.map +1 -1
  28. package/index.d.ts +5 -2
  29. package/index.d.ts.map +1 -1
  30. package/index.js +5 -1
  31. package/index.js.map +1 -1
  32. package/internal/MemoryEnabledRow.d.ts +37 -0
  33. package/internal/MemoryEnabledRow.d.ts.map +1 -0
  34. package/internal/MemoryEnabledRow.js +25 -0
  35. package/internal/MemoryEnabledRow.js.map +1 -0
  36. package/internal/download.d.ts +8 -0
  37. package/internal/download.d.ts.map +1 -0
  38. package/internal/download.js +25 -0
  39. package/internal/download.js.map +1 -0
  40. package/library/useExportResource.d.ts.map +1 -1
  41. package/library/useExportResource.js +3 -17
  42. package/library/useExportResource.js.map +1 -1
  43. package/memory/MemoryListPanel.d.ts +35 -0
  44. package/memory/MemoryListPanel.d.ts.map +1 -0
  45. package/memory/MemoryListPanel.js +141 -0
  46. package/memory/MemoryListPanel.js.map +1 -0
  47. package/memory/MemoryProposalCard.d.ts +35 -0
  48. package/memory/MemoryProposalCard.d.ts.map +1 -0
  49. package/memory/MemoryProposalCard.js +73 -0
  50. package/memory/MemoryProposalCard.js.map +1 -0
  51. package/memory/index.d.ts +10 -0
  52. package/memory/index.d.ts.map +1 -0
  53. package/memory/index.js +14 -0
  54. package/memory/index.js.map +1 -0
  55. package/memory/memoryGroups.d.ts +31 -0
  56. package/memory/memoryGroups.d.ts.map +1 -0
  57. package/memory/memoryGroups.js +52 -0
  58. package/memory/memoryGroups.js.map +1 -0
  59. package/memory/useConfirmMemory.d.ts +31 -0
  60. package/memory/useConfirmMemory.d.ts.map +1 -0
  61. package/memory/useConfirmMemory.js +43 -0
  62. package/memory/useConfirmMemory.js.map +1 -0
  63. package/memory/useDeleteMemory.d.ts +30 -0
  64. package/memory/useDeleteMemory.d.ts.map +1 -0
  65. package/memory/useDeleteMemory.js +42 -0
  66. package/memory/useDeleteMemory.js.map +1 -0
  67. package/memory/useMemories.d.ts +33 -0
  68. package/memory/useMemories.d.ts.map +1 -0
  69. package/memory/useMemories.js +42 -0
  70. package/memory/useMemories.js.map +1 -0
  71. package/memory/useMemory.d.ts +39 -0
  72. package/memory/useMemory.d.ts.map +1 -0
  73. package/memory/useMemory.js +43 -0
  74. package/memory/useMemory.js.map +1 -0
  75. package/memory/useRejectMemory.d.ts +34 -0
  76. package/memory/useRejectMemory.d.ts.map +1 -0
  77. package/memory/useRejectMemory.js +46 -0
  78. package/memory/useRejectMemory.js.map +1 -0
  79. package/memory/useUpdateMemoryContent.d.ts +32 -0
  80. package/memory/useUpdateMemoryContent.d.ts.map +1 -0
  81. package/memory/useUpdateMemoryContent.js +48 -0
  82. package/memory/useUpdateMemoryContent.js.map +1 -0
  83. package/organization/OrgPreferencesPanel.d.ts.map +1 -1
  84. package/organization/OrgPreferencesPanel.js +36 -4
  85. package/organization/OrgPreferencesPanel.js.map +1 -1
  86. package/package.json +4 -4
  87. package/session/AutoApproveIndicator.d.ts +18 -0
  88. package/session/AutoApproveIndicator.d.ts.map +1 -0
  89. package/session/AutoApproveIndicator.js +23 -0
  90. package/session/AutoApproveIndicator.js.map +1 -0
  91. package/session/NewSessionViewer.d.ts.map +1 -1
  92. package/session/NewSessionViewer.js +10 -1
  93. package/session/NewSessionViewer.js.map +1 -1
  94. package/session/SessionViewer.d.ts +26 -1
  95. package/session/SessionViewer.d.ts.map +1 -1
  96. package/session/SessionViewer.js +28 -22
  97. package/session/SessionViewer.js.map +1 -1
  98. package/session/TranscriptExportMenu.d.ts +24 -0
  99. package/session/TranscriptExportMenu.d.ts.map +1 -0
  100. package/session/TranscriptExportMenu.js +26 -0
  101. package/session/TranscriptExportMenu.js.map +1 -0
  102. package/session/facets/SetupTab.d.ts +43 -6
  103. package/session/facets/SetupTab.d.ts.map +1 -1
  104. package/session/facets/SetupTab.js +32 -36
  105. package/session/facets/SetupTab.js.map +1 -1
  106. package/session/facets/UsageTab.d.ts +2 -1
  107. package/session/facets/UsageTab.d.ts.map +1 -1
  108. package/session/facets/UsageTab.js +11 -5
  109. package/session/facets/UsageTab.js.map +1 -1
  110. package/session/facets/index.d.ts +1 -1
  111. package/session/facets/index.d.ts.map +1 -1
  112. package/session/facets/primitives.d.ts +58 -0
  113. package/session/facets/primitives.d.ts.map +1 -0
  114. package/session/facets/primitives.js +59 -0
  115. package/session/facets/primitives.js.map +1 -0
  116. package/session/index.d.ts +5 -1
  117. package/session/index.d.ts.map +1 -1
  118. package/session/index.js +2 -0
  119. package/session/index.js.map +1 -1
  120. package/session/useExportTranscript.d.ts +44 -0
  121. package/session/useExportTranscript.d.ts.map +1 -0
  122. package/session/useExportTranscript.js +109 -0
  123. package/session/useExportTranscript.js.map +1 -0
  124. package/session/useNewSessionFlow.d.ts +16 -0
  125. package/session/useNewSessionFlow.d.ts.map +1 -1
  126. package/session/useNewSessionFlow.js +25 -6
  127. package/session/useNewSessionFlow.js.map +1 -1
  128. package/session/useSessionConversation.d.ts.map +1 -1
  129. package/session/useSessionConversation.js +7 -19
  130. package/session/useSessionConversation.js.map +1 -1
  131. package/session/useSessionExecutions.d.ts +2 -14
  132. package/session/useSessionExecutions.d.ts.map +1 -1
  133. package/session/useSessionExecutions.js +4 -26
  134. package/session/useSessionExecutions.js.map +1 -1
  135. package/session/useSessionPageFlow.d.ts +41 -10
  136. package/session/useSessionPageFlow.d.ts.map +1 -1
  137. package/session/useSessionPageFlow.js +67 -9
  138. package/session/useSessionPageFlow.js.map +1 -1
  139. package/settings/MemorySection.d.ts +12 -0
  140. package/settings/MemorySection.d.ts.map +1 -0
  141. package/settings/MemorySection.js +21 -0
  142. package/settings/MemorySection.js.map +1 -0
  143. package/settings/index.d.ts +1 -0
  144. package/settings/index.d.ts.map +1 -1
  145. package/settings/index.js +1 -0
  146. package/settings/index.js.map +1 -1
  147. package/settings/settings-nav.d.ts.map +1 -1
  148. package/settings/settings-nav.js +2 -1
  149. package/settings/settings-nav.js.map +1 -1
  150. package/src/__tests__/public-api.test.ts +21 -0
  151. package/src/attachment/__tests__/prepare-image.test.ts +12 -6
  152. package/src/composer/ComposerToolbar.tsx +1 -0
  153. package/src/execution/MessageThread.tsx +8 -11
  154. package/src/execution/ResultView.tsx +6 -0
  155. package/src/execution/ToolCallItem.tsx +18 -1
  156. package/src/execution/tool-categories.ts +7 -0
  157. package/src/execution/useFileChangeContent.ts +5 -12
  158. package/src/identity-account/AccountPreferencesPanel.tsx +89 -2
  159. package/src/identity-account/__tests__/AccountPreferencesPanel.test.tsx +112 -0
  160. package/src/identity-account/__tests__/useAccountExecutionDefaults.test.tsx +34 -1
  161. package/src/identity-account/useAccountExecutionDefaults.ts +12 -2
  162. package/src/index.ts +38 -0
  163. package/src/internal/MemoryEnabledRow.tsx +92 -0
  164. package/src/internal/download.ts +29 -0
  165. package/src/library/useExportResource.ts +3 -19
  166. package/src/memory/MemoryListPanel.tsx +532 -0
  167. package/src/memory/MemoryProposalCard.tsx +181 -0
  168. package/src/memory/__tests__/MemoryListPanel.test.tsx +259 -0
  169. package/src/memory/__tests__/MemoryProposalCard.test.tsx +236 -0
  170. package/src/memory/__tests__/memoryHooks.test.tsx +283 -0
  171. package/src/memory/index.ts +24 -0
  172. package/src/memory/memoryGroups.ts +64 -0
  173. package/src/memory/useConfirmMemory.ts +62 -0
  174. package/src/memory/useDeleteMemory.ts +61 -0
  175. package/src/memory/useMemories.ts +66 -0
  176. package/src/memory/useMemory.ts +80 -0
  177. package/src/memory/useRejectMemory.ts +65 -0
  178. package/src/memory/useUpdateMemoryContent.ts +67 -0
  179. package/src/organization/OrgPreferencesPanel.tsx +52 -3
  180. package/src/organization/__tests__/OrgPreferencesPanel.test.tsx +65 -0
  181. package/src/session/AutoApproveIndicator.tsx +45 -0
  182. package/src/session/NewSessionViewer.tsx +17 -0
  183. package/src/session/SessionViewer.tsx +78 -41
  184. package/src/session/TranscriptExportMenu.tsx +78 -0
  185. package/src/session/__tests__/NewSessionViewer.test.tsx +32 -0
  186. package/src/session/__tests__/SessionViewer-autoApprove.test.tsx +244 -0
  187. package/src/session/__tests__/SessionViewer-transcriptExport.test.tsx +189 -0
  188. package/src/session/__tests__/TranscriptExportMenu.test.tsx +97 -0
  189. package/src/session/__tests__/useExportTranscript.test.tsx +187 -0
  190. package/src/session/__tests__/useNewSessionFlow.test.tsx +89 -0
  191. package/src/session/__tests__/useSessionPageFlow.autoApprove.test.tsx +263 -2
  192. package/src/session/facets/SetupTab.tsx +193 -157
  193. package/src/session/facets/UsageTab.tsx +38 -38
  194. package/src/session/facets/index.ts +1 -0
  195. package/src/session/facets/primitives.tsx +169 -0
  196. package/src/session/index.ts +10 -0
  197. package/src/session/useExportTranscript.ts +171 -0
  198. package/src/session/useNewSessionFlow.ts +42 -6
  199. package/src/session/useSessionConversation.ts +10 -16
  200. package/src/session/useSessionExecutions.ts +5 -26
  201. package/src/session/useSessionPageFlow.ts +108 -21
  202. package/src/settings/MemorySection.tsx +45 -0
  203. package/src/settings/index.ts +1 -0
  204. package/src/settings/settings-nav.ts +2 -0
  205. package/src/switch/Switch.tsx +4 -0
  206. package/src/usage/useExportCSV.ts +2 -14
  207. package/src/workflow/__tests__/useWorkflowCanvas.dirty.test.tsx +7 -2
  208. package/src/workflow/__tests__/useWorkflowCanvas.drag.test.tsx +7 -2
  209. package/src/workspace/WorkspaceSurface.tsx +26 -3
  210. package/styles.css +1 -1
  211. package/switch/Switch.d.ts +3 -1
  212. package/switch/Switch.d.ts.map +1 -1
  213. package/switch/Switch.js +2 -2
  214. package/switch/Switch.js.map +1 -1
  215. package/usage/useExportCSV.d.ts.map +1 -1
  216. package/usage/useExportCSV.js +2 -13
  217. package/usage/useExportCSV.js.map +1 -1
  218. package/workspace/WorkspaceSurface.js +11 -2
  219. package/workspace/WorkspaceSurface.js.map +1 -1
@@ -0,0 +1,283 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { renderHook, waitFor } from "@testing-library/react";
3
+ import type { ReactNode } from "react";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import { MemorySchema, type Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
6
+ import { MemoryLifecycleState } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/enum_pb";
7
+ import { MemoryListSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/io_pb";
8
+ import { StigmerError } from "@stigmer/sdk";
9
+ import { StigmerContext } from "../../context";
10
+ import { FetchCacheContext } from "../../internal/FetchCacheProvider";
11
+ import { useMemories } from "../useMemories";
12
+ import { useMemory } from "../useMemory";
13
+ import { useConfirmMemory } from "../useConfirmMemory";
14
+ import { useRejectMemory } from "../useRejectMemory";
15
+ import { useDeleteMemory } from "../useDeleteMemory";
16
+ import { useUpdateMemoryContent } from "../useUpdateMemoryContent";
17
+ import { groupMemoriesByLifecycle, formatMemoryProvenance } from "../memoryGroups";
18
+
19
+ function wrapper(client: unknown) {
20
+ return function Wrapper({ children }: { children: ReactNode }) {
21
+ return (
22
+ <FetchCacheContext.Provider value={null}>
23
+ <StigmerContext.Provider value={client as never}>
24
+ {children}
25
+ </StigmerContext.Provider>
26
+ </FetchCacheContext.Provider>
27
+ );
28
+ };
29
+ }
30
+
31
+ function makeMemory(
32
+ id: string,
33
+ state: MemoryLifecycleState,
34
+ content = "A fact.",
35
+ ): Memory {
36
+ return create(MemorySchema, {
37
+ apiVersion: "agentic.stigmer.ai/v1",
38
+ kind: "Memory",
39
+ metadata: { id, name: id, slug: id, org: "test-org" },
40
+ spec: { content },
41
+ status: { lifecycleState: state },
42
+ });
43
+ }
44
+
45
+ const PROPOSED = makeMemory("mem_proposed", MemoryLifecycleState.lifecycle_state_proposed);
46
+ const CONFIRMED = makeMemory("mem_confirmed", MemoryLifecycleState.lifecycle_state_confirmed);
47
+ const REJECTED = makeMemory("mem_rejected", MemoryLifecycleState.lifecycle_state_rejected);
48
+
49
+ beforeEach(() => vi.clearAllMocks());
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Data hook
53
+ // ---------------------------------------------------------------------------
54
+
55
+ describe("useMemories", () => {
56
+ it("fetches all memories in one cap-sized page", async () => {
57
+ const list = vi
58
+ .fn()
59
+ .mockResolvedValue(create(MemoryListSchema, { totalCount: 2, items: [PROPOSED, CONFIRMED] }));
60
+ const client = { memory: { list } };
61
+
62
+ const { result } = renderHook(() => useMemories("test-org"), {
63
+ wrapper: wrapper(client),
64
+ });
65
+
66
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
67
+ expect(result.current.memories).toHaveLength(2);
68
+ expect(result.current.error).toBeNull();
69
+ // One page of exactly the server cap fetches everything by
70
+ // construction — the list needs no pagination UI.
71
+ expect(list).toHaveBeenCalledWith(
72
+ expect.objectContaining({
73
+ org: "test-org",
74
+ pageInfo: expect.objectContaining({ num: 1, size: 100 }),
75
+ }),
76
+ );
77
+ });
78
+
79
+ it("skips fetching when org is null", () => {
80
+ const list = vi.fn();
81
+ const client = { memory: { list } };
82
+
83
+ const { result } = renderHook(() => useMemories(null), {
84
+ wrapper: wrapper(client),
85
+ });
86
+
87
+ expect(result.current.isLoading).toBe(false);
88
+ expect(result.current.memories).toEqual([]);
89
+ expect(list).not.toHaveBeenCalled();
90
+ });
91
+
92
+ it("surfaces list errors", async () => {
93
+ const failure = new Error("boom");
94
+ const client = { memory: { list: vi.fn().mockRejectedValue(failure) } };
95
+
96
+ const { result } = renderHook(() => useMemories("test-org"), {
97
+ wrapper: wrapper(client),
98
+ });
99
+
100
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
101
+ expect(result.current.error).toBe(failure);
102
+ expect(result.current.memories).toEqual([]);
103
+ });
104
+ });
105
+
106
+ describe("useMemory", () => {
107
+ it("fetches one record by id", async () => {
108
+ const get = vi.fn().mockResolvedValue(PROPOSED);
109
+ const client = { memory: { get } };
110
+
111
+ const { result } = renderHook(() => useMemory("mem_proposed"), {
112
+ wrapper: wrapper(client),
113
+ });
114
+
115
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
116
+ expect(result.current.memory).toBe(PROPOSED);
117
+ expect(result.current.notFound).toBe(false);
118
+ expect(result.current.error).toBeNull();
119
+ expect(get).toHaveBeenCalledWith("mem_proposed");
120
+ });
121
+
122
+ it("reports a deleted record as the notFound STATE, never an error", async () => {
123
+ // Deletion is the consent-revocation mechanism — a consumer must
124
+ // render "no longer stored", not a failure.
125
+ const get = vi
126
+ .fn()
127
+ .mockRejectedValue(new StigmerError("not-found", "memory not found", 5));
128
+ const client = { memory: { get } };
129
+
130
+ const { result } = renderHook(() => useMemory("mem_gone"), {
131
+ wrapper: wrapper(client),
132
+ });
133
+
134
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
135
+ expect(result.current.notFound).toBe(true);
136
+ expect(result.current.memory).toBeNull();
137
+ expect(result.current.error).toBeNull();
138
+ });
139
+
140
+ it("propagates every other failure as an error", async () => {
141
+ const failure = new StigmerError("unavailable", "server down", 14);
142
+ const client = { memory: { get: vi.fn().mockRejectedValue(failure) } };
143
+
144
+ const { result } = renderHook(() => useMemory("mem_x"), {
145
+ wrapper: wrapper(client),
146
+ });
147
+
148
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
149
+ expect(result.current.error).toBe(failure);
150
+ expect(result.current.notFound).toBe(false);
151
+ });
152
+
153
+ it("skips fetching when id is null", () => {
154
+ const get = vi.fn();
155
+ const client = { memory: { get } };
156
+
157
+ const { result } = renderHook(() => useMemory(null), {
158
+ wrapper: wrapper(client),
159
+ });
160
+
161
+ expect(result.current.isLoading).toBe(false);
162
+ expect(result.current.memory).toBeNull();
163
+ expect(get).not.toHaveBeenCalled();
164
+ });
165
+ });
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Decision hooks
169
+ // ---------------------------------------------------------------------------
170
+
171
+ describe("decision hooks", () => {
172
+ it("useConfirmMemory calls memory.confirm and resolves", async () => {
173
+ const confirm = vi.fn().mockResolvedValue(CONFIRMED);
174
+ const client = { memory: { confirm } };
175
+
176
+ const { result } = renderHook(() => useConfirmMemory(), {
177
+ wrapper: wrapper(client),
178
+ });
179
+
180
+ const confirmed = await result.current.confirmMemory("mem_proposed");
181
+ expect(confirmed).toBe(CONFIRMED);
182
+ expect(confirm).toHaveBeenCalledWith("mem_proposed");
183
+ await waitFor(() => expect(result.current.isConfirming).toBe(false));
184
+ expect(result.current.error).toBeNull();
185
+ });
186
+
187
+ it("useRejectMemory surfaces errors and rethrows", async () => {
188
+ const failure = new Error("memory was confirmed — delete it to stop it from being recalled");
189
+ const client = { memory: { reject: vi.fn().mockRejectedValue(failure) } };
190
+
191
+ const { result } = renderHook(() => useRejectMemory(), {
192
+ wrapper: wrapper(client),
193
+ });
194
+
195
+ await expect(result.current.rejectMemory("mem_confirmed")).rejects.toBe(failure);
196
+ await waitFor(() => expect(result.current.error).toBe(failure));
197
+ result.current.clearError();
198
+ await waitFor(() => expect(result.current.error).toBeNull());
199
+ });
200
+
201
+ it("useDeleteMemory calls memory.delete", async () => {
202
+ const del = vi.fn().mockResolvedValue(REJECTED);
203
+ const client = { memory: { delete: del } };
204
+
205
+ const { result } = renderHook(() => useDeleteMemory(), {
206
+ wrapper: wrapper(client),
207
+ });
208
+
209
+ await result.current.deleteMemory("mem_rejected");
210
+ expect(del).toHaveBeenCalledWith("mem_rejected");
211
+ });
212
+
213
+ it("useUpdateMemoryContent maps through toMemoryUpdateInput and overrides only content", async () => {
214
+ const update = vi.fn().mockResolvedValue(CONFIRMED);
215
+ const client = { memory: { update } };
216
+
217
+ const { result } = renderHook(() => useUpdateMemoryContent(), {
218
+ wrapper: wrapper(client),
219
+ });
220
+
221
+ await result.current.updateContent(CONFIRMED, "Edited fact.");
222
+ // The wipe-safe rule: the loaded record's identity and immutable
223
+ // fields ride along; only content changes.
224
+ expect(update).toHaveBeenCalledWith(
225
+ expect.objectContaining({
226
+ id: "mem_confirmed",
227
+ org: "test-org",
228
+ content: "Edited fact.",
229
+ }),
230
+ );
231
+ });
232
+ });
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Grouping helpers
236
+ // ---------------------------------------------------------------------------
237
+
238
+ describe("groupMemoriesByLifecycle", () => {
239
+ it("buckets by state and preserves input order within buckets", () => {
240
+ const secondProposed = makeMemory(
241
+ "mem_proposed_2",
242
+ MemoryLifecycleState.lifecycle_state_proposed,
243
+ );
244
+ const groups = groupMemoriesByLifecycle([
245
+ CONFIRMED,
246
+ PROPOSED,
247
+ REJECTED,
248
+ secondProposed,
249
+ ]);
250
+
251
+ expect(groups.proposed.map((m) => m.metadata?.id)).toEqual([
252
+ "mem_proposed",
253
+ "mem_proposed_2",
254
+ ]);
255
+ expect(groups.confirmed.map((m) => m.metadata?.id)).toEqual(["mem_confirmed"]);
256
+ expect(groups.rejected.map((m) => m.metadata?.id)).toEqual(["mem_rejected"]);
257
+ });
258
+
259
+ it("buckets the impossible unspecified state with proposals, never hiding it", () => {
260
+ const zombie = makeMemory("mem_zombie", MemoryLifecycleState.lifecycle_state_unspecified);
261
+ const groups = groupMemoriesByLifecycle([zombie]);
262
+ expect(groups.proposed).toHaveLength(1);
263
+ });
264
+ });
265
+
266
+ describe("formatMemoryProvenance", () => {
267
+ it("names the proposing agent and session", () => {
268
+ const memory = create(MemorySchema, {
269
+ metadata: { id: "mem_x", org: "test-org" },
270
+ spec: {
271
+ content: "A fact.",
272
+ provenance: { agentId: "agt_1", sessionId: "ses_1" },
273
+ },
274
+ });
275
+ expect(formatMemoryProvenance(memory)).toBe(
276
+ "Proposed by agent agt_1 in session ses_1",
277
+ );
278
+ });
279
+
280
+ it("returns null for records without provenance (created directly)", () => {
281
+ expect(formatMemoryProvenance(PROPOSED)).toBeNull();
282
+ });
283
+ });
@@ -0,0 +1,24 @@
1
+ // Memory — agent-proposed, user-confirmed facts (stigmer/stigmer#293
2
+ // Phase 2). The record is the trust surface over the recall seam: every
3
+ // fact is individually reviewable, editable, and deletable, and nothing
4
+ // is recalled until its subject confirms it.
5
+
6
+ export { MemoryListPanel, type MemoryListPanelProps } from "./MemoryListPanel.js";
7
+ export {
8
+ MemoryProposalCardBody,
9
+ type MemoryProposalCardBodyProps,
10
+ } from "./MemoryProposalCard.js";
11
+ export {
12
+ groupMemoriesByLifecycle,
13
+ formatMemoryProvenance,
14
+ type MemoryGroups,
15
+ } from "./memoryGroups.js";
16
+ export { useMemories, type UseMemoriesReturn } from "./useMemories.js";
17
+ export { useMemory, type UseMemoryReturn } from "./useMemory.js";
18
+ export { useConfirmMemory, type UseConfirmMemoryReturn } from "./useConfirmMemory.js";
19
+ export { useRejectMemory, type UseRejectMemoryReturn } from "./useRejectMemory.js";
20
+ export { useDeleteMemory, type UseDeleteMemoryReturn } from "./useDeleteMemory.js";
21
+ export {
22
+ useUpdateMemoryContent,
23
+ type UseUpdateMemoryContentReturn,
24
+ } from "./useUpdateMemoryContent.js";
@@ -0,0 +1,64 @@
1
+ import type { Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
2
+ import { MemoryLifecycleState } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/enum_pb";
3
+
4
+ /** Memories bucketed by consent lifecycle, pending proposals first. */
5
+ export interface MemoryGroups {
6
+ /** Awaiting the caller's decision — the catch-up queue. */
7
+ readonly proposed: readonly Memory[];
8
+ /** Confirmed facts, recalled into future sessions. */
9
+ readonly confirmed: readonly Memory[];
10
+ /** Rejected proposals, kept for audit until deleted. */
11
+ readonly rejected: readonly Memory[];
12
+ }
13
+
14
+ /**
15
+ * Buckets memories by lifecycle state for the pending-proposals-first
16
+ * page layout (DD-005 D4: the memory page is the catch-up surface for
17
+ * proposals scrolled past in session). Input order (newest first from
18
+ * the server) is preserved inside each bucket.
19
+ *
20
+ * A record in the zero `unspecified` state should not exist (create
21
+ * stamps `proposed`); it is bucketed with proposals so a decision
22
+ * surface is always offered rather than hiding the record.
23
+ */
24
+ export function groupMemoriesByLifecycle(
25
+ memories: readonly Memory[],
26
+ ): MemoryGroups {
27
+ const proposed: Memory[] = [];
28
+ const confirmed: Memory[] = [];
29
+ const rejected: Memory[] = [];
30
+
31
+ for (const memory of memories) {
32
+ switch (memory.status?.lifecycleState) {
33
+ case MemoryLifecycleState.lifecycle_state_confirmed:
34
+ confirmed.push(memory);
35
+ break;
36
+ case MemoryLifecycleState.lifecycle_state_rejected:
37
+ rejected.push(memory);
38
+ break;
39
+ default:
40
+ proposed.push(memory);
41
+ break;
42
+ }
43
+ }
44
+
45
+ return { proposed, confirmed, rejected };
46
+ }
47
+
48
+ /**
49
+ * One-line provenance attribution for a memory, or `null` when the
50
+ * record has none (created directly through the API, not by an agent).
51
+ *
52
+ * Trust requires "where did this come from" beside every fact (DD-004);
53
+ * agent/session display names are not resolvable from the record alone,
54
+ * so ids are shown — they are what links back to the source surfaces.
55
+ */
56
+ export function formatMemoryProvenance(memory: Memory): string | null {
57
+ const provenance = memory.spec?.provenance;
58
+ if (!provenance) return null;
59
+ const parts: string[] = [];
60
+ if (provenance.agentId) parts.push(`agent ${provenance.agentId}`);
61
+ if (provenance.sessionId) parts.push(`session ${provenance.sessionId}`);
62
+ if (parts.length === 0) return null;
63
+ return `Proposed by ${parts.join(" in ")}`;
64
+ }
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
5
+ import { useStigmer } from "../hooks.js";
6
+ import { toError } from "../internal/toError.js";
7
+
8
+ /** Return value of {@link useConfirmMemory}. */
9
+ export interface UseConfirmMemoryReturn {
10
+ /** Confirm a proposed memory by its resource ID. Resolves with the confirmed record. */
11
+ readonly confirmMemory: (id: string) => Promise<Memory>;
12
+ /** `true` while the confirm request is in flight. */
13
+ readonly isConfirming: boolean;
14
+ /** Error from the last failed confirm, or `null` when healthy. */
15
+ readonly error: Error | null;
16
+ /** Reset `error` to `null`. */
17
+ readonly clearError: () => void;
18
+ }
19
+
20
+ /**
21
+ * Behavior hook that wraps `memory.confirm()` — the consent act
22
+ * (DD-005 D3). A confirmed memory is recalled into the caller's future
23
+ * sessions as background context.
24
+ *
25
+ * Confirming an already-confirmed memory succeeds and changes nothing.
26
+ * Confirming a rejected memory fails with FAILED_PRECONDITION: the
27
+ * rejection stands — delete the record and let the agent propose again.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const { confirmMemory, isConfirming } = useConfirmMemory();
32
+ *
33
+ * await confirmMemory(memory.metadata?.id ?? "");
34
+ * refetch(); // refresh the list after the decision
35
+ * ```
36
+ */
37
+ export function useConfirmMemory(): UseConfirmMemoryReturn {
38
+ const stigmer = useStigmer();
39
+ const [isConfirming, setIsConfirming] = useState(false);
40
+ const [error, setError] = useState<Error | null>(null);
41
+
42
+ const clearError = useCallback(() => setError(null), []);
43
+
44
+ const confirmMemory = useCallback(
45
+ async (id: string): Promise<Memory> => {
46
+ setIsConfirming(true);
47
+ setError(null);
48
+
49
+ try {
50
+ return await stigmer.memory.confirm(id);
51
+ } catch (err) {
52
+ setError(toError(err));
53
+ throw err;
54
+ } finally {
55
+ setIsConfirming(false);
56
+ }
57
+ },
58
+ [stigmer],
59
+ );
60
+
61
+ return { confirmMemory, isConfirming, error, clearError };
62
+ }
@@ -0,0 +1,61 @@
1
+ "use client";
2
+
3
+ import { useCallback, useState } from "react";
4
+ import type { Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
5
+ import { useStigmer } from "../hooks.js";
6
+ import { toError } from "../internal/toError.js";
7
+
8
+ /** Return value of {@link useDeleteMemory}. */
9
+ export interface UseDeleteMemoryReturn {
10
+ /** Delete a memory by its resource ID, in any lifecycle state. Resolves with the deleted record. */
11
+ readonly deleteMemory: (id: string) => Promise<Memory>;
12
+ /** `true` while the delete request is in flight. */
13
+ readonly isDeleting: boolean;
14
+ /** Error from the last failed delete, or `null` when healthy. */
15
+ readonly error: Error | null;
16
+ /** Reset `error` to `null`. */
17
+ readonly clearError: () => void;
18
+ }
19
+
20
+ /**
21
+ * Behavior hook that wraps `memory.delete()`.
22
+ *
23
+ * Deletion works in any lifecycle state — it is never refused on
24
+ * lifecycle grounds (DD-004) — and is the revocation mechanism for
25
+ * confirmed facts: the fact stops reaching future sessions immediately.
26
+ * Past executions keep their immutable recall snapshots.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * const { deleteMemory, isDeleting, error } = useDeleteMemory();
31
+ *
32
+ * await deleteMemory(memory.metadata?.id ?? "");
33
+ * refetch(); // refresh the list after deletion
34
+ * ```
35
+ */
36
+ export function useDeleteMemory(): UseDeleteMemoryReturn {
37
+ const stigmer = useStigmer();
38
+ const [isDeleting, setIsDeleting] = useState(false);
39
+ const [error, setError] = useState<Error | null>(null);
40
+
41
+ const clearError = useCallback(() => setError(null), []);
42
+
43
+ const deleteMemory = useCallback(
44
+ async (id: string): Promise<Memory> => {
45
+ setIsDeleting(true);
46
+ setError(null);
47
+
48
+ try {
49
+ return await stigmer.memory.delete(id);
50
+ } catch (err) {
51
+ setError(toError(err));
52
+ throw err;
53
+ } finally {
54
+ setIsDeleting(false);
55
+ }
56
+ },
57
+ [stigmer],
58
+ );
59
+
60
+ return { deleteMemory, isDeleting, error, clearError };
61
+ }
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import { create } from "@bufbuild/protobuf";
4
+ import type { Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
5
+ import { ListMemoriesRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/io_pb";
6
+ import { useStigmer } from "../hooks.js";
7
+ import { useFetch } from "../internal/useFetch.js";
8
+
9
+ /**
10
+ * The server-enforced per-subject-per-org record ceiling (all lifecycle
11
+ * states). Requesting one page of exactly this size fetches EVERY record
12
+ * the caller can see, by construction — the cap is why this list needs
13
+ * no pagination UI.
14
+ */
15
+ const MEMORY_CAP = 100;
16
+
17
+ /** Return value of {@link useMemories}. */
18
+ export interface UseMemoriesReturn {
19
+ /** Every memory the caller can view in the org, newest first. */
20
+ readonly memories: readonly Memory[];
21
+ /** `true` while the initial fetch is in flight. */
22
+ readonly isLoading: boolean;
23
+ /** `true` while a background refetch is in flight. */
24
+ readonly isRefetching: boolean;
25
+ /** Error from the last failed request, or `null` when healthy. */
26
+ readonly error: Error | null;
27
+ /** Discard cached data and re-fetch the list from the server. */
28
+ readonly refetch: () => void;
29
+ }
30
+
31
+ /**
32
+ * Data hook that fetches the caller's memories in an organization.
33
+ *
34
+ * Memory content is subject-only: the server returns exactly the records
35
+ * that are about the caller (FGA-filtered in cloud; single-user in local
36
+ * mode), so this list is always "what the platform remembers about ME
37
+ * here". Ordering is newest first; group by lifecycle state for display
38
+ * with {@link groupMemoriesByLifecycle}.
39
+ *
40
+ * Pass `null` as `org` to skip fetching (stable no-op).
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const { memories, isLoading, refetch } = useMemories(org);
45
+ * const groups = groupMemoriesByLifecycle(memories);
46
+ * ```
47
+ */
48
+ export function useMemories(org: string | null): UseMemoriesReturn {
49
+ const stigmer = useStigmer();
50
+
51
+ const { data: memories, isLoading, isRefetching, error, refetch } = useFetch(
52
+ org
53
+ ? () =>
54
+ stigmer.memory
55
+ .list(create(ListMemoriesRequestSchema, {
56
+ org,
57
+ pageInfo: { num: 1, size: MEMORY_CAP },
58
+ }))
59
+ .then((r) => [...r.items])
60
+ : null,
61
+ [stigmer, org],
62
+ [] as Memory[],
63
+ );
64
+
65
+ return { memories, isLoading, isRefetching, error, refetch };
66
+ }
@@ -0,0 +1,80 @@
1
+ "use client";
2
+
3
+ import type { Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
4
+ import { isNotFound } from "@stigmer/sdk";
5
+ import { useStigmer } from "../hooks.js";
6
+ import { useFetch } from "../internal/useFetch.js";
7
+
8
+ /** Return value of {@link useMemory}. */
9
+ export interface UseMemoryReturn {
10
+ /** The memory record, or `null` while loading / not found / on error. */
11
+ readonly memory: Memory | null;
12
+ /** `true` while the initial fetch is in flight. */
13
+ readonly isLoading: boolean;
14
+ /**
15
+ * `true` when the record no longer exists. A first-class STATE, not an
16
+ * error: deletion is the platform's consent-revocation mechanism, so a
17
+ * consumer (the proposal chip, a deep link) must render "no longer
18
+ * stored" honestly rather than a failure.
19
+ */
20
+ readonly notFound: boolean;
21
+ /** Error from the last failed request (never not-found), or `null`. */
22
+ readonly error: Error | null;
23
+ /** Discard cached data and re-fetch the record from the server. */
24
+ readonly refetch: () => void;
25
+ }
26
+
27
+ /** The fetch result shape that keeps not-found distinct from errors. */
28
+ interface MemoryLookup {
29
+ readonly memory: Memory | null;
30
+ readonly notFound: boolean;
31
+ }
32
+
33
+ const PENDING_LOOKUP: MemoryLookup = { memory: null, notFound: false };
34
+
35
+ /**
36
+ * Data hook that fetches one memory record by id.
37
+ *
38
+ * Built for surfaces that hold a memory's identity but not its CURRENT
39
+ * lifecycle state — the in-thread proposal chip is the primary consumer:
40
+ * the tool result it renders from is frozen at capture time, while the
41
+ * record may have been confirmed, rejected, or deleted since (from the
42
+ * chip itself, the memory settings page, or another device). Content
43
+ * visibility is subject-only, so the fetch answers only for the person
44
+ * the memory is about.
45
+ *
46
+ * Pass `null` as `id` to skip fetching (stable no-op).
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * const { memory, isLoading, notFound } = useMemory(memoryId);
51
+ * ```
52
+ */
53
+ export function useMemory(id: string | null): UseMemoryReturn {
54
+ const stigmer = useStigmer();
55
+
56
+ const { data, isLoading, error, refetch } = useFetch(
57
+ id
58
+ ? () =>
59
+ stigmer.memory.get(id).then(
60
+ (memory): MemoryLookup => ({ memory, notFound: false }),
61
+ (err): MemoryLookup => {
62
+ // Deletion is a state this hook reports, never an error it
63
+ // throws — every other failure propagates.
64
+ if (isNotFound(err)) return { memory: null, notFound: true };
65
+ throw err;
66
+ },
67
+ )
68
+ : null,
69
+ [stigmer, id],
70
+ PENDING_LOOKUP,
71
+ );
72
+
73
+ return {
74
+ memory: data.memory,
75
+ isLoading,
76
+ notFound: data.notFound,
77
+ error,
78
+ refetch,
79
+ };
80
+ }