sentinelayer-cli 0.30.12 → 0.30.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.30.12",
3
+ "version": "0.30.13",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -38,6 +38,10 @@ function normalizePositiveInteger(value, fallbackValue) {
38
38
  return Math.floor(normalized);
39
39
  }
40
40
 
41
+ function isListenerStopDirective(event = {}) {
42
+ return normalizeString(event?.event) === "listener_stop";
43
+ }
44
+
41
45
  function isPlainObject(value) {
42
46
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
43
47
  }
@@ -255,6 +259,8 @@ export async function listenSessionEvents({
255
259
  _readCursor = readSyncCursor,
256
260
  _writeCursor = writeSyncCursor,
257
261
  _sleep = defaultSleep,
262
+ _setInterval = setInterval,
263
+ _clearInterval = clearInterval,
258
264
  _nowMs = Date.now,
259
265
  } = {}) {
260
266
  const normalizedSessionId = normalizeString(sessionId);
@@ -394,6 +400,7 @@ export async function listenSessionEvents({
394
400
  const visibleEvents = [];
395
401
  let preStartEventCount = 0;
396
402
  for (const event of events) {
403
+ if (!isPlainObject(event)) continue;
397
404
  const timestampMs = eventTimestampMs(event);
398
405
  if (!timestampMs || timestampMs < startedAtMs) {
399
406
  preStartEventCount += 1;
@@ -402,6 +409,7 @@ export async function listenSessionEvents({
402
409
  if (isRecentActivity(activityMs, observedAtMs, activeWindowMs)) {
403
410
  lastHumanActivityMs = Math.max(lastHumanActivityMs, activityMs);
404
411
  }
412
+ if (timestampMs && timestampMs < startedAtMs && isListenerStopDirective(event)) continue;
405
413
  if (isSessionListenerLifecycleEvent(event)) continue;
406
414
  if (!eventMatchesAgent(event, normalizedAgentId)) continue;
407
415
  visibleEvents.push(event);
@@ -441,9 +449,14 @@ export async function listenSessionEvents({
441
449
  if (emittedKeys.has(key)) continue;
442
450
  matched += 1;
443
451
  if (!shouldEmitBatch && eventTimestampMs(event) < startedAtMs) continue;
444
- await onEvent(event);
445
452
  emittedKeys.add(key);
446
- emitted += 1;
453
+ try {
454
+ await onEvent(event);
455
+ emitted += 1;
456
+ } catch (error) {
457
+ emittedKeys.delete(key);
458
+ throw error;
459
+ }
447
460
  }
448
461
 
449
462
  if (nextCursor && nextCursor !== cursor) {
@@ -473,6 +486,32 @@ export async function listenSessionEvents({
473
486
  return humanActive;
474
487
  }
475
488
 
489
+ let streamHeartbeatTimer = null;
490
+ let streamHeartbeatInFlight = false;
491
+ const streamHeartbeatMs = Math.max(1_000, Math.min(idleSleepMs, 60_000));
492
+
493
+ function stopStreamHeartbeatTimer() {
494
+ if (!streamHeartbeatTimer) return;
495
+ _clearInterval(streamHeartbeatTimer);
496
+ streamHeartbeatTimer = null;
497
+ }
498
+
499
+ function startStreamHeartbeatTimer() {
500
+ stopStreamHeartbeatTimer();
501
+ streamHeartbeatTimer = _setInterval(async () => {
502
+ if (signal?.aborted || streamHeartbeatInFlight) return;
503
+ streamHeartbeatInFlight = true;
504
+ try {
505
+ await notifyHeartbeat({ nextPollMs: null });
506
+ } finally {
507
+ streamHeartbeatInFlight = false;
508
+ }
509
+ }, streamHeartbeatMs);
510
+ if (streamHeartbeatTimer && typeof streamHeartbeatTimer.unref === "function") {
511
+ streamHeartbeatTimer.unref();
512
+ }
513
+ }
514
+
476
515
  await notifyLifecycle(
477
516
  lifecycleSnapshot("started", {
478
517
  startedAt: new Date(startedAtMs).toISOString(),
@@ -484,6 +523,7 @@ export async function listenSessionEvents({
484
523
  if (normalizedTransport !== "poll") {
485
524
  streamAttempted = true;
486
525
  activeTransport = "stream";
526
+ startStreamHeartbeatTimer();
487
527
  const streamResult = await _stream(normalizedSessionId, {
488
528
  targetPath,
489
529
  since: cursor,
@@ -502,6 +542,8 @@ export async function listenSessionEvents({
502
542
  onHeartbeat: async () => {
503
543
  await notifyHeartbeat({ nextPollMs: null });
504
544
  },
545
+ }).finally(() => {
546
+ stopStreamHeartbeatTimer();
505
547
  });
506
548
  if (!streamResult?.ok) {
507
549
  streamFallbackReason = normalizeString(streamResult?.reason) || lastReason || "stream_failed";