sentinelayer-cli 0.9.0 → 0.9.3

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.
@@ -18,11 +18,188 @@
18
18
  * sync, and vice-versa.
19
19
  */
20
20
 
21
- import { pollHumanMessages, pollSessionEvents } from "./sync.js";
22
- import { appendToStream } from "./stream.js";
21
+ import { listSessionsFromApi, pollHumanMessages, pollSessionEvents } from "./sync.js";
22
+ import { appendToStream, readStream } from "./stream.js";
23
+ import {
24
+ createSession,
25
+ getSession,
26
+ isSessionCacheExpired,
27
+ refreshSessionCacheForRemoteActivity,
28
+ } from "./store.js";
23
29
  import { readSyncCursor, writeSyncCursor } from "./sync-cursor.js";
30
+ import {
31
+ addSessionEventIdentityKeys,
32
+ sessionEventHasKnownIdentity,
33
+ } from "./event-identity.js";
24
34
 
25
35
  const EVENTS_CURSOR_SUFFIX = "events";
36
+ const DEFAULT_EVENT_PAGE_LIMIT = 200;
37
+ const DEFAULT_MAX_EVENT_PAGES = 25;
38
+
39
+ async function readExistingRelayKeys(sessionId, { targetPath = process.cwd() } = {}) {
40
+ const knownKeys = new Set();
41
+ const events = await readStream(sessionId, { targetPath, tail: 0 }).catch(() => []);
42
+ for (const event of events) {
43
+ addSessionEventIdentityKeys(knownKeys, event);
44
+ }
45
+ return knownKeys;
46
+ }
47
+
48
+ async function ensureLocalSessionShell(sessionId, { targetPath = process.cwd() } = {}) {
49
+ const existing = await getSession(sessionId, { targetPath });
50
+ let remoteStatus = "";
51
+ let remoteSession = null;
52
+ const remoteList = await listSessionsFromApi({
53
+ targetPath,
54
+ includeArchived: true,
55
+ limit: 200,
56
+ }).catch(() => null);
57
+ if (remoteList?.ok) {
58
+ const match = (remoteList.sessions || []).find((entry) => entry?.sessionId === sessionId);
59
+ remoteSession = match || null;
60
+ remoteStatus = String(match?.archiveStatus || match?.status || "").trim().toLowerCase();
61
+ }
62
+ if (existing) {
63
+ const existingStatus = String(existing.status || "").trim().toLowerCase();
64
+ const locallyClosedByStatus = existingStatus === "expired" || existingStatus === "archived";
65
+ const remoteAllowsRefresh =
66
+ ["active", "pending"].includes(remoteStatus) || (!remoteStatus && !locallyClosedByStatus);
67
+ if (isSessionCacheExpired(existing) && remoteAllowsRefresh) {
68
+ const refreshed = await refreshSessionCacheForRemoteActivity(sessionId, {
69
+ targetPath,
70
+ title: remoteSession?.title || "",
71
+ lastInteractionAt:
72
+ remoteSession?.lastInteractionAt ||
73
+ remoteSession?.lastActivityAt ||
74
+ remoteSession?.updatedAt ||
75
+ remoteSession?.createdAt ||
76
+ "",
77
+ });
78
+ return {
79
+ materialized: false,
80
+ refreshed: Boolean(refreshed),
81
+ session: refreshed || existing,
82
+ remoteStatus,
83
+ };
84
+ }
85
+ return { materialized: false, refreshed: false, session: existing, remoteStatus };
86
+ }
87
+ const created = await createSession({
88
+ targetPath,
89
+ sessionId,
90
+ title: `remote-${String(sessionId).slice(0, 8)}`,
91
+ });
92
+ return { materialized: true, refreshed: false, session: created, remoteStatus };
93
+ }
94
+
95
+ function sourceFullyRelayed(events = [], successfulKeys = new Set()) {
96
+ const relayedEvents = Array.isArray(events) ? events : [];
97
+ if (relayedEvents.length === 0) return true;
98
+ return relayedEvents.every((event) => sessionEventHasKnownIdentity(event, successfulKeys));
99
+ }
100
+
101
+ function markPostKillEvent(event = {}) {
102
+ if (!event || typeof event !== "object" || Array.isArray(event)) return event;
103
+ const payload = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload)
104
+ ? event.payload
105
+ : {};
106
+ return {
107
+ ...event,
108
+ _post_kill: true,
109
+ payload: {
110
+ ...payload,
111
+ _post_kill: true,
112
+ },
113
+ };
114
+ }
115
+
116
+ function normalizePositiveInteger(value, fallbackValue) {
117
+ if (value === undefined || value === null || String(value).trim() === "") {
118
+ return fallbackValue;
119
+ }
120
+ const normalized = Number(value);
121
+ if (!Number.isFinite(normalized) || normalized <= 0) {
122
+ return fallbackValue;
123
+ }
124
+ return Math.floor(normalized);
125
+ }
126
+
127
+ async function pollSessionEventPages({
128
+ sessionId,
129
+ targetPath,
130
+ since,
131
+ _pollEvents,
132
+ limit,
133
+ maxPages,
134
+ forceCircuitProbe = false,
135
+ }) {
136
+ const normalizedLimit = Math.max(
137
+ 1,
138
+ Math.min(DEFAULT_EVENT_PAGE_LIMIT, normalizePositiveInteger(limit, DEFAULT_EVENT_PAGE_LIMIT)),
139
+ );
140
+ const normalizedMaxPages = Math.max(
141
+ 1,
142
+ Math.min(100, normalizePositiveInteger(maxPages, DEFAULT_MAX_EVENT_PAGES)),
143
+ );
144
+ const events = [];
145
+ let cursor = typeof since === "string" && since.trim() ? since.trim() : null;
146
+ let reason = "";
147
+ let pageCount = 0;
148
+
149
+ for (let page = 0; page < normalizedMaxPages; page += 1) {
150
+ const result = await _pollEvents(sessionId, {
151
+ targetPath,
152
+ since: cursor,
153
+ limit: normalizedLimit,
154
+ forceCircuitProbe,
155
+ });
156
+ pageCount += 1;
157
+ if (!result?.ok) {
158
+ return {
159
+ ok: events.length > 0,
160
+ reason: result?.reason || "poll_failed",
161
+ events,
162
+ cursor,
163
+ pageCount,
164
+ complete: false,
165
+ truncated: events.length > 0,
166
+ };
167
+ }
168
+
169
+ const pageEvents = Array.isArray(result.events) ? result.events : [];
170
+ events.push(...pageEvents);
171
+ const nextCursor =
172
+ typeof result.cursor === "string" && result.cursor.trim() ? result.cursor.trim() : cursor;
173
+ const progressed = nextCursor && nextCursor !== cursor;
174
+ cursor = nextCursor || cursor;
175
+
176
+ if (pageEvents.length < normalizedLimit) {
177
+ return {
178
+ ok: true,
179
+ reason: "",
180
+ events,
181
+ cursor,
182
+ pageCount,
183
+ complete: true,
184
+ truncated: false,
185
+ };
186
+ }
187
+ if (!progressed) {
188
+ reason = "cursor_not_advanced";
189
+ break;
190
+ }
191
+ }
192
+
193
+ return {
194
+ ok: events.length > 0,
195
+ reason: reason || "max_event_pages_reached",
196
+ events,
197
+ cursor,
198
+ pageCount,
199
+ complete: false,
200
+ truncated: true,
201
+ };
202
+ }
26
203
 
