@covibes/zeroshot 5.4.0 → 5.4.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 (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
@@ -1,223 +0,0 @@
1
- /**
2
- * LogicEngine - JavaScript sandbox for agent decision logic
3
- *
4
- * Provides:
5
- * - Isolated VM for executing agent trigger logic
6
- * - Timeout enforcement (1 second)
7
- * - Ledger API access for queries
8
- * - Helper functions for common patterns
9
- * - Sandbox security (no fs, network, child_process)
10
- */
11
-
12
- const vm = require('vm');
13
-
14
- class LogicEngine {
15
- constructor(messageBus, cluster) {
16
- this.messageBus = messageBus;
17
- this.cluster = cluster;
18
- this.timeout = 1000; // 1 second
19
- }
20
-
21
- /**
22
- * Evaluate a trigger logic script
23
- * @param {String} script - JavaScript code to evaluate
24
- * @param {Object} agent - Agent context
25
- * @param {Object} message - Triggering message
26
- * @returns {Boolean} Whether agent should wake up
27
- */
28
- evaluate(script, agent, message) {
29
- try {
30
- // Build sandbox context
31
- const context = this._buildContext(agent, message);
32
-
33
- // Create isolated context with frozen prototypes
34
- // This prevents prototype pollution attacks
35
- const isolatedContext = {};
36
-
37
- // Freeze Object, Array, Function prototypes in the sandbox
38
- isolatedContext.Object = Object.freeze({ ...Object });
39
- isolatedContext.Array = Array;
40
- isolatedContext.Function = Function;
41
-
42
- // Copy safe context properties
43
- Object.assign(isolatedContext, context);
44
-
45
- // Wrap script to prevent prototype access
46
- const wrappedScript = `(function() {
47
- 'use strict';
48
- // Prevent prototype pollution
49
- const frozenObject = Object;
50
- const frozenArray = Array;
51
- Object.freeze(frozenObject.prototype);
52
- Object.freeze(frozenArray.prototype);
53
-
54
- ${script}
55
- })()`;
56
-
57
- // Create and run in context
58
- vm.createContext(isolatedContext);
59
- const result = vm.runInContext(wrappedScript, isolatedContext, {
60
- timeout: this.timeout,
61
- displayErrors: true,
62
- });
63
-
64
- // Coerce to boolean
65
- return Boolean(result);
66
- } catch (error) {
67
- console.error(`Logic evaluation error for agent ${agent.id}:`, error.message);
68
- return false; // Default to false on error
69
- }
70
- }
71
-
72
- /**
73
- * Build sandbox context with APIs and helpers
74
- * @private
75
- */
76
- _buildContext(agent, message) {
77
- const clusterId = agent.cluster_id;
78
-
79
- // Ledger API wrapper (auto-scoped to cluster)
80
- const ledgerAPI = {
81
- query: (criteria) => {
82
- return this.messageBus.query({ ...criteria, cluster_id: clusterId });
83
- },
84
-
85
- findLast: (criteria) => {
86
- return this.messageBus.findLast({ ...criteria, cluster_id: clusterId });
87
- },
88
-
89
- count: (criteria) => {
90
- return this.messageBus.count({ ...criteria, cluster_id: clusterId });
91
- },
92
-
93
- since: (timestamp) => {
94
- return this.messageBus.since({ cluster_id: clusterId, timestamp });
95
- },
96
- };
97
-
98
- // Helper functions
99
- const helpers = {
100
- /**
101
- * Check if all agents have responded to a topic since a timestamp
102
- */
103
- allResponded: (agents, topic, since) => {
104
- const responses = ledgerAPI.query({ topic, since });
105
- const responders = new Set(responses.map((r) => r.sender));
106
- return agents.every((a) => responders.has(a.id || a));
107
- },
108
-
109
- /**
110
- * Check if all responses have approved=true
111
- */
112
- hasConsensus: (topic, since) => {
113
- const responses = ledgerAPI.query({ topic, since });
114
- if (responses.length === 0) return false;
115
- return responses.every((r) => r.content?.data?.approved === true);
116
- },
117
-
118
- /**
119
- * Get time since last message on topic (in milliseconds)
120
- */
121
- timeSinceLastMessage: (topic) => {
122
- const last = ledgerAPI.findLast({ topic });
123
- if (!last) return Infinity;
124
- return Date.now() - last.timestamp;
125
- },
126
-
127
- /**
128
- * Check if a topic has any messages since timestamp
129
- */
130
- hasMessagesSince: (topic, since) => {
131
- const count = ledgerAPI.count({ topic, since });
132
- return count > 0;
133
- },
134
-
135
- /**
136
- * Get cluster config based on complexity and task type
137
- * Returns: { base: 'template-name', params: { ... } }
138
- */
139
- getConfig: require('./config-router').getConfig,
140
- };
141
-
142
- // Cluster API wrapper
143
- const clusterAPI = {
144
- id: clusterId,
145
-
146
- getAgents: () => {
147
- return this.cluster ? this.cluster.agents || [] : [];
148
- },
149
-
150
- getAgentsByRole: (role) => {
151
- return this.cluster ? (this.cluster.agents || []).filter((a) => a.role === role) : [];
152
- },
153
-
154
- getAgent: (id) => {
155
- return this.cluster ? (this.cluster.agents || []).find((a) => a.id === id) : null;
156
- },
157
- };
158
-
159
- // Build context
160
- return {
161
- // Agent context
162
- agent: {
163
- id: agent.id,
164
- role: agent.role,
165
- iteration: agent.iteration || 0,
166
- },
167
-
168
- // Triggering message
169
- message: message || null,
170
-
171
- // APIs
172
- ledger: ledgerAPI,
173
- cluster: clusterAPI,
174
- helpers,
175
-
176
- // Safe built-ins
177
- Set,
178
- Map,
179
- Array,
180
- Object,
181
- String,
182
- Number,
183
- Boolean,
184
- Math,
185
- Date,
186
- JSON,
187
-
188
- // No-op console (prevent output in production)
189
- console: {
190
- log: () => {},
191
- error: () => {},
192
- warn: () => {},
193
- info: () => {},
194
- },
195
- };
196
- }
197
-
198
- /**
199
- * Validate script syntax without executing
200
- * @param {String} script - JavaScript code
201
- * @returns {Object} { valid: Boolean, error: String }
202
- */
203
- validateScript(script) {
204
- try {
205
- // Wrap in function like evaluate() does
206
- const wrappedScript = `(function() { ${script} })()`;
207
- new vm.Script(wrappedScript);
208
- return { valid: true, error: null };
209
- } catch (error) {
210
- return { valid: false, error: error.message };
211
- }
212
- }
213
-
214
- /**
215
- * Set timeout for script execution
216
- * @param {Number} ms - Timeout in milliseconds
217
- */
218
- setTimeout(ms) {
219
- this.timeout = ms;
220
- }
221
- }
222
-
223
- module.exports = LogicEngine;
@@ -1,139 +0,0 @@
1
- /**
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
12
- */
13
-
14
- class MessageBusBridge {
15
- constructor(parentBus, childBus, config) {
16
- this.parentBus = parentBus;
17
- this.childBus = childBus;
18
- this.config = config;
19
-
20
- this.parentUnsubscribe = null;
21
- this.childUnsubscribe = null;
22
- this.active = false;
23
-
24
- this._setupBridge();
25
- }
26
-
27
- /**
28
- * Set up bidirectional message forwarding
29
- * @private
30
- */
31
- _setupBridge() {
32
- // Forward specified parent topics to child
33
- if (this.config.parentTopics && this.config.parentTopics.length > 0) {
34
- this.parentUnsubscribe = this.parentBus.subscribe((message) => {
35
- this._forwardParentToChild(message);
36
- });
37
- }
38
-
39
- // Forward child completion/failure events to parent
40
- this.childUnsubscribe = this.childBus.subscribe((message) => {
41
- this._forwardChildToParent(message);
42
- });
43
-
44
- this.active = true;
45
- }
46
-
47
- /**
48
- * Forward parent message to child cluster
49
- * @private
50
- */
51
- _forwardParentToChild(message) {
52
- // Only forward messages from parent cluster
53
- if (message.cluster_id !== this.config.parentClusterId) {
54
- return;
55
- }
56
-
57
- // Only forward topics specified in config
58
- if (!this.config.parentTopics.includes(message.topic)) {
59
- return;
60
- }
61
-
62
- // Skip already-forwarded messages (prevent loops)
63
- if (message.metadata?.forwarded) {
64
- return;
65
- }
66
-
67
- // Forward to child with metadata flag
68
- this.childBus.publish({
69
- ...message,
70
- cluster_id: this.config.childClusterId,
71
- metadata: {
72
- ...message.metadata,
73
- forwarded: true,
74
- forwardedFrom: this.config.parentClusterId,
75
- },
76
- });
77
- }
78
-
79
- /**
80
- * Forward child message to parent cluster
81
- * @private
82
- */
83
- _forwardChildToParent(message) {
84
- // Only forward messages from child cluster
85
- if (message.cluster_id !== this.config.childClusterId) {
86
- return;
87
- }
88
-
89
- // Only forward completion/failure events
90
- const forwardTopics = ['CLUSTER_COMPLETE', 'CLUSTER_FAILED', 'AGENT_ERROR'];
91
- if (!forwardTopics.includes(message.topic)) {
92
- return;
93
- }
94
-
95
- // Skip already-forwarded messages (prevent loops)
96
- if (message.metadata?.forwarded) {
97
- return;
98
- }
99
-
100
- // Forward to parent with namespaced topic and metadata flag
101
- this.parentBus.publish({
102
- ...message,
103
- cluster_id: this.config.parentClusterId,
104
- topic: `CHILD_${message.topic}`,
105
- metadata: {
106
- ...message.metadata,
107
- forwarded: true,
108
- forwardedFrom: this.config.childClusterId,
109
- childClusterId: this.config.childClusterId,
110
- },
111
- });
112
- }
113
-
114
- /**
115
- * Close the bridge and stop forwarding
116
- */
117
- close() {
118
- if (this.parentUnsubscribe) {
119
- this.parentUnsubscribe();
120
- this.parentUnsubscribe = null;
121
- }
122
-
123
- if (this.childUnsubscribe) {
124
- this.childUnsubscribe();
125
- this.childUnsubscribe = null;
126
- }
127
-
128
- this.active = false;
129
- }
130
-
131
- /**
132
- * Check if bridge is active
133
- */
134
- isActive() {
135
- return this.active;
136
- }
137
- }
138
-
139
- module.exports = MessageBusBridge;
@@ -1,249 +0,0 @@
1
- /**
2
- * MessageBus - Pub/sub layer over Ledger with WebSocket support
3
- *
4
- * Provides:
5
- * - High-level publish/subscribe API
6
- * - WebSocket broadcasting for UI clients
7
- * - Topic-based routing
8
- * - Real-time event distribution
9
- */
10
-
11
- const EventEmitter = require('events');
12
- const Ledger = require('./ledger');
13
-
14
- // Expected listeners per cluster:
15
- // - 5 orchestrator subscriptions (CLUSTER_COMPLETE, CLUSTER_FAILED, AGENT_ERROR, AGENT_LIFECYCLE, CLUSTER_OPERATIONS)
16
- // - 1 ledger internal
17
- // - 1 per agent (can be 10+ with dynamic spawning)
18
- // - topic-specific listeners
19
- const MAX_LISTENERS = 50;
20
-
21
- class MessageBus extends EventEmitter {
22
- constructor(ledger) {
23
- super();
24
- this.setMaxListeners(MAX_LISTENERS);
25
- this.ledger = ledger || new Ledger();
26
- this.wsClients = new Set();
27
-
28
- // Forward ledger events
29
- this.ledger.on('message', (message) => {
30
- this.emit('message', message);
31
- this._broadcastToWebSocket(message);
32
- });
33
- }
34
-
35
- /**
36
- * Publish a message to the ledger
37
- * @param {Object} message - Message to publish
38
- * @returns {Object} Published message with ID
39
- */
40
- publish(message) {
41
- if (!message.cluster_id) {
42
- throw new Error('cluster_id is required');
43
- }
44
-
45
- if (!message.topic) {
46
- throw new Error('topic is required');
47
- }
48
-
49
- if (!message.sender) {
50
- throw new Error('sender is required');
51
- }
52
-
53
- const published = this.ledger.append(message);
54
-
55
- // If ledger is closed, append returns null - skip emission
56
- if (!published) {
57
- return null;
58
- }
59
-
60
- // Emit to topic-specific listeners
61
- this.emit(`topic:${message.topic}`, published);
62
-
63
- return published;
64
- }
65
-
66
- /**
67
- * Subscribe to all messages
68
- * @param {Function} callback - Called with each message
69
- * @returns {Function} Unsubscribe function
70
- */
71
- subscribe(callback) {
72
- this.on('message', callback);
73
- return () => this.off('message', callback);
74
- }
75
-
76
- /**
77
- * Subscribe to specific topic
78
- * @param {String} topic - Topic to subscribe to
79
- * @param {Function} callback - Called with matching messages
80
- * @returns {Function} Unsubscribe function
81
- */
82
- subscribeTopic(topic, callback) {
83
- const event = `topic:${topic}`;
84
- this.on(event, callback);
85
- return () => this.off(event, callback);
86
- }
87
-
88
- /**
89
- * Subscribe to multiple topics
90
- * @param {Array<String>} topics - Topics to subscribe to
91
- * @param {Function} callback - Called with matching messages
92
- * @returns {Function} Unsubscribe function
93
- */
94
- subscribeTopics(topics, callback) {
95
- const unsubscribers = topics.map((topic) => this.subscribeTopic(topic, callback));
96
- return () => unsubscribers.forEach((unsub) => unsub());
97
- }
98
-
99
- /**
100
- * Query messages (passthrough to ledger)
101
- */
102
- query(criteria) {
103
- return this.ledger.query(criteria);
104
- }
105
-
106
- /**
107
- * Find last message (passthrough to ledger)
108
- */
109
- findLast(criteria) {
110
- return this.ledger.findLast(criteria);
111
- }
112
-
113
- /**
114
- * Count messages (passthrough to ledger)
115
- */
116
- count(criteria) {
117
- return this.ledger.count(criteria);
118
- }
119
-
120
- /**
121
- * Get messages since timestamp (passthrough to ledger)
122
- */
123
- since(params) {
124
- return this.ledger.since(params);
125
- }
126
-
127
- /**
128
- * Get all messages (passthrough to ledger)
129
- */
130
- getAll(cluster_id) {
131
- return this.ledger.getAll(cluster_id);
132
- }
133
-
134
- /**
135
- * Publish multiple messages atomically
136
- * All messages are committed in a single transaction with contiguous timestamps.
137
- * Use this for task completion to prevent message interleaving between agents.
138
- *
139
- * @param {Array<Object>} messages - Array of messages to publish
140
- * @returns {Array<Object>} Published messages with IDs
141
- */
142
- batchPublish(messages) {
143
- // Validate all messages
144
- for (const message of messages) {
145
- if (!message.cluster_id) {
146
- throw new Error('cluster_id is required for all messages');
147
- }
148
- if (!message.topic) {
149
- throw new Error('topic is required for all messages');
150
- }
151
- if (!message.sender) {
152
- throw new Error('sender is required for all messages');
153
- }
154
- }
155
-
156
- // Delegate to ledger's atomic batchAppend
157
- const published = this.ledger.batchAppend(messages);
158
-
159
- // Emit to topic-specific listeners for each message
160
- for (const msg of published) {
161
- this.emit(`topic:${msg.topic}`, msg);
162
- }
163
-
164
- return published;
165
- }
166
-
167
- /**
168
- * Get aggregated token usage by role (passthrough to ledger)
169
- * @param {String} cluster_id - Cluster ID
170
- * @returns {Object} Token usage aggregated by role with _total key
171
- */
172
- getTokensByRole(cluster_id) {
173
- return this.ledger.getTokensByRole(cluster_id);
174
- }
175
-
176
- /**
177
- * Register a WebSocket client for broadcasts
178
- * @param {WebSocket} ws - WebSocket connection
179
- */
180
- addWebSocketClient(ws) {
181
- this.wsClients.add(ws);
182
-
183
- ws.on('close', () => {
184
- this.wsClients.delete(ws);
185
- });
186
-
187
- ws.on('error', () => {
188
- this.wsClients.delete(ws);
189
- });
190
- }
191
-
192
- /**
193
- * Remove a WebSocket client
194
- * @param {WebSocket} ws - WebSocket connection
195
- */
196
- removeWebSocketClient(ws) {
197
- this.wsClients.delete(ws);
198
- }
199
-
200
- /**
201
- * Broadcast message to all WebSocket clients
202
- * @private
203
- */
204
- _broadcastToWebSocket(message) {
205
- const payload = JSON.stringify({
206
- type: 'message',
207
- data: message,
208
- });
209
-
210
- for (const ws of this.wsClients) {
211
- if (ws.readyState === 1) {
212
- // OPEN
213
- try {
214
- ws.send(payload);
215
- } catch (error) {
216
- console.error('WebSocket send error:', error);
217
- this.wsClients.delete(ws);
218
- }
219
- }
220
- }
221
- }
222
-
223
- /**
224
- * Close the message bus
225
- */
226
- close() {
227
- // Close all WebSocket connections
228
- for (const ws of this.wsClients) {
229
- try {
230
- ws.close();
231
- } catch {
232
- // Ignore errors on close
233
- }
234
- }
235
- this.wsClients.clear();
236
-
237
- // Close ledger
238
- this.ledger.close();
239
- }
240
-
241
- /**
242
- * Clear all messages (for testing)
243
- */
244
- clear() {
245
- this.ledger.clear();
246
- }
247
- }
248
-
249
- module.exports = MessageBus;