@canonmsg/agent-sdk 3.2.3 → 3.2.5

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.
@@ -19,6 +19,7 @@ export declare class RealtimeManager {
19
19
  /** Lower bound for catch-up in conversations with no in-memory cursor. */
20
20
  private readonly replaySyncStartedAt;
21
21
  private replayCatchupInFlight;
22
+ private hasConnectedOnce;
22
23
  private lastSseErrorKey;
23
24
  private lastSseErrorAt;
24
25
  private suppressedSseErrorCount;
@@ -33,6 +34,7 @@ export declare class RealtimeManager {
33
34
  private onConnected;
34
35
  private onDisconnected;
35
36
  constructor(apiKey: string, debouncer: Debouncer, agentId: string, streamUrl?: string, apiClient?: CanonClient);
37
+ private queueReplayCatchup;
36
38
  private hasSeenInboundMessage;
37
39
  private recordSeenInboundMessage;
38
40
  private pruneRecentInboundMessageIds;
package/dist/realtime.js CHANGED
@@ -34,6 +34,7 @@ export class RealtimeManager {
34
34
  /** Lower bound for catch-up in conversations with no in-memory cursor. */
35
35
  replaySyncStartedAt = Date.now();
36
36
  replayCatchupInFlight = null;
37
+ hasConnectedOnce = false;
37
38
  lastSseErrorKey = null;
38
39
  lastSseErrorAt = 0;
39
40
  suppressedSseErrorCount = 0;
@@ -122,16 +123,19 @@ export class RealtimeManager {
122
123
  },
123
124
  onConnected: () => {
124
125
  // Reset backoff is handled internally by CanonStream
126
+ const shouldCatchUp = this.hasConnectedOnce;
127
+ this.hasConnectedOnce = true;
125
128
  this.onConnected?.();
129
+ if (shouldCatchUp) {
130
+ this.queueReplayCatchup();
131
+ }
126
132
  },
127
133
  onDisconnected: () => {
128
134
  this.onDisconnected?.();
129
135
  },
130
136
  onReplayExpired: (payload) => {
131
137
  console.error(`[canon-sdk] SSE replay window expired${payload.lastAvailableId ? ` (oldest available event: ${payload.lastAvailableId})` : ''} — catching up over REST`);
132
- this.replayCatchupInFlight ??= this.runReplayCatchup().finally(() => {
133
- this.replayCatchupInFlight = null;
134
- });
138
+ this.queueReplayCatchup();
135
139
  },
136
140
  onError: (err) => {
137
141
  this.logSseError(err);
@@ -139,6 +143,11 @@ export class RealtimeManager {
139
143
  },
140
144
  });
141
145
  }
146
+ queueReplayCatchup() {
147
+ this.replayCatchupInFlight ??= this.runReplayCatchup().finally(() => {
148
+ this.replayCatchupInFlight = null;
149
+ });
150
+ }
142
151
  hasSeenInboundMessage(conversationId, messageId) {
143
152
  return this.recentInboundMessageIds.has(`${conversationId}:${messageId}`);
144
153
  }
@@ -202,7 +211,13 @@ export class RealtimeManager {
202
211
  if (message.senderId === this.agentId)
203
212
  continue;
204
213
  const createdAtMs = messageCreatedAtMs(message.createdAt);
205
- if (!createdAtMs || createdAtMs <= lowerBoundMs)
214
+ // Use a strict lower bound of `< lowerBoundMs` (not `<=`): a
215
+ // gap-dropped message can share the same createdAt millisecond as
216
+ // the last message seen over SSE (server timestamps collide under
217
+ // bursts — exactly the scenario catch-up targets). The id-dedupe on
218
+ // the next line suppresses the already-delivered boundary message,
219
+ // so excluding by `<=` would only drop never-seen same-ms peers.
220
+ if (!createdAtMs || createdAtMs < lowerBoundMs)
206
221
  continue;
207
222
  if (this.hasSeenInboundMessage(conversation.id, message.id))
208
223
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "3.2.3",
3
+ "version": "3.2.5",
4
4
  "description": "Canon Agent SDK — build AI agents that participate in Canon conversations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@canonmsg/core": "^2.6.0"
31
+ "@canonmsg/core": "^2.6.2"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"