happy-imou-cloud 2.1.10 → 2.1.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/dist/{BaseReasoningProcessor-DbV-vhFy.mjs → BaseReasoningProcessor-BrQM8i44.mjs} +2 -2
- package/dist/{BaseReasoningProcessor-8GjgD9Nk.cjs → BaseReasoningProcessor-G3hno_kB.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-DWeq9rG2.cjs → ProviderSelectionHandler-B0HZZgMG.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-DXgu2zND.mjs → ProviderSelectionHandler-bkna8Dc0.mjs} +2 -2
- package/dist/{api-Dp5_DJo0.mjs → api-CEUrdZHY.mjs} +246 -27
- package/dist/{api-8p1d7M3Z.cjs → api-CtQFwLUJ.cjs} +246 -27
- package/dist/{command-BtjeL-Z7.cjs → command-C6M6wHp1.cjs} +3 -3
- package/dist/{command-DRtgCMGi.mjs → command-DZ8Od7tC.mjs} +3 -3
- package/dist/{index-Dz1Zo1-d.cjs → index-AwnMQcsU.cjs} +114 -17
- package/dist/{index-C_MTvw5p.mjs → index-Drrrz-Ot.mjs} +112 -15
- package/dist/index.cjs +3 -3
- package/dist/index.mjs +3 -3
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +52 -39
- package/dist/lib.d.mts +52 -39
- package/dist/lib.mjs +1 -1
- package/dist/{persistence-De7JoaQp.cjs → persistence-n4WMW33G.cjs} +1 -1
- package/dist/{persistence-Ds1cKgQ_.mjs → persistence-tRrZOs0P.mjs} +1 -1
- package/dist/{registerKillSessionHandler-DL07DW1o.cjs → registerKillSessionHandler-DKWTYJ3L.cjs} +3 -3
- package/dist/{registerKillSessionHandler-Bqd1W5ZT.mjs → registerKillSessionHandler-DPh8LfUR.mjs} +3 -3
- package/dist/{runClaude-DF_hjle_.cjs → runClaude-7rux3CCC.cjs} +5 -5
- package/dist/{runClaude-BtMjqmaI.mjs → runClaude-C-JjQB5i.mjs} +5 -5
- package/dist/{runCodex-MtvDLX7s.cjs → runCodex-D81osbbB.cjs} +6 -6
- package/dist/{runCodex-CxtZTXtC.mjs → runCodex-uHVY1g3W.mjs} +6 -6
- package/dist/{runGemini-CmRPi60L.mjs → runGemini-DFhltfvZ.mjs} +5 -5
- package/dist/{runGemini-Bf8tmYeU.cjs → runGemini-DaSpiomM.cjs} +5 -5
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as createSessionMetadata, p as publishSessionRegistration } from './index-
|
|
2
|
-
import { s as startOfflineReconnection, c as configuration, i as isAuthenticationRequiredError, l as logger } from './api-
|
|
1
|
+
import { a as createSessionMetadata, p as publishSessionRegistration } from './index-Drrrz-Ot.mjs';
|
|
2
|
+
import { s as startOfflineReconnection, c as configuration, i as isAuthenticationRequiredError, l as logger } from './api-CEUrdZHY.mjs';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
4
|
-
var api = require('./api-
|
|
3
|
+
var index = require('./index-AwnMQcsU.cjs');
|
|
4
|
+
var api = require('./api-CtQFwLUJ.cjs');
|
|
5
5
|
var node_events = require('node:events');
|
|
6
6
|
var node_crypto = require('node:crypto');
|
|
7
7
|
|
package/dist/{ProviderSelectionHandler-DWeq9rG2.cjs → ProviderSelectionHandler-B0HZZgMG.cjs}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var api = require('./api-
|
|
4
|
-
var registerKillSessionHandler = require('./registerKillSessionHandler-
|
|
3
|
+
var api = require('./api-CtQFwLUJ.cjs');
|
|
4
|
+
var registerKillSessionHandler = require('./registerKillSessionHandler-DKWTYJ3L.cjs');
|
|
5
5
|
|
|
6
6
|
async function runModeLoop(opts) {
|
|
7
7
|
let currentMode = opts.startingMode;
|
package/dist/{ProviderSelectionHandler-DXgu2zND.mjs → ProviderSelectionHandler-bkna8Dc0.mjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { l as logger } from './api-
|
|
2
|
-
import { g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, a as INTERACTION_TIMED_OUT_ERROR } from './registerKillSessionHandler-
|
|
1
|
+
import { l as logger } from './api-CEUrdZHY.mjs';
|
|
2
|
+
import { g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, a as INTERACTION_TIMED_OUT_ERROR } from './registerKillSessionHandler-DPh8LfUR.mjs';
|
|
3
3
|
|
|
4
4
|
async function runModeLoop(opts) {
|
|
5
5
|
let currentMode = opts.startingMode;
|
|
@@ -16,7 +16,7 @@ import { spawn } from 'node:child_process';
|
|
|
16
16
|
import { Expo } from 'expo-server-sdk';
|
|
17
17
|
|
|
18
18
|
var name = "happy-imou-cloud";
|
|
19
|
-
var version = "2.1.
|
|
19
|
+
var version = "2.1.11";
|
|
20
20
|
var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
|
|
21
21
|
var author = "long.zhu";
|
|
22
22
|
var license = "MIT";
|
|
@@ -431,7 +431,7 @@ async function listDaemonLogFiles(limit = 50) {
|
|
|
431
431
|
return { file, path: fullPath, modified: stats.mtime };
|
|
432
432
|
}).sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
433
433
|
try {
|
|
434
|
-
const { readDaemonState } = await import('./persistence-
|
|
434
|
+
const { readDaemonState } = await import('./persistence-tRrZOs0P.mjs');
|
|
435
435
|
const state = await readDaemonState();
|
|
436
436
|
if (!state) {
|
|
437
437
|
return logs;
|
|
@@ -1737,6 +1737,8 @@ const MAX_PENDING_RELIABLE_SESSION_MESSAGE_BYTES = 512 * 1024;
|
|
|
1737
1737
|
const PROTOCOL_V3_INITIAL_SNAPSHOT_LIMIT = 150;
|
|
1738
1738
|
const PROTOCOL_V3_CHANGES_PAGE_LIMIT = 200;
|
|
1739
1739
|
const PROTOCOL_V3_CAPABILITIES_WAIT_MS = 250;
|
|
1740
|
+
const COMMITTED_SESSION_WRITE_ACK_TIMEOUT_MS = 5e3;
|
|
1741
|
+
const RELIABLE_SESSION_MESSAGE_RETRY_DELAY_MS = 250;
|
|
1740
1742
|
class ApiSessionClient extends EventEmitter {
|
|
1741
1743
|
credentials;
|
|
1742
1744
|
sessionId;
|
|
@@ -1752,8 +1754,12 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1752
1754
|
metadataLock = new AsyncLock();
|
|
1753
1755
|
encryptionKey;
|
|
1754
1756
|
encryptionVariant;
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
+
queuedReliableSessionMessages = [];
|
|
1758
|
+
queuedReliableSessionMessageBytes = 0;
|
|
1759
|
+
inFlightReliableSessionMessage = null;
|
|
1760
|
+
reliableSessionMessageFlushInProgress = false;
|
|
1761
|
+
reliableSessionMessageDrainWaiters = [];
|
|
1762
|
+
reliableSessionMessageRetryTimer = null;
|
|
1757
1763
|
reconnectAfterServerDisconnectTimer = null;
|
|
1758
1764
|
lastSocketServerError = null;
|
|
1759
1765
|
protocolV3SessionSync = null;
|
|
@@ -1765,6 +1771,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1765
1771
|
protocolV3Descriptor = null;
|
|
1766
1772
|
protocolV3SocketCapabilities = null;
|
|
1767
1773
|
protocolV3SessionSyncWaitTimer = null;
|
|
1774
|
+
committedSessionWriteAckMode;
|
|
1768
1775
|
constructor(credentials, session) {
|
|
1769
1776
|
super();
|
|
1770
1777
|
this.credentials = credentials;
|
|
@@ -1776,6 +1783,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1776
1783
|
this.encryptionKey = session.encryptionKey;
|
|
1777
1784
|
this.encryptionVariant = session.encryptionVariant;
|
|
1778
1785
|
this.lastChangeSeq = session.seq;
|
|
1786
|
+
this.committedSessionWriteAckMode = this.credentials.signing ? "unknown" : "unsupported";
|
|
1779
1787
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1780
1788
|
scopePrefix: this.sessionId,
|
|
1781
1789
|
encryptionKey: this.encryptionKey,
|
|
@@ -1808,11 +1816,13 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1808
1816
|
this.socket.on("connect", () => {
|
|
1809
1817
|
logger.debug("Socket connected successfully");
|
|
1810
1818
|
this.clearReconnectAfterServerDisconnectTimer();
|
|
1819
|
+
this.clearReliableSessionMessageRetryTimer();
|
|
1811
1820
|
this.lastSocketServerError = null;
|
|
1812
1821
|
this.rpcHandlerManager.onSocketConnect(this.socket);
|
|
1813
|
-
this.flushReliableSessionMessages();
|
|
1814
1822
|
this.protocolV3Descriptor = null;
|
|
1815
1823
|
this.protocolV3SocketCapabilities = null;
|
|
1824
|
+
this.committedSessionWriteAckMode = this.credentials.signing ? "unknown" : "unsupported";
|
|
1825
|
+
this.flushReliableSessionMessages();
|
|
1816
1826
|
this.scheduleProtocolV3SessionSync();
|
|
1817
1827
|
});
|
|
1818
1828
|
this.socket.on("rpc-request", async (data, callback) => {
|
|
@@ -1820,8 +1830,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1820
1830
|
});
|
|
1821
1831
|
this.socket.on("disconnect", (reason) => {
|
|
1822
1832
|
logger.debug("[API] Socket disconnected:", reason);
|
|
1833
|
+
this.clearReliableSessionMessageRetryTimer();
|
|
1823
1834
|
this.rpcHandlerManager.onSocketDisconnect();
|
|
1824
1835
|
this.invalidateProtocolV3SessionSync();
|
|
1836
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
1825
1837
|
this.retryAfterServerDisconnect(reason);
|
|
1826
1838
|
});
|
|
1827
1839
|
this.socket.on("connect_error", (error) => {
|
|
@@ -1849,8 +1861,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1849
1861
|
connectionType: data.connectionType,
|
|
1850
1862
|
capabilities: data.protocol.capabilities
|
|
1851
1863
|
});
|
|
1864
|
+
this.committedSessionWriteAckMode = this.supportsKnownProtocolCapability("message_ack_v3") ? "supported" : "unsupported";
|
|
1852
1865
|
this.clearProtocolV3SessionSyncWaitTimer();
|
|
1853
1866
|
this.scheduleProtocolV3SessionSync({ waitForCapabilities: false });
|
|
1867
|
+
this.flushReliableSessionMessages();
|
|
1854
1868
|
} catch (error) {
|
|
1855
1869
|
logger.debug("[SOCKET] [CAPABILITIES] [ERROR] Error handling capabilities event", { error });
|
|
1856
1870
|
}
|
|
@@ -1990,15 +2004,24 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1990
2004
|
};
|
|
1991
2005
|
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1992
2006
|
const eventType = typeof body?.type === "string" ? body.type : "unknown";
|
|
2007
|
+
const reliableMessage = {
|
|
2008
|
+
encrypted,
|
|
2009
|
+
type: `codex:${eventType}`,
|
|
2010
|
+
localId: randomUUID()
|
|
2011
|
+
};
|
|
1993
2012
|
if (!this.socket.connected) {
|
|
1994
2013
|
if (this.shouldBufferReliableCodexMessage(body)) {
|
|
1995
2014
|
logger.debug("[API] Socket not connected, buffering reliable Codex message:", { type: eventType });
|
|
1996
|
-
this.
|
|
2015
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
1997
2016
|
} else {
|
|
1998
2017
|
logger.debug("[API] Socket not connected, dropping non-critical Codex message:", { type: eventType });
|
|
1999
2018
|
}
|
|
2000
2019
|
return;
|
|
2001
2020
|
}
|
|
2021
|
+
if (this.shouldBufferReliableCodexMessage(body) && this.shouldUseCommittedSessionWriteAcks()) {
|
|
2022
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2002
2025
|
this.emitEncryptedSessionMessage(encrypted);
|
|
2003
2026
|
}
|
|
2004
2027
|
/**
|
|
@@ -2023,13 +2046,18 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2023
2046
|
logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
|
|
2024
2047
|
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
2025
2048
|
const eventType = typeof body?.type === "string" ? body.type : "unknown";
|
|
2049
|
+
const reliableMessage = {
|
|
2050
|
+
encrypted,
|
|
2051
|
+
type: `acp:${provider}:${eventType}`,
|
|
2052
|
+
localId: randomUUID()
|
|
2053
|
+
};
|
|
2026
2054
|
if (!this.socket.connected) {
|
|
2027
2055
|
if (this.shouldBufferReliableAcpMessage(body)) {
|
|
2028
2056
|
logger.debug("[API] Socket not connected, buffering reliable ACP message:", {
|
|
2029
2057
|
provider,
|
|
2030
2058
|
type: eventType
|
|
2031
2059
|
});
|
|
2032
|
-
this.
|
|
2060
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2033
2061
|
} else {
|
|
2034
2062
|
logger.debug("[API] Socket not connected, dropping non-critical ACP message:", {
|
|
2035
2063
|
provider,
|
|
@@ -2038,10 +2066,14 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2038
2066
|
}
|
|
2039
2067
|
return;
|
|
2040
2068
|
}
|
|
2069
|
+
if (this.shouldBufferReliableAcpMessage(body) && this.shouldUseCommittedSessionWriteAcks()) {
|
|
2070
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2041
2073
|
this.emitEncryptedSessionMessage(encrypted);
|
|
2042
2074
|
}
|
|
2043
2075
|
sendSessionEvent(event, id) {
|
|
2044
|
-
|
|
2076
|
+
const content = {
|
|
2045
2077
|
role: "agent",
|
|
2046
2078
|
content: {
|
|
2047
2079
|
id: id ?? randomUUID(),
|
|
@@ -2050,15 +2082,24 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2050
2082
|
}
|
|
2051
2083
|
};
|
|
2052
2084
|
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
2085
|
+
const reliableMessage = {
|
|
2086
|
+
encrypted,
|
|
2087
|
+
type: `event:${event.type}`,
|
|
2088
|
+
localId: content.content.id
|
|
2089
|
+
};
|
|
2053
2090
|
if (!this.socket.connected) {
|
|
2054
2091
|
if (this.shouldBufferReliableSessionEvent(event)) {
|
|
2055
2092
|
logger.debug("[API] Socket not connected, buffering session event:", { type: event.type });
|
|
2056
|
-
this.
|
|
2093
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2057
2094
|
} else {
|
|
2058
2095
|
logger.debug("[API] Socket not connected, dropping session event:", { type: event.type });
|
|
2059
2096
|
}
|
|
2060
2097
|
return;
|
|
2061
2098
|
}
|
|
2099
|
+
if (this.shouldBufferReliableSessionEvent(event) && this.shouldUseCommittedSessionWriteAcks()) {
|
|
2100
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2062
2103
|
this.emitEncryptedSessionMessage(encrypted);
|
|
2063
2104
|
}
|
|
2064
2105
|
/**
|
|
@@ -2172,6 +2213,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2172
2213
|
* Wait for socket buffer to flush
|
|
2173
2214
|
*/
|
|
2174
2215
|
async flush() {
|
|
2216
|
+
if (!this.socket.connected) {
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
await this.waitForReliableSessionMessagesToDrain();
|
|
2175
2220
|
if (!this.socket.connected) {
|
|
2176
2221
|
return;
|
|
2177
2222
|
}
|
|
@@ -2461,31 +2506,197 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2461
2506
|
supportsKnownProtocolCapability(capability) {
|
|
2462
2507
|
return Array.isArray(this.protocolV3Descriptor?.capabilities) && this.protocolV3Descriptor.capabilities.includes(capability);
|
|
2463
2508
|
}
|
|
2464
|
-
|
|
2509
|
+
shouldUseCommittedSessionWriteAcks() {
|
|
2510
|
+
if (!this.credentials.signing) {
|
|
2511
|
+
return false;
|
|
2512
|
+
}
|
|
2513
|
+
return this.committedSessionWriteAckMode !== "unsupported";
|
|
2514
|
+
}
|
|
2515
|
+
emitEncryptedSessionMessage(encrypted, localId) {
|
|
2465
2516
|
this.socket.emit("message", {
|
|
2466
2517
|
sid: this.sessionId,
|
|
2467
|
-
message: encrypted
|
|
2518
|
+
message: encrypted,
|
|
2519
|
+
...typeof localId === "string" ? { localId } : {}
|
|
2468
2520
|
});
|
|
2469
2521
|
}
|
|
2470
|
-
|
|
2471
|
-
this.
|
|
2472
|
-
this.
|
|
2522
|
+
enqueueReliableSessionMessage(message) {
|
|
2523
|
+
this.queuedReliableSessionMessages.push(message);
|
|
2524
|
+
this.queuedReliableSessionMessageBytes += message.encrypted.length;
|
|
2473
2525
|
this.trimPendingReliableSessionMessages();
|
|
2526
|
+
this.flushReliableSessionMessages();
|
|
2527
|
+
}
|
|
2528
|
+
dequeueReliableSessionMessage() {
|
|
2529
|
+
const message = this.queuedReliableSessionMessages.shift() ?? null;
|
|
2530
|
+
if (!message) {
|
|
2531
|
+
return null;
|
|
2532
|
+
}
|
|
2533
|
+
this.queuedReliableSessionMessageBytes -= message.encrypted.length;
|
|
2534
|
+
return message;
|
|
2474
2535
|
}
|
|
2475
2536
|
flushReliableSessionMessages() {
|
|
2476
|
-
|
|
2537
|
+
this.clearReliableSessionMessageRetryTimer();
|
|
2538
|
+
if (this.reliableSessionMessageFlushInProgress || !this.socket.connected) {
|
|
2539
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
this.reliableSessionMessageFlushInProgress = true;
|
|
2543
|
+
void (async () => {
|
|
2544
|
+
try {
|
|
2545
|
+
while (this.socket.connected) {
|
|
2546
|
+
const message = this.inFlightReliableSessionMessage ?? this.dequeueReliableSessionMessage();
|
|
2547
|
+
if (!message) {
|
|
2548
|
+
this.inFlightReliableSessionMessage = null;
|
|
2549
|
+
break;
|
|
2550
|
+
}
|
|
2551
|
+
if (this.inFlightReliableSessionMessage === null) {
|
|
2552
|
+
logger.debug("[API] Flushing buffered session message after reconnect", {
|
|
2553
|
+
type: message.type,
|
|
2554
|
+
localId: message.localId
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
this.inFlightReliableSessionMessage = message;
|
|
2558
|
+
if (this.shouldUseCommittedSessionWriteAcks()) {
|
|
2559
|
+
const result = await this.emitEncryptedSessionMessageWithAck(message);
|
|
2560
|
+
if (result === "retry") {
|
|
2561
|
+
this.scheduleReliableSessionMessageRetry();
|
|
2562
|
+
break;
|
|
2563
|
+
}
|
|
2564
|
+
} else {
|
|
2565
|
+
this.emitEncryptedSessionMessage(message.encrypted, message.localId);
|
|
2566
|
+
}
|
|
2567
|
+
this.inFlightReliableSessionMessage = null;
|
|
2568
|
+
}
|
|
2569
|
+
} finally {
|
|
2570
|
+
this.reliableSessionMessageFlushInProgress = false;
|
|
2571
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
2572
|
+
}
|
|
2573
|
+
})();
|
|
2574
|
+
}
|
|
2575
|
+
async waitForReliableSessionMessagesToDrain() {
|
|
2576
|
+
if (!this.socket.connected) {
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
this.flushReliableSessionMessages();
|
|
2580
|
+
if (!this.reliableSessionMessageFlushInProgress && this.inFlightReliableSessionMessage === null && this.queuedReliableSessionMessages.length === 0) {
|
|
2477
2581
|
return;
|
|
2478
2582
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
logger.debug("[API] Flushing buffered session messages after reconnect", {
|
|
2482
|
-
count: buffered.length,
|
|
2483
|
-
types: buffered.map((message) => message.type)
|
|
2583
|
+
await new Promise((resolve) => {
|
|
2584
|
+
this.reliableSessionMessageDrainWaiters.push(resolve);
|
|
2484
2585
|
});
|
|
2485
|
-
|
|
2486
|
-
|
|
2586
|
+
}
|
|
2587
|
+
resolveReliableSessionMessageDrainWaitersIfIdle() {
|
|
2588
|
+
if (!this.socket.connected) {
|
|
2589
|
+
const disconnectedWaiters = this.reliableSessionMessageDrainWaiters.splice(0, this.reliableSessionMessageDrainWaiters.length);
|
|
2590
|
+
for (const waiter of disconnectedWaiters) {
|
|
2591
|
+
waiter();
|
|
2592
|
+
}
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
if (this.reliableSessionMessageFlushInProgress || this.inFlightReliableSessionMessage !== null || this.queuedReliableSessionMessages.length > 0) {
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2598
|
+
const waiters = this.reliableSessionMessageDrainWaiters.splice(0, this.reliableSessionMessageDrainWaiters.length);
|
|
2599
|
+
for (const waiter of waiters) {
|
|
2600
|
+
waiter();
|
|
2487
2601
|
}
|
|
2488
2602
|
}
|
|
2603
|
+
scheduleReliableSessionMessageRetry() {
|
|
2604
|
+
if (this.reliableSessionMessageRetryTimer || !this.socket.connected || this.inFlightReliableSessionMessage === null) {
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
this.reliableSessionMessageRetryTimer = setTimeout(() => {
|
|
2608
|
+
this.reliableSessionMessageRetryTimer = null;
|
|
2609
|
+
if (!this.socket.connected || this.inFlightReliableSessionMessage === null) {
|
|
2610
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
this.flushReliableSessionMessages();
|
|
2614
|
+
}, RELIABLE_SESSION_MESSAGE_RETRY_DELAY_MS);
|
|
2615
|
+
}
|
|
2616
|
+
clearReliableSessionMessageRetryTimer() {
|
|
2617
|
+
if (!this.reliableSessionMessageRetryTimer) {
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
clearTimeout(this.reliableSessionMessageRetryTimer);
|
|
2621
|
+
this.reliableSessionMessageRetryTimer = null;
|
|
2622
|
+
}
|
|
2623
|
+
async emitEncryptedSessionMessageWithAck(message) {
|
|
2624
|
+
try {
|
|
2625
|
+
const response = await this.withAckTimeout(
|
|
2626
|
+
Promise.resolve(this.socket.emitWithAck("message", {
|
|
2627
|
+
sid: this.sessionId,
|
|
2628
|
+
message: message.encrypted,
|
|
2629
|
+
localId: message.localId
|
|
2630
|
+
})),
|
|
2631
|
+
COMMITTED_SESSION_WRITE_ACK_TIMEOUT_MS
|
|
2632
|
+
);
|
|
2633
|
+
if (!response || typeof response !== "object" || !("ok" in response)) {
|
|
2634
|
+
if (this.committedSessionWriteAckMode === "unknown") {
|
|
2635
|
+
logger.debug("[API] Falling back to fire-and-forget session writes because websocket write acknowledgements are unavailable", {
|
|
2636
|
+
sessionId: this.sessionId,
|
|
2637
|
+
type: message.type
|
|
2638
|
+
});
|
|
2639
|
+
this.committedSessionWriteAckMode = "unsupported";
|
|
2640
|
+
this.emitEncryptedSessionMessage(message.encrypted, message.localId);
|
|
2641
|
+
return "committed";
|
|
2642
|
+
}
|
|
2643
|
+
logger.debug("[API] Session write acknowledgement payload was invalid, will retry on reconnect", {
|
|
2644
|
+
sessionId: this.sessionId,
|
|
2645
|
+
type: message.type,
|
|
2646
|
+
response
|
|
2647
|
+
});
|
|
2648
|
+
return "retry";
|
|
2649
|
+
}
|
|
2650
|
+
const ackResponse = response;
|
|
2651
|
+
if (!ackResponse.ok) {
|
|
2652
|
+
logger.debug("[API] Session write was rejected by the server", {
|
|
2653
|
+
sessionId: this.sessionId,
|
|
2654
|
+
type: message.type,
|
|
2655
|
+
localId: message.localId,
|
|
2656
|
+
error: ackResponse.error
|
|
2657
|
+
});
|
|
2658
|
+
this.emit("message-ack", ackResponse);
|
|
2659
|
+
return "committed";
|
|
2660
|
+
}
|
|
2661
|
+
this.committedSessionWriteAckMode = "supported";
|
|
2662
|
+
this.emit("message-ack", ackResponse);
|
|
2663
|
+
return "committed";
|
|
2664
|
+
} catch (error) {
|
|
2665
|
+
if (this.committedSessionWriteAckMode === "unknown" && isAckTimeoutError(error)) {
|
|
2666
|
+
logger.debug("[API] Websocket write acknowledgements timed out before protocol capabilities were confirmed, falling back to fire-and-forget writes", {
|
|
2667
|
+
sessionId: this.sessionId,
|
|
2668
|
+
type: message.type
|
|
2669
|
+
});
|
|
2670
|
+
this.committedSessionWriteAckMode = "unsupported";
|
|
2671
|
+
this.emitEncryptedSessionMessage(message.encrypted, message.localId);
|
|
2672
|
+
return "committed";
|
|
2673
|
+
}
|
|
2674
|
+
logger.debug("[API] Reliable session write acknowledgement failed, will retry when the socket reconnects", {
|
|
2675
|
+
sessionId: this.sessionId,
|
|
2676
|
+
type: message.type,
|
|
2677
|
+
localId: message.localId,
|
|
2678
|
+
error
|
|
2679
|
+
});
|
|
2680
|
+
return "retry";
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
async withAckTimeout(promise, timeoutMs) {
|
|
2684
|
+
return await new Promise((resolve, reject) => {
|
|
2685
|
+
const timeout = setTimeout(() => {
|
|
2686
|
+
reject(createAckTimeoutError(timeoutMs));
|
|
2687
|
+
}, timeoutMs);
|
|
2688
|
+
promise.then(
|
|
2689
|
+
(value) => {
|
|
2690
|
+
clearTimeout(timeout);
|
|
2691
|
+
resolve(value);
|
|
2692
|
+
},
|
|
2693
|
+
(error) => {
|
|
2694
|
+
clearTimeout(timeout);
|
|
2695
|
+
reject(error);
|
|
2696
|
+
}
|
|
2697
|
+
);
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2489
2700
|
shouldBufferReliableCodexMessage(body) {
|
|
2490
2701
|
switch (body?.type) {
|
|
2491
2702
|
case "message":
|
|
@@ -2532,19 +2743,19 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2532
2743
|
}
|
|
2533
2744
|
trimPendingReliableSessionMessages() {
|
|
2534
2745
|
let dropped = 0;
|
|
2535
|
-
while (this.
|
|
2536
|
-
const removed = this.
|
|
2746
|
+
while (this.queuedReliableSessionMessages.length > MAX_PENDING_RELIABLE_SESSION_MESSAGES || this.queuedReliableSessionMessageBytes > MAX_PENDING_RELIABLE_SESSION_MESSAGE_BYTES) {
|
|
2747
|
+
const removed = this.queuedReliableSessionMessages.shift();
|
|
2537
2748
|
if (!removed) {
|
|
2538
2749
|
break;
|
|
2539
2750
|
}
|
|
2540
|
-
this.
|
|
2751
|
+
this.queuedReliableSessionMessageBytes -= removed.encrypted.length;
|
|
2541
2752
|
dropped += 1;
|
|
2542
2753
|
}
|
|
2543
2754
|
if (dropped > 0) {
|
|
2544
2755
|
logger.debug("[API] Dropped oldest buffered session messages to cap reconnect memory usage", {
|
|
2545
2756
|
dropped,
|
|
2546
|
-
remaining: this.
|
|
2547
|
-
bytes: this.
|
|
2757
|
+
remaining: this.queuedReliableSessionMessages.length,
|
|
2758
|
+
bytes: this.queuedReliableSessionMessageBytes
|
|
2548
2759
|
});
|
|
2549
2760
|
}
|
|
2550
2761
|
}
|
|
@@ -2601,6 +2812,14 @@ function createAbortError() {
|
|
|
2601
2812
|
error.name = "AbortError";
|
|
2602
2813
|
return error;
|
|
2603
2814
|
}
|
|
2815
|
+
function createAckTimeoutError(timeoutMs) {
|
|
2816
|
+
const error = new Error(`Timed out waiting for websocket message acknowledgement after ${timeoutMs}ms`);
|
|
2817
|
+
error.name = "AckTimeoutError";
|
|
2818
|
+
return error;
|
|
2819
|
+
}
|
|
2820
|
+
function isAckTimeoutError(error) {
|
|
2821
|
+
return error instanceof Error && error.name === "AckTimeoutError";
|
|
2822
|
+
}
|
|
2604
2823
|
|
|
2605
2824
|
class ApiMachineClient {
|
|
2606
2825
|
constructor(credentials, machine) {
|