@soleri/core 2.1.0 → 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 (207) hide show
  1. package/dist/brain/brain.d.ts +3 -1
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +60 -4
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +36 -1
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +119 -14
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +32 -0
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/control/identity-manager.d.ts +22 -0
  12. package/dist/control/identity-manager.d.ts.map +1 -0
  13. package/dist/control/identity-manager.js +233 -0
  14. package/dist/control/identity-manager.js.map +1 -0
  15. package/dist/control/intent-router.d.ts +32 -0
  16. package/dist/control/intent-router.d.ts.map +1 -0
  17. package/dist/control/intent-router.js +242 -0
  18. package/dist/control/intent-router.js.map +1 -0
  19. package/dist/control/types.d.ts +68 -0
  20. package/dist/control/types.d.ts.map +1 -0
  21. package/dist/control/types.js +9 -0
  22. package/dist/control/types.js.map +1 -0
  23. package/dist/curator/curator.d.ts +29 -0
  24. package/dist/curator/curator.d.ts.map +1 -1
  25. package/dist/curator/curator.js +135 -0
  26. package/dist/curator/curator.js.map +1 -1
  27. package/dist/facades/types.d.ts +1 -1
  28. package/dist/governance/governance.d.ts +42 -0
  29. package/dist/governance/governance.d.ts.map +1 -0
  30. package/dist/governance/governance.js +488 -0
  31. package/dist/governance/governance.js.map +1 -0
  32. package/dist/governance/index.d.ts +3 -0
  33. package/dist/governance/index.d.ts.map +1 -0
  34. package/dist/governance/index.js +2 -0
  35. package/dist/governance/index.js.map +1 -0
  36. package/dist/governance/types.d.ts +102 -0
  37. package/dist/governance/types.d.ts.map +1 -0
  38. package/dist/governance/types.js +3 -0
  39. package/dist/governance/types.js.map +1 -0
  40. package/dist/index.d.ts +32 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +29 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/logging/logger.d.ts +37 -0
  45. package/dist/logging/logger.d.ts.map +1 -0
  46. package/dist/logging/logger.js +145 -0
  47. package/dist/logging/logger.js.map +1 -0
  48. package/dist/logging/types.d.ts +19 -0
  49. package/dist/logging/types.d.ts.map +1 -0
  50. package/dist/logging/types.js +2 -0
  51. package/dist/logging/types.js.map +1 -0
  52. package/dist/loop/loop-manager.d.ts +49 -0
  53. package/dist/loop/loop-manager.d.ts.map +1 -0
  54. package/dist/loop/loop-manager.js +105 -0
  55. package/dist/loop/loop-manager.js.map +1 -0
  56. package/dist/loop/types.d.ts +35 -0
  57. package/dist/loop/types.d.ts.map +1 -0
  58. package/dist/loop/types.js +8 -0
  59. package/dist/loop/types.js.map +1 -0
  60. package/dist/planning/gap-analysis.d.ts +29 -0
  61. package/dist/planning/gap-analysis.d.ts.map +1 -0
  62. package/dist/planning/gap-analysis.js +265 -0
  63. package/dist/planning/gap-analysis.js.map +1 -0
  64. package/dist/planning/gap-types.d.ts +29 -0
  65. package/dist/planning/gap-types.d.ts.map +1 -0
  66. package/dist/planning/gap-types.js +28 -0
  67. package/dist/planning/gap-types.js.map +1 -0
  68. package/dist/planning/planner.d.ts +150 -1
  69. package/dist/planning/planner.d.ts.map +1 -1
  70. package/dist/planning/planner.js +365 -2
  71. package/dist/planning/planner.js.map +1 -1
  72. package/dist/project/project-registry.d.ts +79 -0
  73. package/dist/project/project-registry.d.ts.map +1 -0
  74. package/dist/project/project-registry.js +276 -0
  75. package/dist/project/project-registry.js.map +1 -0
  76. package/dist/project/types.d.ts +28 -0
  77. package/dist/project/types.d.ts.map +1 -0
  78. package/dist/project/types.js +5 -0
  79. package/dist/project/types.js.map +1 -0
  80. package/dist/runtime/admin-extra-ops.d.ts +13 -0
  81. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  82. package/dist/runtime/admin-extra-ops.js +284 -0
  83. package/dist/runtime/admin-extra-ops.js.map +1 -0
  84. package/dist/runtime/admin-ops.d.ts +15 -0
  85. package/dist/runtime/admin-ops.d.ts.map +1 -0
  86. package/dist/runtime/admin-ops.js +322 -0
  87. package/dist/runtime/admin-ops.js.map +1 -0
  88. package/dist/runtime/capture-ops.d.ts +15 -0
  89. package/dist/runtime/capture-ops.d.ts.map +1 -0
  90. package/dist/runtime/capture-ops.js +345 -0
  91. package/dist/runtime/capture-ops.js.map +1 -0
  92. package/dist/runtime/core-ops.d.ts +7 -3
  93. package/dist/runtime/core-ops.d.ts.map +1 -1
  94. package/dist/runtime/core-ops.js +474 -8
  95. package/dist/runtime/core-ops.js.map +1 -1
  96. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  97. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  98. package/dist/runtime/curator-extra-ops.js +59 -0
  99. package/dist/runtime/curator-extra-ops.js.map +1 -0
  100. package/dist/runtime/domain-ops.d.ts.map +1 -1
  101. package/dist/runtime/domain-ops.js +59 -13
  102. package/dist/runtime/domain-ops.js.map +1 -1
  103. package/dist/runtime/grading-ops.d.ts +14 -0
  104. package/dist/runtime/grading-ops.d.ts.map +1 -0
  105. package/dist/runtime/grading-ops.js +105 -0
  106. package/dist/runtime/grading-ops.js.map +1 -0
  107. package/dist/runtime/loop-ops.d.ts +13 -0
  108. package/dist/runtime/loop-ops.d.ts.map +1 -0
  109. package/dist/runtime/loop-ops.js +179 -0
  110. package/dist/runtime/loop-ops.js.map +1 -0
  111. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  112. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  113. package/dist/runtime/memory-cross-project-ops.js +165 -0
  114. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  115. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  116. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  117. package/dist/runtime/memory-extra-ops.js +173 -0
  118. package/dist/runtime/memory-extra-ops.js.map +1 -0
  119. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  120. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  121. package/dist/runtime/orchestrate-ops.js +240 -0
  122. package/dist/runtime/orchestrate-ops.js.map +1 -0
  123. package/dist/runtime/planning-extra-ops.d.ts +17 -0
  124. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  125. package/dist/runtime/planning-extra-ops.js +300 -0
  126. package/dist/runtime/planning-extra-ops.js.map +1 -0
  127. package/dist/runtime/project-ops.d.ts +15 -0
  128. package/dist/runtime/project-ops.d.ts.map +1 -0
  129. package/dist/runtime/project-ops.js +181 -0
  130. package/dist/runtime/project-ops.js.map +1 -0
  131. package/dist/runtime/runtime.d.ts.map +1 -1
  132. package/dist/runtime/runtime.js +44 -1
  133. package/dist/runtime/runtime.js.map +1 -1
  134. package/dist/runtime/types.d.ts +21 -0
  135. package/dist/runtime/types.d.ts.map +1 -1
  136. package/dist/runtime/vault-extra-ops.d.ts +9 -0
  137. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  138. package/dist/runtime/vault-extra-ops.js +195 -0
  139. package/dist/runtime/vault-extra-ops.js.map +1 -0
  140. package/dist/telemetry/telemetry.d.ts +48 -0
  141. package/dist/telemetry/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry/telemetry.js +87 -0
  143. package/dist/telemetry/telemetry.js.map +1 -0
  144. package/dist/vault/vault.d.ts +94 -0
  145. package/dist/vault/vault.d.ts.map +1 -1
  146. package/dist/vault/vault.js +340 -1
  147. package/dist/vault/vault.js.map +1 -1
  148. package/package.json +1 -1
  149. package/src/__tests__/admin-extra-ops.test.ts +420 -0
  150. package/src/__tests__/admin-ops.test.ts +271 -0
  151. package/src/__tests__/brain-intelligence.test.ts +205 -0
  152. package/src/__tests__/brain.test.ts +131 -0
  153. package/src/__tests__/capture-ops.test.ts +509 -0
  154. package/src/__tests__/core-ops.test.ts +266 -2
  155. package/src/__tests__/curator-extra-ops.test.ts +359 -0
  156. package/src/__tests__/domain-ops.test.ts +66 -0
  157. package/src/__tests__/governance.test.ts +522 -0
  158. package/src/__tests__/grading-ops.test.ts +340 -0
  159. package/src/__tests__/identity-manager.test.ts +243 -0
  160. package/src/__tests__/intent-router.test.ts +222 -0
  161. package/src/__tests__/logger.test.ts +200 -0
  162. package/src/__tests__/loop-ops.test.ts +398 -0
  163. package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
  164. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  165. package/src/__tests__/orchestrate-ops.test.ts +284 -0
  166. package/src/__tests__/planner.test.ts +331 -0
  167. package/src/__tests__/planning-extra-ops.test.ts +548 -0
  168. package/src/__tests__/project-ops.test.ts +367 -0
  169. package/src/__tests__/vault-extra-ops.test.ts +407 -0
  170. package/src/brain/brain.ts +114 -7
  171. package/src/brain/intelligence.ts +179 -10
  172. package/src/brain/types.ts +38 -0
  173. package/src/control/identity-manager.ts +354 -0
  174. package/src/control/intent-router.ts +326 -0
  175. package/src/control/types.ts +102 -0
  176. package/src/curator/curator.ts +213 -0
  177. package/src/governance/governance.ts +698 -0
  178. package/src/governance/index.ts +18 -0
  179. package/src/governance/types.ts +111 -0
  180. package/src/index.ts +102 -2
  181. package/src/logging/logger.ts +154 -0
  182. package/src/logging/types.ts +21 -0
  183. package/src/loop/loop-manager.ts +130 -0
  184. package/src/loop/types.ts +44 -0
  185. package/src/planning/gap-analysis.ts +506 -0
  186. package/src/planning/gap-types.ts +58 -0
  187. package/src/planning/planner.ts +478 -2
  188. package/src/project/project-registry.ts +358 -0
  189. package/src/project/types.ts +31 -0
  190. package/src/runtime/admin-extra-ops.ts +307 -0
  191. package/src/runtime/admin-ops.ts +329 -0
  192. package/src/runtime/capture-ops.ts +385 -0
  193. package/src/runtime/core-ops.ts +535 -7
  194. package/src/runtime/curator-extra-ops.ts +71 -0
  195. package/src/runtime/domain-ops.ts +65 -13
  196. package/src/runtime/grading-ops.ts +121 -0
  197. package/src/runtime/loop-ops.ts +194 -0
  198. package/src/runtime/memory-cross-project-ops.ts +192 -0
  199. package/src/runtime/memory-extra-ops.ts +186 -0
  200. package/src/runtime/orchestrate-ops.ts +272 -0
  201. package/src/runtime/planning-extra-ops.ts +327 -0
  202. package/src/runtime/project-ops.ts +196 -0
  203. package/src/runtime/runtime.ts +49 -1
  204. package/src/runtime/types.ts +21 -0
  205. package/src/runtime/vault-extra-ops.ts +225 -0
  206. package/src/telemetry/telemetry.ts +118 -0
  207. package/src/vault/vault.ts +412 -1
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Admin / infrastructure operations — 8 ops for agent self-management.
3
+ *
4
+ * These ops let agents introspect their own health, configuration, and
5
+ * runtime state. No new modules needed — uses existing runtime parts.
6
+ */
7
+
8
+ import { readFileSync, statSync } from 'node:fs';
9
+ import { join, dirname } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import type { OpDefinition } from '../facades/types.js';
12
+ import type { AgentRuntime } from './types.js';
13
+
14
+ /**
15
+ * Resolve the @soleri/core package.json version.
16
+ * Walks up from this file's directory to find the closest package.json.
17
+ */
18
+ function getCoreVersion(): string {
19
+ try {
20
+ // __dirname equivalent for ESM
21
+ const thisDir = dirname(fileURLToPath(import.meta.url));
22
+ // Walk up until we find package.json
23
+ let dir = thisDir;
24
+ for (let i = 0; i < 5; i++) {
25
+ try {
26
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
27
+ return pkg.version ?? 'unknown';
28
+ } catch {
29
+ dir = dirname(dir);
30
+ }
31
+ }
32
+ } catch {
33
+ // Fallback — import.meta.url may not be available in some test envs
34
+ }
35
+ return 'unknown';
36
+ }
37
+
38
+ /**
39
+ * Create the 8 admin/infrastructure operations for an agent runtime.
40
+ *
41
+ * Groups: health (1), introspection (4), diagnostics (2), mutation (1).
42
+ */
43
+ export function createAdminOps(runtime: AgentRuntime): OpDefinition[] {
44
+ const { vault, brain, brainIntelligence, cognee, llmClient, keyPool, curator } = runtime;
45
+
46
+ return [
47
+ // ─── Health ──────────────────────────────────────────────────────
48
+ {
49
+ name: 'admin_health',
50
+ description: 'Comprehensive agent health check — vault, cognee, LLM, brain status.',
51
+ auth: 'read',
52
+ handler: async () => {
53
+ const vaultStats = vault.stats();
54
+ const cogneeStatus = cognee.getStatus();
55
+ const llmAvailable = llmClient.isAvailable();
56
+ const brainStats = brain.getStats();
57
+ const curatorStatus = curator.getStatus();
58
+
59
+ return {
60
+ status: 'ok',
61
+ vault: { entries: vaultStats.totalEntries, domains: Object.keys(vaultStats.byDomain) },
62
+ cognee: { available: cogneeStatus?.available ?? false },
63
+ llm: llmAvailable,
64
+ brain: { vocabularySize: brainStats.vocabularySize, feedbackCount: brainStats.feedbackCount },
65
+ curator: { initialized: curatorStatus.initialized },
66
+ };
67
+ },
68
+ },
69
+
70
+ // ─── Introspection ───────────────────────────────────────────────
71
+ {
72
+ name: 'admin_tool_list',
73
+ description: 'List all available ops with descriptions and auth levels.',
74
+ auth: 'read',
75
+ handler: async (params) => {
76
+ // The caller can pass in the full ops list via `_allOps` (injected by
77
+ // the facade builder). If not provided, we return only admin ops.
78
+ const allOps = params._allOps as Array<{ name: string; description: string; auth: string }> | undefined;
79
+ if (allOps) {
80
+ return {
81
+ count: allOps.length,
82
+ ops: allOps.map((op) => ({
83
+ name: op.name,
84
+ description: op.description,
85
+ auth: op.auth,
86
+ })),
87
+ };
88
+ }
89
+ // Fallback — just describe admin ops
90
+ return {
91
+ count: 8,
92
+ ops: [
93
+ { name: 'admin_health', auth: 'read' },
94
+ { name: 'admin_tool_list', auth: 'read' },
95
+ { name: 'admin_config', auth: 'read' },
96
+ { name: 'admin_vault_size', auth: 'read' },
97
+ { name: 'admin_uptime', auth: 'read' },
98
+ { name: 'admin_version', auth: 'read' },
99
+ { name: 'admin_reset_cache', auth: 'write' },
100
+ { name: 'admin_diagnostic', auth: 'read' },
101
+ ],
102
+ };
103
+ },
104
+ },
105
+ {
106
+ name: 'admin_config',
107
+ description: 'Get current runtime configuration — agentId, paths, log level.',
108
+ auth: 'read',
109
+ handler: async () => {
110
+ const { agentId, vaultPath, plansPath, dataDir, logLevel } = runtime.config;
111
+ return {
112
+ agentId,
113
+ vaultPath: vaultPath ?? null,
114
+ plansPath: plansPath ?? null,
115
+ dataDir: dataDir ?? null,
116
+ logLevel: logLevel ?? 'info',
117
+ };
118
+ },
119
+ },
120
+ {
121
+ name: 'admin_vault_size',
122
+ description: 'Get vault database file size on disk (bytes). Returns null for in-memory vaults.',
123
+ auth: 'read',
124
+ handler: async () => {
125
+ const dbPath = runtime.config.vaultPath;
126
+ if (!dbPath || dbPath === ':memory:') {
127
+ return { path: ':memory:', sizeBytes: null, sizeHuman: 'in-memory' };
128
+ }
129
+ try {
130
+ const stat = statSync(dbPath);
131
+ const sizeBytes = stat.size;
132
+ const sizeHuman = formatBytes(sizeBytes);
133
+ return { path: dbPath, sizeBytes, sizeHuman };
134
+ } catch {
135
+ return { path: dbPath, sizeBytes: null, sizeHuman: 'file not found' };
136
+ }
137
+ },
138
+ },
139
+ {
140
+ name: 'admin_uptime',
141
+ description: 'Time since runtime creation — seconds and human-readable.',
142
+ auth: 'read',
143
+ handler: async () => {
144
+ const uptimeMs = Date.now() - runtime.createdAt;
145
+ const uptimeSec = Math.floor(uptimeMs / 1000);
146
+ return {
147
+ createdAt: new Date(runtime.createdAt).toISOString(),
148
+ uptimeMs,
149
+ uptimeSec,
150
+ uptimeHuman: formatUptime(uptimeSec),
151
+ };
152
+ },
153
+ },
154
+ {
155
+ name: 'admin_version',
156
+ description: 'Package version info for @soleri/core and Node.js.',
157
+ auth: 'read',
158
+ handler: async () => {
159
+ return {
160
+ core: getCoreVersion(),
161
+ node: process.version,
162
+ platform: process.platform,
163
+ arch: process.arch,
164
+ };
165
+ },
166
+ },
167
+
168
+ // ─── Mutation ────────────────────────────────────────────────────
169
+ {
170
+ name: 'admin_reset_cache',
171
+ description: 'Clear all caches — brain vocabulary and cognee health cache. Forces fresh data on next access.',
172
+ auth: 'write',
173
+ handler: async () => {
174
+ // Rebuild brain vocabulary (clears old TF-IDF state, rebuilds from vault)
175
+ brain.rebuildVocabulary();
176
+
177
+ // Reset cognee health cache by checking health again (no direct reset method)
178
+ // The next isAvailable check will need a fresh health probe
179
+ const cogneeHealth = await cognee.healthCheck().catch(() => null);
180
+
181
+ return {
182
+ cleared: ['brain_vocabulary', 'cognee_health_cache'],
183
+ brainVocabularySize: brain.getStats().vocabularySize,
184
+ cogneeAvailable: cogneeHealth?.available ?? false,
185
+ };
186
+ },
187
+ },
188
+
189
+ // ─── Diagnostics ─────────────────────────────────────────────────
190
+ {
191
+ name: 'admin_diagnostic',
192
+ description: 'Run diagnostic checks and return a comprehensive report.',
193
+ auth: 'read',
194
+ handler: async () => {
195
+ const checks: Array<{ name: string; status: 'ok' | 'warn' | 'error'; detail: string }> = [];
196
+
197
+ // 1. Vault connectivity
198
+ try {
199
+ const stats = vault.stats();
200
+ checks.push({
201
+ name: 'vault',
202
+ status: 'ok',
203
+ detail: `${stats.totalEntries} entries across ${Object.keys(stats.byDomain).length} domains`,
204
+ });
205
+ } catch (err) {
206
+ checks.push({
207
+ name: 'vault',
208
+ status: 'error',
209
+ detail: err instanceof Error ? err.message : String(err),
210
+ });
211
+ }
212
+
213
+ // 2. Brain vocabulary
214
+ try {
215
+ const brainStats = brain.getStats();
216
+ const status = brainStats.vocabularySize > 0 ? 'ok' : 'warn';
217
+ checks.push({
218
+ name: 'brain_vocabulary',
219
+ status,
220
+ detail: `${brainStats.vocabularySize} terms, ${brainStats.feedbackCount} feedback entries`,
221
+ });
222
+ } catch (err) {
223
+ checks.push({
224
+ name: 'brain_vocabulary',
225
+ status: 'error',
226
+ detail: err instanceof Error ? err.message : String(err),
227
+ });
228
+ }
229
+
230
+ // 3. Brain intelligence
231
+ try {
232
+ const intStats = brainIntelligence.getStats();
233
+ checks.push({
234
+ name: 'brain_intelligence',
235
+ status: 'ok',
236
+ detail: `${intStats.strengths} strengths, ${intStats.sessions} sessions`,
237
+ });
238
+ } catch (err) {
239
+ checks.push({
240
+ name: 'brain_intelligence',
241
+ status: 'error',
242
+ detail: err instanceof Error ? err.message : String(err),
243
+ });
244
+ }
245
+
246
+ // 4. Cognee availability
247
+ try {
248
+ const cogneeStatus = cognee.getStatus();
249
+ if (cogneeStatus?.available) {
250
+ checks.push({ name: 'cognee', status: 'ok', detail: `Connected to ${cogneeStatus.url}` });
251
+ } else {
252
+ checks.push({
253
+ name: 'cognee',
254
+ status: 'warn',
255
+ detail: cogneeStatus?.error ?? 'Not connected (no health check yet)',
256
+ });
257
+ }
258
+ } catch (err) {
259
+ checks.push({
260
+ name: 'cognee',
261
+ status: 'error',
262
+ detail: err instanceof Error ? err.message : String(err),
263
+ });
264
+ }
265
+
266
+ // 5. LLM key pools
267
+ const llmStatus = llmClient.isAvailable();
268
+ checks.push({
269
+ name: 'llm_openai',
270
+ status: llmStatus.openai ? 'ok' : 'warn',
271
+ detail: llmStatus.openai ? 'Keys available' : 'No keys configured',
272
+ });
273
+ checks.push({
274
+ name: 'llm_anthropic',
275
+ status: llmStatus.anthropic ? 'ok' : 'warn',
276
+ detail: llmStatus.anthropic ? 'Keys available' : 'No keys configured',
277
+ });
278
+
279
+ // 6. Curator
280
+ try {
281
+ const curatorStatus = curator.getStatus();
282
+ checks.push({
283
+ name: 'curator',
284
+ status: curatorStatus.initialized ? 'ok' : 'error',
285
+ detail: curatorStatus.initialized ? 'Initialized' : 'Not initialized',
286
+ });
287
+ } catch (err) {
288
+ checks.push({
289
+ name: 'curator',
290
+ status: 'error',
291
+ detail: err instanceof Error ? err.message : String(err),
292
+ });
293
+ }
294
+
295
+ const errorCount = checks.filter((c) => c.status === 'error').length;
296
+ const warnCount = checks.filter((c) => c.status === 'warn').length;
297
+ const overall = errorCount > 0 ? 'unhealthy' : warnCount > 0 ? 'degraded' : 'healthy';
298
+
299
+ return {
300
+ overall,
301
+ checks,
302
+ summary: `${checks.length} checks: ${checks.length - errorCount - warnCount} ok, ${warnCount} warn, ${errorCount} error`,
303
+ };
304
+ },
305
+ },
306
+ ];
307
+ }
308
+
309
+ // ─── Helpers ────────────────────────────────────────────────────────
310
+
311
+ function formatBytes(bytes: number): string {
312
+ if (bytes < 1024) return `${bytes} B`;
313
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
314
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
315
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
316
+ }
317
+
318
+ function formatUptime(seconds: number): string {
319
+ if (seconds < 60) return `${seconds}s`;
320
+ const minutes = Math.floor(seconds / 60);
321
+ const secs = seconds % 60;
322
+ if (minutes < 60) return `${minutes}m ${secs}s`;
323
+ const hours = Math.floor(minutes / 60);
324
+ const mins = minutes % 60;
325
+ if (hours < 24) return `${hours}h ${mins}m`;
326
+ const days = Math.floor(hours / 24);
327
+ const hrs = hours % 24;
328
+ return `${days}d ${hrs}h`;
329
+ }