ruflo 3.38.8 → 3.38.11

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": "ruflo",
3
- "version": "3.38.8",
3
+ "version": "3.38.11",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration platform. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "bin/ruflo.js",
6
6
  "type": "module",
@@ -129,6 +129,30 @@ describe("smoothStreamUpdates", () => {
129
129
  expect(delays[0]).toBeGreaterThanOrEqual(delays.at(-1) ?? 0);
130
130
  });
131
131
 
132
+ it("drains stream chunks without timer pacing while the page is hidden", async () => {
133
+ const source: MessageUpdate[] = [
134
+ { type: MessageUpdateType.Stream, token: "one two three four five " },
135
+ ];
136
+ const delays: number[] = [];
137
+
138
+ const updates = await collect(
139
+ smoothStreamUpdates(fromArray(source), {
140
+ minDelayMs: 5,
141
+ maxDelayMs: 80,
142
+ _internal: {
143
+ sleep: async (ms: number) => {
144
+ delays.push(ms);
145
+ },
146
+ isPageHidden: () => true,
147
+ detectChunk: (buffer) => /\S+\s+/.exec(buffer)?.[0] ?? null,
148
+ },
149
+ })
150
+ );
151
+
152
+ expect(streamText(updates)).toBe("one two three four five ");
153
+ expect(delays).toEqual([]);
154
+ });
155
+
132
156
  it("handles CJK text correctly", async () => {
133
157
  const source: MessageUpdate[] = [{ type: MessageUpdateType.Stream, token: "你好,世界!" }];
134
158
 
@@ -38,6 +38,7 @@ type SmoothStreamConfig = {
38
38
  now?: () => number;
39
39
  sleep?: (ms: number) => Promise<void>;
40
40
  detectChunk?: ChunkDetector;
41
+ isPageHidden?: () => boolean;
41
42
  };
42
43
  };
43
44
 
@@ -168,7 +169,12 @@ export async function* smoothStreamUpdates(
168
169
  maxDelayMs = 80,
169
170
  minRateCharsPerMs = 0.3,
170
171
  maxBufferedMs = 400,
171
- _internal: { now = () => performance.now(), sleep = defaultSleep, detectChunk } = {},
172
+ _internal: {
173
+ now = () => performance.now(),
174
+ sleep = defaultSleep,
175
+ detectChunk,
176
+ isPageHidden = defaultIsPageHidden,
177
+ } = {},
172
178
  }: SmoothStreamConfig = {}
173
179
  ): AsyncGenerator<MessageUpdate> {
174
180
  const chunkDetector = detectChunk ?? createWordChunkDetector();
@@ -251,7 +257,7 @@ export async function* smoothStreamUpdates(
251
257
  const effectiveMinDelayMs = underBacklogPressure ? 0 : minDelayMs;
252
258
  const delayMs = Math.round(Math.max(effectiveMinDelayMs, Math.min(maxDelayMs, rawDelay)));
253
259
 
254
- if (delayMs > 0) {
260
+ if (delayMs > 0 && !isPageHidden()) {
255
261
  await sleep(delayMs);
256
262
  }
257
263
  }
@@ -318,6 +324,8 @@ export const isMessageToolProgressUpdate = (
318
324
 
319
325
  const defaultSleep = (ms: number): Promise<void> =>
320
326
  new Promise((resolve) => setTimeout(resolve, ms));
327
+ const defaultIsPageHidden = (): boolean =>
328
+ typeof document !== "undefined" && document.visibilityState === "hidden";
321
329
  const waitForEvent = (eventTarget: EventTarget, eventName: string) =>
322
330
  new Promise<boolean>((resolve) =>
323
331
  eventTarget.addEventListener(eventName, () => resolve(true), { once: true })
@@ -249,6 +249,7 @@
249
249
  // Initialize lastUpdateTime outside the loop to persist between updates
250
250
  let lastUpdateTime = new Date();
251
251
  let frameFlushScheduled = false;
252
+ let frameFlushTimeout: ReturnType<typeof setTimeout> | undefined;
252
253
 
253
254
  const flushBuffer = (currentTime: Date) => {
254
255
  if (buffer.length === 0) return;
@@ -261,14 +262,20 @@
261
262
  if (frameFlushScheduled) return;
262
263
  frameFlushScheduled = true;
263
264
  const flush = () => {
265
+ if (!frameFlushScheduled) return;
264
266
  frameFlushScheduled = false;
267
+ if (frameFlushTimeout !== undefined) {
268
+ clearTimeout(frameFlushTimeout);
269
+ frameFlushTimeout = undefined;
270
+ }
265
271
  flushBuffer(new Date());
266
272
  };
267
273
  if (typeof requestAnimationFrame === "function") {
268
274
  requestAnimationFrame(flush);
269
- } else {
270
- setTimeout(flush, 0);
271
275
  }
276
+ // requestAnimationFrame is suspended in background tabs. Keep a
277
+ // bounded fallback so buffered tokens still reach the DOM there.
278
+ frameFlushTimeout = setTimeout(flush, 300);
272
279
  };
273
280
 
274
281
  for await (const update of messageUpdatesIterator) {