agent-relay 2.0.19 → 2.0.21

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 (142) hide show
  1. package/CHANGELOG.md +217 -24
  2. package/bin/relay-pty-darwin-arm64 +0 -0
  3. package/bin/relay-pty-darwin-x64 +0 -0
  4. package/bin/relay-pty-linux-x64 +0 -0
  5. package/dist/dashboard/out/404.html +1 -1
  6. package/dist/dashboard/out/app/onboarding.html +1 -1
  7. package/dist/dashboard/out/app/onboarding.txt +1 -1
  8. package/dist/dashboard/out/app.html +1 -1
  9. package/dist/dashboard/out/app.txt +1 -1
  10. package/dist/dashboard/out/cloud/link.html +1 -1
  11. package/dist/dashboard/out/cloud/link.txt +1 -1
  12. package/dist/dashboard/out/complete-profile.html +1 -1
  13. package/dist/dashboard/out/complete-profile.txt +1 -1
  14. package/dist/dashboard/out/connect-repos.html +1 -1
  15. package/dist/dashboard/out/connect-repos.txt +1 -1
  16. package/dist/dashboard/out/history.html +1 -1
  17. package/dist/dashboard/out/history.txt +1 -1
  18. package/dist/dashboard/out/index.html +1 -1
  19. package/dist/dashboard/out/index.txt +1 -1
  20. package/dist/dashboard/out/login.html +1 -1
  21. package/dist/dashboard/out/login.txt +1 -1
  22. package/dist/dashboard/out/metrics.html +1 -1
  23. package/dist/dashboard/out/metrics.txt +1 -1
  24. package/dist/dashboard/out/pricing.html +1 -1
  25. package/dist/dashboard/out/pricing.txt +1 -1
  26. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  27. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  28. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  29. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  30. package/dist/dashboard/out/providers/setup/cursor.html +1 -1
  31. package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
  32. package/dist/dashboard/out/providers.html +1 -1
  33. package/dist/dashboard/out/providers.txt +1 -1
  34. package/dist/dashboard/out/signup.html +1 -1
  35. package/dist/dashboard/out/signup.txt +1 -1
  36. package/package.json +23 -17
  37. package/packages/api-types/package.json +2 -2
  38. package/packages/bridge/dist/spawner.d.ts +2 -0
  39. package/packages/bridge/dist/spawner.js +76 -24
  40. package/packages/bridge/package.json +8 -8
  41. package/packages/cli-tester/README.md +277 -0
  42. package/packages/cli-tester/dist/index.d.ts +21 -0
  43. package/packages/cli-tester/dist/index.js +21 -0
  44. package/packages/cli-tester/dist/utils/credential-check.d.ts +56 -0
  45. package/packages/cli-tester/dist/utils/credential-check.js +230 -0
  46. package/packages/cli-tester/dist/utils/socket-client.d.ts +76 -0
  47. package/packages/cli-tester/dist/utils/socket-client.js +153 -0
  48. package/packages/cli-tester/docker/entrypoint.sh +58 -0
  49. package/packages/cli-tester/package.json +32 -0
  50. package/packages/cli-tester/scripts/clear-auth.sh +101 -0
  51. package/packages/cli-tester/scripts/inject-message.sh +42 -0
  52. package/packages/cli-tester/scripts/start.sh +71 -0
  53. package/packages/cli-tester/scripts/test-cli.sh +56 -0
  54. package/packages/cli-tester/scripts/test-full-spawn.sh +238 -0
  55. package/packages/cli-tester/scripts/test-registration.sh +182 -0
  56. package/packages/cli-tester/scripts/test-setup-flow.sh +202 -0
  57. package/packages/cli-tester/scripts/test-spawn.sh +140 -0
  58. package/packages/cli-tester/scripts/test-with-daemon.sh +247 -0
  59. package/packages/cli-tester/scripts/verify-auth.sh +112 -0
  60. package/packages/cloud/package.json +6 -6
  61. package/packages/config/dist/cli-auth-config.js +65 -0
  62. package/packages/config/package.json +2 -2
  63. package/packages/continuity/package.json +1 -1
  64. package/packages/daemon/dist/router.js +4 -4
  65. package/packages/daemon/dist/server.js +38 -19
  66. package/packages/daemon/dist/spawn-manager.d.ts +4 -0
  67. package/packages/daemon/dist/spawn-manager.js +2 -0
  68. package/packages/daemon/package.json +12 -12
  69. package/packages/dashboard/dist/server.js +4 -0
  70. package/packages/dashboard/package.json +14 -14
  71. package/packages/dashboard/ui-dist/404.html +1 -1
  72. package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
  73. package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
  74. package/packages/dashboard/ui-dist/app.html +1 -1
  75. package/packages/dashboard/ui-dist/app.txt +1 -1
  76. package/packages/dashboard/ui-dist/cloud/link.html +1 -1
  77. package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
  78. package/packages/dashboard/ui-dist/complete-profile.html +1 -1
  79. package/packages/dashboard/ui-dist/complete-profile.txt +1 -1
  80. package/packages/dashboard/ui-dist/connect-repos.html +1 -1
  81. package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
  82. package/packages/dashboard/ui-dist/history.html +1 -1
  83. package/packages/dashboard/ui-dist/history.txt +1 -1
  84. package/packages/dashboard/ui-dist/index.html +1 -1
  85. package/packages/dashboard/ui-dist/index.txt +1 -1
  86. package/packages/dashboard/ui-dist/login.html +1 -1
  87. package/packages/dashboard/ui-dist/login.txt +1 -1
  88. package/packages/dashboard/ui-dist/metrics.html +1 -1
  89. package/packages/dashboard/ui-dist/metrics.txt +1 -1
  90. package/packages/dashboard/ui-dist/pricing.html +1 -1
  91. package/packages/dashboard/ui-dist/pricing.txt +1 -1
  92. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
  93. package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
  94. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
  95. package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
  96. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
  97. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
  98. package/packages/dashboard/ui-dist/providers.html +1 -1
  99. package/packages/dashboard/ui-dist/providers.txt +1 -1
  100. package/packages/dashboard/ui-dist/signup.html +1 -1
  101. package/packages/dashboard/ui-dist/signup.txt +1 -1
  102. package/packages/dashboard-server/dist/server.js +4 -0
  103. package/packages/dashboard-server/package.json +12 -12
  104. package/packages/hooks/package.json +4 -4
  105. package/packages/mcp/package.json +2 -2
  106. package/packages/memory/package.json +2 -2
  107. package/packages/policy/package.json +2 -2
  108. package/packages/protocol/package.json +1 -1
  109. package/packages/resiliency/package.json +1 -1
  110. package/packages/sdk/README.md +512 -58
  111. package/packages/sdk/dist/client.d.ts +135 -1
  112. package/packages/sdk/dist/client.js +338 -0
  113. package/packages/sdk/dist/index.d.ts +2 -1
  114. package/packages/sdk/dist/index.js +2 -0
  115. package/packages/sdk/dist/logs.d.ts +61 -0
  116. package/packages/sdk/dist/logs.js +95 -0
  117. package/packages/sdk/dist/protocol/index.d.ts +1 -1
  118. package/packages/sdk/dist/protocol/types.d.ts +186 -1
  119. package/packages/sdk/package.json +3 -3
  120. package/packages/spawner/package.json +2 -2
  121. package/packages/state/package.json +1 -1
  122. package/packages/storage/dist/sqlite-adapter.js +2 -0
  123. package/packages/storage/package.json +2 -2
  124. package/packages/telemetry/package.json +1 -1
  125. package/packages/trajectory/package.json +2 -2
  126. package/packages/user-directory/package.json +2 -2
  127. package/packages/utils/package.json +1 -1
  128. package/packages/wrapper/dist/base-wrapper.js +27 -10
  129. package/packages/wrapper/dist/relay-pty-orchestrator.js +16 -16
  130. package/packages/wrapper/dist/tmux-wrapper.js +16 -0
  131. package/packages/wrapper/package.json +7 -7
  132. package/scripts/hooks/install.sh +16 -0
  133. package/scripts/hooks/pre-commit +60 -0
  134. package/specs/PRIMITIVES_ROADMAP.md +2154 -0
  135. /package/dist/dashboard/out/_next/static/{cREcLZyPb-5NyVZje0Qfe → 7MZPqYkVGw3EGzVBkVmY9}/_buildManifest.js +0 -0
  136. /package/dist/dashboard/out/_next/static/{cREcLZyPb-5NyVZje0Qfe → 7MZPqYkVGw3EGzVBkVmY9}/_ssgManifest.js +0 -0
  137. /package/packages/dashboard/ui-dist/_next/static/{N3ajGnJqRESKyCjDvyU52 → 7MZPqYkVGw3EGzVBkVmY9}/_buildManifest.js +0 -0
  138. /package/packages/dashboard/ui-dist/_next/static/{N3ajGnJqRESKyCjDvyU52 → 7MZPqYkVGw3EGzVBkVmY9}/_ssgManifest.js +0 -0
  139. /package/packages/dashboard/ui-dist/_next/static/{UQiyWwBxIP-9it3GYVBDL → iJ3Uiz3IrqUJL7IxKZHiV}/_buildManifest.js +0 -0
  140. /package/packages/dashboard/ui-dist/_next/static/{UQiyWwBxIP-9it3GYVBDL → iJ3Uiz3IrqUJL7IxKZHiV}/_ssgManifest.js +0 -0
  141. /package/packages/dashboard/ui-dist/_next/static/{cREcLZyPb-5NyVZje0Qfe → l-jd878zUJ_IlraqEWMZc}/_buildManifest.js +0 -0
  142. /package/packages/dashboard/ui-dist/_next/static/{cREcLZyPb-5NyVZje0Qfe → l-jd878zUJ_IlraqEWMZc}/_ssgManifest.js +0 -0
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Lightweight client for agent-to-agent communication via Agent Relay daemon.
6
6
  */
