@cortexmemory/cli 0.27.3 → 0.28.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 (68) hide show
  1. package/dist/commands/db.d.ts.map +1 -1
  2. package/dist/commands/db.js +18 -6
  3. package/dist/commands/db.js.map +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +191 -80
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/dev.js +3 -2
  8. package/dist/commands/dev.js.map +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +12 -0
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/types.d.ts +1 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/app-template-sync.d.ts.map +1 -1
  15. package/dist/utils/app-template-sync.js +35 -13
  16. package/dist/utils/app-template-sync.js.map +1 -1
  17. package/dist/utils/init/quickstart-setup.d.ts.map +1 -1
  18. package/dist/utils/init/quickstart-setup.js.map +1 -1
  19. package/package.json +4 -4
  20. package/templates/basic/.env.local.example +23 -0
  21. package/templates/basic/README.md +181 -56
  22. package/templates/basic/package-lock.json +2180 -406
  23. package/templates/basic/package.json +23 -5
  24. package/templates/basic/src/__tests__/chat.test.ts +340 -0
  25. package/templates/basic/src/__tests__/cortex.test.ts +260 -0
  26. package/templates/basic/src/__tests__/display.test.ts +455 -0
  27. package/templates/basic/src/__tests__/e2e/fact-extraction.test.ts +498 -0
  28. package/templates/basic/src/__tests__/e2e/memory-flow.test.ts +355 -0
  29. package/templates/basic/src/__tests__/e2e/server-e2e.test.ts +414 -0
  30. package/templates/basic/src/__tests__/helpers/test-utils.ts +345 -0
  31. package/templates/basic/src/__tests__/integration/chat-flow.test.ts +422 -0
  32. package/templates/basic/src/__tests__/integration/server.test.ts +441 -0
  33. package/templates/basic/src/__tests__/llm.test.ts +344 -0
  34. package/templates/basic/src/chat.ts +300 -0
  35. package/templates/basic/src/cortex.ts +203 -0
  36. package/templates/basic/src/display.ts +425 -0
  37. package/templates/basic/src/index.ts +194 -64
  38. package/templates/basic/src/llm.ts +214 -0
  39. package/templates/basic/src/server.ts +280 -0
  40. package/templates/basic/vitest.config.ts +33 -0
  41. package/templates/basic/vitest.e2e.config.ts +28 -0
  42. package/templates/basic/vitest.integration.config.ts +25 -0
  43. package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +1 -1
  44. package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +61 -19
  45. package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +14 -18
  46. package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +4 -7
  47. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +95 -23
  48. package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +339 -0
  49. package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +16 -16
  50. package/templates/vercel-ai-quickstart/app/globals.css +24 -9
  51. package/templates/vercel-ai-quickstart/app/page.tsx +41 -15
  52. package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +3 -1
  53. package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +6 -6
  54. package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +19 -8
  55. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +46 -16
  56. package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
  57. package/templates/vercel-ai-quickstart/jest.config.js +8 -1
  58. package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +165 -0
  59. package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
  60. package/templates/vercel-ai-quickstart/lib/versions.ts +60 -0
  61. package/templates/vercel-ai-quickstart/next.config.js +10 -2
  62. package/templates/vercel-ai-quickstart/package.json +23 -12
  63. package/templates/vercel-ai-quickstart/test-api.mjs +303 -0
  64. package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +483 -0
  65. package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
  66. package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
  67. package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
  68. package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +4 -1
