@soleri/core 2.0.2 → 2.4.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 (226) hide show
  1. package/dist/brain/brain.d.ts +14 -50
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +207 -16
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +86 -0
  6. package/dist/brain/intelligence.d.ts.map +1 -0
  7. package/dist/brain/intelligence.js +771 -0
  8. package/dist/brain/intelligence.js.map +1 -0
  9. package/dist/brain/types.d.ts +197 -0
  10. package/dist/brain/types.d.ts.map +1 -0
  11. package/dist/brain/types.js +2 -0
  12. package/dist/brain/types.js.map +1 -0
  13. package/dist/cognee/client.d.ts +35 -0
  14. package/dist/cognee/client.d.ts.map +1 -0
  15. package/dist/cognee/client.js +291 -0
  16. package/dist/cognee/client.js.map +1 -0
  17. package/dist/cognee/types.d.ts +46 -0
  18. package/dist/cognee/types.d.ts.map +1 -0
  19. package/dist/cognee/types.js +3 -0
  20. package/dist/cognee/types.js.map +1 -0
  21. package/dist/control/identity-manager.d.ts +22 -0
  22. package/dist/control/identity-manager.d.ts.map +1 -0
  23. package/dist/control/identity-manager.js +233 -0
  24. package/dist/control/identity-manager.js.map +1 -0
  25. package/dist/control/intent-router.d.ts +32 -0
  26. package/dist/control/intent-router.d.ts.map +1 -0
  27. package/dist/control/intent-router.js +242 -0
  28. package/dist/control/intent-router.js.map +1 -0
  29. package/dist/control/types.d.ts +68 -0
  30. package/dist/control/types.d.ts.map +1 -0
  31. package/dist/control/types.js +9 -0
  32. package/dist/control/types.js.map +1 -0
  33. package/dist/curator/curator.d.ts +29 -0
  34. package/dist/curator/curator.d.ts.map +1 -1
  35. package/dist/curator/curator.js +142 -5
  36. package/dist/curator/curator.js.map +1 -1
  37. package/dist/facades/types.d.ts +1 -1
  38. package/dist/governance/governance.d.ts +42 -0
  39. package/dist/governance/governance.d.ts.map +1 -0
  40. package/dist/governance/governance.js +488 -0
  41. package/dist/governance/governance.js.map +1 -0
  42. package/dist/governance/index.d.ts +3 -0
  43. package/dist/governance/index.d.ts.map +1 -0
  44. package/dist/governance/index.js +2 -0
  45. package/dist/governance/index.js.map +1 -0
  46. package/dist/governance/types.d.ts +102 -0
  47. package/dist/governance/types.d.ts.map +1 -0
  48. package/dist/governance/types.js +3 -0
  49. package/dist/governance/types.js.map +1 -0
  50. package/dist/index.d.ts +35 -3
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +32 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/llm/llm-client.d.ts.map +1 -1
  55. package/dist/llm/llm-client.js +9 -2
  56. package/dist/llm/llm-client.js.map +1 -1
  57. package/dist/logging/logger.d.ts +37 -0
  58. package/dist/logging/logger.d.ts.map +1 -0
  59. package/dist/logging/logger.js +145 -0
  60. package/dist/logging/logger.js.map +1 -0
  61. package/dist/logging/types.d.ts +19 -0
  62. package/dist/logging/types.d.ts.map +1 -0
  63. package/dist/logging/types.js +2 -0
  64. package/dist/logging/types.js.map +1 -0
  65. package/dist/loop/loop-manager.d.ts +49 -0
  66. package/dist/loop/loop-manager.d.ts.map +1 -0
  67. package/dist/loop/loop-manager.js +105 -0
  68. package/dist/loop/loop-manager.js.map +1 -0
  69. package/dist/loop/types.d.ts +35 -0
  70. package/dist/loop/types.d.ts.map +1 -0
  71. package/dist/loop/types.js +8 -0
  72. package/dist/loop/types.js.map +1 -0
  73. package/dist/planning/gap-analysis.d.ts +29 -0
  74. package/dist/planning/gap-analysis.d.ts.map +1 -0
  75. package/dist/planning/gap-analysis.js +265 -0
  76. package/dist/planning/gap-analysis.js.map +1 -0
  77. package/dist/planning/gap-types.d.ts +29 -0
  78. package/dist/planning/gap-types.d.ts.map +1 -0
  79. package/dist/planning/gap-types.js +28 -0
  80. package/dist/planning/gap-types.js.map +1 -0
  81. package/dist/planning/planner.d.ts +150 -1
  82. package/dist/planning/planner.d.ts.map +1 -1
  83. package/dist/planning/planner.js +365 -2
  84. package/dist/planning/planner.js.map +1 -1
  85. package/dist/project/project-registry.d.ts +79 -0
  86. package/dist/project/project-registry.d.ts.map +1 -0
  87. package/dist/project/project-registry.js +276 -0
  88. package/dist/project/project-registry.js.map +1 -0
  89. package/dist/project/types.d.ts +28 -0
  90. package/dist/project/types.d.ts.map +1 -0
  91. package/dist/project/types.js +5 -0
  92. package/dist/project/types.js.map +1 -0
  93. package/dist/runtime/admin-extra-ops.d.ts +13 -0
  94. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  95. package/dist/runtime/admin-extra-ops.js +284 -0
  96. package/dist/runtime/admin-extra-ops.js.map +1 -0
  97. package/dist/runtime/admin-ops.d.ts +15 -0
  98. package/dist/runtime/admin-ops.d.ts.map +1 -0
  99. package/dist/runtime/admin-ops.js +322 -0
  100. package/dist/runtime/admin-ops.js.map +1 -0
  101. package/dist/runtime/capture-ops.d.ts +15 -0
  102. package/dist/runtime/capture-ops.d.ts.map +1 -0
  103. package/dist/runtime/capture-ops.js +345 -0
  104. package/dist/runtime/capture-ops.js.map +1 -0
  105. package/dist/runtime/core-ops.d.ts +7 -3
  106. package/dist/runtime/core-ops.d.ts.map +1 -1
  107. package/dist/runtime/core-ops.js +646 -15
  108. package/dist/runtime/core-ops.js.map +1 -1
  109. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  110. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  111. package/dist/runtime/curator-extra-ops.js +59 -0
  112. package/dist/runtime/curator-extra-ops.js.map +1 -0
  113. package/dist/runtime/domain-ops.d.ts.map +1 -1
  114. package/dist/runtime/domain-ops.js +59 -13
  115. package/dist/runtime/domain-ops.js.map +1 -1
  116. package/dist/runtime/grading-ops.d.ts +14 -0
  117. package/dist/runtime/grading-ops.d.ts.map +1 -0
  118. package/dist/runtime/grading-ops.js +105 -0
  119. package/dist/runtime/grading-ops.js.map +1 -0
  120. package/dist/runtime/loop-ops.d.ts +13 -0
  121. package/dist/runtime/loop-ops.d.ts.map +1 -0
  122. package/dist/runtime/loop-ops.js +179 -0
  123. package/dist/runtime/loop-ops.js.map +1 -0
  124. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  125. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  126. package/dist/runtime/memory-cross-project-ops.js +165 -0
  127. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  128. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  129. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  130. package/dist/runtime/memory-extra-ops.js +173 -0
  131. package/dist/runtime/memory-extra-ops.js.map +1 -0
  132. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  133. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  134. package/dist/runtime/orchestrate-ops.js +240 -0
  135. package/dist/runtime/orchestrate-ops.js.map +1 -0
  136. package/dist/runtime/planning-extra-ops.d.ts +17 -0
  137. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  138. package/dist/runtime/planning-extra-ops.js +300 -0
  139. package/dist/runtime/planning-extra-ops.js.map +1 -0
  140. package/dist/runtime/project-ops.d.ts +15 -0
  141. package/dist/runtime/project-ops.d.ts.map +1 -0
  142. package/dist/runtime/project-ops.js +181 -0
  143. package/dist/runtime/project-ops.js.map +1 -0
  144. package/dist/runtime/runtime.d.ts.map +1 -1
  145. package/dist/runtime/runtime.js +48 -1
  146. package/dist/runtime/runtime.js.map +1 -1
  147. package/dist/runtime/types.d.ts +23 -0
  148. package/dist/runtime/types.d.ts.map +1 -1
  149. package/dist/runtime/vault-extra-ops.d.ts +9 -0
  150. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  151. package/dist/runtime/vault-extra-ops.js +195 -0
  152. package/dist/runtime/vault-extra-ops.js.map +1 -0
  153. package/dist/telemetry/telemetry.d.ts +48 -0
  154. package/dist/telemetry/telemetry.d.ts.map +1 -0
  155. package/dist/telemetry/telemetry.js +87 -0
  156. package/dist/telemetry/telemetry.js.map +1 -0
  157. package/dist/vault/vault.d.ts +94 -0
  158. package/dist/vault/vault.d.ts.map +1 -1
  159. package/dist/vault/vault.js +340 -1
  160. package/dist/vault/vault.js.map +1 -1
  161. package/package.json +1 -1
  162. package/src/__tests__/admin-extra-ops.test.ts +420 -0
  163. package/src/__tests__/admin-ops.test.ts +271 -0
  164. package/src/__tests__/brain-intelligence.test.ts +828 -0
  165. package/src/__tests__/brain.test.ts +396 -27
  166. package/src/__tests__/capture-ops.test.ts +509 -0
  167. package/src/__tests__/cognee-client.test.ts +524 -0
  168. package/src/__tests__/core-ops.test.ts +341 -49
  169. package/src/__tests__/curator-extra-ops.test.ts +359 -0
  170. package/src/__tests__/curator.test.ts +126 -31
  171. package/src/__tests__/domain-ops.test.ts +111 -9
  172. package/src/__tests__/governance.test.ts +522 -0
  173. package/src/__tests__/grading-ops.test.ts +340 -0
  174. package/src/__tests__/identity-manager.test.ts +243 -0
  175. package/src/__tests__/intent-router.test.ts +222 -0
  176. package/src/__tests__/logger.test.ts +200 -0
  177. package/src/__tests__/loop-ops.test.ts +398 -0
  178. package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
  179. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  180. package/src/__tests__/orchestrate-ops.test.ts +284 -0
  181. package/src/__tests__/planner.test.ts +331 -0
  182. package/src/__tests__/planning-extra-ops.test.ts +548 -0
  183. package/src/__tests__/project-ops.test.ts +367 -0
  184. package/src/__tests__/runtime.test.ts +13 -11
  185. package/src/__tests__/vault-extra-ops.test.ts +407 -0
  186. package/src/brain/brain.ts +308 -72
  187. package/src/brain/intelligence.ts +1230 -0
  188. package/src/brain/types.ts +214 -0
  189. package/src/cognee/client.ts +352 -0
  190. package/src/cognee/types.ts +62 -0
  191. package/src/control/identity-manager.ts +354 -0
  192. package/src/control/intent-router.ts +326 -0
  193. package/src/control/types.ts +102 -0
  194. package/src/curator/curator.ts +265 -15
  195. package/src/governance/governance.ts +698 -0
  196. package/src/governance/index.ts +18 -0
  197. package/src/governance/types.ts +111 -0
  198. package/src/index.ts +128 -3
  199. package/src/llm/llm-client.ts +18 -24
  200. package/src/logging/logger.ts +154 -0
  201. package/src/logging/types.ts +21 -0
  202. package/src/loop/loop-manager.ts +130 -0
  203. package/src/loop/types.ts +44 -0
  204. package/src/planning/gap-analysis.ts +506 -0
  205. package/src/planning/gap-types.ts +58 -0
  206. package/src/planning/planner.ts +478 -2
  207. package/src/project/project-registry.ts +358 -0
  208. package/src/project/types.ts +31 -0
  209. package/src/runtime/admin-extra-ops.ts +307 -0
  210. package/src/runtime/admin-ops.ts +329 -0
  211. package/src/runtime/capture-ops.ts +385 -0
  212. package/src/runtime/core-ops.ts +747 -26
  213. package/src/runtime/curator-extra-ops.ts +71 -0
  214. package/src/runtime/domain-ops.ts +65 -13
  215. package/src/runtime/grading-ops.ts +121 -0
  216. package/src/runtime/loop-ops.ts +194 -0
  217. package/src/runtime/memory-cross-project-ops.ts +192 -0
  218. package/src/runtime/memory-extra-ops.ts +186 -0
  219. package/src/runtime/orchestrate-ops.ts +272 -0
  220. package/src/runtime/planning-extra-ops.ts +327 -0
  221. package/src/runtime/project-ops.ts +196 -0
  222. package/src/runtime/runtime.ts +54 -1
  223. package/src/runtime/types.ts +23 -0
  224. package/src/runtime/vault-extra-ops.ts +225 -0
  225. package/src/telemetry/telemetry.ts +118 -0
  226. package/src/vault/vault.ts +412 -1
