baselineos 0.2.0-beta.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.
- package/LICENSE +17 -0
- package/README.md +198 -0
- package/dist/__evals__/runner.d.ts +2 -0
- package/dist/__evals__/runner.js +14687 -0
- package/dist/__evals__/runner.js.map +1 -0
- package/dist/api/server.d.ts +21 -0
- package/dist/api/server.js +1007 -0
- package/dist/api/server.js.map +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +8427 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/core/agent-bus.d.ts +110 -0
- package/dist/core/agent-bus.js +242 -0
- package/dist/core/agent-bus.js.map +1 -0
- package/dist/core/cache.d.ts +66 -0
- package/dist/core/cache.js +160 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/config.d.ts +1002 -0
- package/dist/core/config.js +429 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/indexer.d.ts +152 -0
- package/dist/core/indexer.js +481 -0
- package/dist/core/indexer.js.map +1 -0
- package/dist/core/llm-tracer.d.ts +2 -0
- package/dist/core/llm-tracer.js +241 -0
- package/dist/core/llm-tracer.js.map +1 -0
- package/dist/core/memory.d.ts +86 -0
- package/dist/core/memory.js +346 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/opa-client.d.ts +51 -0
- package/dist/core/opa-client.js +157 -0
- package/dist/core/opa-client.js.map +1 -0
- package/dist/core/opa-policy-gate.d.ts +133 -0
- package/dist/core/opa-policy-gate.js +454 -0
- package/dist/core/opa-policy-gate.js.map +1 -0
- package/dist/core/orchestrator.d.ts +14 -0
- package/dist/core/orchestrator.js +1297 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/pii-detector.d.ts +82 -0
- package/dist/core/pii-detector.js +126 -0
- package/dist/core/pii-detector.js.map +1 -0
- package/dist/core/rag-engine.d.ts +121 -0
- package/dist/core/rag-engine.js +504 -0
- package/dist/core/rag-engine.js.map +1 -0
- package/dist/core/task-queue.d.ts +69 -0
- package/dist/core/task-queue.js +124 -0
- package/dist/core/task-queue.js.map +1 -0
- package/dist/core/telemetry.d.ts +56 -0
- package/dist/core/telemetry.js +94 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/types.d.ts +328 -0
- package/dist/core/types.js +24 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +12444 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-tracer-CIIujuO-.d.ts +493 -0
- package/dist/mcp/server.d.ts +2651 -0
- package/dist/mcp/server.js +676 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/orchestrator-DF89k_AK.d.ts +506 -0
- package/package.json +157 -0
- package/templates/README.md +7 -0
- package/templates/baseline.config.ts +207 -0
|
@@ -0,0 +1,1007 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
3
|
+
import { createServer } from 'http';
|
|
4
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
5
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
6
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
7
|
+
import { ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
8
|
+
import { trace, propagation, context } from '@opentelemetry/api';
|
|
9
|
+
|
|
10
|
+
// src/api/server.ts
|
|
11
|
+
var _sdk = null;
|
|
12
|
+
function initTelemetry() {
|
|
13
|
+
if (_sdk || process.env["BASELINE_OTEL_DISABLED"] === "1") return;
|
|
14
|
+
const endpoint = process.env["OTEL_EXPORTER_OTLP_ENDPOINT"] ?? "http://localhost:4318";
|
|
15
|
+
const exporter = new OTLPTraceExporter({
|
|
16
|
+
url: `${endpoint}/v1/traces`
|
|
17
|
+
});
|
|
18
|
+
_sdk = new NodeSDK({
|
|
19
|
+
resource: resourceFromAttributes({
|
|
20
|
+
[ATTR_SERVICE_NAME]: process.env["OTEL_SERVICE_NAME"] ?? "baselineos",
|
|
21
|
+
[ATTR_SERVICE_VERSION]: "0.2.0-beta.1"
|
|
22
|
+
}),
|
|
23
|
+
traceExporter: exporter
|
|
24
|
+
// W3C traceparent propagation is the default — no explicit config needed
|
|
25
|
+
});
|
|
26
|
+
_sdk.start();
|
|
27
|
+
process.on("SIGTERM", () => {
|
|
28
|
+
_sdk?.shutdown().catch(() => {
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
process.on("SIGINT", () => {
|
|
32
|
+
_sdk?.shutdown().catch(() => {
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
new Proxy({}, {
|
|
37
|
+
get(_target, prop) {
|
|
38
|
+
return trace.getTracer("baselineos", "0.2.0-beta.1")[prop];
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
function extractTraceContext() {
|
|
42
|
+
const carrier = {};
|
|
43
|
+
propagation.inject(context.active(), carrier);
|
|
44
|
+
return carrier["traceparent"];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/api/server.ts
|
|
48
|
+
var TASK_STATUSES = [
|
|
49
|
+
"pending",
|
|
50
|
+
"planning",
|
|
51
|
+
"executing",
|
|
52
|
+
"verifying",
|
|
53
|
+
"reviewing",
|
|
54
|
+
"correcting",
|
|
55
|
+
"completed",
|
|
56
|
+
"failed",
|
|
57
|
+
"blocked"
|
|
58
|
+
];
|
|
59
|
+
var CONTEXT_LEVELS = ["oneliner", "summary", "working", "full", "expanded"];
|
|
60
|
+
var MEMORY_SCOPES = ["working", "session", "long-term", "shared", "all"];
|
|
61
|
+
function parseTaskStatus(value) {
|
|
62
|
+
return typeof value === "string" && TASK_STATUSES.includes(value) ? value : void 0;
|
|
63
|
+
}
|
|
64
|
+
function parseContextLevel(value) {
|
|
65
|
+
if (typeof value === "string" && CONTEXT_LEVELS.includes(value)) {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
return "working";
|
|
69
|
+
}
|
|
70
|
+
function parseScope(value) {
|
|
71
|
+
if (typeof value === "string" && MEMORY_SCOPES.includes(value)) {
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
function parseJson(value) {
|
|
77
|
+
if (!value) return void 0;
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(value);
|
|
80
|
+
} catch {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function parsePolicy(value) {
|
|
85
|
+
if (!value || typeof value !== "object") return void 0;
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function parseBudget(value) {
|
|
89
|
+
if (!value || typeof value !== "object") return void 0;
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function parseSocketMessage(value) {
|
|
93
|
+
if (!value || typeof value !== "object") return null;
|
|
94
|
+
const message = value;
|
|
95
|
+
if (message.type === "ping") return { type: "ping" };
|
|
96
|
+
if (message.type === "subscribe") {
|
|
97
|
+
return {
|
|
98
|
+
type: "subscribe",
|
|
99
|
+
taskId: typeof message.taskId === "string" ? message.taskId : void 0
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
var APIServer = class {
|
|
105
|
+
app;
|
|
106
|
+
server;
|
|
107
|
+
wss;
|
|
108
|
+
baseline;
|
|
109
|
+
config;
|
|
110
|
+
clients;
|
|
111
|
+
unsubscribeEvents;
|
|
112
|
+
// ── Prometheus counters (in-process, reset on restart) ──────────────────
|
|
113
|
+
metricsCounters = {
|
|
114
|
+
requestsTotal: /* @__PURE__ */ new Map(),
|
|
115
|
+
// label: "method_path_status"
|
|
116
|
+
requestDurationMs: /* @__PURE__ */ new Map(),
|
|
117
|
+
// label: "method"
|
|
118
|
+
tasksTotal: /* @__PURE__ */ new Map(),
|
|
119
|
+
// label: task status
|
|
120
|
+
tokensByAgentRole: /* @__PURE__ */ new Map(),
|
|
121
|
+
// label: agent role (SIGNAL-013)
|
|
122
|
+
wsConnections: 0,
|
|
123
|
+
wsMessagesTotal: 0,
|
|
124
|
+
serverStartTime: Date.now(),
|
|
125
|
+
/** Running ratio of autonomous tasks that passed self-verification (SIGNAL-046) */
|
|
126
|
+
autonomousAccuracy: 1,
|
|
127
|
+
autonomousTotal: 0,
|
|
128
|
+
autonomousPassed: 0
|
|
129
|
+
};
|
|
130
|
+
/** Optional CostGuard for per-tenant cost metrics (SIGNAL-052) */
|
|
131
|
+
costGuard;
|
|
132
|
+
/** Optional TraceCurator for dataset size gauge (SIGNAL-049) */
|
|
133
|
+
traceCurator;
|
|
134
|
+
/** Optional ModelVersionRegistry for active model version gauge (SIGNAL-049) */
|
|
135
|
+
modelVersionRegistry;
|
|
136
|
+
/** Optional ProductionEvalPipeline for latest eval score gauge (SIGNAL-056) */
|
|
137
|
+
productionEvalPipeline;
|
|
138
|
+
/** Optional DriftDetector for model drift metrics (SIGNAL-071) */
|
|
139
|
+
driftDetector;
|
|
140
|
+
/** Optional OPAPolicyGate for SLO-6 latency metrics */
|
|
141
|
+
opaPolicyGate;
|
|
142
|
+
constructor(baseline, config) {
|
|
143
|
+
this.baseline = baseline;
|
|
144
|
+
this.config = config;
|
|
145
|
+
this.clients = /* @__PURE__ */ new Set();
|
|
146
|
+
this.unsubscribeEvents = [];
|
|
147
|
+
initTelemetry();
|
|
148
|
+
this.app = express();
|
|
149
|
+
this.server = createServer(this.app);
|
|
150
|
+
this.wss = new WebSocketServer({ server: this.server, path: "/api/stream" });
|
|
151
|
+
this.wss.on("error", (error) => {
|
|
152
|
+
const code = error?.code;
|
|
153
|
+
if (code !== "EADDRINUSE") {
|
|
154
|
+
console.error("[WS Error]", error);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
this.setupMiddleware();
|
|
158
|
+
this.setupRoutes();
|
|
159
|
+
this.setupWebSocket();
|
|
160
|
+
this.attachEventBridge();
|
|
161
|
+
}
|
|
162
|
+
// ─── Setup ─────────────────────────────────────────────────────────────────
|
|
163
|
+
setupMiddleware() {
|
|
164
|
+
this.app.use(express.json());
|
|
165
|
+
const allowedOrigins = this.config.allowedOrigins ?? ["http://localhost:3000", "http://localhost:3141", "http://localhost:5173", "tauri://localhost"];
|
|
166
|
+
this.app.use((req, res, next) => {
|
|
167
|
+
const origin = req.headers.origin;
|
|
168
|
+
if (origin && allowedOrigins.includes(origin)) {
|
|
169
|
+
res.header("Access-Control-Allow-Origin", origin);
|
|
170
|
+
}
|
|
171
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
172
|
+
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
173
|
+
if (req.method === "OPTIONS") {
|
|
174
|
+
return res.sendStatus(200);
|
|
175
|
+
}
|
|
176
|
+
next();
|
|
177
|
+
});
|
|
178
|
+
this.app.use((_req, res, next) => {
|
|
179
|
+
const traceparent = extractTraceContext();
|
|
180
|
+
if (traceparent) res.setHeader("traceparent", traceparent);
|
|
181
|
+
next();
|
|
182
|
+
});
|
|
183
|
+
this.app.use((req, res, next) => {
|
|
184
|
+
const start = Date.now();
|
|
185
|
+
res.on("finish", () => {
|
|
186
|
+
const duration = Date.now() - start;
|
|
187
|
+
console.log(`[API] ${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
|
|
188
|
+
if (req.path !== "/metrics") {
|
|
189
|
+
const reqKey = `${req.method}_${res.statusCode}`;
|
|
190
|
+
this.metricsCounters.requestsTotal.set(
|
|
191
|
+
reqKey,
|
|
192
|
+
(this.metricsCounters.requestsTotal.get(reqKey) ?? 0) + 1
|
|
193
|
+
);
|
|
194
|
+
const durKey = req.method;
|
|
195
|
+
const existing = this.metricsCounters.requestDurationMs.get(durKey) ?? [];
|
|
196
|
+
existing.push(duration);
|
|
197
|
+
if (existing.length > 1e3) existing.shift();
|
|
198
|
+
this.metricsCounters.requestDurationMs.set(durKey, existing);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
next();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
setupRoutes() {
|
|
205
|
+
this.app.get("/api/health", (req, res) => {
|
|
206
|
+
res.json({ status: "ok", version: "0.2.0-beta.1", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
207
|
+
});
|
|
208
|
+
this.app.get("/api/ready", (req, res) => {
|
|
209
|
+
const ready = !!this.baseline;
|
|
210
|
+
res.status(ready ? 200 : 503).json({
|
|
211
|
+
ready,
|
|
212
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
this.app.post("/api/tasks", this.createTask.bind(this));
|
|
216
|
+
this.app.get("/api/tasks", this.listTasks.bind(this));
|
|
217
|
+
this.app.get("/api/tasks/:id", this.getTask.bind(this));
|
|
218
|
+
this.app.delete("/api/tasks/:id", this.cancelTask.bind(this));
|
|
219
|
+
this.app.post("/api/tasks/:id/approve", this.approveTask.bind(this));
|
|
220
|
+
this.app.get("/api/tasks/:id/artifacts", this.getTaskArtifacts.bind(this));
|
|
221
|
+
this.app.get("/api/tasks/:id/audit", this.getTaskAudit.bind(this));
|
|
222
|
+
this.app.get("/api/tasks/:id/evidence", this.getTaskEvidence.bind(this));
|
|
223
|
+
this.app.get("/api/workflows", this.listWorkflows.bind(this));
|
|
224
|
+
this.app.post("/api/workflows/:id/run", this.runWorkflow.bind(this));
|
|
225
|
+
this.app.get("/api/checkpoints/:id", this.getCheckpoint.bind(this));
|
|
226
|
+
this.app.post("/api/checkpoints/:id/recover", this.recoverCheckpoint.bind(this));
|
|
227
|
+
this.app.get("/api/agents", this.listAgents.bind(this));
|
|
228
|
+
this.app.post("/api/search", this.search.bind(this));
|
|
229
|
+
this.app.get("/api/context/:subject", this.getContext.bind(this));
|
|
230
|
+
this.app.post("/api/index", this.indexKnowledge.bind(this));
|
|
231
|
+
this.app.post("/api/init", this.initDeployment.bind(this));
|
|
232
|
+
this.app.post("/api/scan", this.scanDeployment.bind(this));
|
|
233
|
+
this.app.post("/api/memory", this.remember.bind(this));
|
|
234
|
+
this.app.get("/api/memory/:key", this.recall.bind(this));
|
|
235
|
+
this.app.get("/api/stats", this.getStats.bind(this));
|
|
236
|
+
this.app.get("/metrics", this.getMetrics.bind(this));
|
|
237
|
+
this.app.use(this.errorHandler.bind(this));
|
|
238
|
+
}
|
|
239
|
+
setupWebSocket() {
|
|
240
|
+
this.wss.on("connection", (ws) => {
|
|
241
|
+
this.clients.add(ws);
|
|
242
|
+
console.log("[WS] Client connected");
|
|
243
|
+
this.metricsCounters.wsConnections++;
|
|
244
|
+
ws.on("close", () => {
|
|
245
|
+
this.clients.delete(ws);
|
|
246
|
+
console.log("[WS] Client disconnected");
|
|
247
|
+
});
|
|
248
|
+
ws.on("message", async (data) => {
|
|
249
|
+
try {
|
|
250
|
+
const message = JSON.parse(data.toString());
|
|
251
|
+
await this.handleWebSocketMessage(ws, message);
|
|
252
|
+
} catch {
|
|
253
|
+
ws.send(JSON.stringify({ error: "Invalid message" }));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
ws.send(JSON.stringify({
|
|
257
|
+
type: "connected",
|
|
258
|
+
timestamp: Date.now(),
|
|
259
|
+
message: "Connected to BaselineOS"
|
|
260
|
+
}));
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
attachEventBridge() {
|
|
264
|
+
const events = [
|
|
265
|
+
"task:created",
|
|
266
|
+
"task:decomposed",
|
|
267
|
+
"task:assigned",
|
|
268
|
+
"task:started",
|
|
269
|
+
"task:checkpoint",
|
|
270
|
+
"task:verified",
|
|
271
|
+
"task:review-requested",
|
|
272
|
+
"task:reviewed",
|
|
273
|
+
"task:correction-needed",
|
|
274
|
+
"task:corrected",
|
|
275
|
+
"task:completed",
|
|
276
|
+
"task:failed",
|
|
277
|
+
"task:blocked",
|
|
278
|
+
"task:approved",
|
|
279
|
+
"agent:status-changed",
|
|
280
|
+
"agent:trust-updated",
|
|
281
|
+
"escalation"
|
|
282
|
+
];
|
|
283
|
+
for (const event of events) {
|
|
284
|
+
const unsubscribe = this.baseline.onEvent(event, (payload) => {
|
|
285
|
+
this.broadcast({ ...payload, timestamp: Date.now() });
|
|
286
|
+
if (event === "task:completed" || event === "task:failed") {
|
|
287
|
+
const status = event === "task:completed" ? "completed" : "failed";
|
|
288
|
+
this.metricsCounters.tasksTotal.set(
|
|
289
|
+
status,
|
|
290
|
+
(this.metricsCounters.tasksTotal.get(status) ?? 0) + 1
|
|
291
|
+
);
|
|
292
|
+
if (event === "task:completed") {
|
|
293
|
+
const completedPayload = payload;
|
|
294
|
+
const tokens = completedPayload.task.actualTokens ?? 0;
|
|
295
|
+
if (tokens > 0 && completedPayload.task.assignedAgent) {
|
|
296
|
+
const agents = this.baseline.listAgents();
|
|
297
|
+
const agent = agents.find((a) => a.id === completedPayload.task.assignedAgent);
|
|
298
|
+
const role = agent?.role ?? "unknown";
|
|
299
|
+
this.metricsCounters.tokensByAgentRole.set(
|
|
300
|
+
role,
|
|
301
|
+
(this.metricsCounters.tokensByAgentRole.get(role) ?? 0) + tokens
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const task = completedPayload.task;
|
|
305
|
+
if (task.assignedAgent) {
|
|
306
|
+
this.metricsCounters.autonomousTotal += 1;
|
|
307
|
+
const hasFailure = task.verificationResults?.some((v) => !v.passed);
|
|
308
|
+
if (!hasFailure) {
|
|
309
|
+
this.metricsCounters.autonomousPassed += 1;
|
|
310
|
+
}
|
|
311
|
+
this.metricsCounters.autonomousAccuracy = this.metricsCounters.autonomousTotal > 0 ? this.metricsCounters.autonomousPassed / this.metricsCounters.autonomousTotal : 1;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (event === "task:created") {
|
|
316
|
+
this.metricsCounters.tasksTotal.set(
|
|
317
|
+
"created",
|
|
318
|
+
(this.metricsCounters.tasksTotal.get("created") ?? 0) + 1
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
this.unsubscribeEvents.push(unsubscribe);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// ─── Task Handlers ─────────────────────────────────────────────────────────
|
|
326
|
+
async createTask(req, res, next) {
|
|
327
|
+
try {
|
|
328
|
+
const acceptanceCriteriaInput = Array.isArray(req.body.acceptanceCriteria) ? req.body.acceptanceCriteria : [];
|
|
329
|
+
const acceptanceCriteria = acceptanceCriteriaInput.filter((item) => typeof item?.description === "string" && item.description.trim().length > 0).map((item, index) => ({
|
|
330
|
+
description: item.description?.trim() || `Criteria ${index + 1}`,
|
|
331
|
+
type: item.type ?? "automated",
|
|
332
|
+
weight: typeof item.weight === "number" ? item.weight : 1
|
|
333
|
+
}));
|
|
334
|
+
const input = {
|
|
335
|
+
title: req.body.title,
|
|
336
|
+
description: req.body.description,
|
|
337
|
+
priority: req.body.priority || "medium",
|
|
338
|
+
requiredCapabilities: Array.isArray(req.body.requiredCapabilities) ? req.body.requiredCapabilities : [],
|
|
339
|
+
acceptanceCriteria,
|
|
340
|
+
context: req.body.context && typeof req.body.context === "object" ? req.body.context : {}
|
|
341
|
+
};
|
|
342
|
+
const handle = await this.baseline.run(input);
|
|
343
|
+
this.broadcast({
|
|
344
|
+
type: "task:created",
|
|
345
|
+
taskId: handle.id,
|
|
346
|
+
title: input.title,
|
|
347
|
+
timestamp: Date.now()
|
|
348
|
+
});
|
|
349
|
+
if (req.body.blocking) {
|
|
350
|
+
const result = await handle.wait(req.body.timeout);
|
|
351
|
+
res.json({ task: result });
|
|
352
|
+
} else {
|
|
353
|
+
res.status(201).json({
|
|
354
|
+
taskId: handle.id,
|
|
355
|
+
status: handle.status
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
next(error);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async listTasks(req, res, next) {
|
|
363
|
+
try {
|
|
364
|
+
const status = parseTaskStatus(req.query.status);
|
|
365
|
+
const tasks = this.baseline.listTasks(status ? { status } : void 0);
|
|
366
|
+
res.json({
|
|
367
|
+
tasks: tasks.map((t) => ({
|
|
368
|
+
id: t.id,
|
|
369
|
+
title: t.title,
|
|
370
|
+
status: t.status,
|
|
371
|
+
priority: t.priority,
|
|
372
|
+
complexity: t.complexity,
|
|
373
|
+
createdAt: t.createdAt,
|
|
374
|
+
completedAt: t.completedAt
|
|
375
|
+
}))
|
|
376
|
+
});
|
|
377
|
+
} catch (error) {
|
|
378
|
+
next(error);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async getTask(req, res, next) {
|
|
382
|
+
try {
|
|
383
|
+
const task = this.baseline.getTask(req.params.id);
|
|
384
|
+
if (!task) {
|
|
385
|
+
res.status(404).json({ error: "not_found", message: "Task not found" });
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
res.json({ task });
|
|
389
|
+
} catch (error) {
|
|
390
|
+
next(error);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async cancelTask(req, res, next) {
|
|
394
|
+
try {
|
|
395
|
+
const cancelled = this.baseline.cancelTask(
|
|
396
|
+
req.params.id,
|
|
397
|
+
typeof req.body?.reason === "string" ? req.body.reason : void 0
|
|
398
|
+
);
|
|
399
|
+
if (!cancelled) {
|
|
400
|
+
res.status(404).json({ error: "not_found", message: "Task not found or not cancellable" });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
res.json({ cancelled: true });
|
|
404
|
+
} catch (error) {
|
|
405
|
+
next(error);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async approveTask(req, res, next) {
|
|
409
|
+
try {
|
|
410
|
+
const approvedBy = typeof req.body?.approvedBy === "string" ? req.body.approvedBy : "human";
|
|
411
|
+
const approvalToken = typeof req.body?.approvalToken === "string" ? req.body.approvalToken : void 0;
|
|
412
|
+
const task = await this.baseline.approveTask(req.params.id, approvedBy, approvalToken);
|
|
413
|
+
res.json({ task });
|
|
414
|
+
} catch (error) {
|
|
415
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
416
|
+
if (message.includes("not found")) {
|
|
417
|
+
res.status(404).json({ error: "not_found", message });
|
|
418
|
+
} else if (message.includes("not blocked")) {
|
|
419
|
+
res.status(409).json({ error: "conflict", message });
|
|
420
|
+
} else if (message.includes("token") || message.includes("expired")) {
|
|
421
|
+
res.status(403).json({ error: "forbidden", message });
|
|
422
|
+
} else {
|
|
423
|
+
next(error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async getTaskArtifacts(req, res, next) {
|
|
428
|
+
try {
|
|
429
|
+
const task = this.baseline.getTask(req.params.id);
|
|
430
|
+
if (!task) {
|
|
431
|
+
res.status(404).json({ error: "not_found", message: "Task not found" });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
res.json({ artifacts: task.artifacts });
|
|
435
|
+
} catch (error) {
|
|
436
|
+
next(error);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async getTaskAudit(req, res, next) {
|
|
440
|
+
try {
|
|
441
|
+
const taskId = req.params.id;
|
|
442
|
+
const audit = this.baseline.getAuditTrail(taskId);
|
|
443
|
+
res.json({ taskId, audit });
|
|
444
|
+
} catch (error) {
|
|
445
|
+
next(error);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
async getTaskEvidence(req, res, next) {
|
|
449
|
+
try {
|
|
450
|
+
const taskId = req.params.id;
|
|
451
|
+
const bundle = this.baseline.getEvidenceBundle(taskId);
|
|
452
|
+
if (!bundle.task) {
|
|
453
|
+
res.status(404).json({ error: "not_found", message: "Task not found" });
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
res.json({ taskId, bundle });
|
|
457
|
+
} catch (error) {
|
|
458
|
+
next(error);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async listWorkflows(req, res, next) {
|
|
462
|
+
try {
|
|
463
|
+
const workflows = this.baseline.listWorkflows();
|
|
464
|
+
res.json({ workflows });
|
|
465
|
+
} catch (error) {
|
|
466
|
+
next(error);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async runWorkflow(req, res, next) {
|
|
470
|
+
try {
|
|
471
|
+
const handle = await this.baseline.runWorkflow(req.params.id, {
|
|
472
|
+
title: req.body.title,
|
|
473
|
+
description: req.body.description,
|
|
474
|
+
priority: req.body.priority,
|
|
475
|
+
requiredCapabilities: Array.isArray(req.body.requiredCapabilities) ? req.body.requiredCapabilities : void 0,
|
|
476
|
+
acceptanceCriteria: Array.isArray(req.body.acceptanceCriteria) ? req.body.acceptanceCriteria : void 0,
|
|
477
|
+
context: req.body.context && typeof req.body.context === "object" ? req.body.context : void 0
|
|
478
|
+
});
|
|
479
|
+
if (req.body.blocking) {
|
|
480
|
+
const result = await handle.wait(req.body.timeout);
|
|
481
|
+
res.json({ task: result });
|
|
482
|
+
} else {
|
|
483
|
+
res.status(201).json({ taskId: handle.id, status: handle.status });
|
|
484
|
+
}
|
|
485
|
+
} catch (error) {
|
|
486
|
+
next(error);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async getCheckpoint(req, res, next) {
|
|
490
|
+
try {
|
|
491
|
+
const checkpoint = this.baseline.loadCheckpoint(req.params.id);
|
|
492
|
+
if (!checkpoint) {
|
|
493
|
+
res.status(404).json({ error: "not_found", message: "Checkpoint not found" });
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
res.json({ checkpoint });
|
|
497
|
+
} catch (error) {
|
|
498
|
+
next(error);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async recoverCheckpoint(req, res, next) {
|
|
502
|
+
try {
|
|
503
|
+
const task = this.baseline.recoverTaskFromCheckpoint(req.params.id);
|
|
504
|
+
if (!task) {
|
|
505
|
+
res.status(404).json({ error: "not_found", message: "Checkpoint not found" });
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
res.json({ task });
|
|
509
|
+
} catch (error) {
|
|
510
|
+
next(error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// ─── Agent Handlers ────────────────────────────────────────────────────────
|
|
514
|
+
async listAgents(req, res, next) {
|
|
515
|
+
try {
|
|
516
|
+
const agents = this.baseline.listAgents();
|
|
517
|
+
res.json({ agents });
|
|
518
|
+
} catch (error) {
|
|
519
|
+
next(error);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// ─── Knowledge Handlers ────────────────────────────────────────────────────
|
|
523
|
+
async search(req, res, next) {
|
|
524
|
+
try {
|
|
525
|
+
const policy = req.body.policy && typeof req.body.policy === "object" ? req.body.policy : void 0;
|
|
526
|
+
const results = await this.baseline.search(req.body.query, {
|
|
527
|
+
limit: req.body.limit,
|
|
528
|
+
policy
|
|
529
|
+
});
|
|
530
|
+
res.json({ results });
|
|
531
|
+
} catch (error) {
|
|
532
|
+
next(error);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async getContext(req, res, next) {
|
|
536
|
+
try {
|
|
537
|
+
const level = parseContextLevel(req.query.level);
|
|
538
|
+
const policy = parsePolicy(parseJson(req.query.policy ? String(req.query.policy) : void 0));
|
|
539
|
+
const overlayOrder = req.query.overlayOrder ? String(req.query.overlayOrder).split(",").map((v) => v.trim()).filter((v) => v === "org" || v === "repo" || v === "public") : void 0;
|
|
540
|
+
const budget = parseBudget(parseJson(req.query.budget ? String(req.query.budget) : void 0));
|
|
541
|
+
const context2 = await this.baseline.getContext(req.params.subject, level, {
|
|
542
|
+
policy,
|
|
543
|
+
overlayOrder,
|
|
544
|
+
budget
|
|
545
|
+
});
|
|
546
|
+
if (!context2) {
|
|
547
|
+
res.status(404).json({ error: "not_found", message: "Context not found" });
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
res.json({ context: context2 });
|
|
551
|
+
} catch (error) {
|
|
552
|
+
next(error);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async indexKnowledge(req, res, next) {
|
|
556
|
+
try {
|
|
557
|
+
const result = await this.baseline.index({ full: req.body.full });
|
|
558
|
+
res.json({
|
|
559
|
+
indexed: result.indexed,
|
|
560
|
+
errors: result.errors,
|
|
561
|
+
skipped: result.skipped,
|
|
562
|
+
changed: result.changed,
|
|
563
|
+
removed: result.removed,
|
|
564
|
+
message: `Indexed ${result.indexed} documents`
|
|
565
|
+
});
|
|
566
|
+
} catch (error) {
|
|
567
|
+
next(error);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// ─── Init & Scan Handlers (GA-07, GA-08) ───────────────────────────────────
|
|
571
|
+
async initDeployment(req, res, next) {
|
|
572
|
+
try {
|
|
573
|
+
const contextPath = req.body.contextPath || process.cwd();
|
|
574
|
+
const scan = await this.scanPath(contextPath);
|
|
575
|
+
const result = await this.baseline.index({ full: false });
|
|
576
|
+
res.json({
|
|
577
|
+
initialized: true,
|
|
578
|
+
contextPath,
|
|
579
|
+
detected: scan,
|
|
580
|
+
indexed: result.indexed,
|
|
581
|
+
message: `Baseline initialized for ${contextPath}`
|
|
582
|
+
});
|
|
583
|
+
} catch (error) {
|
|
584
|
+
next(error);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async scanDeployment(req, res, next) {
|
|
588
|
+
try {
|
|
589
|
+
const contextPath = req.body.contextPath || process.cwd();
|
|
590
|
+
const detected = await this.scanPath(contextPath);
|
|
591
|
+
res.json({ contextPath, detected });
|
|
592
|
+
} catch (error) {
|
|
593
|
+
next(error);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async scanPath(contextPath) {
|
|
597
|
+
const detected = {};
|
|
598
|
+
try {
|
|
599
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
600
|
+
const { join } = await import('path');
|
|
601
|
+
const pkgPath = join(contextPath, "package.json");
|
|
602
|
+
if (existsSync(pkgPath)) {
|
|
603
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
604
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
605
|
+
if (allDeps["@anthropic-ai/sdk"]) detected.provider = "anthropic";
|
|
606
|
+
else if (allDeps["openai"]) detected.provider = "openai";
|
|
607
|
+
if (allDeps["langchain"] || allDeps["@langchain/core"]) detected.framework = "langchain";
|
|
608
|
+
if (allDeps["@modelcontextprotocol/sdk"]) detected.mcp = true;
|
|
609
|
+
if (allDeps["bullmq"] || allDeps["@temporalio/client"]) detected.orchestration = true;
|
|
610
|
+
detected.runtime = "node";
|
|
611
|
+
detected.name = pkg.name;
|
|
612
|
+
}
|
|
613
|
+
const pyReq = join(contextPath, "requirements.txt");
|
|
614
|
+
if (existsSync(pyReq)) {
|
|
615
|
+
const reqs = readFileSync(pyReq, "utf8");
|
|
616
|
+
if (reqs.includes("anthropic")) detected.provider = "anthropic";
|
|
617
|
+
else if (reqs.includes("openai")) detected.provider = "openai";
|
|
618
|
+
if (reqs.includes("langchain")) detected.framework = "langchain";
|
|
619
|
+
detected.runtime = "python";
|
|
620
|
+
}
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
return detected;
|
|
624
|
+
}
|
|
625
|
+
// ─── Memory Handlers ───────────────────────────────────────────────────────
|
|
626
|
+
async remember(req, res, next) {
|
|
627
|
+
try {
|
|
628
|
+
await this.baseline.remember(req.body.key, req.body.value, {
|
|
629
|
+
scope: req.body.scope,
|
|
630
|
+
sessionId: req.body.sessionId,
|
|
631
|
+
taskId: req.body.taskId,
|
|
632
|
+
repo: req.body.repo,
|
|
633
|
+
persona: req.body.persona,
|
|
634
|
+
ttl: req.body.ttl,
|
|
635
|
+
tags: Array.isArray(req.body.tags) ? req.body.tags : void 0
|
|
636
|
+
});
|
|
637
|
+
res.json({ stored: true });
|
|
638
|
+
} catch (error) {
|
|
639
|
+
next(error);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async recall(req, res, next) {
|
|
643
|
+
try {
|
|
644
|
+
const scope = parseScope(req.query.scope);
|
|
645
|
+
const sessionId = req.query.sessionId ? String(req.query.sessionId) : void 0;
|
|
646
|
+
const taskId = req.query.taskId ? String(req.query.taskId) : void 0;
|
|
647
|
+
const repo = req.query.repo ? String(req.query.repo) : void 0;
|
|
648
|
+
const persona = req.query.persona ? String(req.query.persona) : void 0;
|
|
649
|
+
const value = await this.baseline.recall(req.params.key, {
|
|
650
|
+
scope,
|
|
651
|
+
sessionId,
|
|
652
|
+
taskId,
|
|
653
|
+
repo,
|
|
654
|
+
persona
|
|
655
|
+
});
|
|
656
|
+
if (value === void 0) {
|
|
657
|
+
res.status(404).json({ error: "not_found", message: "Key not found" });
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
res.json({ value });
|
|
661
|
+
} catch (error) {
|
|
662
|
+
next(error);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// ─── Stats Handler ─────────────────────────────────────────────────────────
|
|
666
|
+
async getStats(req, res, next) {
|
|
667
|
+
try {
|
|
668
|
+
const stats = this.baseline.getStats();
|
|
669
|
+
res.json(stats);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
next(error);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// ─── Prometheus Metrics (SIGNAL-005) ───────────────────────────────────────
|
|
675
|
+
async getMetrics(req, res, next) {
|
|
676
|
+
try {
|
|
677
|
+
const stats = this.baseline.getStats();
|
|
678
|
+
const lines = [];
|
|
679
|
+
const gauge = (name, help, value, labels) => {
|
|
680
|
+
lines.push(`# HELP ${name} ${help}`);
|
|
681
|
+
lines.push(`# TYPE ${name} gauge`);
|
|
682
|
+
lines.push(labels ? `${name}{${labels}} ${value}` : `${name} ${value}`);
|
|
683
|
+
};
|
|
684
|
+
const counter = (name, help, value, labels) => {
|
|
685
|
+
lines.push(`# HELP ${name} ${help}`);
|
|
686
|
+
lines.push(`# TYPE ${name} counter`);
|
|
687
|
+
lines.push(labels ? `${name}{${labels}} ${value}` : `${name} ${value}`);
|
|
688
|
+
};
|
|
689
|
+
gauge(
|
|
690
|
+
"baseline_uptime_seconds",
|
|
691
|
+
"Seconds since API server started",
|
|
692
|
+
Math.round((Date.now() - this.metricsCounters.serverStartTime) / 1e3)
|
|
693
|
+
);
|
|
694
|
+
const tasks = this.baseline.listTasks();
|
|
695
|
+
const tasksByStatus = tasks.reduce((acc, t) => {
|
|
696
|
+
acc[t.status] = (acc[t.status] ?? 0) + 1;
|
|
697
|
+
return acc;
|
|
698
|
+
}, {});
|
|
699
|
+
lines.push("# HELP baseline_tasks_active Current tasks by status");
|
|
700
|
+
lines.push("# TYPE baseline_tasks_active gauge");
|
|
701
|
+
for (const [status, count] of Object.entries(tasksByStatus)) {
|
|
702
|
+
lines.push(`baseline_tasks_active{status="${status}"} ${count}`);
|
|
703
|
+
}
|
|
704
|
+
lines.push("# HELP baseline_tasks_total Cumulative tasks by terminal status");
|
|
705
|
+
lines.push("# TYPE baseline_tasks_total counter");
|
|
706
|
+
for (const [status, count] of this.metricsCounters.tasksTotal) {
|
|
707
|
+
lines.push(`baseline_tasks_total{status="${status}"} ${count}`);
|
|
708
|
+
}
|
|
709
|
+
const agentStats = stats.orchestrator;
|
|
710
|
+
gauge("baseline_agents_total", "Total registered agents", agentStats.agents?.total ?? 0);
|
|
711
|
+
gauge(
|
|
712
|
+
"baseline_agents_active",
|
|
713
|
+
"Currently active agents",
|
|
714
|
+
Object.values(agentStats.agents?.byRole ?? {}).reduce((a, b) => a + b, 0)
|
|
715
|
+
);
|
|
716
|
+
const memStats = stats.memory;
|
|
717
|
+
const memTotal = (memStats.working ?? 0) + (memStats.session ?? 0) + (memStats.longTerm ?? 0) + (memStats.shared ?? 0);
|
|
718
|
+
gauge("baseline_memory_entries", "Total memory entries stored", memTotal);
|
|
719
|
+
const idxStats = stats.indexer;
|
|
720
|
+
gauge("baseline_knowledge_documents", "Documents indexed in knowledge base", idxStats.documents ?? 0);
|
|
721
|
+
gauge("baseline_knowledge_compressions", "Pre-computed compressions in index", idxStats.compressions ?? 0);
|
|
722
|
+
const cacheStats = stats.cache;
|
|
723
|
+
if (typeof cacheStats.hits === "number") {
|
|
724
|
+
counter("baseline_cache_hits_total", "Total semantic cache hits", cacheStats.hits);
|
|
725
|
+
counter("baseline_cache_misses_total", "Total semantic cache misses", cacheStats.misses ?? 0);
|
|
726
|
+
if (cacheStats.hits + (cacheStats.misses ?? 0) > 0) {
|
|
727
|
+
gauge(
|
|
728
|
+
"baseline_cache_hit_rate",
|
|
729
|
+
"Cache hit rate (0\u20131)",
|
|
730
|
+
cacheStats.hits / (cacheStats.hits + (cacheStats.misses ?? 0))
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
lines.push("# HELP baseline_http_requests_total HTTP requests by method and status");
|
|
735
|
+
lines.push("# TYPE baseline_http_requests_total counter");
|
|
736
|
+
for (const [label, count] of this.metricsCounters.requestsTotal) {
|
|
737
|
+
const [method, status] = label.split("_");
|
|
738
|
+
lines.push(`baseline_http_requests_total{method="${method}",status="${status}"} ${count}`);
|
|
739
|
+
}
|
|
740
|
+
lines.push("# HELP baseline_http_request_duration_p50_ms HTTP request duration p50 in ms");
|
|
741
|
+
lines.push("# TYPE baseline_http_request_duration_p50_ms gauge");
|
|
742
|
+
lines.push("# HELP baseline_http_request_duration_p95_ms HTTP request duration p95 in ms");
|
|
743
|
+
lines.push("# TYPE baseline_http_request_duration_p95_ms gauge");
|
|
744
|
+
for (const [method, durations] of this.metricsCounters.requestDurationMs) {
|
|
745
|
+
if (durations.length > 0) {
|
|
746
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
747
|
+
const p50 = sorted[Math.floor(sorted.length * 0.5)];
|
|
748
|
+
const p95 = sorted[Math.floor(sorted.length * 0.95)];
|
|
749
|
+
lines.push(`baseline_http_request_duration_p50_ms{method="${method}"} ${p50}`);
|
|
750
|
+
lines.push(`baseline_http_request_duration_p95_ms{method="${method}"} ${p95}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (this.metricsCounters.tokensByAgentRole.size > 0) {
|
|
754
|
+
lines.push("# HELP baseline_task_tokens_total Cumulative tokens used by agent role");
|
|
755
|
+
lines.push("# TYPE baseline_task_tokens_total counter");
|
|
756
|
+
for (const [role, tokens] of this.metricsCounters.tokensByAgentRole) {
|
|
757
|
+
lines.push(`baseline_task_tokens_total{role="${role}"} ${tokens}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
gauge("baseline_ws_active_connections", "Currently active WebSocket connections", this.clients.size);
|
|
761
|
+
counter("baseline_ws_connections_total", "Total WebSocket connections since start", this.metricsCounters.wsConnections);
|
|
762
|
+
const rollbackTotal = this.metricsCounters.tasksTotal.get("rolledback") ?? 0;
|
|
763
|
+
counter("baseline_rollback_total", "Cumulative rollbacks since start", rollbackTotal);
|
|
764
|
+
gauge(
|
|
765
|
+
"baseline_autonomous_accuracy",
|
|
766
|
+
"Ratio of self-verified autonomous task completions to total autonomous completions (0\u20131)",
|
|
767
|
+
this.metricsCounters.autonomousAccuracy
|
|
768
|
+
);
|
|
769
|
+
if (this.costGuard) {
|
|
770
|
+
const tenantCosts = this.costGuard.getCostByTenant();
|
|
771
|
+
if (tenantCosts.length > 0) {
|
|
772
|
+
lines.push("# HELP baseline_tenant_cost_usd Estimated LLM cost in USD by tenant");
|
|
773
|
+
lines.push("# TYPE baseline_tenant_cost_usd gauge");
|
|
774
|
+
for (const tc of tenantCosts) {
|
|
775
|
+
lines.push(`baseline_tenant_cost_usd{tenant_id="${tc.tenantId}"} ${tc.totalCostUsd.toFixed(6)}`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (this.traceCurator) {
|
|
780
|
+
gauge(
|
|
781
|
+
"baseline_trace_curator_dataset_size",
|
|
782
|
+
"Number of ground-truth cases in the TraceCurator dataset",
|
|
783
|
+
this.traceCurator.getDatasetSize()
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
if (this.modelVersionRegistry) {
|
|
787
|
+
const activeProviders = this.modelVersionRegistry.listActiveProviders();
|
|
788
|
+
if (activeProviders.length > 0) {
|
|
789
|
+
lines.push("# HELP baseline_active_model_version Active model version per provider (info metric, value=1)");
|
|
790
|
+
lines.push("# TYPE baseline_active_model_version gauge");
|
|
791
|
+
for (const { provider, modelId } of activeProviders) {
|
|
792
|
+
lines.push(`baseline_active_model_version{provider="${provider}",model="${modelId}"} 1`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (this.productionEvalPipeline) {
|
|
797
|
+
const history = this.productionEvalPipeline.getHistory();
|
|
798
|
+
if (history.length > 0) {
|
|
799
|
+
const latestByModel = /* @__PURE__ */ new Map();
|
|
800
|
+
for (const entry of history) {
|
|
801
|
+
latestByModel.set(entry.modelId, entry.score);
|
|
802
|
+
}
|
|
803
|
+
lines.push("# HELP baseline_eval_score_latest Latest production eval score per model (0-100)");
|
|
804
|
+
lines.push("# TYPE baseline_eval_score_latest gauge");
|
|
805
|
+
for (const [modelId, score] of latestByModel) {
|
|
806
|
+
lines.push(`baseline_eval_score_latest{model_id="${modelId}"} ${score}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (this.driftDetector && this.productionEvalPipeline) {
|
|
811
|
+
const history = this.productionEvalPipeline.getHistory();
|
|
812
|
+
if (history.length >= 3) {
|
|
813
|
+
const driftReport = this.driftDetector.check(history);
|
|
814
|
+
const severityNum = driftReport.severity === "critical" ? 2 : driftReport.severity === "warning" ? 1 : 0;
|
|
815
|
+
gauge(
|
|
816
|
+
"baseline_model_drift_severity",
|
|
817
|
+
"Model quality drift severity: 0=none 1=warning 2=critical",
|
|
818
|
+
severityNum
|
|
819
|
+
);
|
|
820
|
+
gauge(
|
|
821
|
+
"baseline_model_drift_delta",
|
|
822
|
+
"Score delta from rolling baseline (negative = regression)",
|
|
823
|
+
driftReport.delta
|
|
824
|
+
);
|
|
825
|
+
gauge(
|
|
826
|
+
"baseline_model_drift_baseline_score",
|
|
827
|
+
"Rolling baseline score used for drift comparison",
|
|
828
|
+
driftReport.baselineScore
|
|
829
|
+
);
|
|
830
|
+
gauge(
|
|
831
|
+
"baseline_model_drift_current_score",
|
|
832
|
+
"Current model eval score from latest run",
|
|
833
|
+
driftReport.currentScore
|
|
834
|
+
);
|
|
835
|
+
counter(
|
|
836
|
+
"baseline_model_drift_samples_analyzed",
|
|
837
|
+
"Number of eval history entries analyzed for drift",
|
|
838
|
+
driftReport.samplesAnalyzed
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (this.opaPolicyGate) {
|
|
843
|
+
const opaMetrics = this.opaPolicyGate.getEvalMetrics();
|
|
844
|
+
if (opaMetrics.count > 0) {
|
|
845
|
+
gauge("baseline_opa_evaluation_p50_ms", "OPA policy evaluation p50 latency in ms", opaMetrics.p50Ms);
|
|
846
|
+
gauge("baseline_opa_evaluation_p95_ms", "OPA policy evaluation p95 latency in ms", opaMetrics.p95Ms);
|
|
847
|
+
gauge("baseline_opa_evaluation_p99_ms", "OPA policy evaluation p99 latency in ms (SLO-6 target: < 500ms)", opaMetrics.p99Ms);
|
|
848
|
+
counter("baseline_opa_evaluations_total", "Total OPA policy evaluations", opaMetrics.count);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
gauge(
|
|
852
|
+
"baseline_governance_required_approvals",
|
|
853
|
+
"Operations requiring human approval",
|
|
854
|
+
stats.governance.requireApproval
|
|
855
|
+
);
|
|
856
|
+
gauge(
|
|
857
|
+
"baseline_governance_restricted_operations",
|
|
858
|
+
"Restricted operation count",
|
|
859
|
+
stats.governance.restricted
|
|
860
|
+
);
|
|
861
|
+
res.set("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
|
|
862
|
+
res.send(lines.join("\n") + "\n");
|
|
863
|
+
} catch (error) {
|
|
864
|
+
next(error);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
/** Inject a CostGuard instance for per-tenant cost metrics (SIGNAL-052). */
|
|
868
|
+
setCostGuard(guard) {
|
|
869
|
+
this.costGuard = guard;
|
|
870
|
+
}
|
|
871
|
+
/** Inject a TraceCurator for the dataset-size gauge (SIGNAL-049). */
|
|
872
|
+
setTraceCurator(curator) {
|
|
873
|
+
this.traceCurator = curator;
|
|
874
|
+
}
|
|
875
|
+
/** Inject a ModelVersionRegistry for the active-model gauge (SIGNAL-049). */
|
|
876
|
+
setModelVersionRegistry(registry) {
|
|
877
|
+
this.modelVersionRegistry = registry;
|
|
878
|
+
}
|
|
879
|
+
/** Inject a ProductionEvalPipeline for the eval score gauge (SIGNAL-056). */
|
|
880
|
+
setProductionEvalPipeline(pipeline) {
|
|
881
|
+
this.productionEvalPipeline = pipeline;
|
|
882
|
+
}
|
|
883
|
+
/** Inject a DriftDetector for model drift metrics (SIGNAL-071). */
|
|
884
|
+
setDriftDetector(detector) {
|
|
885
|
+
this.driftDetector = detector;
|
|
886
|
+
}
|
|
887
|
+
/** Inject an OPAPolicyGate for SLO-6 latency metrics. */
|
|
888
|
+
setOPAPolicyGate(gate) {
|
|
889
|
+
this.opaPolicyGate = gate;
|
|
890
|
+
}
|
|
891
|
+
// ─── WebSocket ─────────────────────────────────────────────────────────────
|
|
892
|
+
async handleWebSocketMessage(ws, message) {
|
|
893
|
+
const parsed = parseSocketMessage(message);
|
|
894
|
+
if (!parsed) {
|
|
895
|
+
ws.send(JSON.stringify({ error: "Unknown message type" }));
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
switch (parsed.type) {
|
|
899
|
+
case "subscribe":
|
|
900
|
+
ws.send(JSON.stringify({ type: "subscribed", taskId: parsed.taskId }));
|
|
901
|
+
break;
|
|
902
|
+
case "ping":
|
|
903
|
+
ws.send(JSON.stringify({ type: "pong", timestamp: Date.now() }));
|
|
904
|
+
break;
|
|
905
|
+
default:
|
|
906
|
+
ws.send(JSON.stringify({ error: "Unknown message type" }));
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Broadcast message to all connected WebSocket clients.
|
|
911
|
+
*/
|
|
912
|
+
broadcast(message) {
|
|
913
|
+
const data = JSON.stringify(message);
|
|
914
|
+
for (const client of this.clients) {
|
|
915
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
916
|
+
client.send(data);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
// ─── Error Handler ─────────────────────────────────────────────────────────
|
|
921
|
+
errorHandler(err, req, res, _next) {
|
|
922
|
+
process.stderr.write(`[baseline:api] ${req.method} ${req.path} \u2014 ${err.stack ?? err.message}
|
|
923
|
+
`);
|
|
924
|
+
res.status(500).json({
|
|
925
|
+
error: "internal_error",
|
|
926
|
+
message: "An unexpected error occurred. Check server logs for details."
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
// ─── Lifecycle ─────────────────────────────────────────────────────────────
|
|
930
|
+
formatStartError(error) {
|
|
931
|
+
if (error.code === "EADDRINUSE") {
|
|
932
|
+
return new Error(
|
|
933
|
+
`Port ${this.config.port} is already in use. Stop the existing Baseline API process or choose a different port.`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
if (error.code === "EACCES") {
|
|
937
|
+
return new Error(
|
|
938
|
+
`Insufficient permission to bind port ${this.config.port}. Try a non-privileged port (for example: 3141).`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
return new Error(`Failed to start API server on port ${this.config.port}: ${error.message}`);
|
|
942
|
+
}
|
|
943
|
+
async start() {
|
|
944
|
+
if (this.server.listening) return;
|
|
945
|
+
return new Promise((resolve, reject) => {
|
|
946
|
+
const onError = (error) => {
|
|
947
|
+
this.server.off("listening", onListening);
|
|
948
|
+
reject(this.formatStartError(error));
|
|
949
|
+
};
|
|
950
|
+
const onListening = () => {
|
|
951
|
+
this.server.off("error", onError);
|
|
952
|
+
resolve();
|
|
953
|
+
};
|
|
954
|
+
this.server.once("error", onError);
|
|
955
|
+
this.server.once("listening", onListening);
|
|
956
|
+
this.server.listen(this.config.port);
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
async stop() {
|
|
960
|
+
this.wss.close();
|
|
961
|
+
if (!this.server.listening) return;
|
|
962
|
+
return new Promise((resolve, reject) => {
|
|
963
|
+
this.server.close((error) => {
|
|
964
|
+
if (error) {
|
|
965
|
+
reject(error);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
resolve();
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
/**
|
|
974
|
+
* BaselineOS OpenTelemetry — SIGNAL-012
|
|
975
|
+
*
|
|
976
|
+
* Distributed tracing for the multi-agent orchestration layer.
|
|
977
|
+
* Initializes the OTel Node SDK and exports a shared tracer.
|
|
978
|
+
*
|
|
979
|
+
* Spans emitted:
|
|
980
|
+
* task.execute root span per executeTask() call
|
|
981
|
+
* task.policy.pre OPA pre-execution gate
|
|
982
|
+
* task.policy.post OPA post-execution gate
|
|
983
|
+
* task.perform performExecution() step loop
|
|
984
|
+
* task.self_verify selfVerify() LLM call
|
|
985
|
+
* task.review conductReview() LLM call
|
|
986
|
+
*
|
|
987
|
+
* Trace context is propagated through AgentBus messages via the
|
|
988
|
+
* optional `traceContext` field (W3C traceparent format).
|
|
989
|
+
*
|
|
990
|
+
* Configuration:
|
|
991
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT OTLP HTTP collector (default: http://localhost:4318)
|
|
992
|
+
* OTEL_SERVICE_NAME service name override (default: baselineos)
|
|
993
|
+
* BASELINE_OTEL_DISABLED set to '1' to disable entirely (e.g. in tests)
|
|
994
|
+
*
|
|
995
|
+
* @license Apache-2.0
|
|
996
|
+
*/
|
|
997
|
+
/**
|
|
998
|
+
* BaselineOS API Server
|
|
999
|
+
*
|
|
1000
|
+
* REST API and WebSocket for programmatic access.
|
|
1001
|
+
*
|
|
1002
|
+
* @license Apache-2.0
|
|
1003
|
+
*/
|
|
1004
|
+
|
|
1005
|
+
export { APIServer };
|
|
1006
|
+
//# sourceMappingURL=server.js.map
|
|
1007
|
+
//# sourceMappingURL=server.js.map
|