@@ -1,22 +1,40 @@
1
1
  {
2
2
  "name": "{{PROJECT_NAME}}",
3
- "version": "0.1.1",
4
- "description": "AI agent with Cortex Memory SDK",
3
+ "version": "1.0.0",
4
+ "description": "AI agent with Cortex Memory SDK - demonstrating memory orchestration",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "node dev-runner.mjs",
8
+ "start": "tsx --env-file=.env.local src/index.ts",
9
+ "server": "tsx --env-file=.env.local src/server.ts",
8
10
  "build": "tsc && npm run build:convex",
9
11
  "build:convex": "convex deploy",
10
- "start": "tsx --env-file=.env.local src/index.ts"
12
+ "test": "vitest run",
13
+ "test:watch": "vitest",
14
+ "test:coverage": "vitest run --coverage",
15
+ "test:unit": "vitest run --config vitest.config.ts",
16
+ "test:integration": "vitest run --config vitest.integration.config.ts",
17
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
18
+ "test:all": "npm run test:unit && npm run test:integration && npm run test:e2e"
11
19
  },
12
20
  "dependencies": {
13
21
  "@cortexmemory/sdk": "latest",
14
22
  "convex": "^1.31.2",
15
- "dotenv": "^17.2.3"
23
+ "dotenv": "^17.2.3",
24
+ "hono": "^4.11.3",
25
+ "@hono/node-server": "^1.19.7"
16
26
  },
17
27
  "devDependencies": {
18
28
  "@types/node": "^25.0.3",
29
+ "@vitest/coverage-v8": "^4.0.16",
19
30
  "typescript": "^5.9.3",
20
- "tsx": "^4.21.0"
31
+ "tsx": "^4.21.0",
32
+ "vitest": "^4.0.16"
33
+ },
34
+ "optionalDependencies": {
35
+ "openai": "^6.15.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=20"
21
39
  }
