@meshagent/meshagent-agents 0.44.0 → 0.44.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## [0.44.1]
2
+ - Stability
3
+
1
4
  ## [0.44.0]
2
5
  - TypeScript agent clients now support thread watch/unwatch, multiple thread storages, and richer start-thread payloads, including tool-choice metadata.
3
6
  - Chat sessions now preserve event timestamps, reset replay state correctly, and handle more agent message types such as tool-call progress, secret requests, model changes, interrupts, and usage updates.
@@ -137,6 +137,11 @@ export declare class MessagingChatClient extends BaseChatClient {
137
137
  readonly agentName?: string;
138
138
  private readonly pendingParticipantWaits;
139
139
  private started;
140
+ private agentParticipantId?;
141
+ private hasConnected;
142
+ private waitingForParticipant;
143
+ private roomAbortController?;
144
+ private messagingListenersAttached;
140
145
  constructor({ room, agentName }: {
141
146
  room: RoomClient;
142
147
  agentName?: string;
@@ -144,6 +149,8 @@ export declare class MessagingChatClient extends BaseChatClient {
144
149
  localParticipantName(): string | undefined;
145
150
  start(): Promise<void>;
146
151
  stop(): Promise<void>;
152
+ private attachMessagingListeners;
153
+ private detachMessagingListeners;
147
154
  agentParticipant(): RemoteParticipant | null;
148
155
  waitForAgentParticipant({ waitKey }?: {
149
156
  waitKey?: string;
@@ -153,6 +160,10 @@ export declare class MessagingChatClient extends BaseChatClient {
153
160
  ignoreOffline?: boolean;
154
161
  }): Promise<void>;
155
162
  private readonly onMessagingChanged;
163
+ private readonly onMessagingParticipantChanged;
164
+ private readonly onMessagingParticipantRemoved;
165
+ private startRoomStatusListener;
166
+ private reopenOpenSessions;
156
167
  private readonly onMessagingMessage;
157
168
  }
158
169
  export declare class ChatThreadSession extends ChangeEmitter {
@@ -282,11 +282,6 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
282
282
  handleAgentMessage(message, { createdAt, attachment } = {}) {
283
283
  if (message.type === agent_messages_js_1.agentConnectionStatusType) {
284
284
  this._connectionStatus = message;
285
- for (const session of this.sessionsByPath.values()) {
286
- if (session.isOpen) {
287
- session.addAgentMessage(new AgentMessageEvent({ message, createdAt, attachment }));
288
- }
289
- }
290
285
  }
291
286
  this.eventQueue.add(new AgentMessageEvent({ message, createdAt, attachment }));
292
287
  const sourceMessageId = sourceMessageIdFor(message);
@@ -324,11 +319,6 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
324
319
  });
325
320
  this._connectionStatus = event;
326
321
  this.eventQueue.add(new AgentMessageEvent({ message: event }));
327
- for (const session of this.sessionsByPath.values()) {
328
- if (session.isOpen) {
329
- session.addAgentMessage(new AgentMessageEvent({ message: event }));
330
- }
331
- }
332
322
  this.notifyListeners();
333
323
  }
334
324
  removeThreadSession(threadPath) {
@@ -347,10 +337,50 @@ class MessagingChatClient extends BaseChatClient {
347
337
  super();
348
338
  this.pendingParticipantWaits = new Map();
349
339
  this.started = false;
340
+ this.hasConnected = false;
341
+ this.waitingForParticipant = false;
342
+ this.messagingListenersAttached = false;
350
343
  this.onMessagingChanged = () => {
351
344
  const participant = this.agentParticipant();
345
+ const participantId = participant?.id;
346
+ if (participantId !== this.agentParticipantId) {
347
+ if (participantId == null) {
348
+ if (this.agentParticipantId != null) {
349
+ this.waitingForParticipant = true;
350
+ this.emitConnectionStatus({
351
+ status: "disconnected",
352
+ message: "Agent messaging disconnected",
353
+ reason: "participant_disconnected",
354
+ });
355
+ this.emitConnectionStatus({
356
+ status: "reconnecting",
357
+ message: "Waiting for agent messaging",
358
+ reason: "participant_disconnected",
359
+ });
360
+ }
361
+ else if (this.started && this.waitingForParticipant) {
362
+ this.emitConnectionStatus({
363
+ status: "reconnecting",
364
+ message: "Waiting for agent messaging",
365
+ reason: "participant_offline",
366
+ });
367
+ }
368
+ }
369
+ else {
370
+ const status = this.hasConnected ? "reconnected" : "connected";
371
+ this.hasConnected = true;
372
+ this.waitingForParticipant = false;
373
+ this.emitConnectionStatus({
374
+ status,
375
+ message: status === "reconnected" ? "Agent messaging reconnected" : "Agent messaging connected",
376
+ });
377
+ if (status === "reconnected") {
378
+ void this.reopenOpenSessions();
379
+ }
380
+ }
381
+ this.agentParticipantId = participantId;
382
+ }
352
383
  if (participant != null) {
353
- this.emitConnectionStatus({ status: "connected", message: "Agent messaging connected" });
354
384
  for (const wait of this.pendingParticipantWaits.values()) {
355
385
  if (!wait.completed) {
356
386
  wait.complete(participant);
@@ -359,6 +389,12 @@ class MessagingChatClient extends BaseChatClient {
359
389
  }
360
390
  this.notifyListeners();
361
391
  };
392
+ this.onMessagingParticipantChanged = () => {
393
+ this.onMessagingChanged();
394
+ };
395
+ this.onMessagingParticipantRemoved = () => {
396
+ this.onMessagingChanged();
397
+ };
362
398
  this.onMessagingMessage = (event) => {
363
399
  this.onMessagingChanged();
364
400
  const roomMessage = agentRoomMessageFromEvent(event);
@@ -378,7 +414,7 @@ class MessagingChatClient extends BaseChatClient {
378
414
  };
379
415
  this.room = room;
380
416
  this.agentName = agentName;
381
- this.room.messaging.addListener("message", this.onMessagingMessage);
417
+ this.attachMessagingListeners();
382
418
  }
383
419
  localParticipantName() {
384
420
  const name = this.room.localParticipant?.getAttribute("name");
@@ -389,12 +425,18 @@ class MessagingChatClient extends BaseChatClient {
389
425
  return;
390
426
  }
391
427
  this.started = true;
428
+ this.attachMessagingListeners();
392
429
  this.room.messaging.start();
393
- this.room.messaging.enable();
430
+ this.startRoomStatusListener();
431
+ await this.room.messaging.enable();
432
+ this.waitingForParticipant = true;
433
+ this.onMessagingChanged();
394
434
  }
395
435
  async stop() {
396
436
  this.started = false;
397
- this.room.messaging.removeListener("message", this.onMessagingMessage);
437
+ this.roomAbortController?.abort();
438
+ this.roomAbortController = undefined;
439
+ this.detachMessagingListeners();
398
440
  for (const wait of this.pendingParticipantWaits.values()) {
399
441
  if (!wait.completed) {
400
442
  wait.completeError(new Error("Chat client stopped while waiting for an agent participant."));
@@ -402,12 +444,35 @@ class MessagingChatClient extends BaseChatClient {
402
444
  }
403
445
  this.pendingParticipantWaits.clear();
404
446
  }
447
+ attachMessagingListeners() {
448
+ if (this.messagingListenersAttached) {
449
+ return;
450
+ }
451
+ this.room.messaging.addListener("message", this.onMessagingMessage);
452
+ this.room.messaging.addListener("participant_added", this.onMessagingParticipantChanged);
453
+ this.room.messaging.addListener("participant_removed", this.onMessagingParticipantRemoved);
454
+ this.room.messaging.addListener("messaging_enabled", this.onMessagingParticipantChanged);
455
+ this.messagingListenersAttached = true;
456
+ }
457
+ detachMessagingListeners() {
458
+ if (!this.messagingListenersAttached) {
459
+ return;
460
+ }
461
+ this.room.messaging.removeListener("messaging_enabled", this.onMessagingParticipantChanged);
462
+ this.room.messaging.removeListener("participant_removed", this.onMessagingParticipantRemoved);
463
+ this.room.messaging.removeListener("participant_added", this.onMessagingParticipantChanged);
464
+ this.room.messaging.removeListener("message", this.onMessagingMessage);
465
+ this.messagingListenersAttached = false;
466
+ }
405
467
  agentParticipant() {
406
468
  const normalizedAgentName = this.agentName?.trim();
407
469
  for (const participant of this.room.messaging.remoteParticipants) {
408
470
  if (normalizedAgentName != null && normalizedAgentName !== "" && participant.getAttribute("name") !== normalizedAgentName) {
409
471
  continue;
410
472
  }
473
+ if (normalizedAgentName != null && normalizedAgentName !== "") {
474
+ return participant;
475
+ }
411
476
  if (participant.getAttribute("supports_agent_messages") === true) {
412
477
  return participant;
413
478
  }
@@ -432,7 +497,13 @@ class MessagingChatClient extends BaseChatClient {
432
497
  }
433
498
  async sendAgentMessage(message, { attachment, ignoreOffline = false } = {}) {
434
499
  await this.start();
435
- const participant = this.agentParticipant() ?? await this.waitForAgentParticipant({ waitKey: message.messageId });
500
+ let participant = this.agentParticipant();
501
+ if (participant == null) {
502
+ if (ignoreOffline) {
503
+ return;
504
+ }
505
+ participant = await this.waitForAgentParticipant({ waitKey: message.messageId });
506
+ }
436
507
  await this.room.messaging.sendMessage({
437
508
  to: participant,
438
509
  type: agent_messages_js_1.agentRoomMessageType,
@@ -441,6 +512,43 @@ class MessagingChatClient extends BaseChatClient {
441
512
  ignoreOffline,
442
513
  });
443
514
  }
515
+ startRoomStatusListener() {
516
+ if (this.roomAbortController != null) {
517
+ return;
518
+ }
519
+ const controller = new AbortController();
520
+ this.roomAbortController = controller;
521
+ void (async () => {
522
+ for await (const event of this.room.listen({ abortSignal: controller.signal })) {
523
+ if (controller.signal.aborted || event.name !== "room.status") {
524
+ continue;
525
+ }
526
+ const status = String(event.status ?? "").trim().toLowerCase();
527
+ const message = String(event.message ?? "").trim();
528
+ if (status === "connected" || status === "ready" || status === "reconnected") {
529
+ this.waitingForParticipant = true;
530
+ this.onMessagingChanged();
531
+ }
532
+ else if (status === "disconnected" || status === "reconnecting" || status === "connecting") {
533
+ this.agentParticipantId = undefined;
534
+ this.waitingForParticipant = true;
535
+ this.emitConnectionStatus({
536
+ status: status === "disconnected" ? "disconnected" : "reconnecting",
537
+ message: message !== "" ? message : status === "disconnected" ? "Agent messaging disconnected" : "Waiting for agent messaging",
538
+ reason: message !== "" ? message : undefined,
539
+ });
540
+ }
541
+ }
542
+ })().catch(() => undefined);
543
+ }
544
+ async reopenOpenSessions() {
545
+ for (const session of this.sessions) {
546
+ if (!session.isOpen) {
547
+ continue;
548
+ }
549
+ await session.open({ load: true, sinceTurn: session.lastCompletedTurnId });
550
+ }
551
+ }
444
552
  }
445
553
  exports.MessagingChatClient = MessagingChatClient;
446
554
  function isThreadListMessage(message) {
@@ -772,7 +880,7 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
772
880
  return;
773
881
  }
774
882
  if (message.type === agent_messages_js_1.agentToolCallEndedType) {
775
- const itemId = normalizedString(message.itemId);
883
+ const itemId = message.error == null ? undefined : normalizedString(message.itemId);
776
884
  if (itemId != null) {
777
885
  this.upsertItemMessage(`tool_call:${itemId}`, event);
778
886
  }
@@ -93,6 +93,7 @@ export declare class AgentThreadStorageRepository extends ThreadStorageRepositor
93
93
  renameThread(threadPath: string, name: string): Promise<void>;
94
94
  private requestList;
95
95
  private handleEvent;
96
+ private reconnectWatch;
96
97
  private handleThreadsListed;
97
98
  private upsertThread;
98
99
  private entryFromAgentThread;
@@ -367,6 +367,12 @@ class AgentThreadStorageRepository extends ThreadStorageRepository {
367
367
  }
368
368
  handleEvent(event) {
369
369
  const message = event.message;
370
+ if (message.type === "meshagent.agent.connection.status") {
371
+ if (String(message.status ?? "").trim().toLowerCase() === "reconnected") {
372
+ void this.reconnectWatch();
373
+ }
374
+ return;
375
+ }
370
376
  if (message.type === "meshagent.agent.thread.listed") {
371
377
  this.handleThreadsListed(message);
372
378
  return;
@@ -386,6 +392,13 @@ class AgentThreadStorageRepository extends ThreadStorageRepository {
386
392
  }
387
393
  }
388
394
  }
395
+ async reconnectWatch() {
396
+ if (this.closed) {
397
+ return;
398
+ }
399
+ await this.chatClient.sendAgentMessage(new agent_messages_js_1.WatchThreads(), { ignoreOffline: true });
400
+ await this.requestList();
401
+ }
389
402
  handleThreadsListed(message) {
390
403
  const nextEntries = new Map();
391
404
  for (const thread of message.threads) {
@@ -137,6 +137,11 @@ export declare class MessagingChatClient extends BaseChatClient {
137
137
  readonly agentName?: string;
138
138
  private readonly pendingParticipantWaits;
139
139
  private started;
140
+ private agentParticipantId?;
141
+ private hasConnected;
142
+ private waitingForParticipant;
143
+ private roomAbortController?;
144
+ private messagingListenersAttached;
140
145
  constructor({ room, agentName }: {
141
146
  room: RoomClient;
142
147
  agentName?: string;
@@ -144,6 +149,8 @@ export declare class MessagingChatClient extends BaseChatClient {
144
149
  localParticipantName(): string | undefined;
145
150
  start(): Promise<void>;
146
151
  stop(): Promise<void>;
152
+ private attachMessagingListeners;
153
+ private detachMessagingListeners;
147
154
  agentParticipant(): RemoteParticipant | null;
148
155
  waitForAgentParticipant({ waitKey }?: {
149
156
  waitKey?: string;
@@ -153,6 +160,10 @@ export declare class MessagingChatClient extends BaseChatClient {
153
160
  ignoreOffline?: boolean;
154
161
  }): Promise<void>;
155
162
  private readonly onMessagingChanged;
163
+ private readonly onMessagingParticipantChanged;
164
+ private readonly onMessagingParticipantRemoved;
165
+ private startRoomStatusListener;
166
+ private reopenOpenSessions;
156
167
  private readonly onMessagingMessage;
157
168
  }
158
169
  export declare class ChatThreadSession extends ChangeEmitter {
@@ -275,11 +275,6 @@ export class BaseChatClient extends ChangeEmitter {
275
275
  handleAgentMessage(message, { createdAt, attachment } = {}) {
276
276
  if (message.type === agentConnectionStatusType) {
277
277
  this._connectionStatus = message;
278
- for (const session of this.sessionsByPath.values()) {
279
- if (session.isOpen) {
280
- session.addAgentMessage(new AgentMessageEvent({ message, createdAt, attachment }));
281
- }
282
- }
283
278
  }
284
279
  this.eventQueue.add(new AgentMessageEvent({ message, createdAt, attachment }));
285
280
  const sourceMessageId = sourceMessageIdFor(message);
@@ -317,11 +312,6 @@ export class BaseChatClient extends ChangeEmitter {
317
312
  });
318
313
  this._connectionStatus = event;
319
314
  this.eventQueue.add(new AgentMessageEvent({ message: event }));
320
- for (const session of this.sessionsByPath.values()) {
321
- if (session.isOpen) {
322
- session.addAgentMessage(new AgentMessageEvent({ message: event }));
323
- }
324
- }
325
315
  this.notifyListeners();
326
316
  }
327
317
  removeThreadSession(threadPath) {
@@ -339,10 +329,50 @@ export class MessagingChatClient extends BaseChatClient {
339
329
  super();
340
330
  this.pendingParticipantWaits = new Map();
341
331
  this.started = false;
332
+ this.hasConnected = false;
333
+ this.waitingForParticipant = false;
334
+ this.messagingListenersAttached = false;
342
335
  this.onMessagingChanged = () => {
343
336
  const participant = this.agentParticipant();
337
+ const participantId = participant?.id;
338
+ if (participantId !== this.agentParticipantId) {
339
+ if (participantId == null) {
340
+ if (this.agentParticipantId != null) {
341
+ this.waitingForParticipant = true;
342
+ this.emitConnectionStatus({
343
+ status: "disconnected",
344
+ message: "Agent messaging disconnected",
345
+ reason: "participant_disconnected",
346
+ });
347
+ this.emitConnectionStatus({
348
+ status: "reconnecting",
349
+ message: "Waiting for agent messaging",
350
+ reason: "participant_disconnected",
351
+ });
352
+ }
353
+ else if (this.started && this.waitingForParticipant) {
354
+ this.emitConnectionStatus({
355
+ status: "reconnecting",
356
+ message: "Waiting for agent messaging",
357
+ reason: "participant_offline",
358
+ });
359
+ }
360
+ }
361
+ else {
362
+ const status = this.hasConnected ? "reconnected" : "connected";
363
+ this.hasConnected = true;
364
+ this.waitingForParticipant = false;
365
+ this.emitConnectionStatus({
366
+ status,
367
+ message: status === "reconnected" ? "Agent messaging reconnected" : "Agent messaging connected",
368
+ });
369
+ if (status === "reconnected") {
370
+ void this.reopenOpenSessions();
371
+ }
372
+ }
373
+ this.agentParticipantId = participantId;
374
+ }
344
375
  if (participant != null) {
345
- this.emitConnectionStatus({ status: "connected", message: "Agent messaging connected" });
346
376
  for (const wait of this.pendingParticipantWaits.values()) {
347
377
  if (!wait.completed) {
348
378
  wait.complete(participant);
@@ -351,6 +381,12 @@ export class MessagingChatClient extends BaseChatClient {
351
381
  }
352
382
  this.notifyListeners();
353
383
  };
384
+ this.onMessagingParticipantChanged = () => {
385
+ this.onMessagingChanged();
386
+ };
387
+ this.onMessagingParticipantRemoved = () => {
388
+ this.onMessagingChanged();
389
+ };
354
390
  this.onMessagingMessage = (event) => {
355
391
  this.onMessagingChanged();
356
392
  const roomMessage = agentRoomMessageFromEvent(event);
@@ -370,7 +406,7 @@ export class MessagingChatClient extends BaseChatClient {
370
406
  };
371
407
  this.room = room;
372
408
  this.agentName = agentName;
373
- this.room.messaging.addListener("message", this.onMessagingMessage);
409
+ this.attachMessagingListeners();
374
410
  }
375
411
  localParticipantName() {
376
412
  const name = this.room.localParticipant?.getAttribute("name");
@@ -381,12 +417,18 @@ export class MessagingChatClient extends BaseChatClient {
381
417
  return;
382
418
  }
383
419
  this.started = true;
420
+ this.attachMessagingListeners();
384
421
  this.room.messaging.start();
385
- this.room.messaging.enable();
422
+ this.startRoomStatusListener();
423
+ await this.room.messaging.enable();
424
+ this.waitingForParticipant = true;
425
+ this.onMessagingChanged();
386
426
  }
387
427
  async stop() {
388
428
  this.started = false;
389
- this.room.messaging.removeListener("message", this.onMessagingMessage);
429
+ this.roomAbortController?.abort();
430
+ this.roomAbortController = undefined;
431
+ this.detachMessagingListeners();
390
432
  for (const wait of this.pendingParticipantWaits.values()) {
391
433
  if (!wait.completed) {
392
434
  wait.completeError(new Error("Chat client stopped while waiting for an agent participant."));
@@ -394,12 +436,35 @@ export class MessagingChatClient extends BaseChatClient {
394
436
  }
395
437
  this.pendingParticipantWaits.clear();
396
438
  }
439
+ attachMessagingListeners() {
440
+ if (this.messagingListenersAttached) {
441
+ return;
442
+ }
443
+ this.room.messaging.addListener("message", this.onMessagingMessage);
444
+ this.room.messaging.addListener("participant_added", this.onMessagingParticipantChanged);
445
+ this.room.messaging.addListener("participant_removed", this.onMessagingParticipantRemoved);
446
+ this.room.messaging.addListener("messaging_enabled", this.onMessagingParticipantChanged);
447
+ this.messagingListenersAttached = true;
448
+ }
449
+ detachMessagingListeners() {
450
+ if (!this.messagingListenersAttached) {
451
+ return;
452
+ }
453
+ this.room.messaging.removeListener("messaging_enabled", this.onMessagingParticipantChanged);
454
+ this.room.messaging.removeListener("participant_removed", this.onMessagingParticipantRemoved);
455
+ this.room.messaging.removeListener("participant_added", this.onMessagingParticipantChanged);
456
+ this.room.messaging.removeListener("message", this.onMessagingMessage);
457
+ this.messagingListenersAttached = false;
458
+ }
397
459
  agentParticipant() {
398
460
  const normalizedAgentName = this.agentName?.trim();
399
461
  for (const participant of this.room.messaging.remoteParticipants) {
400
462
  if (normalizedAgentName != null && normalizedAgentName !== "" && participant.getAttribute("name") !== normalizedAgentName) {
401
463
  continue;
402
464
  }
465
+ if (normalizedAgentName != null && normalizedAgentName !== "") {
466
+ return participant;
467
+ }
403
468
  if (participant.getAttribute("supports_agent_messages") === true) {
404
469
  return participant;
405
470
  }
@@ -424,7 +489,13 @@ export class MessagingChatClient extends BaseChatClient {
424
489
  }
425
490
  async sendAgentMessage(message, { attachment, ignoreOffline = false } = {}) {
426
491
  await this.start();
427
- const participant = this.agentParticipant() ?? await this.waitForAgentParticipant({ waitKey: message.messageId });
492
+ let participant = this.agentParticipant();
493
+ if (participant == null) {
494
+ if (ignoreOffline) {
495
+ return;
496
+ }
497
+ participant = await this.waitForAgentParticipant({ waitKey: message.messageId });
498
+ }
428
499
  await this.room.messaging.sendMessage({
429
500
  to: participant,
430
501
  type: agentRoomMessageType,
@@ -433,6 +504,43 @@ export class MessagingChatClient extends BaseChatClient {
433
504
  ignoreOffline,
434
505
  });
435
506
  }
507
+ startRoomStatusListener() {
508
+ if (this.roomAbortController != null) {
509
+ return;
510
+ }
511
+ const controller = new AbortController();
512
+ this.roomAbortController = controller;
513
+ void (async () => {
514
+ for await (const event of this.room.listen({ abortSignal: controller.signal })) {
515
+ if (controller.signal.aborted || event.name !== "room.status") {
516
+ continue;
517
+ }
518
+ const status = String(event.status ?? "").trim().toLowerCase();
519
+ const message = String(event.message ?? "").trim();
520
+ if (status === "connected" || status === "ready" || status === "reconnected") {
521
+ this.waitingForParticipant = true;
522
+ this.onMessagingChanged();
523
+ }
524
+ else if (status === "disconnected" || status === "reconnecting" || status === "connecting") {
525
+ this.agentParticipantId = undefined;
526
+ this.waitingForParticipant = true;
527
+ this.emitConnectionStatus({
528
+ status: status === "disconnected" ? "disconnected" : "reconnecting",
529
+ message: message !== "" ? message : status === "disconnected" ? "Agent messaging disconnected" : "Waiting for agent messaging",
530
+ reason: message !== "" ? message : undefined,
531
+ });
532
+ }
533
+ }
534
+ })().catch(() => undefined);
535
+ }
536
+ async reopenOpenSessions() {
537
+ for (const session of this.sessions) {
538
+ if (!session.isOpen) {
539
+ continue;
540
+ }
541
+ await session.open({ load: true, sinceTurn: session.lastCompletedTurnId });
542
+ }
543
+ }
436
544
  }
437
545
  function isThreadListMessage(message) {
438
546
  return message.type === agentThreadListedType
@@ -763,7 +871,7 @@ export class ChatThreadSession extends ChangeEmitter {
763
871
  return;
764
872
  }
765
873
  if (message.type === agentToolCallEndedType) {
766
- const itemId = normalizedString(message.itemId);
874
+ const itemId = message.error == null ? undefined : normalizedString(message.itemId);
767
875
  if (itemId != null) {
768
876
  this.upsertItemMessage(`tool_call:${itemId}`, event);
769
877
  }
@@ -93,6 +93,7 @@ export declare class AgentThreadStorageRepository extends ThreadStorageRepositor
93
93
  renameThread(threadPath: string, name: string): Promise<void>;
94
94
  private requestList;
95
95
  private handleEvent;
96
+ private reconnectWatch;
96
97
  private handleThreadsListed;
97
98
  private upsertThread;
98
99
  private entryFromAgentThread;
@@ -358,6 +358,12 @@ export class AgentThreadStorageRepository extends ThreadStorageRepository {
358
358
  }
359
359
  handleEvent(event) {
360
360
  const message = event.message;
361
+ if (message.type === "meshagent.agent.connection.status") {
362
+ if (String(message.status ?? "").trim().toLowerCase() === "reconnected") {
363
+ void this.reconnectWatch();
364
+ }
365
+ return;
366
+ }
361
367
  if (message.type === "meshagent.agent.thread.listed") {
362
368
  this.handleThreadsListed(message);
363
369
  return;
@@ -377,6 +383,13 @@ export class AgentThreadStorageRepository extends ThreadStorageRepository {
377
383
  }
378
384
  }
379
385
  }
386
+ async reconnectWatch() {
387
+ if (this.closed) {
388
+ return;
389
+ }
390
+ await this.chatClient.sendAgentMessage(new WatchThreads(), { ignoreOffline: true });
391
+ await this.requestList();
392
+ }
380
393
  handleThreadsListed(message) {
381
394
  const nextEntries = new Map();
382
395
  for (const thread of message.threads) {
@@ -137,6 +137,11 @@ export declare class MessagingChatClient extends BaseChatClient {
137
137
  readonly agentName?: string;
138
138
  private readonly pendingParticipantWaits;
139
139
  private started;
140
+ private agentParticipantId?;
141
+ private hasConnected;
142
+ private waitingForParticipant;
143
+ private roomAbortController?;
144
+ private messagingListenersAttached;
140
145
  constructor({ room, agentName }: {
141
146
  room: RoomClient;
142
147
  agentName?: string;
@@ -144,6 +149,8 @@ export declare class MessagingChatClient extends BaseChatClient {
144
149
  localParticipantName(): string | undefined;
145
150
  start(): Promise<void>;
146
151
  stop(): Promise<void>;
152
+ private attachMessagingListeners;
153
+ private detachMessagingListeners;
147
154
  agentParticipant(): RemoteParticipant | null;
148
155
  waitForAgentParticipant({ waitKey }?: {
149
156
  waitKey?: string;
@@ -153,6 +160,10 @@ export declare class MessagingChatClient extends BaseChatClient {
153
160
  ignoreOffline?: boolean;
154
161
  }): Promise<void>;
155
162
  private readonly onMessagingChanged;
163
+ private readonly onMessagingParticipantChanged;
164
+ private readonly onMessagingParticipantRemoved;
165
+ private startRoomStatusListener;
166
+ private reopenOpenSessions;
156
167
  private readonly onMessagingMessage;
157
168
  }
158
169
  export declare class ChatThreadSession extends ChangeEmitter {
@@ -282,11 +282,6 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
282
282
  handleAgentMessage(message, { createdAt, attachment } = {}) {
283
283
  if (message.type === agent_messages_js_1.agentConnectionStatusType) {
284
284
  this._connectionStatus = message;
285
- for (const session of this.sessionsByPath.values()) {
286
- if (session.isOpen) {
287
- session.addAgentMessage(new AgentMessageEvent({ message, createdAt, attachment }));
288
- }
289
- }
290
285
  }
291
286
  this.eventQueue.add(new AgentMessageEvent({ message, createdAt, attachment }));
292
287
  const sourceMessageId = sourceMessageIdFor(message);
@@ -324,11 +319,6 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
324
319
  });
325
320
  this._connectionStatus = event;
326
321
  this.eventQueue.add(new AgentMessageEvent({ message: event }));
327
- for (const session of this.sessionsByPath.values()) {
328
- if (session.isOpen) {
329
- session.addAgentMessage(new AgentMessageEvent({ message: event }));
330
- }
331
- }
332
322
  this.notifyListeners();
333
323
  }
334
324
  removeThreadSession(threadPath) {
@@ -347,10 +337,50 @@ class MessagingChatClient extends BaseChatClient {
347
337
  super();
348
338
  this.pendingParticipantWaits = new Map();
349
339
  this.started = false;
340
+ this.hasConnected = false;
341
+ this.waitingForParticipant = false;
342
+ this.messagingListenersAttached = false;
350
343
  this.onMessagingChanged = () => {
351
344
  const participant = this.agentParticipant();
345
+ const participantId = participant?.id;
346
+ if (participantId !== this.agentParticipantId) {
347
+ if (participantId == null) {
348
+ if (this.agentParticipantId != null) {
349
+ this.waitingForParticipant = true;
350
+ this.emitConnectionStatus({
351
+ status: "disconnected",
352
+ message: "Agent messaging disconnected",
353
+ reason: "participant_disconnected",
354
+ });
355
+ this.emitConnectionStatus({
356
+ status: "reconnecting",
357
+ message: "Waiting for agent messaging",
358
+ reason: "participant_disconnected",
359
+ });
360
+ }
361
+ else if (this.started && this.waitingForParticipant) {
362
+ this.emitConnectionStatus({
363
+ status: "reconnecting",
364
+ message: "Waiting for agent messaging",
365
+ reason: "participant_offline",
366
+ });
367
+ }
368
+ }
369
+ else {
370
+ const status = this.hasConnected ? "reconnected" : "connected";
371
+ this.hasConnected = true;
372
+ this.waitingForParticipant = false;
373
+ this.emitConnectionStatus({
374
+ status,
375
+ message: status === "reconnected" ? "Agent messaging reconnected" : "Agent messaging connected",
376
+ });
377
+ if (status === "reconnected") {
378
+ void this.reopenOpenSessions();
379
+ }
380
+ }
381
+ this.agentParticipantId = participantId;
382
+ }
352
383
  if (participant != null) {
353
- this.emitConnectionStatus({ status: "connected", message: "Agent messaging connected" });
354
384
  for (const wait of this.pendingParticipantWaits.values()) {
355
385
  if (!wait.completed) {
356
386
  wait.complete(participant);
@@ -359,6 +389,12 @@ class MessagingChatClient extends BaseChatClient {
359
389
  }
360
390
  this.notifyListeners();
361
391
  };
392
+ this.onMessagingParticipantChanged = () => {
393
+ this.onMessagingChanged();
394
+ };
395
+ this.onMessagingParticipantRemoved = () => {
396
+ this.onMessagingChanged();
397
+ };
362
398
  this.onMessagingMessage = (event) => {
363
399
  this.onMessagingChanged();
364
400
  const roomMessage = agentRoomMessageFromEvent(event);
@@ -378,7 +414,7 @@ class MessagingChatClient extends BaseChatClient {
378
414
  };
379
415
  this.room = room;
380
416
  this.agentName = agentName;
381
- this.room.messaging.addListener("message", this.onMessagingMessage);
417
+ this.attachMessagingListeners();
382
418
  }
383
419
  localParticipantName() {
384
420
  const name = this.room.localParticipant?.getAttribute("name");
@@ -389,12 +425,18 @@ class MessagingChatClient extends BaseChatClient {
389
425
  return;
390
426
  }
391
427
  this.started = true;
428
+ this.attachMessagingListeners();
392
429
  this.room.messaging.start();
393
- this.room.messaging.enable();
430
+ this.startRoomStatusListener();
431
+ await this.room.messaging.enable();
432
+ this.waitingForParticipant = true;
433
+ this.onMessagingChanged();
394
434
  }
395
435
  async stop() {
396
436
  this.started = false;
397
- this.room.messaging.removeListener("message", this.onMessagingMessage);
437
+ this.roomAbortController?.abort();
438
+ this.roomAbortController = undefined;
439
+ this.detachMessagingListeners();
398
440
  for (const wait of this.pendingParticipantWaits.values()) {
399
441
  if (!wait.completed) {
400
442
  wait.completeError(new Error("Chat client stopped while waiting for an agent participant."));
@@ -402,12 +444,35 @@ class MessagingChatClient extends BaseChatClient {
402
444
  }
403
445
  this.pendingParticipantWaits.clear();
404
446
  }
447
+ attachMessagingListeners() {
448
+ if (this.messagingListenersAttached) {
449
+ return;
450
+ }
451
+ this.room.messaging.addListener("message", this.onMessagingMessage);
452
+ this.room.messaging.addListener("participant_added", this.onMessagingParticipantChanged);
453
+ this.room.messaging.addListener("participant_removed", this.onMessagingParticipantRemoved);
454
+ this.room.messaging.addListener("messaging_enabled", this.onMessagingParticipantChanged);
455
+ this.messagingListenersAttached = true;
456
+ }
457
+ detachMessagingListeners() {
458
+ if (!this.messagingListenersAttached) {
459
+ return;
460
+ }
461
+ this.room.messaging.removeListener("messaging_enabled", this.onMessagingParticipantChanged);
462
+ this.room.messaging.removeListener("participant_removed", this.onMessagingParticipantRemoved);
463
+ this.room.messaging.removeListener("participant_added", this.onMessagingParticipantChanged);
464
+ this.room.messaging.removeListener("message", this.onMessagingMessage);
465
+ this.messagingListenersAttached = false;
466
+ }
405
467
  agentParticipant() {
406
468
  const normalizedAgentName = this.agentName?.trim();
407
469
  for (const participant of this.room.messaging.remoteParticipants) {
408
470
  if (normalizedAgentName != null && normalizedAgentName !== "" && participant.getAttribute("name") !== normalizedAgentName) {
409
471
  continue;
410
472
  }
473
+ if (normalizedAgentName != null && normalizedAgentName !== "") {
474
+ return participant;
475
+ }
411
476
  if (participant.getAttribute("supports_agent_messages") === true) {
412
477
  return participant;
413
478
  }
@@ -432,7 +497,13 @@ class MessagingChatClient extends BaseChatClient {
432
497
  }
433
498
  async sendAgentMessage(message, { attachment, ignoreOffline = false } = {}) {
434
499
  await this.start();
435
- const participant = this.agentParticipant() ?? await this.waitForAgentParticipant({ waitKey: message.messageId });
500
+ let participant = this.agentParticipant();
501
+ if (participant == null) {
502
+ if (ignoreOffline) {
503
+ return;
504
+ }
505
+ participant = await this.waitForAgentParticipant({ waitKey: message.messageId });
506
+ }
436
507
  await this.room.messaging.sendMessage({
437
508
  to: participant,
438
509
  type: agent_messages_js_1.agentRoomMessageType,
@@ -441,6 +512,43 @@ class MessagingChatClient extends BaseChatClient {
441
512
  ignoreOffline,
442
513
  });
443
514
  }
515
+ startRoomStatusListener() {
516
+ if (this.roomAbortController != null) {
517
+ return;
518
+ }
519
+ const controller = new AbortController();
520
+ this.roomAbortController = controller;
521
+ void (async () => {
522
+ for await (const event of this.room.listen({ abortSignal: controller.signal })) {
523
+ if (controller.signal.aborted || event.name !== "room.status") {
524
+ continue;
525
+ }
526
+ const status = String(event.status ?? "").trim().toLowerCase();
527
+ const message = String(event.message ?? "").trim();
528
+ if (status === "connected" || status === "ready" || status === "reconnected") {
529
+ this.waitingForParticipant = true;
530
+ this.onMessagingChanged();
531
+ }
532
+ else if (status === "disconnected" || status === "reconnecting" || status === "connecting") {
533
+ this.agentParticipantId = undefined;
534
+ this.waitingForParticipant = true;
535
+ this.emitConnectionStatus({
536
+ status: status === "disconnected" ? "disconnected" : "reconnecting",
537
+ message: message !== "" ? message : status === "disconnected" ? "Agent messaging disconnected" : "Waiting for agent messaging",
538
+ reason: message !== "" ? message : undefined,
539
+ });
540
+ }
541
+ }
542
+ })().catch(() => undefined);
543
+ }
544
+ async reopenOpenSessions() {
545
+ for (const session of this.sessions) {
546
+ if (!session.isOpen) {
547
+ continue;
548
+ }
549
+ await session.open({ load: true, sinceTurn: session.lastCompletedTurnId });
550
+ }
551
+ }
444
552
  }
445
553
  exports.MessagingChatClient = MessagingChatClient;
446
554
  function isThreadListMessage(message) {
@@ -772,7 +880,7 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
772
880
  return;
773
881
  }
774
882
  if (message.type === agent_messages_js_1.agentToolCallEndedType) {
775
- const itemId = normalizedString(message.itemId);
883
+ const itemId = message.error == null ? undefined : normalizedString(message.itemId);
776
884
  if (itemId != null) {
777
885
  this.upsertItemMessage(`tool_call:${itemId}`, event);
778
886
  }
@@ -93,6 +93,7 @@ export declare class AgentThreadStorageRepository extends ThreadStorageRepositor
93
93
  renameThread(threadPath: string, name: string): Promise<void>;
94
94
  private requestList;
95
95
  private handleEvent;
96
+ private reconnectWatch;
96
97
  private handleThreadsListed;
97
98
  private upsertThread;
98
99
  private entryFromAgentThread;
@@ -367,6 +367,12 @@ class AgentThreadStorageRepository extends ThreadStorageRepository {
367
367
  }
368
368
  handleEvent(event) {
369
369
  const message = event.message;
370
+ if (message.type === "meshagent.agent.connection.status") {
371
+ if (String(message.status ?? "").trim().toLowerCase() === "reconnected") {
372
+ void this.reconnectWatch();
373
+ }
374
+ return;
375
+ }
370
376
  if (message.type === "meshagent.agent.thread.listed") {
371
377
  this.handleThreadsListed(message);
372
378
  return;
@@ -386,6 +392,13 @@ class AgentThreadStorageRepository extends ThreadStorageRepository {
386
392
  }
387
393
  }
388
394
  }
395
+ async reconnectWatch() {
396
+ if (this.closed) {
397
+ return;
398
+ }
399
+ await this.chatClient.sendAgentMessage(new agent_messages_js_1.WatchThreads(), { ignoreOffline: true });
400
+ await this.requestList();
401
+ }
389
402
  handleThreadsListed(message) {
390
403
  const nextEntries = new Map();
391
404
  for (const thread of message.threads) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshagent/meshagent-agents",
3
- "version": "0.44.0",
3
+ "version": "0.44.1",
4
4
  "description": "Agent chat sessions and clients for Meshagent TypeScript applications.",
5
5
  "homepage": "https://github.com/meshagent/meshagent-agents-ts",
6
6
  "scripts": {
@@ -47,7 +47,7 @@
47
47
  "typescript": "^5.8.2"
48
48
  },
49
49
  "dependencies": {
50
- "@meshagent/meshagent": "^0.44.0",
50
+ "@meshagent/meshagent": "^0.44.1",
51
51
  "apache-arrow": "^21.0.0",
52
52
  "uuid": "^14.0.0"
53
53
  },