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.
Files changed (64) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +198 -0
  3. package/dist/__evals__/runner.d.ts +2 -0
  4. package/dist/__evals__/runner.js +14687 -0
  5. package/dist/__evals__/runner.js.map +1 -0
  6. package/dist/api/server.d.ts +21 -0
  7. package/dist/api/server.js +1007 -0
  8. package/dist/api/server.js.map +1 -0
  9. package/dist/cli/bin.d.ts +1 -0
  10. package/dist/cli/bin.js +8427 -0
  11. package/dist/cli/bin.js.map +1 -0
  12. package/dist/core/agent-bus.d.ts +110 -0
  13. package/dist/core/agent-bus.js +242 -0
  14. package/dist/core/agent-bus.js.map +1 -0
  15. package/dist/core/cache.d.ts +66 -0
  16. package/dist/core/cache.js +160 -0
  17. package/dist/core/cache.js.map +1 -0
  18. package/dist/core/config.d.ts +1002 -0
  19. package/dist/core/config.js +429 -0
  20. package/dist/core/config.js.map +1 -0
  21. package/dist/core/indexer.d.ts +152 -0
  22. package/dist/core/indexer.js +481 -0
  23. package/dist/core/indexer.js.map +1 -0
  24. package/dist/core/llm-tracer.d.ts +2 -0
  25. package/dist/core/llm-tracer.js +241 -0
  26. package/dist/core/llm-tracer.js.map +1 -0
  27. package/dist/core/memory.d.ts +86 -0
  28. package/dist/core/memory.js +346 -0
  29. package/dist/core/memory.js.map +1 -0
  30. package/dist/core/opa-client.d.ts +51 -0
  31. package/dist/core/opa-client.js +157 -0
  32. package/dist/core/opa-client.js.map +1 -0
  33. package/dist/core/opa-policy-gate.d.ts +133 -0
  34. package/dist/core/opa-policy-gate.js +454 -0
  35. package/dist/core/opa-policy-gate.js.map +1 -0
  36. package/dist/core/orchestrator.d.ts +14 -0
  37. package/dist/core/orchestrator.js +1297 -0
  38. package/dist/core/orchestrator.js.map +1 -0
  39. package/dist/core/pii-detector.d.ts +82 -0
  40. package/dist/core/pii-detector.js +126 -0
  41. package/dist/core/pii-detector.js.map +1 -0
  42. package/dist/core/rag-engine.d.ts +121 -0
  43. package/dist/core/rag-engine.js +504 -0
  44. package/dist/core/rag-engine.js.map +1 -0
  45. package/dist/core/task-queue.d.ts +69 -0
  46. package/dist/core/task-queue.js +124 -0
  47. package/dist/core/task-queue.js.map +1 -0
  48. package/dist/core/telemetry.d.ts +56 -0
  49. package/dist/core/telemetry.js +94 -0
  50. package/dist/core/telemetry.js.map +1 -0
  51. package/dist/core/types.d.ts +328 -0
  52. package/dist/core/types.js +24 -0
  53. package/dist/core/types.js.map +1 -0
  54. package/dist/index.d.ts +21 -0
  55. package/dist/index.js +12444 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/llm-tracer-CIIujuO-.d.ts +493 -0
  58. package/dist/mcp/server.d.ts +2651 -0
  59. package/dist/mcp/server.js +676 -0
  60. package/dist/mcp/server.js.map +1 -0
  61. package/dist/orchestrator-DF89k_AK.d.ts +506 -0
  62. package/package.json +157 -0
  63. package/templates/README.md +7 -0
  64. package/templates/baseline.config.ts +207 -0
