agenthub-multiagent-mcp 1.1.5 → 1.4.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.
package/src/client.ts CHANGED
@@ -28,6 +28,76 @@ export interface PendingTask {
28
28
  created_at: string;
29
29
  }
30
30
 
31
+ // Ticket system types
32
+ export interface TicketTask {
33
+ id: string;
34
+ org_id: string;
35
+ project_id: string;
36
+ epic_id?: string;
37
+ story_id?: string;
38
+ key: string;
39
+ type: string;
40
+ title: string;
41
+ description?: string;
42
+ status: string;
43
+ priority: string;
44
+ points?: number;
45
+ review_mode: string;
46
+ assignee_id?: string;
47
+ assigned_agent?: string;
48
+ claimed_by?: string;
49
+ labels?: string[];
50
+ sprint_id?: string;
51
+ due_date?: string;
52
+ created_by: string;
53
+ created_at: string;
54
+ updated_at: string;
55
+ }
56
+
57
+ export interface TicketTaskContext {
58
+ task: TicketTask;
59
+ story?: {
60
+ id: string;
61
+ title: string;
62
+ description?: string;
63
+ status: string;
64
+ };
65
+ epic?: {
66
+ id: string;
67
+ title: string;
68
+ description?: string;
69
+ status: string;
70
+ };
71
+ project?: {
72
+ id: string;
73
+ key: string;
74
+ name: string;
75
+ description?: string;
76
+ };
77
+ }
78
+
79
+ export interface TaskCheckpoint {
80
+ id: string;
81
+ task_id: string;
82
+ task_key: string;
83
+ agent_id: string;
84
+ description: string;
85
+ status: string;
86
+ reviewed_by?: string;
87
+ review_notes?: string;
88
+ reviewed_at?: string;
89
+ created_at: string;
90
+ }
91
+
92
+ export interface CheckpointResponse {
93
+ id: string;
94
+ task_id: string;
95
+ task_key: string;
96
+ status: string;
97
+ slack_posted: boolean;
98
+ created_at: string;
99
+ }
100
+
31
101
  export interface TaskCompletionResponse {
32
102
  task_id: string;
33
103
  elapsed_time: string;
@@ -150,11 +220,19 @@ export class ApiClient {
150
220
  async startWork(
151
221
  agentId: string,
152
222
  task: string,
153
- project?: string
154
- ): Promise<{ acknowledged: boolean; slack_posted: boolean }> {
223
+ project?: string,
224
+ taskId?: string
225
+ ): Promise<{
226
+ acknowledged: boolean;
227
+ slack_posted: boolean;
228
+ task_id?: string;
229
+ task_key?: string;
230
+ task_linked?: boolean;
231
+ }> {
155
232
  return this.request("POST", `/agents/${agentId}/start-work`, {
156
233
  task,
157
234
  project,
235
+ task_id: taskId,
158
236
  });
159
237
  }
160
238
 
@@ -294,4 +372,242 @@ export class ApiClient {
294
372
  reason,
295
373
  });
296
374
  }
