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.
- package/.trajectories/active/traj_3yx9dy148mge.json +42 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
- package/.trajectories/index.json +140 -1
- package/README.md +23 -9
- package/TRAIL_GIT_AUTH_FIX.md +113 -0
- package/deploy/workspace/codex.config.toml +1 -1
- package/deploy/workspace/entrypoint.sh +20 -79
- package/deploy/workspace/gh-relay +156 -0
- package/deploy/workspace/git-credential-relay +5 -1
- package/dist/bridge/multi-project-client.js +13 -10
- package/dist/bridge/spawner.d.ts +2 -0
- package/dist/bridge/spawner.js +58 -76
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +297 -30
- package/dist/cloud/api/admin.js +16 -3
- package/dist/cloud/api/codex-auth-helper.js +28 -8
- package/dist/cloud/api/consensus.d.ts +13 -0
- package/dist/cloud/api/consensus.js +259 -0
- package/dist/cloud/api/daemons.js +205 -1
- package/dist/cloud/api/git.js +37 -7
- package/dist/cloud/api/onboarding.js +4 -1
- package/dist/cloud/api/provider-env.d.ts +5 -0
- package/dist/cloud/api/provider-env.js +27 -0
- package/dist/cloud/api/providers.js +2 -0
- package/dist/cloud/api/test-helpers.js +130 -0
- package/dist/cloud/api/workspaces.js +38 -3
- package/dist/cloud/db/bulk-ingest.d.ts +88 -0
- package/dist/cloud/db/bulk-ingest.js +268 -0
- package/dist/cloud/db/drizzle.d.ts +33 -0
- package/dist/cloud/db/drizzle.js +174 -2
- package/dist/cloud/db/index.d.ts +24 -5
- package/dist/cloud/db/index.js +19 -4
- package/dist/cloud/db/schema.d.ts +397 -3
- package/dist/cloud/db/schema.js +75 -1
- package/dist/cloud/provisioner/index.d.ts +8 -0
- package/dist/cloud/provisioner/index.js +256 -50
- package/dist/cloud/server.js +47 -3
- package/dist/cloud/services/index.d.ts +1 -0
- package/dist/cloud/services/index.js +2 -0
- package/dist/cloud/services/nango.d.ts +3 -4
- package/dist/cloud/services/nango.js +11 -33
- package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
- package/dist/cloud/services/workspace-keepalive.js +234 -0
- package/dist/config/relay-config.d.ts +23 -0
- package/dist/config/relay-config.js +23 -0
- package/dist/daemon/agent-manager.d.ts +20 -1
- package/dist/daemon/agent-manager.js +51 -0
- package/dist/daemon/agent-registry.js +4 -4
- package/dist/daemon/agent-signing.d.ts +158 -0
- package/dist/daemon/agent-signing.js +523 -0
- package/dist/daemon/api.js +18 -1
- package/dist/daemon/cli-auth.d.ts +4 -1
- package/dist/daemon/cli-auth.js +55 -11
- package/dist/daemon/cloud-sync.d.ts +47 -1
- package/dist/daemon/cloud-sync.js +152 -3
- package/dist/daemon/connection.d.ts +28 -0
- package/dist/daemon/connection.js +113 -22
- package/dist/daemon/consensus-integration.d.ts +167 -0
- package/dist/daemon/consensus-integration.js +371 -0
- package/dist/daemon/consensus.d.ts +271 -0
- package/dist/daemon/consensus.js +632 -0
- package/dist/daemon/delivery-tracker.d.ts +34 -0
- package/dist/daemon/delivery-tracker.js +104 -0
- package/dist/daemon/enhanced-features.d.ts +118 -0
- package/dist/daemon/enhanced-features.js +178 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.js +5 -0
- package/dist/daemon/rate-limiter.d.ts +68 -0
- package/dist/daemon/rate-limiter.js +130 -0
- package/dist/daemon/router.d.ts +18 -11
- package/dist/daemon/router.js +57 -113
- package/dist/daemon/server.d.ts +13 -1
- package/dist/daemon/server.js +71 -9
- package/dist/daemon/sync-queue.d.ts +116 -0
- package/dist/daemon/sync-queue.js +361 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
- package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -0
- package/dist/dashboard/out/cloud/link.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +2 -2
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -3
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +1 -1
- package/dist/dashboard-server/server.js +244 -28
- package/dist/health-worker-manager.d.ts +62 -0
- package/dist/health-worker-manager.js +144 -0
- package/dist/health-worker.d.ts +9 -0
- package/dist/health-worker.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/memory/context-compaction.d.ts +156 -0
- package/dist/memory/context-compaction.js +453 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/protocol/channels.js +4 -4
- package/dist/protocol/framing.d.ts +72 -10
- package/dist/protocol/framing.js +194 -25
- package/dist/storage/adapter.d.ts +8 -1
- package/dist/storage/adapter.js +11 -0
- package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
- package/dist/storage/batched-sqlite-adapter.js +183 -0
- package/dist/storage/dead-letter-queue.d.ts +196 -0
- package/dist/storage/dead-letter-queue.js +427 -0
- package/dist/storage/dlq-adapter.d.ts +195 -0
- package/dist/storage/dlq-adapter.js +664 -0
- package/dist/trajectory/config.d.ts +32 -14
- package/dist/trajectory/config.js +38 -16
- package/dist/trajectory/integration.js +217 -64
- package/dist/utils/git-remote.d.ts +47 -0
- package/dist/utils/git-remote.js +125 -0
- package/dist/utils/id-generator.d.ts +35 -0
- package/dist/utils/id-generator.js +60 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/precompiled-patterns.d.ts +110 -0
- package/dist/utils/precompiled-patterns.js +322 -0
- package/dist/wrapper/auth-detection.js +1 -1
- package/dist/wrapper/base-wrapper.d.ts +40 -0
- package/dist/wrapper/base-wrapper.js +60 -6
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +89 -31
- package/dist/wrapper/idle-detector.d.ts +102 -0
- package/dist/wrapper/idle-detector.js +279 -0
- package/dist/wrapper/parser.d.ts +4 -0
- package/dist/wrapper/parser.js +19 -1
- package/dist/wrapper/pty-wrapper.d.ts +14 -2
- package/dist/wrapper/pty-wrapper.js +132 -32
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +20 -2
- package/dist/wrapper/tmux-wrapper.js +163 -40
- package/package.json +3 -1
- package/scripts/run-migrations.js +43 -0
- package/scripts/verify-schema.js +134 -0
- package/tests/benchmarks/protocol.bench.ts +310 -0
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
- package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
- /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
|