@tjamescouch/agentchat 0.22.0 → 0.23.0

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 (135) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/bin/agentchat.d.ts +7 -0
  3. package/dist/bin/agentchat.d.ts.map +1 -0
  4. package/dist/bin/agentchat.js +1511 -0
  5. package/dist/bin/agentchat.js.map +1 -0
  6. package/dist/lib/allowlist.d.ts +77 -0
  7. package/dist/lib/allowlist.d.ts.map +1 -0
  8. package/dist/lib/allowlist.js +151 -0
  9. package/dist/lib/allowlist.js.map +1 -0
  10. package/dist/lib/client.d.ts +147 -0
  11. package/dist/lib/client.d.ts.map +1 -0
  12. package/dist/lib/client.js +704 -0
  13. package/dist/lib/client.js.map +1 -0
  14. package/dist/lib/daemon.d.ts +122 -0
  15. package/dist/lib/daemon.d.ts.map +1 -0
  16. package/dist/lib/daemon.js +523 -0
  17. package/dist/lib/daemon.js.map +1 -0
  18. package/dist/lib/deploy/akash.d.ts +271 -0
  19. package/dist/lib/deploy/akash.d.ts.map +1 -0
  20. package/dist/lib/deploy/akash.js +671 -0
  21. package/dist/lib/deploy/akash.js.map +1 -0
  22. package/dist/lib/deploy/config.d.ts +62 -0
  23. package/dist/lib/deploy/config.d.ts.map +1 -0
  24. package/dist/lib/deploy/config.js +116 -0
  25. package/dist/lib/deploy/config.js.map +1 -0
  26. package/dist/lib/deploy/docker.d.ts +37 -0
  27. package/dist/lib/deploy/docker.d.ts.map +1 -0
  28. package/dist/lib/deploy/docker.js +122 -0
  29. package/dist/lib/deploy/docker.js.map +1 -0
  30. package/dist/lib/deploy/index.d.ts +11 -0
  31. package/dist/lib/deploy/index.d.ts.map +1 -0
  32. package/dist/lib/deploy/index.js +11 -0
  33. package/dist/lib/deploy/index.js.map +1 -0
  34. package/dist/lib/escrow-hooks.d.ts +199 -0
  35. package/dist/lib/escrow-hooks.d.ts.map +1 -0
  36. package/dist/lib/escrow-hooks.js +221 -0
  37. package/dist/lib/escrow-hooks.js.map +1 -0
  38. package/dist/lib/identity.d.ts +134 -0
  39. package/dist/lib/identity.d.ts.map +1 -0
  40. package/dist/lib/identity.js +334 -0
  41. package/dist/lib/identity.js.map +1 -0
  42. package/dist/lib/jitter.d.ts +42 -0
  43. package/dist/lib/jitter.d.ts.map +1 -0
  44. package/{lib → dist/lib}/jitter.js +16 -19
  45. package/dist/lib/jitter.js.map +1 -0
  46. package/dist/lib/proposals.d.ts +223 -0
  47. package/dist/lib/proposals.d.ts.map +1 -0
  48. package/dist/lib/proposals.js +379 -0
  49. package/dist/lib/proposals.js.map +1 -0
  50. package/dist/lib/protocol.d.ts +220 -0
  51. package/dist/lib/protocol.d.ts.map +1 -0
  52. package/dist/lib/protocol.js +507 -0
  53. package/dist/lib/protocol.js.map +1 -0
  54. package/dist/lib/receipts.d.ts +134 -0
  55. package/dist/lib/receipts.d.ts.map +1 -0
  56. package/dist/lib/receipts.js +270 -0
  57. package/dist/lib/receipts.js.map +1 -0
  58. package/dist/lib/reputation.d.ts +250 -0
  59. package/dist/lib/reputation.d.ts.map +1 -0
  60. package/dist/lib/reputation.js +586 -0
  61. package/dist/lib/reputation.js.map +1 -0
  62. package/dist/lib/security.d.ts +27 -0
  63. package/dist/lib/security.d.ts.map +1 -0
  64. package/dist/lib/security.js +150 -0
  65. package/dist/lib/security.js.map +1 -0
  66. package/dist/lib/server/handlers/admin.d.ts +26 -0
  67. package/dist/lib/server/handlers/admin.d.ts.map +1 -0
  68. package/dist/lib/server/handlers/admin.js +76 -0
  69. package/dist/lib/server/handlers/admin.js.map +1 -0
  70. package/dist/lib/server/handlers/identity.d.ts +36 -0
  71. package/dist/lib/server/handlers/identity.d.ts.map +1 -0
  72. package/dist/lib/server/handlers/identity.js +330 -0
  73. package/dist/lib/server/handlers/identity.js.map +1 -0
  74. package/dist/lib/server/handlers/index.d.ts +10 -0
  75. package/dist/lib/server/handlers/index.d.ts.map +1 -0
  76. package/dist/lib/server/handlers/index.js +15 -0
  77. package/dist/lib/server/handlers/index.js.map +1 -0
  78. package/dist/lib/server/handlers/message.d.ts +47 -0
  79. package/dist/lib/server/handlers/message.d.ts.map +1 -0
  80. package/dist/lib/server/handlers/message.js +265 -0
  81. package/dist/lib/server/handlers/message.js.map +1 -0
  82. package/dist/lib/server/handlers/presence.d.ts +18 -0
  83. package/dist/lib/server/handlers/presence.d.ts.map +1 -0
  84. package/dist/lib/server/handlers/presence.js +35 -0
  85. package/dist/lib/server/handlers/presence.js.map +1 -0
  86. package/dist/lib/server/handlers/proposal.d.ts +38 -0
  87. package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
  88. package/dist/lib/server/handlers/proposal.js +273 -0
  89. package/dist/lib/server/handlers/proposal.js.map +1 -0
  90. package/dist/lib/server/handlers/skills.d.ts +22 -0
  91. package/dist/lib/server/handlers/skills.d.ts.map +1 -0
  92. package/dist/lib/server/handlers/skills.js +119 -0
  93. package/dist/lib/server/handlers/skills.js.map +1 -0
  94. package/dist/lib/server-directory.d.ts +85 -0
  95. package/dist/lib/server-directory.d.ts.map +1 -0
  96. package/dist/lib/server-directory.js +177 -0
  97. package/dist/lib/server-directory.js.map +1 -0
  98. package/dist/lib/server.d.ts +162 -0
  99. package/dist/lib/server.d.ts.map +1 -0
  100. package/dist/lib/server.js +602 -0
  101. package/dist/lib/server.js.map +1 -0
  102. package/dist/lib/types.d.ts +461 -0
  103. package/dist/lib/types.d.ts.map +1 -0
  104. package/dist/lib/types.js +98 -0
  105. package/dist/lib/types.js.map +1 -0
  106. package/package.json +22 -13
  107. package/bin/agentchat.js +0 -1617
  108. package/lib/chat.py +0 -241
  109. package/lib/client.js +0 -821
  110. package/lib/daemon.js +0 -562
  111. package/lib/deploy/akash.js +0 -811
  112. package/lib/deploy/config.js +0 -128
  113. package/lib/deploy/docker.js +0 -132
  114. package/lib/deploy/index.js +0 -24
  115. package/lib/elo_swarm.py +0 -569
  116. package/lib/escrow-hooks.js +0 -237
  117. package/lib/identity.js +0 -376
  118. package/lib/proposals.js +0 -426
  119. package/lib/protocol.js +0 -484
  120. package/lib/receipts.js +0 -294
  121. package/lib/reputation.js +0 -664
  122. package/lib/security.js +0 -183
  123. package/lib/server/handlers/identity.js +0 -242
  124. package/lib/server/handlers/index.js +0 -42
  125. package/lib/server/handlers/message.js +0 -306
  126. package/lib/server/handlers/presence.js +0 -44
  127. package/lib/server/handlers/proposal.js +0 -358
  128. package/lib/server/handlers/skills.js +0 -141
  129. package/lib/server-directory.js +0 -181
  130. package/lib/server.js +0 -528
  131. package/lib/supervisor/USAGE.md +0 -110
  132. package/lib/supervisor/agent-supervisor.sh +0 -135
  133. package/lib/supervisor/agentctl.sh +0 -250
  134. package/lib/supervisor/killswitch.sh +0 -36
  135. package/lib/supervisor/notify.sh +0 -19