375
+
376
+ // Ticket system methods (for agent integration)
377
+
378
+ async getAvailableTasks(params?: {
379
+ status?: string;
380
+ project_id?: string;
381
+ priority?: string;
382
+ required_type?: string;
383
+ }): Promise<{ tasks: TicketTask[]; total: number }> {
384
+ const query = new URLSearchParams();
385
+ if (params?.status) query.set("status", params.status);
386
+ if (params?.project_id) query.set("project_id", params.project_id);
387
+ if (params?.priority) query.set("priority", params.priority);
388
+ if (params?.required_type) query.set("required_type", params.required_type);
389
+ const queryStr = query.toString() ? `?${query}` : "";
390
+ return this.request("GET", `/agent-tickets/available${queryStr}`);
391
+ }
392
+
393
+ async getTicketTask(taskId: string): Promise<TicketTaskContext> {
394
+ return this.request("GET", `/agent-tickets/${taskId}`);
395
+ }
396
+
397
+ async claimTicketTask(
398
+ taskId: string,
399
+ agentId: string
400
+ ): Promise<{
401
+ success: boolean;
402
+ task: TicketTask;
403
+ task_id: string;
404
+ task_key: string;
405
+ }> {
406
+ return this.request("POST", `/agent-tickets/${taskId}/claim`, {
407
+ agent_id: agentId,
408
+ });
409
+ }
410
+
411
+ async createCheckpoint(
412
+ taskId: string,
413
+ agentId: string,
414
+ description: string
415
+ ): Promise<CheckpointResponse> {
416
+ return this.request("POST", `/agent-tickets/${taskId}/checkpoint`, {
417
+ agent_id: agentId,
418
+ description,
419
+ });
420
+ }
421
+
422
+ // Browser-based registration flow
423
+ async initRegistration(agentId?: string, agentName?: string): Promise<{
424
+ session_token: string;
425
+ dashboard_url: string;
426
+ expires_at: string;
427
+ }> {
428
+ return this.request("POST", "/agents/register-init", {
429
+ agent_id: agentId,
430
+ agent_name: agentName,
431
+ });
432
+ }
433
+
434
+ async pollRegistrationCallback(token: string): Promise<{
435
+ status: "pending" | "completed" | "expired";
436
+ agent_id?: string;
437
+ agent_name?: string;
438
+ agent_type?: string;
439
+ connect_token?: string;
440
+ expires_at?: string;
441
+ }> {
442
+ const url = `${this.baseUrl}/agents/register-callback/${token}`;
443
+ const response = await fetch(url, {
444
+ method: "GET",
445
+ headers: {
446
+ "Content-Type": "application/json",
447
+ "X-API-Key": this.apiKey,
448
+ },
449
+ });
450
+
451
+ if (response.status === 202) {
452
+ // Still pending
453
+ const data = await response.json() as { status: string; expires_at: string };
454
+ return { status: "pending", expires_at: data.expires_at };
455
+ }
456
+
457
+ if (response.status === 410) {
458
+ // Expired
459
+ return { status: "expired" };
460
+ }
461
+
462
+ if (!response.ok) {
463
+ const data = await response.json() as { message?: string };
464
+ throw new Error(data.message || `Request failed: ${response.status}`);
465
+ }
466
+
467
+ const data = await response.json() as {
468
+ status: string;
469
+ agent_id: string;
470
+ agent_name: string;
471
+ agent_type: string;
472
+ connect_token: string;
473
+ };
474
+
475
+ return {
476
+ status: "completed",
477
+ agent_id: data.agent_id,
478
+ agent_name: data.agent_name,
479
+ agent_type: data.agent_type,
480
+ connect_token: data.connect_token,
481
+ };
482
+ }
483
+
484
+ getBaseUrl(): string {
485
+ return this.baseUrl;
486
+ }
487
+
488
+ // Ticket creation methods
489
+
490
+ async createEpic(params: {
491
+ project_id: string;
492
+ title: string;
493
+ description?: string;
494
+ source_file?: string;
495
+ }): Promise<{ id: string; key: string }> {
496
+ return this.request("POST", `/projects/${params.project_id}/epics`, {
497
+ title: params.title,
498
+ description: params.description,
499
+ metadata: params.source_file ? JSON.stringify({ source_file: params.source_file }) : undefined,
500
+ });
501
+ }
502
+
503
+ async createStory(params: {
504
+ project_id: string;
505
+ epic_id?: string;
506
+ title: string;
507
+ description?: string;
508
+ required_type?: string;
509
+ labels?: string[];
510
+ order?: number;
511
+ }): Promise<{ id: string; key: string }> {
512
+ return this.request("POST", `/projects/${params.project_id}/stories`, {
513
+ epic_id: params.epic_id,
514
+ title: params.title,
515
+ description: params.description,
516
+ required_type: params.required_type,
517
+ labels: params.labels,
518
+ backlog_order: params.order,
519
+ });
520
+ }
521
+
522
+ async createTask(params: {
523
+ project_id: string;
524
+ story_id?: string;
525
+ epic_id?: string;
526
+ title: string;
527
+ description?: string;
528
+ required_type?: string;
529
+ labels?: string[];
530
+ order?: number;
531
+ }): Promise<{ id: string; key: string }> {
532
+ return this.request("POST", `/projects/${params.project_id}/tasks`, {
533
+ story_id: params.story_id,
534
+ epic_id: params.epic_id,
535
+ title: params.title,
536
+ description: params.description,
537
+ required_type: params.required_type,
538
+ labels: params.labels,
539
+ backlog_order: params.order,
540
+ });
541
+ }
542
+
543
+ async addComment(params: {
544
+ ticket_id: string;
545
+ body: string;
546
+ }): Promise<{ id: string }> {
547
+ return this.request("POST", `/tickets/${params.ticket_id}/comments`, {
548
+ body: params.body,
549
+ });
550
+ }
551
+
552
+ async updateTicketStatus(params: {
553
+ ticket_id: string;
554
+ status: string;
555
+ summary?: string;
556
+ files_changed?: string[];
557
+ }): Promise<{ success: boolean }> {
558
+ return this.request("PATCH", `/tasks/${params.ticket_id}`, {
559
+ status: params.status,
560
+ summary: params.summary,
561
+ files_changed: params.files_changed,
562
+ });
563
+ }
564
+
565
+ async uploadAttachment(params: {
566
+ ticket_id: string;
567
+ ticket_type: string;
568
+ ticket_key: string;
569
+ file_path: string;
570
+ description?: string;
571
+ agent_id?: string;
572
+ }): Promise<{
573
+ id: string;
574
+ url: string;
575
+ filename: string;
576
+ size: number;
577
+ mime_type: string;
578
+ }> {
579
+ const fs = await import("fs");
580
+ const FormData = (await import("form-data")).default;
581
+
582
+ const form = new FormData();
583
+ form.append("ticket_id", params.ticket_id);
584
+ form.append("ticket_type", params.ticket_type);
585
+ form.append("ticket_key", params.ticket_key);
586
+ form.append("file", fs.createReadStream(params.file_path));
587
+ if (params.description) form.append("description", params.description);
588
+ if (params.agent_id) form.append("agent_id", params.agent_id);
589
+
590
+ const response = await fetch(`${this.baseUrl}/attachments`, {
591
+ method: "POST",
592
+ headers: {
593
+ "X-API-Key": this.apiKey,
594
+ ...form.getHeaders(),
595
+ },
596
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
597
+ body: form as any,
598
+ });
599
+
600
+ if (!response.ok) {
601
+ const error = await response.json() as { message?: string };
602
+ throw new Error(error.message || `Upload failed: ${response.status}`);
603
+ }
604
+
605
+ return response.json() as Promise<{
606
+ id: string;
607
+ url: string;
608
+ filename: string;
609
+ size: number;
610
+ mime_type: string;
611
+ }>;
612
+ }
297
613
  }
