agent-relay 1.3.1 → 1.3.3

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 (202) hide show
  1. package/.trajectories/active/traj_3yx9dy148mge.json +42 -0
  2. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
  3. package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
  4. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
  5. package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
  6. package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
  7. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
  8. package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
  9. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
  10. package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
  11. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
  12. package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
  13. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
  14. package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
  15. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
  16. package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
  17. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
  18. package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
  19. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
  20. package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
  21. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
  22. package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
  23. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
  24. package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
  25. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
  26. package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
  27. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
  28. package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
  29. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
  30. package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
  31. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
  32. package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
  33. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
  34. package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
  35. package/.trajectories/index.json +140 -1
  36. package/README.md +23 -9
  37. package/TRAIL_GIT_AUTH_FIX.md +113 -0
  38. package/deploy/workspace/codex.config.toml +1 -1
  39. package/deploy/workspace/entrypoint.sh +20 -79
  40. package/deploy/workspace/gh-relay +156 -0
  41. package/deploy/workspace/git-credential-relay +5 -1
  42. package/dist/bridge/multi-project-client.js +13 -10
  43. package/dist/bridge/spawner.d.ts +2 -0
  44. package/dist/bridge/spawner.js +58 -76
  45. package/dist/bridge/types.d.ts +2 -0
  46. package/dist/cli/index.d.ts +8 -6
  47. package/dist/cli/index.js +297 -30
  48. package/dist/cloud/api/admin.js +16 -3
  49. package/dist/cloud/api/codex-auth-helper.js +28 -8
  50. package/dist/cloud/api/consensus.d.ts +13 -0
  51. package/dist/cloud/api/consensus.js +259 -0
  52. package/dist/cloud/api/daemons.js +205 -1
  53. package/dist/cloud/api/git.js +37 -7
  54. package/dist/cloud/api/onboarding.js +4 -1
  55. package/dist/cloud/api/provider-env.d.ts +5 -0
  56. package/dist/cloud/api/provider-env.js +27 -0
  57. package/dist/cloud/api/providers.js +2 -0
  58. package/dist/cloud/api/test-helpers.js +130 -0
  59. package/dist/cloud/api/workspaces.js +38 -3
  60. package/dist/cloud/db/bulk-ingest.d.ts +88 -0
  61. package/dist/cloud/db/bulk-ingest.js +268 -0
  62. package/dist/cloud/db/drizzle.d.ts +33 -0
  63. package/dist/cloud/db/drizzle.js +174 -2
  64. package/dist/cloud/db/index.d.ts +24 -5
  65. package/dist/cloud/db/index.js +19 -4
  66. package/dist/cloud/db/schema.d.ts +397 -3
  67. package/dist/cloud/db/schema.js +75 -1
  68. package/dist/cloud/provisioner/index.d.ts +8 -0
  69. package/dist/cloud/provisioner/index.js +256 -50
  70. package/dist/cloud/server.js +47 -3
  71. package/dist/cloud/services/index.d.ts +1 -0
  72. package/dist/cloud/services/index.js +2 -0
  73. package/dist/cloud/services/nango.d.ts +3 -4
  74. package/dist/cloud/services/nango.js +11 -33
  75. package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
  76. package/dist/cloud/services/workspace-keepalive.js +234 -0
  77. package/dist/config/relay-config.d.ts +23 -0
  78. package/dist/config/relay-config.js +23 -0
  79. package/dist/daemon/agent-manager.d.ts +20 -1
  80. package/dist/daemon/agent-manager.js +51 -0
  81. package/dist/daemon/agent-registry.js +4 -4
  82. package/dist/daemon/agent-signing.d.ts +158 -0
  83. package/dist/daemon/agent-signing.js +523 -0
  84. package/dist/daemon/api.js +18 -1
  85. package/dist/daemon/cli-auth.d.ts +4 -1
  86. package/dist/daemon/cli-auth.js +55 -11
  87. package/dist/daemon/cloud-sync.d.ts +47 -1
  88. package/dist/daemon/cloud-sync.js +152 -3
  89. package/dist/daemon/connection.d.ts +28 -0
  90. package/dist/daemon/connection.js +113 -22
  91. package/dist/daemon/consensus-integration.d.ts +167 -0
  92. package/dist/daemon/consensus-integration.js +371 -0
  93. package/dist/daemon/consensus.d.ts +271 -0
  94. package/dist/daemon/consensus.js +632 -0
  95. package/dist/daemon/delivery-tracker.d.ts +34 -0
  96. package/dist/daemon/delivery-tracker.js +104 -0
  97. package/dist/daemon/enhanced-features.d.ts +118 -0
  98. package/dist/daemon/enhanced-features.js +178 -0
  99. package/dist/daemon/index.d.ts +4 -0
  100. package/dist/daemon/index.js +5 -0
  101. package/dist/daemon/rate-limiter.d.ts +68 -0
  102. package/dist/daemon/rate-limiter.js +130 -0
  103. package/dist/daemon/router.d.ts +18 -11
  104. package/dist/daemon/router.js +57 -113
  105. package/dist/daemon/server.d.ts +13 -1
  106. package/dist/daemon/server.js +71 -9
  107. package/dist/daemon/sync-queue.d.ts +116 -0
  108. package/dist/daemon/sync-queue.js +361 -0
  109. package/dist/dashboard/out/404.html +1 -1
  110. package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
  111. package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
  112. package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
  113. package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
  114. package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
  115. package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
  116. package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
  117. package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
  118. package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
  119. package/dist/dashboard/out/app/onboarding.html +1 -1
  120. package/dist/dashboard/out/app/onboarding.txt +1 -1
  121. package/dist/dashboard/out/app.html +1 -1
  122. package/dist/dashboard/out/app.txt +2 -2
  123. package/dist/dashboard/out/cloud/link.html +1 -0
  124. package/dist/dashboard/out/cloud/link.txt +7 -0
  125. package/dist/dashboard/out/connect-repos.html +1 -1
  126. package/dist/dashboard/out/connect-repos.txt +1 -1
  127. package/dist/dashboard/out/history.html +1 -1
  128. package/dist/dashboard/out/history.txt +2 -2
  129. package/dist/dashboard/out/index.html +1 -1
  130. package/dist/dashboard/out/index.txt +2 -2
  131. package/dist/dashboard/out/login.html +2 -3
  132. package/dist/dashboard/out/login.txt +2 -2
  133. package/dist/dashboard/out/metrics.html +1 -1
  134. package/dist/dashboard/out/metrics.txt +2 -2
  135. package/dist/dashboard/out/pricing.html +2 -2
  136. package/dist/dashboard/out/pricing.txt +1 -1
  137. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  138. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  139. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  140. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  141. package/dist/dashboard/out/providers.html +1 -1
  142. package/dist/dashboard/out/providers.txt +1 -1
  143. package/dist/dashboard/out/signup.html +2 -2
  144. package/dist/dashboard/out/signup.txt +1 -1
  145. package/dist/dashboard-server/server.js +244 -28
  146. package/dist/health-worker-manager.d.ts +62 -0
  147. package/dist/health-worker-manager.js +144 -0
  148. package/dist/health-worker.d.ts +9 -0
  149. package/dist/health-worker.js +79 -0
  150. package/dist/index.d.ts +2 -1
  151. package/dist/index.js +5 -1
  152. package/dist/memory/context-compaction.d.ts +156 -0
  153. package/dist/memory/context-compaction.js +453 -0
  154. package/dist/memory/index.d.ts +1 -0
  155. package/dist/memory/index.js +1 -0
  156. package/dist/protocol/channels.js +4 -4
  157. package/dist/protocol/framing.d.ts +72 -10
  158. package/dist/protocol/framing.js +194 -25
  159. package/dist/storage/adapter.d.ts +8 -1
  160. package/dist/storage/adapter.js +11 -0
  161. package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
  162. package/dist/storage/batched-sqlite-adapter.js +183 -0
  163. package/dist/storage/dead-letter-queue.d.ts +196 -0
  164. package/dist/storage/dead-letter-queue.js +427 -0
  165. package/dist/storage/dlq-adapter.d.ts +195 -0
  166. package/dist/storage/dlq-adapter.js +664 -0
  167. package/dist/trajectory/config.d.ts +32 -14
  168. package/dist/trajectory/config.js +38 -16
  169. package/dist/trajectory/integration.js +217 -64
  170. package/dist/utils/git-remote.d.ts +47 -0
  171. package/dist/utils/git-remote.js +125 -0
  172. package/dist/utils/id-generator.d.ts +35 -0
  173. package/dist/utils/id-generator.js +60 -0
  174. package/dist/utils/index.d.ts +1 -0
  175. package/dist/utils/index.js +1 -0
  176. package/dist/utils/precompiled-patterns.d.ts +110 -0
  177. package/dist/utils/precompiled-patterns.js +322 -0
  178. package/dist/wrapper/auth-detection.js +1 -1
  179. package/dist/wrapper/base-wrapper.d.ts +40 -0
  180. package/dist/wrapper/base-wrapper.js +60 -6
  181. package/dist/wrapper/client.d.ts +14 -4
  182. package/dist/wrapper/client.js +89 -31
  183. package/dist/wrapper/idle-detector.d.ts +102 -0
  184. package/dist/wrapper/idle-detector.js +279 -0
  185. package/dist/wrapper/parser.d.ts +4 -0
  186. package/dist/wrapper/parser.js +19 -1
  187. package/dist/wrapper/pty-wrapper.d.ts +14 -2
  188. package/dist/wrapper/pty-wrapper.js +132 -32
  189. package/dist/wrapper/shared.d.ts +1 -1
  190. package/dist/wrapper/shared.js +1 -1
  191. package/dist/wrapper/tmux-wrapper.d.ts +20 -2
  192. package/dist/wrapper/tmux-wrapper.js +163 -40
  193. package/package.json +3 -1
  194. package/scripts/run-migrations.js +43 -0
  195. package/scripts/verify-schema.js +134 -0
  196. package/tests/benchmarks/protocol.bench.ts +310 -0
  197. package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
  198. package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
  199. package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
  200. package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
  201. /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
  202. /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_ssgManifest.js +0 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Consensus Integration for Agent Relay