@@ -1,237 +0,0 @@
1
- /**
2
- * EscrowHooks - Event system for external escrow integration
3
- *
4
- * Allows external systems (blockchain, multi-sig, compliance) to hook into
5
- * escrow lifecycle events without modifying core AgentChat code.
6
- *
7
- * Events:
8
- * escrow:created - Escrow created when proposal accepted with stakes
9
- * escrow:released - Escrow released (expired, cancelled)
10
- * settlement:completion - Proposal completed, stakes returned
11
- * settlement:dispute - Proposal disputed, stakes transferred/burned
12
- */
13
-
14
- export const EscrowEvent = {
15
- CREATED: 'escrow:created',
16
- RELEASED: 'escrow:released',
17
- COMPLETION_SETTLED: 'settlement:completion',
18
- DISPUTE_SETTLED: 'settlement:dispute'
19
- };
20
-
21
- export class EscrowHooks {
22
- constructor(options = {}) {
23
- this.handlers = new Map(); // event -> Set of handlers
24
- this.logger = options.logger || console;
25
- this.continueOnError = options.continueOnError !== false; // default true
26
-
27
- // Initialize event handler sets
28
- for (const event of Object.values(EscrowEvent)) {
29
- this.handlers.set(event, new Set());
30
- }
31
- }
32
-
33
- /**
34
- * Register a handler for an escrow event
35
- * @param {string} event - Event name from EscrowEvent
36
- * @param {Function} handler - Async function(payload) to call
37
- * @returns {Function} Unsubscribe function
38
- */
39
- on(event, handler) {
40
- if (!this.handlers.has(event)) {
41
- throw new Error(`Unknown escrow event: ${event}`);
42
- }
43
-
44
- if (typeof handler !== 'function') {
45
- throw new Error('Handler must be a function');
46
- }
47
-
48
- this.handlers.get(event).add(handler);
49
-
50
- // Return unsubscribe function
51
- return () => this.off(event, handler);
52
- }
53
-
54
- /**
55
- * Remove a handler for an escrow event
56
- * @param {string} event - Event name
57
- * @param {Function} handler - Handler to remove
58
- */
59
- off(event, handler) {
60
- if (this.handlers.has(event)) {
61
- this.handlers.get(event).delete(handler);
62
- }
63
- }
64
-
65
- /**
66
- * Remove all handlers for an event (or all events)
67
- * @param {string} [event] - Optional event name
68
- */
69
- clear(event) {
70
- if (event) {
71
- if (this.handlers.has(event)) {
72
- this.handlers.get(event).clear();
73
- }
74
- } else {
75
- for (const handlers of this.handlers.values()) {
76
- handlers.clear();
77
- }
78
- }
79
- }
80
-
81
- /**
82
- * Emit an escrow event to all registered handlers
83
- * @param {string} event - Event name
84
- * @param {Object} payload - Event payload
85
- * @returns {Promise<Object>} Results from all handlers
86
- */
87
- async emit(event, payload) {
88
- if (!this.handlers.has(event)) {
89
- throw new Error(`Unknown escrow event: ${event}`);
90
- }
91
-
92
- const handlers = this.handlers.get(event);
93
- if (handlers.size === 0) {
94
- return { event, handled: false, results: [] };
95
- }
96
-
97
- const results = [];
98
- const errors = [];
99
-
100
- for (const handler of handlers) {
101
- try {
102
- const result = await handler(payload);
103
- results.push({ success: true, result });
104
- } catch (err) {
105
- const errorInfo = {
106
- success: false,
107
- error: err.message,
108
- stack: err.stack
109
- };
110
- errors.push(errorInfo);
111
- results.push(errorInfo);
112
-
113
- this.logger.error?.(`[EscrowHooks] Error in ${event} handler:`, err.message);
114
-
115
- if (!this.continueOnError) {
116
- break;
117
- }
118
- }
119
- }
120
-
121
- return {
122
- event,
123
- handled: true,
124
- results,
125
- errors: errors.length > 0 ? errors : undefined
126
- };
127
- }
128
-
129
- /**
130
- * Check if any handlers are registered for an event
131
- * @param {string} event - Event name
132
- * @returns {boolean}
133
- */
134
- hasHandlers(event) {
135
- return this.handlers.has(event) && this.handlers.get(event).size > 0;
136
- }
137
-
138
- /**
139
- * Get count of handlers for an event
140
- * @param {string} event - Event name
141
- * @returns {number}
142
- */
143
- handlerCount(event) {
144
- return this.handlers.has(event) ? this.handlers.get(event).size : 0;
145
- }
146
- }
147
-
148
- /**
149
- * Create payload for escrow:created event
150
- */
151
- export function createEscrowCreatedPayload(proposal, escrowResult) {
152
- return {
153
- event: EscrowEvent.CREATED,
154
- timestamp: Date.now(),
155
- proposal_id: proposal.id,
156
- from_agent: proposal.from,
157
- to_agent: proposal.to,
158
- proposer_stake: proposal.proposer_stake || 0,
159
- acceptor_stake: proposal.acceptor_stake || 0,
160
- total_stake: (proposal.proposer_stake || 0) + (proposal.acceptor_stake || 0),
161
- task: proposal.task,
162
- amount: proposal.amount,
163
- currency: proposal.currency,
164
- expires: proposal.expires,
165
- escrow_id: escrowResult.escrow?.proposal_id || proposal.id
166
- };
167
- }
168
-
169
- /**
170
- * Create payload for settlement:completion event
171
- */
172
- export function createCompletionPayload(proposal, ratingChanges) {
173
- const escrowInfo = ratingChanges?._escrow || {};
174
- return {
175
- event: EscrowEvent.COMPLETION_SETTLED,
176
- timestamp: Date.now(),
177
- proposal_id: proposal.id,
178
- from_agent: proposal.from,
179
- to_agent: proposal.to,
180
- completed_by: proposal.completed_by,
181
- completion_proof: proposal.completion_proof,
182
- settlement: 'returned',
183
- stakes_returned: {
184
- proposer: escrowInfo.proposer_stake || 0,
185
- acceptor: escrowInfo.acceptor_stake || 0
186
- },
187
- rating_changes: {
188
- [proposal.from]: ratingChanges?.[proposal.from],
189
- [proposal.to]: ratingChanges?.[proposal.to]
190
- }
191
- };
192
- }
193
-
194
- /**
195
- * Create payload for settlement:dispute event
196
- */
197
- export function createDisputePayload(proposal, ratingChanges) {
198
- const escrowInfo = ratingChanges?._escrow || {};
199
- return {
200
- event: EscrowEvent.DISPUTE_SETTLED,
201
- timestamp: Date.now(),
202
- proposal_id: proposal.id,
203
- from_agent: proposal.from,
204
- to_agent: proposal.to,
205
- disputed_by: proposal.disputed_by,
206
- dispute_reason: proposal.dispute_reason,
207
- settlement: escrowInfo.settlement || 'settled',
208
- settlement_reason: escrowInfo.settlement_reason,
209
- fault_determination: escrowInfo.fault_party,
210
- stakes_transferred: escrowInfo.transferred,
211
- stakes_burned: escrowInfo.burned,
212
- rating_changes: {
213
- [proposal.from]: ratingChanges?.[proposal.from],
214
- [proposal.to]: ratingChanges?.[proposal.to]
215
- }
216
- };
217
- }
218
-
219
- /**
220
- * Create payload for escrow:released event
221
- */
222
- export function createEscrowReleasedPayload(proposalId, escrow, reason) {
223
- return {
224
- event: EscrowEvent.RELEASED,
225
- timestamp: Date.now(),
226
- proposal_id: proposalId,
227
- from_agent: escrow.from?.agent_id,
228
- to_agent: escrow.to?.agent_id,
229
- stakes_released: {
230
- proposer: escrow.from?.stake || 0,
231
- acceptor: escrow.to?.stake || 0
232
- },
233
- reason: reason || 'expired'
234
- };
235
- }
236
-
237
- export default EscrowHooks;
package/lib/identity.js DELETED
@@ -1,376 +0,0 @@
1
- /**
2
- * AgentChat Identity Module
3
- * Ed25519 key generation, storage, and signing
4
- */
5
-
6
- import crypto from 'crypto';
7
- import fs from 'fs/promises';
8
- import path from 'path';
9
- import os from 'os';
10
-
11
- // Default identity file location
12
- export const DEFAULT_IDENTITY_PATH = path.join(process.cwd(), '.agentchat', 'identity.json');
13
-
14
- /**
15
- * Generate stable agent ID from pubkey
16
- * Returns first 8 chars of SHA256 hash (hex)
17
- */
18
- export function pubkeyToAgentId(pubkey) {
19
- const hash = crypto.createHash('sha256').update(pubkey).digest('hex');
20
- return hash.substring(0, 8);
21
- }
22
-
23
- /**
24
- * Validate Ed25519 public key in PEM format
25
- */
26
- export function isValidPubkey(pubkey) {
27
- if (!pubkey || typeof pubkey !== 'string') return false;
28
-
29
- try {
30
- const keyObj = crypto.createPublicKey(pubkey);
31
- return keyObj.asymmetricKeyType === 'ed25519';
32
- } catch {
33
- return false;
34
- }
35
- }
36
-
37
- /**
38
- * AgentChat Identity
39
- * Represents an agent's Ed25519 keypair and associated metadata
40
- */
41
- export class Identity {
42
- constructor(data) {
43
- this.name = data.name;
44
- this.pubkey = data.pubkey; // PEM format
45
- this.privkey = data.privkey; // PEM format (null if loaded from export)
46
- this.created = data.created;
47
- this.rotations = data.rotations || []; // Array of rotation records
48
-
49
- // Lazy-load crypto key objects
50
- this._publicKey = null;
51
- this._privateKey = null;
52
- }
53
-
54
- /**
55
- * Generate new Ed25519 keypair
56
- */
57
- static generate(name) {
58
- const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
59
-
60
- return new Identity({
61
- name,
62
- pubkey: publicKey.export({ type: 'spki', format: 'pem' }),
63
- privkey: privateKey.export({ type: 'pkcs8', format: 'pem' }),
64
- created: new Date().toISOString()
65
- });
66
- }
67
-
68
- /**
69
- * Load identity from JSON file
70
- */
71
- static async load(filePath = DEFAULT_IDENTITY_PATH) {
72
- const data = await fs.readFile(filePath, 'utf-8');
73
- const parsed = JSON.parse(data);
74
- return new Identity(parsed);
75
- }
76
-
77
- /**
78
- * Save identity to JSON file
79
- */
80
- async save(filePath = DEFAULT_IDENTITY_PATH) {
81
- // Ensure directory exists
82
- const dir = path.dirname(filePath);
83
- await fs.mkdir(dir, { recursive: true });
84
-
85
- const data = {
86
- name: this.name,
87
- pubkey: this.pubkey,
88
- privkey: this.privkey,
89
- created: this.created,
90
- rotations: this.rotations
91
- };
92
-
93
- await fs.writeFile(filePath, JSON.stringify(data, null, 2), {
94
- mode: 0o600 // Owner read/write only
95
- });
96
- }
97
-
98
- /**
99
- * Check if identity file exists
100
- */
101
- static async exists(filePath = DEFAULT_IDENTITY_PATH) {
102
- try {
103
- await fs.access(filePath);
104
- return true;
105
- } catch {
106
- return false;
107
- }
108
- }
109
-
110
- /**
111
- * Get fingerprint (first 16 chars of SHA256 hash of pubkey)
112
- */
113
- getFingerprint() {
114
- const hash = crypto.createHash('sha256').update(this.pubkey).digest('hex');
115
- return hash.substring(0, 16);
116
- }
117
-
118
- /**
119
- * Get stable agent ID (first 8 chars of fingerprint)
120
- */
121
- getAgentId() {
122
- return pubkeyToAgentId(this.pubkey);
123
- }
124
-
125
- /**
126
- * Sign data with private key
127
- * Returns base64-encoded signature
128
- */
129
- sign(data) {
130
- if (!this.privkey) {
131
- throw new Error('Private key not available (identity was loaded from export)');
132
- }
133
-
134
- if (!this._privateKey) {
135
- this._privateKey = crypto.createPrivateKey(this.privkey);
136
- }
137
-
138
- const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
139
- const signature = crypto.sign(null, buffer, this._privateKey);
140
- return signature.toString('base64');
141
- }
142
-
143
- /**
144
- * Verify a signature
145
- * Static method for verifying any message
146
- */
147
- static verify(data, signature, pubkey) {
148
- try {
149
- const keyObj = crypto.createPublicKey(pubkey);
150
- const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
151
- const sigBuffer = Buffer.from(signature, 'base64');
152
- return crypto.verify(null, buffer, keyObj, sigBuffer);
153
- } catch {
154
- return false;
155
- }
156
- }
157
-
158
- /**
159
- * Export for sharing (pubkey only, no private key)
160
- */
161
- export() {
162
- return {
163
- name: this.name,
164
- pubkey: this.pubkey,
165
- created: this.created,
166
- rotations: this.rotations
167
- };
168
- }
169
-
170
- /**
171
- * Rotate to a new keypair
172
- * Signs the new public key with the old private key for chain of custody
173
- * @returns {object} Rotation record with old_pubkey, new_pubkey, signature, timestamp
174
- */
175
- rotate() {
176
- if (!this.privkey) {
177
- throw new Error('Private key not available - cannot rotate');
178
- }
179
-
180
- // Generate new keypair
181
- const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
182
- const newPubkey = publicKey.export({ type: 'spki', format: 'pem' });
183
- const newPrivkey = privateKey.export({ type: 'pkcs8', format: 'pem' });
184
-
185
- // Use same timestamp for both signing and record
186
- const timestamp = new Date().toISOString();
187
-
188
- // Create rotation record content to sign
189
- const rotationContent = JSON.stringify({
190
- old_pubkey: this.pubkey,
191
- new_pubkey: newPubkey,
192
- timestamp
193
- });
194
-
195
- // Sign with old private key
196
- const signature = this.sign(rotationContent);
197
-
198
- // Create rotation record
199
- const rotationRecord = {
200
- old_pubkey: this.pubkey,
201
- old_agent_id: this.getAgentId(),
202
- new_pubkey: newPubkey,
203
- new_agent_id: pubkeyToAgentId(newPubkey),
204
- signature,
205
- timestamp
206
- };
207
-
208
- // Update identity with new keys
209
- this.rotations.push(rotationRecord);
210
- this.pubkey = newPubkey;
211
- this.privkey = newPrivkey;
212
- this._publicKey = null;
213
- this._privateKey = null;
214
-
215
- return rotationRecord;
216
- }
217
-
218
- /**
219
- * Verify a rotation record
220
- * Checks that the signature is valid using the old public key
221
- * @param {object} record - Rotation record to verify
222
- * @returns {boolean} True if signature is valid
223
- */
224
- static verifyRotation(record) {
225
- try {
226
- const rotationContent = JSON.stringify({
227
- old_pubkey: record.old_pubkey,
228
- new_pubkey: record.new_pubkey,
229
- timestamp: record.timestamp
230
- });
231
-
232
- return Identity.verify(rotationContent, record.signature, record.old_pubkey);
233
- } catch {
234
- return false;
235
- }
236
- }
237
-
238
- /**
239
- * Verify the entire rotation chain
240
- * @returns {object} { valid: boolean, errors: string[] }
241
- */
242
- verifyRotationChain() {
243
- const errors = [];
244
-
245
- if (this.rotations.length === 0) {
246
- return { valid: true, errors: [] };
247
- }
248
-
249
- // Verify each rotation in sequence
250
- for (let i = 0; i < this.rotations.length; i++) {
251
- const record = this.rotations[i];
252
-
253
- // Verify signature
254
- if (!Identity.verifyRotation(record)) {
255
- errors.push(`Rotation ${i + 1}: Invalid signature`);
256
- continue;
257
- }
258
-
259
- // Verify chain continuity (each new_pubkey should match next old_pubkey)
260
- if (i < this.rotations.length - 1) {
261
- const nextRecord = this.rotations[i + 1];
262
- if (record.new_pubkey !== nextRecord.old_pubkey) {
263
- errors.push(`Rotation ${i + 1}: Chain break - new_pubkey doesn't match next old_pubkey`);
264
- }
265
- }
266
- }
267
-
268
- // Verify final pubkey matches current identity
269
- const lastRotation = this.rotations[this.rotations.length - 1];
270
- if (lastRotation.new_pubkey !== this.pubkey) {
271
- errors.push('Final rotation new_pubkey does not match current identity pubkey');
272
- }
273
-
274
- return {
275
- valid: errors.length === 0,
276
- errors
277
- };
278
- }
279
-
280
- /**
281
- * Get the original (genesis) public key before any rotations
282
- * @returns {string} Original public key in PEM format
283
- */
284
- getOriginalPubkey() {
285
- if (this.rotations.length === 0) {
286
- return this.pubkey;
287
- }
288
- return this.rotations[0].old_pubkey;
289
- }
290
-
291
- /**
292
- * Get the original (genesis) agent ID
293
- * @returns {string} Original agent ID
294
- */
295
- getOriginalAgentId() {
296
- return pubkeyToAgentId(this.getOriginalPubkey());
297
- }
298
-
299
- /**
300
- * Generate a signed revocation notice for this identity
301
- * A revocation notice declares that the key should no longer be trusted
302
- * @param {string} reason - Reason for revocation (e.g., "compromised", "retired", "lost")
303
- * @returns {object} Revocation notice with pubkey, reason, signature, timestamp
304
- */
305
- revoke(reason = 'revoked') {
306
- if (!this.privkey) {
307
- throw new Error('Private key not available - cannot create revocation notice');
308
- }
309
-
310
- const timestamp = new Date().toISOString();
311
-
312
- // Create revocation content to sign
313
- const revocationContent = JSON.stringify({
314
- type: 'REVOCATION',
315
- pubkey: this.pubkey,
316
- agent_id: this.getAgentId(),
317
- reason,
318
- timestamp
319
- });
320
-
321
- // Sign with the key being revoked (proves ownership)
322
- const signature = this.sign(revocationContent);
323
-
324
- return {
325
- type: 'REVOCATION',
326
- pubkey: this.pubkey,
327
- agent_id: this.getAgentId(),
328
- fingerprint: this.getFingerprint(),
329
- reason,
330
- timestamp,
331
- signature,
332
- // Include rotation history for full chain verification
333
- rotations: this.rotations.length > 0 ? this.rotations : undefined,
334
- original_agent_id: this.rotations.length > 0 ? this.getOriginalAgentId() : undefined
335
- };
336
- }
337
-
338
- /**
339
- * Verify a revocation notice
340
- * Checks that the signature is valid using the pubkey in the notice
341
- * @param {object} notice - Revocation notice to verify
342
- * @returns {boolean} True if signature is valid
343
- */
344
- static verifyRevocation(notice) {
345
- if (!notice || notice.type !== 'REVOCATION') {
346
- return false;
347
- }
348
-
349
- try {
350
- const revocationContent = JSON.stringify({
351
- type: 'REVOCATION',
352
- pubkey: notice.pubkey,
353
- agent_id: notice.agent_id,
354
- reason: notice.reason,
355
- timestamp: notice.timestamp
356
- });
357
-
358
- return Identity.verify(revocationContent, notice.signature, notice.pubkey);
359
- } catch {
360
- return false;
361
- }
362
- }
363
-
364
- /**
365
- * Check if a pubkey has been revoked by checking against a revocation notice
366
- * @param {string} pubkey - Public key to check
367
- * @param {object} notice - Revocation notice
368
- * @returns {boolean} True if the pubkey matches the revoked key
369
- */
370
- static isRevoked(pubkey, notice) {
371
- if (!Identity.verifyRevocation(notice)) {
372
- return false;
373
- }
374
- return notice.pubkey === pubkey;
375
- }
376
- }