7
- import { type Envelope, type SendPayload, type SendMeta, type AckPayload, type PayloadKind, type SpeakOnTrigger, type EntityType, type ChannelMessagePayload, type MessageAttachment } from './protocol/types.js';
7
+ import { type Envelope, type SendPayload, type SendMeta, type AckPayload, type PayloadKind, type SpeakOnTrigger, type EntityType, type ChannelMessagePayload, type MessageAttachment, type SpawnResultPayload, type ReleaseResultPayload, type StatusResponsePayload, type InboxMessage, type AgentInfo, type HealthResponsePayload, type MetricsResponsePayload, type CreateProposalOptions, type VoteOptions } from './protocol/types.js';
8
8
  export type ClientState = 'DISCONNECTED' | 'CONNECTING' | 'HANDSHAKING' | 'READY' | 'BACKOFF';
9
9
  export interface SyncOptions {
10
10
  timeoutMs?: number;
@@ -62,6 +62,9 @@ export declare class RelayClient {
62
62
  private writeQueue;
63
63
  private writeScheduled;
64
64
  private pendingSyncAcks;
65
+ private pendingSpawns;
66
+ private pendingReleases;
67
+ private pendingQueries;
65
68
  onMessage?: (from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string) => void;
66
69
  /**
67
70
  * Callback for channel messages.
@@ -125,6 +128,35 @@ export declare class RelayClient {
125
128
  * Send log output to the daemon for dashboard streaming.
126
129
  */
127
130
  sendLog(data: string): boolean;
131
+ /**
132
+ * Spawn a new agent via the relay daemon.
133
+ * @param options - Spawn options
134
+ * @param options.name - Name for the new agent
135
+ * @param options.cli - CLI to use (claude, codex, gemini, etc.)
136
+ * @param options.task - Task description
137
+ * @param options.cwd - Working directory
138
+ * @param options.team - Team name
139
+ * @param options.interactive - Interactive mode
140
+ * @param options.shadowOf - Spawn as shadow of this agent
141
+ * @param options.shadowSpeakOn - Shadow speak-on triggers
142
+ * @param timeoutMs - Timeout for spawn operation (default: 30000ms)
143
+ */
144
+ spawn(options: {
145
+ name: string;
146
+ cli: string;
147
+ task?: string;
148
+ cwd?: string;
149
+ team?: string;
150
+ interactive?: boolean;
151
+ shadowOf?: string;
152
+ shadowSpeakOn?: SpeakOnTrigger[];
153
+ }, timeoutMs?: number): Promise<SpawnResultPayload>;
154
+ /**
155
+ * Release (terminate) an agent via the relay daemon.
156
+ * @param name - Agent name to release
157
+ * @param timeoutMs - Timeout for release operation (default: 10000ms)
158
+ */
159
+ release(name: string, timeoutMs?: number): Promise<ReleaseResultPayload>;
128
160
  /**
129
161
  * Join a channel.
130
162
  * @param channel - Channel name (e.g., '#general', 'dm:alice:bob')
@@ -161,6 +193,102 @@ export declare class RelayClient {
161
193
  attachments?: MessageAttachment[];
162
194
  data?: Record<string, unknown>;
163
195
  }): boolean;
196
+ /**
197
+ * Create a consensus proposal.
198
+ *
199
+ * The proposal will be broadcast to all participants. They can vote using
200
+ * the `vote()` method. Results are delivered via `onMessage` callback.
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * client.createProposal({
205
+ * title: 'Approve API design',
206
+ * description: 'Should we proceed with the REST API design?',
207
+ * participants: ['Developer', 'Reviewer', 'Lead'],
208
+ * consensusType: 'majority',
209
+ * });
210
+ * ```
211
+ *
212
+ * @param options - Proposal options
213
+ * @returns true if the message was sent
214
+ */
215
+ createProposal(options: CreateProposalOptions): boolean;
216
+ /**
217
+ * Vote on a consensus proposal.
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * // Approve with a reason
222
+ * client.vote({
223
+ * proposalId: 'prop_123',
224
+ * value: 'approve',
225
+ * reason: 'Looks good to me',
226
+ * });
227
+ *
228
+ * // Reject without reason
229
+ * client.vote({ proposalId: 'prop_123', value: 'reject' });
230
+ * ```
231
+ *
232
+ * @param options - Vote options
233
+ * @returns true if the message was sent
234
+ */
235
+ vote(options: VoteOptions): boolean;
236
+ /**
237
+ * Send a query to the daemon and wait for a response.
238
+ * @internal
239
+ */
240
+ private query;
241
+ /**
242
+ * Get daemon status information.
243
+ * @returns Daemon status including version, uptime, and counts
244
+ */
245
+ getStatus(): Promise<StatusResponsePayload>;
246
+ /**
247
+ * Get messages from the inbox.
248
+ * @param options - Filter options
249
+ * @param options.limit - Maximum number of messages to return
250
+ * @param options.unreadOnly - Only return unread messages
251
+ * @param options.from - Filter by sender
252
+ * @param options.channel - Filter by channel
253
+ * @returns Array of inbox messages
254
+ */
255
+ getInbox(options?: {
256
+ limit?: number;
257
+ unreadOnly?: boolean;
258
+ from?: string;
259
+ channel?: string;
260
+ }): Promise<InboxMessage[]>;
261
+ /**
262
+ * List online agents.
263
+ * @param options - Filter options
264
+ * @param options.includeIdle - Include idle agents (default: true)
265
+ * @param options.project - Filter by project
266
+ * @returns Array of agent info
267
+ */
268
+ listAgents(options?: {
269
+ includeIdle?: boolean;
270
+ project?: string;
271
+ }): Promise<AgentInfo[]>;
272
+ /**
273
+ * Get system health information.
274
+ * @param options - Include options
275
+ * @param options.includeCrashes - Include crash history (default: true)
276
+ * @param options.includeAlerts - Include alerts (default: true)
277
+ * @returns Health information including score, issues, and recommendations
278
+ */
279
+ getHealth(options?: {
280
+ includeCrashes?: boolean;
281
+ includeAlerts?: boolean;
282
+ }): Promise<HealthResponsePayload>;
283
+ /**
284
+ * Get resource metrics for agents.
285
+ * @param options - Filter options
286
+ * @param options.agent - Filter to a specific agent
287
+ * @returns Metrics including memory, CPU, and system info
288
+ */
289
+ getMetrics(options?: {
290
+ agent?: string;
291
+ }): Promise<MetricsResponsePayload>;
164
292
  private setState;
165
293
  private sendHello;
166
294
  private send;
@@ -171,11 +299,17 @@ export declare class RelayClient {
171
299
  private handleDeliver;
172
300
  private handleChannelMessage;
173
301
  private handleAck;
302
+ private handleSpawnResult;
303
+ private handleReleaseResult;
304
+ private handleQueryResponse;
174
305
  private handlePing;
175
306
  private handleErrorFrame;
176
307
  private handleDisconnect;
177
308
  private handleError;
178
309
  private rejectPendingSyncAcks;
310
+ private rejectPendingSpawns;
311
+ private rejectPendingReleases;
312
+ private rejectPendingQueries;
179
313
  private scheduleReconnect;
180
314
  }
181
315
  //# sourceMappingURL=client.d.ts.map
@@ -73,6 +73,9 @@ export class RelayClient {
73
73
  writeQueue = [];
74
74
  writeScheduled = false;
75
75
  pendingSyncAcks = new Map();
76
+ pendingSpawns = new Map();
77
+ pendingReleases = new Map();
78
+ pendingQueries = new Map();
76
79
  // Event handlers
77
80
  onMessage;
78
81
  /**
@@ -350,6 +353,91 @@ export class RelayClient {
350
353
  return this.send(envelope);
351
354
  }
352
355
  // =============================================================================
356
+ // Spawn/Release Operations
357
+ // =============================================================================
358
+ /**
359
+ * Spawn a new agent via the relay daemon.
360
+ * @param options - Spawn options
361
+ * @param options.name - Name for the new agent
362
+ * @param options.cli - CLI to use (claude, codex, gemini, etc.)
363
+ * @param options.task - Task description
364
+ * @param options.cwd - Working directory
365
+ * @param options.team - Team name
366
+ * @param options.interactive - Interactive mode
367
+ * @param options.shadowOf - Spawn as shadow of this agent
368
+ * @param options.shadowSpeakOn - Shadow speak-on triggers
369
+ * @param timeoutMs - Timeout for spawn operation (default: 30000ms)
370
+ */
371
+ async spawn(options, timeoutMs = 30000) {
372
+ if (this._state !== 'READY') {
373
+ throw new Error('Client not ready');
374
+ }
375
+ const envelopeId = generateId();
376
+ return new Promise((resolve, reject) => {
377
+ const timeoutHandle = setTimeout(() => {
378
+ this.pendingSpawns.delete(envelopeId);
379
+ reject(new Error(`Spawn timeout after ${timeoutMs}ms`));
380
+ }, timeoutMs);
381
+ this.pendingSpawns.set(envelopeId, { resolve, reject, timeoutHandle });
382
+ const envelope = {
383
+ v: PROTOCOL_VERSION,
384
+ type: 'SPAWN',
385
+ id: envelopeId,
386
+ ts: Date.now(),
387
+ payload: {
388
+ name: options.name,
389
+ cli: options.cli,
390
+ task: options.task || '',
391
+ cwd: options.cwd,
392
+ team: options.team,
393
+ interactive: options.interactive,
394
+ shadowOf: options.shadowOf,
395
+ shadowSpeakOn: options.shadowSpeakOn,
396
+ spawnerName: this.config.agentName,
397
+ },
398
+ };
399
+ const sent = this.send(envelope);
400
+ if (!sent) {
401
+ clearTimeout(timeoutHandle);
402
+ this.pendingSpawns.delete(envelopeId);
403
+ reject(new Error('Failed to send spawn message'));
404
+ }
405
+ });
406
+ }
407
+ /**
408
+ * Release (terminate) an agent via the relay daemon.
409
+ * @param name - Agent name to release
410
+ * @param timeoutMs - Timeout for release operation (default: 10000ms)
411
+ */
412
+ async release(name, timeoutMs = 10000) {
413
+ if (this._state !== 'READY') {
414
+ throw new Error('Client not ready');
415
+ }
416
+ const envelopeId = generateId();
417
+ return new Promise((resolve, reject) => {
418
+ const timeoutHandle = setTimeout(() => {
419
+ this.pendingReleases.delete(envelopeId);
420
+ reject(new Error(`Release timeout after ${timeoutMs}ms`));
421
+ }, timeoutMs);
422
+ this.pendingReleases.set(envelopeId, { resolve, reject, timeoutHandle });
423
+ const envelope = {
424
+ v: PROTOCOL_VERSION,
425
+ type: 'RELEASE',
426
+ id: envelopeId,
427
+ ts: Date.now(),
428
+ payload: {
429
+ name,
430
+ },
431
+ };
432
+ const sent = this.send(envelope);
433
+ if (!sent) {
434
+ clearTimeout(timeoutHandle);
435
+ this.pendingReleases.delete(envelopeId);
436
+ reject(new Error('Failed to send release message'));
437
+ }
438
+ });
439
+ }
440
+ // =============================================================================
353
441
  // Channel Operations
354
442
  // =============================================================================
355
443
  /**
@@ -461,6 +549,188 @@ export class RelayClient {
461
549
  };
462
550
  return this.send(envelope);
463
551
  }
552
+ // =============================================================================
553
+ // Consensus Operations
554
+ // =============================================================================
555
+ /**
556
+ * Create a consensus proposal.
557
+ *
558
+ * The proposal will be broadcast to all participants. They can vote using
559
+ * the `vote()` method. Results are delivered via `onMessage` callback.
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * client.createProposal({
564
+ * title: 'Approve API design',
565
+ * description: 'Should we proceed with the REST API design?',
566
+ * participants: ['Developer', 'Reviewer', 'Lead'],
567
+ * consensusType: 'majority',
568
+ * });
569
+ * ```
570
+ *
571
+ * @param options - Proposal options
572
+ * @returns true if the message was sent
573
+ */
574
+ createProposal(options) {
575
+ if (this._state !== 'READY') {
576
+ return false;
577
+ }
578
+ // Build the PROPOSE command message
579
+ const lines = [
580
+ `PROPOSE: ${options.title}`,
581
+ `TYPE: ${options.consensusType ?? 'majority'}`,
582
+ `PARTICIPANTS: ${options.participants.join(', ')}`,
583
+ `DESCRIPTION: ${options.description}`,
584
+ ];
585
+ if (options.timeoutMs !== undefined) {
586
+ lines.push(`TIMEOUT: ${options.timeoutMs}`);
587
+ }
588
+ if (options.quorum !== undefined) {
589
+ lines.push(`QUORUM: ${options.quorum}`);
590
+ }
591
+ if (options.threshold !== undefined) {
592
+ lines.push(`THRESHOLD: ${options.threshold}`);
593
+ }
594
+ const body = lines.join('\n');
595
+ // Send to the special _consensus recipient
596
+ return this.sendMessage('_consensus', body, 'action');
597
+ }
598
+ /**
599
+ * Vote on a consensus proposal.
600
+ *
601
+ * @example
602
+ * ```typescript
603
+ * // Approve with a reason
604
+ * client.vote({
605
+ * proposalId: 'prop_123',
606
+ * value: 'approve',
607
+ * reason: 'Looks good to me',
608
+ * });
609
+ *
610
+ * // Reject without reason
611
+ * client.vote({ proposalId: 'prop_123', value: 'reject' });
612
+ * ```
613
+ *
614
+ * @param options - Vote options
615
+ * @returns true if the message was sent
616
+ */
617
+ vote(options) {
618
+ if (this._state !== 'READY') {
619
+ return false;
620
+ }
621
+ // Build the VOTE command
622
+ let body = `VOTE ${options.proposalId} ${options.value}`;
623
+ if (options.reason) {
624
+ body += ` ${options.reason}`;
625
+ }
626
+ // Send to the special _consensus recipient
627
+ return this.sendMessage('_consensus', body, 'action');
628
+ }
629
+ // =============================================================================
630
+ // Query Operations
631
+ // =============================================================================
632
+ /**
633
+ * Send a query to the daemon and wait for a response.
634
+ * @internal
635
+ */
636
+ async query(type, payload, timeoutMs = 5000) {
637
+ if (this._state !== 'READY') {
638
+ throw new Error('Client not ready');
639
+ }
640
+ const envelopeId = generateId();
641
+ return new Promise((resolve, reject) => {
642
+ const timeoutHandle = setTimeout(() => {
643
+ this.pendingQueries.delete(envelopeId);
644
+ reject(new Error(`Query timeout after ${timeoutMs}ms`));
645
+ }, timeoutMs);
646
+ this.pendingQueries.set(envelopeId, {
647
+ resolve: resolve,
648
+ reject,
649
+ timeoutHandle,
650
+ });
651
+ const envelope = {
652
+ v: PROTOCOL_VERSION,
653
+ type: type,
654
+ id: envelopeId,
655
+ ts: Date.now(),
656
+ payload,
657
+ };
658
+ const sent = this.send(envelope);
659
+ if (!sent) {
660
+ clearTimeout(timeoutHandle);
661
+ this.pendingQueries.delete(envelopeId);
662
+ reject(new Error(`Failed to send ${type} query`));
663
+ }
664
+ });
665
+ }
666
+ /**
667
+ * Get daemon status information.
668
+ * @returns Daemon status including version, uptime, and counts
669
+ */
670
+ async getStatus() {
671
+ return this.query('STATUS', {});
672
+ }
673
+ /**
674
+ * Get messages from the inbox.
675
+ * @param options - Filter options
676
+ * @param options.limit - Maximum number of messages to return
677
+ * @param options.unreadOnly - Only return unread messages
678
+ * @param options.from - Filter by sender
679
+ * @param options.channel - Filter by channel
680
+ * @returns Array of inbox messages
681
+ */
682
+ async getInbox(options = {}) {
683
+ const payload = {
684
+ agent: this.config.agentName,
685
+ limit: options.limit,
686
+ unreadOnly: options.unreadOnly,
687
+ from: options.from,
688
+ channel: options.channel,
689
+ };
690
+ const response = await this.query('INBOX', payload);
691
+ return response.messages || [];
692
+ }
693
+ /**
694
+ * List online agents.
695
+ * @param options - Filter options
696
+ * @param options.includeIdle - Include idle agents (default: true)
697
+ * @param options.project - Filter by project
698
+ * @returns Array of agent info
699
+ */
700
+ async listAgents(options = {}) {
701
+ const payload = {
702
+ includeIdle: options.includeIdle ?? true,
703
+ project: options.project,
704
+ };
705
+ const response = await this.query('LIST_AGENTS', payload);
706
+ return response.agents || [];
707
+ }
708
+ /**
709
+ * Get system health information.
710
+ * @param options - Include options
711
+ * @param options.includeCrashes - Include crash history (default: true)
712
+ * @param options.includeAlerts - Include alerts (default: true)
713
+ * @returns Health information including score, issues, and recommendations
714
+ */
715
+ async getHealth(options = {}) {
716
+ const payload = {
717
+ includeCrashes: options.includeCrashes ?? true,
718
+ includeAlerts: options.includeAlerts ?? true,
719
+ };
720
+ return this.query('HEALTH', payload);
721
+ }
722
+ /**
723
+ * Get resource metrics for agents.
724
+ * @param options - Filter options
725
+ * @param options.agent - Filter to a specific agent
726
+ * @returns Metrics including memory, CPU, and system info
727
+ */
728
+ async getMetrics(options = {}) {
729
+ const payload = {
730
+ agent: options.agent,
731
+ };
732
+ return this.query('METRICS', payload);
733
+ }
464
734
  // Private methods
465
735
  setState(state) {
466
736
  this._state = state;
@@ -552,6 +822,12 @@ export class RelayClient {
552
822
  case 'ACK':
553
823
  this.handleAck(envelope);
554
824
  break;
825
+ case 'SPAWN_RESULT':
826
+ this.handleSpawnResult(envelope);
827
+ break;
828
+ case 'RELEASE_RESULT':
829
+ this.handleReleaseResult(envelope);
830
+ break;
555
831
  case 'ERROR':
556
832
  this.handleErrorFrame(envelope);
557
833
  break;
@@ -560,6 +836,13 @@ export class RelayClient {
560
836
  console.warn('[sdk] Server busy, backing off');
561
837
  }
562
838
  break;
839
+ case 'STATUS_RESPONSE':
840
+ case 'INBOX_RESPONSE':
841
+ case 'LIST_AGENTS_RESPONSE':
842
+ case 'HEALTH_RESPONSE':
843
+ case 'METRICS_RESPONSE':
844
+ this.handleQueryResponse(envelope);
845
+ break;
563
846
  }
564
847
  }
565
848
  handleWelcome(envelope) {
@@ -627,6 +910,37 @@ export class RelayClient {
627
910
  this.pendingSyncAcks.delete(correlationId);
628
911
  pending.resolve(envelope.payload);
629
912
  }
913
+ handleSpawnResult(envelope) {
914
+ const replyTo = envelope.payload.replyTo;
915
+ if (!replyTo)
916
+ return;
917
+ const pending = this.pendingSpawns.get(replyTo);
918
+ if (!pending)
919
+ return;
920
+ clearTimeout(pending.timeoutHandle);
921
+ this.pendingSpawns.delete(replyTo);
922
+ pending.resolve(envelope.payload);
923
+ }
924
+ handleReleaseResult(envelope) {
925
+ const replyTo = envelope.payload.replyTo;
926
+ if (!replyTo)
927
+ return;
928
+ const pending = this.pendingReleases.get(replyTo);
929
+ if (!pending)
930
+ return;
931
+ clearTimeout(pending.timeoutHandle);
932
+ this.pendingReleases.delete(replyTo);
933
+ pending.resolve(envelope.payload);
934
+ }
935
+ handleQueryResponse(envelope) {
936
+ // Query responses use the envelope id to match requests
937
+ const pending = this.pendingQueries.get(envelope.id);
938
+ if (!pending)
939
+ return;
940
+ clearTimeout(pending.timeoutHandle);
941
+ this.pendingQueries.delete(envelope.id);
942
+ pending.resolve(envelope.payload);
943
+ }
630
944
  handlePing(envelope) {
631
945
  this.send({
632
946
  v: PROTOCOL_VERSION,
@@ -656,6 +970,9 @@ export class RelayClient {
656
970
  this.parser.reset();
657
971
  this.socket = undefined;
658
972
  this.rejectPendingSyncAcks(new Error('Disconnected while awaiting ACK'));
973
+ this.rejectPendingSpawns(new Error('Disconnected while awaiting spawn result'));
974
+ this.rejectPendingReleases(new Error('Disconnected while awaiting release result'));
975
+ this.rejectPendingQueries(new Error('Disconnected while awaiting query response'));
659
976
  if (this._destroyed) {
660
977
  this.setState('DISCONNECTED');
661
978
  return;
@@ -685,6 +1002,27 @@ export class RelayClient {
685
1002
  this.pendingSyncAcks.delete(correlationId);
686
1003
  }
687
1004
  }
1005
+ rejectPendingSpawns(error) {
1006
+ for (const [id, pending] of this.pendingSpawns.entries()) {
1007
+ clearTimeout(pending.timeoutHandle);
1008
+ pending.reject(error);
1009
+ this.pendingSpawns.delete(id);
1010
+ }
1011
+ }
1012
+ rejectPendingReleases(error) {
1013
+ for (const [id, pending] of this.pendingReleases.entries()) {
1014
+ clearTimeout(pending.timeoutHandle);
1015
+ pending.reject(error);
1016
+ this.pendingReleases.delete(id);
1017
+ }
1018
+ }
1019
+ rejectPendingQueries(error) {
1020
+ for (const [id, pending] of this.pendingQueries.entries()) {
1021
+ clearTimeout(pending.timeoutHandle);
1022
+ pending.reject(error);
1023
+ this.pendingQueries.delete(id);
1024
+ }
1025
+ }
688
1026
  scheduleReconnect() {
689
1027
  this.setState('BACKOFF');
690
1028
  this.reconnectAttempts++;
@@ -27,6 +27,7 @@
27
27
  */
28
28
  export { RelayClient, type ClientState, type ClientConfig, type SyncOptions, } from './client.js';
29
29
  export { createRelay, createPair, type Relay, type RelayConfig, } from './standalone.js';
30
- export { PROTOCOL_VERSION, type MessageType, type PayloadKind, type Envelope, type EntityType, type SendPayload, type SendMeta, type SyncMeta, type DeliveryInfo, type AckPayload, type ErrorCode, type ErrorPayload, type SpeakOnTrigger, type ShadowConfig, type SpawnPayload, type SpawnResultPayload, type ReleasePayload, type ReleaseResultPayload, type ChannelMessagePayload, type ChannelJoinPayload, type ChannelLeavePayload, } from './protocol/index.js';
30
+ export { PROTOCOL_VERSION, type MessageType, type PayloadKind, type Envelope, type EntityType, type SendPayload, type SendMeta, type SyncMeta, type DeliveryInfo, type AckPayload, type ErrorCode, type ErrorPayload, type SpeakOnTrigger, type ShadowConfig, type SpawnPayload, type SpawnResultPayload, type ReleasePayload, type ReleaseResultPayload, type ChannelMessagePayload, type ChannelJoinPayload, type ChannelLeavePayload, type MessageAttachment, type StatusResponsePayload, type InboxMessage, type AgentInfo, type HealthResponsePayload, type CrashRecord, type AlertRecord, type MetricsResponsePayload, type AgentMetrics, type ConsensusType, type VoteValue, type ProposalStatus, type CreateProposalOptions, type VoteOptions, } from './protocol/index.js';
31
31
  export { encodeFrame, encodeFrameLegacy, FrameParser, MAX_FRAME_BYTES, } from './protocol/index.js';
32
+ export { getLogs, listLoggedAgents, type GetLogsOptions, type LogsResult, } from './logs.js';
32
33
  //# sourceMappingURL=index.d.ts.map
@@ -33,4 +33,6 @@ export { createRelay, createPair, } from './standalone.js';
33
33
  export { PROTOCOL_VERSION, } from './protocol/index.js';
34
34
  // Framing utilities
35
35
  export { encodeFrame, encodeFrameLegacy, FrameParser, MAX_FRAME_BYTES, } from './protocol/index.js';
36
+ // Log utilities (file-based, doesn't require connection)
37
+ export { getLogs, listLoggedAgents, } from './logs.js';
36
38
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Log reading utilities for Agent Relay SDK.
3
+ *
4
+ * These utilities read agent logs from the local filesystem.
5
+ */
6
+ /**
7
+ * Options for reading agent logs.
8
+ */
9
+ export interface GetLogsOptions {
10
+ /** Directory containing worker logs. Defaults to `.agent-relay/worker-logs` in cwd. */
11
+ logsDir?: string;
12
+ /** Number of lines to return from the end. Default: 50 */
13
+ lines?: number;
14
+ }
15
+ /**
16
+ * Result of a logs query.
17
+ */
18
+ export interface LogsResult {
19
+ /** Agent name */
20
+ agent: string;
21
+ /** Log content */
22
+ content: string;
23
+ /** Whether log file exists */
24
+ found: boolean;
25
+ /** Number of lines returned */
26
+ lineCount: number;
27
+ }
28
+ /**
29
+ * Get logs for a specific agent.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { getLogs } from '@agent-relay/sdk';
34
+ *
35
+ * const result = await getLogs('Worker1', { lines: 100 });
36
+ * if (result.found) {
37
+ * console.log(result.content);
38
+ * }
39
+ * ```
40
+ *
41
+ * @param agent - Agent name
42
+ * @param options - Options for reading logs
43
+ * @returns Log content and metadata
44
+ */
45
+ export declare function getLogs(agent: string, options?: GetLogsOptions): Promise<LogsResult>;
46
+ /**
47
+ * List all agents that have log files.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * import { listLoggedAgents } from '@agent-relay/sdk';
52
+ *
53
+ * const agents = await listLoggedAgents();
54
+ * console.log('Agents with logs:', agents);
55
+ * ```
56
+ *
57
+ * @param logsDir - Directory containing worker logs
58
+ * @returns Array of agent names
59
+ */
60
+ export declare function listLoggedAgents(logsDir?: string): Promise<string[]>;
61
+ //# sourceMappingURL=logs.d.ts.map