3
+ *
4
+ * Integrates the consensus mechanism with the router/daemon.
5
+ * This is an optional feature that can be enabled to allow agents
6
+ * to participate in distributed decision-making.
7
+ *
8
+ * Usage:
9
+ * 1. Create a ConsensusIntegration with the router and optional config
10
+ * 2. Call processIncomingMessage() on each received message to detect votes
11
+ * 3. Use createProposal() to start a new consensus vote
12
+ *
13
+ * Example:
14
+ * ```typescript
15
+ * const consensus = new ConsensusIntegration(router, { enabled: true });
16
+ *
17
+ * // Create a proposal
18
+ * consensus.createProposal({
19
+ * title: 'Approve API design',
20
+ * description: 'Should we proceed with the REST API design?',
21
+ * proposer: 'Architect',
22
+ * participants: ['Developer', 'Reviewer', 'Lead'],
23
+ * consensusType: 'majority',
24
+ * });
25
+ *
26
+ * // Process incoming messages to detect votes
27
+ * consensus.processIncomingMessage(from, body);
28
+ * ```
29
+ */
30
+ import { generateId } from '../utils/id-generator.js';
31
+ import { ConsensusEngine, createConsensusEngine, formatProposalMessage, formatResultMessage, parseVoteCommand, parseProposalCommand, isConsensusCommand, } from './consensus.js';
32
+ import { PROTOCOL_VERSION } from '../protocol/types.js';
33
+ const DEFAULT_CONFIG = {
34
+ enabled: true,
35
+ autoBroadcast: true,
36
+ autoResultBroadcast: true,
37
+ logEvents: true,
38
+ };
39
+ // =============================================================================
40
+ // Consensus Integration
41
+ // =============================================================================
42
+ /**
43
+ * Integrates consensus mechanism with the relay router.
44
+ * Provides automatic proposal broadcasting and vote detection.
45
+ */
46
+ export class ConsensusIntegration {
47
+ config;
48
+ engine;
49
+ router;
50
+ log;
51
+ constructor(router, config = {}) {
52
+ this.config = { ...DEFAULT_CONFIG, ...config };
53
+ this.router = router;
54
+ this.engine = createConsensusEngine(config.consensus);
55
+ // Setup logging
56
+ this.log = this.config.logEvents
57
+ ? (msg, data) => console.log(`[consensus] ${msg}`, data ?? '')
58
+ : () => { };
59
+ // Subscribe to engine events
60
+ this.setupEventHandlers();
61
+ }
62
+ /**
63
+ * Check if consensus is enabled.
64
+ */
65
+ get enabled() {
66
+ return this.config.enabled;
67
+ }
68
+ /**
69
+ * Get the underlying consensus engine.
70
+ */
71
+ getEngine() {
72
+ return this.engine;
73
+ }
74
+ /**
75
+ * Create a new proposal and optionally broadcast to participants.
76
+ */
77
+ createProposal(options) {
78
+ if (!this.config.enabled) {
79
+ throw new Error('Consensus is not enabled');
80
+ }
81
+ const proposal = this.engine.createProposal({
82
+ ...options,
83
+ thread: `consensus-${options.title.toLowerCase().replace(/\s+/g, '-')}`,
84
+ });
85
+ this.log('Proposal created', { id: proposal.id, title: proposal.title });
86
+ return proposal;
87
+ }
88
+ /**
89
+ * Process an incoming message to detect and handle consensus commands.
90
+ * Handles both PROPOSE and VOTE commands.
91
+ */
92
+ processIncomingMessage(from, body) {
93
+ if (!this.config.enabled) {
94
+ return { isConsensusCommand: false };
95
+ }
96
+ // Check for PROPOSE command
97
+ const proposeCmd = parseProposalCommand(body);
98
+ if (proposeCmd) {
99
+ try {
100
+ const proposal = this.createProposal({
101
+ ...proposeCmd,
102
+ proposer: from,
103
+ });
104
+ this.log('Proposal created via command', {
105
+ from,
106
+ proposalId: proposal.id,
107
+ title: proposal.title,
108
+ });
109
+ return {
110
+ isConsensusCommand: true,
111
+ type: 'propose',
112
+ result: { success: true, proposal },
113
+ };
114
+ }
115
+ catch (err) {
116
+ return {
117
+ isConsensusCommand: true,
118
+ type: 'propose',
119
+ result: {
120
+ success: false,
121
+ error: err instanceof Error ? err.message : 'Failed to create proposal',
122
+ },
123
+ };
124
+ }
125
+ }
126
+ // Check for VOTE command
127
+ const voteCmd = parseVoteCommand(body);
128
+ if (voteCmd) {
129
+ const result = this.engine.vote(voteCmd.proposalId, from, voteCmd.value, voteCmd.reason);
130
+ this.log('Vote received', {
131
+ from,
132
+ proposalId: voteCmd.proposalId,
133
+ value: voteCmd.value,
134
+ success: result.success,
135
+ });
136
+ return { isConsensusCommand: true, type: 'vote', result };
137
+ }
138
+ return { isConsensusCommand: false };
139
+ }
140
+ /**
141
+ * Check if a message is a consensus command without processing it.
142
+ */
143
+ isConsensusMessage(body) {
144
+ return isConsensusCommand(body);
145
+ }
146
+ /**
147
+ * Get pending proposals for an agent.
148
+ */
149
+ getPendingVotes(agentName) {
150
+ return this.engine.getPendingVotesForAgent(agentName);
151
+ }
152
+ /**
153
+ * Get all proposals for an agent.
154
+ */
155
+ getProposals(agentName) {
156
+ return this.engine.getProposalsForAgent(agentName);
157
+ }
158
+ /**
159
+ * Get a specific proposal by ID.
160
+ */
161
+ getProposal(proposalId) {
162
+ return this.engine.getProposal(proposalId);
163
+ }
164
+ /**
165
+ * Cancel a proposal.
166
+ */
167
+ cancelProposal(proposalId, agentName) {
168
+ return this.engine.cancelProposal(proposalId, agentName);
169
+ }
170
+ /**
171
+ * Get consensus statistics.
172
+ */
173
+ getStats() {
174
+ return this.engine.getStats();
175
+ }
176
+ /**
177
+ * Cleanup resources.
178
+ */
179
+ cleanup() {
180
+ this.engine.cleanup();
181
+ }
182
+ // ===========================================================================
183
+ // Private Methods
184
+ // ===========================================================================
185
+ /**
186
+ * Sync a proposal to the cloud dashboard.
187
+ *
188
+ * Auto-detects cloud settings from workspace env vars:
189
+ * - CLOUD_API_URL / AGENT_RELAY_CLOUD_URL - cloud URL
190
+ * - WORKSPACE_ID / AGENT_RELAY_WORKSPACE_ID - workspace ID
191
+ * - WORKSPACE_TOKEN / AGENT_RELAY_API_KEY - auth token
192
+ */
193
+ async syncToCloud(proposal, event) {
194
+ // Get cloud sync settings - check workspace env vars first, then agent-relay vars
195
+ const cloudUrl = this.config.cloudSync?.url
196
+ || process.env.CLOUD_API_URL
197
+ || process.env.AGENT_RELAY_CLOUD_URL;
198
+ const workspaceId = this.config.cloudSync?.workspaceId
199
+ || process.env.WORKSPACE_ID
200
+ || process.env.AGENT_RELAY_WORKSPACE_ID;
201
+ const token = this.config.cloudSync?.apiKey
202
+ || process.env.WORKSPACE_TOKEN
203
+ || process.env.AGENT_RELAY_API_KEY;
204
+ // Skip if no cloud URL configured
205
+ if (!cloudUrl) {
206
+ return;
207
+ }
208
+ // Skip if no workspace ID
209
+ if (!workspaceId) {
210
+ return;
211
+ }
212
+ try {
213
+ const url = `${cloudUrl}/api/daemons/consensus/sync`;
214
+ // Build headers - token is optional for localhost
215
+ const headers = {
216
+ 'Content-Type': 'application/json',
217
+ };
218
+ if (token) {
219
+ headers['Authorization'] = `Bearer ${token}`;
220
+ }
221
+ const response = await fetch(url, {
222
+ method: 'POST',
223
+ headers,
224
+ body: JSON.stringify({ proposal, event, workspaceId }),
225
+ });
226
+ if (!response.ok) {
227
+ const errorText = await response.text();
228
+ this.log(`Cloud sync failed: ${response.status} ${errorText}`);
229
+ }
230
+ else {
231
+ this.log(`Cloud sync: ${event} for proposal ${proposal.id}`);
232
+ }
233
+ }
234
+ catch (err) {
235
+ // Don't fail on cloud sync errors - just log them
236
+ this.log(`Cloud sync error: ${err instanceof Error ? err.message : String(err)}`);
237
+ }
238
+ }
239
+ setupEventHandlers() {
240
+ // Broadcast new proposals to participants
241
+ this.engine.on('proposal:created', (proposal) => {
242
+ if (this.config.autoBroadcast) {
243
+ this.broadcastProposal(proposal);
244
+ }
245
+ // Sync to cloud dashboard
246
+ this.syncToCloud(proposal, 'created');
247
+ });
248
+ // Notify participants when someone votes
249
+ this.engine.on('proposal:voted', (proposal, vote) => {
250
+ this.log('Vote recorded', {
251
+ proposalId: proposal.id,
252
+ voter: vote.agent,
253
+ value: vote.value,
254
+ });
255
+ // Sync updated proposal to cloud dashboard
256
+ this.syncToCloud(proposal, 'voted');
257
+ });
258
+ // Broadcast results when resolved
259
+ this.engine.on('proposal:resolved', (proposal, result) => {
260
+ this.log('Proposal resolved', {
261
+ id: proposal.id,
262
+ decision: result.decision,
263
+ participation: `${(result.participation * 100).toFixed(1)}%`,
264
+ });
265
+ if (this.config.autoResultBroadcast) {
266
+ this.broadcastResult(proposal, result);
267
+ }
268
+ // Sync resolved proposal to cloud dashboard
269
+ this.syncToCloud(proposal, 'resolved');
270
+ });
271
+ // Log expired proposals
272
+ this.engine.on('proposal:expired', (proposal) => {
273
+ this.log('Proposal expired', { id: proposal.id, title: proposal.title });
274
+ // Sync expired proposal to cloud dashboard
275
+ this.syncToCloud(proposal, 'expired');
276
+ });
277
+ // Log cancelled proposals
278
+ this.engine.on('proposal:cancelled', (proposal) => {
279
+ this.log('Proposal cancelled', { id: proposal.id, title: proposal.title });
280
+ // Sync cancelled proposal to cloud dashboard
281
+ this.syncToCloud(proposal, 'cancelled');
282
+ });
283
+ }
284
+ /**
285
+ * Broadcast a proposal to all participants via the router.
286
+ */
287
+ broadcastProposal(proposal) {
288
+ const message = formatProposalMessage(proposal);
289
+ for (const participant of proposal.participants) {
290
+ this.sendToAgent(proposal.proposer, participant, message, proposal.thread);
291
+ }
292
+ this.log('Proposal broadcast', {
293
+ id: proposal.id,
294
+ recipients: proposal.participants.length,
295
+ });
296
+ }
297
+ /**
298
+ * Broadcast the result of a proposal to all participants.
299
+ */
300
+ broadcastResult(proposal, result) {
301
+ const message = formatResultMessage(proposal, result);
302
+ // Send to proposer
303
+ this.sendToAgent('_consensus', proposal.proposer, message, proposal.thread);
304
+ // Send to all participants
305
+ for (const participant of proposal.participants) {
306
+ if (participant !== proposal.proposer) {
307
+ this.sendToAgent('_consensus', participant, message, proposal.thread);
308
+ }
309
+ }
310
+ this.log('Result broadcast', {
311
+ id: proposal.id,
312
+ decision: result.decision,
313
+ recipients: proposal.participants.length,
314
+ });
315
+ }
316
+ /**
317
+ * Send a message to an agent via the router.
318
+ */
319
+ sendToAgent(from, to, body, thread) {
320
+ // Create a SEND envelope
321
+ const envelope = {
322
+ v: PROTOCOL_VERSION,
323
+ type: 'SEND',
324
+ id: generateId(),
325
+ ts: Date.now(),
326
+ from,
327
+ to,
328
+ payload: {
329
+ kind: 'action',
330
+ body,
331
+ thread,
332
+ data: {
333
+ _isConsensusMessage: true,
334
+ _consensusAction: 'proposal',
335
+ },
336
+ },
337
+ };
338
+ // Get the target connection and route
339
+ const target = this.router.getConnection(to);
340
+ if (target) {
341
+ // Use a mock connection for system messages
342
+ const mockFrom = {
343
+ id: `consensus-${generateId()}`,
344
+ agentName: from,
345
+ sessionId: 'consensus-system',
346
+ close: () => { },
347
+ send: () => true,
348
+ getNextSeq: () => 0,
349
+ };
350
+ // Route the message
351
+ this.router.route(mockFrom, envelope);
352
+ }
353
+ else {
354
+ this.log(`Target agent not connected: ${to}`);
355
+ }
356
+ }
357
+ }
358
+ // =============================================================================
359
+ // Factory Function
360
+ // =============================================================================
361
+ /**
362
+ * Create a consensus integration instance.
363
+ */
364
+ export function createConsensusIntegration(router, config) {
365
+ return new ConsensusIntegration(router, config);
366
+ }
367
+ // =============================================================================
368
+ // Re-exports for convenience
369
+ // =============================================================================
370
+ export { ConsensusEngine, createConsensusEngine, formatProposalMessage, formatResultMessage, parseVoteCommand, parseProposalCommand, isConsensusCommand, };
371
+ //# sourceMappingURL=consensus-integration.js.map
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Agent Consensus Mechanism
3
+ *
4
+ * Enables distributed decision-making across multiple agents.
5
+ * Inspired by russian-code-ts roadmap: "Consensus-based decision making"
6
+ *
7
+ * Consensus Types:
8
+ * 1. Majority Vote - Simple >50% agreement
9
+ * 2. Supermajority - 2/3 or configurable threshold
10
+ * 3. Unanimous - All participants must agree
11
+ * 4. Weighted - Votes weighted by agent role/expertise
12
+ * 5. Quorum - Minimum participation required
13
+ *
14
+ * Use Cases:
15
+ * - Code review approval (2+ agents approve)
16
+ * - Architecture decisions (lead + majority)
17
+ * - Deployment gates (all critical agents agree)
18
+ * - Task assignment (weighted by expertise)
19
+ */
20
+ import { EventEmitter } from 'node:events';
21
+ export type ConsensusType = 'majority' | 'supermajority' | 'unanimous' | 'weighted' | 'quorum';
22
+ export type VoteValue = 'approve' | 'reject' | 'abstain';
23
+ export type ProposalStatus = 'pending' | 'approved' | 'rejected' | 'expired' | 'cancelled';
24
+ export interface AgentWeight {
25
+ /** Agent name */
26
+ agent: string;
27
+ /** Vote weight (default: 1) */
28
+ weight: number;
29
+ /** Agent role for context */
30
+ role?: string;
31
+ }
32
+ export interface Vote {
33
+ /** Voting agent */
34
+ agent: string;
35
+ /** Vote value */
36
+ value: VoteValue;
37
+ /** Vote weight (resolved at vote time) */
38
+ weight: number;
39
+ /** Optional reasoning */
40
+ reason?: string;
41
+ /** Vote timestamp */
42
+ timestamp: number;
43
+ }
44
+ export interface Proposal {
45
+ /** Unique proposal ID */
46
+ id: string;
47
+ /** Proposal title/subject */
48
+ title: string;
49
+ /** Detailed description */
50
+ description: string;
51
+ /** Proposing agent */
52
+ proposer: string;
53
+ /** Consensus type required */
54
+ consensusType: ConsensusType;
55
+ /** Agents allowed to vote */
56
+ participants: string[];
57
+ /** Minimum votes required (for quorum) */
58
+ quorum?: number;
59
+ /** Threshold for supermajority (0-1, default 0.67) */
60
+ threshold?: number;
61
+ /** Agent weights (for weighted voting) */
62
+ weights?: AgentWeight[];
63
+ /** Proposal creation timestamp */
64
+ createdAt: number;
65
+ /** Expiry timestamp */
66
+ expiresAt: number;
67
+ /** Current status */
68
+ status: ProposalStatus;
69
+ /** Collected votes */
70
+ votes: Vote[];
71
+ /** Result details (set when resolved) */
72
+ result?: ConsensusResult;
73
+ /** Optional metadata */
74
+ metadata?: Record<string, unknown>;
75
+ /** Thread ID for relay messages */
76
+ thread?: string;
77
+ }
78
+ export interface ConsensusResult {
79
+ /** Final decision */
80
+ decision: 'approved' | 'rejected' | 'no_consensus';
81
+ /** Total approve weight */
82
+ approveWeight: number;
83
+ /** Total reject weight */
84
+ rejectWeight: number;
85
+ /** Total abstain weight */
86
+ abstainWeight: number;
87
+ /** Participation rate (0-1) */
88
+ participation: number;
89
+ /** Whether quorum was met */
90
+ quorumMet: boolean;
91
+ /** Resolution timestamp */
92
+ resolvedAt: number;
93
+ /** Agents who didn't vote */
94
+ nonVoters: string[];
95
+ }
96
+ export interface ConsensusConfig {
97
+ /** Default proposal timeout in ms (default: 5 minutes) */
98
+ defaultTimeoutMs: number;
99
+ /** Default consensus type */
100
+ defaultConsensusType: ConsensusType;
101
+ /** Default supermajority threshold */
102
+ defaultThreshold: number;
103
+ /** Allow vote changes before resolution */
104
+ allowVoteChange: boolean;
105
+ /** Auto-resolve when consensus is mathematically certain */
106
+ autoResolve: boolean;
107
+ /** Broadcast proposals to all participants */
108
+ broadcastProposals: boolean;
109
+ }
110
+ export interface ConsensusEvents {
111
+ 'proposal:created': (proposal: Proposal) => void;
112
+ 'proposal:voted': (proposal: Proposal, vote: Vote) => void;
113
+ 'proposal:resolved': (proposal: Proposal, result: ConsensusResult) => void;
114
+ 'proposal:expired': (proposal: Proposal) => void;
115
+ 'proposal:cancelled': (proposal: Proposal) => void;
116
+ }
117
+ export declare class ConsensusEngine extends EventEmitter {
118
+ private config;
119
+ private proposals;
120
+ private expiryTimers;
121
+ constructor(config?: Partial<ConsensusConfig>);
122
+ /**
123
+ * Create a new proposal.
124
+ */
125
+ createProposal(options: {
126
+ title: string;
127
+ description: string;
128
+ proposer: string;
129
+ participants: string[];
130
+ consensusType?: ConsensusType;
131
+ timeoutMs?: number;
132
+ quorum?: number;
133
+ threshold?: number;
134
+ weights?: AgentWeight[];
135
+ metadata?: Record<string, unknown>;
136
+ thread?: string;
137
+ }): Proposal;
138
+ /**
139
+ * Submit a vote on a proposal.
140
+ */
141
+ vote(proposalId: string, agent: string, value: VoteValue, reason?: string): {
142
+ success: boolean;
143
+ error?: string;
144
+ proposal?: Proposal;
145
+ };
146
+ /**
147
+ * Get a proposal by ID.
148
+ */
149
+ getProposal(proposalId: string): Proposal | null;
150
+ /**
151
+ * Get all proposals for an agent (as participant or proposer).
152
+ */
153
+ getProposalsForAgent(agent: string): Proposal[];
154
+ /**
155
+ * Get pending proposals awaiting an agent's vote.
156
+ */
157
+ getPendingVotesForAgent(agent: string): Proposal[];
158
+ /**
159
+ * Cancel a proposal (only proposer can cancel).
160
+ */
161
+ cancelProposal(proposalId: string, agent: string): {
162
+ success: boolean;
163
+ error?: string;
164
+ };
165
+ /**
166
+ * Force resolve a proposal (for admin/system use).
167
+ */
168
+ forceResolve(proposalId: string): ConsensusResult | null;
169
+ /**
170
+ * Calculate current consensus result.
171
+ */
172
+ calculateResult(proposal: Proposal): ConsensusResult;
173
+ /**
174
+ * Determine decision based on consensus type and votes.
175
+ */
176
+ private determineDecision;
177
+ /**
178
+ * Check if proposal can be resolved early (consensus mathematically certain).
179
+ */
180
+ private canResolveEarly;
181
+ /**
182
+ * Get weight for an agent in a proposal.
183
+ */
184
+ private getAgentWeight;
185
+ /**
186
+ * Get total weight of all participants.
187
+ */
188
+ private getTotalWeight;
189
+ /**
190
+ * Resolve a proposal with result.
191
+ */
192
+ private resolveProposal;
193
+ /**
194
+ * Expire a proposal.
195
+ */
196
+ private expireProposal;
197
+ /**
198
+ * Schedule expiry timer for a proposal.
199
+ */
200
+ private scheduleExpiry;
201
+ /**
202
+ * Clear expiry timer for a proposal.
203
+ */
204
+ private clearExpiryTimer;
205
+ /**
206
+ * Cleanup all timers (for shutdown).
207
+ */
208
+ cleanup(): void;
209
+ /**
210
+ * Get consensus statistics.
211
+ */
212
+ getStats(): {
213
+ total: number;
214
+ pending: number;
215
+ approved: number;
216
+ rejected: number;
217
+ expired: number;
218
+ cancelled: number;
219
+ avgParticipation: number;
220
+ };
221
+ }
222
+ /**
223
+ * Create a consensus engine with the given configuration.
224
+ */
225
+ export declare function createConsensusEngine(config?: Partial<ConsensusConfig>): ConsensusEngine;
226
+ /**
227
+ * Format a proposal as a relay message for broadcasting.
228
+ */
229
+ export declare function formatProposalMessage(proposal: Proposal): string;
230
+ /**
231
+ * Parse a vote command from a relay message.
232
+ */
233
+ export declare function parseVoteCommand(message: string): {
234
+ proposalId: string;
235
+ value: VoteValue;
236
+ reason?: string;
237
+ } | null;
238
+ /**
239
+ * Format a consensus result as a relay message.
240
+ */
241
+ export declare function formatResultMessage(proposal: Proposal, result: ConsensusResult): string;
242
+ export interface ParsedProposalCommand {
243
+ title: string;
244
+ description: string;
245
+ participants: string[];
246
+ consensusType: ConsensusType;
247
+ timeoutMs?: number;
248
+ quorum?: number;
249
+ threshold?: number;
250
+ metadata?: Record<string, unknown>;
251
+ }
252
+ /**
253
+ * Parse a PROPOSE command from a relay message.
254
+ *
255
+ * Format:
256
+ * ```
257
+ * PROPOSE: Title of the proposal
258
+ * TYPE: majority|supermajority|unanimous|weighted|quorum
259
+ * PARTICIPANTS: Agent1, Agent2, Agent3
260
+ * DESCRIPTION: Detailed description of what is being proposed
261
+ * TIMEOUT: 3600000 (optional, in milliseconds)
262
+ * QUORUM: 3 (optional, minimum votes)
263
+ * THRESHOLD: 0.67 (optional, for supermajority)
264
+ * ```
265
+ */
266
+ export declare function parseProposalCommand(message: string): ParsedProposalCommand | null;
267
+ /**
268
+ * Check if a message is a consensus command (PROPOSE or VOTE).
269
+ */
270
+ export declare function isConsensusCommand(message: string): boolean;
271
+ //# sourceMappingURL=consensus.d.ts.map