package/src/heartbeat.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  /**
2
2
  * Background heartbeat manager with enhanced logging for pending tasks/messages
3
+ * and automatic reconnection on failures.
3
4
  */
4
5
 
5
6
  import { ApiClient } from "./client.js";
7
+ import { AgentState } from "./state.js";
8
+
9
+ // Reconnection callback type
10
+ export type ReconnectCallback = () => Promise<AgentState | null>;
6
11
 
7
12
  export class HeartbeatManager {
8
13
  private client: ApiClient;
@@ -13,16 +18,34 @@ export class HeartbeatManager {
13
18
  private lastUnreadMessages = 0;
14
19
  private heartbeatCount = 0;
15
20
 
21
+ // Retry state
22
+ private consecutiveFailures = 0;
23
+ private backoffMs = 1000; // Start at 1 second
24
+ private readonly maxBackoffMs = 30000; // Cap at 30 seconds
25
+ private readonly failuresBeforeReconnect = 3;
26
+ private reconnectCallback?: ReconnectCallback;
27
+ private isReconnecting = false;
28
+
16
29
  constructor(client: ApiClient) {
17
30
  this.client = client;
18
31
  }
19
32
 
33
+ /**
34
+ * Set callback for reconnection attempts
35
+ */
36
+ setReconnectCallback(callback: ReconnectCallback): void {
37
+ this.reconnectCallback = callback;
38
+ }
39
+
20
40
  start(agentId: string): void {
21
41
  this.stop(); // Clear any existing interval
22
42
  this.agentId = agentId;
23
43
  this.heartbeatCount = 0;
24
44
  this.lastPendingTasks = 0;
25
45
  this.lastUnreadMessages = 0;
46
+ this.consecutiveFailures = 0;
47
+ this.backoffMs = 1000;
48
+ this.isReconnecting = false;
26
49
 
27
50
  // Send heartbeat every 30 seconds
28
51
  this.intervalId = setInterval(async () => {
@@ -31,12 +54,12 @@ export class HeartbeatManager {
31
54
  try {
32
55
  await this.sendHeartbeat();
33
56
  } catch (error) {
34
- console.error("Heartbeat failed:", error);
57
+ await this.handleHeartbeatFailure(error);
35
58
  }
36
59
  }, 30_000);
37
60
 
38
61
  // Send initial heartbeat
39
- this.sendHeartbeat();
62
+ this.sendHeartbeat().catch((error) => this.handleHeartbeatFailure(error));
40
63
  }
41
64
 
42
65
  stop(): void {
@@ -57,42 +80,102 @@ export class HeartbeatManager {
57
80
 
58
81
  setStatus(status: "online" | "busy"): void {
59
82
  this.status = status;
60
- this.sendHeartbeat();
83
+ this.sendHeartbeat().catch((error) => this.handleHeartbeatFailure(error));
61
84
  }
62
85
 
63
86
  private async sendHeartbeat(): Promise<void> {
64
87
  if (!this.agentId) return;
65
88
 
66
- try {
67
- const response = await this.client.heartbeat(this.agentId, this.status);
68
- this.heartbeatCount++;
89
+ const response = await this.client.heartbeat(this.agentId, this.status);
69
90
 
70
- const { pending_tasks_count, unread_messages_count } = response;
91
+ // Success - reset failure state
92
+ this.consecutiveFailures = 0;
93
+ this.backoffMs = 1000;
94
+ this.heartbeatCount++;
71
95
 
72
- // Log if there are new pending tasks
73
- if (pending_tasks_count > 0 && pending_tasks_count !== this.lastPendingTasks) {
74
- console.error(`\n🔔 [AgentHub] You have ${pending_tasks_count} pending task(s) waiting! Use get_pending_tasks to view them.\n`);
75
- }
96
+ const { pending_tasks_count, unread_messages_count } = response;
76
97
 
77
- // Log if there are new unread messages
78
- if (unread_messages_count > 0 && unread_messages_count !== this.lastUnreadMessages) {
79
- console.error(`\n📬 [AgentHub] You have ${unread_messages_count} unread message(s)! Use check_inbox to read them.\n`);
98
+ // Log if there are new pending tasks
99
+ if (pending_tasks_count > 0 && pending_tasks_count !== this.lastPendingTasks) {
100
+ console.error(`\n🔔 [AgentHub] You have ${pending_tasks_count} pending task(s) waiting! Use get_pending_tasks to view them.\n`);
101
+ }
102
+
103
+ // Log if there are new unread messages
104
+ if (unread_messages_count > 0 && unread_messages_count !== this.lastUnreadMessages) {
105
+ console.error(`\n📬 [AgentHub] You have ${unread_messages_count} unread message(s)! Use check_inbox to read them.\n`);
106
+ }
107
+
108
+ // Periodic reminder every 5 heartbeats (2.5 minutes) if there are pending items
109
+ if (this.heartbeatCount % 5 === 0) {
110
+ if (pending_tasks_count > 0 || unread_messages_count > 0) {
111
+ const parts = [];
112
+ if (pending_tasks_count > 0) parts.push(`${pending_tasks_count} pending task(s)`);
113
+ if (unread_messages_count > 0) parts.push(`${unread_messages_count} unread message(s)`);
114
+ console.error(`\n⏰ [AgentHub] Reminder: You have ${parts.join(" and ")}.\n`);
80
115
  }
116
+ }
117
+
118
+ this.lastPendingTasks = pending_tasks_count;
119
+ this.lastUnreadMessages = unread_messages_count;
120
+ }
121
+
122
+ private async handleHeartbeatFailure(error: unknown): Promise<void> {
123
+ this.consecutiveFailures++;
124
+ console.error(`[AgentHub] Heartbeat failed (attempt ${this.consecutiveFailures}):`, error);
81
125
 
82
- // Periodic reminder every 5 heartbeats (2.5 minutes) if there are pending items
83
- if (this.heartbeatCount % 5 === 0) {
84
- if (pending_tasks_count > 0 || unread_messages_count > 0) {
85
- const parts = [];
86
- if (pending_tasks_count > 0) parts.push(`${pending_tasks_count} pending task(s)`);
87
- if (unread_messages_count > 0) parts.push(`${unread_messages_count} unread message(s)`);
88
- console.error(`\n⏰ [AgentHub] Reminder: You have ${parts.join(" and ")}.\n`);
89
- }
126
+ // Check if we should attempt full reconnection
127
+ if (this.consecutiveFailures >= this.failuresBeforeReconnect && !this.isReconnecting) {
128
+ await this.attemptReconnection();
129
+ } else {
130
+ // Schedule retry with exponential backoff
131
+ this.scheduleRetry();
132
+ }
133
+ }
134
+
135
+ private scheduleRetry(): void {
136
+ console.error(`[AgentHub] Retrying heartbeat in ${this.backoffMs}ms...`);
137
+
138
+ setTimeout(async () => {
139
+ if (!this.agentId) return;
140
+
141
+ try {
142
+ await this.sendHeartbeat();
143
+ console.error("[AgentHub] Heartbeat recovered!");
144
+ } catch (error) {
145
+ await this.handleHeartbeatFailure(error);
90
146
  }
147
+ }, this.backoffMs);
148
+
149
+ // Increase backoff for next failure (exponential with cap)
150
+ this.backoffMs = Math.min(this.backoffMs * 2, this.maxBackoffMs);
151
+ }
152
+
153
+ private async attemptReconnection(): Promise<void> {
154
+ if (!this.reconnectCallback) {
155
+ console.error("[AgentHub] No reconnect callback configured, continuing retries...");
156
+ this.scheduleRetry();
157
+ return;
158
+ }
159
+
160
+ this.isReconnecting = true;
161
+ console.error("[AgentHub] Attempting full reconnection...");
91
162
 
92
- this.lastPendingTasks = pending_tasks_count;
93
- this.lastUnreadMessages = unread_messages_count;
163
+ try {
164
+ const state = await this.reconnectCallback();
165
+ if (state) {
166
+ this.agentId = state.agent_id;
167
+ this.consecutiveFailures = 0;
168
+ this.backoffMs = 1000;
169
+ this.isReconnecting = false;
170
+ console.error(`[AgentHub] Reconnected successfully as ${state.agent_id}`);
171
+ } else {
172
+ throw new Error("No state available for reconnection");
173
+ }
94
174
  } catch (error) {
95
- console.error("Heartbeat failed:", error);
175
+ console.error("[AgentHub] Reconnection failed:", error);
176
+ this.isReconnecting = false;
177
+ this.consecutiveFailures = 0; // Reset to try again after more failures
178
+ this.scheduleRetry();
96
179
  }
97
180
  }
98
181
 
package/src/index.ts CHANGED
@@ -10,11 +10,14 @@ import {
10
10
  import { ApiClient } from "./client.js";
11
11
  import { registerTools, handleToolCall } from "./tools/index.js";
12
12
  import { HeartbeatManager } from "./heartbeat.js";
13
+ import { WebSocketClient } from "./websocket.js";
14
+ import * as state from "./state.js";
13
15
 
14
16
  // Environment configuration
15
17
  const AGENTHUB_URL = process.env.AGENTHUB_URL || "https://agenthub.contetial.com";
16
18
  const AGENTHUB_API_KEY = process.env.AGENTHUB_API_KEY || "";
17
19
  const AGENTHUB_AGENT_ID = process.env.AGENTHUB_AGENT_ID || "";
20
+ const AGENTHUB_AUTO_RECONNECT = process.env.AGENTHUB_AUTO_RECONNECT !== "false"; // Enabled by default
18
21
 
19
22
  // Validate configuration
20
23
  if (!AGENTHUB_API_KEY) {
@@ -28,14 +31,61 @@ const client = new ApiClient(AGENTHUB_URL, AGENTHUB_API_KEY);
28
31
  // Initialize heartbeat manager
29
32
  const heartbeat = new HeartbeatManager(client);
30
33
 
31
- // Track current agent ID
34
+ // Initialize WebSocket client for push notifications
35
+ const wsClient = new WebSocketClient(AGENTHUB_URL, AGENTHUB_API_KEY);
36
+
37
+ // Track current agent ID and working directory
32
38
  let currentAgentId = AGENTHUB_AGENT_ID;
39
+ const workingDir = process.cwd();
40
+
41
+ /**
42
+ * Attempt to reconnect using stored state
43
+ */
44
+ async function attemptReconnect(): Promise<state.AgentState | null> {
45
+ const existingState = state.loadState(workingDir);
46
+ if (!existingState) {
47
+ return null;
48
+ }
49
+
50
+ const owner = state.getCurrentOwner();
51
+ if (existingState.owner !== owner) {
52
+ console.error("[AgentHub] State file belongs to different user, skipping auto-reconnect");
53
+ return null;
54
+ }
55
+
56
+ try {
57
+ const result = await client.reconnectAgent(
58
+ existingState.agent_id,
59
+ existingState.token,
60
+ owner,
61
+ undefined, // model - keep existing
62
+ existingState.name
63
+ );
64
+
65
+ if (result.reconnected) {
66
+ currentAgentId = result.agent_id;
67
+ // Update state with any changes
68
+ state.saveState(workingDir, {
69
+ ...existingState,
70
+ name: result.name,
71
+ });
72
+ return existingState;
73
+ }
74
+ } catch (error) {
75
+ console.error("[AgentHub] Reconnect attempt failed:", error);
76
+ }
77
+
78
+ return null;
79
+ }
80
+
81
+ // Set up reconnect callback for heartbeat manager
82
+ heartbeat.setReconnectCallback(attemptReconnect);
33
83
 
34
84
  // Create MCP server
35
85
  const server = new Server(
36
86
  {
37
87
  name: "agenthub",
38
- version: "1.1.5",
88
+ version: "1.2.0",
39
89
  },
40
90
  {
41
91
  capabilities: {
@@ -61,9 +111,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
61
111
  setCurrentAgentId: (id: string) => {
62
112
  currentAgentId = id;
63
113
  heartbeat.start(id);
114
+ wsClient.connect(id);
115
+ },
116
+ stopHeartbeat: () => {
117
+ heartbeat.stop();
118
+ wsClient.close();
64
119
  },
65
- stopHeartbeat: () => heartbeat.stop(),
66
120
  getWorkingDir: () => process.cwd(),
121
+ getPushedItems: () => wsClient.getPushedItems(),
67
122
  });
68
123
 
69
124
  return {
@@ -91,11 +146,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
91
146
  // Graceful shutdown
92
147
  process.on("SIGINT", () => {
93
148
  heartbeat.stop();
149
+ wsClient.close();
94
150
  process.exit(0);
95
151
  });
96
152
 
97
153
  process.on("SIGTERM", () => {
98
154
  heartbeat.stop();
155
+ wsClient.close();
99
156
  process.exit(0);
100
157
  });
101
158
 
@@ -105,15 +162,31 @@ async function main() {
105
162
  await server.connect(transport);
106
163
  console.error("AgentHub MCP server running");
107
164
 
108
- // Auto-register if agent ID is provided
165
+ // Auto-register if agent ID is provided via environment
109
166
  if (AGENTHUB_AGENT_ID) {
110
167
  try {
111
168
  await client.registerAgent(AGENTHUB_AGENT_ID);
112
169
  heartbeat.start(AGENTHUB_AGENT_ID);
170
+ wsClient.connect(AGENTHUB_AGENT_ID);
113
171
  console.error(`Auto-registered as agent: ${AGENTHUB_AGENT_ID}`);
114
172
  } catch (error) {
115
173
  console.error(`Failed to auto-register: ${error}`);
116
174
  }
175
+ } else if (AGENTHUB_AUTO_RECONNECT) {
176
+ // Try to auto-reconnect from stored state
177
+ const existingState = state.loadState(workingDir);
178
+ if (existingState) {
179
+ console.error(`[AgentHub] Found existing state for agent: ${existingState.agent_id}`);
180
+ const reconnected = await attemptReconnect();
181
+ if (reconnected) {
182
+ heartbeat.start(reconnected.agent_id);
183
+ wsClient.connect(reconnected.agent_id);
184
+ const displayName = reconnected.name || reconnected.agent_id;
185
+ console.error(`[AgentHub] Auto-reconnected as: ${displayName}`);
186
+ } else {
187
+ console.error("[AgentHub] Auto-reconnect failed, manual registration required");
188
+ }
189
+ }
117
190
  }
118
191
  }
119
192
 
package/src/state.ts CHANGED
@@ -23,6 +23,7 @@ export interface AgentState {
23
23
  name?: string;
24
24
  owner: string;
25
25
  token: string;
26
+ agent_type?: string;
26
27
  last_task?: string;
27
28
  registered_at: string;
28
29
  }