27
204
  /**
28
205
  * Fetch new human messages for a session, append them to the local
@@ -46,6 +223,10 @@ export async function hydrateSessionFromRemote({
46
223
  _poll = pollHumanMessages,
47
224
  _pollEvents = pollSessionEvents,
48
225
  _append = appendToStream,
226
+ _ensureLocalSession = ensureLocalSessionShell,
227
+ probeOpenCircuit = true,
228
+ eventPageLimit = DEFAULT_EVENT_PAGE_LIMIT,
229
+ maxEventPages = DEFAULT_MAX_EVENT_PAGES,
49
230
  } = {}) {
50
231
  if (!sessionId || typeof sessionId !== "string") {
51
232
  return {
@@ -74,25 +255,56 @@ export async function hydrateSessionFromRemote({
74
255
  // Run both pollers in parallel — they hit different endpoints and
75
256
  // are independent. A human-only poll stays fast even when the
76
257
  // events poll is heavy.
77
- const [humanResult, eventsResult] = await Promise.all([
258
+ let [humanResult, eventsResult] = await Promise.all([
78
259
  _poll(sessionId, { targetPath, since: humanCursor }),
79
- _pollEvents(sessionId, { targetPath, since: eventsCursor }),
260
+ pollSessionEventPages({
261
+ sessionId,
262
+ targetPath,
263
+ since: eventsCursor,
264
+ _pollEvents,
265
+ limit: eventPageLimit,
266
+ maxPages: maxEventPages,
267
+ }),
80
268
  ]);
81
269
 
270
+ if (
271
+ probeOpenCircuit &&
272
+ humanResult?.reason === "circuit_breaker_open" &&
273
+ eventsResult?.reason === "circuit_breaker_open"
274
+ ) {
275
+ [humanResult, eventsResult] = await Promise.all([
276
+ _poll(sessionId, { targetPath, since: humanCursor, forceCircuitProbe: true }),
277
+ pollSessionEventPages({
278
+ sessionId,
279
+ targetPath,
280
+ since: eventsCursor,
281
+ _pollEvents,
282
+ limit: eventPageLimit,
283
+ maxPages: maxEventPages,
284
+ forceCircuitProbe: true,
285
+ }),
286
+ ]);
287
+ }
288
+
82
289
  // Dedup across sources — both endpoints can return the same event
83
290
  // (e.g. a human relay event). Cursor values are unique per event.
84
291
  const seenCursors = new Set();
292
+ const seenKeys = new Set();
85
293
  const merged = [];
86
294
  for (const e of humanResult?.events || []) {
87
295
  const c = (e && typeof e === "object" && typeof e.cursor === "string") ? e.cursor : "";
88
296
  if (c && seenCursors.has(c)) continue;
297
+ if (sessionEventHasKnownIdentity(e, seenKeys)) continue;
89
298
  if (c) seenCursors.add(c);
299
+ addSessionEventIdentityKeys(seenKeys, e);
90
300
  merged.push(e);
91
301
  }
92
302
  for (const e of eventsResult?.events || []) {
93
303
  const c = (e && typeof e === "object" && typeof e.cursor === "string") ? e.cursor : "";
94
304
  if (c && seenCursors.has(c)) continue;
305
+ if (sessionEventHasKnownIdentity(e, seenKeys)) continue;
95
306
  if (c) seenCursors.add(c);
307
+ addSessionEventIdentityKeys(seenKeys, e);
96
308
  merged.push(e);
97
309
  }
98
310
 
@@ -112,10 +324,33 @@ export async function hydrateSessionFromRemote({
112
324
  }
113
325
 
114
326
  let relayed = 0;
115
- for (const event of merged) {
327
+ let materializedLocalSession = false;
328
+ let remoteStatus = "";
329
+ const successfulRelayKeys =
330
+ merged.length > 0 ? await readExistingRelayKeys(sessionId, { targetPath }) : new Set();
331
+ const newEvents = successfulRelayKeys.size > 0
332
+ ? merged.filter((event) => !sessionEventHasKnownIdentity(event, successfulRelayKeys))
333
+ : merged;
334
+ if (newEvents.length > 0) {
335
+ try {
336
+ const localSession = await _ensureLocalSession(sessionId, { targetPath });
337
+ materializedLocalSession = Boolean(localSession?.materialized);
338
+ remoteStatus = String(localSession?.remoteStatus || "").trim().toLowerCase();
339
+ } catch {
340
+ // Keep the old degraded behavior: append attempts below will
341
+ // fail visibly in the returned counters, but remote polling still
342
+ // returns a structured result.
343
+ }
344
+ }
345
+ const appendEvents =
346
+ remoteStatus && !["active", "pending"].includes(remoteStatus)
347
+ ? newEvents.map((event) => markPostKillEvent(event))
348
+ : newEvents;
349
+ for (const event of appendEvents) {
116
350
  try {
117
- await _append(sessionId, event, { targetPath });
351
+ await _append(sessionId, event, { targetPath, syncRemote: false });
118
352
  relayed += 1;
353
+ addSessionEventIdentityKeys(successfulRelayKeys, event);
119
354
  } catch {
120
355
  // Append errors are observable via the stream but should not
121
356
  // abort the rest of the batch — partial relay is still progress.
@@ -123,11 +358,13 @@ export async function hydrateSessionFromRemote({
123
358
  }
124
359
 
125
360
  let persistedCursor = false;
126
- if (typeof humanResult?.cursor === "string" && humanResult.cursor.trim()) {
361
+ const humanCursorSafe = sourceFullyRelayed(humanResult?.events || [], successfulRelayKeys);
362
+ const eventsCursorSafe = sourceFullyRelayed(eventsResult?.events || [], successfulRelayKeys);
363
+ if (humanCursorSafe && typeof humanResult?.cursor === "string" && humanResult.cursor.trim()) {
127
364
  const result = await writeSyncCursor(sessionId, humanResult.cursor, { targetPath }).catch(() => null);
128
365
  persistedCursor = Boolean(result && result.written);
129
366
  }
130
- if (typeof eventsResult?.cursor === "string" && eventsResult.cursor.trim()) {
367
+ if (eventsCursorSafe && typeof eventsResult?.cursor === "string" && eventsResult.cursor.trim()) {
131
368
  await writeSyncCursor(sessionId, eventsResult.cursor, {
132
369
  targetPath,
133
370
  suffix: EVENTS_CURSOR_SUFFIX,
@@ -145,5 +382,12 @@ export async function hydrateSessionFromRemote({
145
382
  eventsRelayed: (eventsResult?.events || []).length,
146
383
  eventsCursor:
147
384
  typeof eventsResult?.cursor === "string" ? eventsResult.cursor : eventsCursor || null,
385
+ eventsPageCount: Number(eventsResult?.pageCount || 0),
386
+ eventsBackfillComplete: Boolean(eventsResult?.complete !== false),
387
+ eventsBackfillTruncated: Boolean(eventsResult?.truncated),
388
+ eventsBackfillReason: eventsResult?.complete === false ? eventsResult?.reason || "" : "",
389
+ materializedLocalSession,
390
+ localAppendComplete: humanCursorSafe && eventsCursorSafe,
391
+ remoteStatus: remoteStatus || null,
148
392
  };
149
393
  }
@@ -183,6 +183,31 @@ function normalizeSharedResources(raw = {}, { nowIso = new Date().toISOString()
183
183
  };
184
184
  }
185
185
 
186
+ function normalizeRemoteTitleSync(raw = {}, { nowIso = new Date().toISOString() } = {}) {
187
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
188
+ return null;
189
+ }
190
+ const title = normalizeString(raw.title);
191
+ const lastAttemptAt = raw.lastAttemptAt
192
+ ? normalizeIsoTimestamp(raw.lastAttemptAt, nowIso)
193
+ : null;
194
+ const lastSyncedAt = raw.lastSyncedAt
195
+ ? normalizeIsoTimestamp(raw.lastSyncedAt, nowIso)
196
+ : null;
197
+ const failureReason = normalizeString(raw.failureReason) || null;
198
+ const pending = Boolean(raw.pending);
199
+ if (!pending && !title && !lastAttemptAt && !lastSyncedAt && !failureReason) {
200
+ return null;
201
+ }
202
+ return {
203
+ pending,
204
+ title: title || null,
205
+ lastAttemptAt,
206
+ lastSyncedAt,
207
+ failureReason,
208
+ };
209
+ }
210
+
186
211
  function normalizeTemplateAgent(raw = {}) {
187
212
  const source = raw && typeof raw === "object" ? raw : {};
188
213
  return {
@@ -357,12 +382,14 @@ function normalizeMetadata(raw = {}, { sessionId, targetPath, nowIso } = {}) {
357
382
  archiveStatus: normalizeString(raw.archiveStatus) || "pending",
358
383
  codebaseContext: normalizeCodebaseContext(raw.codebaseContext || {}),
359
384
  sharedResources: normalizeSharedResources(raw.sharedResources || {}, { nowIso }),
385
+ remoteTitleSync: normalizeRemoteTitleSync(raw.remoteTitleSync || null, { nowIso }),
360
386
  template: normalizeSessionTemplate(raw.template || null),
361
387
  };
362
388
  }
363
389
 
364
390
  function isExpired(metadata, nowIso = new Date().toISOString()) {
365
- if (!metadata || normalizeSessionStatus(metadata.status) === SESSION_STATUS_EXPIRED) {
391
+ const status = normalizeSessionStatus(metadata?.status);
392
+ if (!metadata || status === SESSION_STATUS_EXPIRED || status === SESSION_STATUS_ARCHIVED) {
366
393
  return true;
367
394
  }
368
395
  const expiryEpoch = Date.parse(normalizeIsoTimestamp(metadata.expiresAt, nowIso));
@@ -373,6 +400,10 @@ function isExpired(metadata, nowIso = new Date().toISOString()) {
373
400
  return nowEpoch >= expiryEpoch;
374
401
  }
375
402
 
403
+ export function isSessionCacheExpired(metadata, nowIso = new Date().toISOString()) {
404
+ return isExpired(metadata, nowIso);
405
+ }
406
+
376
407
  function buildSessionPayload(metadata, paths, nowIso = new Date().toISOString()) {
377
408
  return {
378
409
  sessionId: metadata.sessionId,
@@ -391,6 +422,7 @@ function buildSessionPayload(metadata, paths, nowIso = new Date().toISOString())
391
422
  s3Path: metadata.s3Path,
392
423
  codebaseContext: metadata.codebaseContext,
393
424
  sharedResources: metadata.sharedResources,
425
+ remoteTitleSync: metadata.remoteTitleSync,
394
426
  template: metadata.template,
395
427
  };
396
428
  }
@@ -464,6 +496,7 @@ export async function createSession({
464
496
  archiveStatus: "pending",
465
497
  codebaseContext,
466
498
  sharedResources: normalizeSharedResources({}, { nowIso }),
499
+ remoteTitleSync: null,
467
500
  template: normalizeSessionTemplate(template),
468
501
  },
469
502
  {
@@ -498,6 +531,88 @@ export async function updateSessionTitle(
498
531
  return buildSessionPayload(saved, loaded.paths, nowIso);
499
532
  }
500
533
 
534
+ export async function refreshSessionCacheForRemoteActivity(
535
+ sessionId,
536
+ {
537
+ targetPath = process.cwd(),
538
+ title = "",
539
+ ttlSeconds = DEFAULT_TTL_SECONDS,
540
+ lastInteractionAt = "",
541
+ nowIso = new Date().toISOString(),
542
+ } = {}
543
+ ) {
544
+ const loaded = await loadMetadata(sessionId, { targetPath });
545
+ if (!loaded) {
546
+ return null;
547
+ }
548
+ const normalizedTtlSeconds = normalizePositiveInteger(ttlSeconds, DEFAULT_TTL_SECONDS);
549
+ const remoteInteractionIso = normalizeIsoTimestamp(
550
+ lastInteractionAt,
551
+ loaded.metadata.lastInteractionAt || loaded.metadata.createdAt || nowIso
552
+ );
553
+ const currentInteractionEpoch = Date.parse(
554
+ normalizeIsoTimestamp(loaded.metadata.lastInteractionAt, loaded.metadata.createdAt || nowIso)
555
+ );
556
+ const remoteInteractionEpoch = Date.parse(remoteInteractionIso);
557
+ const lastInteractionIso =
558
+ Number.isFinite(remoteInteractionEpoch) &&
559
+ (!Number.isFinite(currentInteractionEpoch) || remoteInteractionEpoch > currentInteractionEpoch)
560
+ ? remoteInteractionIso
561
+ : normalizeIsoTimestamp(loaded.metadata.lastInteractionAt, nowIso);
562
+
563
+ const metadata = {
564
+ ...loaded.metadata,
565
+ updatedAt: nowIso,
566
+ expiresAt: toIsoAfterSeconds(nowIso, normalizedTtlSeconds),
567
+ ttlSeconds: normalizedTtlSeconds,
568
+ status: SESSION_STATUS_ACTIVE,
569
+ expiredAt: null,
570
+ lastInteractionAt: lastInteractionIso,
571
+ };
572
+ const normalizedTitle = normalizeString(title);
573
+ if (normalizedTitle) {
574
+ metadata.title = normalizedTitle;
575
+ }
576
+
577
+ const saved = await saveMetadata(metadata, loaded.paths);
578
+ return buildSessionPayload(saved, loaded.paths, nowIso);
579
+ }
580
+
581
+ export async function recordSessionRemoteTitleSync(
582
+ sessionId,
583
+ {
584
+ targetPath = process.cwd(),
585
+ title = "",
586
+ pending = false,
587
+ failureReason = "",
588
+ lastAttemptAt = "",
589
+ lastSyncedAt = "",
590
+ } = {}
591
+ ) {
592
+ const loaded = await loadMetadata(sessionId, { targetPath });
593
+ if (!loaded) {
594
+ return null;
595
+ }
596
+ const nowIso = new Date().toISOString();
597
+ const metadata = {
598
+ ...loaded.metadata,
599
+ remoteTitleSync: normalizeRemoteTitleSync(
600
+ {
601
+ pending: Boolean(pending),
602
+ title: normalizeString(title) || normalizeString(loaded.metadata.title) || null,
603
+ lastAttemptAt: lastAttemptAt || nowIso,
604
+ lastSyncedAt:
605
+ !pending && !normalizeString(failureReason) ? lastSyncedAt || nowIso : lastSyncedAt || null,
606
+ failureReason: normalizeString(failureReason) || null,
607
+ },
608
+ { nowIso },
609
+ ),
610
+ updatedAt: nowIso,
611
+ };
612
+ const saved = await saveMetadata(metadata, loaded.paths);
613
+ return buildSessionPayload(saved, loaded.paths, nowIso);
614
+ }
615
+
501
616
  export async function getSession(sessionId, { targetPath = process.cwd() } = {}) {
502
617
  const loaded = await loadMetadata(sessionId, { targetPath });
503
618
  if (!loaded) {
@@ -3,6 +3,7 @@ import process from "node:process";
3
3
  import { setTimeout as sleep } from "node:timers/promises";
4
4
 
5
5
  import { createAgentEvent, normalizeAgentEvent } from "../events/schema.js";
6
+ import { enrichEventWithMentions } from "./mentions.js";
6
7
  import { resolveSessionPaths } from "./paths.js";
7
8
  import { redactEventPayload } from "./redact.js";
8
9
  import { syncSessionEventToApi } from "./sync.js";
@@ -223,7 +224,7 @@ function filterBySince(events = [], since) {
223
224
  export async function appendToStream(
224
225
  sessionId,
225
226
  event,
226
- { targetPath = process.cwd(), maxEvents = DEFAULT_MAX_STREAM_EVENTS } = {}
227
+ { targetPath = process.cwd(), maxEvents = DEFAULT_MAX_STREAM_EVENTS, syncRemote = true } = {}
227
228
  ) {
228
229
  const paths = resolveSessionPaths(sessionId, { targetPath });
229
230
  const metadata = await readSessionMetadata(paths);
@@ -235,7 +236,14 @@ export async function appendToStream(
235
236
  }
236
237
 
237
238
  const rawEvent = materializeCanonicalEvent(paths.sessionId, event);
238
- const canonicalEvent = redactEventPayload(rawEvent);
239
+ // Parse `@<name>` mentions out of the message text and surface them
240
+ // as payload.to + payload.mentions so the listener (sl session listen)
241
+ // and Senti daemon can route to/notify the addressed agent. Pure +
242
+ // idempotent — if the caller already supplied an explicit `to/recipient`
243
+ // we leave it alone. Runs before redaction so the redactor sees a
244
+ // stable shape.
245
+ const enrichedEvent = enrichEventWithMentions(rawEvent);
246
+ const canonicalEvent = redactEventPayload(enrichedEvent);
239
247
  const nowIso = new Date().toISOString();
240
248
  const normalizedMaxEvents = normalizePositiveInteger(maxEvents, DEFAULT_MAX_STREAM_EVENTS);
241
249
 
@@ -252,10 +260,12 @@ export async function appendToStream(
252
260
  await releaseLock(paths.lockPath);
253
261
  }
254
262
 
255
- // Best-effort dashboard sync. Never block local stream durability on API state.
256
- void syncSessionEventToApi(paths.sessionId, canonicalEvent, {
257
- targetPath,
258
- }).catch(() => {});
263
+ if (syncRemote) {
264
+ // Best-effort dashboard sync. Never block local stream durability on API state.
265
+ void syncSessionEventToApi(paths.sessionId, canonicalEvent, {
266
+ targetPath,
267
+ }).catch(() => {});
268
+ }
259
269
 
260
270
  return canonicalEvent;
261
271
  }
@@ -320,7 +330,7 @@ export async function* tailStream(
320
330
  }
321
331
 
322
332
  try {
323
- await sleep(normalizedPollMs, null, signal ? { signal } : undefined);
333
+ await sleep(normalizedPollMs, null, signal ? { signal, ref: false } : { ref: false });
324
334
  } catch (error) {
325
335
  if (error && typeof error === "object" && error.name === "AbortError") {
326
336
  return;