@@ -0,0 +1,420 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { createAgentRuntime } from '../runtime/runtime.js';
3
+ import { createAdminExtraOps } from '../runtime/admin-extra-ops.js';
4
+ import type { AgentRuntime } from '../runtime/types.js';
5
+ import type { OpDefinition } from '../facades/types.js';
6
+
7
+ describe('createAdminExtraOps', () => {
8
+ let runtime: AgentRuntime;
9
+ let ops: OpDefinition[];
10
+
11
+ function findOp(name: string): OpDefinition {
12
+ const op = ops.find((o) => o.name === name);
13
+ if (!op) throw new Error(`Op "${name}" not found`);
14
+ return op;
15
+ }
16
+
17
+ afterEach(() => {
18
+ runtime?.close();
19
+ });
20
+
21
+ function setup() {
22
+ runtime = createAgentRuntime({
23
+ agentId: 'test-admin-extra',
24
+ vaultPath: ':memory:',
25
+ });
26
+ ops = createAdminExtraOps(runtime);
27
+ }
28
+
29
+ it('should return 10 ops', () => {
30
+ setup();
31
+ expect(ops).toHaveLength(10);
32
+ const names = ops.map((o) => o.name);
33
+ expect(names).toEqual([
34
+ 'admin_telemetry',
35
+ 'admin_telemetry_recent',
36
+ 'admin_telemetry_reset',
37
+ 'admin_permissions',
38
+ 'admin_vault_analytics',
39
+ 'admin_search_insights',
40
+ 'admin_module_status',
41
+ 'admin_env',
42
+ 'admin_gc',
43
+ 'admin_export_config',
44
+ ]);
45
+ });
46
+
47
+ // ─── admin_telemetry ────────────────────────────────────────────
48
+
49
+ describe('admin_telemetry', () => {
50
+ it('should return zero stats initially', async () => {
51
+ setup();
52
+ const result = (await findOp('admin_telemetry').handler({})) as {
53
+ totalCalls: number;
54
+ successRate: number;
55
+ avgDurationMs: number;
56
+ callsByFacade: Record<string, number>;
57
+ callsByOp: Record<string, number>;
58
+ };
59
+
60
+ expect(result.totalCalls).toBe(0);
61
+ expect(result.successRate).toBe(1);
62
+ expect(result.avgDurationMs).toBe(0);
63
+ expect(result.callsByFacade).toEqual({});
64
+ expect(result.callsByOp).toEqual({});
65
+ });
66
+
67
+ it('should reflect recorded calls', async () => {
68
+ setup();
69
+ runtime.telemetry.record({ facade: 'core', op: 'search', durationMs: 100, success: true });
70
+ runtime.telemetry.record({ facade: 'core', op: 'search', durationMs: 200, success: true });
71
+ runtime.telemetry.record({ facade: 'core', op: 'register', durationMs: 50, success: false, error: 'test error' });
72
+
73
+ const result = (await findOp('admin_telemetry').handler({})) as {
74
+ totalCalls: number;
75
+ successRate: number;
76
+ avgDurationMs: number;
77
+ callsByFacade: Record<string, number>;
78
+ callsByOp: Record<string, number>;
79
+ errorsByOp: Record<string, number>;
80
+ };
81
+
82
+ expect(result.totalCalls).toBe(3);
83
+ expect(result.successRate).toBe(0.667);
84
+ expect(result.avgDurationMs).toBeGreaterThan(0);
85
+ expect(result.callsByFacade.core).toBe(3);
86
+ expect(result.callsByOp.search).toBe(2);
87
+ expect(result.callsByOp.register).toBe(1);
88
+ expect(result.errorsByOp.register).toBe(1);
89
+ });
90
+ });
91
+
92
+ // ─── admin_telemetry_recent ─────────────────────────────────────
93
+
94
+ describe('admin_telemetry_recent', () => {
95
+ it('should return recent calls in reverse order', async () => {
96
+ setup();
97
+ runtime.telemetry.record({ facade: 'core', op: 'op_a', durationMs: 10, success: true });
98
+ runtime.telemetry.record({ facade: 'core', op: 'op_b', durationMs: 20, success: true });
99
+ runtime.telemetry.record({ facade: 'core', op: 'op_c', durationMs: 30, success: true });
100
+
101
+ const result = (await findOp('admin_telemetry_recent').handler({ limit: 2 })) as Array<{
102
+ op: string;
103
+ }>;
104
+
105
+ expect(result).toHaveLength(2);
106
+ expect(result[0].op).toBe('op_c');
107
+ expect(result[1].op).toBe('op_b');
108
+ });
109
+
110
+ it('should return empty array when no calls recorded', async () => {
111
+ setup();
112
+ const result = (await findOp('admin_telemetry_recent').handler({})) as unknown[];
113
+ expect(result).toEqual([]);
114
+ });
115
+ });
116
+
117
+ // ─── admin_telemetry_reset ──────────────────────────────────────
118
+
119
+ describe('admin_telemetry_reset', () => {
120
+ it('should clear telemetry data', async () => {
121
+ setup();
122
+ runtime.telemetry.record({ facade: 'core', op: 'search', durationMs: 100, success: true });
123
+
124
+ const resetResult = (await findOp('admin_telemetry_reset').handler({})) as {
125
+ reset: boolean;
126
+ };
127
+ expect(resetResult.reset).toBe(true);
128
+
129
+ const stats = (await findOp('admin_telemetry').handler({})) as { totalCalls: number };
130
+ expect(stats.totalCalls).toBe(0);
131
+ });
132
+
133
+ it('should have write auth', () => {
134
+ setup();
135
+ expect(findOp('admin_telemetry_reset').auth).toBe('write');
136
+ });
137
+ });
138
+
139
+ // ─── admin_permissions ──────────────────────────────────────────
140
+
141
+ describe('admin_permissions', () => {
142
+ it('should return moderate as default', async () => {
143
+ setup();
144
+ const result = (await findOp('admin_permissions').handler({ action: 'get' })) as {
145
+ level: string;
146
+ };
147
+ expect(result.level).toBe('moderate');
148
+ });
149
+
150
+ it('should allow setting permission level', async () => {
151
+ setup();
152
+ await findOp('admin_permissions').handler({ action: 'set', level: 'strict' });
153
+ const result = (await findOp('admin_permissions').handler({ action: 'get' })) as {
154
+ level: string;
155
+ };
156
+ expect(result.level).toBe('strict');
157
+ });
158
+
159
+ it('should not change level when set without level param', async () => {
160
+ setup();
161
+ await findOp('admin_permissions').handler({ action: 'set', level: 'permissive' });
162
+ await findOp('admin_permissions').handler({ action: 'set' });
163
+ const result = (await findOp('admin_permissions').handler({ action: 'get' })) as {
164
+ level: string;
165
+ };
166
+ expect(result.level).toBe('permissive');
167
+ });
168
+
169
+ it('should have write auth', () => {
170
+ setup();
171
+ expect(findOp('admin_permissions').auth).toBe('write');
172
+ });
173
+ });
174
+
175
+ // ─── admin_vault_analytics ──────────────────────────────────────
176
+
177
+ describe('admin_vault_analytics', () => {
178
+ it('should return zeroes for empty vault', async () => {
179
+ setup();
180
+ const result = (await findOp('admin_vault_analytics').handler({})) as {
181
+ totalEntries: number;
182
+ byDomain: Record<string, number>;
183
+ byType: Record<string, number>;
184
+ byAge: Record<string, number>;
185
+ avgTagsPerEntry: number;
186
+ entriesWithoutTags: number;
187
+ entriesWithoutDescription: number;
188
+ };
189
+
190
+ expect(result.totalEntries).toBe(0);
191
+ expect(result.byDomain).toEqual({});
192
+ expect(result.byType).toEqual({});
193
+ expect(result.avgTagsPerEntry).toBe(0);
194
+ expect(result.entriesWithoutTags).toBe(0);
195
+ expect(result.entriesWithoutDescription).toBe(0);
196
+ });
197
+
198
+ it('should return domain and type breakdowns after seed', async () => {
199
+ setup();
200
+ runtime.vault.seed([
201
+ {
202
+ id: 'va-1',
203
+ type: 'pattern',
204
+ domain: 'testing',
205
+ title: 'Analytics Test 1',
206
+ severity: 'warning',
207
+ description: 'Test entry.',
208
+ tags: ['a', 'b'],
209
+ },
210
+ {
211
+ id: 'va-2',
212
+ type: 'anti-pattern',
213
+ domain: 'testing',
214
+ title: 'Analytics Test 2',
215
+ severity: 'critical',
216
+ description: 'Test entry 2.',
217
+ tags: ['c'],
218
+ },
219
+ {
220
+ id: 'va-3',
221
+ type: 'rule',
222
+ domain: 'design',
223
+ title: 'Analytics Test 3',
224
+ severity: 'suggestion',
225
+ description: 'Test entry 3.',
226
+ tags: [],
227
+ },
228
+ ]);
229
+
230
+ const result = (await findOp('admin_vault_analytics').handler({})) as {
231
+ totalEntries: number;
232
+ byDomain: Record<string, number>;
233
+ byType: Record<string, number>;
234
+ avgTagsPerEntry: number;
235
+ entriesWithoutTags: number;
236
+ };
237
+
238
+ expect(result.totalEntries).toBe(3);
239
+ expect(result.byDomain.testing).toBe(2);
240
+ expect(result.byDomain.design).toBe(1);
241
+ expect(result.byType.pattern).toBe(1);
242
+ expect(result.byType['anti-pattern']).toBe(1);
243
+ expect(result.byType.rule).toBe(1);
244
+ expect(result.avgTagsPerEntry).toBe(1); // (2+1+0)/3 = 1
245
+ expect(result.entriesWithoutTags).toBe(1);
246
+ });
247
+ });
248
+
249
+ // ─── admin_search_insights ──────────────────────────────────────
250
+
251
+ describe('admin_search_insights', () => {
252
+ it('should return empty insights with no feedback', async () => {
253
+ setup();
254
+ const result = (await findOp('admin_search_insights').handler({})) as {
255
+ totalFeedback: number;
256
+ missRate: number;
257
+ missCount: number;
258
+ topMissedQueries: unknown[];
259
+ };
260
+
261
+ expect(result.totalFeedback).toBe(0);
262
+ expect(result.missRate).toBe(0);
263
+ expect(result.missCount).toBe(0);
264
+ expect(result.topMissedQueries).toEqual([]);
265
+ });
266
+
267
+ it('should compute miss rate from feedback', async () => {
268
+ setup();
269
+ // Seed entries for feedback to reference
270
+ runtime.vault.seed([
271
+ {
272
+ id: 'si-1',
273
+ type: 'pattern',
274
+ domain: 'testing',
275
+ title: 'Insight Test',
276
+ severity: 'warning',
277
+ description: 'Test.',
278
+ tags: ['test'],
279
+ },
280
+ ]);
281
+
282
+ // Record feedback
283
+ runtime.brain.recordFeedback({ query: 'test query', entryId: 'si-1', action: 'accepted' });
284
+ runtime.brain.recordFeedback({ query: 'missed query', entryId: 'si-1', action: 'dismissed' });
285
+ runtime.brain.recordFeedback({ query: 'failed query', entryId: 'si-1', action: 'failed' });
286
+
287
+ const result = (await findOp('admin_search_insights').handler({})) as {
288
+ totalFeedback: number;
289
+ missRate: number;
290
+ missCount: number;
291
+ topMissedQueries: Array<{ query: string; count: number }>;
292
+ };
293
+
294
+ expect(result.totalFeedback).toBe(3);
295
+ expect(result.missCount).toBe(2);
296
+ expect(result.missRate).toBe(0.667);
297
+ expect(result.topMissedQueries.length).toBeGreaterThan(0);
298
+ });
299
+ });
300
+
301
+ // ─── admin_module_status ────────────────────────────────────────
302
+
303
+ describe('admin_module_status', () => {
304
+ it('should return all modules as initialized', async () => {
305
+ setup();
306
+ const result = (await findOp('admin_module_status').handler({})) as {
307
+ vault: boolean;
308
+ brain: boolean;
309
+ planner: boolean;
310
+ curator: boolean;
311
+ governance: boolean;
312
+ cognee: { available: boolean };
313
+ loop: { active: boolean };
314
+ llm: { openai: boolean; anthropic: boolean };
315
+ };
316
+
317
+ expect(result.vault).toBe(true);
318
+ expect(result.brain).toBe(true);
319
+ expect(result.planner).toBe(true);
320
+ expect(result.curator).toBe(true);
321
+ expect(result.governance).toBe(true);
322
+ expect(result.cognee).toEqual({ available: false });
323
+ expect(result.loop).toEqual({ active: false });
324
+ expect(typeof result.llm.openai).toBe('boolean');
325
+ expect(typeof result.llm.anthropic).toBe('boolean');
326
+ });
327
+ });
328
+
329
+ // ─── admin_env ──────────────────────────────────────────────────
330
+
331
+ describe('admin_env', () => {
332
+ it('should return node version and platform', async () => {
333
+ setup();
334
+ const result = (await findOp('admin_env').handler({})) as {
335
+ nodeVersion: string;
336
+ platform: string;
337
+ arch: string;
338
+ pid: number;
339
+ memoryUsage: { rss: number };
340
+ cwd: string;
341
+ };
342
+
343
+ expect(result.nodeVersion).toMatch(/^v\d+/);
344
+ expect(typeof result.platform).toBe('string');
345
+ expect(typeof result.arch).toBe('string');
346
+ expect(typeof result.pid).toBe('number');
347
+ expect(result.memoryUsage.rss).toBeGreaterThan(0);
348
+ expect(typeof result.cwd).toBe('string');
349
+ });
350
+ });
351
+
352
+ // ─── admin_gc ───────────────────────────────────────────────────
353
+
354
+ describe('admin_gc', () => {
355
+ it('should return cleared modules list', async () => {
356
+ setup();
357
+ const result = (await findOp('admin_gc').handler({})) as { cleared: string[] };
358
+
359
+ expect(result.cleared).toContain('brain');
360
+ expect(result.cleared).toContain('cognee');
361
+ expect(result.cleared).toContain('telemetry');
362
+ });
363
+
364
+ it('should have write auth', () => {
365
+ setup();
366
+ expect(findOp('admin_gc').auth).toBe('write');
367
+ });
368
+ });
369
+
370
+ // ─── admin_export_config ────────────────────────────────────────
371
+
372
+ describe('admin_export_config', () => {
373
+ it('should return agent config without secrets', async () => {
374
+ setup();
375
+ const result = (await findOp('admin_export_config').handler({})) as {
376
+ agentId: string;
377
+ vaultPath: string | null;
378
+ plansPath: string | null;
379
+ logLevel: string;
380
+ modules: string[];
381
+ };
382
+
383
+ expect(result.agentId).toBe('test-admin-extra');
384
+ expect(result.vaultPath).toBe(':memory:');
385
+ expect(result.logLevel).toBe('info');
386
+ expect(result.modules).toContain('vault');
387
+ expect(result.modules).toContain('brain');
388
+ expect(result.modules).toContain('telemetry');
389
+ expect(result.modules.length).toBeGreaterThan(5);
390
+ });
391
+ });
392
+
393
+ // ─── Auth levels ────────────────────────────────────────────────
394
+
395
+ describe('auth levels', () => {
396
+ it('should use read auth for status ops', () => {
397
+ setup();
398
+ const readOps = [
399
+ 'admin_telemetry',
400
+ 'admin_telemetry_recent',
401
+ 'admin_vault_analytics',
402
+ 'admin_search_insights',
403
+ 'admin_module_status',
404
+ 'admin_env',
405
+ 'admin_export_config',
406
+ ];
407
+ for (const name of readOps) {
408
+ expect(findOp(name).auth).toBe('read');
409
+ }
410
+ });
411
+
412
+ it('should use write auth for mutation ops', () => {
413
+ setup();
414
+ const writeOps = ['admin_telemetry_reset', 'admin_permissions', 'admin_gc'];
415
+ for (const name of writeOps) {
416
+ expect(findOp(name).auth).toBe('write');
417
+ }
418
+ });
419
+ });
420
+ });
@@ -0,0 +1,271 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { createAgentRuntime } from '../runtime/runtime.js';
3
+ import { createAdminOps } from '../runtime/admin-ops.js';
4
+ import type { AgentRuntime } from '../runtime/types.js';
5
+ import type { OpDefinition } from '../facades/types.js';
6
+
7
+ describe('createAdminOps', () => {
8
+ let runtime: AgentRuntime;
9
+ let ops: OpDefinition[];
10
+
11
+ function findOp(name: string): OpDefinition {
12
+ const op = ops.find((o) => o.name === name);
13
+ if (!op) throw new Error(`Op "${name}" not found`);
14
+ return op;
15
+ }
16
+
17
+ afterEach(() => {
18
+ runtime?.close();
19
+ });
20
+
21
+ function setup() {
22
+ runtime = createAgentRuntime({
23
+ agentId: 'test-admin',
24
+ vaultPath: ':memory:',
25
+ });
26
+ ops = createAdminOps(runtime);
27
+ }
28
+
29
+ it('should return 8 ops', () => {
30
+ setup();
31
+ expect(ops).toHaveLength(8);
32
+ const names = ops.map((o) => o.name);
33
+ expect(names).toEqual([
34
+ 'admin_health',
35
+ 'admin_tool_list',
36
+ 'admin_config',
37
+ 'admin_vault_size',
38
+ 'admin_uptime',
39
+ 'admin_version',
40
+ 'admin_reset_cache',
41
+ 'admin_diagnostic',
42
+ ]);
43
+ });
44
+
45
+ // ─── admin_health ──────────────────────────────────────────────
46
+
47
+ describe('admin_health', () => {
48
+ it('should return comprehensive health status', async () => {
49
+ setup();
50
+ const result = (await findOp('admin_health').handler({})) as Record<string, unknown>;
51
+
52
+ expect(result.status).toBe('ok');
53
+ expect(result.vault).toEqual({ entries: 0, domains: [] });
54
+ expect(result.cognee).toEqual({ available: false });
55
+ expect(result.llm).toHaveProperty('openai');
56
+ expect(result.llm).toHaveProperty('anthropic');
57
+ expect(result.brain).toHaveProperty('vocabularySize');
58
+ expect(result.brain).toHaveProperty('feedbackCount');
59
+ expect(result.curator).toEqual({ initialized: true });
60
+ });
61
+
62
+ it('should reflect vault entries after seed', async () => {
63
+ setup();
64
+ runtime.vault.seed([
65
+ {
66
+ id: 'ah-1',
67
+ type: 'pattern',
68
+ domain: 'testing',
69
+ title: 'Health Test',
70
+ severity: 'warning',
71
+ description: 'Test entry.',
72
+ tags: ['test'],
73
+ },
74
+ ]);
75
+ const result = (await findOp('admin_health').handler({})) as Record<string, unknown>;
76
+ const vault = result.vault as { entries: number; domains: string[] };
77
+ expect(vault.entries).toBe(1);
78
+ expect(vault.domains).toContain('testing');
79
+ });
80
+ });
81
+
82
+ // ─── admin_tool_list ───────────────────────────────────────────
83
+
84
+ describe('admin_tool_list', () => {
85
+ it('should return fallback list without _allOps', async () => {
86
+ setup();
87
+ const result = (await findOp('admin_tool_list').handler({})) as {
88
+ count: number;
89
+ ops: Array<{ name: string }>;
90
+ };
91
+
92
+ expect(result.count).toBe(8);
93
+ expect(result.ops.map((o) => o.name)).toContain('admin_health');
94
+ expect(result.ops.map((o) => o.name)).toContain('admin_diagnostic');
95
+ });
96
+
97
+ it('should return injected ops when _allOps is provided', async () => {
98
+ setup();
99
+ const allOps = [
100
+ { name: 'search', description: 'Search vault', auth: 'read' },
101
+ { name: 'admin_health', description: 'Health check', auth: 'read' },
102
+ ];
103
+ const result = (await findOp('admin_tool_list').handler({ _allOps: allOps })) as {
104
+ count: number;
105
+ ops: Array<{ name: string; description: string; auth: string }>;
106
+ };
107
+
108
+ expect(result.count).toBe(2);
109
+ expect(result.ops[0].name).toBe('search');
110
+ expect(result.ops[1].name).toBe('admin_health');
111
+ });
112
+ });
113
+
114
+ // ─── admin_config ──────────────────────────────────────────────
115
+
116
+ describe('admin_config', () => {
117
+ it('should return runtime config', async () => {
118
+ setup();
119
+ const result = (await findOp('admin_config').handler({})) as Record<string, unknown>;
120
+
121
+ expect(result.agentId).toBe('test-admin');
122
+ expect(result.vaultPath).toBe(':memory:');
123
+ expect(result.logLevel).toBe('info');
124
+ });
125
+ });
126
+
127
+ // ─── admin_vault_size ──────────────────────────────────────────
128
+
129
+ describe('admin_vault_size', () => {
130
+ it('should return null size for in-memory vault', async () => {
131
+ setup();
132
+ const result = (await findOp('admin_vault_size').handler({})) as Record<string, unknown>;
133
+
134
+ expect(result.path).toBe(':memory:');
135
+ expect(result.sizeBytes).toBeNull();
136
+ expect(result.sizeHuman).toBe('in-memory');
137
+ });
138
+ });
139
+
140
+ // ─── admin_uptime ──────────────────────────────────────────────
141
+
142
+ describe('admin_uptime', () => {
143
+ it('should return uptime since creation', async () => {
144
+ setup();
145
+ const result = (await findOp('admin_uptime').handler({})) as Record<string, unknown>;
146
+
147
+ expect(result.createdAt).toBeDefined();
148
+ expect(typeof result.uptimeMs).toBe('number');
149
+ expect(typeof result.uptimeSec).toBe('number');
150
+ expect(typeof result.uptimeHuman).toBe('string');
151
+ expect((result.uptimeMs as number)).toBeGreaterThanOrEqual(0);
152
+ expect((result.uptimeMs as number)).toBeLessThan(5000); // should be nearly instant
153
+ });
154
+
155
+ it('should have valid ISO date in createdAt', async () => {
156
+ setup();
157
+ const result = (await findOp('admin_uptime').handler({})) as { createdAt: string };
158
+ const parsed = new Date(result.createdAt);
159
+ expect(parsed.getTime()).toBeGreaterThan(0);
160
+ });
161
+ });
162
+
163
+ // ─── admin_version ─────────────────────────────────────────────
164
+
165
+ describe('admin_version', () => {
166
+ it('should return version information', async () => {
167
+ setup();
168
+ const result = (await findOp('admin_version').handler({})) as Record<string, unknown>;
169
+
170
+ expect(typeof result.core).toBe('string');
171
+ expect(result.node).toMatch(/^v\d+/);
172
+ expect(typeof result.platform).toBe('string');
173
+ expect(typeof result.arch).toBe('string');
174
+ });
175
+ });
176
+
177
+ // ─── admin_reset_cache ─────────────────────────────────────────
178
+
179
+ describe('admin_reset_cache', () => {
180
+ it('should clear caches and return status', async () => {
181
+ setup();
182
+ const result = (await findOp('admin_reset_cache').handler({})) as Record<string, unknown>;
183
+
184
+ expect(result.cleared).toEqual(['brain_vocabulary', 'cognee_health_cache']);
185
+ expect(typeof (result as { brainVocabularySize: number }).brainVocabularySize).toBe('number');
186
+ expect(typeof (result as { cogneeAvailable: boolean }).cogneeAvailable).toBe('boolean');
187
+ });
188
+
189
+ it('should have write auth', () => {
190
+ setup();
191
+ const op = findOp('admin_reset_cache');
192
+ expect(op.auth).toBe('write');
193
+ });
194
+ });
195
+
196
+ // ─── admin_diagnostic ──────────────────────────────────────────
197
+
198
+ describe('admin_diagnostic', () => {
199
+ it('should return diagnostic report with all checks', async () => {
200
+ setup();
201
+ const result = (await findOp('admin_diagnostic').handler({})) as {
202
+ overall: string;
203
+ checks: Array<{ name: string; status: string; detail: string }>;
204
+ summary: string;
205
+ };
206
+
207
+ expect(['healthy', 'degraded', 'unhealthy']).toContain(result.overall);
208
+ expect(result.checks.length).toBeGreaterThanOrEqual(7);
209
+
210
+ const checkNames = result.checks.map((c) => c.name);
211
+ expect(checkNames).toContain('vault');
212
+ expect(checkNames).toContain('brain_vocabulary');
213
+ expect(checkNames).toContain('brain_intelligence');
214
+ expect(checkNames).toContain('cognee');
215
+ expect(checkNames).toContain('llm_openai');
216
+ expect(checkNames).toContain('llm_anthropic');
217
+ expect(checkNames).toContain('curator');
218
+ });
219
+
220
+ it('should report vault as ok for empty vault', async () => {
221
+ setup();
222
+ const result = (await findOp('admin_diagnostic').handler({})) as {
223
+ checks: Array<{ name: string; status: string }>;
224
+ };
225
+
226
+ const vaultCheck = result.checks.find((c) => c.name === 'vault');
227
+ expect(vaultCheck?.status).toBe('ok');
228
+ });
229
+
230
+ it('should report curator as ok', async () => {
231
+ setup();
232
+ const result = (await findOp('admin_diagnostic').handler({})) as {
233
+ checks: Array<{ name: string; status: string }>;
234
+ };
235
+
236
+ const curatorCheck = result.checks.find((c) => c.name === 'curator');
237
+ expect(curatorCheck?.status).toBe('ok');
238
+ });
239
+
240
+ it('should include summary string', async () => {
241
+ setup();
242
+ const result = (await findOp('admin_diagnostic').handler({})) as { summary: string };
243
+ expect(result.summary).toMatch(/\d+ checks:/);
244
+ });
245
+ });
246
+
247
+ // ─── Auth levels ───────────────────────────────────────────────
248
+
249
+ describe('auth levels', () => {
250
+ it('should use read auth for status ops', () => {
251
+ setup();
252
+ const readOps = [
253
+ 'admin_health',
254
+ 'admin_tool_list',
255
+ 'admin_config',
256
+ 'admin_vault_size',
257
+ 'admin_uptime',
258
+ 'admin_version',
259
+ 'admin_diagnostic',
260
+ ];
261
+ for (const name of readOps) {
262
+ expect(findOp(name).auth).toBe('read');
263
+ }
264
+ });
265
+
266
+ it('should use write auth for mutation ops', () => {
267
+ setup();
268
+ expect(findOp('admin_reset_cache').auth).toBe('write');
269
+ });
270
+ });
271
+ });