next-observer 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -2
  3. package/dist/agents/cards.d.ts +14 -0
  4. package/dist/agents/cards.js +85 -0
  5. package/dist/agents/cards.js.map +1 -0
  6. package/dist/agents/chat.d.ts +21 -0
  7. package/dist/agents/chat.js +83 -0
  8. package/dist/agents/chat.js.map +1 -0
  9. package/dist/agents/index.d.ts +6 -0
  10. package/dist/agents/index.js +8 -0
  11. package/dist/agents/index.js.map +1 -0
  12. package/dist/agents/investigator.d.ts +50 -0
  13. package/dist/agents/investigator.js +123 -0
  14. package/dist/agents/investigator.js.map +1 -0
  15. package/dist/agents/mock-llm.d.ts +7 -0
  16. package/dist/agents/mock-llm.js +46 -0
  17. package/dist/agents/mock-llm.js.map +1 -0
  18. package/dist/agents/model.d.ts +5 -0
  19. package/dist/agents/model.js +20 -0
  20. package/dist/agents/model.js.map +1 -0
  21. package/dist/agents/specialists.d.ts +20 -0
  22. package/dist/agents/specialists.js +74 -0
  23. package/dist/agents/specialists.js.map +1 -0
  24. package/dist/bin.d.ts +2 -0
  25. package/dist/bin.js +20 -0
  26. package/dist/bin.js.map +1 -0
  27. package/dist/cli.d.ts +29 -0
  28. package/dist/cli.js +231 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/collector/chat.d.ts +93 -0
  31. package/dist/collector/chat.js +2 -0
  32. package/dist/collector/chat.js.map +1 -0
  33. package/dist/collector/decode.d.ts +52 -0
  34. package/dist/collector/decode.js +59 -0
  35. package/dist/collector/decode.js.map +1 -0
  36. package/dist/collector/framework.d.ts +7 -0
  37. package/dist/collector/framework.js +9 -0
  38. package/dist/collector/framework.js.map +1 -0
  39. package/dist/collector/index.d.ts +6 -0
  40. package/dist/collector/index.js +5 -0
  41. package/dist/collector/index.js.map +1 -0
  42. package/dist/collector/memory-storage.d.ts +21 -0
  43. package/dist/collector/memory-storage.js +185 -0
  44. package/dist/collector/memory-storage.js.map +1 -0
  45. package/dist/collector/server.d.ts +31 -0
  46. package/dist/collector/server.js +267 -0
  47. package/dist/collector/server.js.map +1 -0
  48. package/dist/collector/static.d.ts +3 -0
  49. package/dist/collector/static.js +43 -0
  50. package/dist/collector/static.js.map +1 -0
  51. package/dist/collector/types.d.ts +116 -0
  52. package/dist/collector/types.js +2 -0
  53. package/dist/collector/types.js.map +1 -0
  54. package/dist/debug/demo.d.ts +4 -0
  55. package/dist/debug/demo.js +90 -0
  56. package/dist/debug/demo.js.map +1 -0
  57. package/dist/debug/detector.d.ts +61 -0
  58. package/dist/debug/detector.js +116 -0
  59. package/dist/debug/detector.js.map +1 -0
  60. package/dist/debug/index.d.ts +3 -0
  61. package/dist/debug/index.js +5 -0
  62. package/dist/debug/index.js.map +1 -0
  63. package/dist/debug/queries.d.ts +207 -0
  64. package/dist/debug/queries.js +242 -0
  65. package/dist/debug/queries.js.map +1 -0
  66. package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  67. package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  68. package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  69. package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  70. package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  71. package/dist/ui/assets/index-Y3VIawX_.css +2 -0
  72. package/dist/ui/assets/index-vwPciq9g.js +11 -0
  73. package/dist/ui/index.html +13 -0
  74. package/package.json +72 -4
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Next.js internal steps of a request ("executing api route (app) …", "render route (app) …", "start response"): they
3
+ * carry `next.span_type` but are not the request's server span. They repeat the endpoint's and user code's numbers, so
4
+ * agents' aggregates skip them — otherwise one slow call shows up as three problems. Traces keep them for structure.
5
+ */
6
+ export function isFrameworkSpan(span) {
7
+ return span.attributes?.['next.span_type'] !== undefined && span.kind !== 'server';
8
+ }
9
+ //# sourceMappingURL=framework.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework.js","sourceRoot":"","sources":["../../src/collector/framework.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiD;IAC/E,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,gBAAgB,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAA;AACpF,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { startCollector, type Collector, type CollectorOptions } from './server.js';
2
+ export { MemoryStorage, type MemoryStorageOptions } from './memory-storage.js';
3
+ export { decodeOtlpJson, type OtlpTraceRequest } from './decode.js';
4
+ export type * from './types.js';
5
+ export { isFrameworkSpan } from './framework.js';
6
+ export type { ChatEvent, ChatHandler, ChatRequest, EvidenceCard, ProactiveEvent, VersionStats } from './chat.js';
@@ -0,0 +1,5 @@
1
+ export { startCollector } from './server.js';
2
+ export { MemoryStorage } from './memory-storage.js';
3
+ export { decodeOtlpJson } from './decode.js';
4
+ export { isFrameworkSpan } from './framework.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/collector/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAyC,MAAM,aAAa,CAAA;AACnF,OAAO,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAyB,MAAM,aAAa,CAAA;AAEnE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { NormalizedSpan, OperationFilter, OperationStats, ServiceInfo, SpanFilter, StorageAdapter, TraceFilter, TraceSummary } from './types.js';
2
+ export interface MemoryStorageOptions {
3
+ /** Default 100_000. */
4
+ maxSpans?: number;
5
+ }
6
+ export declare function percentile(sorted: number[], p: number): number;
7
+ export declare class MemoryStorage implements StorageAdapter {
8
+ private readonly spans;
9
+ private readonly byTrace;
10
+ private readonly firstRequests;
11
+ private readonly maxSpans;
12
+ constructor(options?: MemoryStorageOptions);
13
+ insertSpans(spans: NormalizedSpan[]): Promise<void>;
14
+ queryTraces(filter?: TraceFilter): Promise<TraceSummary[]>;
15
+ getTrace(traceId: string): Promise<NormalizedSpan[]>;
16
+ getOperationStats(filter?: OperationFilter): Promise<OperationStats[]>;
17
+ querySpans(filter?: SpanFilter): Promise<NormalizedSpan[]>;
18
+ isColdStart(span: NormalizedSpan): boolean;
19
+ getServices(): Promise<ServiceInfo[]>;
20
+ count(): Promise<number>;
21
+ }
@@ -0,0 +1,185 @@
1
+ import { isFrameworkSpan } from './framework.js';
2
+ // Nearest-rank percentile over an ascending array.
3
+ export function percentile(sorted, p) {
4
+ if (sorted.length === 0)
5
+ return 0;
6
+ return sorted[Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1)];
7
+ }
8
+ const round = (n) => Math.round(n * 1000) / 1000;
9
+ // Case-insensitive: agents and people type "payment", spans say "chargePayment".
10
+ const nameMatches = (name, query) => name.toLowerCase().includes(query.toLowerCase());
11
+ function summarize(traceId, spans) {
12
+ const ids = new Set(spans.map((s) => s.spanId));
13
+ // Root = span whose parent isn't in this trace (true root, or the earliest orphan while spans still arrive).
14
+ const roots = spans.filter((s) => !s.parentSpanId || !ids.has(s.parentSpanId));
15
+ const root = roots.reduce((a, b) => (b.startTimeMs < a.startTimeMs ? b : a), roots[0] ?? spans[0]);
16
+ const start = Math.min(...spans.map((s) => s.startTimeMs));
17
+ const end = Math.max(...spans.map((s) => s.startTimeMs + s.durationMs));
18
+ return {
19
+ traceId,
20
+ rootName: root.name,
21
+ rootService: root.service,
22
+ services: [...new Set(spans.map((s) => s.service))].sort(),
23
+ startTimeMs: start,
24
+ durationMs: round(end - start),
25
+ spanCount: spans.length,
26
+ errorCount: spans.filter((s) => s.status === 'error').length,
27
+ };
28
+ }
29
+ function firstRequestKey(span) {
30
+ const instance = span.resource['service.instance.id'];
31
+ if (span.kind !== 'server' || typeof instance !== 'string')
32
+ return undefined;
33
+ return `${span.service}\u0000${instance}\u0000${span.name}`;
34
+ }
35
+ export class MemoryStorage {
36
+ spans = [];
37
+ byTrace = new Map();
38
+ // Earliest server span per (service, instance, route). Kept after eviction so a later request never becomes "cold".
39
+ firstRequests = new Map();
40
+ maxSpans;
41
+ constructor(options = {}) {
42
+ this.maxSpans = options.maxSpans ?? 100_000;
43
+ }
44
+ async insertSpans(spans) {
45
+ for (const span of spans) {
46
+ this.spans.push(span);
47
+ const key = firstRequestKey(span);
48
+ if (key) {
49
+ const first = this.firstRequests.get(key);
50
+ // Batches arrive out of order: an earlier request replaces the one seen first.
51
+ if (!first || span.startTimeMs < first.startTimeMs)
52
+ this.firstRequests.set(key, span);
53
+ }
54
+ const trace = this.byTrace.get(span.traceId);
55
+ if (trace)
56
+ trace.push(span);
57
+ else
58
+ this.byTrace.set(span.traceId, [span]);
59
+ }
60
+ while (this.spans.length > this.maxSpans) {
61
+ const evicted = this.spans.shift();
62
+ const trace = this.byTrace.get(evicted.traceId);
63
+ trace.splice(trace.indexOf(evicted), 1);
64
+ if (trace.length === 0)
65
+ this.byTrace.delete(evicted.traceId);
66
+ }
67
+ }
68
+ async queryTraces(filter = {}) {
69
+ const result = [];
70
+ for (const [traceId, spans] of this.byTrace) {
71
+ if (filter.service && !spans.some((s) => s.service === filter.service))
72
+ continue;
73
+ if (filter.operation && !spans.some((s) => nameMatches(s.name, filter.operation)))
74
+ continue;
75
+ const summary = summarize(traceId, spans);
76
+ if (filter.hasError !== undefined && summary.errorCount > 0 !== filter.hasError)
77
+ continue;
78
+ if (filter.minDurationMs !== undefined && summary.durationMs < filter.minDurationMs)
79
+ continue;
80
+ if (filter.fromMs !== undefined && summary.startTimeMs < filter.fromMs)
81
+ continue;
82
+ if (filter.toMs !== undefined && summary.startTimeMs > filter.toMs)
83
+ continue;
84
+ result.push(summary);
85
+ }
86
+ return result.sort((a, b) => b.startTimeMs - a.startTimeMs).slice(0, filter.limit ?? 50);
87
+ }
88
+ // Waterfall order: by start time; on equal start a parent comes before its children (depth tie-break).
89
+ async getTrace(traceId) {
90
+ const spans = this.byTrace.get(traceId) ?? [];
91
+ const byId = new Map(spans.map((s) => [s.spanId, s]));
92
+ const depth = (s) => {
93
+ let d = 0;
94
+ for (let p = s.parentSpanId && byId.get(s.parentSpanId); p && d < spans.length; p = p.parentSpanId && byId.get(p.parentSpanId))
95
+ d++;
96
+ return d;
97
+ };
98
+ return [...spans].sort((a, b) => a.startTimeMs - b.startTimeMs || depth(a) - depth(b));
99
+ }
100
+ async getOperationStats(filter = {}) {
101
+ const groups = new Map();
102
+ for (const s of this.spans) {
103
+ if (filter.service && s.service !== filter.service)
104
+ continue;
105
+ if (filter.operation && !nameMatches(s.name, filter.operation))
106
+ continue;
107
+ if (filter.hideFramework && isFrameworkSpan(s))
108
+ continue;
109
+ if (filter.fromMs !== undefined && s.startTimeMs < filter.fromMs)
110
+ continue;
111
+ if (filter.toMs !== undefined && s.startTimeMs > filter.toMs)
112
+ continue;
113
+ const key = filter.byVersion ? `${s.service}\u0000${s.serviceVersion}\u0000${s.name}` : `${s.service}\u0000${s.name}`;
114
+ const group = groups.get(key);
115
+ if (group)
116
+ group.push(s);
117
+ else
118
+ groups.set(key, [s]);
119
+ }
120
+ const stats = [];
121
+ for (const group of groups.values()) {
122
+ const warm = filter.hideColdStarts ? group.filter((s) => !this.isColdStart(s)) : group;
123
+ const spans = warm.length > 0 ? warm : group;
124
+ const coldStarts = group.length - warm.length;
125
+ const durations = spans.map((s) => s.durationMs).sort((a, b) => a - b);
126
+ const errorCount = spans.filter((s) => s.status === 'error').length;
127
+ stats.push({
128
+ service: spans[0].service,
129
+ ...(filter.byVersion ? { serviceVersion: spans[0].serviceVersion } : {}),
130
+ operation: spans[0].name,
131
+ count: spans.length,
132
+ errorCount,
133
+ errorRate: round(errorCount / spans.length),
134
+ avgMs: round(durations.reduce((a, b) => a + b, 0) / durations.length),
135
+ p50Ms: round(percentile(durations, 50)),
136
+ p95Ms: round(percentile(durations, 95)),
137
+ p99Ms: round(percentile(durations, 99)),
138
+ maxMs: round(durations[durations.length - 1]),
139
+ ...(coldStarts > 0 ? { coldStarts, ...(warm.length === 0 ? { onlyColdStarts: true } : {}) } : {}),
140
+ });
141
+ }
142
+ return stats.sort((a, b) => b.p95Ms - a.p95Ms);
143
+ }
144
+ async querySpans(filter = {}) {
145
+ const result = [];
146
+ const limit = filter.limit ?? 100;
147
+ for (let i = this.spans.length - 1; i >= 0 && result.length < limit; i--) {
148
+ const s = this.spans[i];
149
+ if (filter.service && s.service !== filter.service)
150
+ continue;
151
+ if (filter.operation && !nameMatches(s.name, filter.operation))
152
+ continue;
153
+ if (filter.hideFramework && isFrameworkSpan(s))
154
+ continue;
155
+ if (filter.status && s.status !== filter.status)
156
+ continue;
157
+ if (filter.fromMs !== undefined && s.startTimeMs < filter.fromMs)
158
+ continue;
159
+ if (filter.toMs !== undefined && s.startTimeMs > filter.toMs)
160
+ continue;
161
+ result.push(s);
162
+ }
163
+ return result;
164
+ }
165
+ isColdStart(span) {
166
+ const key = firstRequestKey(span);
167
+ return key !== undefined && this.firstRequests.get(key) === span;
168
+ }
169
+ async getServices() {
170
+ const services = new Map();
171
+ for (const s of this.spans) {
172
+ const info = services.get(s.service) ?? { name: s.service, versions: [], spanCount: 0, lastSeenMs: 0 };
173
+ info.spanCount++;
174
+ info.lastSeenMs = Math.max(info.lastSeenMs, s.startTimeMs);
175
+ if (s.serviceVersion && !info.versions.includes(s.serviceVersion))
176
+ info.versions.push(s.serviceVersion);
177
+ services.set(s.service, info);
178
+ }
179
+ return [...services.values()].sort((a, b) => a.name.localeCompare(b.name));
180
+ }
181
+ async count() {
182
+ return this.spans.length;
183
+ }
184
+ }
185
+ //# sourceMappingURL=memory-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-storage.js","sourceRoot":"","sources":["../../src/collector/memory-storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type AnomalyDetector } from '../debug/detector.js';
2
+ import type { ChatHandler } from './chat.js';
3
+ import type { StorageAdapter } from './types.js';
4
+ export interface CollectorOptions {
5
+ /** Default 4318 (standard OTLP/HTTP port). 0 = random free port. */
6
+ port?: number;
7
+ host?: string;
8
+ storage?: StorageAdapter;
9
+ /** When set, ingest requires a matching `x-api-key` header. */
10
+ apiKey?: string;
11
+ /** Default 10 MB. */
12
+ maxBodyBytes?: number;
13
+ /** Built UI to serve at /. Default: the package's dist/ui. `false` disables it. */
14
+ uiDir?: string | false;
15
+ /** Agents answering /api/chat (the CLI always provides them); absent → chat disabled. */
16
+ chat?: {
17
+ mode: 'mock' | 'real';
18
+ handle: ChatHandler;
19
+ };
20
+ /** Watches ingested spans; on an anomaly pushes it to /api/chat/events and, with `chat`, investigates on its own. */
21
+ detector?: Pick<AnomalyDetector, 'observe' | 'check'>;
22
+ /** How often the detector window is checked. Default 5 s. */
23
+ detectorIntervalMs?: number;
24
+ }
25
+ export interface Collector {
26
+ url: string;
27
+ port: number;
28
+ storage: StorageAdapter;
29
+ close(): Promise<void>;
30
+ }
31
+ export declare function startCollector(options?: CollectorOptions): Promise<Collector>;
@@ -0,0 +1,267 @@
1
+ // OTLP/HTTP collector + query API. Node built-ins only.
2
+ // POST /v1/traces OTLP/JSON ingest (protobuf → 415 until Sprint 2 phase B)
3
+ // GET /health
4
+ // GET /api/services
5
+ // GET /api/traces?service&operation&minDurationMs&hasError&fromMs&toMs&limit
6
+ // GET /api/traces/:traceId
7
+ // GET /api/operations?service&operation&fromMs&toMs
8
+ // GET /api/chat { enabled, mode }
9
+ // POST /api/chat { question, sessionId? } → NDJSON stream of ChatEvent (status, step…, card…, report | error)
10
+ // GET /api/chat/events SSE: proactive turns (detector anomaly → investigation), recent ones replayed on connect
11
+ // GET /* the UI (dist/ui) with SPA fallback
12
+ import { createServer } from 'node:http';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { questionFor } from '../debug/detector.js';
15
+ import { decodeOtlpJson } from './decode.js';
16
+ import { MemoryStorage } from './memory-storage.js';
17
+ import { serveUi } from './static.js';
18
+ const REPLAY_EVENTS = 200;
19
+ const CHAT_DISABLED = 'chat is disabled: this collector was started without agents';
20
+ const MAX_QUESTION = 2000;
21
+ const DEFAULT_UI_DIR = fileURLToPath(new URL('../ui/', import.meta.url));
22
+ class HttpError extends Error {
23
+ status;
24
+ constructor(status, message) {
25
+ super(message);
26
+ this.status = status;
27
+ }
28
+ }
29
+ const CORS = {
30
+ 'access-control-allow-origin': '*',
31
+ 'access-control-allow-methods': 'GET, POST, OPTIONS',
32
+ 'access-control-allow-headers': 'content-type, x-api-key',
33
+ };
34
+ function send(res, status, body) {
35
+ res.writeHead(status, { ...CORS, 'content-type': 'application/json' }).end(JSON.stringify(body));
36
+ }
37
+ function readBody(req, limit) {
38
+ return new Promise((resolve, reject) => {
39
+ let size = 0;
40
+ let tooLarge = false;
41
+ const chunks = [];
42
+ // Past the limit we keep draining without buffering, so the 413 response can still be delivered.
43
+ req.on('data', (chunk) => {
44
+ size += chunk.length;
45
+ if (size > limit)
46
+ tooLarge = true;
47
+ else
48
+ chunks.push(chunk);
49
+ });
50
+ req.on('end', () => {
51
+ if (tooLarge)
52
+ reject(new HttpError(413, `body exceeds ${limit} bytes`));
53
+ else
54
+ resolve(Buffer.concat(chunks).toString('utf8'));
55
+ });
56
+ req.on('error', reject);
57
+ });
58
+ }
59
+ function numberParam(params, name) {
60
+ const raw = params.get(name);
61
+ if (raw === null || raw === '')
62
+ return undefined;
63
+ const n = Number(raw);
64
+ if (Number.isNaN(n))
65
+ throw new HttpError(400, `${name} must be a number`);
66
+ return n;
67
+ }
68
+ function booleanParam(params, name) {
69
+ const raw = params.get(name);
70
+ if (raw === null || raw === '')
71
+ return undefined;
72
+ if (raw !== 'true' && raw !== 'false')
73
+ throw new HttpError(400, `${name} must be true or false`);
74
+ return raw === 'true';
75
+ }
76
+ const stringParam = (params, name) => params.get(name) || undefined;
77
+ export async function startCollector(options = {}) {
78
+ const storage = options.storage ?? new MemoryStorage();
79
+ const maxBodyBytes = options.maxBodyBytes ?? 10 * 1024 * 1024;
80
+ async function ingest(req, res) {
81
+ if (options.apiKey && req.headers['x-api-key'] !== options.apiKey)
82
+ throw new HttpError(401, 'invalid x-api-key');
83
+ const type = req.headers['content-type'] ?? '';
84
+ if (!type.includes('application/json')) {
85
+ throw new HttpError(415, `unsupported content-type "${type}": only OTLP/JSON is accepted, configure the exporter for http/json`);
86
+ }
87
+ let payload;
88
+ try {
89
+ payload = JSON.parse(await readBody(req, maxBodyBytes));
90
+ }
91
+ catch (error) {
92
+ if (error instanceof HttpError)
93
+ throw error;
94
+ throw new HttpError(400, 'body is not valid JSON');
95
+ }
96
+ const spans = decodeOtlpJson(payload);
97
+ await storage.insertSpans(spans);
98
+ // A restart of next dev compiles each route on its first request — not an anomaly to investigate.
99
+ options.detector?.observe(spans.filter((s) => !storage.isColdStart(s)));
100
+ send(res, 200, {});
101
+ }
102
+ // --- proactive: detector → SSE broadcast → investigation, one at a time ---
103
+ const subscribers = new Set();
104
+ const recent = [];
105
+ let seq = 0;
106
+ const broadcast = (message) => {
107
+ const envelope = { seq: ++seq, ...message };
108
+ recent.push(envelope);
109
+ if (recent.length > REPLAY_EVENTS)
110
+ recent.shift();
111
+ const line = `data: ${JSON.stringify(envelope)}\n\n`;
112
+ // A queued investigation can outlive close(): never write to an ended response.
113
+ for (const res of subscribers)
114
+ if (!res.writableEnded)
115
+ res.write(line);
116
+ };
117
+ let investigations = Promise.resolve();
118
+ const onAnomalies = () => {
119
+ for (const anomaly of options.detector?.check() ?? []) {
120
+ const turnId = anomaly.id;
121
+ broadcast({ turnId, event: { type: 'anomaly', anomaly } });
122
+ if (!options.chat)
123
+ continue;
124
+ const chat = options.chat;
125
+ // Queued: two investigations at once would double the model load and interleave in the UI.
126
+ investigations = investigations.then(() => chat.handle({ question: questionFor(anomaly) }, (event) => broadcast({ turnId, event })).catch((error) => {
127
+ broadcast({ turnId, event: { type: 'error', message: error instanceof Error ? error.message : String(error) } });
128
+ }));
129
+ }
130
+ };
131
+ function events(req, res) {
132
+ res.writeHead(200, { ...CORS, 'content-type': 'text/event-stream; charset=utf-8', 'cache-control': 'no-cache', connection: 'keep-alive' });
133
+ res.write(': connected\n\n');
134
+ for (const envelope of recent)
135
+ res.write(`data: ${JSON.stringify(envelope)}\n\n`);
136
+ subscribers.add(res);
137
+ req.on('close', () => subscribers.delete(res));
138
+ }
139
+ async function chat(req, res) {
140
+ if (!options.chat)
141
+ throw new HttpError(503, CHAT_DISABLED);
142
+ let body;
143
+ try {
144
+ body = JSON.parse(await readBody(req, 64 * 1024));
145
+ }
146
+ catch (error) {
147
+ if (error instanceof HttpError)
148
+ throw error;
149
+ throw new HttpError(400, 'body is not valid JSON');
150
+ }
151
+ const question = typeof body?.question === 'string' ? body.question.trim() : '';
152
+ if (!question)
153
+ throw new HttpError(400, 'question is required');
154
+ if (question.length > MAX_QUESTION)
155
+ throw new HttpError(400, `question is longer than ${MAX_QUESTION} characters`);
156
+ const sessionId = body.sessionId;
157
+ if (sessionId !== undefined && (typeof sessionId !== 'string' || !/^[\w-]{1,100}$/.test(sessionId))) {
158
+ throw new HttpError(400, 'sessionId must be 1–100 characters of letters, digits, _ or -');
159
+ }
160
+ // One JSON event per line, flushed as the agents work — the UI shows each step live.
161
+ res.writeHead(200, { ...CORS, 'content-type': 'application/x-ndjson; charset=utf-8', 'cache-control': 'no-cache' });
162
+ const emit = (event) => {
163
+ if (!res.writableEnded)
164
+ res.write(JSON.stringify(event) + '\n');
165
+ };
166
+ try {
167
+ await options.chat.handle({ question, ...(sessionId ? { sessionId } : {}) }, emit);
168
+ }
169
+ catch (error) {
170
+ emit({ type: 'error', message: error instanceof Error ? error.message : String(error) });
171
+ }
172
+ res.end();
173
+ }
174
+ async function route(req, res) {
175
+ const url = new URL(req.url ?? '/', 'http://collector');
176
+ const q = url.searchParams;
177
+ if (req.method === 'OPTIONS')
178
+ return void res.writeHead(204, CORS).end();
179
+ if (req.method === 'POST' && url.pathname === '/v1/traces')
180
+ return ingest(req, res);
181
+ if (req.method === 'POST' && url.pathname === '/api/chat')
182
+ return chat(req, res);
183
+ if (req.method !== 'GET')
184
+ throw new HttpError(404, 'not found');
185
+ if (url.pathname === '/api/chat/events')
186
+ return events(req, res);
187
+ if (url.pathname === '/api/chat') {
188
+ return send(res, 200, options.chat ? { enabled: true, mode: options.chat.mode } : { enabled: false, reason: CHAT_DISABLED });
189
+ }
190
+ if (url.pathname === '/health')
191
+ return send(res, 200, { status: 'ok', spans: await storage.count() });
192
+ if (url.pathname === '/api/services')
193
+ return send(res, 200, await storage.getServices());
194
+ if (url.pathname === '/api/traces') {
195
+ return send(res, 200, await storage.queryTraces({
196
+ service: stringParam(q, 'service'),
197
+ operation: stringParam(q, 'operation'),
198
+ minDurationMs: numberParam(q, 'minDurationMs'),
199
+ hasError: booleanParam(q, 'hasError'),
200
+ fromMs: numberParam(q, 'fromMs'),
201
+ toMs: numberParam(q, 'toMs'),
202
+ limit: numberParam(q, 'limit'),
203
+ }));
204
+ }
205
+ const traceMatch = url.pathname.match(/^\/api\/traces\/([0-9a-f]{32})$/);
206
+ if (traceMatch) {
207
+ const spans = await storage.getTrace(traceMatch[1]);
208
+ if (spans.length === 0)
209
+ throw new HttpError(404, 'trace not found');
210
+ return send(res, 200, { traceId: traceMatch[1], spans });
211
+ }
212
+ if (url.pathname === '/api/operations') {
213
+ return send(res, 200, await storage.getOperationStats({
214
+ service: stringParam(q, 'service'),
215
+ operation: stringParam(q, 'operation'),
216
+ fromMs: numberParam(q, 'fromMs'),
217
+ toMs: numberParam(q, 'toMs'),
218
+ }));
219
+ }
220
+ if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/v1/'))
221
+ throw new HttpError(404, 'not found');
222
+ const uiDir = options.uiDir ?? DEFAULT_UI_DIR;
223
+ if (uiDir && (await serveUi(uiDir, url.pathname, res)))
224
+ return;
225
+ throw new HttpError(404, 'not found');
226
+ }
227
+ const server = createServer((req, res) => {
228
+ route(req, res).catch((error) => {
229
+ if (res.headersSent)
230
+ return;
231
+ if (error instanceof HttpError)
232
+ return send(res, error.status, { error: error.message });
233
+ console.error('[next-observer collector]', error);
234
+ send(res, 500, { error: 'internal error' });
235
+ });
236
+ });
237
+ await new Promise((resolve, reject) => {
238
+ server.once('error', reject);
239
+ server.listen(options.port ?? 4318, options.host ?? '127.0.0.1', resolve);
240
+ });
241
+ const detectorTimer = options.detector ? setInterval(onAnomalies, options.detectorIntervalMs ?? 5000) : undefined;
242
+ // A comment line every 15 s keeps proxies from closing idle SSE connections.
243
+ const heartbeat = setInterval(() => {
244
+ for (const res of subscribers)
245
+ res.write(': ping\n\n');
246
+ }, 15_000);
247
+ detectorTimer?.unref();
248
+ heartbeat.unref();
249
+ const { port } = server.address();
250
+ const host = options.host ?? '127.0.0.1';
251
+ return {
252
+ url: `http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${port}`,
253
+ port,
254
+ storage,
255
+ close: () => new Promise((resolve, reject) => {
256
+ clearInterval(detectorTimer);
257
+ clearInterval(heartbeat);
258
+ for (const res of subscribers)
259
+ res.end();
260
+ subscribers.clear();
261
+ server.close((error) => (error ? reject(error) : resolve()));
262
+ // Defensive: don't let any lingering client socket delay shutdown (a hang was seen once in e2e, cause not reproduced).
263
+ server.closeAllConnections();
264
+ }),
265
+ };
266
+ }
267
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/collector/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ /** Returns false when there is no UI build, so the caller can answer 404. */
3
+ export declare function serveUi(uiDir: string, pathname: string, res: ServerResponse): Promise<boolean>;
@@ -0,0 +1,43 @@
1
+ // Serves the built UI (dist/ui) with SPA fallback: unknown paths get index.html so deep links like /traces/<id> work.
2
+ import { readFile, stat } from 'node:fs/promises';
3
+ import { extname, join, resolve, sep } from 'node:path';
4
+ const CONTENT_TYPES = {
5
+ '.html': 'text/html; charset=utf-8',
6
+ '.js': 'text/javascript; charset=utf-8',
7
+ '.css': 'text/css; charset=utf-8',
8
+ '.json': 'application/json',
9
+ '.svg': 'image/svg+xml',
10
+ '.png': 'image/png',
11
+ '.ico': 'image/x-icon',
12
+ '.woff2': 'font/woff2',
13
+ '.map': 'application/json',
14
+ };
15
+ async function isFile(path) {
16
+ return stat(path).then((s) => s.isFile(), () => false);
17
+ }
18
+ /** Returns false when there is no UI build, so the caller can answer 404. */
19
+ export async function serveUi(uiDir, pathname, res) {
20
+ const root = resolve(uiDir);
21
+ const index = join(root, 'index.html');
22
+ if (!(await isFile(index)))
23
+ return false;
24
+ let decoded;
25
+ try {
26
+ decoded = decodeURIComponent(pathname);
27
+ }
28
+ catch {
29
+ decoded = '/';
30
+ }
31
+ const candidate = resolve(root, `.${decoded}`);
32
+ // Anything resolving outside the UI dir (../ tricks) falls back to index.html instead of being read.
33
+ const inside = candidate === root || candidate.startsWith(root + sep);
34
+ const file = inside && (await isFile(candidate)) ? candidate : index;
35
+ const hashedAsset = file.startsWith(join(root, 'assets') + sep);
36
+ res.writeHead(200, {
37
+ 'content-type': CONTENT_TYPES[extname(file)] ?? 'application/octet-stream',
38
+ 'cache-control': hashedAsset ? 'public, max-age=31536000, immutable' : 'no-cache',
39
+ });
40
+ res.end(await readFile(file));
41
+ return true;
42
+ }
43
+ //# sourceMappingURL=static.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/collector/static.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAGvD,MAAM,aAAa,GAA2B;IAC5C,OAAO,EAAE,0BAA0B;IACnC,KAAK,EAAE,gCAAgC;IACvC,MAAM,EAAE,yBAAyB;IACjC,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,cAAc;IACtB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,kBAAkB;CAC3B,CAAA;AAED,KAAK,UAAU,MAAM,CAAC,IAAY;IAChC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;AACxD,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAa,EAAE,QAAgB,EAAE,GAAmB;IAChF,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IACtC,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAExC,IAAI,OAAe,CAAA;IACnB,IAAI,CAAC;QACH,OAAO,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,GAAG,CAAA;IACf,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,CAAA;IAC9C,qGAAqG;IACrG,MAAM,MAAM,GAAG,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,CAAA;IACrE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;IAEpE,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,GAAG,CAAC,CAAA;IAC/D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,0BAA0B;QAC1E,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC,qCAAqC,CAAC,CAAC,CAAC,UAAU;KAClF,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;IAC7B,OAAO,IAAI,CAAA;AACb,CAAC"}