@kb-labs/rest-api-app 2.112.0 → 2.116.11

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/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { loadRestApiConfig, combineManifestsToRegistry } from '@kb-labs/rest-api-core';
2
2
  import Fastify from 'fastify';
3
- import { loadEnvFromRoot, createServiceBootstrap, platform, getPlatformRoot } from '@kb-labs/core-runtime';
3
+ import { platform } from '@kb-labs/core-runtime';
4
4
  import { EventEmitter } from 'events';
5
5
  import { performance, monitorEventLoopDelay } from 'perf_hooks';
6
- import { OperationMetricsTracker, getListenOptions, registerOpenAPI, createCorrelatedLogger, createServiceObservabilityDescribe, createServiceObservabilityHealth } from '@kb-labs/shared-http';
6
+ import { OperationMetricsTracker, getListenOptions, registerOpenAPI, createHttpLogger, createServiceObservabilityDescribe, createServiceObservabilityHealth } from '@kb-labs/shared-http';
7
7
  import { Registry, Histogram, Counter, Gauge } from 'prom-client';
8
8
  import * as os from 'os';
9
9
  import { hostname } from 'os';
@@ -29,9 +29,9 @@ import fastifyRateLimit from '@fastify/rate-limit';
29
29
  import { ulid } from 'ulid';
30
30
  import { randomUUID, createHash } from 'crypto';
31
31
  import { TenantRateLimiter, getDefaultTenantTier } from '@kb-labs/core-tenant';
32
- import { promises, existsSync, readFileSync } from 'fs';
33
- import { findRepoRoot } from '@kb-labs/core-sys';
32
+ import { existsSync, readFileSync } from 'fs';
34
33
  import { makeAssemblyHook } from '@kb-labs/plugin-runtime';
34
+ import { runService } from '@kb-labs/shared-daemon';
35
35
 
36
36
  // src/bootstrap.ts
37
37
  var EventHub = class {
@@ -899,17 +899,17 @@ async function registerEventRoutes(server, basePath, registry, readiness, eventH
899
899
  request.kbLogger.warn("Failed to fetch system health for SSE client", { err: error });
900
900
  }
901
901
  });
