ruflo 3.38.9 → 3.38.12
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.
|
|
3
|
+
"version": "3.38.12",
|
|
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: {
|
|
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) {
|