@cryptiklemur/lattice 5.13.2 → 5.13.4

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.
@@ -41,38 +41,59 @@ const sessionPermissionOverrides = new Map();
41
41
  export function getAvailableModels() {
42
42
  return getWarmupModels();
43
43
  }
44
- function createMessageQueue() {
44
+ function createMessageQueue(label) {
45
45
  const queue = [];
46
46
  let waiting = null;
47
47
  let ended = false;
48
+ let readCount = 0;
49
+ const readWaiters = new Map();
48
50
  return {
49
51
  push: function (msg) {
50
52
  if (waiting) {
53
+ log.chat("MQ[%s] push: SDK was waiting, delivering immediately (queued=%d)", label || "?", queue.length);
51
54
  const resolve = waiting;
52
55
  waiting = null;
53
56
  resolve({ value: msg, done: false });
54
57
  }
55
58
  else {
56
59
  queue.push(msg);
60
+ log.chat("MQ[%s] push: buffered (queued=%d)", label || "?", queue.length);
57
61
  }
58
62
  },
59
63
  end: function () {
60
64
  ended = true;
65
+ log.chat("MQ[%s] end called (queued=%d, waiting=%s)", label || "?", queue.length, String(!!waiting));
61
66
  if (waiting) {
62
67
  const resolve = waiting;
63
68
  waiting = null;
64
69
  resolve({ value: undefined, done: true });
65
70
  }
66
71
  },
72
+ waitForRead: function (n) {
73
+ if (readCount >= n)
74
+ return Promise.resolve();
75
+ return new Promise(function (resolve) {
76
+ readWaiters.set(n, resolve);
77
+ });
78
+ },
67
79
  [Symbol.asyncIterator]: function () {
68
80
  return {
69
81
  next: function () {
82
+ readCount++;
83
+ const waiter = readWaiters.get(readCount);
84
+ if (waiter) {
85
+ readWaiters.delete(readCount);
86
+ waiter();
87
+ }
70
88
  if (queue.length > 0) {
89
+ log.chat("MQ[%s] read #%d: from buffer (remaining=%d)", label || "?", readCount, queue.length - 1);
71
90
  return Promise.resolve({ value: queue.shift(), done: false });
72
91
  }
73
92
  if (ended) {
93
+ log.chat("MQ[%s] read #%d: ended", label || "?", readCount);
74
94
  return Promise.resolve({ value: undefined, done: true });
75
95
  }
96
+ log.chat("MQ[%s] read #%d: waiting for push", label || "?", readCount);
76
97
  return new Promise(function (resolve) {
77
98
  waiting = resolve;
78
99
  });
@@ -654,7 +675,7 @@ export function startChatStream(options) {
654
675
  text,
655
676
  uuid: crypto.randomUUID(),
656
677
  });
657
- const mq = createMessageQueue();
678
+ const mq = createMessageQueue(sessionId.slice(0, 8));
658
679
  const firstMsg = buildSDKUserMessage(prompt, attachments, sessionId);
659
680
  const stream = query({ prompt: mq, options: queryOptions });
660
681
  pendingStreams.delete(sessionId);
@@ -695,20 +716,47 @@ export function startChatStream(options) {
695
716
  catch (initErr) {
696
717
  log.chat("Session %s SDK initialization FAILED: %O", sessionId, initErr);
697
718
  }
698
- log.chat("Session %s pushing first message to queue", sessionId);
719
+ if (shouldResume) {
720
+ log.chat("Session %s waiting for SDK read #2 before pushing message", sessionId);
721
+ await mq.waitForRead(2);
722
+ log.chat("Session %s SDK read #2 triggered, pushing first message", sessionId);
723
+ }
724
+ else {
725
+ log.chat("Session %s pushing first message to queue", sessionId);
726
+ }
699
727
  mq.push(firstMsg);
700
728
  let retrying = false;
729
+ const TURN_TIMEOUT_MS = 30000;
730
+ let turnTimer = null;
731
+ function resetTurnTimer() {
732
+ if (turnTimer)
733
+ clearTimeout(turnTimer);
734
+ turnTimer = setTimeout(function () {
735
+ if (!sessionStream.sawNewTurnContent && !sessionStream.ended) {
736
+ log.chat("Session %s turn timeout: no new content after %dms, aborting", sessionId, TURN_TIMEOUT_MS);
737
+ abortController.abort();
738
+ }
739
+ }, TURN_TIMEOUT_MS);
740
+ }
741
+ resetTurnTimer();
701
742
  try {
702
743
  log.chat("Session %s entering stream loop", sessionId);
703
744
  let msgCount = 0;
704
745
  for await (const msg of stream) {
705
746
  msgCount++;
706
747
  log.chat("Session %s msg #%d type=%s", sessionId, msgCount, msg.type);
748
+ if (msg.type === "stream_event" || (msg.type === "assistant" && msg.message?.model !== "<synthetic>")) {
749
+ resetTurnTimer();
750
+ }
707
751
  processMessage(sessionStream, msg);
708
752
  }
753
+ if (turnTimer)
754
+ clearTimeout(turnTimer);
709
755
  log.chat("Session %s stream ended normally after %d messages", sessionId, msgCount);
710
756
  }
711
757
  catch (err) {
758
+ if (turnTimer)
759
+ clearTimeout(turnTimer);
712
760
  const errMsg = err instanceof Error ? err.message : String(err);
713
761
  log.chat("Session %s stream error: %s", sessionId, errMsg);
714
762
  if (errMsg.includes("aborted") || errMsg.includes("AbortError")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "5.13.2",
3
+ "version": "5.13.4",
4
4
  "description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
5
5
  "license": "MIT",
6
6
  "author": "Aaron Scherer <me@aaronscherer.me>",