22
40
  }
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Unit tests for chat.ts
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
6
+
7
+ // Create mock functions we can control
8
+ const mockRecall = vi.fn();
9
+ const mockRemember = vi.fn();
10
+ const mockFactsList = vi.fn();
11
+ const mockConversationsGet = vi.fn();
12
+
13
+ const mockCortex = {
14
+ memory: {
15
+ recall: mockRecall,
16
+ remember: mockRemember,
17
+ },
18
+ facts: {
19
+ list: mockFactsList,
20
+ },
21
+ conversations: {
22
+ get: mockConversationsGet,
23
+ },
24
+ };
25
+
26
+ // Mock dependencies
27
+ vi.mock("../cortex.js", () => ({
28
+ getCortex: vi.fn(() => mockCortex),
29
+ CONFIG: {
30
+ memorySpaceId: "test-space",
31
+ userId: "test-user",
32
+ userName: "Test User",
33
+ agentId: "test-agent",
34
+ agentName: "Test Agent",
35
+ enableFactExtraction: true,
36
+ enableGraphMemory: false,
37
+ debug: false,
38
+ },
39
+ buildRememberParams: vi.fn().mockResolvedValue({
40
+ memorySpaceId: "test-space",
41
+ conversationId: "conv-123",
42
+ userId: "test-user",
43
+ agentId: "test-agent",
44
+ userMessage: "Hello",
45
+ agentResponse: "Hi there",
46
+ }),
47
+ createLayerObserver: vi.fn().mockReturnValue({
48
+ onOrchestrationStart: vi.fn(),
49
+ onLayerUpdate: vi.fn(),
50
+ onOrchestrationComplete: vi.fn(),
51
+ }),
52
+ }));
53
+
54
+ vi.mock("../display.js", () => ({
55
+ printRecallResults: vi.fn(),
56
+ printOrchestrationComplete: vi.fn(),
57
+ printInfo: vi.fn(),
58
+ }));
59
+
60
+ vi.mock("../llm.js", () => ({
61
+ generateResponse: vi.fn().mockResolvedValue("Test response"),
62
+ isLLMAvailable: vi.fn().mockReturnValue(false),
63
+ }));
64
+
65
+ describe("chat", () => {
66
+ beforeEach(() => {
67
+ vi.clearAllMocks();
68
+ // Reset default mock behaviors
69
+ mockRecall.mockResolvedValue({ memories: [], facts: [] });
70
+ mockRemember.mockResolvedValue({});
71
+ mockFactsList.mockResolvedValue({ facts: [] });
72
+ mockConversationsGet.mockResolvedValue(null);
73
+ });
74
+
75
+ afterEach(() => {
76
+ vi.clearAllMocks();
77
+ });
78
+
79
+ describe("generateConversationId", () => {
80
+ it("generates unique IDs", async () => {
81
+ const { generateConversationId } = await import("../chat.js");
82
+
83
+ const id1 = generateConversationId();
84
+ const id2 = generateConversationId();
85
+
86
+ expect(id1).not.toBe(id2);
87
+ });
88
+
89
+ it("generates IDs with correct format", async () => {
90
+ const { generateConversationId } = await import("../chat.js");
91
+
92
+ const id = generateConversationId();
93
+
94
+ expect(id).toMatch(/^conv-\d+-[a-z0-9]+$/);
95
+ });
96
+ });
97
+
98
+ describe("getConversationId", () => {
99
+ it("creates a new conversation ID if none exists", async () => {
100
+ vi.resetModules();
101
+ const { getConversationId } = await import("../chat.js");
102
+
103
+ const id = getConversationId();
104
+
105
+ expect(id).toMatch(/^conv-\d+-[a-z0-9]+$/);
106
+ });
107
+
108
+ it("returns the same ID on subsequent calls", async () => {
109
+ vi.resetModules();
110
+ const { getConversationId } = await import("../chat.js");
111
+
112
+ const id1 = getConversationId();
113
+ const id2 = getConversationId();
114
+
115
+ expect(id1).toBe(id2);
116
+ });
117
+ });
118
+
119
+ describe("newConversation", () => {
120
+ it("creates a new conversation ID", async () => {
121
+ vi.resetModules();
122
+ const { newConversation, getConversationId } = await import("../chat.js");
123
+
124
+ const oldId = getConversationId();
125
+ const newId = newConversation();
126
+
127
+ expect(newId).not.toBe(oldId);
128
+ });
129
+
130
+ it("prints info message", async () => {
131
+ const { printInfo } = await import("../display.js");
132
+ vi.resetModules();
133
+ const { newConversation } = await import("../chat.js");
134
+
135
+ newConversation();
136
+
137
+ expect(printInfo).toHaveBeenCalledWith(
138
+ expect.stringContaining("Started new conversation"),
139
+ );
140
+ });
141
+ });
142
+
143
+ describe("chat", () => {
144
+ it("calls recall, generate, and remember in sequence", async () => {
145
+ mockRecall.mockResolvedValue({ memories: [], facts: [] });
146
+ mockRemember.mockResolvedValue({});
147
+
148
+ const { generateResponse } = await import("../llm.js");
149
+ vi.mocked(generateResponse).mockResolvedValue("Test response");
150
+
151
+ vi.resetModules();
152
+ const { chat } = await import("../chat.js");
153
+
154
+ const result = await chat("Hello", "conv-123");
155
+
156
+ expect(mockRecall).toHaveBeenCalled();
157
+ expect(generateResponse).toHaveBeenCalledWith("Hello", [], []);
158
+ expect(mockRemember).toHaveBeenCalled();
159
+ expect(result.response).toBe("Test response");
160
+ });
161
+
162
+ it("returns conversation ID and recall counts", async () => {
163
+ mockRecall.mockResolvedValue({
164
+ memories: [{ content: "mem1" }],
165
+ facts: [{ content: "fact1" }, { content: "fact2" }],
166
+ });
167
+ mockRemember.mockResolvedValue({});
168
+
169
+ vi.resetModules();
170
+ const { chat } = await import("../chat.js");
171
+
172
+ const result = await chat("Hello", "conv-123");
173
+
174
+ expect(result.conversationId).toBe("conv-123");
175
+ expect(result.memoriesRecalled).toBe(1);
176
+ expect(result.factsRecalled).toBe(2);
177
+ });
178
+
179
+ it("handles recall errors gracefully", async () => {
180
+ // Reset the generateResponse mock to ensure it returns expected value
181
+ const { generateResponse } = await import("../llm.js");
182
+ vi.mocked(generateResponse).mockResolvedValue("Test response");
183
+
184
+ // Import chat first before setting up error behavior
185
+ const { chat, generateConversationId } = await import("../chat.js");
186
+
187
+ // Now set up the error
188
+ mockRecall.mockRejectedValueOnce(new Error("Recall failed"));
189
+ mockRemember.mockResolvedValue({});
190
+
191
+ // Should not throw
192
+ const convId = generateConversationId();
193
+ const result = await chat("Hello", convId);
194
+
195
+ expect(result.response).toBe("Test response");
196
+ expect(result.memoriesRecalled).toBe(0);
197
+ expect(result.factsRecalled).toBe(0);
198
+ });
199
+
200
+ it("still returns response if remember fails", async () => {
201
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
202
+
203
+ // Reset the generateResponse mock to ensure it returns expected value
204
+ const { generateResponse } = await import("../llm.js");
205
+ vi.mocked(generateResponse).mockResolvedValue("Test response");
206
+
207
+ // Import chat first
208
+ const { chat, generateConversationId } = await import("../chat.js");
209
+
210
+ // Now set up the behaviors
211
+ mockRecall.mockResolvedValueOnce({ memories: [], facts: [] });
212
+ mockRemember.mockRejectedValueOnce(new Error("Remember failed"));
213
+
214
+ const convId = generateConversationId();
215
+ const result = await chat("Hello", convId);
216
+
217
+ expect(result.response).toBe("Test response");
218
+ expect(consoleSpy).toHaveBeenCalledWith(
219
+ "Failed to store memory:",
220
+ expect.any(Error),
221
+ );
222
+
223
+ consoleSpy.mockRestore();
224
+ });
225
+
226
+ it("uses current conversation ID if none provided", async () => {
227
+ mockRecall.mockResolvedValue({ memories: [], facts: [] });
228
+ mockRemember.mockResolvedValue({});
229
+
230
+ vi.resetModules();
231
+ const { chat, getConversationId } = await import("../chat.js");
232
+
233
+ const currentId = getConversationId();
234
+ const result = await chat("Hello");
235
+
236
+ expect(result.conversationId).toBe(currentId);
237
+ });
238
+ });
239
+
240
+ describe("recallMemories", () => {
241
+ it("searches and prints results", async () => {
242
+ mockRecall.mockResolvedValue({
243
+ memories: [{ content: "test memory" }],
244
+ facts: [{ content: "test fact" }],
245
+ });
246
+
247
+ const { printRecallResults } = await import("../display.js");
248
+
249
+ vi.resetModules();
250
+ const { recallMemories } = await import("../chat.js");
251
+
252
+ await recallMemories("test query");
253
+
254
+ expect(mockRecall).toHaveBeenCalledWith(
255
+ expect.objectContaining({
256
+ query: "test query",
257
+ }),
258
+ );
259
+ expect(printRecallResults).toHaveBeenCalled();
260
+ });
261
+
262
+ it("handles errors gracefully", async () => {
263
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
264
+
265
+ mockRecall.mockRejectedValue(new Error("Search failed"));
266
+
267
+ vi.resetModules();
268
+ const { recallMemories } = await import("../chat.js");
269
+
270
+ await recallMemories("test query");
271
+
272
+ expect(consoleSpy).toHaveBeenCalledWith(
273
+ "Recall failed:",
274
+ expect.any(Error),
275
+ );
276
+
277
+ consoleSpy.mockRestore();
278
+ });
279
+ });
280
+
281
+ describe("listFacts", () => {
282
+ it("lists facts and prints results", async () => {
283
+ mockFactsList.mockResolvedValue({
284
+ facts: [{ content: "fact 1" }, { content: "fact 2" }],
285
+ });
286
+
287
+ const { printRecallResults } = await import("../display.js");
288
+
289
+ vi.resetModules();
290
+ const { listFacts } = await import("../chat.js");
291
+
292
+ await listFacts();
293
+
294
+ expect(mockFactsList).toHaveBeenCalled();
295
+ expect(printRecallResults).toHaveBeenCalledWith(
296
+ [],
297
+ expect.arrayContaining([
298
+ expect.objectContaining({ content: "fact 1" }),
299
+ ]),
300
+ );
301
+ });
302
+ });
303
+
304
+ describe("getHistory", () => {
305
+ it("prints info when no conversation is active", async () => {
306
+ const { printInfo } = await import("../display.js");
307
+
308
+ vi.resetModules();
309
+ const { getHistory } = await import("../chat.js");
310
+
311
+ // getHistory will call getCortex and use the mock
312
+ await getHistory();
313
+
314
+ // Without a conversation ID, it should print info
315
+ expect(printInfo).toHaveBeenCalled();
316
+ });
317
+ });
318
+
319
+ describe("printConfig", () => {
320
+ it("prints configuration details", async () => {
321
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
322
+
323
+ vi.resetModules();
324
+ const { printConfig } = await import("../chat.js");
325
+
326
+ printConfig();
327
+
328
+ const output = consoleSpy.mock.calls.flat().join("\n");
329
+ expect(output).toContain("Configuration:");
330
+ expect(output).toContain("Memory Space:");
331
+ expect(output).toContain("User:");
332
+ expect(output).toContain("Agent:");
333
+ expect(output).toContain("Fact Extraction:");
334
+ expect(output).toContain("Graph Sync:");
335
+ expect(output).toContain("LLM:");
336
+
337
+ consoleSpy.mockRestore();
338
+ });
339
+ });
340
+ });
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Unit tests for cortex.ts
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
6
+
7
+ // Mock the display module before importing cortex
8
+ vi.mock("../display.js", () => ({
9
+ printLayerUpdate: vi.fn(),
10
+ printOrchestrationStart: vi.fn(),
11
+ }));
12
+
13
+ describe("cortex", () => {
14
+ const originalEnv = process.env;
15
+
16
+ beforeEach(() => {
17
+ vi.resetModules();
18
+ process.env = { ...originalEnv };
19
+ });
20
+
21
+ afterEach(() => {
22
+ process.env = originalEnv;
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ describe("CONFIG", () => {
27
+ it("uses default values when environment variables are not set", async () => {
28
+ delete process.env.MEMORY_SPACE_ID;
29
+ delete process.env.USER_ID;
30
+ delete process.env.USER_NAME;
31
+ delete process.env.AGENT_ID;
32
+ delete process.env.AGENT_NAME;
33
+ delete process.env.CORTEX_FACT_EXTRACTION;
34
+ delete process.env.CORTEX_GRAPH_SYNC;
35
+ delete process.env.DEBUG;
36
+
37
+ const { CONFIG } = await import("../cortex.js");
38
+
39
+ expect(CONFIG.memorySpaceId).toBe("basic-demo");
40
+ expect(CONFIG.userId).toBe("demo-user");
41
+ expect(CONFIG.userName).toBe("Demo User");
42
+ expect(CONFIG.agentId).toBe("basic-assistant");
43
+ expect(CONFIG.agentName).toBe("Cortex CLI Assistant");
44
+ expect(CONFIG.enableFactExtraction).toBe(true);
45
+ expect(CONFIG.enableGraphMemory).toBe(false);
46
+ expect(CONFIG.debug).toBe(false);
47
+ });
48
+
49
+ it("uses environment variables when set", async () => {
50
+ process.env.MEMORY_SPACE_ID = "custom-space";
51
+ process.env.USER_ID = "custom-user";
52
+ process.env.USER_NAME = "Custom User";
53
+ process.env.AGENT_ID = "custom-agent";
54
+ process.env.AGENT_NAME = "Custom Agent";
55
+ process.env.CORTEX_FACT_EXTRACTION = "false";
56
+ process.env.CORTEX_GRAPH_SYNC = "true";
57
+ process.env.DEBUG = "true";
58
+
59
+ const { CONFIG } = await import("../cortex.js");
60
+
61
+ expect(CONFIG.memorySpaceId).toBe("custom-space");
62
+ expect(CONFIG.userId).toBe("custom-user");
63
+ expect(CONFIG.userName).toBe("Custom User");
64
+ expect(CONFIG.agentId).toBe("custom-agent");
65
+ expect(CONFIG.agentName).toBe("Custom Agent");
66
+ expect(CONFIG.enableFactExtraction).toBe(false);
67
+ expect(CONFIG.enableGraphMemory).toBe(true);
68
+ expect(CONFIG.debug).toBe(true);
69
+ });
70
+ });
71
+
72
+ describe("getCortex", () => {
73
+ it("throws error when CONVEX_URL is not set", async () => {
74
+ delete process.env.CONVEX_URL;
75
+
76
+ const { getCortex } = await import("../cortex.js");
77
+
78
+ expect(() => getCortex()).toThrow("CONVEX_URL environment variable is required");
79
+ });
80
+
81
+ it("creates Cortex client when CONVEX_URL is set", async () => {
82
+ process.env.CONVEX_URL = "https://test.convex.cloud";
83
+
84
+ // Mock Cortex SDK
85
+ vi.doMock("@cortexmemory/sdk", () => ({
86
+ Cortex: vi.fn().mockImplementation(() => ({
87
+ memory: {},
88
+ close: vi.fn(),
89
+ })),
90
+ }));
91
+
92
+ const { getCortex } = await import("../cortex.js");
93
+ const cortex = getCortex();
94
+
95
+ expect(cortex).toBeDefined();
96
+ expect(cortex.memory).toBeDefined();
97
+ });
98
+
99
+ it("returns same instance on subsequent calls (singleton)", async () => {
100
+ process.env.CONVEX_URL = "https://test.convex.cloud";
101
+
102
+ vi.doMock("@cortexmemory/sdk", () => ({
103
+ Cortex: vi.fn().mockImplementation(() => ({
104
+ memory: {},
105
+ close: vi.fn(),
106
+ })),
107
+ }));
108
+
109
+ const { getCortex } = await import("../cortex.js");
110
+
111
+ const instance1 = getCortex();
112
+ const instance2 = getCortex();
113
+
114
+ expect(instance1).toBe(instance2);
115
+ });
116
+ });
117
+
118
+ describe("closeCortex", () => {
119
+ it("closes the client and resets singleton", async () => {
120
+ process.env.CONVEX_URL = "https://test.convex.cloud";
121
+
122
+ const closeMock = vi.fn();
123
+ vi.doMock("@cortexmemory/sdk", () => ({
124
+ Cortex: vi.fn().mockImplementation(() => ({
125
+ memory: {},
126
+ close: closeMock,
127
+ })),
128
+ }));
129
+
130
+ const { getCortex, closeCortex } = await import("../cortex.js");
131
+
132
+ getCortex(); // Create instance
133
+ closeCortex();
134
+
135
+ expect(closeMock).toHaveBeenCalled();
136
+ });
137
+
138
+ it("does nothing when no client exists", async () => {
139
+ const { closeCortex } = await import("../cortex.js");
140
+
141
+ // Should not throw
142
+ expect(() => closeCortex()).not.toThrow();
143
+ });
144
+ });
145
+
146
+ describe("createLayerObserver", () => {
147
+ it("returns an observer with required methods", async () => {
148
+ const { createLayerObserver } = await import("../cortex.js");
149
+
150
+ const observer = createLayerObserver();
151
+
152
+ expect(observer).toHaveProperty("onOrchestrationStart");
153
+ expect(observer).toHaveProperty("onLayerUpdate");
154
+ expect(observer).toHaveProperty("onOrchestrationComplete");
155
+ expect(typeof observer.onOrchestrationStart).toBe("function");
156
+ expect(typeof observer.onLayerUpdate).toBe("function");
157
+ expect(typeof observer.onOrchestrationComplete).toBe("function");
158
+ });
159
+
160
+ it("calls printOrchestrationStart on orchestration start", async () => {
161
+ const { printOrchestrationStart } = await import("../display.js");
162
+ const { createLayerObserver } = await import("../cortex.js");
163
+
164
+ const observer = createLayerObserver();
165
+ observer.onOrchestrationStart("test-id");
166
+
167
+ expect(printOrchestrationStart).toHaveBeenCalledWith("test-id");
168
+ });
169
+
170
+ it("calls printLayerUpdate on layer update", async () => {
171
+ const { printLayerUpdate } = await import("../display.js");
172
+ const { createLayerObserver } = await import("../cortex.js");
173
+
174
+ const observer = createLayerObserver();
175
+ const event = {
176
+ layer: "facts",
177
+ status: "complete" as const,
178
+ timestamp: Date.now(),
179
+ latencyMs: 100,
180
+ };
181
+ observer.onLayerUpdate(event);
182
+
183
+ expect(printLayerUpdate).toHaveBeenCalledWith(event);
184
+ });
185
+ });
186
+
187
+ describe("getEmbeddingProvider", () => {
188
+ it("returns undefined when OPENAI_API_KEY is not set", async () => {
189
+ delete process.env.OPENAI_API_KEY;
190
+
191
+ const { getEmbeddingProvider } = await import("../cortex.js");
192
+ const provider = await getEmbeddingProvider();
193
+
194
+ expect(provider).toBeUndefined();
195
+ });
196
+
197
+ it("returns embedding function when OPENAI_API_KEY is set", async () => {
198
+ process.env.OPENAI_API_KEY = "sk-test-key";
199
+
200
+ // Mock OpenAI
201
+ vi.doMock("openai", () => ({
202
+ default: vi.fn().mockImplementation(() => ({
203
+ embeddings: {
204
+ create: vi.fn().mockResolvedValue({
205
+ data: [{ embedding: new Array(1536).fill(0.1) }],
206
+ }),
207
+ },
208
+ })),
209
+ }));
210
+
211
+ const { getEmbeddingProvider } = await import("../cortex.js");
212
+ const provider = await getEmbeddingProvider();
213
+
214
+ expect(provider).toBeDefined();
215
+ expect(typeof provider).toBe("function");
216
+ });
217
+ });
218
+
219
+ describe("buildRememberParams", () => {
220
+ it("builds params with CONFIG values", async () => {
221
+ delete process.env.OPENAI_API_KEY;
222
+
223
+ const { buildRememberParams, CONFIG } = await import("../cortex.js");
224
+
225
+ const params = await buildRememberParams({
226
+ userMessage: "Hello",
227
+ agentResponse: "Hi there",
228
+ conversationId: "conv-123",
229
+ });
230
+
231
+ expect(params.memorySpaceId).toBe(CONFIG.memorySpaceId);
232
+ expect(params.userId).toBe(CONFIG.userId);
233
+ expect(params.userName).toBe(CONFIG.userName);
234
+ expect(params.agentId).toBe(CONFIG.agentId);
235
+ expect(params.userMessage).toBe("Hello");
236
+ expect(params.agentResponse).toBe("Hi there");
237
+ expect(params.conversationId).toBe("conv-123");
238
+ });
239
+
240
+ it("includes belief revision when fact extraction is enabled", async () => {
241
+ delete process.env.OPENAI_API_KEY;
242
+ process.env.CORTEX_FACT_EXTRACTION = "true";
243
+
244
+ const { buildRememberParams } = await import("../cortex.js");
245
+
246
+ const params = await buildRememberParams({
247
+ userMessage: "Hello",
248
+ agentResponse: "Hi",
249
+ conversationId: "conv-123",
250
+ });
251
+
252
+ expect(params.extractFacts).toBe(true);
253
+ expect(params.beliefRevision).toEqual({
254
+ enabled: true,
255
+ slotMatching: true,
256
+ llmResolution: false, // No OpenAI key
257
+ });
258
+ });
259
+ });
260
+ });