auto: sync OpenClaw config 2026-09-09 16:13
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Generic throttled flush controller.
|
||||
*
|
||||
* A pure scheduling primitive that manages timer-based throttling,
|
||||
* mutex-guarded flushing, and reflush-on-conflict. Contains no
|
||||
* business logic — the actual flush work is provided via a callback.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FlushController = void 0;
|
||||
const reply_dispatcher_types_1 = require("./reply-dispatcher-types.js");
|
||||
// ---------------------------------------------------------------------------
|
||||
// FlushController
|
||||
// ---------------------------------------------------------------------------
|
||||
class FlushController {
|
||||
doFlush;
|
||||
flushInProgress = false;
|
||||
flushResolvers = [];
|
||||
needsReflush = false;
|
||||
pendingFlushTimer = null;
|
||||
lastUpdateTime = 0;
|
||||
isCompleted = false;
|
||||
constructor(doFlush) {
|
||||
this.doFlush = doFlush;
|
||||
}
|
||||
/** Mark the controller as completed — no more flushes after current one. */
|
||||
complete() {
|
||||
this.isCompleted = true;
|
||||
}
|
||||
/** Cancel any pending deferred flush timer. */
|
||||
cancelPendingFlush() {
|
||||
if (this.pendingFlushTimer) {
|
||||
clearTimeout(this.pendingFlushTimer);
|
||||
this.pendingFlushTimer = null;
|
||||
}
|
||||
}
|
||||
/** Wait for any in-progress flush to finish. */
|
||||
waitForFlush() {
|
||||
if (!this.flushInProgress)
|
||||
return Promise.resolve();
|
||||
return new Promise((resolve) => this.flushResolvers.push(resolve));
|
||||
}
|
||||
/**
|
||||
* Execute a flush (mutex-guarded, with reflush on conflict).
|
||||
*
|
||||
* If a flush is already in progress, marks needsReflush so a
|
||||
* follow-up flush fires immediately after the current one completes.
|
||||
*/
|
||||
async flush() {
|
||||
if (!this.cardMessageReady() || this.flushInProgress || this.isCompleted) {
|
||||
if (this.flushInProgress && !this.isCompleted)
|
||||
this.needsReflush = true;
|
||||
return;
|
||||
}
|
||||
this.flushInProgress = true;
|
||||
this.needsReflush = false;
|
||||
// Update timestamp BEFORE the API call to prevent concurrent callers
|
||||
// from also entering the flush (race condition fix).
|
||||
this.lastUpdateTime = Date.now();
|
||||
try {
|
||||
await this.doFlush();
|
||||
this.lastUpdateTime = Date.now();
|
||||
}
|
||||
finally {
|
||||
this.flushInProgress = false;
|
||||
const resolvers = this.flushResolvers;
|
||||
this.flushResolvers = [];
|
||||
for (const resolve of resolvers)
|
||||
resolve();
|
||||
// If events arrived while the API call was in flight,
|
||||
// schedule an immediate follow-up flush.
|
||||
if (this.needsReflush && !this.isCompleted && !this.pendingFlushTimer) {
|
||||
this.needsReflush = false;
|
||||
this.pendingFlushTimer = setTimeout(() => {
|
||||
this.pendingFlushTimer = null;
|
||||
void this.flush();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Throttled update entry point.
|
||||
*
|
||||
* @param throttleMs - Minimum interval between flushes (varies by
|
||||
* CardKit vs IM patch mode). Passed in by the caller so this
|
||||
* controller remains business-logic-free.
|
||||
*/
|
||||
async throttledUpdate(throttleMs) {
|
||||
if (!this.cardMessageReady())
|
||||
return;
|
||||
const now = Date.now();
|
||||
const elapsed = now - this.lastUpdateTime;
|
||||
if (elapsed >= throttleMs) {
|
||||
this.cancelPendingFlush();
|
||||
if (elapsed > reply_dispatcher_types_1.THROTTLE_CONSTANTS.LONG_GAP_THRESHOLD_MS) {
|
||||
// After a long gap, batch briefly so the first visible update
|
||||
// contains meaningful text rather than just 1-2 characters.
|
||||
this.lastUpdateTime = now;
|
||||
this.pendingFlushTimer = setTimeout(() => {
|
||||
this.pendingFlushTimer = null;
|
||||
void this.flush();
|
||||
}, reply_dispatcher_types_1.THROTTLE_CONSTANTS.BATCH_AFTER_GAP_MS);
|
||||
}
|
||||
else {
|
||||
await this.flush();
|
||||
}
|
||||
}
|
||||
else if (!this.pendingFlushTimer) {
|
||||
// Inside throttle window — schedule a deferred flush
|
||||
const delay = throttleMs - elapsed;
|
||||
this.pendingFlushTimer = setTimeout(() => {
|
||||
this.pendingFlushTimer = null;
|
||||
void this.flush();
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------------------
|
||||
// Internal
|
||||
// ------------------------------------------------------------------
|
||||
/** Overridable gate: subclasses / consumers can set via setCardMessageReady. */
|
||||
_cardMessageReady = false;
|
||||
cardMessageReady() {
|
||||
return this._cardMessageReady;
|
||||
}
|
||||
setCardMessageReady(ready) {
|
||||
this._cardMessageReady = ready;
|
||||
if (ready) {
|
||||
// Initialize the timestamp so the first throttledUpdate sees a
|
||||
// small elapsed time (matching original behavior where
|
||||
// lastCardUpdateTime = Date.now() was set during card creation).
|
||||
this.lastUpdateTime = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.FlushController = FlushController;
|
||||
Reference in New Issue
Block a user