@kb-labs/studio-data-client 0.2.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.
- package/README.md +210 -0
- package/dist/index.d.ts +2755 -0
- package/dist/index.js +3961 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3961 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { ulid } from 'ulid';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
|
5
|
+
import { useMemo, useState, useEffect, useRef, useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
// src/errors/kb-error.ts
|
|
8
|
+
var KBError = class extends Error {
|
|
9
|
+
constructor(code, message, status, cause) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.message = message;
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.cause = cause;
|
|
15
|
+
this.name = "KBError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var errorCodes = {
|
|
19
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
20
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
21
|
+
AUTH_ERROR: "AUTH_ERROR",
|
|
22
|
+
NOT_FOUND: "NOT_FOUND",
|
|
23
|
+
SERVER_ERROR: "SERVER_ERROR",
|
|
24
|
+
TIMEOUT_ERROR: "TIMEOUT_ERROR",
|
|
25
|
+
TIMEOUT: "TIMEOUT",
|
|
26
|
+
CONFLICT: "CONFLICT",
|
|
27
|
+
RATE_LIMIT: "RATE_LIMIT",
|
|
28
|
+
TOOL_ERROR: "TOOL_ERROR"
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/client/error-mapper.ts
|
|
32
|
+
function isErrorEnvelopeResponse(obj) {
|
|
33
|
+
return typeof obj === "object" && obj !== null && "ok" in obj && obj.ok === false && "error" in obj && typeof obj.error === "object" && obj.error !== null && "message" in obj.error;
|
|
34
|
+
}
|
|
35
|
+
function mapFetchError(error, response) {
|
|
36
|
+
if (!response) {
|
|
37
|
+
if (error instanceof KBError) {
|
|
38
|
+
return error;
|
|
39
|
+
}
|
|
40
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
41
|
+
return new KBError(errorCodes.NETWORK_ERROR, "Network request failed", void 0, error);
|
|
42
|
+
}
|
|
43
|
+
return new KBError(errorCodes.NETWORK_ERROR, "Unknown error occurred", void 0, error);
|
|
44
|
+
}
|
|
45
|
+
const parsedBody = error;
|
|
46
|
+
const errorEnvelope = parsedBody && typeof parsedBody === "object" && isErrorEnvelopeResponse(parsedBody) ? parsedBody : null;
|
|
47
|
+
const httpError = {
|
|
48
|
+
status: response.status,
|
|
49
|
+
statusText: response.statusText,
|
|
50
|
+
data: parsedBody ?? null
|
|
51
|
+
};
|
|
52
|
+
const statusCode = response.status;
|
|
53
|
+
let errorMessage = void 0;
|
|
54
|
+
if (errorEnvelope !== null) {
|
|
55
|
+
const envelope = errorEnvelope;
|
|
56
|
+
errorMessage = envelope.error.message;
|
|
57
|
+
}
|
|
58
|
+
if (statusCode === 404) {
|
|
59
|
+
return new KBError(errorCodes.NOT_FOUND, errorMessage || "Resource not found", 404, httpError);
|
|
60
|
+
}
|
|
61
|
+
if (statusCode === 401) {
|
|
62
|
+
return new KBError(errorCodes.AUTH_ERROR, errorMessage || "Authentication failed", 401, httpError);
|
|
63
|
+
}
|
|
64
|
+
if (statusCode === 403) {
|
|
65
|
+
return new KBError(errorCodes.AUTH_ERROR, errorMessage || "Forbidden", 403, httpError);
|
|
66
|
+
}
|
|
67
|
+
if (statusCode === 409) {
|
|
68
|
+
return new KBError(errorCodes.CONFLICT, errorMessage || "Conflict", 409, httpError);
|
|
69
|
+
}
|
|
70
|
+
if (statusCode === 429) {
|
|
71
|
+
return new KBError(errorCodes.RATE_LIMIT, errorMessage || "Rate limit exceeded", 429, httpError);
|
|
72
|
+
}
|
|
73
|
+
if (statusCode >= 500) {
|
|
74
|
+
return new KBError(errorCodes.SERVER_ERROR, errorMessage || "Server error", statusCode, httpError);
|
|
75
|
+
}
|
|
76
|
+
return new KBError(errorCodes.NETWORK_ERROR, errorMessage || httpError.statusText || "Request failed", statusCode, httpError);
|
|
77
|
+
}
|
|
78
|
+
function mapErrorEnvelope(envelope) {
|
|
79
|
+
const httpError = {
|
|
80
|
+
status: 400,
|
|
81
|
+
// Default status
|
|
82
|
+
statusText: envelope.error.message,
|
|
83
|
+
data: envelope
|
|
84
|
+
};
|
|
85
|
+
const errorCode = envelope.error.code;
|
|
86
|
+
if (errorCode === "E_NOT_FOUND") {
|
|
87
|
+
return new KBError(errorCodes.NOT_FOUND, envelope.error.message, 404, httpError);
|
|
88
|
+
}
|
|
89
|
+
if (errorCode === "E_UNAUTHORIZED") {
|
|
90
|
+
return new KBError(errorCodes.AUTH_ERROR, envelope.error.message, 401, httpError);
|
|
91
|
+
}
|
|
92
|
+
if (errorCode === "E_FORBIDDEN") {
|
|
93
|
+
return new KBError(errorCodes.AUTH_ERROR, envelope.error.message, 403, httpError);
|
|
94
|
+
}
|
|
95
|
+
if (errorCode === "E_CONFLICT") {
|
|
96
|
+
return new KBError(errorCodes.CONFLICT, envelope.error.message, 409, httpError);
|
|
97
|
+
}
|
|
98
|
+
if (errorCode === "E_RATE_LIMIT") {
|
|
99
|
+
return new KBError(errorCodes.RATE_LIMIT, envelope.error.message, 429, httpError);
|
|
100
|
+
}
|
|
101
|
+
if (errorCode === "E_TIMEOUT") {
|
|
102
|
+
return new KBError(errorCodes.TIMEOUT, envelope.error.message, 408, httpError);
|
|
103
|
+
}
|
|
104
|
+
if (errorCode.startsWith("E_TOOL_")) {
|
|
105
|
+
return new KBError(errorCodes.TOOL_ERROR, envelope.error.message, 500, httpError);
|
|
106
|
+
}
|
|
107
|
+
return new KBError(errorCodes.SERVER_ERROR, envelope.error.message, 500, httpError);
|
|
108
|
+
}
|
|
109
|
+
var HttpClient = class {
|
|
110
|
+
client;
|
|
111
|
+
requestInterceptors = [];
|
|
112
|
+
responseInterceptors = [];
|
|
113
|
+
errorInterceptors = [];
|
|
114
|
+
baseUrl;
|
|
115
|
+
constructor(baseUrl = "", token) {
|
|
116
|
+
this.baseUrl = baseUrl;
|
|
117
|
+
this.client = axios.create({
|
|
118
|
+
baseURL: baseUrl,
|
|
119
|
+
headers: {
|
|
120
|
+
"Content-Type": "application/json",
|
|
121
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
this.client.interceptors.request.use((config) => {
|
|
125
|
+
if (!config.headers["X-Request-Id"]) {
|
|
126
|
+
config.headers["X-Request-Id"] = ulid();
|
|
127
|
+
}
|
|
128
|
+
return config;
|
|
129
|
+
});
|
|
130
|
+
this.client.interceptors.response.use(
|
|
131
|
+
(response) => {
|
|
132
|
+
const data = response.data;
|
|
133
|
+
if (data && typeof data === "object" && "ok" in data) {
|
|
134
|
+
if (data.ok === true && "data" in data) {
|
|
135
|
+
response.data = data.data;
|
|
136
|
+
} else if (data.ok === false && "error" in data) {
|
|
137
|
+
const error = mapFetchError(data.error, {
|
|
138
|
+
status: response.status,
|
|
139
|
+
statusText: response.statusText
|
|
140
|
+
});
|
|
141
|
+
return Promise.reject(error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return response;
|
|
145
|
+
},
|
|
146
|
+
(error) => {
|
|
147
|
+
const kbError = mapFetchError(error.response?.data, {
|
|
148
|
+
status: error.response?.status ?? 500,
|
|
149
|
+
statusText: error.response?.statusText ?? "Unknown Error"
|
|
150
|
+
});
|
|
151
|
+
return Promise.reject(kbError);
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
addRequestInterceptor(interceptor) {
|
|
156
|
+
this.requestInterceptors.push(interceptor);
|
|
157
|
+
}
|
|
158
|
+
addResponseInterceptor(interceptor) {
|
|
159
|
+
this.responseInterceptors.push(interceptor);
|
|
160
|
+
}
|
|
161
|
+
addErrorInterceptor(interceptor) {
|
|
162
|
+
this.errorInterceptors.push(interceptor);
|
|
163
|
+
}
|
|
164
|
+
getBaseUrl() {
|
|
165
|
+
return this.baseUrl;
|
|
166
|
+
}
|
|
167
|
+
async fetch(path, options = {}) {
|
|
168
|
+
try {
|
|
169
|
+
const config = {
|
|
170
|
+
url: path,
|
|
171
|
+
...options
|
|
172
|
+
};
|
|
173
|
+
const response = await this.client.request(config);
|
|
174
|
+
return response.data;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
const kbError = error instanceof KBError ? error : mapFetchError(error);
|
|
177
|
+
throw await this.processError(kbError);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async processError(error) {
|
|
181
|
+
let processedError = error;
|
|
182
|
+
for (const interceptor of this.errorInterceptors) {
|
|
183
|
+
processedError = await interceptor(processedError);
|
|
184
|
+
}
|
|
185
|
+
throw processedError;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/client/envelope-interceptor.ts
|
|
190
|
+
function createEnvelopeInterceptor() {
|
|
191
|
+
return async (response) => {
|
|
192
|
+
const contentType = response.headers.get("content-type");
|
|
193
|
+
if (!contentType?.includes("application/json")) {
|
|
194
|
+
return response;
|
|
195
|
+
}
|
|
196
|
+
if (contentType.includes("text/event-stream")) {
|
|
197
|
+
return response;
|
|
198
|
+
}
|
|
199
|
+
const clonedResponse = response.clone();
|
|
200
|
+
try {
|
|
201
|
+
const envelope = await clonedResponse.json();
|
|
202
|
+
if (envelope && typeof envelope === "object" && "ok" in envelope) {
|
|
203
|
+
if (envelope.ok === true && "data" in envelope) {
|
|
204
|
+
const unwrappedData = envelope.data;
|
|
205
|
+
const headers = new Headers(response.headers);
|
|
206
|
+
headers.set("Content-Type", "application/json");
|
|
207
|
+
return new Response(JSON.stringify(unwrappedData), {
|
|
208
|
+
status: response.status,
|
|
209
|
+
statusText: response.statusText,
|
|
210
|
+
headers
|
|
211
|
+
});
|
|
212
|
+
} else if (envelope.ok === false && "error" in envelope) {
|
|
213
|
+
return response;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return response;
|
|
217
|
+
} catch (_error) {
|
|
218
|
+
return response;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function extractEnvelopeMeta(response) {
|
|
223
|
+
try {
|
|
224
|
+
const requestId = response.headers.get("X-Request-Id") || void 0;
|
|
225
|
+
const apiVersion = response.headers.get("X-Schema-Version") || response.headers.get("x-schema-version") || void 0;
|
|
226
|
+
return {
|
|
227
|
+
requestId: requestId || void 0,
|
|
228
|
+
apiVersion: apiVersion || void 0
|
|
229
|
+
};
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/contracts/common.ts
|
|
236
|
+
var SCHEMA_VERSION = "1.0";
|
|
237
|
+
var idSchema = z.string();
|
|
238
|
+
var isoDateSchema = z.string().datetime();
|
|
239
|
+
var packageRefSchema = z.object({
|
|
240
|
+
name: z.string(),
|
|
241
|
+
version: z.string().optional(),
|
|
242
|
+
private: z.boolean().optional(),
|
|
243
|
+
path: z.string().optional()
|
|
244
|
+
});
|
|
245
|
+
var runRefSchema = z.object({
|
|
246
|
+
id: idSchema,
|
|
247
|
+
startedAt: isoDateSchema,
|
|
248
|
+
endedAt: isoDateSchema.optional(),
|
|
249
|
+
status: z.enum(["pending", "ok", "warn", "fail"])
|
|
250
|
+
});
|
|
251
|
+
var actionResultSchema = z.object({
|
|
252
|
+
ok: z.boolean(),
|
|
253
|
+
message: z.string().optional(),
|
|
254
|
+
runId: idSchema.optional()
|
|
255
|
+
});
|
|
256
|
+
var auditSummarySchema = z.object({
|
|
257
|
+
ts: isoDateSchema,
|
|
258
|
+
totals: z.object({
|
|
259
|
+
packages: z.number(),
|
|
260
|
+
ok: z.number(),
|
|
261
|
+
warn: z.number(),
|
|
262
|
+
fail: z.number(),
|
|
263
|
+
durationMs: z.number()
|
|
264
|
+
}),
|
|
265
|
+
topFailures: z.array(
|
|
266
|
+
z.object({
|
|
267
|
+
pkg: z.string(),
|
|
268
|
+
checks: z.array(z.enum(["style", "types", "tests", "build", "devlink", "mind"]))
|
|
269
|
+
})
|
|
270
|
+
)
|
|
271
|
+
});
|
|
272
|
+
var auditCheckSchema = z.object({
|
|
273
|
+
id: z.enum(["style", "types", "tests", "build", "devlink", "mind"]),
|
|
274
|
+
ok: z.boolean(),
|
|
275
|
+
errors: z.number().optional(),
|
|
276
|
+
warnings: z.number().optional(),
|
|
277
|
+
meta: z.unknown().optional()
|
|
278
|
+
});
|
|
279
|
+
var auditPackageReportSchema = z.object({
|
|
280
|
+
pkg: packageRefSchema,
|
|
281
|
+
lastRun: runRefSchema,
|
|
282
|
+
checks: z.array(auditCheckSchema),
|
|
283
|
+
artifacts: z.object({
|
|
284
|
+
json: z.string().optional(),
|
|
285
|
+
md: z.string().optional(),
|
|
286
|
+
txt: z.string().optional(),
|
|
287
|
+
html: z.string().optional()
|
|
288
|
+
})
|
|
289
|
+
});
|
|
290
|
+
var releasePreviewSchema = z.object({
|
|
291
|
+
range: z.object({
|
|
292
|
+
from: z.string(),
|
|
293
|
+
to: z.string()
|
|
294
|
+
}),
|
|
295
|
+
packages: z.array(
|
|
296
|
+
z.object({
|
|
297
|
+
name: z.string(),
|
|
298
|
+
prev: z.string(),
|
|
299
|
+
next: z.string(),
|
|
300
|
+
bump: z.enum(["major", "minor", "patch", "none"]),
|
|
301
|
+
breaking: z.number().optional()
|
|
302
|
+
})
|
|
303
|
+
),
|
|
304
|
+
manifestJson: z.string().optional(),
|
|
305
|
+
markdown: z.string().optional()
|
|
306
|
+
});
|
|
307
|
+
var healthStatusSchema = z.object({
|
|
308
|
+
ok: z.boolean(),
|
|
309
|
+
timestamp: isoDateSchema,
|
|
310
|
+
sources: z.array(
|
|
311
|
+
z.object({
|
|
312
|
+
name: z.string(),
|
|
313
|
+
ok: z.boolean(),
|
|
314
|
+
latency: z.number().optional(),
|
|
315
|
+
error: z.string().optional()
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// src/sources/http-system-source.ts
|
|
321
|
+
var HttpSystemSource = class {
|
|
322
|
+
constructor(client) {
|
|
323
|
+
this.client = client;
|
|
324
|
+
}
|
|
325
|
+
async getHealth() {
|
|
326
|
+
const snapshot = await this.client.fetch("/observability/health");
|
|
327
|
+
const degraded = snapshot.status === "degraded" || snapshot.state === "partial_observability";
|
|
328
|
+
return {
|
|
329
|
+
ok: snapshot.status === "healthy",
|
|
330
|
+
timestamp: snapshot.observedAt,
|
|
331
|
+
sources: snapshot.checks.length > 0 ? snapshot.checks.map((check) => ({
|
|
332
|
+
name: check.id,
|
|
333
|
+
ok: check.status === "ok",
|
|
334
|
+
latency: check.latencyMs,
|
|
335
|
+
error: check.status === "warn" ? "system_degraded" : check.status === "error" ? check.message ?? "system_error" : void 0
|
|
336
|
+
})) : [
|
|
337
|
+
{
|
|
338
|
+
name: "system",
|
|
339
|
+
ok: snapshot.status === "healthy",
|
|
340
|
+
error: degraded ? "system_degraded" : void 0
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
snapshot
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get ready status
|
|
348
|
+
*/
|
|
349
|
+
async getReady() {
|
|
350
|
+
try {
|
|
351
|
+
return this.client.fetch("/ready");
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (error instanceof KBError && error.status === 503) {
|
|
354
|
+
const payload = error.cause?.data;
|
|
355
|
+
if (payload && typeof payload === "object" && "ready" in payload) {
|
|
356
|
+
return payload;
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
ready: false,
|
|
360
|
+
reason: "unknown"
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get info
|
|
368
|
+
*/
|
|
369
|
+
async getInfo() {
|
|
370
|
+
const response = await this.client.fetch("/info");
|
|
371
|
+
return response.data;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get capabilities
|
|
375
|
+
*/
|
|
376
|
+
async getCapabilities() {
|
|
377
|
+
const response = await this.client.fetch("/info/capabilities");
|
|
378
|
+
return response.data;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get config (redacted)
|
|
382
|
+
*/
|
|
383
|
+
async getConfig() {
|
|
384
|
+
const response = await this.client.fetch("/info/config");
|
|
385
|
+
return response.data;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get all registered API routes
|
|
389
|
+
*/
|
|
390
|
+
async getRoutes() {
|
|
391
|
+
return this.client.fetch("/routes");
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get the base URL of the API
|
|
395
|
+
*/
|
|
396
|
+
getBaseUrl() {
|
|
397
|
+
return this.client.getBaseUrl();
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// src/sources/http-workflow-source.ts
|
|
402
|
+
function buildQuery(params) {
|
|
403
|
+
if (!params || Object.keys(params).length === 0) {
|
|
404
|
+
return "";
|
|
405
|
+
}
|
|
406
|
+
const query = new URLSearchParams();
|
|
407
|
+
if (params.status) {
|
|
408
|
+
query.set("status", params.status);
|
|
409
|
+
}
|
|
410
|
+
if (typeof params.limit === "number" && !Number.isNaN(params.limit)) {
|
|
411
|
+
query.set("limit", String(params.limit));
|
|
412
|
+
}
|
|
413
|
+
const qs = query.toString();
|
|
414
|
+
return qs ? `?${qs}` : "";
|
|
415
|
+
}
|
|
416
|
+
var HttpWorkflowSource = class {
|
|
417
|
+
constructor(client) {
|
|
418
|
+
this.client = client;
|
|
419
|
+
}
|
|
420
|
+
async listRuns(filters) {
|
|
421
|
+
const query = buildQuery(filters);
|
|
422
|
+
return this.client.fetch(`/plugins/workflow/runs${query}`);
|
|
423
|
+
}
|
|
424
|
+
async getRun(runId) {
|
|
425
|
+
try {
|
|
426
|
+
const response = await this.client.fetch(
|
|
427
|
+
`/plugins/workflow/runs/${encodeURIComponent(runId)}`
|
|
428
|
+
);
|
|
429
|
+
return response.run;
|
|
430
|
+
} catch (error) {
|
|
431
|
+
if (error instanceof KBError && error.status === 404) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async cancelRun(runId) {
|
|
438
|
+
await this.client.fetch(
|
|
439
|
+
`/plugins/workflow/workflows/runs/${encodeURIComponent(runId)}/cancel`,
|
|
440
|
+
{ method: "POST" }
|
|
441
|
+
);
|
|
442
|
+
const run = await this.getRun(runId);
|
|
443
|
+
if (!run) {
|
|
444
|
+
return { id: runId, status: "cancelled" };
|
|
445
|
+
}
|
|
446
|
+
return run;
|
|
447
|
+
}
|
|
448
|
+
async runWorkflow(params) {
|
|
449
|
+
const response = await this.client.fetch(
|
|
450
|
+
`/plugins/workflow/workflows/${encodeURIComponent(params.spec.name)}/run`,
|
|
451
|
+
{
|
|
452
|
+
method: "POST",
|
|
453
|
+
data: {
|
|
454
|
+
input: params.metadata
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
return response.run;
|
|
459
|
+
}
|
|
460
|
+
async listEvents(runId, options = {}) {
|
|
461
|
+
const params = new URLSearchParams();
|
|
462
|
+
if (options.cursor) {
|
|
463
|
+
params.set("cursor", options.cursor);
|
|
464
|
+
}
|
|
465
|
+
if (typeof options.limit === "number" && !Number.isNaN(options.limit)) {
|
|
466
|
+
params.set("limit", String(options.limit));
|
|
467
|
+
}
|
|
468
|
+
const query = params.toString();
|
|
469
|
+
return this.client.fetch(
|
|
470
|
+
`/workflows/runs/${runId}/events${query ? `?${query}` : ""}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
// 🆕 NEW methods for UI
|
|
474
|
+
async getStats() {
|
|
475
|
+
return this.client.fetch("/plugins/workflow/stats");
|
|
476
|
+
}
|
|
477
|
+
async listWorkflows(filters) {
|
|
478
|
+
const params = new URLSearchParams();
|
|
479
|
+
if (typeof filters?.limit === "number" && !Number.isNaN(filters.limit)) {
|
|
480
|
+
params.set("limit", String(filters.limit));
|
|
481
|
+
}
|
|
482
|
+
const query = params.toString();
|
|
483
|
+
return this.client.fetch(
|
|
484
|
+
`/plugins/workflow/workflows${query ? `?${query}` : ""}`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
async getWorkflow(workflowId) {
|
|
488
|
+
try {
|
|
489
|
+
return await this.client.fetch(
|
|
490
|
+
`/plugins/workflow/workflows/${encodeURIComponent(workflowId)}`
|
|
491
|
+
);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
if (error instanceof KBError && error.status === 404) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async runWorkflowById(workflowId, input) {
|
|
500
|
+
return this.client.fetch(
|
|
501
|
+
`/plugins/workflow/workflows/${encodeURIComponent(workflowId)}/run`,
|
|
502
|
+
{
|
|
503
|
+
method: "POST",
|
|
504
|
+
data: { input }
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
async listJobs(filters) {
|
|
509
|
+
const params = new URLSearchParams();
|
|
510
|
+
if (filters?.type) {
|
|
511
|
+
params.set("type", filters.type);
|
|
512
|
+
}
|
|
513
|
+
if (filters?.status) {
|
|
514
|
+
params.set("status", filters.status);
|
|
515
|
+
}
|
|
516
|
+
if (typeof filters?.limit === "number" && !Number.isNaN(filters.limit)) {
|
|
517
|
+
params.set("limit", String(filters.limit));
|
|
518
|
+
}
|
|
519
|
+
if (typeof filters?.offset === "number" && !Number.isNaN(filters.offset)) {
|
|
520
|
+
params.set("offset", String(filters.offset));
|
|
521
|
+
}
|
|
522
|
+
const query = params.toString();
|
|
523
|
+
return this.client.fetch(
|
|
524
|
+
`/plugins/workflow/jobs${query ? `?${query}` : ""}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
async getJob(jobId) {
|
|
528
|
+
try {
|
|
529
|
+
return await this.client.fetch(
|
|
530
|
+
`/plugins/workflow/jobs/${encodeURIComponent(jobId)}`
|
|
531
|
+
);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (error instanceof KBError && error.status === 404) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async getJobSteps(jobId) {
|
|
540
|
+
return this.client.fetch(
|
|
541
|
+
`/plugins/workflow/jobs/${encodeURIComponent(jobId)}/steps`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
async getJobLogs(jobId, filters) {
|
|
545
|
+
const params = new URLSearchParams();
|
|
546
|
+
if (typeof filters?.limit === "number" && !Number.isNaN(filters.limit)) {
|
|
547
|
+
params.set("limit", String(filters.limit));
|
|
548
|
+
}
|
|
549
|
+
if (typeof filters?.offset === "number" && !Number.isNaN(filters.offset)) {
|
|
550
|
+
params.set("offset", String(filters.offset));
|
|
551
|
+
}
|
|
552
|
+
if (filters?.level) {
|
|
553
|
+
params.set("level", filters.level);
|
|
554
|
+
}
|
|
555
|
+
const query = params.toString();
|
|
556
|
+
return this.client.fetch(
|
|
557
|
+
`/plugins/workflow/jobs/${encodeURIComponent(jobId)}/logs${query ? `?${query}` : ""}`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
async listCronJobs() {
|
|
561
|
+
return this.client.fetch("/plugins/workflow/cron");
|
|
562
|
+
}
|
|
563
|
+
async getPendingApprovals(runId) {
|
|
564
|
+
return this.client.fetch(
|
|
565
|
+
`/plugins/workflow/runs/${encodeURIComponent(runId)}/pending-approvals`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
async resolveApproval(params) {
|
|
569
|
+
const { runId, ...body } = params;
|
|
570
|
+
return this.client.fetch(
|
|
571
|
+
`/plugins/workflow/runs/${encodeURIComponent(runId)}/approve`,
|
|
572
|
+
{
|
|
573
|
+
method: "POST",
|
|
574
|
+
data: body
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
async cancelWorkflowRun(runId) {
|
|
579
|
+
return this.client.fetch(
|
|
580
|
+
`/plugins/workflow/workflows/runs/${encodeURIComponent(runId)}/cancel`,
|
|
581
|
+
{ method: "POST" }
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
async getWorkflowRuns(workflowId, filters) {
|
|
585
|
+
const params = new URLSearchParams();
|
|
586
|
+
if (typeof filters?.limit === "number" && !Number.isNaN(filters.limit)) {
|
|
587
|
+
params.set("limit", String(filters.limit));
|
|
588
|
+
}
|
|
589
|
+
if (typeof filters?.offset === "number" && !Number.isNaN(filters.offset)) {
|
|
590
|
+
params.set("offset", String(filters.offset));
|
|
591
|
+
}
|
|
592
|
+
if (filters?.status) {
|
|
593
|
+
params.set("status", filters.status);
|
|
594
|
+
}
|
|
595
|
+
const query = params.toString();
|
|
596
|
+
return this.client.fetch(
|
|
597
|
+
`/plugins/workflow/workflows/${encodeURIComponent(workflowId)}/runs${query ? `?${query}` : ""}`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// src/sources/http-cache-source.ts
|
|
603
|
+
var HttpCacheSource = class {
|
|
604
|
+
constructor(client) {
|
|
605
|
+
this.client = client;
|
|
606
|
+
}
|
|
607
|
+
async invalidateCache() {
|
|
608
|
+
return this.client.fetch("/cache/invalidate", {
|
|
609
|
+
method: "POST"
|
|
610
|
+
// No Content-Type header needed for empty body POST
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// src/sources/prometheus-metrics-parser.ts
|
|
616
|
+
function parseLabels(raw) {
|
|
617
|
+
if (!raw) {
|
|
618
|
+
return {};
|
|
619
|
+
}
|
|
620
|
+
const labels = {};
|
|
621
|
+
const pattern = /([a-zA-Z_][a-zA-Z0-9_]*)="((?:\\"|[^"])*)"/g;
|
|
622
|
+
for (const match of raw.matchAll(pattern)) {
|
|
623
|
+
const key = match[1];
|
|
624
|
+
if (!key) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
labels[key] = (match[2] ?? "").replace(/\\"/g, '"');
|
|
628
|
+
}
|
|
629
|
+
return labels;
|
|
630
|
+
}
|
|
631
|
+
function parseSamples(text) {
|
|
632
|
+
const samples = [];
|
|
633
|
+
for (const line of text.split("\n")) {
|
|
634
|
+
const trimmed = line.trim();
|
|
635
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const match = trimmed.match(/^([a-zA-Z_:][a-zA-Z0-9_:]*)(?:\{([^}]*)\})?\s+(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)$/);
|
|
639
|
+
if (!match) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
samples.push({
|
|
643
|
+
name: match[1],
|
|
644
|
+
labels: parseLabels(match[2]),
|
|
645
|
+
value: Number(match[3])
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
return samples;
|
|
649
|
+
}
|
|
650
|
+
function sumMetric(samples, metricName) {
|
|
651
|
+
return samples.filter((sample) => sample.name === metricName).reduce((sum, sample) => sum + sample.value, 0);
|
|
652
|
+
}
|
|
653
|
+
function latestMetric(samples, metricName) {
|
|
654
|
+
const metric = [...samples].reverse().find((sample) => sample.name === metricName);
|
|
655
|
+
return metric ? metric.value : null;
|
|
656
|
+
}
|
|
657
|
+
function buildHistogram(samples, bucketMetric) {
|
|
658
|
+
return samples.filter((sample) => sample.name === bucketMetric && sample.labels.le).map((sample) => ({
|
|
659
|
+
le: sample.labels.le === "+Inf" ? Number.POSITIVE_INFINITY : Number(sample.labels.le),
|
|
660
|
+
count: sample.value
|
|
661
|
+
})).sort((a, b) => a.le - b.le);
|
|
662
|
+
}
|
|
663
|
+
function interpolateQuantile(quantile, histogram) {
|
|
664
|
+
if (histogram.length === 0) {
|
|
665
|
+
return 0;
|
|
666
|
+
}
|
|
667
|
+
const total = histogram.find((point) => !Number.isFinite(point.le))?.count ?? histogram[histogram.length - 1].count;
|
|
668
|
+
if (total <= 0) {
|
|
669
|
+
return 0;
|
|
670
|
+
}
|
|
671
|
+
const target = total * quantile;
|
|
672
|
+
let previousCount = 0;
|
|
673
|
+
let previousLe = 0;
|
|
674
|
+
for (const point of histogram) {
|
|
675
|
+
if (point.count >= target) {
|
|
676
|
+
if (!Number.isFinite(point.le)) {
|
|
677
|
+
return previousLe;
|
|
678
|
+
}
|
|
679
|
+
const bucketCount = point.count - previousCount;
|
|
680
|
+
if (bucketCount <= 0) {
|
|
681
|
+
return point.le;
|
|
682
|
+
}
|
|
683
|
+
const progress = (target - previousCount) / bucketCount;
|
|
684
|
+
return previousLe + (point.le - previousLe) * progress;
|
|
685
|
+
}
|
|
686
|
+
previousCount = point.count;
|
|
687
|
+
if (Number.isFinite(point.le)) {
|
|
688
|
+
previousLe = point.le;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return previousLe;
|
|
692
|
+
}
|
|
693
|
+
function buildLatency(samples) {
|
|
694
|
+
const count = sumMetric(samples, "http_request_duration_ms_count");
|
|
695
|
+
const total = sumMetric(samples, "http_request_duration_ms_sum");
|
|
696
|
+
const histogram = buildHistogram(samples, "http_request_duration_ms_bucket");
|
|
697
|
+
const finiteBuckets = histogram.filter((point) => Number.isFinite(point.le) && point.count > 0);
|
|
698
|
+
return {
|
|
699
|
+
average: count > 0 ? total / count : 0,
|
|
700
|
+
min: finiteBuckets[0]?.le ?? 0,
|
|
701
|
+
max: finiteBuckets[finiteBuckets.length - 1]?.le ?? 0,
|
|
702
|
+
p50: interpolateQuantile(0.5, histogram),
|
|
703
|
+
p95: interpolateQuantile(0.95, histogram),
|
|
704
|
+
p99: interpolateQuantile(0.99, histogram)
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function groupByLabel(samples, metricName, label) {
|
|
708
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const sample of samples) {
|
|
710
|
+
if (sample.name !== metricName || !sample.labels[label]) {
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
const key = sample.labels[label];
|
|
714
|
+
const existing = grouped.get(key) ?? [];
|
|
715
|
+
existing.push(sample);
|
|
716
|
+
grouped.set(key, existing);
|
|
717
|
+
}
|
|
718
|
+
return grouped;
|
|
719
|
+
}
|
|
720
|
+
function buildPluginMetrics(samples) {
|
|
721
|
+
const groupedCounts = groupByLabel(samples, "kb_plugin_request_total", "plugin");
|
|
722
|
+
const durationSums = groupByLabel(samples, "kb_plugin_request_duration_ms_sum", "plugin");
|
|
723
|
+
const durationCounts = groupByLabel(samples, "kb_plugin_request_duration_ms_count", "plugin");
|
|
724
|
+
return Array.from(/* @__PURE__ */ new Set([
|
|
725
|
+
...groupedCounts.keys(),
|
|
726
|
+
...durationSums.keys(),
|
|
727
|
+
...durationCounts.keys()
|
|
728
|
+
])).map((pluginId) => {
|
|
729
|
+
const requests = (groupedCounts.get(pluginId) ?? []).reduce((sum, sample) => sum + sample.value, 0);
|
|
730
|
+
const errors = (groupedCounts.get(pluginId) ?? []).filter((sample) => Number(sample.labels.status_code ?? 0) >= 400).reduce((sum, sample) => sum + sample.value, 0);
|
|
731
|
+
const totalDuration = (durationSums.get(pluginId) ?? []).reduce((sum, sample) => sum + sample.value, 0);
|
|
732
|
+
const durationCount = (durationCounts.get(pluginId) ?? []).reduce((sum, sample) => sum + sample.value, 0);
|
|
733
|
+
return {
|
|
734
|
+
pluginId,
|
|
735
|
+
requests,
|
|
736
|
+
errors,
|
|
737
|
+
latency: {
|
|
738
|
+
average: durationCount > 0 ? totalDuration / durationCount : 0,
|
|
739
|
+
min: 0,
|
|
740
|
+
max: 0
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function buildTenantMetrics(samples) {
|
|
746
|
+
const groupedCounts = groupByLabel(samples, "kb_tenant_request_total", "tenant");
|
|
747
|
+
const errorCounts = groupByLabel(samples, "kb_tenant_request_errors_total", "tenant");
|
|
748
|
+
const durationSums = groupByLabel(samples, "kb_tenant_request_duration_ms_sum", "tenant");
|
|
749
|
+
const durationCounts = groupByLabel(samples, "kb_tenant_request_duration_ms_count", "tenant");
|
|
750
|
+
return Array.from(/* @__PURE__ */ new Set([
|
|
751
|
+
...groupedCounts.keys(),
|
|
752
|
+
...errorCounts.keys(),
|
|
753
|
+
...durationSums.keys(),
|
|
754
|
+
...durationCounts.keys()
|
|
755
|
+
])).map((tenantId) => {
|
|
756
|
+
const requests = (groupedCounts.get(tenantId) ?? []).reduce((sum, sample) => sum + sample.value, 0);
|
|
757
|
+
const errors = (errorCounts.get(tenantId) ?? []).reduce((sum, sample) => sum + sample.value, 0);
|
|
758
|
+
const totalDuration = (durationSums.get(tenantId) ?? []).reduce((sum, sample) => sum + sample.value, 0);
|
|
759
|
+
const durationCount = (durationCounts.get(tenantId) ?? []).reduce((sum, sample) => sum + sample.value, 0);
|
|
760
|
+
return {
|
|
761
|
+
tenantId,
|
|
762
|
+
requests,
|
|
763
|
+
errors,
|
|
764
|
+
latency: {
|
|
765
|
+
average: durationCount > 0 ? totalDuration / durationCount : 0
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
function buildErrorBreakdown(samples) {
|
|
771
|
+
const byStatusCode = {};
|
|
772
|
+
for (const sample of samples) {
|
|
773
|
+
if (sample.name !== "http_requests_total") {
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const statusCode = Number(sample.labels.status_code ?? 0);
|
|
777
|
+
if (statusCode < 400) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
byStatusCode[statusCode] = (byStatusCode[statusCode] ?? 0) + sample.value;
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
byStatusCode,
|
|
784
|
+
recent: []
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function parsePrometheusMetrics(text) {
|
|
788
|
+
const samples = parseSamples(text);
|
|
789
|
+
const totalRequests = sumMetric(samples, "http_requests_total");
|
|
790
|
+
const errors = buildErrorBreakdown(samples);
|
|
791
|
+
const clientErrors = Object.entries(errors.byStatusCode).filter(([code]) => Number(code) >= 400 && Number(code) < 500).reduce((sum, [, count]) => sum + count, 0);
|
|
792
|
+
const serverErrors = Object.entries(errors.byStatusCode).filter(([code]) => Number(code) >= 500).reduce((sum, [, count]) => sum + count, 0);
|
|
793
|
+
const pluginMountTotal = latestMetric(samples, "kb_plugins_mount_total");
|
|
794
|
+
const pluginMountSucceeded = latestMetric(samples, "kb_plugins_mount_succeeded");
|
|
795
|
+
const pluginMountFailed = latestMetric(samples, "kb_plugins_mount_failed");
|
|
796
|
+
const pluginMountElapsedMs = latestMetric(samples, "kb_plugins_mount_elapsed_ms");
|
|
797
|
+
const uptimeSeconds = latestMetric(samples, "process_uptime_seconds") ?? 0;
|
|
798
|
+
const redisHealthy = latestMetric(samples, "kb_redis_healthy");
|
|
799
|
+
const redisTransitions = groupByLabel(samples, "kb_redis_status_transitions_total", "state");
|
|
800
|
+
return {
|
|
801
|
+
requests: {
|
|
802
|
+
total: totalRequests,
|
|
803
|
+
success: Math.max(0, totalRequests - clientErrors - serverErrors),
|
|
804
|
+
clientErrors,
|
|
805
|
+
serverErrors
|
|
806
|
+
},
|
|
807
|
+
latency: buildLatency(samples),
|
|
808
|
+
perPlugin: buildPluginMetrics(samples),
|
|
809
|
+
perTenant: buildTenantMetrics(samples),
|
|
810
|
+
errors,
|
|
811
|
+
timestamps: {
|
|
812
|
+
startTime: Date.now() - uptimeSeconds * 1e3,
|
|
813
|
+
lastRequest: null
|
|
814
|
+
},
|
|
815
|
+
redis: {
|
|
816
|
+
updates: sumMetric(samples, "kb_redis_status_updates_total"),
|
|
817
|
+
healthyTransitions: (redisTransitions.get("healthy") ?? []).reduce((sum, sample) => sum + sample.value, 0),
|
|
818
|
+
unhealthyTransitions: (redisTransitions.get("unhealthy") ?? []).reduce((sum, sample) => sum + sample.value, 0),
|
|
819
|
+
lastStatus: redisHealthy === null ? null : {
|
|
820
|
+
healthy: redisHealthy >= 1,
|
|
821
|
+
state: redisHealthy >= 1 ? "healthy" : "unhealthy",
|
|
822
|
+
role: "unknown"
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
pluginMounts: pluginMountTotal === null ? null : {
|
|
826
|
+
total: pluginMountTotal,
|
|
827
|
+
succeeded: pluginMountSucceeded ?? 0,
|
|
828
|
+
failed: pluginMountFailed ?? 0,
|
|
829
|
+
elapsedMs: pluginMountElapsedMs ?? 0
|
|
830
|
+
},
|
|
831
|
+
uptime: {
|
|
832
|
+
seconds: uptimeSeconds,
|
|
833
|
+
startTime: new Date(Date.now() - uptimeSeconds * 1e3).toISOString(),
|
|
834
|
+
lastRequest: null
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/sources/http-observability-source.ts
|
|
840
|
+
var HttpObservabilitySource = class {
|
|
841
|
+
constructor(client) {
|
|
842
|
+
this.client = client;
|
|
843
|
+
}
|
|
844
|
+
async getStateBrokerStats() {
|
|
845
|
+
try {
|
|
846
|
+
return await this.client.fetch(
|
|
847
|
+
"/observability/state-broker"
|
|
848
|
+
);
|
|
849
|
+
} catch (error) {
|
|
850
|
+
throw new KBError(
|
|
851
|
+
"STATE_BROKER_FETCH_FAILED",
|
|
852
|
+
"Failed to fetch State Broker stats",
|
|
853
|
+
500,
|
|
854
|
+
error
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
async getDevKitHealth() {
|
|
859
|
+
try {
|
|
860
|
+
return await this.client.fetch(
|
|
861
|
+
"/observability/devkit"
|
|
862
|
+
);
|
|
863
|
+
} catch (error) {
|
|
864
|
+
throw new KBError(
|
|
865
|
+
"DEVKIT_FETCH_FAILED",
|
|
866
|
+
"Failed to fetch DevKit health",
|
|
867
|
+
500,
|
|
868
|
+
error
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
async getPrometheusMetrics() {
|
|
873
|
+
try {
|
|
874
|
+
const payload = await this.client.fetch(
|
|
875
|
+
"/metrics",
|
|
876
|
+
{ responseType: "text" }
|
|
877
|
+
);
|
|
878
|
+
return parsePrometheusMetrics(payload);
|
|
879
|
+
} catch (error) {
|
|
880
|
+
throw new KBError(
|
|
881
|
+
"PROMETHEUS_METRICS_FETCH_FAILED",
|
|
882
|
+
"Failed to fetch Prometheus metrics",
|
|
883
|
+
500,
|
|
884
|
+
error
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
subscribeToSystemEvents(onEvent, onError) {
|
|
889
|
+
const eventSource = new EventSource(`${this.client.getBaseUrl()}/events/registry`);
|
|
890
|
+
eventSource.addEventListener("registry", (e) => {
|
|
891
|
+
try {
|
|
892
|
+
const event = JSON.parse(e.data);
|
|
893
|
+
onEvent(event);
|
|
894
|
+
} catch (err) {
|
|
895
|
+
console.error("Failed to parse registry event:", err);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
eventSource.addEventListener("health", (e) => {
|
|
899
|
+
try {
|
|
900
|
+
const event = JSON.parse(e.data);
|
|
901
|
+
onEvent(event);
|
|
902
|
+
} catch (err) {
|
|
903
|
+
console.error("Failed to parse health event:", err);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
eventSource.onerror = () => {
|
|
907
|
+
onError(new Error("Connection to event stream failed"));
|
|
908
|
+
eventSource.close();
|
|
909
|
+
};
|
|
910
|
+
return () => {
|
|
911
|
+
eventSource.close();
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
async queryLogs(filters) {
|
|
915
|
+
try {
|
|
916
|
+
const params = new URLSearchParams();
|
|
917
|
+
if (filters.from) {
|
|
918
|
+
params.append("from", filters.from);
|
|
919
|
+
}
|
|
920
|
+
if (filters.to) {
|
|
921
|
+
params.append("to", filters.to);
|
|
922
|
+
}
|
|
923
|
+
if (filters.level) {
|
|
924
|
+
params.append("level", filters.level);
|
|
925
|
+
}
|
|
926
|
+
if (filters.plugin) {
|
|
927
|
+
params.append("plugin", filters.plugin);
|
|
928
|
+
}
|
|
929
|
+
if (filters.executionId) {
|
|
930
|
+
params.append("executionId", filters.executionId);
|
|
931
|
+
}
|
|
932
|
+
if (filters.tenantId) {
|
|
933
|
+
params.append("tenantId", filters.tenantId);
|
|
934
|
+
}
|
|
935
|
+
if (filters.search) {
|
|
936
|
+
params.append("search", filters.search);
|
|
937
|
+
}
|
|
938
|
+
if (filters.limit !== void 0) {
|
|
939
|
+
params.append("limit", String(filters.limit));
|
|
940
|
+
}
|
|
941
|
+
if (filters.offset !== void 0) {
|
|
942
|
+
params.append("offset", String(filters.offset));
|
|
943
|
+
}
|
|
944
|
+
const queryString = params.toString();
|
|
945
|
+
const url = queryString ? `/logs?${queryString}` : "/logs";
|
|
946
|
+
return await this.client.fetch(url);
|
|
947
|
+
} catch (error) {
|
|
948
|
+
throw new KBError(
|
|
949
|
+
"LOGS_QUERY_FAILED",
|
|
950
|
+
"Failed to query logs",
|
|
951
|
+
500,
|
|
952
|
+
error
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
subscribeToLogs(onLog, onError, filters) {
|
|
957
|
+
const params = new URLSearchParams();
|
|
958
|
+
if (filters?.level) {
|
|
959
|
+
params.append("level", filters.level);
|
|
960
|
+
}
|
|
961
|
+
if (filters?.plugin) {
|
|
962
|
+
params.append("plugin", filters.plugin);
|
|
963
|
+
}
|
|
964
|
+
if (filters?.executionId) {
|
|
965
|
+
params.append("executionId", filters.executionId);
|
|
966
|
+
}
|
|
967
|
+
if (filters?.tenantId) {
|
|
968
|
+
params.append("tenantId", filters.tenantId);
|
|
969
|
+
}
|
|
970
|
+
const queryString = params.toString();
|
|
971
|
+
const streamBase = this.client.getBaseUrl();
|
|
972
|
+
const url = queryString ? `${streamBase}/logs/stream?${queryString}` : `${streamBase}/logs/stream`;
|
|
973
|
+
const eventSource = new EventSource(url);
|
|
974
|
+
eventSource.addEventListener("log", (e) => {
|
|
975
|
+
try {
|
|
976
|
+
const log = JSON.parse(e.data);
|
|
977
|
+
onLog(log);
|
|
978
|
+
} catch (err) {
|
|
979
|
+
console.error("Failed to parse log event:", err);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
eventSource.onerror = () => {
|
|
983
|
+
onError(new Error("Connection to log stream failed"));
|
|
984
|
+
eventSource.close();
|
|
985
|
+
};
|
|
986
|
+
return () => {
|
|
987
|
+
eventSource.close();
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
async summarizeLogs(request) {
|
|
991
|
+
try {
|
|
992
|
+
return await this.client.fetch("/logs/summarize", {
|
|
993
|
+
method: "POST",
|
|
994
|
+
data: request
|
|
995
|
+
});
|
|
996
|
+
} catch (error) {
|
|
997
|
+
throw new KBError(
|
|
998
|
+
"LOG_SUMMARIZATION_FAILED",
|
|
999
|
+
"Failed to summarize logs",
|
|
1000
|
+
500,
|
|
1001
|
+
error
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
async getLog(id, includeRelated) {
|
|
1006
|
+
try {
|
|
1007
|
+
const params = new URLSearchParams();
|
|
1008
|
+
if (includeRelated) {
|
|
1009
|
+
params.append("includeRelated", "true");
|
|
1010
|
+
}
|
|
1011
|
+
const queryString = params.toString();
|
|
1012
|
+
const url = queryString ? `/logs/${id}?${queryString}` : `/logs/${id}`;
|
|
1013
|
+
return await this.client.fetch(url);
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
throw new KBError(
|
|
1016
|
+
"LOG_FETCH_FAILED",
|
|
1017
|
+
"Failed to fetch log",
|
|
1018
|
+
500,
|
|
1019
|
+
error
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
async getRelatedLogs(id) {
|
|
1024
|
+
try {
|
|
1025
|
+
return await this.client.fetch(
|
|
1026
|
+
`/logs/${id}/related`
|
|
1027
|
+
);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
throw new KBError(
|
|
1030
|
+
"RELATED_LOGS_FETCH_FAILED",
|
|
1031
|
+
"Failed to fetch related logs",
|
|
1032
|
+
500,
|
|
1033
|
+
error
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async getMetricsHistory(query) {
|
|
1038
|
+
try {
|
|
1039
|
+
const params = new URLSearchParams();
|
|
1040
|
+
params.append("metric", query.metric);
|
|
1041
|
+
params.append("range", query.range);
|
|
1042
|
+
if (query.interval) {
|
|
1043
|
+
params.append("interval", query.interval);
|
|
1044
|
+
}
|
|
1045
|
+
return await this.client.fetch(
|
|
1046
|
+
`/observability/metrics/history?${params.toString()}`
|
|
1047
|
+
);
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
throw new KBError(
|
|
1050
|
+
"METRICS_HISTORY_FETCH_FAILED",
|
|
1051
|
+
"Failed to fetch metrics history",
|
|
1052
|
+
500,
|
|
1053
|
+
error
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async getMetricsHeatmap(query) {
|
|
1058
|
+
try {
|
|
1059
|
+
const params = new URLSearchParams();
|
|
1060
|
+
params.append("metric", query.metric);
|
|
1061
|
+
if (query.days) {
|
|
1062
|
+
params.append("days", String(query.days));
|
|
1063
|
+
}
|
|
1064
|
+
return await this.client.fetch(
|
|
1065
|
+
`/observability/metrics/heatmap?${params.toString()}`
|
|
1066
|
+
);
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
throw new KBError(
|
|
1069
|
+
"METRICS_HEATMAP_FETCH_FAILED",
|
|
1070
|
+
"Failed to fetch metrics heatmap",
|
|
1071
|
+
500,
|
|
1072
|
+
error
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
async queryIncidents(query) {
|
|
1077
|
+
try {
|
|
1078
|
+
const params = new URLSearchParams();
|
|
1079
|
+
if (query?.limit) {
|
|
1080
|
+
params.append("limit", String(query.limit));
|
|
1081
|
+
}
|
|
1082
|
+
if (query?.severity) {
|
|
1083
|
+
const severityList = Array.isArray(query.severity) ? query.severity : [query.severity];
|
|
1084
|
+
params.append("severity", severityList.join(","));
|
|
1085
|
+
}
|
|
1086
|
+
if (query?.type) {
|
|
1087
|
+
const typeList = Array.isArray(query.type) ? query.type : [query.type];
|
|
1088
|
+
params.append("type", typeList.join(","));
|
|
1089
|
+
}
|
|
1090
|
+
if (query?.from) {
|
|
1091
|
+
params.append("from", String(query.from));
|
|
1092
|
+
}
|
|
1093
|
+
if (query?.to) {
|
|
1094
|
+
params.append("to", String(query.to));
|
|
1095
|
+
}
|
|
1096
|
+
if (query?.includeResolved !== void 0) {
|
|
1097
|
+
params.append("includeResolved", String(query.includeResolved));
|
|
1098
|
+
}
|
|
1099
|
+
const queryString = params.toString();
|
|
1100
|
+
const url = queryString ? `/observability/incidents/history?${queryString}` : "/observability/incidents/history";
|
|
1101
|
+
return await this.client.fetch(url);
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
throw new KBError(
|
|
1104
|
+
"INCIDENTS_QUERY_FAILED",
|
|
1105
|
+
"Failed to query incidents",
|
|
1106
|
+
500,
|
|
1107
|
+
error
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
async createIncident(payload) {
|
|
1112
|
+
try {
|
|
1113
|
+
return await this.client.fetch(
|
|
1114
|
+
"/observability/incidents",
|
|
1115
|
+
{
|
|
1116
|
+
method: "POST",
|
|
1117
|
+
data: payload
|
|
1118
|
+
}
|
|
1119
|
+
);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
throw new KBError(
|
|
1122
|
+
"INCIDENT_CREATE_FAILED",
|
|
1123
|
+
"Failed to create incident",
|
|
1124
|
+
500,
|
|
1125
|
+
error
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async resolveIncident(id, resolutionNotes) {
|
|
1130
|
+
try {
|
|
1131
|
+
return await this.client.fetch(
|
|
1132
|
+
`/observability/incidents/${id}/resolve`,
|
|
1133
|
+
{
|
|
1134
|
+
method: "POST",
|
|
1135
|
+
data: { resolutionNotes }
|
|
1136
|
+
}
|
|
1137
|
+
);
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
throw new KBError(
|
|
1140
|
+
"INCIDENT_RESOLVE_FAILED",
|
|
1141
|
+
"Failed to resolve incident",
|
|
1142
|
+
500,
|
|
1143
|
+
error
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
async listIncidents(query) {
|
|
1148
|
+
try {
|
|
1149
|
+
const params = buildIncidentQueryParams(query);
|
|
1150
|
+
const url = `/observability/incidents${params ? `?${params}` : ""}`;
|
|
1151
|
+
const response = await this.client.fetch(url);
|
|
1152
|
+
return {
|
|
1153
|
+
ok: true,
|
|
1154
|
+
data: response
|
|
1155
|
+
// HttpClient auto-unwraps envelope
|
|
1156
|
+
};
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
throw new KBError(
|
|
1159
|
+
"INCIDENTS_LIST_FAILED",
|
|
1160
|
+
"Failed to list incidents",
|
|
1161
|
+
500,
|
|
1162
|
+
error
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async getIncident(id) {
|
|
1167
|
+
try {
|
|
1168
|
+
const data = await this.client.fetch(`/observability/incidents/${id}`);
|
|
1169
|
+
return {
|
|
1170
|
+
ok: true,
|
|
1171
|
+
data
|
|
1172
|
+
};
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
throw new KBError(
|
|
1175
|
+
"INCIDENT_FETCH_FAILED",
|
|
1176
|
+
"Failed to fetch incident",
|
|
1177
|
+
500,
|
|
1178
|
+
error
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async analyzeIncident(id) {
|
|
1183
|
+
try {
|
|
1184
|
+
const data = await this.client.fetch(
|
|
1185
|
+
`/observability/incidents/${id}/analyze`,
|
|
1186
|
+
{ method: "POST" }
|
|
1187
|
+
);
|
|
1188
|
+
return {
|
|
1189
|
+
ok: true,
|
|
1190
|
+
data
|
|
1191
|
+
};
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
throw new KBError(
|
|
1194
|
+
"INCIDENT_ANALYSIS_FAILED",
|
|
1195
|
+
"Failed to analyze incident",
|
|
1196
|
+
500,
|
|
1197
|
+
error
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
async chatWithInsights(question, context) {
|
|
1202
|
+
try {
|
|
1203
|
+
return await this.client.fetch("/observability/insights/chat", {
|
|
1204
|
+
method: "POST",
|
|
1205
|
+
data: { question, context }
|
|
1206
|
+
});
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
throw new KBError(
|
|
1209
|
+
"INSIGHTS_CHAT_FAILED",
|
|
1210
|
+
"Failed to chat with AI insights",
|
|
1211
|
+
500,
|
|
1212
|
+
error
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
function buildIncidentQueryParams(query) {
|
|
1218
|
+
if (!query) {
|
|
1219
|
+
return "";
|
|
1220
|
+
}
|
|
1221
|
+
const params = new URLSearchParams();
|
|
1222
|
+
if (query.limit !== void 0) {
|
|
1223
|
+
params.append("limit", query.limit.toString());
|
|
1224
|
+
}
|
|
1225
|
+
if (query.severity) {
|
|
1226
|
+
const severities = Array.isArray(query.severity) ? query.severity : [query.severity];
|
|
1227
|
+
severities.forEach((s) => params.append("severity", s));
|
|
1228
|
+
}
|
|
1229
|
+
if (query.type) {
|
|
1230
|
+
const types = Array.isArray(query.type) ? query.type : [query.type];
|
|
1231
|
+
types.forEach((t) => params.append("type", t));
|
|
1232
|
+
}
|
|
1233
|
+
if (query.from !== void 0) {
|
|
1234
|
+
params.append("from", query.from.toString());
|
|
1235
|
+
}
|
|
1236
|
+
if (query.to !== void 0) {
|
|
1237
|
+
params.append("to", query.to.toString());
|
|
1238
|
+
}
|
|
1239
|
+
if (query.includeResolved !== void 0) {
|
|
1240
|
+
params.append("includeResolved", query.includeResolved.toString());
|
|
1241
|
+
}
|
|
1242
|
+
return params.toString();
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/sources/http-analytics-source.ts
|
|
1246
|
+
var HttpAnalyticsSource = class {
|
|
1247
|
+
constructor(client) {
|
|
1248
|
+
this.client = client;
|
|
1249
|
+
}
|
|
1250
|
+
async getEvents(query) {
|
|
1251
|
+
const params = new URLSearchParams();
|
|
1252
|
+
if (query?.type) {
|
|
1253
|
+
if (Array.isArray(query.type)) {
|
|
1254
|
+
query.type.forEach((t) => params.append("type", t));
|
|
1255
|
+
} else {
|
|
1256
|
+
params.set("type", query.type);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
if (query?.source) {
|
|
1260
|
+
params.set("source", query.source);
|
|
1261
|
+
}
|
|
1262
|
+
if (query?.actor) {
|
|
1263
|
+
params.set("actor", query.actor);
|
|
1264
|
+
}
|
|
1265
|
+
if (query?.from) {
|
|
1266
|
+
params.set("from", query.from);
|
|
1267
|
+
}
|
|
1268
|
+
if (query?.to) {
|
|
1269
|
+
params.set("to", query.to);
|
|
1270
|
+
}
|
|
1271
|
+
if (query?.limit) {
|
|
1272
|
+
params.set("limit", String(query.limit));
|
|
1273
|
+
}
|
|
1274
|
+
if (query?.offset) {
|
|
1275
|
+
params.set("offset", String(query.offset));
|
|
1276
|
+
}
|
|
1277
|
+
const path = `/analytics/events${params.toString() ? `?${params}` : ""}`;
|
|
1278
|
+
return this.client.fetch(path);
|
|
1279
|
+
}
|
|
1280
|
+
async getStats() {
|
|
1281
|
+
return this.client.fetch("/analytics/stats");
|
|
1282
|
+
}
|
|
1283
|
+
async getBufferStatus() {
|
|
1284
|
+
try {
|
|
1285
|
+
return this.client.fetch("/analytics/buffer/status");
|
|
1286
|
+
} catch (_error) {
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
async getDlqStatus() {
|
|
1291
|
+
try {
|
|
1292
|
+
return this.client.fetch("/analytics/dlq/status");
|
|
1293
|
+
} catch (_error) {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
// src/sources/http-adapters-source.ts
|
|
1300
|
+
var HttpAdaptersSource = class {
|
|
1301
|
+
constructor(client) {
|
|
1302
|
+
this.client = client;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Build query string from date range options
|
|
1306
|
+
*/
|
|
1307
|
+
buildQueryString(options) {
|
|
1308
|
+
const params = new URLSearchParams();
|
|
1309
|
+
if (options?.from) {
|
|
1310
|
+
params.append("from", options.from);
|
|
1311
|
+
}
|
|
1312
|
+
if (options?.to) {
|
|
1313
|
+
params.append("to", options.to);
|
|
1314
|
+
}
|
|
1315
|
+
if (options?.models) {
|
|
1316
|
+
const modelsArray = Array.isArray(options.models) ? options.models : [options.models];
|
|
1317
|
+
if (modelsArray.length > 0) {
|
|
1318
|
+
params.append("models", modelsArray.join(","));
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
if (options?.groupBy) {
|
|
1322
|
+
params.append("groupBy", options.groupBy);
|
|
1323
|
+
}
|
|
1324
|
+
if (options?.breakdownBy) {
|
|
1325
|
+
params.append("breakdownBy", options.breakdownBy);
|
|
1326
|
+
}
|
|
1327
|
+
if (options?.metrics && options.metrics.length > 0) {
|
|
1328
|
+
params.append("metrics", options.metrics.join(","));
|
|
1329
|
+
}
|
|
1330
|
+
const qs = params.toString();
|
|
1331
|
+
return qs ? `?${qs}` : "";
|
|
1332
|
+
}
|
|
1333
|
+
async getLLMUsage(options) {
|
|
1334
|
+
const query = this.buildQueryString(options);
|
|
1335
|
+
return this.client.fetch(`/adapters/llm/usage${query}`);
|
|
1336
|
+
}
|
|
1337
|
+
async getEmbeddingsUsage(options) {
|
|
1338
|
+
const query = this.buildQueryString(options);
|
|
1339
|
+
return this.client.fetch(`/adapters/embeddings/usage${query}`);
|
|
1340
|
+
}
|
|
1341
|
+
async getVectorStoreUsage(options) {
|
|
1342
|
+
const query = this.buildQueryString(options);
|
|
1343
|
+
return this.client.fetch(`/adapters/vectorstore/usage${query}`);
|
|
1344
|
+
}
|
|
1345
|
+
async getCacheUsage(options) {
|
|
1346
|
+
const query = this.buildQueryString(options);
|
|
1347
|
+
return this.client.fetch(`/adapters/cache/usage${query}`);
|
|
1348
|
+
}
|
|
1349
|
+
async getStorageUsage(options) {
|
|
1350
|
+
const query = this.buildQueryString(options);
|
|
1351
|
+
return this.client.fetch(`/adapters/storage/usage${query}`);
|
|
1352
|
+
}
|
|
1353
|
+
async getLLMDailyStats(options) {
|
|
1354
|
+
const query = this.buildQueryString(options);
|
|
1355
|
+
return this.client.fetch(`/adapters/llm/daily-stats${query}`);
|
|
1356
|
+
}
|
|
1357
|
+
async getEmbeddingsDailyStats(options) {
|
|
1358
|
+
const query = this.buildQueryString(options);
|
|
1359
|
+
return this.client.fetch(`/adapters/embeddings/daily-stats${query}`);
|
|
1360
|
+
}
|
|
1361
|
+
async getVectorStoreDailyStats(options) {
|
|
1362
|
+
const query = this.buildQueryString(options);
|
|
1363
|
+
return this.client.fetch(`/adapters/vectorstore/daily-stats${query}`);
|
|
1364
|
+
}
|
|
1365
|
+
async getCacheDailyStats(options) {
|
|
1366
|
+
const query = this.buildQueryString(options);
|
|
1367
|
+
return this.client.fetch(`/adapters/cache/daily-stats${query}`);
|
|
1368
|
+
}
|
|
1369
|
+
async getStorageDailyStats(options) {
|
|
1370
|
+
const query = this.buildQueryString(options);
|
|
1371
|
+
return this.client.fetch(`/adapters/storage/daily-stats${query}`);
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1375
|
+
// src/sources/http-platform-source.ts
|
|
1376
|
+
var HttpPlatformSource = class {
|
|
1377
|
+
constructor(client) {
|
|
1378
|
+
this.client = client;
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Get platform configuration
|
|
1382
|
+
*/
|
|
1383
|
+
async getConfig() {
|
|
1384
|
+
return this.client.fetch("/platform/config");
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
// src/sources/http-plugins-source.ts
|
|
1389
|
+
var HttpPluginsSource = class {
|
|
1390
|
+
constructor(client) {
|
|
1391
|
+
this.client = client;
|
|
1392
|
+
}
|
|
1393
|
+
async getPlugins() {
|
|
1394
|
+
return this.client.fetch("/plugins/registry");
|
|
1395
|
+
}
|
|
1396
|
+
async askAboutPlugin(pluginId, request) {
|
|
1397
|
+
return this.client.fetch(`/plugins/${encodeURIComponent(pluginId)}/ask`, {
|
|
1398
|
+
method: "POST",
|
|
1399
|
+
data: request
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
// src/mocks/mock-system-source.ts
|
|
1405
|
+
function delay(ms) {
|
|
1406
|
+
return new Promise((resolve) => {
|
|
1407
|
+
setTimeout(() => resolve(), ms);
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
var MockSystemSource = class {
|
|
1411
|
+
async getHealth() {
|
|
1412
|
+
await delay(150);
|
|
1413
|
+
const snapshot = {
|
|
1414
|
+
schema: "kb.observability/1",
|
|
1415
|
+
contractVersion: "1.0",
|
|
1416
|
+
serviceId: "rest",
|
|
1417
|
+
instanceId: "mock-rest",
|
|
1418
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1419
|
+
status: "healthy",
|
|
1420
|
+
uptimeSec: 3600,
|
|
1421
|
+
metricsEndpoint: "/api/v1/metrics",
|
|
1422
|
+
logsSource: "rest",
|
|
1423
|
+
capabilities: ["httpMetrics", "eventLoopMetrics", "operationMetrics", "logCorrelation"],
|
|
1424
|
+
checks: [
|
|
1425
|
+
{ id: "registry", status: "ok", message: "Registry snapshot loaded" },
|
|
1426
|
+
{ id: "plugin-routes", status: "ok", message: "12 plugin routes mounted" }
|
|
1427
|
+
],
|
|
1428
|
+
snapshot: {
|
|
1429
|
+
cpuPercent: 6.2,
|
|
1430
|
+
rssBytes: 188743680,
|
|
1431
|
+
heapUsedBytes: 73400320,
|
|
1432
|
+
eventLoopLagMs: 12,
|
|
1433
|
+
activeOperations: 3
|
|
1434
|
+
},
|
|
1435
|
+
topOperations: [
|
|
1436
|
+
{ operation: "http.GET /api/v1/health", count: 42, avgDurationMs: 3.1, maxDurationMs: 8.2, errorCount: 0 }
|
|
1437
|
+
],
|
|
1438
|
+
state: "active"
|
|
1439
|
+
};
|
|
1440
|
+
return {
|
|
1441
|
+
ok: true,
|
|
1442
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1443
|
+
sources: [
|
|
1444
|
+
{ name: "registry", ok: true, latency: 100 },
|
|
1445
|
+
{ name: "plugin-routes", ok: true, latency: 120 }
|
|
1446
|
+
],
|
|
1447
|
+
snapshot
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
async getRoutes() {
|
|
1451
|
+
await delay(100);
|
|
1452
|
+
return {
|
|
1453
|
+
schema: "kb.routes/1",
|
|
1454
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1455
|
+
count: 5,
|
|
1456
|
+
routes: [
|
|
1457
|
+
{ method: "GET", url: "/api/v1/health" },
|
|
1458
|
+
{ method: "GET", url: "/api/v1/ready" },
|
|
1459
|
+
{ method: "GET", url: "/api/v1/plugins" },
|
|
1460
|
+
{ method: "GET", url: "/api/v1/plugins/:id" },
|
|
1461
|
+
{ method: "POST", url: "/api/v1/workflows/run" }
|
|
1462
|
+
]
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
getBaseUrl() {
|
|
1466
|
+
return "http://localhost:5050/api/v1";
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
function createMockRun(overrides = {}) {
|
|
1470
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1471
|
+
return {
|
|
1472
|
+
id: ulid(),
|
|
1473
|
+
name: "demo-workflow",
|
|
1474
|
+
version: "1.0.0",
|
|
1475
|
+
status: "success",
|
|
1476
|
+
createdAt: now,
|
|
1477
|
+
queuedAt: now,
|
|
1478
|
+
startedAt: now,
|
|
1479
|
+
finishedAt: now,
|
|
1480
|
+
trigger: {
|
|
1481
|
+
type: "manual",
|
|
1482
|
+
actor: "mock-user",
|
|
1483
|
+
payload: {}
|
|
1484
|
+
},
|
|
1485
|
+
jobs: [],
|
|
1486
|
+
artifacts: [],
|
|
1487
|
+
metadata: {},
|
|
1488
|
+
...overrides
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
var MockWorkflowSource = class {
|
|
1492
|
+
runs = [createMockRun()];
|
|
1493
|
+
async listRuns(_filters) {
|
|
1494
|
+
return {
|
|
1495
|
+
runs: this.runs,
|
|
1496
|
+
total: this.runs.length
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
async getRun(runId) {
|
|
1500
|
+
return this.runs.find((run) => run.id === runId) ?? null;
|
|
1501
|
+
}
|
|
1502
|
+
async cancelRun(runId) {
|
|
1503
|
+
const run = await this.getRun(runId);
|
|
1504
|
+
if (!run) {
|
|
1505
|
+
throw new Error(`Run ${runId} not found`);
|
|
1506
|
+
}
|
|
1507
|
+
const updated = {
|
|
1508
|
+
...run,
|
|
1509
|
+
status: "cancelled",
|
|
1510
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1511
|
+
};
|
|
1512
|
+
this.runs = this.runs.map((existing) => existing.id === runId ? updated : existing);
|
|
1513
|
+
return updated;
|
|
1514
|
+
}
|
|
1515
|
+
async runWorkflow(params) {
|
|
1516
|
+
const run = createMockRun({
|
|
1517
|
+
id: ulid(),
|
|
1518
|
+
name: params.spec.name,
|
|
1519
|
+
version: params.spec.version,
|
|
1520
|
+
status: "queued",
|
|
1521
|
+
metadata: params.metadata ?? {}
|
|
1522
|
+
});
|
|
1523
|
+
this.runs = [run, ...this.runs];
|
|
1524
|
+
return run;
|
|
1525
|
+
}
|
|
1526
|
+
async listEvents() {
|
|
1527
|
+
return {
|
|
1528
|
+
events: [],
|
|
1529
|
+
cursor: null
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
// 🆕 NEW mock methods for UI
|
|
1533
|
+
async getStats() {
|
|
1534
|
+
return {
|
|
1535
|
+
workflows: {
|
|
1536
|
+
total: 5,
|
|
1537
|
+
active: 3,
|
|
1538
|
+
inactive: 2
|
|
1539
|
+
},
|
|
1540
|
+
jobs: {
|
|
1541
|
+
running: 2,
|
|
1542
|
+
pending: 1,
|
|
1543
|
+
completed: 15,
|
|
1544
|
+
failed: 3
|
|
1545
|
+
},
|
|
1546
|
+
crons: {
|
|
1547
|
+
total: 4,
|
|
1548
|
+
enabled: 3,
|
|
1549
|
+
disabled: 1
|
|
1550
|
+
},
|
|
1551
|
+
activeExecutions: [
|
|
1552
|
+
{
|
|
1553
|
+
id: "job-1",
|
|
1554
|
+
type: "workflow:demo",
|
|
1555
|
+
workflowName: "Demo Workflow",
|
|
1556
|
+
status: "running",
|
|
1557
|
+
progress: 45,
|
|
1558
|
+
progressMessage: "Processing step 2 of 4",
|
|
1559
|
+
startedAt: new Date(Date.now() - 3e4).toISOString(),
|
|
1560
|
+
durationMs: 3e4
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
id: "job-2",
|
|
1564
|
+
type: "workflow:build",
|
|
1565
|
+
workflowName: "Build Pipeline",
|
|
1566
|
+
status: "running",
|
|
1567
|
+
progress: 75,
|
|
1568
|
+
progressMessage: "Running tests",
|
|
1569
|
+
startedAt: new Date(Date.now() - 12e4).toISOString(),
|
|
1570
|
+
durationMs: 12e4
|
|
1571
|
+
}
|
|
1572
|
+
],
|
|
1573
|
+
recentActivity: [
|
|
1574
|
+
{
|
|
1575
|
+
id: "job-3",
|
|
1576
|
+
type: "workflow:deploy",
|
|
1577
|
+
workflowName: "Deploy to Production",
|
|
1578
|
+
status: "completed",
|
|
1579
|
+
finishedAt: new Date(Date.now() - 3e5).toISOString(),
|
|
1580
|
+
durationMs: 18e4
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
id: "job-4",
|
|
1584
|
+
type: "workflow:test",
|
|
1585
|
+
workflowName: "Integration Tests",
|
|
1586
|
+
status: "failed",
|
|
1587
|
+
finishedAt: new Date(Date.now() - 6e5).toISOString(),
|
|
1588
|
+
durationMs: 45e3,
|
|
1589
|
+
error: "Test suite failed: 3 of 15 tests failed"
|
|
1590
|
+
}
|
|
1591
|
+
]
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
async listWorkflows(_filters) {
|
|
1595
|
+
return {
|
|
1596
|
+
workflows: [
|
|
1597
|
+
{
|
|
1598
|
+
id: "demo-workflow",
|
|
1599
|
+
name: "Demo Workflow",
|
|
1600
|
+
description: "Example workflow for testing",
|
|
1601
|
+
source: "manifest",
|
|
1602
|
+
pluginId: "demo-plugin",
|
|
1603
|
+
status: "active",
|
|
1604
|
+
tags: ["demo", "test"]
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
id: "build-pipeline",
|
|
1608
|
+
name: "Build Pipeline",
|
|
1609
|
+
description: "CI/CD build workflow",
|
|
1610
|
+
source: "standalone",
|
|
1611
|
+
status: "active",
|
|
1612
|
+
tags: ["ci", "build"]
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
id: "deploy-prod",
|
|
1616
|
+
name: "Deploy to Production",
|
|
1617
|
+
description: "Production deployment workflow",
|
|
1618
|
+
source: "manifest",
|
|
1619
|
+
pluginId: "deploy-plugin",
|
|
1620
|
+
status: "active",
|
|
1621
|
+
tags: ["deploy", "production"]
|
|
1622
|
+
}
|
|
1623
|
+
]
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
async getWorkflow(workflowId) {
|
|
1627
|
+
const workflows = await this.listWorkflows();
|
|
1628
|
+
return workflows.workflows.find((w) => w.id === workflowId) ?? null;
|
|
1629
|
+
}
|
|
1630
|
+
async runWorkflowById(workflowId, _input) {
|
|
1631
|
+
const runId = ulid();
|
|
1632
|
+
console.log(`[MockWorkflowSource] Running workflow ${workflowId}, run ID: ${runId}`);
|
|
1633
|
+
return {
|
|
1634
|
+
runId,
|
|
1635
|
+
status: "pending"
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
async listJobs(filters) {
|
|
1639
|
+
const mockJobs = [
|
|
1640
|
+
{
|
|
1641
|
+
id: "job-1",
|
|
1642
|
+
type: "workflow:demo",
|
|
1643
|
+
status: "running",
|
|
1644
|
+
priority: 5,
|
|
1645
|
+
createdAt: new Date(Date.now() - 6e4).toISOString(),
|
|
1646
|
+
startedAt: new Date(Date.now() - 3e4).toISOString(),
|
|
1647
|
+
attempt: 1,
|
|
1648
|
+
maxRetries: 3,
|
|
1649
|
+
progress: 45,
|
|
1650
|
+
progressMessage: "Processing step 2 of 4"
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
id: "job-2",
|
|
1654
|
+
type: "workflow:build",
|
|
1655
|
+
status: "completed",
|
|
1656
|
+
priority: 8,
|
|
1657
|
+
createdAt: new Date(Date.now() - 3e5).toISOString(),
|
|
1658
|
+
startedAt: new Date(Date.now() - 28e4).toISOString(),
|
|
1659
|
+
finishedAt: new Date(Date.now() - 12e4).toISOString(),
|
|
1660
|
+
attempt: 1,
|
|
1661
|
+
maxRetries: 3,
|
|
1662
|
+
result: { success: true }
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
id: "job-3",
|
|
1666
|
+
type: "workflow:test",
|
|
1667
|
+
status: "failed",
|
|
1668
|
+
priority: 5,
|
|
1669
|
+
createdAt: new Date(Date.now() - 6e5).toISOString(),
|
|
1670
|
+
startedAt: new Date(Date.now() - 58e4).toISOString(),
|
|
1671
|
+
finishedAt: new Date(Date.now() - 535e3).toISOString(),
|
|
1672
|
+
attempt: 3,
|
|
1673
|
+
maxRetries: 3,
|
|
1674
|
+
error: "Test suite failed: 3 of 15 tests failed"
|
|
1675
|
+
}
|
|
1676
|
+
];
|
|
1677
|
+
let filtered = mockJobs;
|
|
1678
|
+
if (filters?.status) {
|
|
1679
|
+
filtered = filtered.filter((j) => j.status === filters.status);
|
|
1680
|
+
}
|
|
1681
|
+
if (filters?.type) {
|
|
1682
|
+
filtered = filtered.filter((j) => j.type.includes(filters.type));
|
|
1683
|
+
}
|
|
1684
|
+
return { jobs: filtered };
|
|
1685
|
+
}
|
|
1686
|
+
async getJob(jobId) {
|
|
1687
|
+
const jobs = await this.listJobs();
|
|
1688
|
+
return jobs.jobs.find((j) => j.id === jobId) ?? null;
|
|
1689
|
+
}
|
|
1690
|
+
async getJobSteps(jobId) {
|
|
1691
|
+
return {
|
|
1692
|
+
jobId,
|
|
1693
|
+
workflowName: "Demo Workflow",
|
|
1694
|
+
status: "running",
|
|
1695
|
+
steps: [
|
|
1696
|
+
{
|
|
1697
|
+
name: "checkout",
|
|
1698
|
+
handler: "git-checkout",
|
|
1699
|
+
status: "completed",
|
|
1700
|
+
startedAt: new Date(Date.now() - 12e4).toISOString(),
|
|
1701
|
+
finishedAt: new Date(Date.now() - 11e4).toISOString(),
|
|
1702
|
+
durationMs: 1e4
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
name: "build",
|
|
1706
|
+
handler: "npm-build",
|
|
1707
|
+
status: "running",
|
|
1708
|
+
progress: 65,
|
|
1709
|
+
startedAt: new Date(Date.now() - 11e4).toISOString()
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
name: "test",
|
|
1713
|
+
handler: "npm-test",
|
|
1714
|
+
status: "pending"
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
name: "deploy",
|
|
1718
|
+
handler: "k8s-deploy",
|
|
1719
|
+
status: "pending"
|
|
1720
|
+
}
|
|
1721
|
+
],
|
|
1722
|
+
currentStep: 1
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
async getJobLogs(jobId, _filters) {
|
|
1726
|
+
return {
|
|
1727
|
+
jobId,
|
|
1728
|
+
logs: [
|
|
1729
|
+
{
|
|
1730
|
+
timestamp: new Date(Date.now() - 12e4).toISOString(),
|
|
1731
|
+
level: "info",
|
|
1732
|
+
message: "Starting workflow execution",
|
|
1733
|
+
context: { step: "init" }
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
timestamp: new Date(Date.now() - 115e3).toISOString(),
|
|
1737
|
+
level: "info",
|
|
1738
|
+
message: "Checking out code from repository",
|
|
1739
|
+
context: { step: "checkout" }
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
timestamp: new Date(Date.now() - 11e4).toISOString(),
|
|
1743
|
+
level: "info",
|
|
1744
|
+
message: "Checkout completed successfully",
|
|
1745
|
+
context: { step: "checkout", duration: 1e4 }
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
timestamp: new Date(Date.now() - 105e3).toISOString(),
|
|
1749
|
+
level: "info",
|
|
1750
|
+
message: "Installing dependencies",
|
|
1751
|
+
context: { step: "build" }
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
timestamp: new Date(Date.now() - 85e3).toISOString(),
|
|
1755
|
+
level: "info",
|
|
1756
|
+
message: "Running build scripts",
|
|
1757
|
+
context: { step: "build" }
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
timestamp: new Date(Date.now() - 6e4).toISOString(),
|
|
1761
|
+
level: "warn",
|
|
1762
|
+
message: 'Build warning: Unused variable "foo" in module "bar"',
|
|
1763
|
+
context: { step: "build" }
|
|
1764
|
+
}
|
|
1765
|
+
],
|
|
1766
|
+
total: 6,
|
|
1767
|
+
hasMore: false
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
async listCronJobs() {
|
|
1771
|
+
return {
|
|
1772
|
+
crons: [
|
|
1773
|
+
{
|
|
1774
|
+
id: "daily-backup",
|
|
1775
|
+
schedule: "0 2 * * *",
|
|
1776
|
+
jobType: "backup:database",
|
|
1777
|
+
timezone: "UTC",
|
|
1778
|
+
enabled: true,
|
|
1779
|
+
lastRun: new Date(Date.now() - 864e5).toISOString(),
|
|
1780
|
+
nextRun: new Date(Date.now() + 864e5).toISOString(),
|
|
1781
|
+
pluginId: "backup-plugin"
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
id: "hourly-sync",
|
|
1785
|
+
schedule: "0 * * * *",
|
|
1786
|
+
jobType: "sync:data",
|
|
1787
|
+
timezone: "UTC",
|
|
1788
|
+
enabled: true,
|
|
1789
|
+
lastRun: new Date(Date.now() - 36e5).toISOString(),
|
|
1790
|
+
nextRun: new Date(Date.now() + 6e5).toISOString(),
|
|
1791
|
+
pluginId: "sync-plugin"
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
id: "weekly-report",
|
|
1795
|
+
schedule: "0 9 * * 1",
|
|
1796
|
+
jobType: "report:generate",
|
|
1797
|
+
timezone: "America/New_York",
|
|
1798
|
+
enabled: false,
|
|
1799
|
+
pluginId: "report-plugin"
|
|
1800
|
+
}
|
|
1801
|
+
]
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
async cancelWorkflowRun(runId) {
|
|
1805
|
+
return { cancelled: true, runId };
|
|
1806
|
+
}
|
|
1807
|
+
async getWorkflowRuns(workflowId, _filters) {
|
|
1808
|
+
return {
|
|
1809
|
+
workflowId,
|
|
1810
|
+
runs: [
|
|
1811
|
+
{
|
|
1812
|
+
id: "run-1",
|
|
1813
|
+
workflowId,
|
|
1814
|
+
status: "completed",
|
|
1815
|
+
trigger: { type: "manual", user: "admin" },
|
|
1816
|
+
startedAt: new Date(Date.now() - 36e5).toISOString(),
|
|
1817
|
+
finishedAt: new Date(Date.now() - 342e4).toISOString(),
|
|
1818
|
+
durationMs: 18e4
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
id: "run-2",
|
|
1822
|
+
workflowId,
|
|
1823
|
+
status: "failed",
|
|
1824
|
+
trigger: { type: "cron" },
|
|
1825
|
+
startedAt: new Date(Date.now() - 72e5).toISOString(),
|
|
1826
|
+
finishedAt: new Date(Date.now() - 7155e3).toISOString(),
|
|
1827
|
+
durationMs: 45e3,
|
|
1828
|
+
error: 'Step "deploy" failed: connection timeout'
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
id: "run-3",
|
|
1832
|
+
workflowId,
|
|
1833
|
+
status: "completed",
|
|
1834
|
+
trigger: { type: "api", user: "ci-system" },
|
|
1835
|
+
startedAt: new Date(Date.now() - 864e5).toISOString(),
|
|
1836
|
+
finishedAt: new Date(Date.now() - 8622e4).toISOString(),
|
|
1837
|
+
durationMs: 18e4
|
|
1838
|
+
}
|
|
1839
|
+
],
|
|
1840
|
+
total: 3
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
// src/mocks/mock-cache-source.ts
|
|
1846
|
+
var MockCacheSource = class {
|
|
1847
|
+
async invalidateCache() {
|
|
1848
|
+
return {
|
|
1849
|
+
invalidated: true,
|
|
1850
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1851
|
+
previousRev: 1,
|
|
1852
|
+
newRev: 2,
|
|
1853
|
+
pluginsDiscovered: 4
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
|
|
1858
|
+
// src/mocks/mock-observability-source.ts
|
|
1859
|
+
function delay2(ms) {
|
|
1860
|
+
return new Promise((resolve) => {
|
|
1861
|
+
setTimeout(() => resolve(), ms);
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
var MockObservabilitySource = class {
|
|
1865
|
+
async getStateBrokerStats() {
|
|
1866
|
+
await delay2(150);
|
|
1867
|
+
return {
|
|
1868
|
+
uptime: 36e5,
|
|
1869
|
+
// 1 hour
|
|
1870
|
+
totalEntries: 42,
|
|
1871
|
+
totalSize: 10240,
|
|
1872
|
+
// 10KB
|
|
1873
|
+
hitRate: 0.85,
|
|
1874
|
+
missRate: 0.15,
|
|
1875
|
+
evictions: 5,
|
|
1876
|
+
namespaces: {
|
|
1877
|
+
mind: {
|
|
1878
|
+
entries: 20,
|
|
1879
|
+
hits: 100,
|
|
1880
|
+
misses: 10,
|
|
1881
|
+
size: 5120
|
|
1882
|
+
},
|
|
1883
|
+
workflow: {
|
|
1884
|
+
entries: 15,
|
|
1885
|
+
hits: 80,
|
|
1886
|
+
misses: 5,
|
|
1887
|
+
size: 3072
|
|
1888
|
+
},
|
|
1889
|
+
plugin: {
|
|
1890
|
+
entries: 7,
|
|
1891
|
+
hits: 30,
|
|
1892
|
+
misses: 2,
|
|
1893
|
+
size: 2048
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
async getDevKitHealth() {
|
|
1899
|
+
await delay2(200);
|
|
1900
|
+
return {
|
|
1901
|
+
healthScore: 68,
|
|
1902
|
+
grade: "D",
|
|
1903
|
+
issues: {
|
|
1904
|
+
duplicateDeps: 30,
|
|
1905
|
+
missingReadmes: 12,
|
|
1906
|
+
typeErrors: 3012,
|
|
1907
|
+
brokenImports: 5,
|
|
1908
|
+
unusedExports: 120
|
|
1909
|
+
},
|
|
1910
|
+
packages: 91,
|
|
1911
|
+
avgTypeCoverage: 91.1
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
async getPrometheusMetrics() {
|
|
1915
|
+
await delay2(180);
|
|
1916
|
+
const now = Date.now();
|
|
1917
|
+
const startTime = now - 72e5;
|
|
1918
|
+
return {
|
|
1919
|
+
requests: {
|
|
1920
|
+
total: 1542,
|
|
1921
|
+
success: 1489,
|
|
1922
|
+
clientErrors: 38,
|
|
1923
|
+
serverErrors: 15
|
|
1924
|
+
},
|
|
1925
|
+
latency: {
|
|
1926
|
+
average: 47.3,
|
|
1927
|
+
min: 3.2,
|
|
1928
|
+
max: 892.5,
|
|
1929
|
+
p50: 38.1,
|
|
1930
|
+
p95: 125.7,
|
|
1931
|
+
p99: 287.4
|
|
1932
|
+
},
|
|
1933
|
+
perPlugin: [
|
|
1934
|
+
{
|
|
1935
|
+
pluginId: "workflow",
|
|
1936
|
+
requests: 523,
|
|
1937
|
+
errors: 12,
|
|
1938
|
+
latency: {
|
|
1939
|
+
average: 52.1,
|
|
1940
|
+
min: 5.3,
|
|
1941
|
+
max: 345.2
|
|
1942
|
+
}
|
|
1943
|
+
},
|
|
1944
|
+
{
|
|
1945
|
+
pluginId: "mind",
|
|
1946
|
+
requests: 387,
|
|
1947
|
+
errors: 8,
|
|
1948
|
+
latency: {
|
|
1949
|
+
average: 68.5,
|
|
1950
|
+
min: 12.1,
|
|
1951
|
+
max: 892.5
|
|
1952
|
+
}
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
pluginId: "commit",
|
|
1956
|
+
requests: 142,
|
|
1957
|
+
errors: 3,
|
|
1958
|
+
latency: {
|
|
1959
|
+
average: 35.2,
|
|
1960
|
+
min: 8.7,
|
|
1961
|
+
max: 156.3
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
],
|
|
1965
|
+
perTenant: [
|
|
1966
|
+
{
|
|
1967
|
+
tenantId: "default",
|
|
1968
|
+
requests: 1542,
|
|
1969
|
+
errors: 53,
|
|
1970
|
+
latency: {
|
|
1971
|
+
average: 47.3
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
],
|
|
1975
|
+
errors: {
|
|
1976
|
+
byStatusCode: {
|
|
1977
|
+
400: 12,
|
|
1978
|
+
404: 18,
|
|
1979
|
+
422: 8,
|
|
1980
|
+
500: 10,
|
|
1981
|
+
503: 5
|
|
1982
|
+
},
|
|
1983
|
+
recent: [
|
|
1984
|
+
{
|
|
1985
|
+
timestamp: now - 12e4,
|
|
1986
|
+
statusCode: 500,
|
|
1987
|
+
errorCode: "INTERNAL_ERROR",
|
|
1988
|
+
message: "Database connection timeout"
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
timestamp: now - 3e5,
|
|
1992
|
+
statusCode: 404,
|
|
1993
|
+
message: "Resource not found"
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
timestamp: now - 45e4,
|
|
1997
|
+
statusCode: 422,
|
|
1998
|
+
errorCode: "VALIDATION_ERROR",
|
|
1999
|
+
message: "Invalid request payload"
|
|
2000
|
+
}
|
|
2001
|
+
]
|
|
2002
|
+
},
|
|
2003
|
+
timestamps: {
|
|
2004
|
+
startTime,
|
|
2005
|
+
lastRequest: now - 2500
|
|
2006
|
+
},
|
|
2007
|
+
redis: {
|
|
2008
|
+
updates: 342,
|
|
2009
|
+
healthyTransitions: 2,
|
|
2010
|
+
unhealthyTransitions: 1,
|
|
2011
|
+
lastStatus: {
|
|
2012
|
+
healthy: true,
|
|
2013
|
+
state: "ready",
|
|
2014
|
+
role: "master"
|
|
2015
|
+
}
|
|
2016
|
+
},
|
|
2017
|
+
pluginMounts: {
|
|
2018
|
+
total: 8,
|
|
2019
|
+
succeeded: 7,
|
|
2020
|
+
failed: 1,
|
|
2021
|
+
elapsedMs: 1247
|
|
2022
|
+
},
|
|
2023
|
+
uptime: {
|
|
2024
|
+
seconds: 7200,
|
|
2025
|
+
startTime: new Date(startTime).toISOString(),
|
|
2026
|
+
lastRequest: new Date(now - 2500).toISOString()
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
async queryLogs(filters) {
|
|
2031
|
+
await delay2(200);
|
|
2032
|
+
const now = Date.now();
|
|
2033
|
+
const oldest = new Date(now - 36e5).toISOString();
|
|
2034
|
+
const newest = new Date(now).toISOString();
|
|
2035
|
+
const mockLogs = Array.from({ length: filters.limit || 20 }, (_, i) => ({
|
|
2036
|
+
time: new Date(now - i * 6e4).toISOString(),
|
|
2037
|
+
level: ["info", "warn", "error"][i % 3],
|
|
2038
|
+
msg: `Mock log entry ${i + 1}`,
|
|
2039
|
+
plugin: filters.plugin || ["rest-api", "workflow", "mind"][i % 3],
|
|
2040
|
+
executionId: filters.executionId || `exec-${i}`,
|
|
2041
|
+
tenantId: filters.tenantId || "default",
|
|
2042
|
+
meta: { mockData: true, index: i }
|
|
2043
|
+
}));
|
|
2044
|
+
return {
|
|
2045
|
+
ok: true,
|
|
2046
|
+
data: {
|
|
2047
|
+
logs: mockLogs,
|
|
2048
|
+
total: 42,
|
|
2049
|
+
filters,
|
|
2050
|
+
bufferStats: {
|
|
2051
|
+
size: mockLogs.length,
|
|
2052
|
+
maxSize: 1e3,
|
|
2053
|
+
oldest,
|
|
2054
|
+
newest
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
subscribeToSystemEvents(onEvent, _onError) {
|
|
2060
|
+
const interval = setInterval(() => {
|
|
2061
|
+
const now = Date.now();
|
|
2062
|
+
if (Math.random() > 0.5) {
|
|
2063
|
+
onEvent({
|
|
2064
|
+
type: "health",
|
|
2065
|
+
status: "healthy",
|
|
2066
|
+
ts: new Date(now).toISOString(),
|
|
2067
|
+
ready: true,
|
|
2068
|
+
reason: null,
|
|
2069
|
+
registryPartial: false,
|
|
2070
|
+
registryStale: false,
|
|
2071
|
+
registryLoaded: true,
|
|
2072
|
+
pluginMountInProgress: false,
|
|
2073
|
+
pluginRoutesMounted: true,
|
|
2074
|
+
pluginsMounted: 7,
|
|
2075
|
+
pluginsFailed: 1,
|
|
2076
|
+
lastPluginMountTs: new Date(now - 6e4).toISOString(),
|
|
2077
|
+
pluginRoutesLastDurationMs: 1247,
|
|
2078
|
+
redisEnabled: true,
|
|
2079
|
+
redisHealthy: true
|
|
2080
|
+
});
|
|
2081
|
+
} else {
|
|
2082
|
+
onEvent({
|
|
2083
|
+
type: "registry",
|
|
2084
|
+
rev: Math.random().toString(36).substring(7),
|
|
2085
|
+
generatedAt: new Date(now).toISOString(),
|
|
2086
|
+
partial: false,
|
|
2087
|
+
stale: false,
|
|
2088
|
+
expiresAt: new Date(now + 3e5).toISOString(),
|
|
2089
|
+
ttlMs: 3e5,
|
|
2090
|
+
checksum: Math.random().toString(36).substring(2, 15),
|
|
2091
|
+
checksumAlgorithm: "sha256",
|
|
2092
|
+
previousChecksum: null
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
}, 3e3);
|
|
2096
|
+
return () => {
|
|
2097
|
+
clearInterval(interval);
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
subscribeToLogs(onLog, _onError, filters) {
|
|
2101
|
+
let counter = 0;
|
|
2102
|
+
const interval = setInterval(() => {
|
|
2103
|
+
const now = Date.now();
|
|
2104
|
+
onLog({
|
|
2105
|
+
time: new Date(now).toISOString(),
|
|
2106
|
+
level: ["info", "warn", "error"][counter % 3],
|
|
2107
|
+
msg: `Mock live log ${counter + 1}`,
|
|
2108
|
+
plugin: filters?.plugin || ["rest-api", "workflow", "mind"][counter % 3],
|
|
2109
|
+
executionId: filters?.executionId || `exec-${counter}`,
|
|
2110
|
+
tenantId: filters?.tenantId || "default",
|
|
2111
|
+
meta: { mockData: true, counter }
|
|
2112
|
+
});
|
|
2113
|
+
counter++;
|
|
2114
|
+
}, 2e3);
|
|
2115
|
+
return () => {
|
|
2116
|
+
clearInterval(interval);
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
async getLog(_id, includeRelated) {
|
|
2120
|
+
await delay2(200);
|
|
2121
|
+
const now = Date.now();
|
|
2122
|
+
const mockLog = {
|
|
2123
|
+
id: _id,
|
|
2124
|
+
time: new Date(now - 12e4).toISOString(),
|
|
2125
|
+
// 2 minutes ago
|
|
2126
|
+
level: "error",
|
|
2127
|
+
msg: `Mock log entry with id ${_id}`,
|
|
2128
|
+
plugin: "rest-api",
|
|
2129
|
+
executionId: `exec-${_id}`,
|
|
2130
|
+
tenantId: "default",
|
|
2131
|
+
requestId: `req-${_id}`,
|
|
2132
|
+
traceId: `trace-${_id}`,
|
|
2133
|
+
err: {
|
|
2134
|
+
name: "MockError",
|
|
2135
|
+
message: "This is a mock error for demonstration",
|
|
2136
|
+
stack: `MockError: This is a mock error for demonstration
|
|
2137
|
+
at mockFunction (mock.ts:42)
|
|
2138
|
+
at handler (handler.ts:89)
|
|
2139
|
+
at process (process.ts:123)`
|
|
2140
|
+
},
|
|
2141
|
+
meta: { mockData: true }
|
|
2142
|
+
};
|
|
2143
|
+
let related;
|
|
2144
|
+
if (includeRelated) {
|
|
2145
|
+
related = Array.from({ length: 3 }, (_, i) => ({
|
|
2146
|
+
id: `${_id}-related-${i}`,
|
|
2147
|
+
time: new Date(now - (12e4 + i * 1e3)).toISOString(),
|
|
2148
|
+
level: ["info", "debug", "error"][i % 3],
|
|
2149
|
+
msg: `Mock related log ${i + 1}`,
|
|
2150
|
+
plugin: "rest-api",
|
|
2151
|
+
executionId: mockLog.executionId,
|
|
2152
|
+
requestId: mockLog.requestId,
|
|
2153
|
+
traceId: mockLog.traceId,
|
|
2154
|
+
meta: { mockData: true, relatedIndex: i }
|
|
2155
|
+
}));
|
|
2156
|
+
}
|
|
2157
|
+
return { log: mockLog, related };
|
|
2158
|
+
}
|
|
2159
|
+
async getRelatedLogs(_id) {
|
|
2160
|
+
await delay2(150);
|
|
2161
|
+
const now = Date.now();
|
|
2162
|
+
const mockLogs = Array.from({ length: 5 }, (_, i) => ({
|
|
2163
|
+
id: `${_id}-related-${i}`,
|
|
2164
|
+
time: new Date(now - (12e4 + i * 1e3)).toISOString(),
|
|
2165
|
+
level: ["info", "debug", "warn", "error"][i % 4],
|
|
2166
|
+
msg: `Mock related log ${i + 1}`,
|
|
2167
|
+
plugin: "rest-api",
|
|
2168
|
+
executionId: `exec-${_id}`,
|
|
2169
|
+
requestId: `req-${_id}`,
|
|
2170
|
+
traceId: `trace-${_id}`,
|
|
2171
|
+
meta: { mockData: true, relatedIndex: i }
|
|
2172
|
+
}));
|
|
2173
|
+
return {
|
|
2174
|
+
total: mockLogs.length,
|
|
2175
|
+
logs: mockLogs,
|
|
2176
|
+
correlationKeys: {
|
|
2177
|
+
requestId: `req-${_id}`,
|
|
2178
|
+
traceId: `trace-${_id}`,
|
|
2179
|
+
executionId: `exec-${_id}`
|
|
2180
|
+
}
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
async summarizeLogs(request) {
|
|
2184
|
+
await delay2(1500);
|
|
2185
|
+
return {
|
|
2186
|
+
ok: true,
|
|
2187
|
+
data: {
|
|
2188
|
+
summary: {
|
|
2189
|
+
question: request.question,
|
|
2190
|
+
timeRange: {
|
|
2191
|
+
from: request.timeRange?.from || null,
|
|
2192
|
+
to: request.timeRange?.to || null
|
|
2193
|
+
},
|
|
2194
|
+
total: 42,
|
|
2195
|
+
stats: {
|
|
2196
|
+
total: 42,
|
|
2197
|
+
byLevel: {
|
|
2198
|
+
error: 3,
|
|
2199
|
+
warn: 5,
|
|
2200
|
+
info: 34
|
|
2201
|
+
},
|
|
2202
|
+
byPlugin: {
|
|
2203
|
+
"rest-api": 20,
|
|
2204
|
+
"workflow": 15,
|
|
2205
|
+
"mind": 7
|
|
2206
|
+
},
|
|
2207
|
+
topErrors: [
|
|
2208
|
+
{ message: "Connection timeout", count: 2 },
|
|
2209
|
+
{ message: "Invalid parameter", count: 1 }
|
|
2210
|
+
],
|
|
2211
|
+
timeRange: {
|
|
2212
|
+
from: request.timeRange?.from || null,
|
|
2213
|
+
to: request.timeRange?.to || null
|
|
2214
|
+
}
|
|
2215
|
+
},
|
|
2216
|
+
groups: null
|
|
2217
|
+
},
|
|
2218
|
+
aiSummary: `## Mock AI Summary
|
|
2219
|
+
|
|
2220
|
+
Based on the analysis of ${42} log entries:
|
|
2221
|
+
|
|
2222
|
+
**Key Findings:**
|
|
2223
|
+
- Total of 3 errors and 5 warnings detected
|
|
2224
|
+
- Most active component: rest-api (20 logs)
|
|
2225
|
+
- Primary issue: Connection timeouts (2 occurrences)
|
|
2226
|
+
|
|
2227
|
+
**Timeline:**
|
|
2228
|
+
The system has been mostly stable with info-level logs. Two connection timeout errors were observed, likely related to external service availability.
|
|
2229
|
+
|
|
2230
|
+
**Recommendations:**
|
|
2231
|
+
1. Investigate network connectivity to external services
|
|
2232
|
+
2. Consider implementing retry logic with exponential backoff
|
|
2233
|
+
3. Monitor timeout patterns over next 24 hours
|
|
2234
|
+
|
|
2235
|
+
*Note: This is mock data from MockObservabilitySource*`,
|
|
2236
|
+
message: null
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
async getMetricsHistory(query) {
|
|
2241
|
+
await delay2(100);
|
|
2242
|
+
const now = Date.now();
|
|
2243
|
+
const rangeMs = {
|
|
2244
|
+
"1m": 60 * 1e3,
|
|
2245
|
+
"5m": 5 * 60 * 1e3,
|
|
2246
|
+
"10m": 10 * 60 * 1e3,
|
|
2247
|
+
"30m": 30 * 60 * 1e3,
|
|
2248
|
+
"1h": 60 * 60 * 1e3
|
|
2249
|
+
}[query.range];
|
|
2250
|
+
const intervalMs = query.interval === "1m" ? 60 * 1e3 : query.interval === "5m" ? 5 * 60 * 1e3 : 5e3;
|
|
2251
|
+
const points = [];
|
|
2252
|
+
for (let t = now - rangeMs; t <= now; t += intervalMs) {
|
|
2253
|
+
let value = 0;
|
|
2254
|
+
switch (query.metric) {
|
|
2255
|
+
case "requests":
|
|
2256
|
+
value = 100 + Math.floor(Math.random() * 50);
|
|
2257
|
+
break;
|
|
2258
|
+
case "errors":
|
|
2259
|
+
value = Math.floor(Math.random() * 5);
|
|
2260
|
+
break;
|
|
2261
|
+
case "latency":
|
|
2262
|
+
value = 30 + Math.floor(Math.random() * 20);
|
|
2263
|
+
break;
|
|
2264
|
+
case "uptime":
|
|
2265
|
+
value = (now - t) / 1e3;
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
points.push({ timestamp: t, value });
|
|
2269
|
+
}
|
|
2270
|
+
return points;
|
|
2271
|
+
}
|
|
2272
|
+
async getMetricsHeatmap(query) {
|
|
2273
|
+
await delay2(150);
|
|
2274
|
+
const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
2275
|
+
const cells = [];
|
|
2276
|
+
for (const day of days) {
|
|
2277
|
+
for (let hour = 0; hour < 24; hour++) {
|
|
2278
|
+
cells.push({ day, hour, value: mockHeatmapValue(query.metric, hour) });
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
return cells;
|
|
2282
|
+
}
|
|
2283
|
+
async queryIncidents(query) {
|
|
2284
|
+
await delay2(200);
|
|
2285
|
+
const mockIncidents = [
|
|
2286
|
+
{
|
|
2287
|
+
id: "inc-1",
|
|
2288
|
+
type: "latency_spike",
|
|
2289
|
+
severity: "warning",
|
|
2290
|
+
title: "Elevated API Latency",
|
|
2291
|
+
details: "Average latency increased to 250ms (baseline: 50ms)",
|
|
2292
|
+
rootCause: [
|
|
2293
|
+
{
|
|
2294
|
+
factor: "Database connection pool exhaustion",
|
|
2295
|
+
confidence: 0.85,
|
|
2296
|
+
evidence: "Connection pool utilization at 100%, wait queue growing"
|
|
2297
|
+
},
|
|
2298
|
+
{
|
|
2299
|
+
factor: "Increased query complexity",
|
|
2300
|
+
confidence: 0.6,
|
|
2301
|
+
evidence: "Average query time increased 3x in the last hour"
|
|
2302
|
+
}
|
|
2303
|
+
],
|
|
2304
|
+
affectedServices: ["rest-api", "workflow-runtime"],
|
|
2305
|
+
timestamp: Date.now() - 2 * 60 * 60 * 1e3,
|
|
2306
|
+
// 2 hours ago
|
|
2307
|
+
resolvedAt: Date.now() - 1 * 60 * 60 * 1e3,
|
|
2308
|
+
// 1 hour ago
|
|
2309
|
+
resolutionNotes: "Increased connection pool size from 10 to 20",
|
|
2310
|
+
metadata: { peakLatency: 350, duration: "45m" }
|
|
2311
|
+
},
|
|
2312
|
+
{
|
|
2313
|
+
id: "inc-2",
|
|
2314
|
+
type: "error_rate",
|
|
2315
|
+
severity: "critical",
|
|
2316
|
+
title: "High Error Rate in Mind Plugin",
|
|
2317
|
+
details: "Error rate spiked to 15% (baseline: <1%)",
|
|
2318
|
+
affectedServices: ["mind-engine"],
|
|
2319
|
+
timestamp: Date.now() - 30 * 60 * 1e3,
|
|
2320
|
+
// 30 minutes ago
|
|
2321
|
+
metadata: { errorCount: 47, requestCount: 310 }
|
|
2322
|
+
},
|
|
2323
|
+
{
|
|
2324
|
+
id: "inc-3",
|
|
2325
|
+
type: "plugin_failure",
|
|
2326
|
+
severity: "warning",
|
|
2327
|
+
title: "Plugin Mount Timeout",
|
|
2328
|
+
details: "workflow plugin took 15s to mount (expected: <3s)",
|
|
2329
|
+
affectedServices: ["workflow-runtime"],
|
|
2330
|
+
timestamp: Date.now() - 10 * 60 * 1e3,
|
|
2331
|
+
// 10 minutes ago
|
|
2332
|
+
resolvedAt: Date.now() - 5 * 60 * 1e3,
|
|
2333
|
+
// 5 minutes ago
|
|
2334
|
+
resolutionNotes: "Plugin restarted successfully"
|
|
2335
|
+
}
|
|
2336
|
+
];
|
|
2337
|
+
let filtered = [...mockIncidents];
|
|
2338
|
+
if (query?.severity) {
|
|
2339
|
+
const severityList = Array.isArray(query.severity) ? query.severity : [query.severity];
|
|
2340
|
+
filtered = filtered.filter((inc) => severityList.includes(inc.severity));
|
|
2341
|
+
}
|
|
2342
|
+
if (query?.type) {
|
|
2343
|
+
const typeList = Array.isArray(query.type) ? query.type : [query.type];
|
|
2344
|
+
filtered = filtered.filter((inc) => typeList.includes(inc.type));
|
|
2345
|
+
}
|
|
2346
|
+
if (query?.from) {
|
|
2347
|
+
filtered = filtered.filter((inc) => inc.timestamp >= query.from);
|
|
2348
|
+
}
|
|
2349
|
+
if (query?.to) {
|
|
2350
|
+
filtered = filtered.filter((inc) => inc.timestamp <= query.to);
|
|
2351
|
+
}
|
|
2352
|
+
if (!query?.includeResolved) {
|
|
2353
|
+
filtered = filtered.filter((inc) => !inc.resolvedAt);
|
|
2354
|
+
}
|
|
2355
|
+
if (query?.limit) {
|
|
2356
|
+
filtered = filtered.slice(0, query.limit);
|
|
2357
|
+
}
|
|
2358
|
+
return filtered;
|
|
2359
|
+
}
|
|
2360
|
+
async createIncident(payload) {
|
|
2361
|
+
await delay2(150);
|
|
2362
|
+
const incident = {
|
|
2363
|
+
id: `inc-${Date.now()}`,
|
|
2364
|
+
...payload,
|
|
2365
|
+
timestamp: payload.timestamp || Date.now()
|
|
2366
|
+
};
|
|
2367
|
+
return incident;
|
|
2368
|
+
}
|
|
2369
|
+
async resolveIncident(_id, resolutionNotes) {
|
|
2370
|
+
await delay2(150);
|
|
2371
|
+
const incident = {
|
|
2372
|
+
id: _id,
|
|
2373
|
+
type: "error_rate",
|
|
2374
|
+
severity: "critical",
|
|
2375
|
+
title: "Mock Incident",
|
|
2376
|
+
details: "Mock incident details",
|
|
2377
|
+
timestamp: Date.now() - 60 * 60 * 1e3,
|
|
2378
|
+
resolvedAt: Date.now(),
|
|
2379
|
+
resolutionNotes
|
|
2380
|
+
};
|
|
2381
|
+
return incident;
|
|
2382
|
+
}
|
|
2383
|
+
async listIncidents(query) {
|
|
2384
|
+
await delay2(150);
|
|
2385
|
+
const incidents = await this.queryIncidents(query);
|
|
2386
|
+
const unresolved = incidents.filter((i) => !i.resolvedAt).length;
|
|
2387
|
+
return {
|
|
2388
|
+
ok: true,
|
|
2389
|
+
data: {
|
|
2390
|
+
incidents,
|
|
2391
|
+
summary: {
|
|
2392
|
+
total: incidents.length,
|
|
2393
|
+
unresolved,
|
|
2394
|
+
bySeverity: {
|
|
2395
|
+
critical: incidents.filter((i) => i.severity === "critical").length,
|
|
2396
|
+
warning: incidents.filter((i) => i.severity === "warning").length,
|
|
2397
|
+
info: incidents.filter((i) => i.severity === "info").length
|
|
2398
|
+
},
|
|
2399
|
+
showing: incidents.length
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
async getIncident(_id) {
|
|
2405
|
+
await delay2(150);
|
|
2406
|
+
const incident = {
|
|
2407
|
+
id: _id,
|
|
2408
|
+
type: "error_rate",
|
|
2409
|
+
severity: "critical",
|
|
2410
|
+
title: "High Error Rate Detected",
|
|
2411
|
+
details: "Error rate exceeded threshold of 10%",
|
|
2412
|
+
timestamp: Date.now() - 60 * 60 * 1e3,
|
|
2413
|
+
affectedServices: ["rest-api", "workflow-engine"],
|
|
2414
|
+
metadata: {
|
|
2415
|
+
errorRate: 15.2,
|
|
2416
|
+
threshold: 10
|
|
2417
|
+
},
|
|
2418
|
+
relatedData: {
|
|
2419
|
+
logs: {
|
|
2420
|
+
errorCount: 42,
|
|
2421
|
+
warnCount: 18,
|
|
2422
|
+
timeRange: [Date.now() - 5 * 60 * 1e3, Date.now()],
|
|
2423
|
+
sampleErrors: [
|
|
2424
|
+
"TypeError: Cannot read property 'name' of undefined",
|
|
2425
|
+
"Error: Failed to connect to database",
|
|
2426
|
+
"Error: Timeout exceeded for operation"
|
|
2427
|
+
]
|
|
2428
|
+
},
|
|
2429
|
+
metrics: {
|
|
2430
|
+
before: {
|
|
2431
|
+
errorRate: 2.1,
|
|
2432
|
+
avgLatency: 45,
|
|
2433
|
+
totalRequests: 1e3
|
|
2434
|
+
},
|
|
2435
|
+
during: {
|
|
2436
|
+
errorRate: 15.2,
|
|
2437
|
+
avgLatency: 120,
|
|
2438
|
+
totalRequests: 500,
|
|
2439
|
+
totalErrors: 76
|
|
2440
|
+
}
|
|
2441
|
+
},
|
|
2442
|
+
timeline: [
|
|
2443
|
+
{
|
|
2444
|
+
timestamp: Date.now(),
|
|
2445
|
+
event: "Incident detected: error_rate",
|
|
2446
|
+
source: "detector"
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
timestamp: Date.now() - 2 * 60 * 1e3,
|
|
2450
|
+
event: "Error: Failed to connect to database",
|
|
2451
|
+
source: "logs"
|
|
2452
|
+
},
|
|
2453
|
+
{
|
|
2454
|
+
timestamp: Date.now() - 3 * 60 * 1e3,
|
|
2455
|
+
event: "Error: Timeout exceeded for operation",
|
|
2456
|
+
source: "logs"
|
|
2457
|
+
}
|
|
2458
|
+
]
|
|
2459
|
+
}
|
|
2460
|
+
};
|
|
2461
|
+
return {
|
|
2462
|
+
ok: true,
|
|
2463
|
+
data: incident
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
async analyzeIncident(_id) {
|
|
2467
|
+
await delay2(800);
|
|
2468
|
+
return {
|
|
2469
|
+
ok: true,
|
|
2470
|
+
data: {
|
|
2471
|
+
summary: "High error rate detected due to database connection issues and timeout problems. System experienced 15.2% error rate, significantly above the 2.1% baseline.",
|
|
2472
|
+
rootCauses: [
|
|
2473
|
+
{
|
|
2474
|
+
factor: "Database connection pool exhaustion",
|
|
2475
|
+
confidence: 0.85,
|
|
2476
|
+
evidence: 'Multiple "Failed to connect to database" errors in logs. Error rate correlation with database connection attempts.'
|
|
2477
|
+
},
|
|
2478
|
+
{
|
|
2479
|
+
factor: "Increased request timeout frequency",
|
|
2480
|
+
confidence: 0.72,
|
|
2481
|
+
evidence: "Timeout errors increased from 0% to 8% during incident window. Latency jumped from 45ms to 120ms."
|
|
2482
|
+
},
|
|
2483
|
+
{
|
|
2484
|
+
factor: "Potential memory leak in error handler",
|
|
2485
|
+
confidence: 0.45,
|
|
2486
|
+
evidence: "TypeError suggests undefined object access, possibly due to improper error handling initialization."
|
|
2487
|
+
}
|
|
2488
|
+
],
|
|
2489
|
+
patterns: [
|
|
2490
|
+
"Error rate spike correlates with increased latency (45ms \u2192 120ms)",
|
|
2491
|
+
"Database connection errors preceded timeout errors by ~1 minute",
|
|
2492
|
+
"Error rate increased 7x above baseline during incident"
|
|
2493
|
+
],
|
|
2494
|
+
recommendations: [
|
|
2495
|
+
"Increase database connection pool size and implement connection retry logic with exponential backoff",
|
|
2496
|
+
"Add circuit breaker pattern to prevent cascading failures when database is unavailable",
|
|
2497
|
+
"Review error handling code for undefined object access, especially in database connection error paths",
|
|
2498
|
+
"Implement request timeout monitoring and alerting at 75% of threshold",
|
|
2499
|
+
"Consider adding database health check endpoint to proactively detect connection issues"
|
|
2500
|
+
],
|
|
2501
|
+
analyzedAt: Date.now(),
|
|
2502
|
+
cached: false
|
|
2503
|
+
}
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
async chatWithInsights(question, context) {
|
|
2507
|
+
await delay2(500);
|
|
2508
|
+
return {
|
|
2509
|
+
answer: `Based on the current system metrics, here's my analysis of "${question}": The system is performing well with average latency of 45ms and 99.2% success rate. No critical issues detected.`,
|
|
2510
|
+
context: [
|
|
2511
|
+
"Current metrics: 1,234 requests in the last hour",
|
|
2512
|
+
"Average latency: 45ms",
|
|
2513
|
+
"Error rate: 0.8%",
|
|
2514
|
+
context?.includeIncidents ? "2 active incidents" : void 0
|
|
2515
|
+
].filter(Boolean),
|
|
2516
|
+
usage: {
|
|
2517
|
+
promptTokens: 150,
|
|
2518
|
+
completionTokens: 80,
|
|
2519
|
+
totalTokens: 230
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
2524
|
+
function mockHeatmapValue(metric, hour) {
|
|
2525
|
+
const isBusinessHour = hour >= 9 && hour <= 17;
|
|
2526
|
+
if (metric === "latency") {
|
|
2527
|
+
return 30 + Math.floor(Math.random() * 40) + (isBusinessHour ? 10 : 0);
|
|
2528
|
+
}
|
|
2529
|
+
if (metric === "errors") {
|
|
2530
|
+
return Math.floor(Math.random() * 10);
|
|
2531
|
+
}
|
|
2532
|
+
if (metric === "requests") {
|
|
2533
|
+
return 50 + Math.floor(Math.random() * 100) + (isBusinessHour ? 50 : 0);
|
|
2534
|
+
}
|
|
2535
|
+
return 0;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// src/mocks/mock-analytics-source.ts
|
|
2539
|
+
function delay3(ms) {
|
|
2540
|
+
return new Promise((resolve) => {
|
|
2541
|
+
setTimeout(() => resolve(), ms);
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
var MockAnalyticsSource = class {
|
|
2545
|
+
mockEvents;
|
|
2546
|
+
constructor() {
|
|
2547
|
+
this.mockEvents = this.generateMockEvents();
|
|
2548
|
+
}
|
|
2549
|
+
generateMockEvents() {
|
|
2550
|
+
const now = Date.now();
|
|
2551
|
+
const events = [];
|
|
2552
|
+
const eventTypes = [
|
|
2553
|
+
"mind.query.started",
|
|
2554
|
+
"mind.query.completed",
|
|
2555
|
+
"workflow.run.started",
|
|
2556
|
+
"workflow.run.completed",
|
|
2557
|
+
"commit.generated",
|
|
2558
|
+
"plugin.mounted"
|
|
2559
|
+
];
|
|
2560
|
+
const sources = ["mind", "workflow", "commit", "core"];
|
|
2561
|
+
const actors = ["user", "agent", "ci"];
|
|
2562
|
+
for (let i = 0; i < 100; i++) {
|
|
2563
|
+
const ts = new Date(now - i * 6e4).toISOString();
|
|
2564
|
+
const type = eventTypes[Math.floor(Math.random() * eventTypes.length)];
|
|
2565
|
+
const source = sources[Math.floor(Math.random() * sources.length)];
|
|
2566
|
+
const actorType = actors[Math.floor(Math.random() * actors.length)];
|
|
2567
|
+
events.push({
|
|
2568
|
+
id: `mock-event-${i}`,
|
|
2569
|
+
schema: "kb.v1",
|
|
2570
|
+
type,
|
|
2571
|
+
ts,
|
|
2572
|
+
ingestTs: ts,
|
|
2573
|
+
source: {
|
|
2574
|
+
product: source,
|
|
2575
|
+
version: "1.0.0"
|
|
2576
|
+
},
|
|
2577
|
+
runId: `run-${Math.floor(i / 5)}`,
|
|
2578
|
+
// Group every 5 events
|
|
2579
|
+
actor: {
|
|
2580
|
+
type: actorType,
|
|
2581
|
+
id: `${actorType}-${Math.floor(Math.random() * 10)}`,
|
|
2582
|
+
name: `Mock ${actorType}`
|
|
2583
|
+
},
|
|
2584
|
+
ctx: {
|
|
2585
|
+
workspace: "/mock/workspace",
|
|
2586
|
+
branch: "main"
|
|
2587
|
+
},
|
|
2588
|
+
payload: {
|
|
2589
|
+
duration: Math.random() * 1e3,
|
|
2590
|
+
success: Math.random() > 0.1
|
|
2591
|
+
}
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
return events;
|
|
2595
|
+
}
|
|
2596
|
+
async getEvents(query) {
|
|
2597
|
+
await delay3(150);
|
|
2598
|
+
let filtered = [...this.mockEvents];
|
|
2599
|
+
if (query?.type) {
|
|
2600
|
+
const types = Array.isArray(query.type) ? query.type : [query.type];
|
|
2601
|
+
filtered = filtered.filter((e) => types.includes(e.type));
|
|
2602
|
+
}
|
|
2603
|
+
if (query?.source) {
|
|
2604
|
+
filtered = filtered.filter((e) => e.source.product === query.source);
|
|
2605
|
+
}
|
|
2606
|
+
if (query?.actor) {
|
|
2607
|
+
filtered = filtered.filter((e) => e.actor?.type === query.actor);
|
|
2608
|
+
}
|
|
2609
|
+
const limit = query?.limit || 100;
|
|
2610
|
+
const offset = query?.offset || 0;
|
|
2611
|
+
const paginated = filtered.slice(offset, offset + limit);
|
|
2612
|
+
return {
|
|
2613
|
+
events: paginated,
|
|
2614
|
+
total: filtered.length,
|
|
2615
|
+
hasMore: offset + limit < filtered.length
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
async getStats() {
|
|
2619
|
+
await delay3(180);
|
|
2620
|
+
const byType = {};
|
|
2621
|
+
const bySource = {};
|
|
2622
|
+
const byActor = {};
|
|
2623
|
+
this.mockEvents.forEach((event) => {
|
|
2624
|
+
byType[event.type] = (byType[event.type] || 0) + 1;
|
|
2625
|
+
bySource[event.source.product] = (bySource[event.source.product] || 0) + 1;
|
|
2626
|
+
if (event.actor) {
|
|
2627
|
+
byActor[event.actor.type] = (byActor[event.actor.type] || 0) + 1;
|
|
2628
|
+
}
|
|
2629
|
+
});
|
|
2630
|
+
const timestamps = this.mockEvents.map((e) => new Date(e.ts).getTime());
|
|
2631
|
+
const oldestTs = Math.min(...timestamps);
|
|
2632
|
+
const newestTs = Math.max(...timestamps);
|
|
2633
|
+
return {
|
|
2634
|
+
totalEvents: this.mockEvents.length,
|
|
2635
|
+
byType,
|
|
2636
|
+
bySource,
|
|
2637
|
+
byActor,
|
|
2638
|
+
timeRange: {
|
|
2639
|
+
from: new Date(oldestTs).toISOString(),
|
|
2640
|
+
to: new Date(newestTs).toISOString()
|
|
2641
|
+
}
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
async getBufferStatus() {
|
|
2645
|
+
await delay3(100);
|
|
2646
|
+
const timestamps = this.mockEvents.map((e) => new Date(e.ts).getTime());
|
|
2647
|
+
return {
|
|
2648
|
+
segments: 3,
|
|
2649
|
+
totalSizeBytes: 524288,
|
|
2650
|
+
// 512 KB
|
|
2651
|
+
oldestEventTs: new Date(Math.min(...timestamps)).toISOString(),
|
|
2652
|
+
newestEventTs: new Date(Math.max(...timestamps)).toISOString()
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
async getDlqStatus() {
|
|
2656
|
+
await delay3(100);
|
|
2657
|
+
return {
|
|
2658
|
+
failedEvents: 2,
|
|
2659
|
+
oldestFailureTs: new Date(Date.now() - 36e5).toISOString()
|
|
2660
|
+
// 1 hour ago
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
|
|
2665
|
+
// src/mocks/mock-adapters-source.ts
|
|
2666
|
+
var MockAdaptersSource = class {
|
|
2667
|
+
async getLLMUsage(_options) {
|
|
2668
|
+
return {
|
|
2669
|
+
totalRequests: 1247,
|
|
2670
|
+
totalTokens: 3456789,
|
|
2671
|
+
totalCost: 12.45,
|
|
2672
|
+
totalCacheReadTokens: 0,
|
|
2673
|
+
totalBillableTokens: 3456789,
|
|
2674
|
+
totalCacheSavingsUsd: 0,
|
|
2675
|
+
byModel: {
|
|
2676
|
+
"gpt-4": {
|
|
2677
|
+
requests: 234,
|
|
2678
|
+
promptTokens: 45678,
|
|
2679
|
+
completionTokens: 23456,
|
|
2680
|
+
totalTokens: 69134,
|
|
2681
|
+
cost: 4.15,
|
|
2682
|
+
costPer1KTokens: 0.06,
|
|
2683
|
+
tokensPerRequest: 295,
|
|
2684
|
+
errorRate: 0.5,
|
|
2685
|
+
avgDurationMs: 2345,
|
|
2686
|
+
cacheReadTokens: 0,
|
|
2687
|
+
billableTokens: 69134,
|
|
2688
|
+
cacheSavingsUsd: 0
|
|
2689
|
+
},
|
|
2690
|
+
"gpt-3.5-turbo": {
|
|
2691
|
+
requests: 789,
|
|
2692
|
+
promptTokens: 234567,
|
|
2693
|
+
completionTokens: 123456,
|
|
2694
|
+
totalTokens: 358023,
|
|
2695
|
+
cost: 0.54,
|
|
2696
|
+
costPer1KTokens: 15e-4,
|
|
2697
|
+
tokensPerRequest: 454,
|
|
2698
|
+
errorRate: 0,
|
|
2699
|
+
avgDurationMs: 876,
|
|
2700
|
+
cacheReadTokens: 0,
|
|
2701
|
+
billableTokens: 358023,
|
|
2702
|
+
cacheSavingsUsd: 0
|
|
2703
|
+
},
|
|
2704
|
+
"claude-3-sonnet": {
|
|
2705
|
+
requests: 224,
|
|
2706
|
+
promptTokens: 567890,
|
|
2707
|
+
completionTokens: 234567,
|
|
2708
|
+
totalTokens: 802457,
|
|
2709
|
+
cost: 7.76,
|
|
2710
|
+
costPer1KTokens: 97e-4,
|
|
2711
|
+
tokensPerRequest: 3582,
|
|
2712
|
+
errorRate: 1.2,
|
|
2713
|
+
avgDurationMs: 1567,
|
|
2714
|
+
cacheReadTokens: 0,
|
|
2715
|
+
billableTokens: 802457,
|
|
2716
|
+
cacheSavingsUsd: 0
|
|
2717
|
+
}
|
|
2718
|
+
},
|
|
2719
|
+
errors: 12,
|
|
2720
|
+
timeRange: {
|
|
2721
|
+
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
2722
|
+
to: (/* @__PURE__ */ new Date()).toISOString()
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
async getEmbeddingsUsage(_options) {
|
|
2727
|
+
return {
|
|
2728
|
+
totalRequests: 3456,
|
|
2729
|
+
totalTextLength: 1234567,
|
|
2730
|
+
totalCost: 2.47,
|
|
2731
|
+
errors: 8,
|
|
2732
|
+
avgDurationMs: 234,
|
|
2733
|
+
batchRequests: 456,
|
|
2734
|
+
singleRequests: 3e3,
|
|
2735
|
+
avgBatchSize: 15.3
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
async getVectorStoreUsage(_options) {
|
|
2739
|
+
return {
|
|
2740
|
+
searchQueries: 2345,
|
|
2741
|
+
upsertOperations: 567,
|
|
2742
|
+
deleteOperations: 89,
|
|
2743
|
+
avgSearchDuration: 45.6,
|
|
2744
|
+
avgSearchScore: 0.87,
|
|
2745
|
+
avgResultsCount: 12.4,
|
|
2746
|
+
totalVectorsUpserted: 45678,
|
|
2747
|
+
totalVectorsDeleted: 1234
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
async getCacheUsage(_options) {
|
|
2751
|
+
return {
|
|
2752
|
+
totalGets: 15678,
|
|
2753
|
+
hits: 13456,
|
|
2754
|
+
misses: 2222,
|
|
2755
|
+
hitRate: 85.8,
|
|
2756
|
+
sets: 3456,
|
|
2757
|
+
avgGetDuration: 2.3,
|
|
2758
|
+
avgSetDuration: 5.7
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
async getStorageUsage(_options) {
|
|
2762
|
+
return {
|
|
2763
|
+
readOperations: 8765,
|
|
2764
|
+
writeOperations: 2345,
|
|
2765
|
+
deleteOperations: 234,
|
|
2766
|
+
totalBytesRead: 123456789,
|
|
2767
|
+
totalBytesWritten: 45678901,
|
|
2768
|
+
avgReadDuration: 12.4,
|
|
2769
|
+
avgWriteDuration: 34.5
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
async getLLMDailyStats(_options) {
|
|
2773
|
+
return this.generateMockDailyStats({
|
|
2774
|
+
totalTokens: [1e4, 6e4],
|
|
2775
|
+
totalCost: [1, 6],
|
|
2776
|
+
avgDurationMs: [500, 2500]
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
async getEmbeddingsDailyStats(_options) {
|
|
2780
|
+
return this.generateMockDailyStats({
|
|
2781
|
+
totalTokens: [5e3, 3e4],
|
|
2782
|
+
totalCost: [0.1, 0.5],
|
|
2783
|
+
avgDurationMs: [100, 500]
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
async getVectorStoreDailyStats(_options) {
|
|
2787
|
+
return this.generateMockDailyStats({
|
|
2788
|
+
totalSearches: [100, 500],
|
|
2789
|
+
totalUpserts: [50, 200],
|
|
2790
|
+
totalDeletes: [10, 50],
|
|
2791
|
+
avgDurationMs: [30, 150]
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
async getCacheDailyStats(_options) {
|
|
2795
|
+
return this.generateMockDailyStats({
|
|
2796
|
+
totalHits: [500, 2e3],
|
|
2797
|
+
totalMisses: [100, 500],
|
|
2798
|
+
totalSets: [200, 800],
|
|
2799
|
+
hitRate: [70, 95]
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
async getStorageDailyStats(_options) {
|
|
2803
|
+
return this.generateMockDailyStats({
|
|
2804
|
+
totalBytesRead: [1e6, 1e7],
|
|
2805
|
+
totalBytesWritten: [5e5, 5e6],
|
|
2806
|
+
avgDurationMs: [10, 50]
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Helper to generate mock daily stats
|
|
2811
|
+
*/
|
|
2812
|
+
generateMockDailyStats(metricsRanges) {
|
|
2813
|
+
const days = 7;
|
|
2814
|
+
const stats = [];
|
|
2815
|
+
const now = /* @__PURE__ */ new Date();
|
|
2816
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
2817
|
+
const date = new Date(now);
|
|
2818
|
+
date.setDate(date.getDate() - i);
|
|
2819
|
+
const dateStr = date.toISOString().split("T")[0];
|
|
2820
|
+
const metrics = {};
|
|
2821
|
+
for (const [key, [min, max]] of Object.entries(metricsRanges)) {
|
|
2822
|
+
metrics[key] = Math.floor(Math.random() * (max - min)) + min;
|
|
2823
|
+
}
|
|
2824
|
+
stats.push({
|
|
2825
|
+
date: dateStr,
|
|
2826
|
+
count: Math.floor(Math.random() * 100) + 50,
|
|
2827
|
+
// 50-150 requests
|
|
2828
|
+
metrics
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2831
|
+
return stats;
|
|
2832
|
+
}
|
|
2833
|
+
};
|
|
2834
|
+
|
|
2835
|
+
// src/mocks/mock-platform-source.ts
|
|
2836
|
+
var MockPlatformSource = class {
|
|
2837
|
+
async getConfig() {
|
|
2838
|
+
return {
|
|
2839
|
+
schema: "kb.platform.config/1",
|
|
2840
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2841
|
+
adapters: {
|
|
2842
|
+
// Multi-adapter example: array of providers for LLM
|
|
2843
|
+
llm: ["@kb-labs/adapters-openai", "@kb-labs/adapters-vibeproxy"],
|
|
2844
|
+
embeddings: "@kb-labs/adapters-openai/embeddings",
|
|
2845
|
+
storage: "@kb-labs/adapters-fs",
|
|
2846
|
+
logger: "@kb-labs/adapters-pino",
|
|
2847
|
+
analytics: "@kb-labs/adapters-analytics-file",
|
|
2848
|
+
vectorStore: "@kb-labs/adapters-qdrant",
|
|
2849
|
+
cache: "@kb-labs/adapters-redis"
|
|
2850
|
+
},
|
|
2851
|
+
adapterOptions: {
|
|
2852
|
+
llm: {
|
|
2853
|
+
defaultModel: "gpt-4o-mini"
|
|
2854
|
+
},
|
|
2855
|
+
vectorStore: {
|
|
2856
|
+
url: "http://localhost:6333"
|
|
2857
|
+
},
|
|
2858
|
+
cache: {
|
|
2859
|
+
url: "redis://localhost:6379"
|
|
2860
|
+
}
|
|
2861
|
+
},
|
|
2862
|
+
execution: {
|
|
2863
|
+
mode: "in-process"
|
|
2864
|
+
},
|
|
2865
|
+
redacted: []
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
|
|
2870
|
+
// src/mocks/mock-plugins-source.ts
|
|
2871
|
+
var mockManifests = {
|
|
2872
|
+
manifests: [
|
|
2873
|
+
{
|
|
2874
|
+
pluginId: "@kb-labs/mind-plugin",
|
|
2875
|
+
source: {
|
|
2876
|
+
kind: "workspace",
|
|
2877
|
+
path: "/workspace/kb-labs-mind/packages/mind-plugin"
|
|
2878
|
+
},
|
|
2879
|
+
pluginRoot: "/workspace/kb-labs-mind/packages/mind-plugin",
|
|
2880
|
+
discoveredAt: new Date(Date.now() - 36e5).toISOString(),
|
|
2881
|
+
// 1 hour ago
|
|
2882
|
+
buildTimestamp: new Date(Date.now() - 72e5).toISOString(),
|
|
2883
|
+
// 2 hours ago
|
|
2884
|
+
manifest: {
|
|
2885
|
+
schema: "kb.plugin/3",
|
|
2886
|
+
id: "@kb-labs/mind-plugin",
|
|
2887
|
+
version: "0.1.0",
|
|
2888
|
+
display: {
|
|
2889
|
+
name: "Mind RAG Plugin",
|
|
2890
|
+
description: "Semantic code search and RAG system",
|
|
2891
|
+
author: "KB Labs",
|
|
2892
|
+
icon: "\u{1F9E0}",
|
|
2893
|
+
tags: ["search", "ai", "rag"]
|
|
2894
|
+
},
|
|
2895
|
+
permissions: {
|
|
2896
|
+
fs: { mode: "read", allow: [".kb/**", "src/**"] },
|
|
2897
|
+
net: { allowHosts: ["api.openai.com"] }
|
|
2898
|
+
},
|
|
2899
|
+
platform: {
|
|
2900
|
+
requires: ["vectorStore", "embeddings", "cache"]
|
|
2901
|
+
},
|
|
2902
|
+
cli: {
|
|
2903
|
+
commands: [
|
|
2904
|
+
{
|
|
2905
|
+
id: "rag-query",
|
|
2906
|
+
group: "mind",
|
|
2907
|
+
describe: "Query the RAG index",
|
|
2908
|
+
handler: "./dist/commands/rag-query.js",
|
|
2909
|
+
flags: [
|
|
2910
|
+
{ name: "text", type: "string", required: true, description: "Query text" },
|
|
2911
|
+
{ name: "agent", type: "boolean", description: "Use agent mode" }
|
|
2912
|
+
]
|
|
2913
|
+
},
|
|
2914
|
+
{
|
|
2915
|
+
id: "rag-index",
|
|
2916
|
+
group: "mind",
|
|
2917
|
+
describe: "Build RAG index",
|
|
2918
|
+
handler: "./dist/commands/rag-index.js",
|
|
2919
|
+
flags: [
|
|
2920
|
+
{ name: "scope", type: "string", default: "default", description: "Index scope" }
|
|
2921
|
+
]
|
|
2922
|
+
}
|
|
2923
|
+
]
|
|
2924
|
+
},
|
|
2925
|
+
rest: {
|
|
2926
|
+
basePath: "/v1/plugins/mind",
|
|
2927
|
+
routes: [
|
|
2928
|
+
{
|
|
2929
|
+
method: "POST",
|
|
2930
|
+
path: "/search",
|
|
2931
|
+
description: "Semantic code search",
|
|
2932
|
+
handler: "./dist/rest/search.js",
|
|
2933
|
+
timeoutMs: 3e4,
|
|
2934
|
+
input: { zod: "./schemas/search-input.ts#SearchInputSchema" },
|
|
2935
|
+
output: { zod: "./schemas/search-output.ts#SearchOutputSchema" }
|
|
2936
|
+
}
|
|
2937
|
+
]
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
pluginId: "@kb-labs/workflow-plugin",
|
|
2943
|
+
source: {
|
|
2944
|
+
kind: "workspace",
|
|
2945
|
+
path: "/workspace/kb-labs-workflow/packages/workflow-plugin"
|
|
2946
|
+
},
|
|
2947
|
+
pluginRoot: "/workspace/kb-labs-workflow/packages/workflow-plugin",
|
|
2948
|
+
discoveredAt: new Date(Date.now() - 36e5).toISOString(),
|
|
2949
|
+
// 1 hour ago
|
|
2950
|
+
buildTimestamp: new Date(Date.now() - 54e5).toISOString(),
|
|
2951
|
+
// 1.5 hours ago
|
|
2952
|
+
manifest: {
|
|
2953
|
+
schema: "kb.plugin/3",
|
|
2954
|
+
id: "@kb-labs/workflow-plugin",
|
|
2955
|
+
version: "1.0.0",
|
|
2956
|
+
display: {
|
|
2957
|
+
name: "Workflow Engine",
|
|
2958
|
+
description: "Orchestrate multi-step workflows",
|
|
2959
|
+
author: "KB Labs",
|
|
2960
|
+
icon: "\u2699\uFE0F",
|
|
2961
|
+
tags: ["workflow", "automation", "orchestration"]
|
|
2962
|
+
},
|
|
2963
|
+
permissions: {
|
|
2964
|
+
fs: { mode: "readwrite", allow: [".kb/workflows/**"] }
|
|
2965
|
+
},
|
|
2966
|
+
platform: {
|
|
2967
|
+
requires: ["storage", "cache"]
|
|
2968
|
+
},
|
|
2969
|
+
cli: {
|
|
2970
|
+
commands: [
|
|
2971
|
+
{
|
|
2972
|
+
id: "run",
|
|
2973
|
+
group: "workflow",
|
|
2974
|
+
describe: "Run a workflow",
|
|
2975
|
+
handler: "./dist/commands/run.js",
|
|
2976
|
+
flags: [
|
|
2977
|
+
{ name: "workflow-id", type: "string", required: true },
|
|
2978
|
+
{ name: "input", type: "string" }
|
|
2979
|
+
]
|
|
2980
|
+
}
|
|
2981
|
+
]
|
|
2982
|
+
},
|
|
2983
|
+
rest: {
|
|
2984
|
+
basePath: "/v1/plugins/workflow",
|
|
2985
|
+
routes: [
|
|
2986
|
+
{
|
|
2987
|
+
method: "POST",
|
|
2988
|
+
path: "/execute",
|
|
2989
|
+
description: "Execute workflow",
|
|
2990
|
+
handler: "./dist/rest/execute.js",
|
|
2991
|
+
input: { $ref: "#/components/schemas/WorkflowExecuteRequest" },
|
|
2992
|
+
output: { $ref: "#/components/schemas/WorkflowExecuteResponse" }
|
|
2993
|
+
},
|
|
2994
|
+
{
|
|
2995
|
+
method: "GET",
|
|
2996
|
+
path: "/status/:runId",
|
|
2997
|
+
description: "Get workflow status",
|
|
2998
|
+
handler: "./dist/rest/status.js",
|
|
2999
|
+
output: { $ref: "#/components/schemas/WorkflowStatus" }
|
|
3000
|
+
}
|
|
3001
|
+
]
|
|
3002
|
+
},
|
|
3003
|
+
jobs: {
|
|
3004
|
+
handlers: [
|
|
3005
|
+
{
|
|
3006
|
+
id: "cleanup-old-runs",
|
|
3007
|
+
handler: "./dist/jobs/cleanup.js",
|
|
3008
|
+
describe: "Clean up old workflow runs"
|
|
3009
|
+
}
|
|
3010
|
+
],
|
|
3011
|
+
cron: [
|
|
3012
|
+
{
|
|
3013
|
+
jobId: "cleanup-old-runs",
|
|
3014
|
+
schedule: "0 2 * * *",
|
|
3015
|
+
enabled: true
|
|
3016
|
+
}
|
|
3017
|
+
]
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
]
|
|
3022
|
+
};
|
|
3023
|
+
var MockPluginsSource = class {
|
|
3024
|
+
async getPlugins() {
|
|
3025
|
+
await new Promise((resolve) => {
|
|
3026
|
+
setTimeout(() => resolve(), 300);
|
|
3027
|
+
});
|
|
3028
|
+
return {
|
|
3029
|
+
...mockManifests,
|
|
3030
|
+
apiBasePath: "/api/v1"
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
async askAboutPlugin(pluginId, request) {
|
|
3034
|
+
await new Promise((resolve) => {
|
|
3035
|
+
setTimeout(() => resolve(), 1e3);
|
|
3036
|
+
});
|
|
3037
|
+
const question = request.question.toLowerCase();
|
|
3038
|
+
let answer = "";
|
|
3039
|
+
if (question.includes("permission") || question.includes("access")) {
|
|
3040
|
+
answer = `This plugin requires the following permissions:
|
|
3041
|
+
|
|
3042
|
+
- File System: Read-only access to .kb/** and src/** directories
|
|
3043
|
+
- Network: Access to api.openai.com for AI operations
|
|
3044
|
+
|
|
3045
|
+
These permissions are necessary for RAG indexing and semantic search functionality.`;
|
|
3046
|
+
} else if (question.includes("command") || question.includes("cli")) {
|
|
3047
|
+
answer = `The plugin provides 2 CLI commands:
|
|
3048
|
+
|
|
3049
|
+
1. **rag-query** - Query the RAG index
|
|
3050
|
+
- Required: --text (query text)
|
|
3051
|
+
- Optional: --agent (use agent mode)
|
|
3052
|
+
|
|
3053
|
+
2. **rag-index** - Build RAG index
|
|
3054
|
+
- Optional: --scope (default: 'default')
|
|
3055
|
+
|
|
3056
|
+
Use these commands to perform semantic code search across your codebase.`;
|
|
3057
|
+
} else if (question.includes("rest") || question.includes("api") || question.includes("endpoint")) {
|
|
3058
|
+
answer = `The plugin exposes 1 REST API endpoint:
|
|
3059
|
+
|
|
3060
|
+
**POST /v1/plugins/mind/search** - Semantic code search
|
|
3061
|
+
- Timeout: 30 seconds
|
|
3062
|
+
- Input: SearchInputSchema (query text and options)
|
|
3063
|
+
- Output: SearchOutputSchema (search results with relevance scores)
|
|
3064
|
+
|
|
3065
|
+
This endpoint allows integration with external tools and web interfaces.`;
|
|
3066
|
+
} else {
|
|
3067
|
+
answer = `This is the Mind RAG Plugin (v0.1.0) - a semantic code search and RAG system.
|
|
3068
|
+
|
|
3069
|
+
Key features:
|
|
3070
|
+
- 2 CLI commands (rag-query, rag-index)
|
|
3071
|
+
- 1 REST API endpoint (/search)
|
|
3072
|
+
- AI-powered semantic search
|
|
3073
|
+
- Read-only file system access
|
|
3074
|
+
- OpenAI API integration
|
|
3075
|
+
|
|
3076
|
+
The plugin helps developers find relevant code using natural language queries.`;
|
|
3077
|
+
}
|
|
3078
|
+
return {
|
|
3079
|
+
answer,
|
|
3080
|
+
usage: {
|
|
3081
|
+
promptTokens: 150,
|
|
3082
|
+
completionTokens: 100
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
// src/factory.ts
|
|
3089
|
+
function createDataSources(config) {
|
|
3090
|
+
if (config.mode === "mock") {
|
|
3091
|
+
return {
|
|
3092
|
+
system: new MockSystemSource(),
|
|
3093
|
+
workflow: new MockWorkflowSource(),
|
|
3094
|
+
cache: new MockCacheSource(),
|
|
3095
|
+
observability: new MockObservabilitySource(),
|
|
3096
|
+
analytics: new MockAnalyticsSource(),
|
|
3097
|
+
adapters: new MockAdaptersSource(),
|
|
3098
|
+
platform: new MockPlatformSource(),
|
|
3099
|
+
plugins: new MockPluginsSource()
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
const baseUrl = config.baseUrl || "";
|
|
3103
|
+
const client = new HttpClient(baseUrl, config.token);
|
|
3104
|
+
return {
|
|
3105
|
+
system: new HttpSystemSource(client),
|
|
3106
|
+
workflow: new HttpWorkflowSource(client),
|
|
3107
|
+
cache: new HttpCacheSource(client),
|
|
3108
|
+
observability: new HttpObservabilitySource(client),
|
|
3109
|
+
analytics: new HttpAnalyticsSource(client),
|
|
3110
|
+
adapters: new HttpAdaptersSource(client),
|
|
3111
|
+
platform: new HttpPlatformSource(client),
|
|
3112
|
+
plugins: new HttpPluginsSource(client)
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// src/query-keys.ts
|
|
3117
|
+
var qk = {
|
|
3118
|
+
// Audit queries
|
|
3119
|
+
audit: {
|
|
3120
|
+
all: ["audit"],
|
|
3121
|
+
summary: () => [...qk.audit.all, "summary"],
|
|
3122
|
+
runs: {
|
|
3123
|
+
all: () => [...qk.audit.all, "runs"],
|
|
3124
|
+
list: (params) => [...qk.audit.runs.all(), "list", params],
|
|
3125
|
+
byId: (runId) => [...qk.audit.runs.all(), "detail", runId]
|
|
3126
|
+
},
|
|
3127
|
+
report: {
|
|
3128
|
+
latest: () => [...qk.audit.all, "report", "latest"],
|
|
3129
|
+
byRunId: (runId) => [...qk.audit.all, "report", runId]
|
|
3130
|
+
},
|
|
3131
|
+
pkg: (name) => [...qk.audit.all, "pkg", name]
|
|
3132
|
+
},
|
|
3133
|
+
// Release queries
|
|
3134
|
+
release: {
|
|
3135
|
+
all: ["release"],
|
|
3136
|
+
preview: (params) => [...qk.release.all, "preview", params],
|
|
3137
|
+
runs: {
|
|
3138
|
+
all: () => [...qk.release.all, "runs"],
|
|
3139
|
+
byId: (runId) => [...qk.release.runs.all(), "detail", runId]
|
|
3140
|
+
},
|
|
3141
|
+
changelog: (params) => [...qk.release.all, "changelog", params]
|
|
3142
|
+
},
|
|
3143
|
+
// Jobs queries
|
|
3144
|
+
jobs: {
|
|
3145
|
+
all: ["jobs"],
|
|
3146
|
+
byId: (jobId) => [...qk.jobs.all, "detail", jobId],
|
|
3147
|
+
logs: {
|
|
3148
|
+
byJobId: (jobId, offset) => [...qk.jobs.all, "logs", jobId, offset],
|
|
3149
|
+
stream: (jobId) => [...qk.jobs.all, "logs", "stream", jobId]
|
|
3150
|
+
},
|
|
3151
|
+
events: (jobId) => [...qk.jobs.all, "events", jobId],
|
|
3152
|
+
list: (params) => [...qk.jobs.all, "list", params]
|
|
3153
|
+
},
|
|
3154
|
+
// System queries
|
|
3155
|
+
system: {
|
|
3156
|
+
all: ["system"],
|
|
3157
|
+
health: {
|
|
3158
|
+
live: () => [...qk.system.all, "health", "live"],
|
|
3159
|
+
ready: () => [...qk.system.all, "health", "ready"]
|
|
3160
|
+
},
|
|
3161
|
+
info: () => [...qk.system.all, "info"],
|
|
3162
|
+
capabilities: () => [...qk.system.all, "capabilities"],
|
|
3163
|
+
config: () => [...qk.system.all, "config"]
|
|
3164
|
+
},
|
|
3165
|
+
workflows: {
|
|
3166
|
+
all: ["workflows"],
|
|
3167
|
+
list: (filters) => [...qk.workflows.all, "list", filters ?? {}],
|
|
3168
|
+
run: (runId) => [...qk.workflows.all, "run", runId]
|
|
3169
|
+
},
|
|
3170
|
+
// DevLink queries
|
|
3171
|
+
devlink: {
|
|
3172
|
+
all: ["devlink"],
|
|
3173
|
+
summary: () => [...qk.devlink.all, "summary"],
|
|
3174
|
+
graph: () => [...qk.devlink.all, "graph"]
|
|
3175
|
+
},
|
|
3176
|
+
// Mind queries
|
|
3177
|
+
mind: {
|
|
3178
|
+
all: ["mind"],
|
|
3179
|
+
summary: () => [...qk.mind.all, "summary"]
|
|
3180
|
+
},
|
|
3181
|
+
// Analytics queries
|
|
3182
|
+
analytics: {
|
|
3183
|
+
all: ["analytics"],
|
|
3184
|
+
summary: (params) => [...qk.analytics.all, "summary", params]
|
|
3185
|
+
},
|
|
3186
|
+
// Platform queries
|
|
3187
|
+
platform: {
|
|
3188
|
+
all: ["platform"],
|
|
3189
|
+
config: () => [...qk.platform.all, "config"]
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
var queryKeys = qk;
|
|
3193
|
+
function useHealthStatus(source) {
|
|
3194
|
+
return useQuery({
|
|
3195
|
+
queryKey: qk.system.health.live(),
|
|
3196
|
+
queryFn: async () => {
|
|
3197
|
+
try {
|
|
3198
|
+
return source.getHealth();
|
|
3199
|
+
} catch (error) {
|
|
3200
|
+
return {
|
|
3201
|
+
ok: false,
|
|
3202
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3203
|
+
sources: [{
|
|
3204
|
+
name: "system",
|
|
3205
|
+
ok: false,
|
|
3206
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3207
|
+
}]
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
},
|
|
3211
|
+
refetchInterval: 3e4,
|
|
3212
|
+
// Poll every 30 seconds
|
|
3213
|
+
staleTime: 15e3,
|
|
3214
|
+
retry: 2,
|
|
3215
|
+
retryDelay: 1e3
|
|
3216
|
+
});
|
|
3217
|
+
}
|
|
3218
|
+
function useReadyStatus(source) {
|
|
3219
|
+
const httpSource = source;
|
|
3220
|
+
return useQuery({
|
|
3221
|
+
queryKey: qk.system.health.ready(),
|
|
3222
|
+
queryFn: async () => {
|
|
3223
|
+
if (httpSource.getReady) {
|
|
3224
|
+
return httpSource.getReady();
|
|
3225
|
+
}
|
|
3226
|
+
return {
|
|
3227
|
+
ready: true
|
|
3228
|
+
};
|
|
3229
|
+
},
|
|
3230
|
+
refetchInterval: 3e4,
|
|
3231
|
+
staleTime: 15e3,
|
|
3232
|
+
retry: 2,
|
|
3233
|
+
retryDelay: 1e3
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
function useSystemInfo(source) {
|
|
3237
|
+
const httpSource = source;
|
|
3238
|
+
return useQuery({
|
|
3239
|
+
queryKey: qk.system.info(),
|
|
3240
|
+
queryFn: async () => {
|
|
3241
|
+
if (httpSource.getInfo) {
|
|
3242
|
+
return httpSource.getInfo();
|
|
3243
|
+
}
|
|
3244
|
+
return {
|
|
3245
|
+
cwd: process.cwd(),
|
|
3246
|
+
repo: void 0,
|
|
3247
|
+
profiles: [],
|
|
3248
|
+
plugins: [],
|
|
3249
|
+
apiVersion: "1.0.0"
|
|
3250
|
+
};
|
|
3251
|
+
},
|
|
3252
|
+
staleTime: 6e4
|
|
3253
|
+
// Cache for 1 minute
|
|
3254
|
+
});
|
|
3255
|
+
}
|
|
3256
|
+
function useCapabilities(source) {
|
|
3257
|
+
const httpSource = source;
|
|
3258
|
+
return useQuery({
|
|
3259
|
+
queryKey: qk.system.capabilities(),
|
|
3260
|
+
queryFn: async () => {
|
|
3261
|
+
if (httpSource.getCapabilities) {
|
|
3262
|
+
return httpSource.getCapabilities();
|
|
3263
|
+
}
|
|
3264
|
+
return {
|
|
3265
|
+
commands: ["audit", "release", "devlink", "mind", "analytics"],
|
|
3266
|
+
adapters: {
|
|
3267
|
+
queue: ["memory"],
|
|
3268
|
+
storage: ["fs"],
|
|
3269
|
+
auth: ["none"]
|
|
3270
|
+
}
|
|
3271
|
+
};
|
|
3272
|
+
},
|
|
3273
|
+
staleTime: 6e4
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
function useWorkflowRuns(source, filters) {
|
|
3277
|
+
const queryKey = useMemo(() => qk.workflows.list(filters), [filters]);
|
|
3278
|
+
return useQuery({
|
|
3279
|
+
queryKey,
|
|
3280
|
+
queryFn: async () => {
|
|
3281
|
+
return source.listRuns(filters);
|
|
3282
|
+
}
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
function useWorkflowRun(runId, source) {
|
|
3286
|
+
return useQuery({
|
|
3287
|
+
queryKey: qk.workflows.run(runId ?? ""),
|
|
3288
|
+
enabled: Boolean(runId),
|
|
3289
|
+
queryFn: async () => {
|
|
3290
|
+
if (!runId) {
|
|
3291
|
+
return null;
|
|
3292
|
+
}
|
|
3293
|
+
return source.getRun(runId);
|
|
3294
|
+
}
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
function useCancelWorkflowRun(source) {
|
|
3298
|
+
const queryClient = useQueryClient();
|
|
3299
|
+
return useMutation({
|
|
3300
|
+
mutationFn: async (runId) => source.cancelRun(runId),
|
|
3301
|
+
onSuccess: (run) => {
|
|
3302
|
+
void queryClient.invalidateQueries({ queryKey: qk.workflows.all, exact: false });
|
|
3303
|
+
if (run?.id) {
|
|
3304
|
+
void queryClient.invalidateQueries({ queryKey: qk.workflows.run(run.id) });
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
function useRunWorkflow(source) {
|
|
3310
|
+
const queryClient = useQueryClient();
|
|
3311
|
+
return useMutation({
|
|
3312
|
+
mutationFn: async (params) => {
|
|
3313
|
+
if (!source.runWorkflow) {
|
|
3314
|
+
throw new Error("Workflow source does not support running workflows");
|
|
3315
|
+
}
|
|
3316
|
+
return source.runWorkflow(params);
|
|
3317
|
+
},
|
|
3318
|
+
onSuccess: (run) => {
|
|
3319
|
+
void queryClient.invalidateQueries({ queryKey: qk.workflows.all, exact: false });
|
|
3320
|
+
if (run?.id) {
|
|
3321
|
+
void queryClient.invalidateQueries({ queryKey: qk.workflows.run(run.id) });
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3326
|
+
function useResolveApproval(source) {
|
|
3327
|
+
const queryClient = useQueryClient();
|
|
3328
|
+
return useMutation({
|
|
3329
|
+
mutationFn: async (params) => {
|
|
3330
|
+
if (!source.resolveApproval) {
|
|
3331
|
+
throw new Error("Workflow source does not support approvals");
|
|
3332
|
+
}
|
|
3333
|
+
return source.resolveApproval(params);
|
|
3334
|
+
},
|
|
3335
|
+
onSuccess: (_data, variables) => {
|
|
3336
|
+
void queryClient.invalidateQueries({ queryKey: qk.workflows.run(variables.runId) });
|
|
3337
|
+
}
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3340
|
+
function useWorkflowLogs(runId, options = {}) {
|
|
3341
|
+
const { follow = true, idleTimeoutMs, enabled = true, baseUrl = "" } = options;
|
|
3342
|
+
const [events, setEvents] = useState([]);
|
|
3343
|
+
const [error, setError] = useState(null);
|
|
3344
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
3345
|
+
useEffect(() => {
|
|
3346
|
+
if (!runId || !enabled) {
|
|
3347
|
+
setIsConnected(false);
|
|
3348
|
+
setError(null);
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
setEvents([]);
|
|
3352
|
+
setError(null);
|
|
3353
|
+
const params = new URLSearchParams();
|
|
3354
|
+
if (follow) {
|
|
3355
|
+
params.set("follow", "1");
|
|
3356
|
+
}
|
|
3357
|
+
if (typeof idleTimeoutMs === "number") {
|
|
3358
|
+
params.set("idleTimeoutMs", String(idleTimeoutMs));
|
|
3359
|
+
}
|
|
3360
|
+
const url = `${baseUrl}/workflows/runs/${runId}/logs${params.toString() ? `?${params}` : ""}`;
|
|
3361
|
+
const eventSource = new EventSource(url);
|
|
3362
|
+
const handleMessage = (event) => {
|
|
3363
|
+
try {
|
|
3364
|
+
const payload = JSON.parse(event.data);
|
|
3365
|
+
setEvents((prev) => [...prev, payload]);
|
|
3366
|
+
} catch (err) {
|
|
3367
|
+
setError(err instanceof Error ? err : new Error("Failed to parse workflow log event"));
|
|
3368
|
+
}
|
|
3369
|
+
};
|
|
3370
|
+
eventSource.addEventListener("workflow.log", handleMessage);
|
|
3371
|
+
eventSource.addEventListener("workflow.done", () => {
|
|
3372
|
+
setIsConnected(false);
|
|
3373
|
+
eventSource.close();
|
|
3374
|
+
});
|
|
3375
|
+
eventSource.onopen = () => {
|
|
3376
|
+
setIsConnected(true);
|
|
3377
|
+
setError(null);
|
|
3378
|
+
};
|
|
3379
|
+
eventSource.onerror = (_event) => {
|
|
3380
|
+
setIsConnected(false);
|
|
3381
|
+
setError(new Error("Workflow log stream disconnected"));
|
|
3382
|
+
eventSource.close();
|
|
3383
|
+
};
|
|
3384
|
+
return () => {
|
|
3385
|
+
eventSource.removeEventListener("workflow.log", handleMessage);
|
|
3386
|
+
eventSource.close();
|
|
3387
|
+
};
|
|
3388
|
+
}, [runId, follow, idleTimeoutMs, enabled]);
|
|
3389
|
+
return {
|
|
3390
|
+
events,
|
|
3391
|
+
error,
|
|
3392
|
+
isConnected
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
function useWorkflowEvents(runId, options = {}) {
|
|
3396
|
+
const { follow = true, pollIntervalMs, cursor, enabled = true, baseUrl = "" } = options;
|
|
3397
|
+
const [events, setEvents] = useState([]);
|
|
3398
|
+
const [error, setError] = useState(null);
|
|
3399
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
3400
|
+
useEffect(() => {
|
|
3401
|
+
if (!runId || !enabled) {
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
setEvents([]);
|
|
3405
|
+
setError(null);
|
|
3406
|
+
const params = new URLSearchParams();
|
|
3407
|
+
if (follow) {
|
|
3408
|
+
params.set("follow", "1");
|
|
3409
|
+
}
|
|
3410
|
+
if (typeof pollIntervalMs === "number") {
|
|
3411
|
+
params.set("pollIntervalMs", String(pollIntervalMs));
|
|
3412
|
+
}
|
|
3413
|
+
if (cursor) {
|
|
3414
|
+
params.set("cursor", cursor);
|
|
3415
|
+
}
|
|
3416
|
+
const url = `${baseUrl}/workflows/runs/${runId}/events${params.toString() ? `?${params}` : ""}`;
|
|
3417
|
+
const eventSource = new EventSource(url);
|
|
3418
|
+
const handleMessage = (event) => {
|
|
3419
|
+
try {
|
|
3420
|
+
const payload = JSON.parse(event.data);
|
|
3421
|
+
setEvents((prev) => [...prev, payload]);
|
|
3422
|
+
} catch (err) {
|
|
3423
|
+
setError(err instanceof Error ? err : new Error("Failed to parse workflow event"));
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3426
|
+
eventSource.addEventListener("workflow.event", handleMessage);
|
|
3427
|
+
eventSource.onopen = () => {
|
|
3428
|
+
setIsConnected(true);
|
|
3429
|
+
setError(null);
|
|
3430
|
+
};
|
|
3431
|
+
eventSource.onerror = () => {
|
|
3432
|
+
setIsConnected(false);
|
|
3433
|
+
setError(new Error("Workflow event stream disconnected"));
|
|
3434
|
+
eventSource.close();
|
|
3435
|
+
};
|
|
3436
|
+
return () => {
|
|
3437
|
+
eventSource.removeEventListener("workflow.event", handleMessage);
|
|
3438
|
+
eventSource.close();
|
|
3439
|
+
};
|
|
3440
|
+
}, [runId, follow, pollIntervalMs, cursor, enabled]);
|
|
3441
|
+
return {
|
|
3442
|
+
events,
|
|
3443
|
+
error,
|
|
3444
|
+
isConnected
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
function useStateBrokerStats(source) {
|
|
3448
|
+
return useQuery({
|
|
3449
|
+
queryKey: ["observability", "state-broker"],
|
|
3450
|
+
queryFn: () => source.getStateBrokerStats(),
|
|
3451
|
+
refetchInterval: 5e3,
|
|
3452
|
+
// Auto-refresh every 5s for real-time updates
|
|
3453
|
+
staleTime: 3e3,
|
|
3454
|
+
// Consider data stale after 3s
|
|
3455
|
+
retry: 2
|
|
3456
|
+
// Retry failed requests twice
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
function useDevKitHealth(source) {
|
|
3460
|
+
return useQuery({
|
|
3461
|
+
queryKey: ["observability", "devkit"],
|
|
3462
|
+
queryFn: () => source.getDevKitHealth(),
|
|
3463
|
+
staleTime: 6e4,
|
|
3464
|
+
// Cache for 1 minute
|
|
3465
|
+
gcTime: 3e5,
|
|
3466
|
+
// Keep in cache for 5 minutes (React Query v5)
|
|
3467
|
+
retry: 1
|
|
3468
|
+
// DevKit can be slow, only retry once
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
function usePrometheusMetrics(source) {
|
|
3472
|
+
return useQuery({
|
|
3473
|
+
queryKey: ["observability", "prometheus-metrics"],
|
|
3474
|
+
queryFn: () => source.getPrometheusMetrics(),
|
|
3475
|
+
refetchInterval: 1e4,
|
|
3476
|
+
// Auto-refresh every 10s
|
|
3477
|
+
staleTime: 8e3,
|
|
3478
|
+
// Consider data stale after 8s
|
|
3479
|
+
retry: 2
|
|
3480
|
+
// Retry failed requests twice
|
|
3481
|
+
});
|
|
3482
|
+
}
|
|
3483
|
+
function useSystemEvents(source) {
|
|
3484
|
+
const [events, setEvents] = useState([]);
|
|
3485
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
3486
|
+
const [error, setError] = useState(null);
|
|
3487
|
+
useEffect(() => {
|
|
3488
|
+
setIsConnected(true);
|
|
3489
|
+
setError(null);
|
|
3490
|
+
const cleanup = source.subscribeToSystemEvents(
|
|
3491
|
+
(event) => {
|
|
3492
|
+
setEvents((prev) => [event, ...prev].slice(0, 100));
|
|
3493
|
+
},
|
|
3494
|
+
(err) => {
|
|
3495
|
+
setIsConnected(false);
|
|
3496
|
+
setError(err);
|
|
3497
|
+
}
|
|
3498
|
+
);
|
|
3499
|
+
return () => {
|
|
3500
|
+
cleanup();
|
|
3501
|
+
setIsConnected(false);
|
|
3502
|
+
};
|
|
3503
|
+
}, [source]);
|
|
3504
|
+
return { events, isConnected, error };
|
|
3505
|
+
}
|
|
3506
|
+
function useLogStream(source, filters) {
|
|
3507
|
+
const [logs, setLogs] = useState([]);
|
|
3508
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
3509
|
+
const [error, setError] = useState(null);
|
|
3510
|
+
useEffect(() => {
|
|
3511
|
+
setIsConnected(true);
|
|
3512
|
+
setError(null);
|
|
3513
|
+
const cleanup = source.subscribeToLogs(
|
|
3514
|
+
(log) => {
|
|
3515
|
+
setLogs((prev) => [log, ...prev].slice(0, 500));
|
|
3516
|
+
},
|
|
3517
|
+
(err) => {
|
|
3518
|
+
setIsConnected(false);
|
|
3519
|
+
setError(err);
|
|
3520
|
+
},
|
|
3521
|
+
filters
|
|
3522
|
+
);
|
|
3523
|
+
return () => {
|
|
3524
|
+
cleanup();
|
|
3525
|
+
setIsConnected(false);
|
|
3526
|
+
};
|
|
3527
|
+
}, [source, filters]);
|
|
3528
|
+
const clearLogs = () => setLogs([]);
|
|
3529
|
+
return { logs, isConnected, error, clearLogs };
|
|
3530
|
+
}
|
|
3531
|
+
function useMetricsHistory(source, query) {
|
|
3532
|
+
return useQuery({
|
|
3533
|
+
queryKey: ["observability", "metrics-history", query.metric, query.range, query.interval],
|
|
3534
|
+
queryFn: () => source.getMetricsHistory(query),
|
|
3535
|
+
refetchInterval: 5e3,
|
|
3536
|
+
// Auto-refresh every 5s
|
|
3537
|
+
staleTime: 3e3,
|
|
3538
|
+
// Consider data stale after 3s
|
|
3539
|
+
retry: 2
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
function useMetricsHeatmap(source, query) {
|
|
3543
|
+
return useQuery({
|
|
3544
|
+
queryKey: ["observability", "metrics-heatmap", query.metric, query.days],
|
|
3545
|
+
queryFn: () => source.getMetricsHeatmap(query),
|
|
3546
|
+
staleTime: 6e4,
|
|
3547
|
+
// Cache for 1 minute
|
|
3548
|
+
gcTime: 3e5,
|
|
3549
|
+
// Keep in cache for 5 minutes
|
|
3550
|
+
retry: 2
|
|
3551
|
+
});
|
|
3552
|
+
}
|
|
3553
|
+
function useIncidents(source, query) {
|
|
3554
|
+
return useQuery({
|
|
3555
|
+
queryKey: ["observability", "incidents", query],
|
|
3556
|
+
queryFn: () => source.queryIncidents(query),
|
|
3557
|
+
refetchInterval: 3e4,
|
|
3558
|
+
// Auto-refresh every 30s
|
|
3559
|
+
staleTime: 2e4,
|
|
3560
|
+
// Consider data stale after 20s
|
|
3561
|
+
retry: 2
|
|
3562
|
+
});
|
|
3563
|
+
}
|
|
3564
|
+
function useAnalyticsEvents(source, query) {
|
|
3565
|
+
return useQuery({
|
|
3566
|
+
queryKey: ["analytics", "events", query],
|
|
3567
|
+
queryFn: () => source.getEvents(query),
|
|
3568
|
+
refetchInterval: 3e4,
|
|
3569
|
+
// Auto-refresh every 30s
|
|
3570
|
+
staleTime: 25e3,
|
|
3571
|
+
// Consider data stale after 25s
|
|
3572
|
+
retry: 2
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
function useAnalyticsStats(source) {
|
|
3576
|
+
return useQuery({
|
|
3577
|
+
queryKey: ["analytics", "stats"],
|
|
3578
|
+
queryFn: () => source.getStats(),
|
|
3579
|
+
refetchInterval: 3e4,
|
|
3580
|
+
// Auto-refresh every 30s
|
|
3581
|
+
staleTime: 25e3,
|
|
3582
|
+
retry: 2
|
|
3583
|
+
});
|
|
3584
|
+
}
|
|
3585
|
+
function useAnalyticsBufferStatus(source) {
|
|
3586
|
+
return useQuery({
|
|
3587
|
+
queryKey: ["analytics", "buffer"],
|
|
3588
|
+
queryFn: () => source.getBufferStatus(),
|
|
3589
|
+
refetchInterval: 1e4,
|
|
3590
|
+
// Auto-refresh every 10s
|
|
3591
|
+
staleTime: 8e3,
|
|
3592
|
+
retry: 2
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
function useAnalyticsDlqStatus(source) {
|
|
3596
|
+
return useQuery({
|
|
3597
|
+
queryKey: ["analytics", "dlq"],
|
|
3598
|
+
queryFn: () => source.getDlqStatus(),
|
|
3599
|
+
refetchInterval: 15e3,
|
|
3600
|
+
// Auto-refresh every 15s
|
|
3601
|
+
staleTime: 12e3,
|
|
3602
|
+
retry: 2
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3605
|
+
var DAILY_STATS = "daily-stats";
|
|
3606
|
+
function useAdaptersLLMUsage(source, options) {
|
|
3607
|
+
return useQuery({
|
|
3608
|
+
queryKey: ["adapters", "llm", "usage", options?.from, options?.to],
|
|
3609
|
+
queryFn: () => source.getLLMUsage(options),
|
|
3610
|
+
refetchInterval: 6e4,
|
|
3611
|
+
// Auto-refresh every 60s (LLM stats change slower)
|
|
3612
|
+
staleTime: 5e4,
|
|
3613
|
+
retry: 2
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
function useAdaptersEmbeddingsUsage(source, options) {
|
|
3617
|
+
return useQuery({
|
|
3618
|
+
queryKey: ["adapters", "embeddings", "usage", options?.from, options?.to],
|
|
3619
|
+
queryFn: () => source.getEmbeddingsUsage(options),
|
|
3620
|
+
refetchInterval: 6e4,
|
|
3621
|
+
staleTime: 5e4,
|
|
3622
|
+
retry: 2
|
|
3623
|
+
});
|
|
3624
|
+
}
|
|
3625
|
+
function useAdaptersVectorStoreUsage(source, options) {
|
|
3626
|
+
return useQuery({
|
|
3627
|
+
queryKey: ["adapters", "vectorstore", "usage", options?.from, options?.to],
|
|
3628
|
+
queryFn: () => source.getVectorStoreUsage(options),
|
|
3629
|
+
refetchInterval: 6e4,
|
|
3630
|
+
staleTime: 5e4,
|
|
3631
|
+
retry: 2
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
function useAdaptersCacheUsage(source, options) {
|
|
3635
|
+
return useQuery({
|
|
3636
|
+
queryKey: ["adapters", "cache", "usage", options?.from, options?.to],
|
|
3637
|
+
queryFn: () => source.getCacheUsage(options),
|
|
3638
|
+
refetchInterval: 3e4,
|
|
3639
|
+
// Cache stats change faster - refresh every 30s
|
|
3640
|
+
staleTime: 25e3,
|
|
3641
|
+
retry: 2
|
|
3642
|
+
});
|
|
3643
|
+
}
|
|
3644
|
+
function useAdaptersStorageUsage(source, options) {
|
|
3645
|
+
return useQuery({
|
|
3646
|
+
queryKey: ["adapters", "storage", "usage", options?.from, options?.to],
|
|
3647
|
+
queryFn: () => source.getStorageUsage(options),
|
|
3648
|
+
refetchInterval: 6e4,
|
|
3649
|
+
staleTime: 5e4,
|
|
3650
|
+
retry: 2
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
function useAdaptersLLMDailyStats(source, options) {
|
|
3654
|
+
return useQuery({
|
|
3655
|
+
queryKey: ["adapters", "llm", DAILY_STATS, options?.from, options?.to, options?.models],
|
|
3656
|
+
queryFn: () => source.getLLMDailyStats(options),
|
|
3657
|
+
refetchInterval: 6e4,
|
|
3658
|
+
// Daily stats change slower
|
|
3659
|
+
staleTime: 5e4,
|
|
3660
|
+
retry: 2
|
|
3661
|
+
});
|
|
3662
|
+
}
|
|
3663
|
+
function useAdaptersEmbeddingsDailyStats(source, options) {
|
|
3664
|
+
return useQuery({
|
|
3665
|
+
queryKey: ["adapters", "embeddings", DAILY_STATS, options?.from, options?.to],
|
|
3666
|
+
queryFn: () => source.getEmbeddingsDailyStats(options),
|
|
3667
|
+
refetchInterval: 6e4,
|
|
3668
|
+
staleTime: 5e4,
|
|
3669
|
+
retry: 2
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
function useAdaptersVectorStoreDailyStats(source, options) {
|
|
3673
|
+
return useQuery({
|
|
3674
|
+
queryKey: ["adapters", "vectorstore", DAILY_STATS, options?.from, options?.to],
|
|
3675
|
+
queryFn: () => source.getVectorStoreDailyStats(options),
|
|
3676
|
+
refetchInterval: 6e4,
|
|
3677
|
+
staleTime: 5e4,
|
|
3678
|
+
retry: 2
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
function useAdaptersCacheDailyStats(source, options) {
|
|
3682
|
+
return useQuery({
|
|
3683
|
+
queryKey: ["adapters", "cache", DAILY_STATS, options?.from, options?.to],
|
|
3684
|
+
queryFn: () => source.getCacheDailyStats(options),
|
|
3685
|
+
refetchInterval: 3e4,
|
|
3686
|
+
// Cache stats change faster
|
|
3687
|
+
staleTime: 25e3,
|
|
3688
|
+
retry: 2
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
function useAdaptersStorageDailyStats(source, options) {
|
|
3692
|
+
return useQuery({
|
|
3693
|
+
queryKey: ["adapters", "storage", DAILY_STATS, options?.from, options?.to],
|
|
3694
|
+
queryFn: () => source.getStorageDailyStats(options),
|
|
3695
|
+
refetchInterval: 6e4,
|
|
3696
|
+
staleTime: 5e4,
|
|
3697
|
+
retry: 2
|
|
3698
|
+
});
|
|
3699
|
+
}
|
|
3700
|
+
function usePlatformConfig(source) {
|
|
3701
|
+
return useQuery({
|
|
3702
|
+
queryKey: qk.platform.config(),
|
|
3703
|
+
queryFn: async () => {
|
|
3704
|
+
return source.getConfig();
|
|
3705
|
+
},
|
|
3706
|
+
staleTime: 6e4,
|
|
3707
|
+
// Cache for 1 minute (config rarely changes)
|
|
3708
|
+
retry: 2,
|
|
3709
|
+
retryDelay: 1e3
|
|
3710
|
+
});
|
|
3711
|
+
}
|
|
3712
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
3713
|
+
function statusToEventType(status) {
|
|
3714
|
+
if (status === "completed") {
|
|
3715
|
+
return "job.finished";
|
|
3716
|
+
}
|
|
3717
|
+
if (status === "failed") {
|
|
3718
|
+
return "job.failed";
|
|
3719
|
+
}
|
|
3720
|
+
if (status === "running") {
|
|
3721
|
+
return "job.started";
|
|
3722
|
+
}
|
|
3723
|
+
return "job.queued";
|
|
3724
|
+
}
|
|
3725
|
+
function useJobEvents(jobId, options = {}) {
|
|
3726
|
+
const {
|
|
3727
|
+
enabled = true,
|
|
3728
|
+
pollInterval = 1e3,
|
|
3729
|
+
baseUrl = "",
|
|
3730
|
+
onEvent,
|
|
3731
|
+
onError,
|
|
3732
|
+
onComplete
|
|
3733
|
+
} = options;
|
|
3734
|
+
const [events, setEvents] = useState([]);
|
|
3735
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
3736
|
+
const [error, setError] = useState(null);
|
|
3737
|
+
const eventSourceRef = useRef(null);
|
|
3738
|
+
const pollingRef = useRef(null);
|
|
3739
|
+
const useSSERef = useRef(true);
|
|
3740
|
+
const reconnect = useCallback(() => {
|
|
3741
|
+
if (eventSourceRef.current) {
|
|
3742
|
+
eventSourceRef.current.close();
|
|
3743
|
+
eventSourceRef.current = null;
|
|
3744
|
+
}
|
|
3745
|
+
if (pollingRef.current) {
|
|
3746
|
+
clearInterval(pollingRef.current);
|
|
3747
|
+
pollingRef.current = null;
|
|
3748
|
+
}
|
|
3749
|
+
setEvents([]);
|
|
3750
|
+
setError(null);
|
|
3751
|
+
setIsConnected(false);
|
|
3752
|
+
useSSERef.current = true;
|
|
3753
|
+
}, []);
|
|
3754
|
+
useEffect(() => {
|
|
3755
|
+
if (!enabled || !jobId) {
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
const eventsUrl = `${baseUrl}/jobs/${jobId}/events`;
|
|
3759
|
+
if (useSSERef.current) {
|
|
3760
|
+
try {
|
|
3761
|
+
const eventSource = new EventSource(eventsUrl);
|
|
3762
|
+
eventSourceRef.current = eventSource;
|
|
3763
|
+
eventSource.onopen = () => {
|
|
3764
|
+
setIsConnected(true);
|
|
3765
|
+
setError(null);
|
|
3766
|
+
};
|
|
3767
|
+
eventSource.onmessage = (e) => {
|
|
3768
|
+
try {
|
|
3769
|
+
const event = JSON.parse(e.data);
|
|
3770
|
+
setEvents((prev) => [...prev, event]);
|
|
3771
|
+
onEvent?.(event);
|
|
3772
|
+
if (event.type === "job.finished" || event.type === "job.failed") {
|
|
3773
|
+
eventSource.close();
|
|
3774
|
+
setIsConnected(false);
|
|
3775
|
+
onComplete?.();
|
|
3776
|
+
}
|
|
3777
|
+
} catch (_err) {
|
|
3778
|
+
setError(_err instanceof Error ? _err : new Error("Failed to parse event"));
|
|
3779
|
+
onError?.(_err instanceof Error ? _err : new Error("Failed to parse event"));
|
|
3780
|
+
}
|
|
3781
|
+
};
|
|
3782
|
+
eventSource.onerror = (_event) => {
|
|
3783
|
+
eventSource.close();
|
|
3784
|
+
eventSourceRef.current = null;
|
|
3785
|
+
useSSERef.current = false;
|
|
3786
|
+
setIsConnected(false);
|
|
3787
|
+
const poll = async () => {
|
|
3788
|
+
try {
|
|
3789
|
+
const response = await fetch(`${baseUrl}/jobs/${jobId}`);
|
|
3790
|
+
if (!response.ok) {
|
|
3791
|
+
throw new Error(`HTTP ${response.status}`);
|
|
3792
|
+
}
|
|
3793
|
+
const data = await response.json();
|
|
3794
|
+
const currentStatus = data.status;
|
|
3795
|
+
const lastEvent = events[events.length - 1];
|
|
3796
|
+
if (!lastEvent || lastEvent.data?.status !== currentStatus) {
|
|
3797
|
+
const event = {
|
|
3798
|
+
type: statusToEventType(currentStatus),
|
|
3799
|
+
jobId,
|
|
3800
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3801
|
+
data: {
|
|
3802
|
+
status: currentStatus,
|
|
3803
|
+
progress: data.progress,
|
|
3804
|
+
error: data.error
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
setEvents((prev) => [...prev, event]);
|
|
3808
|
+
onEvent?.(event);
|
|
3809
|
+
if (TERMINAL_STATUSES.has(currentStatus)) {
|
|
3810
|
+
if (pollingRef.current) {
|
|
3811
|
+
clearInterval(pollingRef.current);
|
|
3812
|
+
pollingRef.current = null;
|
|
3813
|
+
}
|
|
3814
|
+
onComplete?.();
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
} catch (_err) {
|
|
3818
|
+
const errorInstance = _err instanceof Error ? _err : new Error("Polling failed");
|
|
3819
|
+
setError(errorInstance);
|
|
3820
|
+
onError?.(errorInstance);
|
|
3821
|
+
}
|
|
3822
|
+
};
|
|
3823
|
+
poll();
|
|
3824
|
+
pollingRef.current = setInterval(poll, pollInterval);
|
|
3825
|
+
};
|
|
3826
|
+
return () => {
|
|
3827
|
+
if (eventSourceRef.current) {
|
|
3828
|
+
eventSourceRef.current.close();
|
|
3829
|
+
eventSourceRef.current = null;
|
|
3830
|
+
}
|
|
3831
|
+
if (pollingRef.current) {
|
|
3832
|
+
clearInterval(pollingRef.current);
|
|
3833
|
+
pollingRef.current = null;
|
|
3834
|
+
}
|
|
3835
|
+
};
|
|
3836
|
+
} catch (_err) {
|
|
3837
|
+
useSSERef.current = false;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
if (!useSSERef.current && !pollingRef.current) {
|
|
3841
|
+
const poll = async () => {
|
|
3842
|
+
try {
|
|
3843
|
+
const response = await fetch(`${baseUrl}/jobs/${jobId}`);
|
|
3844
|
+
if (!response.ok) {
|
|
3845
|
+
throw new Error(`HTTP ${response.status}`);
|
|
3846
|
+
}
|
|
3847
|
+
const data = await response.json();
|
|
3848
|
+
const currentStatus = data.status;
|
|
3849
|
+
setEvents((prev) => {
|
|
3850
|
+
const lastEvent = prev[prev.length - 1];
|
|
3851
|
+
if (!lastEvent || lastEvent.data?.status !== currentStatus) {
|
|
3852
|
+
const event = {
|
|
3853
|
+
type: statusToEventType(currentStatus),
|
|
3854
|
+
jobId,
|
|
3855
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3856
|
+
data: {
|
|
3857
|
+
status: currentStatus,
|
|
3858
|
+
progress: data.progress,
|
|
3859
|
+
error: data.error
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
onEvent?.(event);
|
|
3863
|
+
if (currentStatus === "completed" || currentStatus === "failed" || currentStatus === "cancelled") {
|
|
3864
|
+
if (pollingRef.current) {
|
|
3865
|
+
clearInterval(pollingRef.current);
|
|
3866
|
+
pollingRef.current = null;
|
|
3867
|
+
}
|
|
3868
|
+
onComplete?.();
|
|
3869
|
+
}
|
|
3870
|
+
return [...prev, event];
|
|
3871
|
+
}
|
|
3872
|
+
return prev;
|
|
3873
|
+
});
|
|
3874
|
+
setIsConnected(true);
|
|
3875
|
+
setError(null);
|
|
3876
|
+
} catch (_err) {
|
|
3877
|
+
const errorInstance = _err instanceof Error ? _err : new Error("Polling failed");
|
|
3878
|
+
setError(errorInstance);
|
|
3879
|
+
onError?.(errorInstance);
|
|
3880
|
+
setIsConnected(false);
|
|
3881
|
+
}
|
|
3882
|
+
};
|
|
3883
|
+
poll();
|
|
3884
|
+
pollingRef.current = setInterval(poll, pollInterval);
|
|
3885
|
+
return () => {
|
|
3886
|
+
if (pollingRef.current) {
|
|
3887
|
+
clearInterval(pollingRef.current);
|
|
3888
|
+
pollingRef.current = null;
|
|
3889
|
+
}
|
|
3890
|
+
};
|
|
3891
|
+
}
|
|
3892
|
+
}, [jobId, enabled, pollInterval, onEvent, onError, onComplete, events]);
|
|
3893
|
+
return {
|
|
3894
|
+
events,
|
|
3895
|
+
isConnected,
|
|
3896
|
+
error,
|
|
3897
|
+
reconnect
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
var MAX_NOTIFICATIONS = 50;
|
|
3901
|
+
function useNotifications(source, maxNotifications = MAX_NOTIFICATIONS) {
|
|
3902
|
+
const [notifications, setNotifications] = useState([]);
|
|
3903
|
+
useEffect(() => {
|
|
3904
|
+
return source.subscribeToLogs(
|
|
3905
|
+
(log) => {
|
|
3906
|
+
if (log.level !== "warn" && log.level !== "error") {
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
let messageText = "No message";
|
|
3910
|
+
if (log.msg) {
|
|
3911
|
+
messageText = typeof log.msg === "string" ? log.msg : JSON.stringify(log.msg);
|
|
3912
|
+
}
|
|
3913
|
+
const notification = {
|
|
3914
|
+
id: log.id || `${log.time}-${Math.random().toString(36).slice(2, 9)}`,
|
|
3915
|
+
// Use log.id from database, fallback to generated
|
|
3916
|
+
timestamp: log.time,
|
|
3917
|
+
level: log.level,
|
|
3918
|
+
message: messageText,
|
|
3919
|
+
plugin: log.plugin,
|
|
3920
|
+
executionId: log.executionId,
|
|
3921
|
+
error: log.err ? {
|
|
3922
|
+
name: String(log.err.name || "Error"),
|
|
3923
|
+
message: String(log.err.message || "Unknown error")
|
|
3924
|
+
} : void 0,
|
|
3925
|
+
read: false
|
|
3926
|
+
};
|
|
3927
|
+
setNotifications((prev) => [notification, ...prev].slice(0, maxNotifications));
|
|
3928
|
+
},
|
|
3929
|
+
(err) => {
|
|
3930
|
+
console.error("Notifications SSE error:", err);
|
|
3931
|
+
}
|
|
3932
|
+
);
|
|
3933
|
+
}, [source, maxNotifications]);
|
|
3934
|
+
const unreadCount = notifications.filter((n) => !n.read).length;
|
|
3935
|
+
const markAsRead = useCallback((id) => {
|
|
3936
|
+
setNotifications(
|
|
3937
|
+
(prev) => prev.map((n) => n.id === id ? { ...n, read: true } : n)
|
|
3938
|
+
);
|
|
3939
|
+
}, []);
|
|
3940
|
+
const markAllAsRead = useCallback(() => {
|
|
3941
|
+
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
|
3942
|
+
}, []);
|
|
3943
|
+
const clearAll = useCallback(() => {
|
|
3944
|
+
setNotifications([]);
|
|
3945
|
+
}, []);
|
|
3946
|
+
const clearNotification = useCallback((id) => {
|
|
3947
|
+
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
3948
|
+
}, []);
|
|
3949
|
+
return {
|
|
3950
|
+
notifications,
|
|
3951
|
+
unreadCount,
|
|
3952
|
+
markAsRead,
|
|
3953
|
+
markAllAsRead,
|
|
3954
|
+
clearAll,
|
|
3955
|
+
clearNotification
|
|
3956
|
+
};
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
export { HttpAdaptersSource, HttpAnalyticsSource, HttpCacheSource, HttpClient, HttpObservabilitySource, HttpPlatformSource, HttpPluginsSource, HttpSystemSource, HttpWorkflowSource, KBError, MockAdaptersSource, MockAnalyticsSource, MockCacheSource, MockObservabilitySource, MockPlatformSource, MockPluginsSource, MockSystemSource, MockWorkflowSource, SCHEMA_VERSION, actionResultSchema, auditCheckSchema, auditPackageReportSchema, auditSummarySchema, createDataSources, createEnvelopeInterceptor, errorCodes, extractEnvelopeMeta, healthStatusSchema, idSchema, isoDateSchema, mapErrorEnvelope, mapFetchError, packageRefSchema, qk, queryKeys, releasePreviewSchema, runRefSchema, useAdaptersCacheDailyStats, useAdaptersCacheUsage, useAdaptersEmbeddingsDailyStats, useAdaptersEmbeddingsUsage, useAdaptersLLMDailyStats, useAdaptersLLMUsage, useAdaptersStorageDailyStats, useAdaptersStorageUsage, useAdaptersVectorStoreDailyStats, useAdaptersVectorStoreUsage, useAnalyticsBufferStatus, useAnalyticsDlqStatus, useAnalyticsEvents, useAnalyticsStats, useCancelWorkflowRun, useCapabilities, useDevKitHealth, useHealthStatus, useIncidents, useJobEvents, useLogStream, useMetricsHeatmap, useMetricsHistory, useNotifications, usePlatformConfig, usePrometheusMetrics, useReadyStatus, useResolveApproval, useRunWorkflow, useStateBrokerStats, useSystemEvents, useSystemInfo, useWorkflowEvents, useWorkflowLogs, useWorkflowRun, useWorkflowRuns };
|
|
3960
|
+
//# sourceMappingURL=index.js.map
|
|
3961
|
+
//# sourceMappingURL=index.js.map
|