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,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaselineOS Agent Bus — SIGNAL-008
|
|
3
|
+
*
|
|
4
|
+
* Pub/sub messaging backbone for agent-to-agent communication.
|
|
5
|
+
* Eliminates orchestrator as a bottleneck for inter-agent coordination.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Typed topic namespacing (task:*, agent:*, trust:*, governance:*, system:*, direct:*)
|
|
9
|
+
* - Wildcard subscriptions (e.g. 'task:*')
|
|
10
|
+
* - Point-to-point direct messages
|
|
11
|
+
* - Request/reply with timeout
|
|
12
|
+
* - TTL-enforced message expiry
|
|
13
|
+
* - Bounded audit history (ring buffer)
|
|
14
|
+
* - Dead letter queue for undeliverable messages
|
|
15
|
+
*
|
|
16
|
+
* @license Apache-2.0
|
|
17
|
+
*/
|
|
18
|
+
type BusTopicCategory = 'task' | 'agent' | 'trust' | 'governance' | 'system' | 'direct';
|
|
19
|
+
type MessagePriority = 'low' | 'normal' | 'high' | 'critical';
|
|
20
|
+
interface BusMessage<T = unknown> {
|
|
21
|
+
/** Unique message ID */
|
|
22
|
+
id: string;
|
|
23
|
+
/**
|
|
24
|
+
* Correlation ID for request/reply pattern.
|
|
25
|
+
* Reply messages set this to the original request's ID.
|
|
26
|
+
*/
|
|
27
|
+
correlationId?: string;
|
|
28
|
+
/** Sender agent ID, or 'system' for bus-originated messages */
|
|
29
|
+
from: string;
|
|
30
|
+
/**
|
|
31
|
+
* Target agent ID for direct messages.
|
|
32
|
+
* Undefined means broadcast to all subscribers of the topic.
|
|
33
|
+
*/
|
|
34
|
+
to?: string;
|
|
35
|
+
/** Namespaced topic, e.g. 'task:completed', 'trust:updated' */
|
|
36
|
+
topic: string;
|
|
37
|
+
category: BusTopicCategory;
|
|
38
|
+
payload: T;
|
|
39
|
+
timestamp: number;
|
|
40
|
+
/** Absolute expiry timestamp (ms). Bus drops messages past this. */
|
|
41
|
+
ttl?: number;
|
|
42
|
+
/** Topic to send reply to (request/reply pattern) */
|
|
43
|
+
replyTo?: string;
|
|
44
|
+
priority: MessagePriority;
|
|
45
|
+
/**
|
|
46
|
+
* W3C traceparent header value for distributed trace propagation (SIGNAL-012).
|
|
47
|
+
* Set automatically by publish() when an active OTel span exists.
|
|
48
|
+
* Consumers can restore the trace context via injectTraceContext().
|
|
49
|
+
*/
|
|
50
|
+
traceContext?: string;
|
|
51
|
+
}
|
|
52
|
+
type MessageHandler<T = unknown> = (message: BusMessage<T>) => void | Promise<void>;
|
|
53
|
+
interface BusSubscription {
|
|
54
|
+
id: string;
|
|
55
|
+
topic: string;
|
|
56
|
+
agentId?: string;
|
|
57
|
+
}
|
|
58
|
+
interface BusStats {
|
|
59
|
+
messagesPublished: number;
|
|
60
|
+
messagesDelivered: number;
|
|
61
|
+
messagesDropped: number;
|
|
62
|
+
deadLetters: number;
|
|
63
|
+
activeSubscriptions: number;
|
|
64
|
+
}
|
|
65
|
+
declare class AgentBus {
|
|
66
|
+
private readonly emitter;
|
|
67
|
+
private readonly history;
|
|
68
|
+
private readonly deadLetters;
|
|
69
|
+
private stats;
|
|
70
|
+
private readonly subscriptions;
|
|
71
|
+
/**
|
|
72
|
+
* Subscribe to a topic or wildcard pattern.
|
|
73
|
+
*
|
|
74
|
+
* @param topic Exact topic ('task:completed') or prefix wildcard ('task:*')
|
|
75
|
+
* @param handler Called for each matching message
|
|
76
|
+
* @param agentId Optional: filter to messages addressed to this agent only
|
|
77
|
+
* @returns subscription handle (pass to unsubscribe)
|
|
78
|
+
*/
|
|
79
|
+
subscribe<T = unknown>(topic: string, handler: MessageHandler<T>, agentId?: string): BusSubscription;
|
|
80
|
+
/** Remove a subscription by its handle */
|
|
81
|
+
unsubscribe(subscription: BusSubscription): void;
|
|
82
|
+
/**
|
|
83
|
+
* Publish a message. Supports wildcard fan-out: a message on 'task:completed'
|
|
84
|
+
* is delivered to both 'task:completed' and 'task:*' subscribers.
|
|
85
|
+
*/
|
|
86
|
+
publish<T = unknown>(message: Omit<BusMessage<T>, 'id' | 'timestamp'>): BusMessage<T>;
|
|
87
|
+
/** Broadcast to all subscribers of a topic (no specific recipient) */
|
|
88
|
+
broadcast<T = unknown>(from: string, topic: string, payload: T, priority?: MessagePriority): BusMessage<T>;
|
|
89
|
+
/** Send a direct message to a specific agent */
|
|
90
|
+
direct<T = unknown>(from: string, to: string, topic: string, payload: T, priority?: MessagePriority): BusMessage<T>;
|
|
91
|
+
/**
|
|
92
|
+
* Request/reply: publishes a message and waits for a reply on a generated
|
|
93
|
+
* reply topic. Rejects if no reply arrives within `timeoutMs`.
|
|
94
|
+
*/
|
|
95
|
+
request<TReq = unknown, TReply = unknown>(from: string, to: string, topic: string, payload: TReq, timeoutMs?: number): Promise<BusMessage<TReply>>;
|
|
96
|
+
/** Reply to a request message */
|
|
97
|
+
reply<T = unknown>(from: string, originalMessage: BusMessage, payload: T): void;
|
|
98
|
+
/**
|
|
99
|
+
* Return message history, optionally filtered by topic prefix.
|
|
100
|
+
* Most recent messages first.
|
|
101
|
+
*/
|
|
102
|
+
getHistory(topicPrefix?: string, limit?: number): BusMessage[];
|
|
103
|
+
getDeadLetters(limit?: number): BusMessage[];
|
|
104
|
+
getStats(): Readonly<BusStats>;
|
|
105
|
+
private normalise;
|
|
106
|
+
private wildcardOf;
|
|
107
|
+
private categoryOf;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { AgentBus, type BusMessage, type BusStats, type BusSubscription, type BusTopicCategory, type MessageHandler, type MessagePriority };
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { EventEmitter } from 'eventemitter3';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import '@opentelemetry/sdk-node';
|
|
4
|
+
import '@opentelemetry/exporter-trace-otlp-http';
|
|
5
|
+
import '@opentelemetry/resources';
|
|
6
|
+
import '@opentelemetry/semantic-conventions';
|
|
7
|
+
import { trace, propagation, context } from '@opentelemetry/api';
|
|
8
|
+
|
|
9
|
+
// src/core/agent-bus.ts
|
|
10
|
+
new Proxy({}, {
|
|
11
|
+
get(_target, prop) {
|
|
12
|
+
return trace.getTracer("baselineos", "0.2.0-beta.1")[prop];
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
function extractTraceContext() {
|
|
16
|
+
const carrier = {};
|
|
17
|
+
propagation.inject(context.active(), carrier);
|
|
18
|
+
return carrier["traceparent"];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/core/agent-bus.ts
|
|
22
|
+
var HISTORY_LIMIT = 500;
|
|
23
|
+
var DEAD_LETTER_LIMIT = 100;
|
|
24
|
+
var AgentBus = class {
|
|
25
|
+
emitter = new EventEmitter();
|
|
26
|
+
history = [];
|
|
27
|
+
deadLetters = [];
|
|
28
|
+
stats = {
|
|
29
|
+
messagesPublished: 0,
|
|
30
|
+
messagesDelivered: 0,
|
|
31
|
+
messagesDropped: 0,
|
|
32
|
+
deadLetters: 0,
|
|
33
|
+
activeSubscriptions: 0
|
|
34
|
+
};
|
|
35
|
+
// Maps subscription ID → { topic, handler } for clean teardown
|
|
36
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
37
|
+
// ─── Subscribe ───────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to a topic or wildcard pattern.
|
|
40
|
+
*
|
|
41
|
+
* @param topic Exact topic ('task:completed') or prefix wildcard ('task:*')
|
|
42
|
+
* @param handler Called for each matching message
|
|
43
|
+
* @param agentId Optional: filter to messages addressed to this agent only
|
|
44
|
+
* @returns subscription handle (pass to unsubscribe)
|
|
45
|
+
*/
|
|
46
|
+
subscribe(topic, handler, agentId) {
|
|
47
|
+
const id = randomUUID();
|
|
48
|
+
const internalTopic = this.normalise(topic);
|
|
49
|
+
const wrapper = (msg) => {
|
|
50
|
+
if (agentId && msg.to && msg.to !== agentId) return;
|
|
51
|
+
handler(msg);
|
|
52
|
+
};
|
|
53
|
+
this.emitter.on(internalTopic, wrapper);
|
|
54
|
+
this.subscriptions.set(id, { topic: internalTopic, handler: wrapper });
|
|
55
|
+
this.stats.activeSubscriptions++;
|
|
56
|
+
return { id, topic: internalTopic, agentId };
|
|
57
|
+
}
|
|
58
|
+
/** Remove a subscription by its handle */
|
|
59
|
+
unsubscribe(subscription) {
|
|
60
|
+
const entry = this.subscriptions.get(subscription.id);
|
|
61
|
+
if (!entry) return;
|
|
62
|
+
this.emitter.off(entry.topic, entry.handler);
|
|
63
|
+
this.subscriptions.delete(subscription.id);
|
|
64
|
+
this.stats.activeSubscriptions = Math.max(0, this.stats.activeSubscriptions - 1);
|
|
65
|
+
}
|
|
66
|
+
// ─── Publish ─────────────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Publish a message. Supports wildcard fan-out: a message on 'task:completed'
|
|
69
|
+
* is delivered to both 'task:completed' and 'task:*' subscribers.
|
|
70
|
+
*/
|
|
71
|
+
publish(message) {
|
|
72
|
+
const traceContext = extractTraceContext();
|
|
73
|
+
const full = {
|
|
74
|
+
id: randomUUID(),
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
...traceContext ? { traceContext } : {},
|
|
77
|
+
...message
|
|
78
|
+
};
|
|
79
|
+
this.stats.messagesPublished++;
|
|
80
|
+
if (full.ttl !== void 0 && Date.now() > full.ttl) {
|
|
81
|
+
this.stats.messagesDropped++;
|
|
82
|
+
return full;
|
|
83
|
+
}
|
|
84
|
+
this.history.push(full);
|
|
85
|
+
if (this.history.length > HISTORY_LIMIT) this.history.shift();
|
|
86
|
+
const exactTopic = this.normalise(full.topic);
|
|
87
|
+
const wildcardTopic = this.wildcardOf(exactTopic);
|
|
88
|
+
const listenerCount = this.emitter.listenerCount(exactTopic) + this.emitter.listenerCount(wildcardTopic);
|
|
89
|
+
if (listenerCount === 0) {
|
|
90
|
+
this.deadLetters.push(full);
|
|
91
|
+
if (this.deadLetters.length > DEAD_LETTER_LIMIT) this.deadLetters.shift();
|
|
92
|
+
this.stats.deadLetters++;
|
|
93
|
+
} else {
|
|
94
|
+
this.emitter.emit(exactTopic, full);
|
|
95
|
+
if (wildcardTopic !== exactTopic) {
|
|
96
|
+
this.emitter.emit(wildcardTopic, full);
|
|
97
|
+
}
|
|
98
|
+
this.stats.messagesDelivered++;
|
|
99
|
+
}
|
|
100
|
+
return full;
|
|
101
|
+
}
|
|
102
|
+
// ─── Convenience helpers ─────────────────────────────────────────────────
|
|
103
|
+
/** Broadcast to all subscribers of a topic (no specific recipient) */
|
|
104
|
+
broadcast(from, topic, payload, priority = "normal") {
|
|
105
|
+
const category = this.categoryOf(topic);
|
|
106
|
+
return this.publish({ from, topic, category, payload, priority });
|
|
107
|
+
}
|
|
108
|
+
/** Send a direct message to a specific agent */
|
|
109
|
+
direct(from, to, topic, payload, priority = "normal") {
|
|
110
|
+
const directTopic = `direct:${to}`;
|
|
111
|
+
return this.publish({
|
|
112
|
+
from,
|
|
113
|
+
to,
|
|
114
|
+
topic: directTopic,
|
|
115
|
+
category: "direct",
|
|
116
|
+
payload: { originalTopic: topic, ...payload ?? {} },
|
|
117
|
+
priority
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Request/reply: publishes a message and waits for a reply on a generated
|
|
122
|
+
* reply topic. Rejects if no reply arrives within `timeoutMs`.
|
|
123
|
+
*/
|
|
124
|
+
request(from, to, topic, payload, timeoutMs = 5e3) {
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const replyTopic = `direct:reply:${randomUUID()}`;
|
|
127
|
+
let sub = null;
|
|
128
|
+
let timer = null;
|
|
129
|
+
const cleanup = () => {
|
|
130
|
+
if (sub) this.unsubscribe(sub);
|
|
131
|
+
if (timer) clearTimeout(timer);
|
|
132
|
+
};
|
|
133
|
+
sub = this.subscribe(replyTopic, (msg) => {
|
|
134
|
+
cleanup();
|
|
135
|
+
resolve(msg);
|
|
136
|
+
});
|
|
137
|
+
timer = setTimeout(() => {
|
|
138
|
+
cleanup();
|
|
139
|
+
reject(new Error(`AgentBus: request to ${to} on ${topic} timed out after ${timeoutMs}ms`));
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
this.publish({
|
|
142
|
+
from,
|
|
143
|
+
to,
|
|
144
|
+
topic: `direct:${to}`,
|
|
145
|
+
category: "direct",
|
|
146
|
+
payload: { originalTopic: topic, ...payload },
|
|
147
|
+
priority: "normal",
|
|
148
|
+
replyTo: replyTopic
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Reply to a request message */
|
|
153
|
+
reply(from, originalMessage, payload) {
|
|
154
|
+
if (!originalMessage.replyTo) return;
|
|
155
|
+
this.publish({
|
|
156
|
+
from,
|
|
157
|
+
to: originalMessage.from,
|
|
158
|
+
topic: originalMessage.replyTo,
|
|
159
|
+
category: "direct",
|
|
160
|
+
payload,
|
|
161
|
+
priority: originalMessage.priority,
|
|
162
|
+
correlationId: originalMessage.id
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// ─── Introspection ────────────────────────────────────────────────────────
|
|
166
|
+
/**
|
|
167
|
+
* Return message history, optionally filtered by topic prefix.
|
|
168
|
+
* Most recent messages first.
|
|
169
|
+
*/
|
|
170
|
+
getHistory(topicPrefix, limit = 50) {
|
|
171
|
+
let items = [...this.history].reverse();
|
|
172
|
+
if (topicPrefix) {
|
|
173
|
+
items = items.filter((m) => m.topic.startsWith(topicPrefix));
|
|
174
|
+
}
|
|
175
|
+
return items.slice(0, limit);
|
|
176
|
+
}
|
|
177
|
+
getDeadLetters(limit = 20) {
|
|
178
|
+
return [...this.deadLetters].reverse().slice(0, limit);
|
|
179
|
+
}
|
|
180
|
+
getStats() {
|
|
181
|
+
return { ...this.stats };
|
|
182
|
+
}
|
|
183
|
+
// ─── Internals ────────────────────────────────────────────────────────────
|
|
184
|
+
normalise(topic) {
|
|
185
|
+
return topic.trim().toLowerCase();
|
|
186
|
+
}
|
|
187
|
+
wildcardOf(topic) {
|
|
188
|
+
const parts = topic.split(":");
|
|
189
|
+
if (parts.length < 2) return topic;
|
|
190
|
+
return `${parts[0]}:*`;
|
|
191
|
+
}
|
|
192
|
+
categoryOf(topic) {
|
|
193
|
+
const prefix = topic.split(":")[0];
|
|
194
|
+
const valid = ["task", "agent", "trust", "governance", "system", "direct"];
|
|
195
|
+
return valid.includes(prefix) ? prefix : "system";
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* BaselineOS OpenTelemetry — SIGNAL-012
|
|
200
|
+
*
|
|
201
|
+
* Distributed tracing for the multi-agent orchestration layer.
|
|
202
|
+
* Initializes the OTel Node SDK and exports a shared tracer.
|
|
203
|
+
*
|
|
204
|
+
* Spans emitted:
|
|
205
|
+
* task.execute root span per executeTask() call
|
|
206
|
+
* task.policy.pre OPA pre-execution gate
|
|
207
|
+
* task.policy.post OPA post-execution gate
|
|
208
|
+
* task.perform performExecution() step loop
|
|
209
|
+
* task.self_verify selfVerify() LLM call
|
|
210
|
+
* task.review conductReview() LLM call
|
|
211
|
+
*
|
|
212
|
+
* Trace context is propagated through AgentBus messages via the
|
|
213
|
+
* optional `traceContext` field (W3C traceparent format).
|
|
214
|
+
*
|
|
215
|
+
* Configuration:
|
|
216
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT OTLP HTTP collector (default: http://localhost:4318)
|
|
217
|
+
* OTEL_SERVICE_NAME service name override (default: baselineos)
|
|
218
|
+
* BASELINE_OTEL_DISABLED set to '1' to disable entirely (e.g. in tests)
|
|
219
|
+
*
|
|
220
|
+
* @license Apache-2.0
|
|
221
|
+
*/
|
|
222
|
+
/**
|
|
223
|
+
* BaselineOS Agent Bus — SIGNAL-008
|
|
224
|
+
*
|
|
225
|
+
* Pub/sub messaging backbone for agent-to-agent communication.
|
|
226
|
+
* Eliminates orchestrator as a bottleneck for inter-agent coordination.
|
|
227
|
+
*
|
|
228
|
+
* Features:
|
|
229
|
+
* - Typed topic namespacing (task:*, agent:*, trust:*, governance:*, system:*, direct:*)
|
|
230
|
+
* - Wildcard subscriptions (e.g. 'task:*')
|
|
231
|
+
* - Point-to-point direct messages
|
|
232
|
+
* - Request/reply with timeout
|
|
233
|
+
* - TTL-enforced message expiry
|
|
234
|
+
* - Bounded audit history (ring buffer)
|
|
235
|
+
* - Dead letter queue for undeliverable messages
|
|
236
|
+
*
|
|
237
|
+
* @license Apache-2.0
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
export { AgentBus };
|
|
241
|
+
//# sourceMappingURL=agent-bus.js.map
|
|
242
|
+
//# sourceMappingURL=agent-bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/telemetry.ts","../../src/core/agent-bus.ts"],"names":[],"mappings":";;;;;;;;;AA8E8B,IAAI,KAAA,CAAM,EAAC,EAAa;AAAA,EACpD,GAAA,CAAI,SAAS,IAAA,EAAM;AACjB,IAAA,OAAO,KAAA,CAAM,SAAA,CAAU,YAAA,EAAc,cAAc,EAAE,IAAoB,CAAA;AAAA,EAC3E;AACF,CAAC;AASM,SAAS,mBAAA,GAA0C;AACxD,EAAA,MAAM,UAAkC,EAAC;AACzC,EAAA,WAAA,CAAY,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAO,EAAG,OAAO,CAAA;AAC5C,EAAA,OAAO,QAAQ,aAAa,CAAA;AAC9B;;;ACRA,IAAM,aAAA,GAAgB,GAAA;AACtB,IAAM,iBAAA,GAAoB,GAAA;AAEnB,IAAM,WAAN,MAAe;AAAA,EACH,OAAA,GAAU,IAAI,YAAA,EAAa;AAAA,EAC3B,UAAwB,EAAC;AAAA,EACzB,cAA4B,EAAC;AAAA,EAEtC,KAAA,GAAkB;AAAA,IACxB,iBAAA,EAAmB,CAAA;AAAA,IACnB,iBAAA,EAAmB,CAAA;AAAA,IACnB,eAAA,EAAiB,CAAA;AAAA,IACjB,WAAA,EAAa,CAAA;AAAA,IACb,mBAAA,EAAqB;AAAA,GACvB;AAAA;AAAA,EAGiB,aAAA,uBAAoB,GAAA,EAGnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYF,SAAA,CACE,KAAA,EACA,OAAA,EACA,OAAA,EACiB;AACjB,IAAA,MAAM,KAAK,UAAA,EAAW;AACtB,IAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AAE1C,IAAA,MAAM,OAAA,GAA0B,CAAC,GAAA,KAAQ;AACvC,MAAA,IAAI,OAAA,IAAW,GAAA,CAAI,EAAA,IAAM,GAAA,CAAI,OAAO,OAAA,EAAS;AAC7C,MAAA,OAAA,CAAQ,GAAoB,CAAA;AAAA,IAC9B,CAAA;AAEA,IAAA,IAAA,CAAK,OAAA,CAAQ,EAAA,CAAG,aAAA,EAAe,OAAO,CAAA;AACtC,IAAA,IAAA,CAAK,aAAA,CAAc,IAAI,EAAA,EAAI,EAAE,OAAO,aAAA,EAAe,OAAA,EAAS,SAAS,CAAA;AACrE,IAAA,IAAA,CAAK,KAAA,CAAM,mBAAA,EAAA;AAEX,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,aAAA,EAAe,OAAA,EAAQ;AAAA,EAC7C;AAAA;AAAA,EAGA,YAAY,YAAA,EAAqC;AAC/C,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,aAAa,EAAE,CAAA;AACpD,IAAA,IAAI,CAAC,KAAA,EAAO;AACZ,IAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,KAAA,EAAO,MAAM,OAAO,CAAA;AAC3C,IAAA,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,YAAA,CAAa,EAAE,CAAA;AACzC,IAAA,IAAA,CAAK,KAAA,CAAM,sBAAsB,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,KAAA,CAAM,sBAAsB,CAAC,CAAA;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAqB,OAAA,EAAiE;AACpF,IAAA,MAAM,eAAe,mBAAA,EAAoB;AACzC,IAAA,MAAM,IAAA,GAAsB;AAAA,MAC1B,IAAI,UAAA,EAAW;AAAA,MACf,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,MACpB,GAAI,YAAA,GAAe,EAAE,YAAA,KAAiB,EAAC;AAAA,MACvC,GAAG;AAAA,KACL;AAEA,IAAA,IAAA,CAAK,KAAA,CAAM,iBAAA,EAAA;AAGX,IAAA,IAAI,KAAK,GAAA,KAAQ,MAAA,IAAa,KAAK,GAAA,EAAI,GAAI,KAAK,GAAA,EAAK;AACnD,MAAA,IAAA,CAAK,KAAA,CAAM,eAAA,EAAA;AACX,MAAA,OAAO,IAAA;AAAA,IACT;AAGA,IAAA,IAAA,CAAK,OAAA,CAAQ,KAAK,IAAkB,CAAA;AACpC,IAAA,IAAI,KAAK,OAAA,CAAQ,MAAA,GAAS,aAAA,EAAe,IAAA,CAAK,QAAQ,KAAA,EAAM;AAE5D,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,KAAK,CAAA;AAC5C,IAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,UAAA,CAAW,UAAU,CAAA;AAEhD,IAAA,MAAM,aAAA,GACJ,KAAK,OAAA,CAAQ,aAAA,CAAc,UAAU,CAAA,GACrC,IAAA,CAAK,OAAA,CAAQ,aAAA,CAAc,aAAa,CAAA;AAE1C,IAAA,IAAI,kBAAkB,CAAA,EAAG;AAEvB,MAAA,IAAA,CAAK,WAAA,CAAY,KAAK,IAAkB,CAAA;AACxC,MAAA,IAAI,KAAK,WAAA,CAAY,MAAA,GAAS,iBAAA,EAAmB,IAAA,CAAK,YAAY,KAAA,EAAM;AACxE,MAAA,IAAA,CAAK,KAAA,CAAM,WAAA,EAAA;AAAA,IACb,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,UAAA,EAAY,IAAI,CAAA;AAClC,MAAA,IAAI,kBAAkB,UAAA,EAAY;AAChC,QAAA,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,IAAI,CAAA;AAAA,MACvC;AACA,MAAA,IAAA,CAAK,KAAA,CAAM,iBAAA,EAAA;AAAA,IACb;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA,EAKA,SAAA,CACE,IAAA,EACA,KAAA,EACA,OAAA,EACA,WAA4B,QAAA,EACb;AACf,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,UAAA,CAAW,KAAK,CAAA;AACtC,IAAA,OAAO,IAAA,CAAK,QAAW,EAAE,IAAA,EAAM,OAAO,QAAA,EAAU,OAAA,EAAS,UAAU,CAAA;AAAA,EACrE;AAAA;AAAA,EAGA,OACE,IAAA,EACA,EAAA,EACA,KAAA,EACA,OAAA,EACA,WAA4B,QAAA,EACb;AACf,IAAA,MAAM,WAAA,GAAc,UAAU,EAAE,CAAA,CAAA;AAChC,IAAA,OAAO,KAAK,OAAA,CAAW;AAAA,MACrB,IAAA;AAAA,MACA,EAAA;AAAA,MACA,KAAA,EAAO,WAAA;AAAA,MACP,QAAA,EAAU,QAAA;AAAA,MACV,SAAS,EAAE,aAAA,EAAe,OAAO,GAAK,OAAA,IAAsB,EAAC,EAAG;AAAA,MAChE;AAAA,KACD,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QACE,IAAA,EACA,EAAA,EACA,KAAA,EACA,OAAA,EACA,YAAY,GAAA,EACiB;AAC7B,IAAA,OAAO,IAAI,OAAA,CAA4B,CAAC,OAAA,EAAS,MAAA,KAAW;AAC1D,MAAA,MAAM,UAAA,GAAa,CAAA,aAAA,EAAgB,UAAA,EAAY,CAAA,CAAA;AAC/C,MAAA,IAAI,GAAA,GAA8B,IAAA;AAClC,MAAA,IAAI,KAAA,GAA8C,IAAA;AAElD,MAAA,MAAM,UAAU,MAAM;AACpB,QAAA,IAAI,GAAA,EAAK,IAAA,CAAK,WAAA,CAAY,GAAG,CAAA;AAC7B,QAAA,IAAI,KAAA,eAAoB,KAAK,CAAA;AAAA,MAC/B,CAAA;AAEA,MAAA,GAAA,GAAM,IAAA,CAAK,SAAA,CAAkB,UAAA,EAAY,CAAC,GAAA,KAAQ;AAChD,QAAA,OAAA,EAAQ;AACR,QAAA,OAAA,CAAQ,GAAG,CAAA;AAAA,MACb,CAAC,CAAA;AAED,MAAA,KAAA,GAAQ,WAAW,MAAM;AACvB,QAAA,OAAA,EAAQ;AACR,QAAA,MAAA,CAAO,IAAI,MAAM,CAAA,qBAAA,EAAwB,EAAE,OAAO,KAAK,CAAA,iBAAA,EAAoB,SAAS,CAAA,EAAA,CAAI,CAAC,CAAA;AAAA,MAC3F,GAAG,SAAS,CAAA;AAIZ,MAAA,IAAA,CAAK,OAAA,CAAiC;AAAA,QACpC,IAAA;AAAA,QACA,EAAA;AAAA,QACA,KAAA,EAAO,UAAU,EAAE,CAAA,CAAA;AAAA,QACnB,QAAA,EAAU,QAAA;AAAA,QACV,OAAA,EAAS,EAAE,aAAA,EAAe,KAAA,EAAO,GAAI,OAAA,EAAmB;AAAA,QACxD,QAAA,EAAU,QAAA;AAAA,QACV,OAAA,EAAS;AAAA,OACV,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,KAAA,CACE,IAAA,EACA,eAAA,EACA,OAAA,EACM;AACN,IAAA,IAAI,CAAC,gBAAgB,OAAA,EAAS;AAC9B,IAAA,IAAA,CAAK,OAAA,CAAW;AAAA,MACd,IAAA;AAAA,MACA,IAAI,eAAA,CAAgB,IAAA;AAAA,MACpB,OAAO,eAAA,CAAgB,OAAA;AAAA,MACvB,QAAA,EAAU,QAAA;AAAA,MACV,OAAA;AAAA,MACA,UAAU,eAAA,CAAgB,QAAA;AAAA,MAC1B,eAAe,eAAA,CAAgB;AAAA,KAChC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAA,CAAW,WAAA,EAAsB,KAAA,GAAQ,EAAA,EAAkB;AACzD,IAAA,IAAI,QAAQ,CAAC,GAAG,IAAA,CAAK,OAAO,EAAE,OAAA,EAAQ;AACtC,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,KAAA,GAAQ,KAAA,CAAM,OAAO,CAAC,CAAA,KAAM,EAAE,KAAA,CAAM,UAAA,CAAW,WAAW,CAAC,CAAA;AAAA,IAC7D;AACA,IAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA;AAAA,EAC7B;AAAA,EAEA,cAAA,CAAe,QAAQ,EAAA,EAAkB;AACvC,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,WAAW,EAAE,OAAA,EAAQ,CAAE,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA;AAAA,EACvD;AAAA,EAEA,QAAA,GAA+B;AAC7B,IAAA,OAAO,EAAE,GAAG,IAAA,CAAK,KAAA,EAAM;AAAA,EACzB;AAAA;AAAA,EAIQ,UAAU,KAAA,EAAuB;AACvC,IAAA,OAAO,KAAA,CAAM,IAAA,EAAK,CAAE,WAAA,EAAY;AAAA,EAClC;AAAA,EAEQ,WAAW,KAAA,EAAuB;AACxC,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAA,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,OAAO,KAAA;AAC7B,IAAA,OAAO,CAAA,EAAG,KAAA,CAAM,CAAC,CAAC,CAAA,EAAA,CAAA;AAAA,EACpB;AAAA,EAEQ,WAAW,KAAA,EAAiC;AAClD,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AACjC,IAAA,MAAM,QAA4B,CAAC,MAAA,EAAQ,SAAS,OAAA,EAAS,YAAA,EAAc,UAAU,QAAQ,CAAA;AAC7F,IAAA,OAAO,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA,GAAI,MAAA,GAAS,QAAA;AAAA,EAC3C;AACF","file":"agent-bus.js","sourcesContent":["/**\n * BaselineOS OpenTelemetry — SIGNAL-012\n *\n * Distributed tracing for the multi-agent orchestration layer.\n * Initializes the OTel Node SDK and exports a shared tracer.\n *\n * Spans emitted:\n * task.execute root span per executeTask() call\n * task.policy.pre OPA pre-execution gate\n * task.policy.post OPA post-execution gate\n * task.perform performExecution() step loop\n * task.self_verify selfVerify() LLM call\n * task.review conductReview() LLM call\n *\n * Trace context is propagated through AgentBus messages via the\n * optional `traceContext` field (W3C traceparent format).\n *\n * Configuration:\n * OTEL_EXPORTER_OTLP_ENDPOINT OTLP HTTP collector (default: http://localhost:4318)\n * OTEL_SERVICE_NAME service name override (default: baselineos)\n * BASELINE_OTEL_DISABLED set to '1' to disable entirely (e.g. in tests)\n *\n * @license Apache-2.0\n */\n\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';\nimport { trace, context, propagation, SpanStatusCode, SpanKind } from '@opentelemetry/api';\nimport type { Span, Tracer, Context } from '@opentelemetry/api';\n\nexport { SpanStatusCode, SpanKind };\nexport type { Span, Tracer, Context };\n\n// ─── SDK init ────────────────────────────────────────────────────────────────\n\nlet _sdk: NodeSDK | null = null;\n\n/**\n * Initialize the OTel SDK. Safe to call multiple times — subsequent calls\n * are no-ops. Should be called once at process startup before any spans.\n *\n * Silently disabled when BASELINE_OTEL_DISABLED=1.\n */\nexport function initTelemetry(): void {\n if (_sdk || process.env['BASELINE_OTEL_DISABLED'] === '1') return;\n\n const endpoint =\n process.env['OTEL_EXPORTER_OTLP_ENDPOINT'] ?? 'http://localhost:4318';\n\n const exporter = new OTLPTraceExporter({\n url: `${endpoint}/v1/traces`,\n });\n\n _sdk = new NodeSDK({\n resource: resourceFromAttributes({\n [ATTR_SERVICE_NAME]: process.env['OTEL_SERVICE_NAME'] ?? 'baselineos',\n [ATTR_SERVICE_VERSION]: '0.2.0-beta.1',\n }),\n traceExporter: exporter,\n // W3C traceparent propagation is the default — no explicit config needed\n });\n\n _sdk.start();\n\n process.on('SIGTERM', () => { _sdk?.shutdown().catch(() => {}); });\n process.on('SIGINT', () => { _sdk?.shutdown().catch(() => {}); });\n}\n\n/** Gracefully shut down the SDK and flush pending spans. */\nexport async function shutdownTelemetry(): Promise<void> {\n await _sdk?.shutdown();\n _sdk = null;\n}\n\n// ─── Tracer ───────────────────────────────────────────────────────────────────\n\nexport const tracer: Tracer = new Proxy({} as Tracer, {\n get(_target, prop) {\n return trace.getTracer('baselineos', '0.2.0-beta.1')[prop as keyof Tracer];\n },\n});\n\n// ─── Trace context helpers ───────────────────────────────────────────────────\n\n/**\n * Serialize the active W3C trace context to a string for propagation\n * through AgentBus messages.\n * Returns undefined when there is no active span.\n */\nexport function extractTraceContext(): string | undefined {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n return carrier['traceparent'];\n}\n\n/**\n * Restore a serialized trace context as the active context.\n * Use as the second argument to `context.with()` before creating child spans.\n */\nexport function injectTraceContext(traceparent: string): Context {\n const carrier: Record<string, string> = { traceparent };\n return propagation.extract(context.active(), carrier);\n}\n\n// ─── Span wrapper ─────────────────────────────────────────────────────────────\n\n/**\n * Run an async function inside a named span.\n * Sets ERROR status and records exception on throw.\n */\nexport async function withSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span: Span) => Promise<T>,\n): Promise<T> {\n return tracer.startActiveSpan(name, { kind: SpanKind.INTERNAL, attributes }, async (span) => {\n try {\n const result = await fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message });\n throw err;\n } finally {\n span.end();\n }\n });\n}\n","/**\n * BaselineOS Agent Bus — SIGNAL-008\n *\n * Pub/sub messaging backbone for agent-to-agent communication.\n * Eliminates orchestrator as a bottleneck for inter-agent coordination.\n *\n * Features:\n * - Typed topic namespacing (task:*, agent:*, trust:*, governance:*, system:*, direct:*)\n * - Wildcard subscriptions (e.g. 'task:*')\n * - Point-to-point direct messages\n * - Request/reply with timeout\n * - TTL-enforced message expiry\n * - Bounded audit history (ring buffer)\n * - Dead letter queue for undeliverable messages\n *\n * @license Apache-2.0\n */\n\nimport { EventEmitter } from 'eventemitter3';\nimport { randomUUID } from 'crypto';\nimport { extractTraceContext } from './telemetry.js';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type BusTopicCategory =\n | 'task' // task lifecycle: task:created, task:completed, task:failed …\n | 'agent' // agent status: agent:status, agent:health, agent:capacity …\n | 'trust' // trust signals: trust:updated, trust:breach, trust:restored …\n | 'governance' // policy events: governance:policy, governance:violation …\n | 'system' // system-wide: system:shutdown, system:degraded …\n | 'direct'; // point-to-point: direct:<agentId>\n\nexport type MessagePriority = 'low' | 'normal' | 'high' | 'critical';\n\nexport interface BusMessage<T = unknown> {\n /** Unique message ID */\n id: string;\n /**\n * Correlation ID for request/reply pattern.\n * Reply messages set this to the original request's ID.\n */\n correlationId?: string;\n /** Sender agent ID, or 'system' for bus-originated messages */\n from: string;\n /**\n * Target agent ID for direct messages.\n * Undefined means broadcast to all subscribers of the topic.\n */\n to?: string;\n /** Namespaced topic, e.g. 'task:completed', 'trust:updated' */\n topic: string;\n category: BusTopicCategory;\n payload: T;\n timestamp: number;\n /** Absolute expiry timestamp (ms). Bus drops messages past this. */\n ttl?: number;\n /** Topic to send reply to (request/reply pattern) */\n replyTo?: string;\n priority: MessagePriority;\n /**\n * W3C traceparent header value for distributed trace propagation (SIGNAL-012).\n * Set automatically by publish() when an active OTel span exists.\n * Consumers can restore the trace context via injectTraceContext().\n */\n traceContext?: string;\n}\n\nexport type MessageHandler<T = unknown> = (\n message: BusMessage<T>,\n) => void | Promise<void>;\n\nexport interface BusSubscription {\n id: string;\n topic: string;\n agentId?: string;\n}\n\nexport interface BusStats {\n messagesPublished: number;\n messagesDelivered: number;\n messagesDropped: number;\n deadLetters: number;\n activeSubscriptions: number;\n}\n\n// ─── AgentBus ─────────────────────────────────────────────────────────────────\n\nconst HISTORY_LIMIT = 500;\nconst DEAD_LETTER_LIMIT = 100;\n\nexport class AgentBus {\n private readonly emitter = new EventEmitter();\n private readonly history: BusMessage[] = [];\n private readonly deadLetters: BusMessage[] = [];\n\n private stats: BusStats = {\n messagesPublished: 0,\n messagesDelivered: 0,\n messagesDropped: 0,\n deadLetters: 0,\n activeSubscriptions: 0,\n };\n\n // Maps subscription ID → { topic, handler } for clean teardown\n private readonly subscriptions = new Map<\n string,\n { topic: string; handler: MessageHandler }\n >();\n\n // ─── Subscribe ───────────────────────────────────────────────────────────\n\n /**\n * Subscribe to a topic or wildcard pattern.\n *\n * @param topic Exact topic ('task:completed') or prefix wildcard ('task:*')\n * @param handler Called for each matching message\n * @param agentId Optional: filter to messages addressed to this agent only\n * @returns subscription handle (pass to unsubscribe)\n */\n subscribe<T = unknown>(\n topic: string,\n handler: MessageHandler<T>,\n agentId?: string,\n ): BusSubscription {\n const id = randomUUID();\n const internalTopic = this.normalise(topic);\n\n const wrapper: MessageHandler = (msg) => {\n if (agentId && msg.to && msg.to !== agentId) return;\n handler(msg as BusMessage<T>);\n };\n\n this.emitter.on(internalTopic, wrapper);\n this.subscriptions.set(id, { topic: internalTopic, handler: wrapper });\n this.stats.activeSubscriptions++;\n\n return { id, topic: internalTopic, agentId };\n }\n\n /** Remove a subscription by its handle */\n unsubscribe(subscription: BusSubscription): void {\n const entry = this.subscriptions.get(subscription.id);\n if (!entry) return;\n this.emitter.off(entry.topic, entry.handler);\n this.subscriptions.delete(subscription.id);\n this.stats.activeSubscriptions = Math.max(0, this.stats.activeSubscriptions - 1);\n }\n\n // ─── Publish ─────────────────────────────────────────────────────────────\n\n /**\n * Publish a message. Supports wildcard fan-out: a message on 'task:completed'\n * is delivered to both 'task:completed' and 'task:*' subscribers.\n */\n publish<T = unknown>(message: Omit<BusMessage<T>, 'id' | 'timestamp'>): BusMessage<T> {\n const traceContext = extractTraceContext();\n const full: BusMessage<T> = {\n id: randomUUID(),\n timestamp: Date.now(),\n ...(traceContext ? { traceContext } : {}),\n ...message,\n } as BusMessage<T>;\n\n this.stats.messagesPublished++;\n\n // TTL check\n if (full.ttl !== undefined && Date.now() > full.ttl) {\n this.stats.messagesDropped++;\n return full;\n }\n\n // Audit ring buffer\n this.history.push(full as BusMessage);\n if (this.history.length > HISTORY_LIMIT) this.history.shift();\n\n const exactTopic = this.normalise(full.topic);\n const wildcardTopic = this.wildcardOf(exactTopic);\n\n const listenerCount =\n this.emitter.listenerCount(exactTopic) +\n this.emitter.listenerCount(wildcardTopic);\n\n if (listenerCount === 0) {\n // Dead letter: no subscribers\n this.deadLetters.push(full as BusMessage);\n if (this.deadLetters.length > DEAD_LETTER_LIMIT) this.deadLetters.shift();\n this.stats.deadLetters++;\n } else {\n this.emitter.emit(exactTopic, full);\n if (wildcardTopic !== exactTopic) {\n this.emitter.emit(wildcardTopic, full);\n }\n this.stats.messagesDelivered++;\n }\n\n return full;\n }\n\n // ─── Convenience helpers ─────────────────────────────────────────────────\n\n /** Broadcast to all subscribers of a topic (no specific recipient) */\n broadcast<T = unknown>(\n from: string,\n topic: string,\n payload: T,\n priority: MessagePriority = 'normal',\n ): BusMessage<T> {\n const category = this.categoryOf(topic);\n return this.publish<T>({ from, topic, category, payload, priority });\n }\n\n /** Send a direct message to a specific agent */\n direct<T = unknown>(\n from: string,\n to: string,\n topic: string,\n payload: T,\n priority: MessagePriority = 'normal',\n ): BusMessage<T> {\n const directTopic = `direct:${to}`;\n return this.publish<T>({\n from,\n to,\n topic: directTopic,\n category: 'direct',\n payload: { originalTopic: topic, ...((payload as object) ?? {}) } as T,\n priority,\n });\n }\n\n /**\n * Request/reply: publishes a message and waits for a reply on a generated\n * reply topic. Rejects if no reply arrives within `timeoutMs`.\n */\n request<TReq = unknown, TReply = unknown>(\n from: string,\n to: string,\n topic: string,\n payload: TReq,\n timeoutMs = 5000,\n ): Promise<BusMessage<TReply>> {\n return new Promise<BusMessage<TReply>>((resolve, reject) => {\n const replyTopic = `direct:reply:${randomUUID()}`;\n let sub: BusSubscription | null = null;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const cleanup = () => {\n if (sub) this.unsubscribe(sub);\n if (timer) clearTimeout(timer);\n };\n\n sub = this.subscribe<TReply>(replyTopic, (msg) => {\n cleanup();\n resolve(msg);\n });\n\n timer = setTimeout(() => {\n cleanup();\n reject(new Error(`AgentBus: request to ${to} on ${topic} timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Route through direct:${to} so the recipient can subscribe to their\n // own direct channel (same routing as the direct() helper).\n this.publish<Record<string, unknown>>({\n from,\n to,\n topic: `direct:${to}`,\n category: 'direct',\n payload: { originalTopic: topic, ...(payload as object) },\n priority: 'normal',\n replyTo: replyTopic,\n });\n });\n }\n\n /** Reply to a request message */\n reply<T = unknown>(\n from: string,\n originalMessage: BusMessage,\n payload: T,\n ): void {\n if (!originalMessage.replyTo) return;\n this.publish<T>({\n from,\n to: originalMessage.from,\n topic: originalMessage.replyTo,\n category: 'direct',\n payload,\n priority: originalMessage.priority,\n correlationId: originalMessage.id,\n });\n }\n\n // ─── Introspection ────────────────────────────────────────────────────────\n\n /**\n * Return message history, optionally filtered by topic prefix.\n * Most recent messages first.\n */\n getHistory(topicPrefix?: string, limit = 50): BusMessage[] {\n let items = [...this.history].reverse();\n if (topicPrefix) {\n items = items.filter((m) => m.topic.startsWith(topicPrefix));\n }\n return items.slice(0, limit);\n }\n\n getDeadLetters(limit = 20): BusMessage[] {\n return [...this.deadLetters].reverse().slice(0, limit);\n }\n\n getStats(): Readonly<BusStats> {\n return { ...this.stats };\n }\n\n // ─── Internals ────────────────────────────────────────────────────────────\n\n private normalise(topic: string): string {\n return topic.trim().toLowerCase();\n }\n\n private wildcardOf(topic: string): string {\n const parts = topic.split(':');\n if (parts.length < 2) return topic;\n return `${parts[0]}:*`;\n }\n\n private categoryOf(topic: string): BusTopicCategory {\n const prefix = topic.split(':')[0] as BusTopicCategory;\n const valid: BusTopicCategory[] = ['task', 'agent', 'trust', 'governance', 'system', 'direct'];\n return valid.includes(prefix) ? prefix : 'system';\n }\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaselineOS Semantic Cache
|
|
3
|
+
*
|
|
4
|
+
* Token-optimized caching with semantic similarity matching.
|
|
5
|
+
*
|
|
6
|
+
* Key Innovation: Similar queries hit cache even if not identical.
|
|
7
|
+
* "How do I authenticate?" matches "authentication process" at 92% similarity.
|
|
8
|
+
*
|
|
9
|
+
* @license Apache-2.0
|
|
10
|
+
*/
|
|
11
|
+
interface CacheConfig {
|
|
12
|
+
ttl: number;
|
|
13
|
+
maxSize: number;
|
|
14
|
+
similarityThreshold: number;
|
|
15
|
+
}
|
|
16
|
+
declare class SemanticCache {
|
|
17
|
+
private exactCache;
|
|
18
|
+
private semanticIndex;
|
|
19
|
+
private config;
|
|
20
|
+
private stats;
|
|
21
|
+
constructor(config?: Partial<CacheConfig>);
|
|
22
|
+
/**
|
|
23
|
+
* Get value from cache.
|
|
24
|
+
* Tries exact match first, then semantic similarity.
|
|
25
|
+
*/
|
|
26
|
+
get<T>(key: string): Promise<T | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Store value in cache.
|
|
29
|
+
*/
|
|
30
|
+
set<T>(key: string, value: T, options?: {
|
|
31
|
+
tokensSaved?: number;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Find semantically similar cached entry.
|
|
35
|
+
*/
|
|
36
|
+
private findSemanticMatch;
|
|
37
|
+
/**
|
|
38
|
+
* Check if key exists (exact match only).
|
|
39
|
+
*/
|
|
40
|
+
has(key: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Delete entry.
|
|
43
|
+
*/
|
|
44
|
+
delete(key: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Clear all entries.
|
|
47
|
+
*/
|
|
48
|
+
clear(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Get cache statistics.
|
|
51
|
+
*/
|
|
52
|
+
getStats(): {
|
|
53
|
+
size: number;
|
|
54
|
+
hits: number;
|
|
55
|
+
misses: number;
|
|
56
|
+
semanticHits: number;
|
|
57
|
+
hitRate: number;
|
|
58
|
+
tokensSaved: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Prune old entries.
|
|
62
|
+
*/
|
|
63
|
+
prune(): number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { type CacheConfig, SemanticCache };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { LRUCache } from 'lru-cache';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
|
|
4
|
+
// src/core/cache.ts
|
|
5
|
+
function hashKey(key) {
|
|
6
|
+
return createHash("sha256").update(key.toLowerCase().trim()).digest("hex").slice(0, 32);
|
|
7
|
+
}
|
|
8
|
+
function calculateSimilarity(a, b) {
|
|
9
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
10
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
11
|
+
if (wordsA.size === 0 || wordsB.size === 0) return 0;
|
|
12
|
+
let intersection = 0;
|
|
13
|
+
for (const word of wordsA) {
|
|
14
|
+
if (wordsB.has(word)) intersection++;
|
|
15
|
+
}
|
|
16
|
+
const union = wordsA.size + wordsB.size - intersection;
|
|
17
|
+
return intersection / union;
|
|
18
|
+
}
|
|
19
|
+
var SemanticCache = class {
|
|
20
|
+
exactCache;
|
|
21
|
+
semanticIndex;
|
|
22
|
+
config;
|
|
23
|
+
stats;
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.config = {
|
|
26
|
+
ttl: config.ttl ?? 864e5,
|
|
27
|
+
// 24 hours
|
|
28
|
+
maxSize: config.maxSize ?? 1e4,
|
|
29
|
+
similarityThreshold: config.similarityThreshold ?? 0.92
|
|
30
|
+
};
|
|
31
|
+
this.exactCache = new LRUCache({
|
|
32
|
+
max: this.config.maxSize,
|
|
33
|
+
ttl: this.config.ttl
|
|
34
|
+
});
|
|
35
|
+
this.semanticIndex = /* @__PURE__ */ new Map();
|
|
36
|
+
this.stats = {
|
|
37
|
+
hits: 0,
|
|
38
|
+
misses: 0,
|
|
39
|
+
semanticHits: 0,
|
|
40
|
+
tokensSaved: 0
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get value from cache.
|
|
45
|
+
* Tries exact match first, then semantic similarity.
|
|
46
|
+
*/
|
|
47
|
+
async get(key) {
|
|
48
|
+
const hash = hashKey(key);
|
|
49
|
+
const exactEntry = this.exactCache.get(hash);
|
|
50
|
+
if (exactEntry) {
|
|
51
|
+
exactEntry.hits++;
|
|
52
|
+
this.stats.hits++;
|
|
53
|
+
this.stats.tokensSaved += exactEntry.tokensSaved;
|
|
54
|
+
return exactEntry.value;
|
|
55
|
+
}
|
|
56
|
+
const semanticMatch = this.findSemanticMatch(key);
|
|
57
|
+
if (semanticMatch) {
|
|
58
|
+
semanticMatch.hits++;
|
|
59
|
+
this.stats.hits++;
|
|
60
|
+
this.stats.semanticHits++;
|
|
61
|
+
this.stats.tokensSaved += semanticMatch.tokensSaved;
|
|
62
|
+
return semanticMatch.value;
|
|
63
|
+
}
|
|
64
|
+
this.stats.misses++;
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Store value in cache.
|
|
69
|
+
*/
|
|
70
|
+
async set(key, value, options) {
|
|
71
|
+
const hash = hashKey(key);
|
|
72
|
+
const entry = {
|
|
73
|
+
key,
|
|
74
|
+
value,
|
|
75
|
+
createdAt: Date.now(),
|
|
76
|
+
hits: 0,
|
|
77
|
+
tokensSaved: options?.tokensSaved ?? 0
|
|
78
|
+
};
|
|
79
|
+
this.exactCache.set(hash, entry);
|
|
80
|
+
this.semanticIndex.set(hash, entry);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Find semantically similar cached entry.
|
|
84
|
+
*/
|
|
85
|
+
findSemanticMatch(query) {
|
|
86
|
+
let bestMatch = null;
|
|
87
|
+
let bestScore = 0;
|
|
88
|
+
for (const entry of this.semanticIndex.values()) {
|
|
89
|
+
const similarity = calculateSimilarity(query, entry.key);
|
|
90
|
+
if (similarity >= this.config.similarityThreshold && similarity > bestScore) {
|
|
91
|
+
bestScore = similarity;
|
|
92
|
+
bestMatch = entry;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return bestMatch;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check if key exists (exact match only).
|
|
99
|
+
*/
|
|
100
|
+
has(key) {
|
|
101
|
+
return this.exactCache.has(hashKey(key));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Delete entry.
|
|
105
|
+
*/
|
|
106
|
+
delete(key) {
|
|
107
|
+
const hash = hashKey(key);
|
|
108
|
+
this.semanticIndex.delete(hash);
|
|
109
|
+
return this.exactCache.delete(hash);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Clear all entries.
|
|
113
|
+
*/
|
|
114
|
+
clear() {
|
|
115
|
+
this.exactCache.clear();
|
|
116
|
+
this.semanticIndex.clear();
|
|
117
|
+
this.stats = { hits: 0, misses: 0, semanticHits: 0, tokensSaved: 0 };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get cache statistics.
|
|
121
|
+
*/
|
|
122
|
+
getStats() {
|
|
123
|
+
const total = this.stats.hits + this.stats.misses;
|
|
124
|
+
return {
|
|
125
|
+
size: this.exactCache.size,
|
|
126
|
+
hits: this.stats.hits,
|
|
127
|
+
misses: this.stats.misses,
|
|
128
|
+
semanticHits: this.stats.semanticHits,
|
|
129
|
+
hitRate: total > 0 ? this.stats.hits / total : 0,
|
|
130
|
+
tokensSaved: this.stats.tokensSaved
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Prune old entries.
|
|
135
|
+
*/
|
|
136
|
+
prune() {
|
|
137
|
+
let pruned = 0;
|
|
138
|
+
for (const [hash] of this.semanticIndex) {
|
|
139
|
+
if (!this.exactCache.has(hash)) {
|
|
140
|
+
this.semanticIndex.delete(hash);
|
|
141
|
+
pruned++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return pruned;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* BaselineOS Semantic Cache
|
|
149
|
+
*
|
|
150
|
+
* Token-optimized caching with semantic similarity matching.
|
|
151
|
+
*
|
|
152
|
+
* Key Innovation: Similar queries hit cache even if not identical.
|
|
153
|
+
* "How do I authenticate?" matches "authentication process" at 92% similarity.
|
|
154
|
+
*
|
|
155
|
+
* @license Apache-2.0
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
export { SemanticCache };
|
|
159
|
+
//# sourceMappingURL=cache.js.map
|
|
160
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/cache.ts"],"names":[],"mappings":"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","file":"cache.js","sourcesContent":["/**\n * BaselineOS Semantic Cache\n * \n * Token-optimized caching with semantic similarity matching.\n * \n * Key Innovation: Similar queries hit cache even if not identical.\n * \"How do I authenticate?\" matches \"authentication process\" at 92% similarity.\n * \n * @license Apache-2.0\n */\n\nimport { LRUCache } from 'lru-cache';\nimport { createHash } from 'crypto';\n\nexport interface CacheConfig {\n ttl: number;\n maxSize: number;\n similarityThreshold: number;\n}\n\ninterface CacheEntry<T = unknown> {\n key: string;\n value: T;\n embedding?: number[];\n createdAt: number;\n hits: number;\n tokensSaved: number;\n}\n\n// Simple hash for exact matching\nfunction hashKey(key: string): string {\n return createHash('sha256').update(key.toLowerCase().trim()).digest('hex').slice(0, 32);\n}\n\n// Simple word-based similarity (placeholder for embedding-based)\nfunction calculateSimilarity(a: string, b: string): number {\n const wordsA = new Set(a.toLowerCase().split(/\\s+/).filter(w => w.length > 2));\n const wordsB = new Set(b.toLowerCase().split(/\\s+/).filter(w => w.length > 2));\n \n if (wordsA.size === 0 || wordsB.size === 0) return 0;\n \n let intersection = 0;\n for (const word of wordsA) {\n if (wordsB.has(word)) intersection++;\n }\n \n const union = wordsA.size + wordsB.size - intersection;\n return intersection / union;\n}\n\nexport class SemanticCache {\n private exactCache: LRUCache<string, CacheEntry>;\n private semanticIndex: Map<string, CacheEntry>;\n private config: CacheConfig;\n private stats: {\n hits: number;\n misses: number;\n semanticHits: number;\n tokensSaved: number;\n };\n\n constructor(config: Partial<CacheConfig> = {}) {\n this.config = {\n ttl: config.ttl ?? 86400000, // 24 hours\n maxSize: config.maxSize ?? 10000,\n similarityThreshold: config.similarityThreshold ?? 0.92,\n };\n\n this.exactCache = new LRUCache<string, CacheEntry>({\n max: this.config.maxSize,\n ttl: this.config.ttl,\n });\n\n this.semanticIndex = new Map();\n\n this.stats = { \n hits: 0, \n misses: 0, \n semanticHits: 0,\n tokensSaved: 0,\n };\n }\n\n /**\n * Get value from cache.\n * Tries exact match first, then semantic similarity.\n */\n async get<T>(key: string): Promise<T | null> {\n const hash = hashKey(key);\n \n // Try exact match first\n const exactEntry = this.exactCache.get(hash);\n if (exactEntry) {\n exactEntry.hits++;\n this.stats.hits++;\n this.stats.tokensSaved += exactEntry.tokensSaved;\n return exactEntry.value as T;\n }\n\n // Try semantic match\n const semanticMatch = this.findSemanticMatch(key);\n if (semanticMatch) {\n semanticMatch.hits++;\n this.stats.hits++;\n this.stats.semanticHits++;\n this.stats.tokensSaved += semanticMatch.tokensSaved;\n return semanticMatch.value as T;\n }\n\n this.stats.misses++;\n return null;\n }\n\n /**\n * Store value in cache.\n */\n async set<T>(key: string, value: T, options?: { tokensSaved?: number }): Promise<void> {\n const hash = hashKey(key);\n \n const entry: CacheEntry<T> = {\n key,\n value,\n createdAt: Date.now(),\n hits: 0,\n tokensSaved: options?.tokensSaved ?? 0,\n };\n\n this.exactCache.set(hash, entry as CacheEntry);\n this.semanticIndex.set(hash, entry as CacheEntry);\n }\n\n /**\n * Find semantically similar cached entry.\n */\n private findSemanticMatch(query: string): CacheEntry | null {\n let bestMatch: CacheEntry | null = null;\n let bestScore = 0;\n\n for (const entry of this.semanticIndex.values()) {\n const similarity = calculateSimilarity(query, entry.key);\n \n if (similarity >= this.config.similarityThreshold && similarity > bestScore) {\n bestScore = similarity;\n bestMatch = entry;\n }\n }\n\n return bestMatch;\n }\n\n /**\n * Check if key exists (exact match only).\n */\n has(key: string): boolean {\n return this.exactCache.has(hashKey(key));\n }\n\n /**\n * Delete entry.\n */\n delete(key: string): boolean {\n const hash = hashKey(key);\n this.semanticIndex.delete(hash);\n return this.exactCache.delete(hash);\n }\n\n /**\n * Clear all entries.\n */\n clear(): void {\n this.exactCache.clear();\n this.semanticIndex.clear();\n this.stats = { hits: 0, misses: 0, semanticHits: 0, tokensSaved: 0 };\n }\n\n /**\n * Get cache statistics.\n */\n getStats() {\n const total = this.stats.hits + this.stats.misses;\n return {\n size: this.exactCache.size,\n hits: this.stats.hits,\n misses: this.stats.misses,\n semanticHits: this.stats.semanticHits,\n hitRate: total > 0 ? this.stats.hits / total : 0,\n tokensSaved: this.stats.tokensSaved,\n };\n }\n\n /**\n * Prune old entries.\n */\n prune(): number {\n // LRU cache handles TTL eviction automatically, but the semantic index\n // can drift out of sync. Prune stale entries from the semantic index.\n let pruned = 0;\n for (const [hash] of this.semanticIndex) {\n if (!this.exactCache.has(hash)) {\n this.semanticIndex.delete(hash);\n pruned++;\n }\n }\n return pruned;\n }\n}\n"]}
|