Skip to content

Commit 25a4d2c

Browse files
committed
refactor: migrate nested cluster support to TypeScript
1 parent 6c28feb commit 25a4d2c

6 files changed

Lines changed: 299 additions & 258 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ src/darwin-keychain-boundary.js
148148
src/guidance-topics.js
149149
src/input-helpers.js
150150
src/ledger-sequence.js
151+
src/message-bus-bridge.js
151152
src/message-buffer.js
152153
src/state-snapshot-normalization.js
153154
src/state-snapshot.js
@@ -176,4 +177,5 @@ src/providers/capabilities.js
176177
src/providers/google/index.js
177178
src/providers/openai/index.js
178179
src/providers/opencode/index.js
180+
src/schemas/sub-cluster.js
179181
src/template-validation/report-formatter.js

eslint.config.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ export default [
402402
'src/guidance-topics.js',
403403
'src/input-helpers.js',
404404
'src/ledger-sequence.js',
405+
'src/message-bus-bridge.js',
405406
'src/message-buffer.js',
406407
'src/state-snapshot-normalization.js',
407408
'src/state-snapshot.js',
@@ -430,6 +431,7 @@ export default [
430431
'src/providers/google/index.js',
431432
'src/providers/openai/index.js',
432433
'src/providers/opencode/index.js',
434+
'src/schemas/sub-cluster.js',
433435
'src/template-validation/report-formatter.js',
434436
],
435437
},
Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,58 @@
11
/**
2-
* MessageBusBridge - Bridges parent and child message buses
3-
*
4-
* Forwards specified topics between parent and child clusters while maintaining
5-
* isolation and preventing message loops.
6-
*
7-
* Features:
8-
* - Forward specified parent topics to child (contextStrategy.parentTopics)
9-
* - Forward child completion events to parent
10-
* - Namespace child topics to avoid collisions
11-
* - Prevent message loops via forwarding flags
2+
* Bridges parent and child message buses while preserving cluster isolation and
3+
* preventing forwarding loops.
124
*/
135

6+
interface BridgeMetadata extends Record<string, unknown> {
7+
forwarded?: unknown;
8+
}
9+
10+
interface BridgeMessage extends Record<string, unknown> {
11+
cluster_id: string;
12+
topic: string;
13+
metadata?: BridgeMetadata;
14+
}
15+
16+
interface BridgeMessageBus {
17+
subscribe(handler: (message: BridgeMessage) => void): () => void;
18+
publish(message: BridgeMessage): unknown;
19+
}
20+
21+
interface MessageBusBridgeConfig {
22+
parentClusterId: string;
23+
childClusterId: string;
24+
parentTopics?: unknown[];
25+
}
26+
27+
function parentTopicName(entry: unknown): unknown {
28+
if (typeof entry === 'string') return entry;
29+
if (typeof entry !== 'object' || entry === null) return undefined;
30+
return 'topic' in entry ? entry.topic : undefined;
31+
}
32+
33+
function isNonEmptyString(value: unknown): value is string {
34+
return typeof value === 'string' && value.length > 0;
35+
}
36+
1437
class MessageBusBridge {
15-
constructor(parentBus, childBus, config) {
38+
parentBus: BridgeMessageBus;
39+
childBus: BridgeMessageBus;
40+
config: MessageBusBridgeConfig;
41+
parentTopicNames: Set<string>;
42+
parentUnsubscribe: (() => void) | null;
43+
childUnsubscribe: (() => void) | null;
44+
active: boolean;
45+
46+
constructor(
47+
parentBus: BridgeMessageBus,
48+
childBus: BridgeMessageBus,
49+
config: MessageBusBridgeConfig
50+
) {
1651
this.parentBus = parentBus;
1752
this.childBus = childBus;
1853
this.config = config;
1954
this.parentTopicNames = new Set(
20-
(config.parentTopics || [])
21-
.map((entry) => (typeof entry === 'string' ? entry : entry?.topic))
22-
.filter((topic) => typeof topic === 'string' && topic.length > 0)
55+
(config.parentTopics || []).map(parentTopicName).filter(isNonEmptyString)
2356
);
2457

2558
this.parentUnsubscribe = null;
@@ -29,47 +62,33 @@ class MessageBusBridge {
2962
this._setupBridge();
3063
}
3164

32-
/**
33-
* Set up bidirectional message forwarding
34-
* @private
35-
*/
36-
_setupBridge() {
37-
// Forward specified parent topics to child
65+
_setupBridge(): void {
3866
if (this.parentTopicNames.size > 0) {
39-
this.parentUnsubscribe = this.parentBus.subscribe((message) => {
67+
this.parentUnsubscribe = this.parentBus.subscribe((message: BridgeMessage) => {
4068
this._forwardParentToChild(message);
4169
});
4270
}
4371

44-
// Forward child completion/failure events to parent
45-
this.childUnsubscribe = this.childBus.subscribe((message) => {
72+
this.childUnsubscribe = this.childBus.subscribe((message: BridgeMessage) => {
4673
this._forwardChildToParent(message);
4774
});
4875

4976
this.active = true;
5077
}
5178

52-
/**
53-
* Forward parent message to child cluster
54-
* @private
55-
*/
56-
_forwardParentToChild(message) {
57-
// Only forward messages from parent cluster
79+
_forwardParentToChild(message: BridgeMessage): void {
5880
if (message.cluster_id !== this.config.parentClusterId) {
5981
return;
6082
}
6183

62-
// Only forward topics specified in config
6384
if (!this.parentTopicNames.has(message.topic)) {
6485
return;
6586
}
6687

67-
// Skip already-forwarded messages (prevent loops)
6888
if (message.metadata?.forwarded) {
6989
return;
7090
}
7191

72-
// Forward to child with metadata flag
7392
this.childBus.publish({
7493
...message,
7594
cluster_id: this.config.childClusterId,
@@ -81,28 +100,20 @@ class MessageBusBridge {
81100
});
82101
}
83102

84-
/**
85-
* Forward child message to parent cluster
86-
* @private
87-
*/
88-
_forwardChildToParent(message) {
89-
// Only forward messages from child cluster
103+
_forwardChildToParent(message: BridgeMessage): void {
90104
if (message.cluster_id !== this.config.childClusterId) {
91105
return;
92106
}
93107

94-
// Only forward completion/failure events
95108
const forwardTopics = ['CLUSTER_COMPLETE', 'CLUSTER_FAILED', 'AGENT_ERROR'];
96109
if (!forwardTopics.includes(message.topic)) {
97110
return;
98111
}
99112

100-
// Skip already-forwarded messages (prevent loops)
101113
if (message.metadata?.forwarded) {
102114
return;
103115
}
104116

105-
// Forward to parent with namespaced topic and metadata flag
106117
this.parentBus.publish({
107118
...message,
108119
cluster_id: this.config.parentClusterId,
@@ -116,10 +127,7 @@ class MessageBusBridge {
116127
});
117128
}
118129

119-
/**
120-
* Close the bridge and stop forwarding
121-
*/
122-
close() {
130+
close(): void {
123131
if (this.parentUnsubscribe) {
124132
this.parentUnsubscribe();
125133
this.parentUnsubscribe = null;
@@ -133,12 +141,9 @@ class MessageBusBridge {
133141
this.active = false;
134142
}
135143

136-
/**
137-
* Check if bridge is active
138-
*/
139-
isActive() {
144+
isActive(): boolean {
140145
return this.active;
141146
}
142147
}
143148

144-
module.exports = MessageBusBridge;
149+
export = MessageBusBridge;

0 commit comments

Comments
 (0)