@@ -0,0 +1,1297 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import { randomUUID } from 'crypto';
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { trace, metrics, SpanKind, SpanStatusCode } from '@opentelemetry/api';
6
+ import '@opentelemetry/sdk-node';
7
+ import '@opentelemetry/exporter-trace-otlp-http';
8
+ import '@opentelemetry/resources';
9
+ import '@opentelemetry/semantic-conventions';
10
+ import { z } from 'zod';
11
+ import { Queue, Worker } from 'bullmq';
12
+
13
+ // src/core/orchestrator.ts
14
+ var tracer = new Proxy({}, {
15
+ get(_target, prop) {
16
+ return trace.getTracer("baselineos", "0.2.0-beta.1")[prop];
17
+ }
18
+ });
19
+ async function withSpan(name, attributes, fn) {
20
+ return tracer.startActiveSpan(name, { kind: SpanKind.INTERNAL, attributes }, async (span) => {
21
+ try {
22
+ const result = await fn(span);
23
+ span.setStatus({ code: SpanStatusCode.OK });
24
+ return result;
25
+ } catch (err) {
26
+ span.recordException(err);
27
+ span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
28
+ throw err;
29
+ } finally {
30
+ span.end();
31
+ }
32
+ });
33
+ }
34
+ var TaskInputSchema = z.object({
35
+ title: z.string().min(1),
36
+ description: z.string().min(1),
37
+ priority: z.enum(["critical", "high", "medium", "low"]).default("medium"),
38
+ requiredCapabilities: z.array(z.string()).default([]),
39
+ acceptanceCriteria: z.array(z.object({
40
+ description: z.string(),
41
+ type: z.enum(["automated", "agent-review", "human-review"]).default("automated"),
42
+ weight: z.number().min(0).max(1).default(1)
43
+ })).default([]),
44
+ context: z.record(z.unknown()).default({})
45
+ });
46
+ var PiiBlockedError = class extends Error {
47
+ constructor(types) {
48
+ super(`PII detected in prompt \u2014 blocked: ${types.join(", ")}`);
49
+ this.types = types;
50
+ this.name = "PiiBlockedError";
51
+ }
52
+ };
53
+ var PRIORITY_MAP = {
54
+ critical: 1,
55
+ high: 2,
56
+ medium: 5,
57
+ low: 10
58
+ };
59
+ var TaskQueue = class {
60
+ queue;
61
+ worker;
62
+ queueName;
63
+ /**
64
+ * @param executor Called by the worker when a job is dequeued.
65
+ * Receives the taskId. Should call Orchestrator.dispatchExecution().
66
+ * @param config Queue configuration.
67
+ */
68
+ constructor(executor, config = {}) {
69
+ this.queueName = config.queueName ?? "baseline-tasks";
70
+ const redisUrl = config.redisUrl ?? process.env["BASELINE_REDIS_URL"] ?? "redis://localhost:6379";
71
+ const connection = this.parseRedisUrl(redisUrl);
72
+ const attempts = config.attempts ?? 3;
73
+ const backoffDelay = config.backoffDelay ?? 2e3;
74
+ this.queue = new Queue(this.queueName, {
75
+ connection,
76
+ defaultJobOptions: {
77
+ attempts,
78
+ backoff: { type: "exponential", delay: backoffDelay },
79
+ removeOnComplete: { count: 500 },
80
+ removeOnFail: { count: 200 }
81
+ }
82
+ });
83
+ this.worker = new Worker(
84
+ this.queueName,
85
+ async (job) => {
86
+ await executor(job.data.taskId);
87
+ },
88
+ {
89
+ connection,
90
+ concurrency: config.concurrency ?? 5
91
+ }
92
+ );
93
+ this.worker.on("failed", (job, err) => {
94
+ console.warn(`[TaskQueue] Job ${job?.id ?? "?"} (task ${job?.data.taskId ?? "?"}) failed: ${err.message}`);
95
+ });
96
+ }
97
+ /**
98
+ * Enqueue a task for execution.
99
+ * Uses taskId as jobId — duplicate enqueues are silently ignored.
100
+ */
101
+ async enqueue(taskId, priority) {
102
+ await this.queue.add(
103
+ "execute",
104
+ { taskId },
105
+ {
106
+ jobId: taskId,
107
+ priority: PRIORITY_MAP[priority]
108
+ }
109
+ );
110
+ }
111
+ /**
112
+ * Queue statistics — useful for health checks and dashboards.
113
+ */
114
+ async getStats() {
115
+ const [waiting, active, completed, failed, delayed] = await Promise.all([
116
+ this.queue.getWaitingCount(),
117
+ this.queue.getActiveCount(),
118
+ this.queue.getCompletedCount(),
119
+ this.queue.getFailedCount(),
120
+ this.queue.getDelayedCount()
121
+ ]);
122
+ return { waiting, active, completed, failed, delayed };
123
+ }
124
+ /** Gracefully close queue and worker connections. */
125
+ async close() {
126
+ await this.worker.close();
127
+ await this.queue.close();
128
+ }
129
+ // ─── Internal ──────────────────────────────────────────────────────────────
130
+ parseRedisUrl(url) {
131
+ try {
132
+ const u = new URL(url);
133
+ const config = {
134
+ host: u.hostname || "localhost",
135
+ port: u.port ? parseInt(u.port, 10) : 6379,
136
+ username: u.username || void 0,
137
+ password: u.password || void 0
138
+ };
139
+ if (u.pathname && u.pathname !== "/") {
140
+ config["db"] = parseInt(u.pathname.slice(1), 10);
141
+ }
142
+ return config;
143
+ } catch {
144
+ return { host: "localhost", port: 6379 };
145
+ }
146
+ }
147
+ };
148
+
149
+ // src/core/orchestrator.ts
150
+ var DEFAULT_CONFIG = {
151
+ maxAutonomousComplexity: "complex",
152
+ selfVerificationRequired: true,
153
+ supervisorReviewThreshold: 0.7,
154
+ qualityReviewSampleRate: 0.1,
155
+ defaultTrustScore: 70,
156
+ defaultMaxAttempts: 3,
157
+ maxTokensPerTask: 1e5,
158
+ maxConcurrentAgents: 10,
159
+ trustGrowthRate: 0.02,
160
+ trustDecayRate: 0.05,
161
+ trustMinimum: 30
162
+ };
163
+ var COMPLEXITY_ORDER = ["trivial", "simple", "moderate", "complex", "epic"];
164
+ function complexityToNumber(c) {
165
+ return COMPLEXITY_ORDER.indexOf(c);
166
+ }
167
+ var Orchestrator = class extends EventEmitter {
168
+ config;
169
+ tasks;
170
+ agents;
171
+ disabledWorkflows;
172
+ checkpoints;
173
+ reviews;
174
+ plans;
175
+ workflows;
176
+ auditByTask;
177
+ auditGlobal;
178
+ taskQueue;
179
+ auditLog;
180
+ constructor(config = {}) {
181
+ super();
182
+ this.config = { ...DEFAULT_CONFIG, ...config };
183
+ this.tasks = /* @__PURE__ */ new Map();
184
+ this.agents = /* @__PURE__ */ new Map();
185
+ this.checkpoints = /* @__PURE__ */ new Map();
186
+ this.reviews = /* @__PURE__ */ new Map();
187
+ this.plans = /* @__PURE__ */ new Map();
188
+ this.workflows = /* @__PURE__ */ new Map();
189
+ this.auditByTask = /* @__PURE__ */ new Map();
190
+ this.auditGlobal = [];
191
+ this.disabledWorkflows = new Set(this.config.disabledWorkflows ?? []);
192
+ if (this.config.taskQueueConfig) {
193
+ this.taskQueue = new TaskQueue(
194
+ async (taskId) => {
195
+ await this.dispatchExecution(taskId);
196
+ },
197
+ this.config.taskQueueConfig
198
+ );
199
+ }
200
+ if (this.config.auditLog) {
201
+ this.auditLog = this.config.auditLog;
202
+ }
203
+ if (Array.isArray(this.config.workflows)) {
204
+ for (const workflow of this.config.workflows) {
205
+ this.registerWorkflow(workflow);
206
+ }
207
+ }
208
+ this.attachAuditListeners();
209
+ }
210
+ // ─── Task Management ─────────────────────────────────────────────────────────
211
+ async createTask(input) {
212
+ const validated = TaskInputSchema.parse(input);
213
+ const task = {
214
+ id: randomUUID(),
215
+ title: validated.title,
216
+ description: validated.description,
217
+ status: "pending",
218
+ priority: validated.priority,
219
+ complexity: this.assessComplexity(validated),
220
+ subtasks: [],
221
+ dependencies: [],
222
+ requiredCapabilities: validated.requiredCapabilities,
223
+ attempts: 0,
224
+ maxAttempts: this.config.defaultMaxAttempts,
225
+ acceptanceCriteria: validated.acceptanceCriteria.map((ac, i) => ({
226
+ id: `ac-${i}`,
227
+ description: ac.description,
228
+ type: ac.type,
229
+ weight: ac.weight
230
+ })),
231
+ verificationResults: [],
232
+ createdAt: Date.now(),
233
+ updatedAt: Date.now(),
234
+ context: validated.context,
235
+ artifacts: []
236
+ };
237
+ this.tasks.set(task.id, task);
238
+ this.emit("task:created", { type: "task:created", task });
239
+ if (this.requiresHumanApproval(task)) {
240
+ task.status = "blocked";
241
+ task.needsHumanReview = true;
242
+ return task;
243
+ }
244
+ if (this.shouldDecompose(task)) {
245
+ await this.decomposeTask(task);
246
+ }
247
+ return task;
248
+ }
249
+ async executeTask(taskId) {
250
+ const task = this.tasks.get(taskId);
251
+ if (!task) {
252
+ throw new Error(`Task not found: ${taskId}`);
253
+ }
254
+ if (task.status === "blocked" && task.needsHumanReview) {
255
+ throw new Error(`Task ${taskId} requires human approval before execution`);
256
+ }
257
+ if (this.config.policyGate) {
258
+ const bestAgent = this.selectBestAgent(task);
259
+ const agentId = bestAgent?.id ?? "system";
260
+ const trustScore = bestAgent?.trustScore ?? this.config.defaultTrustScore;
261
+ const gateResult = await this.config.policyGate.preExecution(task, agentId, trustScore);
262
+ if (gateResult.blocked) {
263
+ task.status = "blocked";
264
+ task.needsHumanReview = true;
265
+ task.updatedAt = Date.now();
266
+ const violation = gateResult.evaluations.find((e) => !e.allowed);
267
+ const reason = violation?.reason ?? "Policy gate denied execution";
268
+ const policy = violation?.policy ?? "unknown";
269
+ if (gateResult.approvalToken) {
270
+ task.context = {
271
+ ...task.context,
272
+ approvalToken: gateResult.approvalToken,
273
+ approvalTokenExpires: gateResult.approvalTokenExpires
274
+ };
275
+ }
276
+ this.emit("task:blocked", { type: "task:blocked", task, reason, policy });
277
+ this.emit("escalation", { type: "escalation", task, reason, escalatedTo: "human" });
278
+ metrics.getMeter("baselineos").createCounter("baseline_escalation_total").add(1, {
279
+ reason: reason ?? "unknown",
280
+ agent_id: task.assignedAgent ?? "unknown"
281
+ });
282
+ return task;
283
+ }
284
+ }
285
+ if (this.taskQueue) {
286
+ await this.taskQueue.enqueue(taskId, task.priority);
287
+ return task;
288
+ }
289
+ return this.dispatchExecution(taskId);
290
+ }
291
+ /**
292
+ * Core execution body — called by the BullMQ worker (or directly when no queue).
293
+ * Runs the full supervision loop: execute → self-verify → review → correct.
294
+ * Not intended to be called externally; exposed for the queue worker closure.
295
+ */
296
+ async dispatchExecution(taskId) {
297
+ const task = this.tasks.get(taskId);
298
+ if (!task) throw new Error(`Task not found: ${taskId}`);
299
+ task.status = "executing";
300
+ task.attempts++;
301
+ this.setFinalApprovalGate(task, "pending");
302
+ this.emit("task:started", { type: "task:started", task });
303
+ if (this.config.personaEngine) {
304
+ const userId = task.context?.userId ?? "system";
305
+ const activePersona = this.config.personaEngine.getActivePersona(userId);
306
+ if (activePersona) {
307
+ task.context = { ...task.context, persona: { id: activePersona.id, name: activePersona.name } };
308
+ }
309
+ this.config.personaEngine.learnFromBehavior(userId, {
310
+ type: "task_execution",
311
+ context: { taskId: task.id, complexity: task.complexity, priority: task.priority },
312
+ timestamp: /* @__PURE__ */ new Date()
313
+ });
314
+ }
315
+ if (this.config.layerSweep) {
316
+ const bestAgent = this.selectBestAgent(task);
317
+ const sweepResult = this.config.layerSweep.sweep({
318
+ taskId: task.id,
319
+ title: task.title,
320
+ description: task.description,
321
+ complexity: task.complexity,
322
+ priority: task.priority,
323
+ agent: bestAgent ? { id: bestAgent.id, role: bestAgent.role, trustScore: bestAgent.trustScore } : void 0
324
+ });
325
+ task.context = { ...task.context, layerSweep: sweepResult };
326
+ if (!sweepResult.overallPassed) {
327
+ const failedLayers = sweepResult.layerResults.filter((r) => !r.passed).map((r) => `${r.layerId}: ${r.checks.filter((c) => !c.passed).map((c) => c.detail).join("; ")}`).join(" | ");
328
+ task.status = "blocked";
329
+ task.needsHumanReview = true;
330
+ task.updatedAt = Date.now();
331
+ this.emit("task:blocked", { type: "task:blocked", task, reason: `Layer sweep failed: ${failedLayers}`, policy: "layer-sweep" });
332
+ this.emit("escalation", { type: "escalation", task, reason: `Governance layers failed: ${failedLayers}`, escalatedTo: "human" });
333
+ return task;
334
+ }
335
+ }
336
+ await withSpan("task.execute", {
337
+ "task.id": task.id,
338
+ "task.priority": task.priority,
339
+ "task.complexity": task.complexity,
340
+ "task.attempt": task.attempts
341
+ }, async (span) => {
342
+ try {
343
+ await this.executeWithSupervision(task);
344
+ const finalApprovalGate = this.getFinalApprovalGate(task);
345
+ if (task.needsHumanReview || finalApprovalGate !== "approved") {
346
+ const reason = task.context.finalGateReason ?? "Final approval gate unresolved";
347
+ task.status = "blocked";
348
+ task.needsHumanReview = true;
349
+ this.setFinalApprovalGate(task, "blocked", reason);
350
+ span.setAttribute("task.outcome", "blocked");
351
+ this.emit("escalation", { type: "escalation", task, reason, escalatedTo: "human" });
352
+ metrics.getMeter("baselineos").createCounter("baseline_escalation_total").add(1, {
353
+ reason: reason ?? "unknown",
354
+ agent_id: task.assignedAgent ?? "unknown"
355
+ });
356
+ } else {
357
+ task.status = "completed";
358
+ task.completedAt = Date.now();
359
+ span.setAttribute("task.outcome", "completed");
360
+ this.emit("task:completed", { type: "task:completed", task });
361
+ }
362
+ if (task.assignedAgent) {
363
+ this.updateAgentTrust(task.assignedAgent, true);
364
+ }
365
+ } catch (error) {
366
+ task.status = "failed";
367
+ this.setFinalApprovalGate(task, "blocked", "Execution failed");
368
+ span.setAttribute("task.outcome", "failed");
369
+ this.emit("task:failed", { type: "task:failed", task, error });
370
+ if (task.assignedAgent) {
371
+ this.updateAgentTrust(task.assignedAgent, false);
372
+ }
373
+ }
374
+ });
375
+ task.updatedAt = Date.now();
376
+ return task;
377
+ }
378
+ /**
379
+ * Execute task with autonomous supervision loop.
380
+ * This is where the AMP/Ralph magic happens.
381
+ */
382
+ async executeWithSupervision(task, startTime = Date.now()) {
383
+ const budget = this.getWorkflowBudget(task);
384
+ await this.performExecution(task, budget);
385
+ if (this.config.selfVerificationRequired) {
386
+ const selfVerifyResult = await this.selfVerify(task);
387
+ if (!selfVerifyResult.passed) {
388
+ if (task.attempts < task.maxAttempts) {
389
+ task.status = "correcting";
390
+ this.emit("task:correction-needed", {
391
+ type: "task:correction-needed",
392
+ task,
393
+ feedback: selfVerifyResult.details
394
+ });
395
+ task.context = {
396
+ ...task.context,
397
+ correctionFeedback: selfVerifyResult.details,
398
+ previousAttempt: task.attempts
399
+ };
400
+ task.attempts += 1;
401
+ if (budget?.maxAttempts && task.attempts > budget.maxAttempts) {
402
+ task.needsHumanReview = true;
403
+ this.setFinalApprovalGate(task, "blocked", "Workflow budget max attempts exceeded");
404
+ return;
405
+ }
406
+ if (budget?.maxDurationMs && Date.now() - startTime > budget.maxDurationMs) {
407
+ task.needsHumanReview = true;
408
+ this.setFinalApprovalGate(task, "blocked", "Workflow budget max duration exceeded");
409
+ return;
410
+ }
411
+ return this.executeWithSupervision(task, startTime);
412
+ }
413
+ task.needsHumanReview = true;
414
+ this.setFinalApprovalGate(task, "blocked", "Self-verification failed and max attempts exhausted");
415
+ task.context = {
416
+ ...task.context,
417
+ escalationReason: "Self-verification failed and max attempts exhausted",
418
+ lastVerificationFailure: selfVerifyResult.details
419
+ };
420
+ return;
421
+ }
422
+ }
423
+ const shouldReview = this.shouldTriggerSupervisorReview(task);
424
+ if (shouldReview) {
425
+ task.status = "reviewing";
426
+ const supervisor = this.findSupervisor(task);
427
+ if (supervisor) {
428
+ this.emit("task:review-requested", {
429
+ type: "task:review-requested",
430
+ task,
431
+ reviewer: supervisor
432
+ });
433
+ const review = await this.conductReview(task, supervisor);
434
+ this.reviews.set(review.id, review);
435
+ this.emit("task:reviewed", { type: "task:reviewed", task, review });
436
+ if (review.overallAssessment === "needs-work") {
437
+ if (task.attempts < task.maxAttempts) {
438
+ task.status = "correcting";
439
+ task.context = {
440
+ ...task.context,
441
+ reviewFeedback: review.feedback,
442
+ requiredChanges: review.requiredChanges
443
+ };
444
+ this.emit("task:correction-needed", {
445
+ type: "task:correction-needed",
446
+ task,
447
+ feedback: review.feedback
448
+ });
449
+ task.attempts += 1;
450
+ if (budget?.maxAttempts && task.attempts > budget.maxAttempts) {
451
+ task.needsHumanReview = true;
452
+ this.setFinalApprovalGate(task, "blocked", "Workflow budget max attempts exceeded");
453
+ return;
454
+ }
455
+ if (budget?.maxDurationMs && Date.now() - startTime > budget.maxDurationMs) {
456
+ task.needsHumanReview = true;
457
+ this.setFinalApprovalGate(task, "blocked", "Workflow budget max duration exceeded");
458
+ return;
459
+ }
460
+ return this.executeWithSupervision(task, startTime);
461
+ }
462
+ task.needsHumanReview = true;
463
+ this.setFinalApprovalGate(task, "blocked", "Supervisor requested changes and max attempts exhausted");
464
+ task.context = {
465
+ ...task.context,
466
+ escalationReason: "Supervisor requested changes and max attempts exhausted",
467
+ lastReviewFeedback: review.feedback
468
+ };
469
+ return;
470
+ } else if (review.overallAssessment === "rejected") {
471
+ const qualityAgent = this.findQualityAgent(task);
472
+ if (qualityAgent) {
473
+ const qualityReview = await this.conductReview(task, qualityAgent);
474
+ if (qualityReview.overallAssessment !== "approved") {
475
+ task.needsHumanReview = true;
476
+ this.setFinalApprovalGate(task, "blocked", "Quality review failed after supervisor rejection");
477
+ this.emit("escalation", {
478
+ type: "escalation",
479
+ task,
480
+ reason: "Quality review failed after supervisor rejection",
481
+ escalatedTo: "human"
482
+ });
483
+ }
484
+ } else {
485
+ task.needsHumanReview = true;
486
+ this.setFinalApprovalGate(task, "blocked", "Quality agent unavailable after supervisor rejection");
487
+ }
488
+ }
489
+ }
490
+ }
491
+ if (Math.random() < this.config.qualityReviewSampleRate) {
492
+ const qualityAgent = this.findQualityAgent(task);
493
+ if (qualityAgent) {
494
+ const qualityReview = await this.conductReview(task, qualityAgent);
495
+ this.reviews.set(qualityReview.id, qualityReview);
496
+ this.emit("task:reviewed", { type: "task:reviewed", task, review: qualityReview });
497
+ if (qualityReview.overallAssessment !== "approved") {
498
+ task.needsHumanReview = true;
499
+ this.setFinalApprovalGate(task, "blocked", "Quality sampling review failed");
500
+ task.context = {
501
+ ...task.context,
502
+ escalationReason: "Quality sampling review failed",
503
+ qualityReviewFeedback: qualityReview.feedback
504
+ };
505
+ this.emit("escalation", {
506
+ type: "escalation",
507
+ task,
508
+ reason: "Quality sampling review failed",
509
+ escalatedTo: "human"
510
+ });
511
+ return;
512
+ }
513
+ }
514
+ }
515
+ this.setFinalApprovalGate(task, "approved");
516
+ }
517
+ async performExecution(task, budget) {
518
+ return withSpan("task.perform", {
519
+ "task.id": task.id,
520
+ "task.workflow_id": task.workflowId ?? ""
521
+ }, async () => {
522
+ return this._performExecution(task, budget);
523
+ });
524
+ }
525
+ async _performExecution(task, budget) {
526
+ if (this.config.piiDetector) {
527
+ try {
528
+ const safeDescription = this.config.piiDetector.processText(task.description, task.id);
529
+ const safeTitle = this.config.piiDetector.processText(task.title, task.id);
530
+ if (safeDescription !== task.description || safeTitle !== task.title) {
531
+ task.context = {
532
+ ...task.context,
533
+ originalDescription: task.description,
534
+ originalTitle: task.title,
535
+ piiRedacted: true
536
+ };
537
+ task.description = safeDescription;
538
+ task.title = safeTitle;
539
+ }
540
+ } catch (err) {
541
+ if (err instanceof PiiBlockedError) {
542
+ task.status = "failed";
543
+ task.context = { ...task.context, piiBlocked: true, piiTypes: err.types };
544
+ task.updatedAt = Date.now();
545
+ throw err;
546
+ }
547
+ throw err;
548
+ }
549
+ }
550
+ const checkpoint = {
551
+ id: randomUUID(),
552
+ taskId: task.id,
553
+ state: { ...task.context },
554
+ artifacts: [...task.artifacts],
555
+ decisions: [],
556
+ createdBy: task.assignedAgent || "orchestrator",
557
+ createdAt: Date.now(),
558
+ description: `Before attempt ${task.attempts}`,
559
+ recoverable: true
560
+ };
561
+ this.checkpoints.set(checkpoint.id, checkpoint);
562
+ task.currentCheckpoint = checkpoint.id;
563
+ this.emit("task:checkpoint", { type: "task:checkpoint", task, checkpoint });
564
+ this.persistCheckpoint(checkpoint);
565
+ const engine = this.config.engine;
566
+ const plan = task.workflowPlanId ? this.plans.get(task.workflowPlanId) : void 0;
567
+ const steps = plan?.steps ?? [];
568
+ const maxSteps = budget?.maxSteps;
569
+ if (steps.length > 0) {
570
+ let stepCount = 0;
571
+ for (const step of steps) {
572
+ if (maxSteps && stepCount >= maxSteps) {
573
+ task.needsHumanReview = true;
574
+ this.setFinalApprovalGate(task, "blocked", "Workflow budget max steps exceeded");
575
+ break;
576
+ }
577
+ step.status = "executing";
578
+ if (engine) {
579
+ const result = await engine.execute(task, step);
580
+ step.status = "completed";
581
+ step.result = {
582
+ success: result.compliance.passed,
583
+ output: result.output,
584
+ artifacts: result.artifacts,
585
+ tokensUsed: result.tokensUsed,
586
+ duration: result.duration
587
+ };
588
+ task.artifacts.push(...result.artifacts);
589
+ task.actualTokens = (task.actualTokens ?? 0) + result.tokensUsed;
590
+ task.context = {
591
+ ...task.context,
592
+ lastOutput: result.output,
593
+ lastModel: result.model,
594
+ compliancePassed: result.compliance.passed,
595
+ complianceScore: result.compliance.score
596
+ };
597
+ if (!result.compliance.passed) {
598
+ task.context = {
599
+ ...task.context,
600
+ complianceViolations: result.compliance.violations
601
+ };
602
+ }
603
+ } else {
604
+ await new Promise((resolve) => setTimeout(resolve, 150));
605
+ step.status = "completed";
606
+ step.result = {
607
+ success: true,
608
+ output: { message: `Executed step: ${step.title}` },
609
+ artifacts: [],
610
+ tokensUsed: 0,
611
+ duration: 150
612
+ };
613
+ }
614
+ stepCount += 1;
615
+ }
616
+ } else {
617
+ if (engine) {
618
+ const result = await engine.execute(task);
619
+ task.artifacts.push(...result.artifacts);
620
+ task.actualTokens = (task.actualTokens ?? 0) + result.tokensUsed;
621
+ task.context = {
622
+ ...task.context,
623
+ lastOutput: result.output,
624
+ lastModel: result.model,
625
+ compliancePassed: result.compliance.passed,
626
+ complianceScore: result.compliance.score
627
+ };
628
+ if (!result.compliance.passed) {
629
+ task.context = {
630
+ ...task.context,
631
+ complianceViolations: result.compliance.violations
632
+ };
633
+ }
634
+ } else {
635
+ await new Promise((resolve) => setTimeout(resolve, 100));
636
+ }
637
+ }
638
+ }
639
+ // ─── Workflow Registry ─────────────────────────────────────────────────────
640
+ registerWorkflow(definition) {
641
+ if (!definition.id) throw new Error("Workflow must have an id");
642
+ this.workflows.set(definition.id, definition);
643
+ }
644
+ listWorkflows() {
645
+ return Array.from(this.workflows.values());
646
+ }
647
+ getWorkflow(id) {
648
+ return this.workflows.get(id);
649
+ }
650
+ /** Disable a workflow type (kill switch). Persists for the lifetime of this Orchestrator instance. */
651
+ disableWorkflow(workflowId) {
652
+ this.disabledWorkflows.add(workflowId);
653
+ this.config.bus?.broadcast("system", "system:workflow-disabled", { workflowId }, "high");
654
+ }
655
+ /** Re-enable a previously disabled workflow. */
656
+ enableWorkflow(workflowId) {
657
+ this.disabledWorkflows.delete(workflowId);
658
+ this.config.bus?.broadcast("system", "system:workflow-enabled", { workflowId }, "normal");
659
+ }
660
+ /** Returns the set of currently disabled workflow IDs. */
661
+ getDisabledWorkflows() {
662
+ return Array.from(this.disabledWorkflows);
663
+ }
664
+ async createTaskFromWorkflow(workflowId, input = {}) {
665
+ if (this.disabledWorkflows.has(workflowId)) {
666
+ throw new Error(`Workflow '${workflowId}' is disabled (kill switch active). Enable it via orchestrator.enableWorkflow('${workflowId}') before creating tasks.`);
667
+ }
668
+ const workflow = this.getWorkflow(workflowId);
669
+ if (!workflow) {
670
+ throw new Error(`Workflow not found: ${workflowId}`);
671
+ }
672
+ const task = await this.createTask({
673
+ title: input.title ?? workflow.name,
674
+ description: input.description ?? workflow.description ?? workflow.name,
675
+ priority: input.priority ?? "medium",
676
+ requiredCapabilities: input.requiredCapabilities ?? workflow.requiredCapabilities ?? [],
677
+ acceptanceCriteria: input.acceptanceCriteria ?? [],
678
+ context: {
679
+ ...input.context ?? {},
680
+ workflowId: workflow.id
681
+ }
682
+ });
683
+ task.workflowId = workflow.id;
684
+ const plan = this.createExecutionPlanForWorkflow(task, workflow);
685
+ task.workflowPlanId = plan.id;
686
+ this.plans.set(plan.id, plan);
687
+ if (workflow.budget?.maxAttempts) {
688
+ task.maxAttempts = workflow.budget.maxAttempts;
689
+ }
690
+ return task;
691
+ }
692
+ async executeWorkflow(workflowId, input = {}) {
693
+ const task = await this.createTaskFromWorkflow(workflowId, input);
694
+ return this.executeTask(task.id);
695
+ }
696
+ createExecutionPlanForWorkflow(task, workflow) {
697
+ const steps = workflow.steps.map((step) => ({
698
+ id: step.id,
699
+ title: step.title,
700
+ description: step.description,
701
+ action: step.action,
702
+ dependencies: [],
703
+ status: "pending",
704
+ verificationCriteria: step.verificationCriteria ?? []
705
+ }));
706
+ return {
707
+ id: `plan-${task.id}`,
708
+ taskId: task.id,
709
+ steps,
710
+ estimatedTokens: 0,
711
+ estimatedDuration: 0,
712
+ riskAssessment: [],
713
+ createdAt: Date.now()
714
+ };
715
+ }
716
+ getWorkflowBudget(task) {
717
+ if (task.workflowId) {
718
+ return this.workflows.get(task.workflowId)?.budget;
719
+ }
720
+ return void 0;
721
+ }
722
+ // ─── Checkpoint Persistence + Recovery ─────────────────────────────────────
723
+ persistCheckpoint(checkpoint) {
724
+ const base = this.config.checkpointsPath ?? ".baseline/checkpoints";
725
+ try {
726
+ if (!existsSync(base)) {
727
+ mkdirSync(base, { recursive: true });
728
+ }
729
+ const file = join(base, `${checkpoint.id}.json`);
730
+ writeFileSync(file, JSON.stringify(checkpoint, null, 2));
731
+ } catch (err) {
732
+ process.stderr.write(`[baseline:orchestrator] Checkpoint persist failed: ${err.message}
733
+ `);
734
+ }
735
+ }
736
+ loadCheckpoint(checkpointId) {
737
+ const base = this.config.checkpointsPath ?? ".baseline/checkpoints";
738
+ const file = join(base, `${checkpointId}.json`);
739
+ if (!existsSync(file)) return null;
740
+ try {
741
+ const raw = JSON.parse(readFileSync(file, "utf-8"));
742
+ return raw;
743
+ } catch (err) {
744
+ process.stderr.write(`[baseline:orchestrator] Checkpoint load failed for ${checkpointId}: ${err.message}
745
+ `);
746
+ return null;
747
+ }
748
+ }
749
+ recoverTaskFromCheckpoint(checkpointId) {
750
+ const checkpoint = this.loadCheckpoint(checkpointId);
751
+ if (!checkpoint) return null;
752
+ const existing = this.tasks.get(checkpoint.taskId);
753
+ if (existing) {
754
+ existing.context = { ...checkpoint.state };
755
+ existing.status = "pending";
756
+ existing.currentCheckpoint = checkpoint.id;
757
+ this.tasks.set(existing.id, existing);
758
+ return existing;
759
+ }
760
+ const recovered = {
761
+ id: checkpoint.taskId,
762
+ title: "Recovered Task",
763
+ description: checkpoint.description,
764
+ status: "pending",
765
+ priority: "medium",
766
+ complexity: "moderate",
767
+ subtasks: [],
768
+ dependencies: [],
769
+ requiredCapabilities: [],
770
+ attempts: 0,
771
+ maxAttempts: this.config.defaultMaxAttempts,
772
+ acceptanceCriteria: [],
773
+ verificationResults: [],
774
+ createdAt: Date.now(),
775
+ updatedAt: Date.now(),
776
+ context: { ...checkpoint.state },
777
+ artifacts: checkpoint.artifacts ?? [],
778
+ currentCheckpoint: checkpoint.id
779
+ };
780
+ this.tasks.set(recovered.id, recovered);
781
+ return recovered;
782
+ }
783
+ async selfVerify(task) {
784
+ return withSpan("task.self_verify", { "task.id": task.id }, async () => {
785
+ return this._selfVerify(task);
786
+ });
787
+ }
788
+ async _selfVerify(task) {
789
+ const engine = this.config.engine;
790
+ const output = task.context.lastOutput ?? "";
791
+ let result;
792
+ if (engine && output) {
793
+ result = await engine.selfVerify(task, output);
794
+ } else {
795
+ result = {
796
+ criterionId: "self-verify",
797
+ passed: true,
798
+ confidence: 0.85,
799
+ details: "Self-verification passed (mock)",
800
+ verifiedBy: task.assignedAgent || "orchestrator",
801
+ verifiedAt: Date.now()
802
+ };
803
+ }
804
+ task.verificationResults.push(result);
805
+ this.emit("task:verified", { type: "task:verified", task, result });
806
+ return result;
807
+ }
808
+ shouldTriggerSupervisorReview(task) {
809
+ const avgConfidence = task.verificationResults.length > 0 ? task.verificationResults.reduce((sum, r) => sum + r.confidence, 0) / task.verificationResults.length : 0.5;
810
+ if (avgConfidence < this.config.supervisorReviewThreshold) {
811
+ return true;
812
+ }
813
+ if (task.priority === "critical") {
814
+ return true;
815
+ }
816
+ return Math.random() < this.config.qualityReviewSampleRate;
817
+ }
818
+ async conductReview(task, reviewer) {
819
+ return withSpan("task.review", {
820
+ "task.id": task.id,
821
+ "reviewer.id": reviewer.id,
822
+ "reviewer.role": reviewer.role
823
+ }, async () => {
824
+ return this._conductReview(task, reviewer);
825
+ });
826
+ }
827
+ async _conductReview(task, reviewer) {
828
+ const engine = this.config.engine;
829
+ const output = task.context.lastOutput ?? "";
830
+ const reviewType = reviewer.role === "supervisor" ? "supervisor" : "quality";
831
+ if (engine && output) {
832
+ const review = await engine.conductReview(task, output, reviewType);
833
+ review.reviewerId = reviewer.id;
834
+ review.revieweeId = task.assignedAgent || "unknown";
835
+ return review;
836
+ }
837
+ return {
838
+ id: randomUUID(),
839
+ taskId: task.id,
840
+ reviewerId: reviewer.id,
841
+ revieweeId: task.assignedAgent || "unknown",
842
+ type: reviewType,
843
+ scope: "full",
844
+ findings: [],
845
+ overallAssessment: "approved",
846
+ confidence: 0.9,
847
+ feedback: "Review completed successfully (mock)",
848
+ suggestions: [],
849
+ requiredChanges: [],
850
+ createdAt: Date.now(),
851
+ completedAt: Date.now(),
852
+ timeSpent: 100
853
+ };
854
+ }
855
+ findSupervisor(_task) {
856
+ return Array.from(this.agents.values()).find(
857
+ (a) => a.role === "supervisor" && a.status === "idle"
858
+ );
859
+ }
860
+ findQualityAgent(_task) {
861
+ return Array.from(this.agents.values()).find(
862
+ (a) => a.role === "quality" && a.status === "idle"
863
+ );
864
+ }
865
+ getTask(taskId) {
866
+ return this.tasks.get(taskId);
867
+ }
868
+ /**
869
+ * Approve a blocked task, optionally recording who approved it.
870
+ * When BASELINE_APPROVAL_SECRET is set, approvalToken must match the
871
+ * HMAC token issued when the task was blocked (SIGNAL-017).
872
+ */
873
+ async approveTask(taskId, approvedBy = "human", approvalToken) {
874
+ const task = this.tasks.get(taskId);
875
+ if (!task) {
876
+ throw new Error(`Task not found: ${taskId}`);
877
+ }
878
+ if (task.status !== "blocked") {
879
+ throw new Error(`Task ${taskId} is not blocked (status: ${task.status})`);
880
+ }
881
+ const approvalSecret = process.env["BASELINE_APPROVAL_SECRET"];
882
+ if (approvalSecret) {
883
+ const storedToken = task.context.approvalToken;
884
+ const tokenExpires = task.context.approvalTokenExpires;
885
+ if (!approvalToken) {
886
+ throw new Error(`Approval token required \u2014 set BASELINE_APPROVAL_SECRET is active`);
887
+ }
888
+ if (!storedToken) {
889
+ throw new Error(`No approval token on file for task ${taskId}`);
890
+ }
891
+ if (tokenExpires && Date.now() > tokenExpires) {
892
+ throw new Error(`Approval token expired for task ${taskId}`);
893
+ }
894
+ const { timingSafeEqual } = await import('crypto');
895
+ const expected = Buffer.from(storedToken);
896
+ const received = Buffer.from(approvalToken);
897
+ if (expected.length !== received.length || !timingSafeEqual(expected, received)) {
898
+ throw new Error(`Invalid approval token for task ${taskId}`);
899
+ }
900
+ }
901
+ task.needsHumanReview = false;
902
+ task.status = "pending";
903
+ task.updatedAt = Date.now();
904
+ task.context = {
905
+ ...task.context,
906
+ approvedBy,
907
+ approvedAt: Date.now(),
908
+ finalApprovalGate: void 0,
909
+ approvalToken: void 0,
910
+ // one-time use — invalidate after approval
911
+ approvalTokenExpires: void 0
912
+ };
913
+ this.emit("task:approved", { type: "task:approved", task, approvedBy });
914
+ return this.executeTask(taskId);
915
+ }
916
+ cancelTask(taskId, reason = "Cancelled by user") {
917
+ const task = this.tasks.get(taskId);
918
+ if (!task) return false;
919
+ if (task.status === "completed" || task.status === "failed") {
920
+ return false;
921
+ }
922
+ task.status = "failed";
923
+ task.updatedAt = Date.now();
924
+ task.context = {
925
+ ...task.context,
926
+ cancelled: true,
927
+ cancelReason: reason
928
+ };
929
+ this.emit("task:failed", { type: "task:failed", task, error: reason });
930
+ return true;
931
+ }
932
+ getAllTasks() {
933
+ return Array.from(this.tasks.values());
934
+ }
935
+ // ─── Agent Management ──────────────────────────────────────────────────────
936
+ registerAgent(agent) {
937
+ const fullAgent = {
938
+ ...agent,
939
+ createdAt: Date.now(),
940
+ lastActiveAt: Date.now()
941
+ };
942
+ this.agents.set(fullAgent.id, fullAgent);
943
+ return fullAgent;
944
+ }
945
+ getAllAgents() {
946
+ return Array.from(this.agents.values());
947
+ }
948
+ /**
949
+ * Select the best available agent for a task based on trust score and capability match.
950
+ * Agents are ranked by: (capability overlap × trust score). Suspended agents are excluded.
951
+ */
952
+ selectBestAgent(task) {
953
+ const candidates = Array.from(this.agents.values()).filter((a) => a.status !== "suspended" && a.status !== "offline").filter((a) => {
954
+ if (task.requiredCapabilities.length === 0) return true;
955
+ return task.requiredCapabilities.some((cap) => a.capabilities.includes(cap));
956
+ });
957
+ if (candidates.length === 0) return void 0;
958
+ const scored = candidates.map((agent) => {
959
+ const capOverlap = task.requiredCapabilities.length > 0 ? task.requiredCapabilities.filter((cap) => agent.capabilities.includes(cap)).length / task.requiredCapabilities.length : 1;
960
+ return { agent, score: capOverlap * agent.trustScore };
961
+ });
962
+ scored.sort((a, b) => b.score - a.score);
963
+ return scored[0]?.agent;
964
+ }
965
+ /**
966
+ * Suspend an agent. Suspended agents are excluded from task selection.
967
+ */
968
+ suspendAgent(agentId, reason = "Trust score below minimum") {
969
+ const agent = this.agents.get(agentId);
970
+ if (!agent || agent.status === "suspended") return false;
971
+ agent.status = "suspended";
972
+ agent.lastActiveAt = Date.now();
973
+ this.emit("agent:suspended", {
974
+ type: "agent:suspended",
975
+ agent,
976
+ reason
977
+ });
978
+ return true;
979
+ }
980
+ /**
981
+ * Reinstate a suspended agent.
982
+ */
983
+ reinstateAgent(agentId) {
984
+ const agent = this.agents.get(agentId);
985
+ if (!agent || agent.status !== "suspended") return false;
986
+ agent.status = "idle";
987
+ agent.lastActiveAt = Date.now();
988
+ this.emit("agent:reinstated", {
989
+ type: "agent:reinstated",
990
+ agent
991
+ });
992
+ return true;
993
+ }
994
+ updateAgentTrust(agentId, success) {
995
+ const agent = this.agents.get(agentId);
996
+ if (!agent) return;
997
+ const previousTrust = agent.trustScore;
998
+ if (success) {
999
+ agent.trustScore += this.config.trustGrowthRate * (100 - agent.trustScore);
1000
+ agent.tasksCompleted++;
1001
+ } else {
1002
+ agent.trustScore -= this.config.trustDecayRate * agent.trustScore;
1003
+ agent.tasksFailed++;
1004
+ }
1005
+ agent.lastActiveAt = Date.now();
1006
+ this.emit("agent:trust-updated", {
1007
+ type: "agent:trust-updated",
1008
+ agent,
1009
+ previousTrust
1010
+ });
1011
+ if (agent.trustScore < this.config.trustMinimum && agent.status !== "suspended") {
1012
+ this.suspendAgent(agentId, `Trust score ${agent.trustScore.toFixed(1)} below minimum ${this.config.trustMinimum}`);
1013
+ }
1014
+ if (this.config.onTrustUpdate) {
1015
+ this.config.onTrustUpdate(agentId, agent.trustScore, success);
1016
+ }
1017
+ }
1018
+ // ─── Task Decomposition ────────────────────────────────────────────────────
1019
+ async decomposeTask(task) {
1020
+ task.status = "planning";
1021
+ const engine = this.config.engine;
1022
+ let phases;
1023
+ if (engine) {
1024
+ const decomposition = await engine.decompose(task);
1025
+ phases = decomposition.phases.length > 0 ? decomposition.phases : [
1026
+ { title: "research", description: `Research: ${task.title}`, capabilities: task.requiredCapabilities },
1027
+ { title: "implement", description: `Implement: ${task.title}`, capabilities: task.requiredCapabilities },
1028
+ { title: "verify", description: `Verify: ${task.title}`, capabilities: task.requiredCapabilities }
1029
+ ];
1030
+ } else {
1031
+ phases = ["research", "design", "implement", "verify", "document"].map((p) => ({
1032
+ title: p,
1033
+ description: `${p} phase for: ${task.description}`,
1034
+ capabilities: task.requiredCapabilities
1035
+ }));
1036
+ }
1037
+ for (let i = 0; i < phases.length; i++) {
1038
+ const phase = phases[i];
1039
+ const subtask = {
1040
+ id: randomUUID(),
1041
+ parentId: task.id,
1042
+ title: `${phase.title}: ${task.title}`,
1043
+ description: phase.description,
1044
+ status: "pending",
1045
+ priority: task.priority,
1046
+ complexity: "simple",
1047
+ subtasks: [],
1048
+ dependencies: i > 0 ? [task.subtasks[i - 1].id] : [],
1049
+ requiredCapabilities: phase.capabilities.length > 0 ? phase.capabilities : task.requiredCapabilities,
1050
+ attempts: 0,
1051
+ maxAttempts: this.config.defaultMaxAttempts,
1052
+ acceptanceCriteria: [],
1053
+ verificationResults: [],
1054
+ createdAt: Date.now(),
1055
+ updatedAt: Date.now(),
1056
+ context: { ...task.context, phase: phase.title },
1057
+ artifacts: []
1058
+ };
1059
+ task.subtasks.push(subtask);
1060
+ this.tasks.set(subtask.id, subtask);
1061
+ }
1062
+ task.status = "pending";
1063
+ this.emit("task:decomposed", { type: "task:decomposed", task, subtasks: task.subtasks });
1064
+ }
1065
+ // ─── Helper Methods ────────────────────────────────────────────────────────
1066
+ assessComplexity(input) {
1067
+ const descLength = input.description.length;
1068
+ const capCount = input.requiredCapabilities.length;
1069
+ const criteriaCount = input.acceptanceCriteria.length;
1070
+ const score = descLength / 100 + capCount * 2 + criteriaCount * 1.5;
1071
+ if (score < 5) return "trivial";
1072
+ if (score < 15) return "simple";
1073
+ if (score < 30) return "moderate";
1074
+ if (score < 50) return "complex";
1075
+ return "epic";
1076
+ }
1077
+ shouldDecompose(task) {
1078
+ return task.complexity === "complex" || task.complexity === "epic";
1079
+ }
1080
+ requiresHumanApproval(task) {
1081
+ const maxIndex = complexityToNumber(this.config.maxAutonomousComplexity);
1082
+ const taskIndex = complexityToNumber(task.complexity);
1083
+ return taskIndex > maxIndex;
1084
+ }
1085
+ // ─── Audit + Evidence ──────────────────────────────────────────────────────
1086
+ attachAuditListeners() {
1087
+ const events = [
1088
+ "task:created",
1089
+ "task:decomposed",
1090
+ "task:assigned",
1091
+ "task:started",
1092
+ "task:checkpoint",
1093
+ "task:verified",
1094
+ "task:review-requested",
1095
+ "task:reviewed",
1096
+ "task:correction-needed",
1097
+ "task:corrected",
1098
+ "task:completed",
1099
+ "task:failed",
1100
+ "task:blocked",
1101
+ "task:approved",
1102
+ "agent:status-changed",
1103
+ "agent:trust-updated",
1104
+ "escalation"
1105
+ ];
1106
+ for (const type of events) {
1107
+ this.on(type, (event) => {
1108
+ this.recordAudit(event);
1109
+ this.publishToBus(event);
1110
+ });
1111
+ }
1112
+ }
1113
+ publishToBus(event) {
1114
+ const bus = this.config.bus;
1115
+ if (!bus) return;
1116
+ const category = event.type.startsWith("task:") ? "task" : event.type.startsWith("agent:") ? "agent" : "system";
1117
+ const agentId = "agent" in event ? event.agent.id : "task" in event && event.task.assignedAgent ? event.task.assignedAgent : void 0;
1118
+ bus.publish({
1119
+ from: agentId ?? "system",
1120
+ topic: event.type,
1121
+ category,
1122
+ payload: event,
1123
+ priority: event.type === "escalation" || event.type === "task:failed" ? "high" : "normal"
1124
+ });
1125
+ }
1126
+ recordAudit(event) {
1127
+ const timestamp = Date.now();
1128
+ this.auditGlobal.push({ timestamp, event });
1129
+ const taskId = this.getEventTaskId(event);
1130
+ if (taskId) {
1131
+ const list = this.auditByTask.get(taskId) ?? [];
1132
+ list.push({ timestamp, event });
1133
+ this.auditByTask.set(taskId, list);
1134
+ }
1135
+ this.auditLog?.append(event);
1136
+ }
1137
+ getEventTaskId(event) {
1138
+ if ("task" in event) {
1139
+ return event.task.id;
1140
+ }
1141
+ return void 0;
1142
+ }
1143
+ getAuditTrail(taskId) {
1144
+ if (taskId) {
1145
+ return this.auditByTask.get(taskId) ?? [];
1146
+ }
1147
+ return this.auditGlobal;
1148
+ }
1149
+ getEvidenceBundle(taskId) {
1150
+ const task = this.tasks.get(taskId);
1151
+ const workflow = task?.workflowId ? this.workflows.get(task.workflowId) : void 0;
1152
+ const plan = task?.workflowPlanId ? this.plans.get(task.workflowPlanId) : void 0;
1153
+ const checkpoints = Array.from(this.checkpoints.values()).filter((c) => c.taskId === taskId);
1154
+ const reviews = this.getReviews(taskId);
1155
+ const verificationResults = task?.verificationResults ?? [];
1156
+ const artifacts = task?.artifacts ?? [];
1157
+ const audit = this.getAuditTrail(taskId);
1158
+ return {
1159
+ task,
1160
+ workflow,
1161
+ plan,
1162
+ checkpoints,
1163
+ reviews,
1164
+ verificationResults,
1165
+ artifacts,
1166
+ audit
1167
+ };
1168
+ }
1169
+ // ─── Statistics ────────────────────────────────────────────────────────────
1170
+ getStats() {
1171
+ const tasksByStatus = {};
1172
+ for (const task of this.tasks.values()) {
1173
+ tasksByStatus[task.status] = (tasksByStatus[task.status] || 0) + 1;
1174
+ }
1175
+ const agentsByRole = {};
1176
+ for (const agent of this.agents.values()) {
1177
+ agentsByRole[agent.role] = (agentsByRole[agent.role] || 0) + 1;
1178
+ }
1179
+ return {
1180
+ tasks: { total: this.tasks.size, byStatus: tasksByStatus },
1181
+ agents: { total: this.agents.size, byRole: agentsByRole }
1182
+ };
1183
+ }
1184
+ // ─── Review Access ─────────────────────────────────────────────────────────
1185
+ getReviews(taskId) {
1186
+ const reviews = Array.from(this.reviews.values());
1187
+ if (taskId) {
1188
+ return reviews.filter((r) => r.taskId === taskId);
1189
+ }
1190
+ return reviews;
1191
+ }
1192
+ setFinalApprovalGate(task, gate, reason) {
1193
+ task.context = {
1194
+ ...task.context,
1195
+ finalApprovalGate: gate,
1196
+ ...reason ? { finalGateReason: reason } : {}
1197
+ };
1198
+ }
1199
+ getFinalApprovalGate(task) {
1200
+ const gate = task.context.finalApprovalGate;
1201
+ if (gate === "pending" || gate === "approved" || gate === "blocked") {
1202
+ return gate;
1203
+ }
1204
+ return void 0;
1205
+ }
1206
+ };
1207
+ /**
1208
+ * BaselineOS OpenTelemetry — SIGNAL-012
1209
+ *
1210
+ * Distributed tracing for the multi-agent orchestration layer.
1211
+ * Initializes the OTel Node SDK and exports a shared tracer.
1212
+ *
1213
+ * Spans emitted:
1214
+ * task.execute root span per executeTask() call
1215
+ * task.policy.pre OPA pre-execution gate
1216
+ * task.policy.post OPA post-execution gate
1217
+ * task.perform performExecution() step loop
1218
+ * task.self_verify selfVerify() LLM call
1219
+ * task.review conductReview() LLM call
1220
+ *
1221
+ * Trace context is propagated through AgentBus messages via the
1222
+ * optional `traceContext` field (W3C traceparent format).
1223
+ *
1224
+ * Configuration:
1225
+ * OTEL_EXPORTER_OTLP_ENDPOINT OTLP HTTP collector (default: http://localhost:4318)
1226
+ * OTEL_SERVICE_NAME service name override (default: baselineos)
1227
+ * BASELINE_OTEL_DISABLED set to '1' to disable entirely (e.g. in tests)
1228
+ *
1229
+ * @license Apache-2.0
1230
+ */
1231
+ /**
1232
+ * BaselineOS Core Types
1233
+ *
1234
+ * @license Apache-2.0
1235
+ */
1236
+ /**
1237
+ * PII Detector — SIGNAL-015
1238
+ *
1239
+ * Scans prompt text for personally identifiable information before it
1240
+ * reaches the LLM router. Supports two enforcement modes:
1241
+ *
1242
+ * redact — replaces detected values with [REDACTED:TYPE] (default)
1243
+ * block — throws PiiBlockedError, halting task execution
1244
+ *
1245
+ * Detected types:
1246
+ * email — RFC 5321 address pattern
1247
+ * phone — US/international format (10-digit minimum)
1248
+ * ssn — US Social Security Number (NNN-NN-NNNN)
1249
+ * credit-card — Visa, Mastercard, Amex, Discover
1250
+ * api-key — OpenAI sk-, Anthropic sk-ant-, GitHub ghp_, AWS AKIA
1251
+ * ip-address — IPv4 addresses
1252
+ *
1253
+ * Integration:
1254
+ * Called by Orchestrator._performExecution() before engine.execute().
1255
+ * Publishes 'governance:pii-detected' on the AgentBus when PII is found.
1256
+ * Sets pii.detected and pii.types on the active OTel span.
1257
+ *
1258
+ * @license Apache-2.0
1259
+ */
1260
+ /**
1261
+ * Task Queue — SIGNAL-018
1262
+ *
1263
+ * BullMQ-backed durable task queue. Provides:
1264
+ * - Priority-ordered execution (critical → high → medium → low)
1265
+ * - Automatic retry with exponential backoff on failure
1266
+ * - Concurrency limiting (configurable, default 5)
1267
+ * - Job deduplication by task ID
1268
+ * - Queue observability via Bull Dashboard / BullMQ Board
1269
+ *
1270
+ * The queue is optional. When not configured, Orchestrator falls back
1271
+ * to direct in-process execution (existing behaviour).
1272
+ *
1273
+ * Configuration:
1274
+ * BASELINE_REDIS_URL Redis connection URL (default: redis://localhost:6379)
1275
+ *
1276
+ * Self-hosted Redis:
1277
+ * docker compose -f docker/docker-compose.monitoring.yml up -d
1278
+ * → redis://localhost:6379
1279
+ *
1280
+ * @license Apache-2.0
1281
+ */
1282
+ /**
1283
+ * BaselineOS Orchestrator
1284
+ *
1285
+ * Autonomous multi-agent task execution with self-supervision.
1286
+ * Implements AMP/Ralph patterns for agent-to-agent oversight.
1287
+ *
1288
+ * Key Innovation: Tasks are decomposed, verified, reviewed, and corrected
1289
+ * by agents — not humans. Human intervention only when all autonomous
1290
+ * options are exhausted (<5% of tasks).
1291
+ *
1292
+ * @license Apache-2.0
1293
+ */
1294
+
1295
+ export { Orchestrator };
1296
+ //# sourceMappingURL=orchestrator.js.map
1297
+ //# sourceMappingURL=orchestrator.js.map