902
- await new Promise((resolve3) => {
902
+ await new Promise((resolve2) => {
903
903
  request.raw.on("close", () => {
904
904
  unsubscribe();
905
905
  reply.raw.end();
906
- resolve3();
906
+ resolve2();
907
907
  });
908
908
  if (request.raw.socket && !request.raw.socket.writable) {
909
909
  void healthPromise.then(() => {
910
910
  unsubscribe();
911
911
  reply.raw.end();
912
- resolve3();
912
+ resolve2();
913
913
  });
914
914
  }
915
915
  });
@@ -1529,8 +1529,8 @@ async function registerHealthRoutes(fastify, config, _repoRoot, registry, readin
1529
1529
  const readyPaths = resolvePaths(basePath, "/ready");
1530
1530
  const observabilityDescribePaths = resolvePaths(basePath, "/observability/describe");
1531
1531
  const observabilityHealthPaths = resolvePaths(basePath, "/observability/health");
1532
- for (const path3 of healthPaths) {
1533
- fastify.get(path3, async (_request, reply) => {
1532
+ for (const path2 of healthPaths) {
1533
+ fastify.get(path2, async (_request, reply) => {
1534
1534
  const eventHub = fastify.kbEventHub;
1535
1535
  try {
1536
1536
  const baseSnapshot = await getBaseSnapshot(registry);
@@ -1575,8 +1575,8 @@ async function registerHealthRoutes(fastify, config, _repoRoot, registry, readin
1575
1575
  }
1576
1576
  });
1577
1577
  }
1578
- for (const path3 of readyPaths) {
1579
- fastify.get(path3, async (_request, reply) => {
1578
+ for (const path2 of readyPaths) {
1579
+ fastify.get(path2, async (_request, reply) => {
1580
1580
  const eventHub = fastify.kbEventHub;
1581
1581
  const { registryLoaded, registryPartial, registryStale } = evaluateRegistryState(registry);
1582
1582
  readiness.registryLoaded = registryLoaded;
@@ -1604,11 +1604,11 @@ async function registerHealthRoutes(fastify, config, _repoRoot, registry, readin
1604
1604
  return reply.code(statusCode).send(response);
1605
1605
  });
1606
1606
  }
1607
- for (const path3 of observabilityDescribePaths) {
1608
- fastify.get(path3, async () => buildRestObservabilityDescribe(basePath));
1607
+ for (const path2 of observabilityDescribePaths) {
1608
+ fastify.get(path2, async () => buildRestObservabilityDescribe(basePath));
1609
1609
  }
1610
- for (const path3 of observabilityHealthPaths) {
1611
- fastify.get(path3, async () => {
1610
+ for (const path2 of observabilityHealthPaths) {
1611
+ fastify.get(path2, async () => {
1612
1612
  try {
1613
1613
  const baseSnapshot = await getBaseSnapshot(registry);
1614
1614
  const pluginsMetrics = metricsCollector.getLastPluginMountSnapshot();
@@ -1930,8 +1930,8 @@ function registerMetricsRoutes(server, config) {
1930
1930
  reply.header("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
1931
1931
  return promMetrics + "\n" + customLines.join("\n");
1932
1932
  };
1933
- for (const path3 of metricsPaths) {
1934
- server.get(path3, handler);
1933
+ for (const path2 of metricsPaths) {
1934
+ server.get(path2, handler);
1935
1935
  }
1936
1936
  server.get(`${basePath}/metrics/headers/debug`, {
1937
1937
  schema: {
@@ -2087,7 +2087,10 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2087
2087
  );
2088
2088
  const handlerExistsMap = /* @__PURE__ */ new Map();
2089
2089
  for (const result of handlerAccessResults) {
2090
- handlerExistsMap.set(result.key, { exists: result.exists, filePath: result.filePath });
2090
+ handlerExistsMap.set(result.key, {
2091
+ exists: result.exists,
2092
+ filePath: result.filePath
2093
+ });
2091
2094
  }
2092
2095
  platform.logger.debug("Batch handler file validation completed", {
2093
2096
  totalHandlers: handlerChecks.length,
@@ -2126,7 +2129,10 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2126
2129
  const reasonCode = inferRouteValidationReasonCode(restValidationErrors);
2127
2130
  logDiagnosticEvent(platform.logger, {
2128
2131
  event: "plugin.routes.validation",
2129
- level: resolveRouteValidationLogLevel(restValidationErrors, manifest.rest?.routes?.length ?? 0),
2132
+ level: resolveRouteValidationLogLevel(
2133
+ restValidationErrors,
2134
+ manifest.rest?.routes?.length ?? 0
2135
+ ),
2130
2136
  reasonCode,
2131
2137
  message: "Plugin route validation failed",
2132
2138
  outcome: "failed",
@@ -2140,22 +2146,31 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2140
2146
  totalRoutes: manifest.rest?.routes?.length ?? 0
2141
2147
  }
2142
2148
  });
2143
- platform.logger.warn("REST validation errors found, will skip problematic routes but continue mounting others", {
2144
- plugin: `${manifest.id}@${manifest.version}`,
2145
- pluginRoot,
2146
- remediation: "Verify REST handler file and export exist",
2147
- errors: restValidationErrors
2148
- });
2149
- const errorPaths = new Set(restValidationErrors.map((error) => {
2150
- const match = error.match(/Route\s+(\w+)\s+([^\s:]+)/);
2151
- return match ? `${match[1]} ${match[2]}` : null;
2152
- }).filter(Boolean));
2149
+ platform.logger.warn(
2150
+ "REST validation errors found, will skip problematic routes but continue mounting others",
2151
+ {
2152
+ plugin: `${manifest.id}@${manifest.version}`,
2153
+ pluginRoot,
2154
+ remediation: "Verify REST handler file and export exist",
2155
+ errors: restValidationErrors
2156
+ }
2157
+ );
2158
+ const errorPaths = new Set(
2159
+ restValidationErrors.map((error) => {
2160
+ const match = error.match(/Route\s+(\w+)\s+([^\s:]+)/);
2161
+ return match ? `${match[1]} ${match[2]}` : null;
2162
+ }).filter(Boolean)
2163
+ );
2153
2164
  const validRoutes = manifest.rest.routes.filter((route) => {
2154
2165
  const routeKey = `${route.method} ${route.path}`;
2155
2166
  return !errorPaths.has(routeKey);
2156
2167
  });
2157
2168
  if (validRoutes.length === 0) {
2158
- restDomainOperationMetrics.recordOperation("plugin.routes.mount", 0, "error");
2169
+ restDomainOperationMetrics.recordOperation(
2170
+ "plugin.routes.mount",
2171
+ 0,
2172
+ "error"
2173
+ );
2159
2174
  logDiagnosticEvent(platform.logger, {
2160
2175
  event: "plugin.routes.validation",
2161
2176
  level: "error",
@@ -2193,7 +2208,10 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2193
2208
  if (manifest.rest.basePath.startsWith("/api/")) {
2194
2209
  pluginBasePath = manifest.rest.basePath;
2195
2210
  } else if (manifest.rest.basePath.startsWith("/v1/")) {
2196
- pluginBasePath = manifest.rest.basePath.replace(/^\/v1/, config.basePath);
2211
+ pluginBasePath = manifest.rest.basePath.replace(
2212
+ /^\/v1/,
2213
+ config.basePath
2214
+ );
2197
2215
  } else {
2198
2216
  pluginBasePath = `${config.basePath}${manifest.rest.basePath}`;
2199
2217
  }
@@ -2226,13 +2244,17 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2226
2244
  );
2227
2245
  }
2228
2246
  mountMetrics.recordSuccess(manifest.id, routesCount, duration);
2229
- restDomainOperationMetrics.recordOperation("plugin.routes.mount", duration, "ok");
2247
+ restDomainOperationMetrics.recordOperation(
2248
+ "plugin.routes.mount",
2249
+ duration,
2250
+ "ok"
2251
+ );
2230
2252
  for (const route of manifest.rest?.routes ?? []) {
2231
- platform.logger.info("Mounted route", {
2232
- plugin: manifest.id,
2233
- method: route.method,
2234
- path: `${pluginBasePath}${route.path}`,
2235
- handler: route.handler
2253
+ platform.logger.debug("Mounted route", {
2254
+ pluginId: manifest.id,
2255
+ "http.method": route.method,
2256
+ "http.route": `${pluginBasePath}${route.path}`,
2257
+ "plugin.handler": route.handler
2236
2258
  });
2237
2259
  }
2238
2260
  platform.logger.info("Successfully mounted plugin routes", {
@@ -2301,7 +2323,11 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2301
2323
  succeeded.push(manifest.id);
2302
2324
  } catch (error) {
2303
2325
  mountMetrics.recordFailure(manifest.id, shortErrorMessage(error));
2304
- restDomainOperationMetrics.recordOperation("plugin.routes.mount", 0, "error");
2326
+ restDomainOperationMetrics.recordOperation(
2327
+ "plugin.routes.mount",
2328
+ 0,
2329
+ "error"
2330
+ );
2305
2331
  logDiagnosticEvent(platform.logger, {
2306
2332
  event: "plugin.routes.mount",
2307
2333
  level: "error",
@@ -2332,14 +2358,17 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2332
2358
  if (readiness) {
2333
2359
  readiness.pluginRouteFailures = routeFailures;
2334
2360
  }
2335
- platform.logger.info("Sequential plugin route and WebSocket channel mounting completed", {
2336
- total: mountableManifests.length,
2337
- succeeded: succeeded.length,
2338
- failed: failed.length,
2339
- mountedRoutes,
2340
- mountedChannels,
2341
- errors
2342
- });
2361
+ platform.logger.info(
2362
+ "Sequential plugin route and WebSocket channel mounting completed",
2363
+ {
2364
+ total: mountableManifests.length,
2365
+ succeeded: succeeded.length,
2366
+ failed: failed.length,
2367
+ mountedRoutes,
2368
+ mountedChannels,
2369
+ errors
2370
+ }
2371
+ );
2343
2372
  } catch (error) {
2344
2373
  logDiagnosticEvent(platform.logger, {
2345
2374
  domain: "registry",
@@ -2367,7 +2396,9 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2367
2396
  error: `rest_discovery_failed ${shortErrorMessage(error)}`
2368
2397
  });
2369
2398
  }
2370
- metricsCollector.completePluginMount(platform.logger);
2399
+ metricsCollector.completePluginMount(
2400
+ platform.logger
2401
+ );
2371
2402
  return;
2372
2403
  }
2373
2404
  if (readiness) {
@@ -2380,15 +2411,20 @@ async function registerPluginRoutes(server, config, repoRoot, registry, readines
2380
2411
  (performance.now() - mountStart).toFixed(2)
2381
2412
  );
2382
2413
  }
2383
- metricsCollector.completePluginMount(platform.logger);
2414
+ metricsCollector.completePluginMount(
2415
+ platform.logger
2416
+ );
2384
2417
  }
2385
2418
  async function registerPluginSnapshotRoutes(server, config, registry) {
2386
2419
  const basePath = config.basePath;
2387
2420
  server.post(`${basePath}/plugins/refresh`, async (_request, reply) => {
2388
2421
  try {
2389
- await restDomainOperationMetrics.observeOperation("plugin.registry.refresh", async () => {
2390
- await registry.refresh();
2391
- });
2422
+ await restDomainOperationMetrics.observeOperation(
2423
+ "plugin.registry.refresh",
2424
+ async () => {
2425
+ await registry.refresh();
2426
+ }
2427
+ );
2392
2428
  const snapshot = await restDomainOperationMetrics.observeOperation(
2393
2429
  "plugin.registry.snapshot",
2394
2430
  async () => registry.snapshot()
@@ -2486,119 +2522,151 @@ async function registerPluginSnapshotRoutes(server, config, registry) {
2486
2522
  });
2487
2523
  }
2488
2524
  });
2489
- server.get(`${basePath}/plugins/@:scope/:name/readme`, {
2490
- schema: { hide: true }
2491
- }, async (request, reply) => {
2492
- const { scope, name } = request.params;
2493
- if (!scope || !name) {
2494
- return reply.code(400).send({ error: "Invalid plugin identifier" });
2495
- }
2496
- const pluginId = `@${scope}/${name}`;
2497
- const snapshot = registry.snapshot();
2498
- const manifests = extractSnapshotManifests(snapshot);
2499
- const entry = manifests.find((e) => e.pluginId === pluginId);
2500
- if (!entry) {
2501
- return reply.code(404).send({ error: `Plugin not found: ${pluginId}` });
2502
- }
2503
- const readmePath = path.join(entry.pluginRoot, "README.md");
2504
- if (!readmePath.startsWith(entry.pluginRoot)) {
2505
- return reply.code(400).send({ error: "Invalid path" });
2506
- }
2507
- try {
2508
- const content = await fs.readFile(readmePath, "utf-8");
2509
- return reply.code(200).header("Content-Type", "text/plain; charset=utf-8").send(content);
2510
- } catch (error) {
2511
- if (error.code === "ENOENT") {
2512
- return reply.code(404).send({ error: "README.md not found" });
2525
+ server.get(
2526
+ `${basePath}/plugins/@:scope/:name/readme`,
2527
+ {
2528
+ schema: { hide: true }
2529
+ },
2530
+ async (request, reply) => {
2531
+ const { scope, name } = request.params;
2532
+ if (!scope || !name) {
2533
+ return reply.code(400).send({ error: "Invalid plugin identifier" });
2534
+ }
2535
+ const pluginId = `@${scope}/${name}`;
2536
+ const snapshot = registry.snapshot();
2537
+ const manifests = extractSnapshotManifests(snapshot);
2538
+ const entry = manifests.find((e) => e.pluginId === pluginId);
2539
+ if (!entry) {
2540
+ return reply.code(404).send({ error: `Plugin not found: ${pluginId}` });
2541
+ }
2542
+ const readmePath = path.join(entry.pluginRoot, "README.md");
2543
+ if (!readmePath.startsWith(entry.pluginRoot)) {
2544
+ return reply.code(400).send({ error: "Invalid path" });
2545
+ }
2546
+ try {
2547
+ const content = await fs.readFile(readmePath, "utf-8");
2548
+ return reply.code(200).header("Content-Type", "text/plain; charset=utf-8").send(content);
2549
+ } catch (error) {
2550
+ if (error.code === "ENOENT") {
2551
+ return reply.code(404).send({ error: "README.md not found" });
2552
+ }
2553
+ return reply.code(500).send({ error: "Failed to read README.md" });
2513
2554
  }
2514
- return reply.code(500).send({ error: "Failed to read README.md" });
2515
- }
2516
- });
2517
- server.get(`${basePath}/plugins/@:scope/:name/changelog`, {
2518
- schema: { hide: true }
2519
- }, async (request, reply) => {
2520
- const { scope, name } = request.params;
2521
- if (!scope || !name) {
2522
- return reply.code(400).send({ error: "Invalid plugin identifier" });
2523
- }
2524
- const pluginId = `@${scope}/${name}`;
2525
- const snapshot = registry.snapshot();
2526
- const manifests = extractSnapshotManifests(snapshot);
2527
- const entry = manifests.find((e) => e.pluginId === pluginId);
2528
- if (!entry) {
2529
- return reply.code(404).send({ error: `Plugin not found: ${pluginId}` });
2530
2555
  }
2531
- const changelogPath = path.join(entry.pluginRoot, "CHANGELOG.md");
2532
- if (!changelogPath.startsWith(entry.pluginRoot)) {
2533
- return reply.code(400).send({ error: "Invalid path" });
2556
+ );
2557
+ server.get(
2558
+ `${basePath}/plugins/@:scope/:name/changelog`,
2559
+ {
2560
+ schema: { hide: true }
2561
+ },
2562
+ async (request, reply) => {
2563
+ const { scope, name } = request.params;
2564
+ if (!scope || !name) {
2565
+ return reply.code(400).send({ error: "Invalid plugin identifier" });
2566
+ }
2567
+ const pluginId = `@${scope}/${name}`;
2568
+ const snapshot = registry.snapshot();
2569
+ const manifests = extractSnapshotManifests(snapshot);
2570
+ const entry = manifests.find((e) => e.pluginId === pluginId);
2571
+ if (!entry) {
2572
+ return reply.code(404).send({ error: `Plugin not found: ${pluginId}` });
2573
+ }
2574
+ const changelogPath = path.join(entry.pluginRoot, "CHANGELOG.md");
2575
+ if (!changelogPath.startsWith(entry.pluginRoot)) {
2576
+ return reply.code(400).send({ error: "Invalid path" });
2577
+ }
2578
+ try {
2579
+ const content = await fs.readFile(changelogPath, "utf-8");
2580
+ return reply.code(200).header("Content-Type", "text/plain; charset=utf-8").send(content);
2581
+ } catch (error) {
2582
+ if (error.code === "ENOENT") {
2583
+ return reply.code(404).send({ error: "CHANGELOG.md not found" });
2584
+ }
2585
+ return reply.code(500).send({ error: "Failed to read CHANGELOG.md" });
2586
+ }
2534
2587
  }
2535
- try {
2536
- const content = await fs.readFile(changelogPath, "utf-8");
2537
- return reply.code(200).header("Content-Type", "text/plain; charset=utf-8").send(content);
2538
- } catch (error) {
2539
- if (error.code === "ENOENT") {
2540
- return reply.code(404).send({ error: "CHANGELOG.md not found" });
2588
+ );
2589
+ server.get(
2590
+ `${basePath}/studio/registry`,
2591
+ {
2592
+ schema: {
2593
+ tags: ["Studio"],
2594
+ summary: "Get Studio Registry V2 (plugin pages for Module Federation)"
2595
+ }
2596
+ },
2597
+ async (_request, reply) => {
2598
+ try {
2599
+ const snapshot = await restDomainOperationMetrics.observeOperation(
2600
+ "plugin.studio.registry",
2601
+ async () => registry.snapshot()
2602
+ );
2603
+ const manifests = extractSnapshotManifests(snapshot);
2604
+ const studioManifests = manifests.filter((entry) => entry.manifest.studio?.version === 2).map((entry) => ({
2605
+ manifest: entry.manifest,
2606
+ pluginRoot: entry.pluginRoot
2607
+ }));
2608
+ const studioRegistry = combineManifestsToRegistry(studioManifests);
2609
+ reply.type("application/json");
2610
+ return studioRegistry;
2611
+ } catch (error) {
2612
+ platform.logger.error(
2613
+ "Failed to generate studio registry",
2614
+ error instanceof Error ? error : new Error(String(error))
2615
+ );
2616
+ reply.code(500).send({
2617
+ error: "Failed to generate studio registry",
2618
+ message: error instanceof Error ? error.message : String(error)
2619
+ });
2541
2620
  }
2542
- return reply.code(500).send({ error: "Failed to read CHANGELOG.md" });
2543
2621
  }
2544
- });
2545
- server.get(`${basePath}/studio/registry`, {
2546
- schema: { tags: ["Studio"], summary: "Get Studio Registry V2 (plugin pages for Module Federation)" }
2547
- }, async (_request, reply) => {
2548
- try {
2549
- const snapshot = await restDomainOperationMetrics.observeOperation(
2550
- "plugin.studio.registry",
2551
- async () => registry.snapshot()
2552
- );
2622
+ );
2623
+ server.get(
2624
+ "/plugins/@:scope/:name/widgets/*",
2625
+ {
2626
+ schema: { hide: true }
2627
+ },
2628
+ async (request, reply) => {
2629
+ const { scope, name } = request.params;
2630
+ const filePath = request.params["*"];
2631
+ if (!scope || !name || !filePath || filePath.includes("..")) {
2632
+ return reply.code(400).send({ error: "Invalid path" });
2633
+ }
2634
+ const pluginId = `@${scope}/${name}`;
2635
+ const snapshot = registry.snapshot();
2553
2636
  const manifests = extractSnapshotManifests(snapshot);
2554
- const studioManifests = manifests.filter((entry) => entry.manifest.studio?.version === 2).map((entry) => ({ manifest: entry.manifest, pluginRoot: entry.pluginRoot }));
2555
- const studioRegistry = combineManifestsToRegistry(studioManifests);
2556
- reply.type("application/json");
2557
- return studioRegistry;
2558
- } catch (error) {
2559
- platform.logger.error("Failed to generate studio registry", error instanceof Error ? error : new Error(String(error)));
2560
- reply.code(500).send({
2561
- error: "Failed to generate studio registry",
2562
- message: error instanceof Error ? error.message : String(error)
2563
- });
2637
+ const entry = manifests.find(
2638
+ (e) => e.manifest.id === pluginId && e.manifest.studio?.version === 2
2639
+ );
2640
+ if (!entry) {
2641
+ return reply.code(404).send({ error: `No widget bundle for ${pluginId}` });
2642
+ }
2643
+ const { join: join2, extname } = await import('path');
2644
+ const { createReadStream, existsSync: existsSync2, statSync } = await import('fs');
2645
+ const bundleDir = join2(entry.pluginRoot, "dist", "widgets");
2646
+ const fullPath = join2(bundleDir, filePath);
2647
+ if (!fullPath.startsWith(bundleDir)) {
2648
+ return reply.code(400).send({ error: "Invalid path" });
2649
+ }
2650
+ if (!existsSync2(fullPath) || !statSync(fullPath).isFile()) {
2651
+ return reply.code(404).send({
2652
+ error: `Widget file not found: ${pluginId}/widgets/${filePath}`
2653
+ });
2654
+ }
2655
+ const MIME = {
2656
+ ".js": "application/javascript",
2657
+ ".mjs": "application/javascript",
2658
+ ".css": "text/css",
2659
+ ".json": "application/json",
2660
+ ".map": "application/json"
2661
+ };
2662
+ const ext = extname(filePath);
2663
+ const isEntry = filePath === "remoteEntry.js";
2664
+ return reply.code(200).header("Content-Type", MIME[ext] ?? "application/octet-stream").header("Content-Length", statSync(fullPath).size).header(
2665
+ "Cache-Control",
2666
+ isEntry ? "public, max-age=10, must-revalidate" : process.env.NODE_ENV === "production" ? "public, max-age=31536000, immutable" : "no-cache"
2667
+ ).send(createReadStream(fullPath));
2564
2668
  }
2565
- });
2566
- server.get("/plugins/@:scope/:name/widgets/*", {
2567
- schema: { hide: true }
2568
- }, async (request, reply) => {
2569
- const { scope, name } = request.params;
2570
- const filePath = request.params["*"];
2571
- if (!scope || !name || !filePath || filePath.includes("..")) {
2572
- return reply.code(400).send({ error: "Invalid path" });
2573
- }
2574
- const pluginId = `@${scope}/${name}`;
2575
- const snapshot = registry.snapshot();
2576
- const manifests = extractSnapshotManifests(snapshot);
2577
- const entry = manifests.find((e) => e.manifest.id === pluginId && e.manifest.studio?.version === 2);
2578
- if (!entry) {
2579
- return reply.code(404).send({ error: `No widget bundle for ${pluginId}` });
2580
- }
2581
- const { join: join3, extname } = await import('path');
2582
- const { createReadStream, existsSync: existsSync2, statSync } = await import('fs');
2583
- const bundleDir = join3(entry.pluginRoot, "dist", "widgets");
2584
- const fullPath = join3(bundleDir, filePath);
2585
- if (!fullPath.startsWith(bundleDir)) {
2586
- return reply.code(400).send({ error: "Invalid path" });
2587
- }
2588
- if (!existsSync2(fullPath) || !statSync(fullPath).isFile()) {
2589
- return reply.code(404).send({ error: `Widget file not found: ${pluginId}/widgets/${filePath}` });
2590
- }
2591
- const MIME = {
2592
- ".js": "application/javascript",
2593
- ".mjs": "application/javascript",
2594
- ".css": "text/css",
2595
- ".json": "application/json",
2596
- ".map": "application/json"
2597
- };
2598
- const ext = extname(filePath);
2599
- const isEntry = filePath === "remoteEntry.js";
2600
- return reply.code(200).header("Content-Type", MIME[ext] ?? "application/octet-stream").header("Content-Length", statSync(fullPath).size).header("Cache-Control", isEntry ? "public, max-age=10, must-revalidate" : process.env.NODE_ENV === "production" ? "public, max-age=31536000, immutable" : "no-cache").send(createReadStream(fullPath));
2601
- });
2669
+ );
2602
2670
  server.get(`${basePath}/plugins/health`, async (_request, reply) => {
2603
2671
  try {
2604
2672
  const snapshot = await restDomainOperationMetrics.observeOperation(
@@ -2617,7 +2685,9 @@ async function registerPluginSnapshotRoutes(server, config, registry) {
2617
2685
  }
2618
2686
  const registryErrors = snapshot.errors ?? [];
2619
2687
  const discoveryDiagnostics = snapshot.diagnostics ?? [];
2620
- const discoveryErrorCount = discoveryDiagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
2688
+ const discoveryErrorCount = discoveryDiagnostics.filter(
2689
+ (diagnostic) => diagnostic.severity === "error"
2690
+ ).length;
2621
2691
  const partialReasons = [];
2622
2692
  if (snapshot.corrupted) {
2623
2693
  partialReasons.push("snapshot_corrupted");
@@ -2658,7 +2728,9 @@ async function registerPluginSnapshotRoutes(server, config, registry) {
2658
2728
  diagnostics: {
2659
2729
  total: discoveryDiagnostics.length,
2660
2730
  errors: discoveryErrorCount,
2661
- warnings: discoveryDiagnostics.filter((diagnostic) => diagnostic.severity === "warning").length,
2731
+ warnings: discoveryDiagnostics.filter(
2732
+ (diagnostic) => diagnostic.severity === "warning"
2733
+ ).length,
2662
2734
  items: discoveryDiagnostics.map((diagnostic) => ({
2663
2735
  severity: diagnostic.severity,
2664
2736
  code: diagnostic.code,
@@ -2677,7 +2749,10 @@ async function registerPluginSnapshotRoutes(server, config, registry) {
2677
2749
  message: registryErrors.length > 0 ? `Registry is partial - ${registryErrors.length} plugin(s) failed to load. Check registryErrors.items for details.` : discoveryErrorCount > 0 ? `Registry has discovery diagnostics - ${discoveryErrorCount} blocking issue(s). Check diagnostics.items for details.` : snapshot.partial && partialReasons.includes("initialization_incomplete") ? "Registry is partial - initialization incomplete. Try refreshing or wait for the snapshot rebuild to complete." : snapshot.partial ? `Registry is partial - reasons: ${partialReasons.join(", ")}` : snapshot.stale ? "Registry is stale - plugin snapshot may be outdated." : validationIssues.length > 0 ? `${validationIssues.length} plugin(s) have validation errors.` : "All plugins are healthy."
2678
2750
  };
2679
2751
  } catch (error) {
2680
- platform.logger.error("Failed to get plugin health", error instanceof Error ? error : new Error(String(error)));
2752
+ platform.logger.error(
2753
+ "Failed to get plugin health",
2754
+ error instanceof Error ? error : new Error(String(error))
2755
+ );
2681
2756
  reply.code(500).send({
2682
2757
  error: "Failed to get plugin health",
2683
2758
  message: error instanceof Error ? error.message : String(error)
@@ -2699,7 +2774,9 @@ async function registerPluginSnapshotRoutes(server, config, registry) {
2699
2774
  "plugin.registry.snapshot",
2700
2775
  async () => registry.snapshot()
2701
2776
  );
2702
- const pluginEntry = snapshot.manifests.find((entry) => entry.pluginId === pluginId);
2777
+ const pluginEntry = snapshot.manifests.find(
2778
+ (entry) => entry.pluginId === pluginId
2779
+ );
2703
2780
  if (!pluginEntry) {
2704
2781
  reply.code(404).send({
2705
2782
  error: "Plugin not found",
@@ -2730,7 +2807,10 @@ Please answer the question based on the plugin manifest above.`;
2730
2807
  usage: response.usage
2731
2808
  };
2732
2809
  } catch (error) {
2733
- platform.logger.error("Failed to get AI answer about plugin", error instanceof Error ? error : new Error(String(error)));
2810
+ platform.logger.error(
2811
+ "Failed to get AI answer about plugin",
2812
+ error instanceof Error ? error : new Error(String(error))
2813
+ );
2734
2814
  reply.code(500).send({
2735
2815
  error: "Failed to get AI answer",
2736
2816
  message: error instanceof Error ? error.message : String(error)
@@ -3292,15 +3372,15 @@ async function registerCacheRoutes(server, config, registry) {
3292
3372
  }
3293
3373
  });
3294
3374
  }
3295
- async function registerObservabilityRoutes(fastify, config, repoRoot, historicalMetrics, platform17) {
3375
+ async function registerObservabilityRoutes(fastify, config, repoRoot, historicalMetrics, platform16) {
3296
3376
  const basePath = normalizeBasePath(config.basePath);
3297
3377
  const stateBrokerPaths = resolvePaths(basePath, "/observability/state-broker");
3298
3378
  const systemMetricsPaths = resolvePaths(basePath, "/observability/system-metrics");
3299
3379
  const metricsHistoryPaths = resolvePaths(basePath, "/observability/metrics/history");
3300
3380
  const metricsHeatmapPaths = resolvePaths(basePath, "/observability/metrics/heatmap");
3301
3381
  const insightsChatPaths = resolvePaths(basePath, "/observability/insights/chat");
3302
- for (const path3 of stateBrokerPaths) {
3303
- fastify.get(path3, { schema: { tags: ["Observability"], summary: "State Broker statistics" } }, async (_request, reply) => {
3382
+ for (const path2 of stateBrokerPaths) {
3383
+ fastify.get(path2, { schema: { tags: ["Observability"], summary: "State Broker statistics" } }, async (_request, reply) => {
3304
3384
  try {
3305
3385
  const stateBrokerUrl = process.env.KB_STATE_DAEMON_URL || "http://localhost:7777";
3306
3386
  fastify.log.debug({ url: stateBrokerUrl }, "Fetching State Broker stats");
@@ -3339,7 +3419,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3339
3419
  }
3340
3420
  };
3341
3421
  } catch (error) {
3342
- platform17?.logger.error("Failed to fetch State Broker stats", error instanceof Error ? error : new Error(String(error)));
3422
+ platform16?.logger.error("Failed to fetch State Broker stats", error instanceof Error ? error : new Error(String(error)));
3343
3423
  const isTimeout = error instanceof Error && error.name === "AbortError";
3344
3424
  return reply.code(503).send({
3345
3425
  ok: false,
@@ -3354,10 +3434,10 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3354
3434
  }
3355
3435
  });
3356
3436
  }
3357
- for (const path3 of systemMetricsPaths) {
3358
- fastify.get(path3, { schema: { tags: ["Observability"], summary: "System resource metrics" } }, async (_request, reply) => {
3437
+ for (const path2 of systemMetricsPaths) {
3438
+ fastify.get(path2, { schema: { tags: ["Observability"], summary: "System resource metrics" } }, async (_request, reply) => {
3359
3439
  try {
3360
- if (!platform17?.cache) {
3440
+ if (!platform16?.cache) {
3361
3441
  return reply.code(503).send({
3362
3442
  ok: false,
3363
3443
  error: {
@@ -3369,18 +3449,18 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3369
3449
  fastify.log.debug("Fetching system metrics from all instances");
3370
3450
  const allMetrics = [];
3371
3451
  try {
3372
- const cacheWithScan = platform17.cache;
3373
- if ("scan" in platform17.cache && typeof cacheWithScan.scan === "function") {
3452
+ const cacheWithScan = platform16.cache;
3453
+ if ("scan" in platform16.cache && typeof cacheWithScan.scan === "function") {
3374
3454
  const keys = await cacheWithScan.scan("system-metrics:*");
3375
3455
  for (const key of keys) {
3376
- const metrics = await platform17.cache.get(key);
3456
+ const metrics = await platform16.cache.get(key);
3377
3457
  if (metrics) {
3378
3458
  allMetrics.push(metrics);
3379
3459
  }
3380
3460
  }
3381
3461
  } else {
3382
3462
  const currentInstanceId = hostname();
3383
- const metrics = await platform17.cache.get(`system-metrics:${currentInstanceId}`);
3463
+ const metrics = await platform16.cache.get(`system-metrics:${currentInstanceId}`);
3384
3464
  if (metrics) {
3385
3465
  allMetrics.push(metrics);
3386
3466
  }
@@ -3389,7 +3469,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3389
3469
  } catch (scanError) {
3390
3470
  fastify.log.warn({ err: scanError }, "Failed to scan platform.cache for system metrics");
3391
3471
  const currentInstanceId = hostname();
3392
- const metrics = await platform17.cache.get(`system-metrics:${currentInstanceId}`);
3472
+ const metrics = await platform16.cache.get(`system-metrics:${currentInstanceId}`);
3393
3473
  if (metrics) {
3394
3474
  allMetrics.push(metrics);
3395
3475
  }
@@ -3437,7 +3517,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3437
3517
  }
3438
3518
  };
3439
3519
  } catch (error) {
3440
- platform17?.logger.error("Failed to fetch system metrics", error instanceof Error ? error : new Error(String(error)));
3520
+ platform16?.logger.error("Failed to fetch system metrics", error instanceof Error ? error : new Error(String(error)));
3441
3521
  return reply.code(500).send({
3442
3522
  ok: false,
3443
3523
  error: {
@@ -3448,8 +3528,8 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3448
3528
  }
3449
3529
  });
3450
3530
  }
3451
- for (const path3 of metricsHistoryPaths) {
3452
- fastify.get(path3, {
3531
+ for (const path2 of metricsHistoryPaths) {
3532
+ fastify.get(path2, {
3453
3533
  schema: {
3454
3534
  tags: ["Observability"],
3455
3535
  summary: "Historical time-series metrics",
@@ -3535,7 +3615,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3535
3615
  }
3536
3616
  };
3537
3617
  } catch (error) {
3538
- platform17?.logger.error("Failed to query historical metrics", error instanceof Error ? error : new Error(String(error)), { query });
3618
+ platform16?.logger.error("Failed to query historical metrics", error instanceof Error ? error : new Error(String(error)), { query });
3539
3619
  return reply.code(500).send({
3540
3620
  ok: false,
3541
3621
  error: {
@@ -3546,8 +3626,8 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3546
3626
  }
3547
3627
  });
3548
3628
  }
3549
- for (const path3 of metricsHeatmapPaths) {
3550
- fastify.get(path3, {
3629
+ for (const path2 of metricsHeatmapPaths) {
3630
+ fastify.get(path2, {
3551
3631
  schema: {
3552
3632
  tags: ["Observability"],
3553
3633
  summary: "Metrics heatmap data",
@@ -3626,7 +3706,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3626
3706
  }
3627
3707
  };
3628
3708
  } catch (error) {
3629
- platform17?.logger.error("Failed to query heatmap data", error instanceof Error ? error : new Error(String(error)), { query });
3709
+ platform16?.logger.error("Failed to query heatmap data", error instanceof Error ? error : new Error(String(error)), { query });
3630
3710
  return reply.code(500).send({
3631
3711
  ok: false,
3632
3712
  error: {
@@ -3637,8 +3717,8 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3637
3717
  }
3638
3718
  });
3639
3719
  }
3640
- for (const path3 of insightsChatPaths) {
3641
- fastify.post(path3, {
3720
+ for (const path2 of insightsChatPaths) {
3721
+ fastify.post(path2, {
3642
3722
  schema: {
3643
3723
  tags: ["Observability"],
3644
3724
  summary: "AI-powered observability insights chat",
@@ -3660,7 +3740,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
3660
3740
  }
3661
3741
  }
3662
3742
  }, async (request, reply) => {
3663
- if (!platform17?.llm) {
3743
+ if (!platform16?.llm) {
3664
3744
  return reply.code(503).send({
3665
3745
  ok: false,
3666
3746
  error: {
@@ -3726,15 +3806,15 @@ Provide a clear, actionable response based on the data above. Include:
3726
3806
 
3727
3807
  Be concise but thorough. Use markdown formatting.`;
3728
3808
  fastify.log.debug({ question: body.question, contextLength: contextText.length }, "Calling LLM for insights");
3729
- const result = await platform17.llm.complete(prompt, {
3809
+ const result = await platform16.llm.complete(prompt, {
3730
3810
  systemPrompt: "You are a DevOps and SRE expert assistant. Analyze system metrics and provide actionable insights. Be concise, technical, and helpful.",
3731
3811
  temperature: 0.7,
3732
3812
  maxTokens: 1e3
3733
3813
  });
3734
3814
  const totalTokens = result.usage.promptTokens + result.usage.completionTokens;
3735
3815
  fastify.log.debug({ tokensUsed: totalTokens }, "LLM response received for insights");
3736
- if (platform17.analytics) {
3737
- platform17.analytics.track("ai_insights.chat", {
3816
+ if (platform16.analytics) {
3817
+ platform16.analytics.track("ai_insights.chat", {
3738
3818
  questionLength: body.question.length,
3739
3819
  contextIncluded: Object.keys(contextConfig).filter((k) => contextConfig[k]),
3740
3820
  timeRange: contextConfig.timeRange,
@@ -3763,9 +3843,9 @@ Be concise but thorough. Use markdown formatting.`;
3763
3843
  }
3764
3844
  };
3765
3845
  } catch (error) {
3766
- platform17?.logger.error("Failed to generate insights", error instanceof Error ? error : new Error(String(error)));
3767
- if (platform17?.analytics) {
3768
- platform17.analytics.track("ai_insights.error", {
3846
+ platform16?.logger.error("Failed to generate insights", error instanceof Error ? error : new Error(String(error)));
3847
+ if (platform16?.analytics) {
3848
+ platform16.analytics.track("ai_insights.error", {
3769
3849
  error: error instanceof Error ? error.message : "Unknown error",
3770
3850
  questionLength: request.body?.question?.length ?? 0
3771
3851
  }).catch(() => {
@@ -3786,8 +3866,8 @@ Be concise but thorough. Use markdown formatting.`;
3786
3866
  async function registerAnalyticsRoutes(fastify, config) {
3787
3867
  const basePath = normalizeBasePath(config.basePath);
3788
3868
  const eventsPaths = resolvePaths(basePath, "/analytics/events");
3789
- for (const path3 of eventsPaths) {
3790
- fastify.get(path3, async (request, reply) => {
3869
+ for (const path2 of eventsPaths) {
3870
+ fastify.get(path2, async (request, reply) => {
3791
3871
  try {
3792
3872
  const analytics = platform.analytics;
3793
3873
  const getEvents = analytics.getEvents;
@@ -3856,8 +3936,8 @@ async function registerAnalyticsRoutes(fastify, config) {
3856
3936
  });
3857
3937
  }
3858
3938
  const statsPaths = resolvePaths(basePath, "/analytics/stats");
3859
- for (const path3 of statsPaths) {
3860
- fastify.get(path3, async (_request, reply) => {
3939
+ for (const path2 of statsPaths) {
3940
+ fastify.get(path2, async (_request, reply) => {
3861
3941
  try {
3862
3942
  const analytics = platform.analytics;
3863
3943
  const getStats = analytics.getStats;
@@ -3908,8 +3988,8 @@ async function registerAnalyticsRoutes(fastify, config) {
3908
3988
  });
3909
3989
  }
3910
3990
  const bufferPaths = resolvePaths(basePath, "/analytics/buffer/status");
3911
- for (const path3 of bufferPaths) {
3912
- fastify.get(path3, async (_request, reply) => {
3991
+ for (const path2 of bufferPaths) {
3992
+ fastify.get(path2, async (_request, reply) => {
3913
3993
  try {
3914
3994
  const analytics = platform.analytics;
3915
3995
  const getBufferStatus = analytics.getBufferStatus;
@@ -3965,8 +4045,8 @@ async function registerAnalyticsRoutes(fastify, config) {
3965
4045
  });
3966
4046
  }
3967
4047
  const dlqPaths = resolvePaths(basePath, "/analytics/dlq/status");
3968
- for (const path3 of dlqPaths) {
3969
- fastify.get(path3, async (_request, reply) => {
4048
+ for (const path2 of dlqPaths) {
4049
+ fastify.get(path2, async (_request, reply) => {
3970
4050
  try {
3971
4051
  const analytics = platform.analytics;
3972
4052
  const getDlqStatus = analytics.getDlqStatus;
@@ -4107,8 +4187,8 @@ function isValidISODate(dateString) {
4107
4187
  async function registerAdaptersRoutes(fastify, config) {
4108
4188
  const basePath = normalizeBasePath(config.basePath);
4109
4189
  const llmUsagePaths = resolvePaths(basePath, "/adapters/llm/usage");
4110
- for (const path3 of llmUsagePaths) {
4111
- fastify.get(path3, async (request, reply) => {
4190
+ for (const path2 of llmUsagePaths) {
4191
+ fastify.get(path2, async (request, reply) => {
4112
4192
  const analytics = platform.analytics;
4113
4193
  if (!analytics.getEvents) {
4114
4194
  return reply.code(501).send({
@@ -4291,8 +4371,8 @@ async function registerAdaptersRoutes(fastify, config) {
4291
4371
  });
4292
4372
  }
4293
4373
  const llmDailyStatsPaths = resolvePaths(basePath, "/adapters/llm/daily-stats");
4294
- for (const path3 of llmDailyStatsPaths) {
4295
- fastify.get(path3, async (request, reply) => {
4374
+ for (const path2 of llmDailyStatsPaths) {
4375
+ fastify.get(path2, async (request, reply) => {
4296
4376
  const analytics = platform.analytics;
4297
4377
  if (!analytics.getDailyStats) {
4298
4378
  return reply.code(501).send({
@@ -4337,8 +4417,8 @@ async function registerAdaptersRoutes(fastify, config) {
4337
4417
  });
4338
4418
  }
4339
4419
  const embeddingsDailyStatsPaths = resolvePaths(basePath, "/adapters/embeddings/daily-stats");
4340
- for (const path3 of embeddingsDailyStatsPaths) {
4341
- fastify.get(path3, async (request, reply) => {
4420
+ for (const path2 of embeddingsDailyStatsPaths) {
4421
+ fastify.get(path2, async (request, reply) => {
4342
4422
  const analytics = platform.analytics;
4343
4423
  if (!analytics.getDailyStats) {
4344
4424
  return reply.code(501).send({
@@ -4378,8 +4458,8 @@ async function registerAdaptersRoutes(fastify, config) {
4378
4458
  });
4379
4459
  }
4380
4460
  const embeddingsUsagePaths = resolvePaths(basePath, "/adapters/embeddings/usage");
4381
- for (const path3 of embeddingsUsagePaths) {
4382
- fastify.get(path3, async (request, reply) => {
4461
+ for (const path2 of embeddingsUsagePaths) {
4462
+ fastify.get(path2, async (request, reply) => {
4383
4463
  const analytics = platform.analytics;
4384
4464
  if (!analytics.getEvents) {
4385
4465
  return reply.code(501).send({
@@ -4507,8 +4587,8 @@ async function registerAdaptersRoutes(fastify, config) {
4507
4587
  });
4508
4588
  }
4509
4589
  const vectorstoreDailyStatsPaths = resolvePaths(basePath, "/adapters/vectorstore/daily-stats");
4510
- for (const path3 of vectorstoreDailyStatsPaths) {
4511
- fastify.get(path3, async (request, reply) => {
4590
+ for (const path2 of vectorstoreDailyStatsPaths) {
4591
+ fastify.get(path2, async (request, reply) => {
4512
4592
  const analytics = platform.analytics;
4513
4593
  if (!analytics.getDailyStats) {
4514
4594
  return reply.code(501).send({
@@ -4548,8 +4628,8 @@ async function registerAdaptersRoutes(fastify, config) {
4548
4628
  });
4549
4629
  }
4550
4630
  const vectorstoreUsagePaths = resolvePaths(basePath, "/adapters/vectorstore/usage");
4551
- for (const path3 of vectorstoreUsagePaths) {
4552
- fastify.get(path3, async (request, reply) => {
4631
+ for (const path2 of vectorstoreUsagePaths) {
4632
+ fastify.get(path2, async (request, reply) => {
4553
4633
  const analytics = platform.analytics;
4554
4634
  if (!analytics.getEvents) {
4555
4635
  return reply.code(501).send({
@@ -4660,8 +4740,8 @@ async function registerAdaptersRoutes(fastify, config) {
4660
4740
  });
4661
4741
  }
4662
4742
  const cacheDailyStatsPaths = resolvePaths(basePath, "/adapters/cache/daily-stats");
4663
- for (const path3 of cacheDailyStatsPaths) {
4664
- fastify.get(path3, async (request, reply) => {
4743
+ for (const path2 of cacheDailyStatsPaths) {
4744
+ fastify.get(path2, async (request, reply) => {
4665
4745
  const analytics = platform.analytics;
4666
4746
  if (!analytics.getDailyStats) {
4667
4747
  return reply.code(501).send({
@@ -4701,8 +4781,8 @@ async function registerAdaptersRoutes(fastify, config) {
4701
4781
  });
4702
4782
  }
4703
4783
  const cacheUsagePaths = resolvePaths(basePath, "/adapters/cache/usage");
4704
- for (const path3 of cacheUsagePaths) {
4705
- fastify.get(path3, async (request, reply) => {
4784
+ for (const path2 of cacheUsagePaths) {
4785
+ fastify.get(path2, async (request, reply) => {
4706
4786
  const analytics = platform.analytics;
4707
4787
  if (!analytics.getEvents) {
4708
4788
  return reply.code(501).send({
@@ -4788,8 +4868,8 @@ async function registerAdaptersRoutes(fastify, config) {
4788
4868
  });
4789
4869
  }
4790
4870
  const storageDailyStatsPaths = resolvePaths(basePath, "/adapters/storage/daily-stats");
4791
- for (const path3 of storageDailyStatsPaths) {
4792
- fastify.get(path3, async (request, reply) => {
4871
+ for (const path2 of storageDailyStatsPaths) {
4872
+ fastify.get(path2, async (request, reply) => {
4793
4873
  const analytics = platform.analytics;
4794
4874
  if (!analytics.getDailyStats) {
4795
4875
  return reply.code(501).send({
@@ -4829,8 +4909,8 @@ async function registerAdaptersRoutes(fastify, config) {
4829
4909
  });
4830
4910
  }
4831
4911
  const storageUsagePaths = resolvePaths(basePath, "/adapters/storage/usage");
4832
- for (const path3 of storageUsagePaths) {
4833
- fastify.get(path3, async (request, reply) => {
4912
+ for (const path2 of storageUsagePaths) {
4913
+ fastify.get(path2, async (request, reply) => {
4834
4914
  const analytics = platform.analytics;
4835
4915
  if (!analytics.getEvents) {
4836
4916
  return reply.code(501).send({
@@ -5037,7 +5117,7 @@ Keep the summary under 300 words. Use simple paragraphs separated by double newl
5037
5117
  }
5038
5118
  function extractCorrelationKeys(log) {
5039
5119
  return {
5040
- requestId: log.fields.requestId ?? log.fields.reqId,
5120
+ requestId: log.fields.requestId,
5041
5121
  traceId: log.fields.traceId,
5042
5122
  executionId: log.fields.executionId,
5043
5123
  sessionId: log.fields.sessionId
@@ -5048,12 +5128,15 @@ async function findRelatedLogs(targetLog) {
5048
5128
  const timeWindow = 6e4;
5049
5129
  if (correlationKeys.requestId || correlationKeys.traceId || correlationKeys.executionId) {
5050
5130
  const relatedLogs = [];
5051
- const result2 = await platform.logs.query({
5052
- from: targetLog.timestamp - timeWindow,
5053
- to: targetLog.timestamp + timeWindow
5054
- }, {
5055
- limit: 1e3
5056
- });
5131
+ const result2 = await platform.logs.query(
5132
+ {
5133
+ from: targetLog.timestamp - timeWindow,
5134
+ to: targetLog.timestamp + timeWindow
5135
+ },
5136
+ {
5137
+ limit: 1e3
5138
+ }
5139
+ );
5057
5140
  for (const log of result2.logs) {
5058
5141
  if (log.id === targetLog.id) {
5059
5142
  continue;
@@ -5067,13 +5150,16 @@ async function findRelatedLogs(targetLog) {
5067
5150
  return relatedLogs.sort((a, b) => a.timestamp - b.timestamp).map(toFrontendLogRecord);
5068
5151
  }
5069
5152
  }
5070
- const result = await platform.logs.query({
5071
- source: targetLog.source,
5072
- from: targetLog.timestamp - timeWindow,
5073
- to: targetLog.timestamp + timeWindow
5074
- }, {
5075
- limit: 50
5076
- });
5153
+ const result = await platform.logs.query(
5154
+ {
5155
+ source: targetLog.source,
5156
+ from: targetLog.timestamp - timeWindow,
5157
+ to: targetLog.timestamp + timeWindow
5158
+ },
5159
+ {
5160
+ limit: 50
5161
+ }
5162
+ );
5077
5163
  return result.logs.filter((log) => log.id !== targetLog.id).sort((a, b) => a.timestamp - b.timestamp).map(toFrontendLogRecord);
5078
5164
  }
5079
5165
  function generateFallbackSummary(stats, logs) {
@@ -5094,7 +5180,9 @@ function generateFallbackSummary(stats, logs) {
5094
5180
  if (Object.keys(stats.byPlugin).length > 0) {
5095
5181
  summary += `By Plugin:
5096
5182
  `;
5097
- Object.entries(stats.byPlugin).sort((a, b) => b[1] - a[1]).slice(0, 5).forEach(([plugin, count]) => {
5183
+ Object.entries(stats.byPlugin).sort(
5184
+ (a, b) => b[1] - a[1]
5185
+ ).slice(0, 5).forEach(([plugin, count]) => {
5098
5186
  summary += `${plugin}: ${count}
5099
5187
  `;
5100
5188
  });
@@ -5120,62 +5208,70 @@ function generateFallbackSummary(stats, logs) {
5120
5208
  return summary;
5121
5209
  }
5122
5210
  async function registerLogRoutes(server, config, eventHub) {
5123
- server.get("/api/v1/logs", { schema: { tags: ["Logs"], summary: "Query logs with filters" } }, async (request, reply) => {
5124
- try {
5125
- const limit = request.query.limit ? parseInt(request.query.limit, 10) : 100;
5126
- const offset = request.query.offset ? parseInt(request.query.offset, 10) : 0;
5127
- const query = {
5128
- level: request.query.level,
5129
- source: request.query.plugin,
5130
- from: request.query.from ? new Date(request.query.from).getTime() : void 0,
5131
- to: request.query.to ? new Date(request.query.to).getTime() : void 0
5132
- };
5133
- let result;
5134
- if (request.query.search) {
5135
- result = await platform.logs.search(request.query.search, {
5136
- limit,
5137
- offset
5138
- });
5139
- } else {
5140
- result = await platform.logs.query(query, {
5141
- limit,
5142
- offset
5211
+ server.get(
5212
+ "/api/v1/logs",
5213
+ { schema: { tags: ["Logs"], summary: "Query logs with filters" } },
5214
+ async (request, reply) => {
5215
+ try {
5216
+ const limit = request.query.limit ? parseInt(request.query.limit, 10) : 100;
5217
+ const offset = request.query.offset ? parseInt(request.query.offset, 10) : 0;
5218
+ const query = {
5219
+ level: request.query.level,
5220
+ source: request.query.plugin,
5221
+ from: request.query.from ? new Date(request.query.from).getTime() : void 0,
5222
+ to: request.query.to ? new Date(request.query.to).getTime() : void 0
5223
+ };
5224
+ let result;
5225
+ if (request.query.search) {
5226
+ result = await platform.logs.search(request.query.search, {
5227
+ limit,
5228
+ offset
5229
+ });
5230
+ } else {
5231
+ result = await platform.logs.query(query, {
5232
+ limit,
5233
+ offset
5234
+ });
5235
+ }
5236
+ const frontendLogs = result.logs.map(toFrontendLogRecord);
5237
+ const stats = await platform.logs.getStats();
5238
+ return {
5239
+ ok: true,
5240
+ data: {
5241
+ logs: frontendLogs,
5242
+ total: result.total,
5243
+ hasMore: result.hasMore,
5244
+ filters: request.query,
5245
+ source: "source" in result ? result.source : void 0,
5246
+ stats: {
5247
+ buffer: stats.buffer ? {
5248
+ size: stats.buffer.size,
5249
+ maxSize: stats.buffer.maxSize,
5250
+ oldest: stats.buffer.oldestTimestamp ? new Date(stats.buffer.oldestTimestamp).toISOString() : void 0,
5251
+ newest: stats.buffer.newestTimestamp ? new Date(stats.buffer.newestTimestamp).toISOString() : void 0
5252
+ } : void 0,
5253
+ persistence: stats.persistence ? {
5254
+ totalLogs: stats.persistence.totalLogs,
5255
+ oldestTimestamp: stats.persistence.oldestTimestamp ? new Date(
5256
+ stats.persistence.oldestTimestamp
5257
+ ).toISOString() : void 0,
5258
+ newestTimestamp: stats.persistence.newestTimestamp ? new Date(
5259
+ stats.persistence.newestTimestamp
5260
+ ).toISOString() : void 0,
5261
+ sizeBytes: stats.persistence.sizeBytes
5262
+ } : void 0
5263
+ }
5264
+ }
5265
+ };
5266
+ } catch (error) {
5267
+ return reply.code(503).send({
5268
+ ok: false,
5269
+ error: "Log query failed",
5270
+ message: error instanceof Error ? error.message : "Unknown error"
5143
5271
  });
5144
5272
  }
5145
- const frontendLogs = result.logs.map(toFrontendLogRecord);
5146
- const stats = await platform.logs.getStats();
5147
- return {
5148
- ok: true,
5149
- data: {
5150
- logs: frontendLogs,
5151
- total: result.total,
5152
- hasMore: result.hasMore,
5153
- filters: request.query,
5154
- source: "source" in result ? result.source : void 0,
5155
- stats: {
5156
- buffer: stats.buffer ? {
5157
- size: stats.buffer.size,
5158
- maxSize: stats.buffer.maxSize,
5159
- oldest: stats.buffer.oldestTimestamp ? new Date(stats.buffer.oldestTimestamp).toISOString() : void 0,
5160
- newest: stats.buffer.newestTimestamp ? new Date(stats.buffer.newestTimestamp).toISOString() : void 0
5161
- } : void 0,
5162
- persistence: stats.persistence ? {
5163
- totalLogs: stats.persistence.totalLogs,
5164
- oldestTimestamp: stats.persistence.oldestTimestamp ? new Date(stats.persistence.oldestTimestamp).toISOString() : void 0,
5165
- newestTimestamp: stats.persistence.newestTimestamp ? new Date(stats.persistence.newestTimestamp).toISOString() : void 0,
5166
- sizeBytes: stats.persistence.sizeBytes
5167
- } : void 0
5168
- }
5169
- }
5170
- };
5171
- } catch (error) {
5172
- return reply.code(503).send({
5173
- ok: false,
5174
- error: "Log query failed",
5175
- message: error instanceof Error ? error.message : "Unknown error"
5176
- });
5177
5273
  }
5178
- });
5274
+ );
5179
5275
  server.get(
5180
5276
  "/api/v1/logs/:id",
5181
5277
  { schema: { tags: ["Logs"], summary: "Get log entry by ID" } },
@@ -5212,7 +5308,12 @@ async function registerLogRoutes(server, config, eventHub) {
5212
5308
  );
5213
5309
  server.get(
5214
5310
  "/api/v1/logs/:id/related",
5215
- { schema: { tags: ["Logs"], summary: "Get logs related to a specific entry" } },
5311
+ {
5312
+ schema: {
5313
+ tags: ["Logs"],
5314
+ summary: "Get logs related to a specific entry"
5315
+ }
5316
+ },
5216
5317
  async (request, reply) => {
5217
5318
  try {
5218
5319
  const log = await platform.logs.getById(request.params.id);
@@ -5241,202 +5342,223 @@ async function registerLogRoutes(server, config, eventHub) {
5241
5342
  }
5242
5343
  }
5243
5344
  );
5244
- server.get("/api/v1/logs/stream", { schema: { hide: true } }, async (request, reply) => {
5245
- const caps = platform.logs.getCapabilities();
5246
- if (!caps.hasStreaming) {
5247
- return reply.code(503).send({
5248
- error: "Log streaming not enabled",
5249
- message: "Logger adapter does not support streaming. Enable logRingBuffer in kb.config.json"
5250
- });
5251
- }
5252
- reply.hijack();
5253
- let streamClosed = false;
5254
- const origin = request.headers.origin;
5255
- if (origin === "http://localhost:3000" || origin === "http://localhost:5173") {
5256
- reply.raw.setHeader("Access-Control-Allow-Origin", origin);
5257
- reply.raw.setHeader("Access-Control-Allow-Credentials", "true");
5258
- } else {
5259
- reply.raw.setHeader("Access-Control-Allow-Origin", "*");
5260
- }
5261
- reply.raw.setHeader("Content-Type", "text/event-stream");
5262
- reply.raw.setHeader("Cache-Control", "no-cache, no-transform");
5263
- reply.raw.setHeader("Connection", "keep-alive");
5264
- try {
5265
- reply.raw.flushHeaders?.();
5266
- reply.raw.write(": connected\n\n");
5267
- } catch (err) {
5268
- streamClosed = true;
5269
- return;
5270
- }
5271
- const unsubscribe = platform.logs.subscribe((log) => {
5272
- if (!streamClosed && !reply.raw.writableEnded && !reply.raw.destroyed) {
5273
- try {
5274
- const frontendLog = toFrontendLogRecord(log);
5275
- reply.raw.write(`event: log
5345
+ server.get(
5346
+ "/api/v1/logs/stream",
5347
+ { schema: { hide: true } },
5348
+ async (request, reply) => {
5349
+ const caps = platform.logs.getCapabilities();
5350
+ if (!caps.hasStreaming) {
5351
+ return reply.code(503).send({
5352
+ error: "Log streaming not enabled",
5353
+ message: "Logger adapter does not support streaming. Enable logRingBuffer in kb.config.json"
5354
+ });
5355
+ }
5356
+ reply.hijack();
5357
+ let streamClosed = false;
5358
+ const origin = request.headers.origin;
5359
+ if (origin === "http://localhost:3000" || origin === "http://localhost:5173") {
5360
+ reply.raw.setHeader("Access-Control-Allow-Origin", origin);
5361
+ reply.raw.setHeader("Access-Control-Allow-Credentials", "true");
5362
+ } else {
5363
+ reply.raw.setHeader("Access-Control-Allow-Origin", "*");
5364
+ }
5365
+ reply.raw.setHeader("Content-Type", "text/event-stream");
5366
+ reply.raw.setHeader("Cache-Control", "no-cache, no-transform");
5367
+ reply.raw.setHeader("Connection", "keep-alive");
5368
+ try {
5369
+ reply.raw.flushHeaders?.();
5370
+ reply.raw.write(": connected\n\n");
5371
+ } catch (err) {
5372
+ streamClosed = true;
5373
+ return;
5374
+ }
5375
+ const unsubscribe = platform.logs.subscribe((log) => {
5376
+ if (!streamClosed && !reply.raw.writableEnded && !reply.raw.destroyed) {
5377
+ try {
5378
+ const frontendLog = toFrontendLogRecord(log);
5379
+ reply.raw.write(`event: log
5276
5380
  `);
5277
- reply.raw.write(`data: ${JSON.stringify(frontendLog)}
5381
+ reply.raw.write(`data: ${JSON.stringify(frontendLog)}
5278
5382
 
5279
5383
  `);
5280
- } catch (err) {
5281
- streamClosed = true;
5282
- }
5283
- }
5284
- });
5285
- const cleanup = () => {
5286
- if (!streamClosed) {
5287
- streamClosed = true;
5288
- unsubscribe();
5289
- try {
5290
- if (!reply.raw.writableEnded && !reply.raw.destroyed) {
5291
- reply.raw.end();
5384
+ } catch (err) {
5385
+ streamClosed = true;
5292
5386
  }
5293
- } catch (err) {
5294
5387
  }
5295
- }
5296
- };
5297
- request.raw.on("close", cleanup);
5298
- request.raw.on("error", cleanup);
5299
- await new Promise(() => {
5300
- });
5301
- });
5302
- server.get("/api/v1/logs/stats", { schema: { tags: ["Logs"], summary: "Get log storage statistics" } }, async (request, reply) => {
5303
- try {
5304
- const stats = await platform.logs.getStats();
5305
- const caps = platform.logs.getCapabilities();
5306
- return {
5307
- ok: true,
5308
- data: {
5309
- capabilities: caps,
5310
- buffer: stats.buffer ? {
5311
- size: stats.buffer.size,
5312
- maxSize: stats.buffer.maxSize,
5313
- oldest: stats.buffer.oldestTimestamp ? new Date(stats.buffer.oldestTimestamp).toISOString() : void 0,
5314
- newest: stats.buffer.newestTimestamp ? new Date(stats.buffer.newestTimestamp).toISOString() : void 0
5315
- } : void 0,
5316
- persistence: stats.persistence ? {
5317
- totalLogs: stats.persistence.totalLogs,
5318
- oldest: stats.persistence.oldestTimestamp ? new Date(stats.persistence.oldestTimestamp).toISOString() : void 0,
5319
- newest: stats.persistence.newestTimestamp ? new Date(stats.persistence.newestTimestamp).toISOString() : void 0,
5320
- sizeBytes: stats.persistence.sizeBytes
5321
- } : void 0
5388
+ });
5389
+ const cleanup = () => {
5390
+ if (!streamClosed) {
5391
+ streamClosed = true;
5392
+ unsubscribe();
5393
+ try {
5394
+ if (!reply.raw.writableEnded && !reply.raw.destroyed) {
5395
+ reply.raw.end();
5396
+ }
5397
+ } catch (err) {
5398
+ }
5322
5399
  }
5323
5400
  };
5324
- } catch (error) {
5325
- return reply.code(500).send({
5326
- ok: false,
5327
- error: "Failed to fetch stats",
5328
- message: error instanceof Error ? error.message : "Unknown error"
5401
+ request.raw.on("close", cleanup);
5402
+ request.raw.on("error", cleanup);
5403
+ await new Promise(() => {
5329
5404
  });
5330
5405
  }
5331
- });
5332
- server.post("/api/v1/logs/summarize", { schema: { tags: ["Logs"], summary: "AI-powered log summarization" } }, async (request, reply) => {
5333
- const { timeRange, filters, groupBy, question, includeContext } = request.body;
5334
- try {
5335
- const query = {
5336
- level: filters?.level,
5337
- source: filters?.plugin,
5338
- from: timeRange?.from ? new Date(timeRange.from).getTime() : void 0,
5339
- to: timeRange?.to ? new Date(timeRange.to).getTime() : void 0
5340
- };
5341
- const result = await platform.logs.query(query, { limit: 1e3 });
5342
- const frontendLogs = result.logs.map(toFrontendLogRecord);
5343
- let filteredLogs = frontendLogs;
5344
- if (filters?.traceId) {
5345
- filteredLogs = filteredLogs.filter((log) => log.traceId === filters.traceId);
5346
- }
5347
- if (filters?.executionId) {
5348
- filteredLogs = filteredLogs.filter((log) => log.executionId === filters.executionId);
5349
- }
5350
- const stats = {
5351
- total: filteredLogs.length,
5352
- byLevel: {},
5353
- byPlugin: {},
5354
- topErrors: [],
5355
- timeRange: {
5356
- from: filteredLogs.length > 0 ? filteredLogs[0].time : null,
5357
- to: filteredLogs.length > 0 ? filteredLogs[filteredLogs.length - 1].time : null
5406
+ );
5407
+ server.get(
5408
+ "/api/v1/logs/stats",
5409
+ { schema: { tags: ["Logs"], summary: "Get log storage statistics" } },
5410
+ async (request, reply) => {
5411
+ try {
5412
+ const stats = await platform.logs.getStats();
5413
+ const caps = platform.logs.getCapabilities();
5414
+ return {
5415
+ ok: true,
5416
+ data: {
5417
+ capabilities: caps,
5418
+ buffer: stats.buffer ? {
5419
+ size: stats.buffer.size,
5420
+ maxSize: stats.buffer.maxSize,
5421
+ oldest: stats.buffer.oldestTimestamp ? new Date(stats.buffer.oldestTimestamp).toISOString() : void 0,
5422
+ newest: stats.buffer.newestTimestamp ? new Date(stats.buffer.newestTimestamp).toISOString() : void 0
5423
+ } : void 0,
5424
+ persistence: stats.persistence ? {
5425
+ totalLogs: stats.persistence.totalLogs,
5426
+ oldest: stats.persistence.oldestTimestamp ? new Date(stats.persistence.oldestTimestamp).toISOString() : void 0,
5427
+ newest: stats.persistence.newestTimestamp ? new Date(stats.persistence.newestTimestamp).toISOString() : void 0,
5428
+ sizeBytes: stats.persistence.sizeBytes
5429
+ } : void 0
5430
+ }
5431
+ };
5432
+ } catch (error) {
5433
+ return reply.code(500).send({
5434
+ ok: false,
5435
+ error: "Failed to fetch stats",
5436
+ message: error instanceof Error ? error.message : "Unknown error"
5437
+ });
5438
+ }
5439
+ }
5440
+ );
5441
+ server.post(
5442
+ "/api/v1/logs/summarize",
5443
+ { schema: { tags: ["Logs"], summary: "AI-powered log summarization" } },
5444
+ async (request, reply) => {
5445
+ const { timeRange, filters, groupBy, question, includeContext } = request.body;
5446
+ try {
5447
+ const query = {
5448
+ level: filters?.level,
5449
+ source: filters?.plugin,
5450
+ from: timeRange?.from ? new Date(timeRange.from).getTime() : void 0,
5451
+ to: timeRange?.to ? new Date(timeRange.to).getTime() : void 0
5452
+ };
5453
+ const result = await platform.logs.query(query, { limit: 1e3 });
5454
+ const frontendLogs = result.logs.map(toFrontendLogRecord);
5455
+ let filteredLogs = frontendLogs;
5456
+ if (filters?.traceId) {
5457
+ filteredLogs = filteredLogs.filter(
5458
+ (log) => log.traceId === filters.traceId
5459
+ );
5358
5460
  }
5359
- };
5360
- const errorMessages = /* @__PURE__ */ new Map();
5361
- for (const log of filteredLogs) {
5362
- stats.byLevel[log.level] = (stats.byLevel[log.level] || 0) + 1;
5363
- if (log.plugin) {
5364
- stats.byPlugin[log.plugin] = (stats.byPlugin[log.plugin] || 0) + 1;
5365
- }
5366
- if (log.level === "error" && log.msg) {
5367
- errorMessages.set(log.msg, (errorMessages.get(log.msg) || 0) + 1);
5368
- }
5369
- }
5370
- stats.topErrors = Array.from(errorMessages.entries()).map(([message2, count]) => ({ message: message2, count })).sort((a, b) => b.count - a.count).slice(0, 10);
5371
- let groups = null;
5372
- if (groupBy) {
5373
- const fieldMap = {
5374
- trace: "traceId",
5375
- execution: "executionId",
5376
- plugin: "plugin"
5461
+ if (filters?.executionId) {
5462
+ filteredLogs = filteredLogs.filter(
5463
+ (log) => log.executionId === filters.executionId
5464
+ );
5465
+ }
5466
+ const stats = {
5467
+ total: filteredLogs.length,
5468
+ byLevel: {},
5469
+ byPlugin: {},
5470
+ topErrors: [],
5471
+ timeRange: {
5472
+ from: filteredLogs.length > 0 ? filteredLogs[0].time : null,
5473
+ to: filteredLogs.length > 0 ? filteredLogs[filteredLogs.length - 1].time : null
5474
+ }
5377
5475
  };
5378
- const groupMap = /* @__PURE__ */ new Map();
5476
+ const errorMessages = /* @__PURE__ */ new Map();
5379
5477
  for (const log of filteredLogs) {
5380
- const fieldName = fieldMap[groupBy] || groupBy;
5381
- const key = String(log[fieldName] || "unknown");
5382
- if (!groupMap.has(key)) {
5383
- groupMap.set(key, []);
5478
+ stats.byLevel[log.level] = (stats.byLevel[log.level] || 0) + 1;
5479
+ if (log.plugin) {
5480
+ stats.byPlugin[log.plugin] = (stats.byPlugin[log.plugin] || 0) + 1;
5481
+ }
5482
+ if (log.level === "error" && log.msg) {
5483
+ errorMessages.set(log.msg, (errorMessages.get(log.msg) || 0) + 1);
5384
5484
  }
5385
- groupMap.get(key).push(log);
5386
5485
  }
5387
- groups = Object.fromEntries(groupMap.entries());
5388
- }
5389
- let aiSummary = null;
5390
- let message = null;
5391
- if (platform.llm && question) {
5392
- try {
5393
- const context = includeContext || {
5394
- errors: true,
5395
- warnings: true,
5396
- info: false,
5397
- metadata: true,
5398
- stackTraces: true
5486
+ stats.topErrors = Array.from(errorMessages.entries()).map(([message2, count]) => ({ message: message2, count })).sort((a, b) => b.count - a.count).slice(0, 10);
5487
+ let groups = null;
5488
+ if (groupBy) {
5489
+ const fieldMap = {
5490
+ trace: "traceId",
5491
+ execution: "executionId",
5492
+ plugin: "plugin"
5399
5493
  };
5400
- const prompt = buildLogSummaryPrompt(question, filteredLogs, stats, context);
5401
- const response = await platform.llm.complete(prompt, {
5402
- temperature: 0.7,
5403
- maxTokens: 1e3,
5404
- systemPrompt: "You are a technical log analysis assistant. Provide clear, actionable insights based on application logs."
5405
- });
5406
- aiSummary = response.content.trim();
5407
- } catch (error) {
5408
- platform.logger.warn("LLM summarization failed, using fallback", {
5409
- error: error instanceof Error ? error.message : String(error)
5410
- });
5494
+ const groupMap = /* @__PURE__ */ new Map();
5495
+ for (const log of filteredLogs) {
5496
+ const fieldName = fieldMap[groupBy] || groupBy;
5497
+ const key = String(log[fieldName] || "unknown");
5498
+ if (!groupMap.has(key)) {
5499
+ groupMap.set(key, []);
5500
+ }
5501
+ groupMap.get(key).push(log);
5502
+ }
5503
+ groups = Object.fromEntries(groupMap.entries());
5504
+ }
5505
+ let aiSummary = null;
5506
+ let message = null;
5507
+ if (platform.llm && question) {
5508
+ try {
5509
+ const context = includeContext || {
5510
+ errors: true,
5511
+ warnings: true,
5512
+ info: false,
5513
+ metadata: true,
5514
+ stackTraces: true
5515
+ };
5516
+ const prompt = buildLogSummaryPrompt(
5517
+ question,
5518
+ filteredLogs,
5519
+ stats,
5520
+ context
5521
+ );
5522
+ const response = await platform.llm.complete(prompt, {
5523
+ temperature: 0.7,
5524
+ maxTokens: 1e3,
5525
+ systemPrompt: "You are a technical log analysis assistant. Provide clear, actionable insights based on application logs."
5526
+ });
5527
+ aiSummary = response.content.trim();
5528
+ } catch (error) {
5529
+ platform.logger.warn("LLM summarization failed, using fallback", {
5530
+ error: error instanceof Error ? error.message : String(error)
5531
+ });
5532
+ aiSummary = generateFallbackSummary(stats, filteredLogs);
5533
+ message = "AI summarization unavailable, showing statistical summary";
5534
+ }
5535
+ } else {
5411
5536
  aiSummary = generateFallbackSummary(stats, filteredLogs);
5412
- message = "AI summarization unavailable, showing statistical summary";
5537
+ message = platform.llm ? "No question provided" : "LLM not configured";
5413
5538
  }
5414
- } else {
5415
- aiSummary = generateFallbackSummary(stats, filteredLogs);
5416
- message = platform.llm ? "No question provided" : "LLM not configured";
5539
+ return {
5540
+ ok: true,
5541
+ data: {
5542
+ summary: {
5543
+ question: question || "General log summary",
5544
+ timeRange: stats.timeRange,
5545
+ total: stats.total,
5546
+ stats,
5547
+ groups
5548
+ },
5549
+ aiSummary,
5550
+ message
5551
+ }
5552
+ };
5553
+ } catch (error) {
5554
+ return reply.code(500).send({
5555
+ ok: false,
5556
+ error: "Log summarization failed",
5557
+ message: error instanceof Error ? error.message : "Unknown error"
5558
+ });
5417
5559
  }
5418
- return {
5419
- ok: true,
5420
- data: {
5421
- summary: {
5422
- question: question || "General log summary",
5423
- timeRange: stats.timeRange,
5424
- total: stats.total,
5425
- stats,
5426
- groups
5427
- },
5428
- aiSummary,
5429
- message
5430
- }
5431
- };
5432
- } catch (error) {
5433
- return reply.code(500).send({
5434
- ok: false,
5435
- error: "Log summarization failed",
5436
- message: error instanceof Error ? error.message : "Unknown error"
5437
- });
5438
5560
  }
5439
- });
5561
+ );
5440
5562
  }
5441
5563
  var SENSITIVE_KEYS = [
5442
5564
  "apiKey",
@@ -5447,10 +5569,10 @@ var SENSITIVE_KEYS = [
5447
5569
  "credentials",
5448
5570
  "auth"
5449
5571
  ];
5450
- function redactSensitiveData(obj, redacted = [], path3 = "") {
5572
+ function redactSensitiveData(obj, redacted = [], path2 = "") {
5451
5573
  const result = {};
5452
5574
  for (const [key, value] of Object.entries(obj)) {
5453
- const currentPath = path3 ? `${path3}.${key}` : key;
5575
+ const currentPath = path2 ? `${path2}.${key}` : key;
5454
5576
  const lowerKey = key.toLowerCase();
5455
5577
  const isSensitive = SENSITIVE_KEYS.some((pattern) => lowerKey.includes(pattern.toLowerCase()));
5456
5578
  if (isSensitive && typeof value === "string") {
@@ -5492,8 +5614,8 @@ async function loadPlatformConfig(repoRoot) {
5492
5614
  async function registerPlatformRoutes(fastify, config, repoRoot) {
5493
5615
  const basePath = normalizeBasePath(config.basePath);
5494
5616
  const configPaths = resolvePaths(basePath, "/platform/config");
5495
- for (const path3 of configPaths) {
5496
- fastify.get(path3, async (_request, reply) => {
5617
+ for (const path2 of configPaths) {
5618
+ fastify.get(path2, async (_request, reply) => {
5497
5619
  try {
5498
5620
  const { adapters, adapterOptions, execution } = await restDomainOperationMetrics.observeOperation(
5499
5621
  "platform.config.get",
@@ -5692,8 +5814,8 @@ function registerRouteCollector(fastify) {
5692
5814
  async function registerDebugRoutes(fastify, config) {
5693
5815
  const basePath = normalizeBasePath(config.basePath);
5694
5816
  const routesPaths = resolvePaths(basePath, "/routes");
5695
- for (const path3 of routesPaths) {
5696
- fastify.get(path3, async (_request, reply) => {
5817
+ for (const path2 of routesPaths) {
5818
+ fastify.get(path2, async (_request, reply) => {
5697
5819
  let routes = collectedRoutes.filter((r) => r.url.startsWith("/api/"));
5698
5820
  if (routes.length === 0) {
5699
5821
  routes = [...collectedRoutes];
@@ -6101,20 +6223,18 @@ function registerRequestIdMiddleware(server) {
6101
6223
  request.id = requestId;
6102
6224
  reply.header("X-Request-Id", requestId);
6103
6225
  reply.header("X-Trace-Id", traceId);
6104
- request.kbLogger = createCorrelatedLogger(platform.logger, {
6226
+ const logger = createHttpLogger(platform.logger, {
6105
6227
  serviceId: "rest",
6106
- logsSource: "rest",
6107
- layer: "rest",
6108
- service: "request",
6228
+ layer: "service",
6229
+ component: "http-request",
6109
6230
  requestId,
6110
6231
  traceId,
6111
6232
  method: request.method,
6112
6233
  url: request.url,
6113
6234
  operation: "http.request"
6114
6235
  });
6115
- const method = request.method.toUpperCase();
6116
- const fullUrl = request.url;
6117
- request.kbLogger.info(`\u2192 ${method} ${fullUrl}`);
6236
+ request.kbLogger = logger;
6237
+ logger.debug("HTTP request started");
6118
6238
  });
6119
6239
  }
6120
6240
 
@@ -6429,41 +6549,40 @@ async function createServer(config, repoRoot, registry) {
6429
6549
  }
6430
6550
  var registryInstance = null;
6431
6551
  var metricsCollector2 = null;
6432
- async function findMonorepoRoot(startDir) {
6433
- let dir = path.resolve(startDir);
6434
- while (true) {
6435
- const hasWorkspace = await promises.access(path.join(dir, "pnpm-workspace.yaml")).then(() => true).catch(() => false);
6436
- if (hasWorkspace) {
6437
- return dir;
6438
- }
6439
- const parent = path.dirname(dir);
6440
- if (parent === dir) {
6441
- break;
6442
- }
6443
- dir = parent;
6444
- }
6445
- return findRepoRoot(startDir);
6446
- }
6447
6552
  async function bootstrap(cwd = process.cwd()) {
6448
- const repoRoot = await findMonorepoRoot(cwd);
6449
- loadEnvFromRoot(repoRoot);
6450
- const { config, diagnostics } = await loadRestApiConfig(cwd);
6451
- await createServiceBootstrap({ appId: "rest-api", repoRoot, assemblyHook: makeAssemblyHook() });
6553
+ await runService({
6554
+ appId: "rest-api",
6555
+ serviceId: "rest",
6556
+ startDir: cwd,
6557
+ defaultPort: 5050,
6558
+ portEnvVar: "REST_API_PORT",
6559
+ defaultHost: "0.0.0.0",
6560
+ hostEnvVar: "REST_API_HOST",
6561
+ platform: {
6562
+ assemblyHook: makeAssemblyHook()
6563
+ },
6564
+ setup: startRestApi
6565
+ });
6566
+ }
6567
+ async function startRestApi({
6568
+ platform: platform16,
6569
+ projectRoot: repoRoot,
6570
+ platformRoot,
6571
+ logger: serviceLogger
6572
+ }) {
6573
+ const { config, diagnostics } = await loadRestApiConfig(repoRoot);
6452
6574
  const startupRequestId = `rest-startup-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
6453
6575
  const startupTraceId = randomUUID();
6454
6576
  const startupSpanId = randomUUID();
6455
- const bootstrapLogger = platform.logger.child({
6456
- layer: "rest",
6457
- service: "bootstrap",
6577
+ const bootstrapLogger = serviceLogger.forComponent("rest-bootstrap").forOperation("rest.bootstrap", {
6458
6578
  requestId: startupRequestId,
6459
- reqId: startupRequestId,
6460
6579
  traceId: startupTraceId,
6461
- spanId: startupSpanId,
6462
- invocationId: startupSpanId,
6463
- executionId: startupSpanId
6464
- });
6580
+ spanId: startupSpanId
6581
+ }).with({ invocationId: startupSpanId, executionId: startupSpanId });
6465
6582
  if (diagnostics.length > 0) {
6466
- bootstrapLogger.warn("Configuration diagnostics", { diagnosticsCount: diagnostics.length });
6583
+ bootstrapLogger.warn("Configuration diagnostics", {
6584
+ diagnosticsCount: diagnostics.length
6585
+ });
6467
6586
  for (const diagnostic of diagnostics) {
6468
6587
  bootstrapLogger.warn("Configuration diagnostic", {
6469
6588
  level: diagnostic.level,
@@ -6471,22 +6590,28 @@ async function bootstrap(cwd = process.cwd()) {
6471
6590
  });
6472
6591
  }
6473
6592
  }
6474
- bootstrapLogger.info("Resolved repo root", { cwd, repoRoot });
6593
+ bootstrapLogger.info("Resolved service roots", {
6594
+ projectRoot: repoRoot,
6595
+ platformRoot
6596
+ });
6475
6597
  bootstrapLogger.info("Platform adapters initialized");
6476
6598
  bootstrapLogger.info("Initializing entity registry");
6477
6599
  const isDevelopment = process.env.NODE_ENV !== "production";
6478
6600
  const snapshotTTL = isDevelopment ? 10 * 60 * 1e3 : 60 * 60 * 1e3;
6479
- const platformRoot = getPlatformRoot();
6480
6601
  const registryInitStart = performance.now();
6481
6602
  const registry = await createRegistry({
6482
6603
  root: repoRoot,
6483
6604
  platformRoot: platformRoot !== repoRoot ? platformRoot : void 0,
6484
6605
  cache: {
6485
6606
  ttlMs: snapshotTTL,
6486
- adapter: platform.cache
6607
+ adapter: platform16.cache
6487
6608
  }
6488
6609
  });
6489
- restDomainOperationMetrics.recordOperation("registry.init", performance.now() - registryInitStart, "ok");
6610
+ restDomainOperationMetrics.recordOperation(
6611
+ "registry.init",
6612
+ performance.now() - registryInitStart,
6613
+ "ok"
6614
+ );
6490
6615
  const plugins = registry.listPlugins();
6491
6616
  bootstrapLogger.info("Entity registry initialized", {
6492
6617
  pluginsFound: plugins.length,
@@ -6503,9 +6628,12 @@ async function bootstrap(cwd = process.cwd()) {
6503
6628
  });
6504
6629
  const server = await createServer(config, repoRoot, registry);
6505
6630
  bootstrapLogger.info("Starting system metrics collector");
6506
- metricsCollector2 = new SystemMetricsCollector("rest", () => metricsCollector.getActiveRequests());
6631
+ metricsCollector2 = new SystemMetricsCollector(
6632
+ "rest",
6633
+ () => metricsCollector.getActiveRequests()
6634
+ );
6507
6635
  await metricsCollector2.start(1e4, 6e4);
6508
- const restTransport = platform.getAdapter("serviceTransport");
6636
+ const restTransport = platform16.getAdapter("serviceTransport");
6509
6637
  const restAddr = restTransport?.listenAddress?.("rest");
6510
6638
  const listenPort = restAddr && "port" in restAddr ? restAddr.port : config.port;
6511
6639
  const restAddrHost = restAddr && "host" in restAddr ? restAddr.host : void 0;
@@ -6522,20 +6650,15 @@ async function bootstrap(cwd = process.cwd()) {
6522
6650
  await registryInstance.dispose();
6523
6651
  registryInstance = null;
6524
6652
  }
6525
- await platform.shutdown();
6526
- bootstrapLogger.info("Platform shutdown complete");
6527
6653
  await server.close();
6528
6654
  bootstrapLogger.info("Server closed");
6529
- process.exit(0);
6530
6655
  };
6531
- process.on("SIGTERM", () => shutdown("SIGTERM"));
6532
- process.on("SIGINT", () => shutdown("SIGINT"));
6656
+ return () => shutdown("service-runner");
6533
6657
  }
6534
6658
 
6535
6659
  // src/index.ts
6536
- bootstrap(process.cwd()).catch((error) => {
6537
- console.error("Failed to start server:", error);
6538
- process.exit(1);
6660
+ bootstrap(process.cwd()).catch(() => {
6661
+ process.exitCode = 1;
6539
6662
  });
6540
6663
  //# sourceMappingURL=index.js.map
6541
6664
  //# sourceMappingURL=index.js.map