@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.
- package/LICENSE +1 -1
- package/README.md +8 -465
- package/cli/index.js +101 -5231
- package/package.json +16 -130
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -149
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -275
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -581
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -208
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -220
- package/lib/git-remote-utils.js +0 -165
- package/lib/id-detector.js +0 -56
- package/lib/provider-defaults.js +0 -62
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -57
- package/lib/settings/claude-auth.js +0 -78
- package/lib/settings.js +0 -503
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/setup-merge-queue.sh +0 -170
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -246
- package/src/agent/agent-context-builder.js +0 -444
- package/src/agent/agent-hook-executor.js +0 -599
- package/src/agent/agent-lifecycle.js +0 -743
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1690
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -217
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -146
- package/src/agent-wrapper.js +0 -579
- package/src/agents/git-pusher-template.js +0 -285
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -574
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1925
- package/src/copy-worker.js +0 -43
- package/src/input-helpers.js +0 -65
- package/src/isolation-manager.js +0 -1471
- package/src/issue-providers/README.md +0 -305
- package/src/issue-providers/azure-devops-provider.js +0 -273
- package/src/issue-providers/base-provider.js +0 -232
- package/src/issue-providers/github-provider.js +0 -179
- package/src/issue-providers/gitlab-provider.js +0 -241
- package/src/issue-providers/index.js +0 -196
- package/src/issue-providers/jira-provider.js +0 -239
- package/src/ledger.js +0 -631
- package/src/lib/safe-exec.js +0 -88
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2815
- package/src/preflight.js +0 -639
- package/src/process-metrics.js +0 -608
- package/src/providers/anthropic/cli-builder.js +0 -73
- package/src/providers/anthropic/index.js +0 -204
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -177
- package/src/providers/base-provider.js +0 -251
- package/src/providers/capabilities.js +0 -60
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -101
- package/src/providers/index.js +0 -91
- package/src/providers/openai/cli-builder.js +0 -133
- package/src/providers/openai/index.js +0 -136
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -143
- package/src/providers/opencode/cli-builder.js +0 -42
- package/src/providers/opencode/index.js +0 -103
- package/src/providers/opencode/models.js +0 -55
- package/src/providers/opencode/output-parser.js +0 -122
- package/src/schemas/sub-cluster.js +0 -185
- package/src/status-footer.js +0 -1026
- package/src/sub-cluster-wrapper.js +0 -601
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -372
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -349
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -129
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -324
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -184
- package/task-lib/attachable-watcher.js +0 -309
- package/task-lib/claude-recovery.js +0 -156
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/episodes.js +0 -105
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -453
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -148
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -8
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -201
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -529
- package/task-lib/tui/formatters.js +0 -215
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -129
- package/task-lib/tui.js +0 -425
- package/task-lib/watcher.js +0 -226
package/src/logic-engine.js
DELETED
|
@@ -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;
|
package/src/message-bus.js
DELETED
|
@@ -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;
|