@yaoyuanchao/dingtalk 1.7.4 → 1.7.6
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 +1 -1
- package/src/monitor.ts +52 -18
package/package.json
CHANGED
package/src/monitor.ts
CHANGED
|
@@ -198,6 +198,30 @@ const AGGREGATION_DELAY_MS = 2000; // 2 seconds - balance between UX and catchin
|
|
|
198
198
|
|
|
199
199
|
const sessionQueues = new Map<string, Promise<void>>();
|
|
200
200
|
const sessionQueueLastActivity = new Map<string, number>();
|
|
201
|
+
|
|
202
|
+
// Track delivery activity per queue key to detect when the SDK's dispatch resolves
|
|
203
|
+
// before the agent's full turn completes (e.g. followup turns running in background).
|
|
204
|
+
// This supplements sessionQueues for the "busy" check.
|
|
205
|
+
const DELIVER_ACTIVITY_GRACE_MS = 8000; // 8s after last delivery, consider idle
|
|
206
|
+
const deliverActivityTimestamps = new Map<string, number>();
|
|
207
|
+
|
|
208
|
+
function markDeliverActivity(queueKey: string): void {
|
|
209
|
+
deliverActivityTimestamps.set(queueKey, Date.now());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function hasActiveDelivery(queueKey: string): boolean {
|
|
213
|
+
const ts = deliverActivityTimestamps.get(queueKey);
|
|
214
|
+
if (!ts) return false;
|
|
215
|
+
if (Date.now() - ts > DELIVER_ACTIVITY_GRACE_MS) {
|
|
216
|
+
deliverActivityTimestamps.delete(queueKey);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function clearDeliverActivity(queueKey: string): void {
|
|
223
|
+
deliverActivityTimestamps.delete(queueKey);
|
|
224
|
+
}
|
|
201
225
|
const SESSION_QUEUE_TTL_MS = 5 * 60 * 1000; // 5 min
|
|
202
226
|
|
|
203
227
|
const QUEUE_BUSY_PHRASES = [
|
|
@@ -1395,7 +1419,11 @@ async function dispatchMessage(params: {
|
|
|
1395
1419
|
const { account, log } = ctx;
|
|
1396
1420
|
|
|
1397
1421
|
const queueKey = `${account.accountId}:${conversationId}`;
|
|
1398
|
-
|
|
1422
|
+
// Check both the explicit queue AND recent delivery activity.
|
|
1423
|
+
// The SDK's dispatchReplyFromConfig may resolve before the agent's full turn
|
|
1424
|
+
// completes (followup turns run in background), clearing the queue entry
|
|
1425
|
+
// while deliveries are still happening.
|
|
1426
|
+
const isQueueBusy = sessionQueues.has(queueKey) || hasActiveDelivery(queueKey);
|
|
1399
1427
|
|
|
1400
1428
|
// If queue is busy, add emotion reaction on user's message to indicate queued
|
|
1401
1429
|
let queueAckCleanup: (() => Promise<void>) | null = null;
|
|
@@ -1437,11 +1465,13 @@ async function dispatchMessage(params: {
|
|
|
1437
1465
|
// Clean up only if this is still the latest task
|
|
1438
1466
|
if (sessionQueues.get(queueKey) === currentTask) {
|
|
1439
1467
|
sessionQueues.delete(queueKey);
|
|
1468
|
+
log?.info?.("[dingtalk] Queue entry removed for " + queueKey + " (deliverActive=" + hasActiveDelivery(queueKey) + ")");
|
|
1440
1469
|
}
|
|
1441
1470
|
});
|
|
1442
1471
|
|
|
1443
1472
|
sessionQueues.set(queueKey, currentTask);
|
|
1444
1473
|
sessionQueueLastActivity.set(queueKey, Date.now());
|
|
1474
|
+
log?.info?.("[dingtalk] Queue entry set for " + queueKey + " (wasQueueBusy=" + isQueueBusy + ")");
|
|
1445
1475
|
|
|
1446
1476
|
// Don't await — fire-and-forget so message buffering and SDK callback stay responsive
|
|
1447
1477
|
}
|
|
@@ -1604,11 +1634,14 @@ async function dispatchMessageInternal(params: {
|
|
|
1604
1634
|
|
|
1605
1635
|
// Await dispatch so per-session queue waits for reply delivery to complete
|
|
1606
1636
|
// before starting the next queued message.
|
|
1637
|
+
const fallbackQueueKey = `${account.accountId}:${conversationId}`;
|
|
1607
1638
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
1608
1639
|
ctx: ctxPayload,
|
|
1609
1640
|
cfg: actualCfg,
|
|
1610
1641
|
dispatcherOptions: {
|
|
1611
1642
|
deliver: async (payload: any) => {
|
|
1643
|
+
// Track delivery activity for queue busy detection
|
|
1644
|
+
markDeliverActivity(fallbackQueueKey);
|
|
1612
1645
|
// Recall typing indicator on first delivery
|
|
1613
1646
|
await cleanupTyping();
|
|
1614
1647
|
|
|
@@ -1668,7 +1701,6 @@ async function dispatchWithFullPipeline(params: {
|
|
|
1668
1701
|
log, setStatus, onFirstReply } = params;
|
|
1669
1702
|
|
|
1670
1703
|
let firstReplyFired = false;
|
|
1671
|
-
let typingSafetyTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
1672
1704
|
|
|
1673
1705
|
// 1. Resolve agent route via own bindings matching (like official plugin).
|
|
1674
1706
|
// OpenClaw's resolveAgentRoute doesn't handle accountId correctly for multi-account.
|
|
@@ -1758,13 +1790,15 @@ async function dispatchWithFullPipeline(params: {
|
|
|
1758
1790
|
}
|
|
1759
1791
|
|
|
1760
1792
|
// 8. Create typing-aware dispatcher
|
|
1793
|
+
const deliverQueueKey = `${account.accountId}:${conversationId}`;
|
|
1761
1794
|
const { dispatcher, replyOptions, markDispatchIdle } = rt.channel.reply.createReplyDispatcherWithTyping({
|
|
1762
1795
|
responsePrefix: '',
|
|
1763
1796
|
deliver: async (payload: any) => {
|
|
1797
|
+
// Track delivery activity for queue busy detection
|
|
1798
|
+
markDeliverActivity(deliverQueueKey);
|
|
1764
1799
|
// Recall typing indicator on first delivery
|
|
1765
1800
|
if (!firstReplyFired && onFirstReply) {
|
|
1766
1801
|
firstReplyFired = true;
|
|
1767
|
-
if (typingSafetyTimeout) { clearTimeout(typingSafetyTimeout); typingSafetyTimeout = null; }
|
|
1768
1802
|
await onFirstReply().catch((err) => {
|
|
1769
1803
|
log?.info?.("[dingtalk] onFirstReply error: " + err);
|
|
1770
1804
|
});
|
|
@@ -1789,24 +1823,24 @@ async function dispatchWithFullPipeline(params: {
|
|
|
1789
1823
|
|
|
1790
1824
|
// 9. Dispatch reply from config
|
|
1791
1825
|
try {
|
|
1826
|
+
log?.info?.("[dingtalk] dispatchReplyFromConfig started for " + deliverQueueKey);
|
|
1792
1827
|
await rt.channel.reply.dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyOptions });
|
|
1828
|
+
log?.info?.("[dingtalk] dispatchReplyFromConfig completed for " + deliverQueueKey);
|
|
1793
1829
|
} finally {
|
|
1794
|
-
|
|
1795
|
-
//
|
|
1796
|
-
//
|
|
1797
|
-
//
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
await onFirstReply().catch(() => {});
|
|
1807
|
-
}
|
|
1808
|
-
}, TYPING_SAFETY_TIMEOUT_MS);
|
|
1830
|
+
// OpenClaw 2026.4+ moved dispatcher.markComplete() + waitForIdle() out of
|
|
1831
|
+
// dispatchReplyFromConfig into the withReplyDispatcher wrapper. Since we call
|
|
1832
|
+
// dispatchReplyFromConfig directly (not through withReplyDispatcher), we must
|
|
1833
|
+
// do this ourselves to ensure all pending deliveries drain before returning.
|
|
1834
|
+
try {
|
|
1835
|
+
if (typeof dispatcher.markComplete === 'function') {
|
|
1836
|
+
dispatcher.markComplete();
|
|
1837
|
+
}
|
|
1838
|
+
await dispatcher.waitForIdle();
|
|
1839
|
+
log?.info?.("[dingtalk] dispatcher.waitForIdle completed for " + deliverQueueKey);
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
log?.info?.("[dingtalk] dispatcher settle error: " + err);
|
|
1809
1842
|
}
|
|
1843
|
+
markDispatchIdle();
|
|
1810
1844
|
}
|
|
1811
1845
|
|
|
1812
1846
|
// 10. Record activity
|