happy-imou-cloud 2.1.10 → 2.1.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/bin/happy-cloud.mjs +1 -1
- package/dist/{BaseReasoningProcessor-DbV-vhFy.mjs → BaseReasoningProcessor-DgKkEyg_.mjs} +2 -2
- package/dist/{BaseReasoningProcessor-8GjgD9Nk.cjs → BaseReasoningProcessor-yYne8hbQ.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-DWeq9rG2.cjs → ProviderSelectionHandler-CARO_dnJ.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-DXgu2zND.mjs → ProviderSelectionHandler-CxLijFCe.mjs} +2 -2
- package/dist/{api-Dp5_DJo0.mjs → api-0mR8QJK2.mjs} +266 -29
- package/dist/{api-8p1d7M3Z.cjs → api-CwRFeZQw.cjs} +266 -29
- package/dist/{command-BtjeL-Z7.cjs → command-0ypdkfyo.cjs} +3 -3
- package/dist/{command-DRtgCMGi.mjs → command-Dh_oQxDu.mjs} +3 -3
- package/dist/{index-Dz1Zo1-d.cjs → index-IkNv7MfY.cjs} +114 -17
- package/dist/{index-C_MTvw5p.mjs → index-zIZBoE62.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 +282 -17
- package/dist/lib.d.mts +282 -17
- package/dist/lib.mjs +1 -1
- package/dist/{persistence-De7JoaQp.cjs → persistence-BlqXzujW.cjs} +1 -1
- package/dist/{persistence-Ds1cKgQ_.mjs → persistence-CZEdRTGx.mjs} +1 -1
- package/dist/{registerKillSessionHandler-DL07DW1o.cjs → registerKillSessionHandler-B0an4vUf.cjs} +133 -9
- package/dist/{registerKillSessionHandler-Bqd1W5ZT.mjs → registerKillSessionHandler-DYg4cQCz.mjs} +133 -9
- package/dist/{runClaude-DF_hjle_.cjs → runClaude-C6PA0-0n.cjs} +15 -7
- package/dist/{runClaude-BtMjqmaI.mjs → runClaude-DWeTS-VL.mjs} +15 -7
- package/dist/{runCodex-MtvDLX7s.cjs → runCodex-B_TM-cqA.cjs} +15 -8
- package/dist/{runCodex-CxtZTXtC.mjs → runCodex-DUXczsJP.mjs} +15 -8
- package/dist/{runGemini-Bf8tmYeU.cjs → runGemini-0-mnECiy.cjs} +15 -7
- package/dist/{runGemini-CmRPi60L.mjs → runGemini-CBpN6a9w.mjs} +15 -7
- package/package.json +1 -1
- package/scripts/devtools/README.md +9 -9
- package/scripts/e2e/fake-codex-acp-agent.mjs +139 -139
- package/scripts/e2e/local-server-session-roundtrip.mjs +1063 -1063
- package/scripts/ensureAcpSdkCompat.mjs +1 -1
package/bin/happy-cloud.mjs
CHANGED
|
@@ -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-zIZBoE62.mjs';
|
|
2
|
+
import { s as startOfflineReconnection, c as configuration, i as isAuthenticationRequiredError, l as logger } from './api-0mR8QJK2.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-IkNv7MfY.cjs');
|
|
4
|
+
var api = require('./api-CwRFeZQw.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-CARO_dnJ.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-CwRFeZQw.cjs');
|
|
4
|
+
var registerKillSessionHandler = require('./registerKillSessionHandler-B0an4vUf.cjs');
|
|
5
5
|
|
|
6
6
|
async function runModeLoop(opts) {
|
|
7
7
|
let currentMode = opts.startingMode;
|
package/dist/{ProviderSelectionHandler-DXgu2zND.mjs → ProviderSelectionHandler-CxLijFCe.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-0mR8QJK2.mjs';
|
|
2
|
+
import { g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, a as INTERACTION_TIMED_OUT_ERROR } from './registerKillSessionHandler-DYg4cQCz.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.12";
|
|
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-CZEdRTGx.mjs');
|
|
435
435
|
const state = await readDaemonState();
|
|
436
436
|
if (!state) {
|
|
437
437
|
return logs;
|
|
@@ -592,9 +592,23 @@ const HappyOrgTaskControlSchema = z.object({
|
|
|
592
592
|
newDecision: z.string().min(1).optional().nullable(),
|
|
593
593
|
newResource: z.string().min(1).optional().nullable()
|
|
594
594
|
});
|
|
595
|
+
const HappyOrgReplyContextSchema = z.object({
|
|
596
|
+
dispatchId: z.string().min(1),
|
|
597
|
+
scope: z.string().min(1),
|
|
598
|
+
replyTo: z.string().min(1)
|
|
599
|
+
});
|
|
600
|
+
const HappyOrgSpecialistHomeIdentitySchema = z.object({
|
|
601
|
+
homeSlug: z.string().min(1),
|
|
602
|
+
path: z.string().min(1).nullish(),
|
|
603
|
+
happySessionId: z.string().min(1).nullish(),
|
|
604
|
+
machineId: z.string().min(1).nullish(),
|
|
605
|
+
startedBy: z.enum(["daemon", "terminal"]).nullish(),
|
|
606
|
+
flavor: z.string().min(1).nullish()
|
|
607
|
+
});
|
|
595
608
|
const HappyOrgMessageMetaSchema = z.object({
|
|
596
609
|
taskContext: HappyOrgTaskContextSchema.optional(),
|
|
597
|
-
control: HappyOrgTaskControlSchema.optional()
|
|
610
|
+
control: HappyOrgTaskControlSchema.optional(),
|
|
611
|
+
replyContext: HappyOrgReplyContextSchema.optional()
|
|
598
612
|
});
|
|
599
613
|
z.object({
|
|
600
614
|
turnStatus: HappyOrgTurnStatusSchema.optional().nullable(),
|
|
@@ -611,7 +625,9 @@ const HappyOrgTurnReportSchema = HappyOrgTaskContextSchema.extend({
|
|
|
611
625
|
blockerCode: z.string().nullable(),
|
|
612
626
|
decisionNeeded: z.string().nullable(),
|
|
613
627
|
targetArtifact: z.string().nullable(),
|
|
614
|
-
repeatFingerprint: z.string().nullable()
|
|
628
|
+
repeatFingerprint: z.string().nullable(),
|
|
629
|
+
replyContext: HappyOrgReplyContextSchema.nullish(),
|
|
630
|
+
specialistHome: HappyOrgSpecialistHomeIdentitySchema.nullish()
|
|
615
631
|
});
|
|
616
632
|
const HappyOrgRepeatEntrySchema = z.object({
|
|
617
633
|
count: z.number().int().nonnegative(),
|
|
@@ -638,6 +654,8 @@ z.object({
|
|
|
638
654
|
taskContext: HappyOrgTaskContextSchema.optional(),
|
|
639
655
|
runtime: HappyOrgRuntimeStateSchema.optional(),
|
|
640
656
|
activeOwner: HappyOrgTaskOwnershipSchema.nullish(),
|
|
657
|
+
replyContext: HappyOrgReplyContextSchema.nullish(),
|
|
658
|
+
specialistHome: HappyOrgSpecialistHomeIdentitySchema.nullish(),
|
|
641
659
|
repeat: z.object({
|
|
642
660
|
threshold: z.number().int().positive(),
|
|
643
661
|
fingerprints: z.record(z.string(), HappyOrgRepeatEntrySchema)
|
|
@@ -1737,6 +1755,8 @@ const MAX_PENDING_RELIABLE_SESSION_MESSAGE_BYTES = 512 * 1024;
|
|
|
1737
1755
|
const PROTOCOL_V3_INITIAL_SNAPSHOT_LIMIT = 150;
|
|
1738
1756
|
const PROTOCOL_V3_CHANGES_PAGE_LIMIT = 200;
|
|
1739
1757
|
const PROTOCOL_V3_CAPABILITIES_WAIT_MS = 250;
|
|
1758
|
+
const COMMITTED_SESSION_WRITE_ACK_TIMEOUT_MS = 5e3;
|
|
1759
|
+
const RELIABLE_SESSION_MESSAGE_RETRY_DELAY_MS = 250;
|
|
1740
1760
|
class ApiSessionClient extends EventEmitter {
|
|
1741
1761
|
credentials;
|
|
1742
1762
|
sessionId;
|
|
@@ -1752,8 +1772,12 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1752
1772
|
metadataLock = new AsyncLock();
|
|
1753
1773
|
encryptionKey;
|
|
1754
1774
|
encryptionVariant;
|
|
1755
|
-
|
|
1756
|
-
|
|
1775
|
+
queuedReliableSessionMessages = [];
|
|
1776
|
+
queuedReliableSessionMessageBytes = 0;
|
|
1777
|
+
inFlightReliableSessionMessage = null;
|
|
1778
|
+
reliableSessionMessageFlushInProgress = false;
|
|
1779
|
+
reliableSessionMessageDrainWaiters = [];
|
|
1780
|
+
reliableSessionMessageRetryTimer = null;
|
|
1757
1781
|
reconnectAfterServerDisconnectTimer = null;
|
|
1758
1782
|
lastSocketServerError = null;
|
|
1759
1783
|
protocolV3SessionSync = null;
|
|
@@ -1765,6 +1789,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1765
1789
|
protocolV3Descriptor = null;
|
|
1766
1790
|
protocolV3SocketCapabilities = null;
|
|
1767
1791
|
protocolV3SessionSyncWaitTimer = null;
|
|
1792
|
+
committedSessionWriteAckMode;
|
|
1768
1793
|
constructor(credentials, session) {
|
|
1769
1794
|
super();
|
|
1770
1795
|
this.credentials = credentials;
|
|
@@ -1776,6 +1801,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1776
1801
|
this.encryptionKey = session.encryptionKey;
|
|
1777
1802
|
this.encryptionVariant = session.encryptionVariant;
|
|
1778
1803
|
this.lastChangeSeq = session.seq;
|
|
1804
|
+
this.committedSessionWriteAckMode = this.credentials.signing ? "unknown" : "unsupported";
|
|
1779
1805
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1780
1806
|
scopePrefix: this.sessionId,
|
|
1781
1807
|
encryptionKey: this.encryptionKey,
|
|
@@ -1808,11 +1834,13 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1808
1834
|
this.socket.on("connect", () => {
|
|
1809
1835
|
logger.debug("Socket connected successfully");
|
|
1810
1836
|
this.clearReconnectAfterServerDisconnectTimer();
|
|
1837
|
+
this.clearReliableSessionMessageRetryTimer();
|
|
1811
1838
|
this.lastSocketServerError = null;
|
|
1812
1839
|
this.rpcHandlerManager.onSocketConnect(this.socket);
|
|
1813
|
-
this.flushReliableSessionMessages();
|
|
1814
1840
|
this.protocolV3Descriptor = null;
|
|
1815
1841
|
this.protocolV3SocketCapabilities = null;
|
|
1842
|
+
this.committedSessionWriteAckMode = this.credentials.signing ? "unknown" : "unsupported";
|
|
1843
|
+
this.flushReliableSessionMessages();
|
|
1816
1844
|
this.scheduleProtocolV3SessionSync();
|
|
1817
1845
|
});
|
|
1818
1846
|
this.socket.on("rpc-request", async (data, callback) => {
|
|
@@ -1820,8 +1848,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1820
1848
|
});
|
|
1821
1849
|
this.socket.on("disconnect", (reason) => {
|
|
1822
1850
|
logger.debug("[API] Socket disconnected:", reason);
|
|
1851
|
+
this.clearReliableSessionMessageRetryTimer();
|
|
1823
1852
|
this.rpcHandlerManager.onSocketDisconnect();
|
|
1824
1853
|
this.invalidateProtocolV3SessionSync();
|
|
1854
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
1825
1855
|
this.retryAfterServerDisconnect(reason);
|
|
1826
1856
|
});
|
|
1827
1857
|
this.socket.on("connect_error", (error) => {
|
|
@@ -1849,8 +1879,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1849
1879
|
connectionType: data.connectionType,
|
|
1850
1880
|
capabilities: data.protocol.capabilities
|
|
1851
1881
|
});
|
|
1882
|
+
this.committedSessionWriteAckMode = this.supportsKnownProtocolCapability("message_ack_v3") ? "supported" : "unsupported";
|
|
1852
1883
|
this.clearProtocolV3SessionSyncWaitTimer();
|
|
1853
1884
|
this.scheduleProtocolV3SessionSync({ waitForCapabilities: false });
|
|
1885
|
+
this.flushReliableSessionMessages();
|
|
1854
1886
|
} catch (error) {
|
|
1855
1887
|
logger.debug("[SOCKET] [CAPABILITIES] [ERROR] Error handling capabilities event", { error });
|
|
1856
1888
|
}
|
|
@@ -1990,15 +2022,24 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1990
2022
|
};
|
|
1991
2023
|
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1992
2024
|
const eventType = typeof body?.type === "string" ? body.type : "unknown";
|
|
2025
|
+
const reliableMessage = {
|
|
2026
|
+
encrypted,
|
|
2027
|
+
type: `codex:${eventType}`,
|
|
2028
|
+
localId: randomUUID()
|
|
2029
|
+
};
|
|
1993
2030
|
if (!this.socket.connected) {
|
|
1994
2031
|
if (this.shouldBufferReliableCodexMessage(body)) {
|
|
1995
2032
|
logger.debug("[API] Socket not connected, buffering reliable Codex message:", { type: eventType });
|
|
1996
|
-
this.
|
|
2033
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
1997
2034
|
} else {
|
|
1998
2035
|
logger.debug("[API] Socket not connected, dropping non-critical Codex message:", { type: eventType });
|
|
1999
2036
|
}
|
|
2000
2037
|
return;
|
|
2001
2038
|
}
|
|
2039
|
+
if (this.shouldBufferReliableCodexMessage(body) && this.shouldUseCommittedSessionWriteAcks()) {
|
|
2040
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2002
2043
|
this.emitEncryptedSessionMessage(encrypted);
|
|
2003
2044
|
}
|
|
2004
2045
|
/**
|
|
@@ -2023,13 +2064,18 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2023
2064
|
logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
|
|
2024
2065
|
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
2025
2066
|
const eventType = typeof body?.type === "string" ? body.type : "unknown";
|
|
2067
|
+
const reliableMessage = {
|
|
2068
|
+
encrypted,
|
|
2069
|
+
type: `acp:${provider}:${eventType}`,
|
|
2070
|
+
localId: randomUUID()
|
|
2071
|
+
};
|
|
2026
2072
|
if (!this.socket.connected) {
|
|
2027
2073
|
if (this.shouldBufferReliableAcpMessage(body)) {
|
|
2028
2074
|
logger.debug("[API] Socket not connected, buffering reliable ACP message:", {
|
|
2029
2075
|
provider,
|
|
2030
2076
|
type: eventType
|
|
2031
2077
|
});
|
|
2032
|
-
this.
|
|
2078
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2033
2079
|
} else {
|
|
2034
2080
|
logger.debug("[API] Socket not connected, dropping non-critical ACP message:", {
|
|
2035
2081
|
provider,
|
|
@@ -2038,10 +2084,14 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2038
2084
|
}
|
|
2039
2085
|
return;
|
|
2040
2086
|
}
|
|
2087
|
+
if (this.shouldBufferReliableAcpMessage(body) && this.shouldUseCommittedSessionWriteAcks()) {
|
|
2088
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2041
2091
|
this.emitEncryptedSessionMessage(encrypted);
|
|
2042
2092
|
}
|
|
2043
2093
|
sendSessionEvent(event, id) {
|
|
2044
|
-
|
|
2094
|
+
const content = {
|
|
2045
2095
|
role: "agent",
|
|
2046
2096
|
content: {
|
|
2047
2097
|
id: id ?? randomUUID(),
|
|
@@ -2050,15 +2100,24 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2050
2100
|
}
|
|
2051
2101
|
};
|
|
2052
2102
|
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
2103
|
+
const reliableMessage = {
|
|
2104
|
+
encrypted,
|
|
2105
|
+
type: `event:${event.type}`,
|
|
2106
|
+
localId: content.content.id
|
|
2107
|
+
};
|
|
2053
2108
|
if (!this.socket.connected) {
|
|
2054
2109
|
if (this.shouldBufferReliableSessionEvent(event)) {
|
|
2055
2110
|
logger.debug("[API] Socket not connected, buffering session event:", { type: event.type });
|
|
2056
|
-
this.
|
|
2111
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2057
2112
|
} else {
|
|
2058
2113
|
logger.debug("[API] Socket not connected, dropping session event:", { type: event.type });
|
|
2059
2114
|
}
|
|
2060
2115
|
return;
|
|
2061
2116
|
}
|
|
2117
|
+
if (this.shouldBufferReliableSessionEvent(event) && this.shouldUseCommittedSessionWriteAcks()) {
|
|
2118
|
+
this.enqueueReliableSessionMessage(reliableMessage);
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2062
2121
|
this.emitEncryptedSessionMessage(encrypted);
|
|
2063
2122
|
}
|
|
2064
2123
|
/**
|
|
@@ -2172,6 +2231,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2172
2231
|
* Wait for socket buffer to flush
|
|
2173
2232
|
*/
|
|
2174
2233
|
async flush() {
|
|
2234
|
+
if (!this.socket.connected) {
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
await this.waitForReliableSessionMessagesToDrain();
|
|
2175
2238
|
if (!this.socket.connected) {
|
|
2176
2239
|
return;
|
|
2177
2240
|
}
|
|
@@ -2461,31 +2524,197 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2461
2524
|
supportsKnownProtocolCapability(capability) {
|
|
2462
2525
|
return Array.isArray(this.protocolV3Descriptor?.capabilities) && this.protocolV3Descriptor.capabilities.includes(capability);
|
|
2463
2526
|
}
|
|
2464
|
-
|
|
2527
|
+
shouldUseCommittedSessionWriteAcks() {
|
|
2528
|
+
if (!this.credentials.signing) {
|
|
2529
|
+
return false;
|
|
2530
|
+
}
|
|
2531
|
+
return this.committedSessionWriteAckMode !== "unsupported";
|
|
2532
|
+
}
|
|
2533
|
+
emitEncryptedSessionMessage(encrypted, localId) {
|
|
2465
2534
|
this.socket.emit("message", {
|
|
2466
2535
|
sid: this.sessionId,
|
|
2467
|
-
message: encrypted
|
|
2536
|
+
message: encrypted,
|
|
2537
|
+
...typeof localId === "string" ? { localId } : {}
|
|
2468
2538
|
});
|
|
2469
2539
|
}
|
|
2470
|
-
|
|
2471
|
-
this.
|
|
2472
|
-
this.
|
|
2540
|
+
enqueueReliableSessionMessage(message) {
|
|
2541
|
+
this.queuedReliableSessionMessages.push(message);
|
|
2542
|
+
this.queuedReliableSessionMessageBytes += message.encrypted.length;
|
|
2473
2543
|
this.trimPendingReliableSessionMessages();
|
|
2544
|
+
this.flushReliableSessionMessages();
|
|
2545
|
+
}
|
|
2546
|
+
dequeueReliableSessionMessage() {
|
|
2547
|
+
const message = this.queuedReliableSessionMessages.shift() ?? null;
|
|
2548
|
+
if (!message) {
|
|
2549
|
+
return null;
|
|
2550
|
+
}
|
|
2551
|
+
this.queuedReliableSessionMessageBytes -= message.encrypted.length;
|
|
2552
|
+
return message;
|
|
2474
2553
|
}
|
|
2475
2554
|
flushReliableSessionMessages() {
|
|
2476
|
-
|
|
2555
|
+
this.clearReliableSessionMessageRetryTimer();
|
|
2556
|
+
if (this.reliableSessionMessageFlushInProgress || !this.socket.connected) {
|
|
2557
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
2477
2558
|
return;
|
|
2478
2559
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2560
|
+
this.reliableSessionMessageFlushInProgress = true;
|
|
2561
|
+
void (async () => {
|
|
2562
|
+
try {
|
|
2563
|
+
while (this.socket.connected) {
|
|
2564
|
+
const message = this.inFlightReliableSessionMessage ?? this.dequeueReliableSessionMessage();
|
|
2565
|
+
if (!message) {
|
|
2566
|
+
this.inFlightReliableSessionMessage = null;
|
|
2567
|
+
break;
|
|
2568
|
+
}
|
|
2569
|
+
if (this.inFlightReliableSessionMessage === null) {
|
|
2570
|
+
logger.debug("[API] Flushing buffered session message after reconnect", {
|
|
2571
|
+
type: message.type,
|
|
2572
|
+
localId: message.localId
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
this.inFlightReliableSessionMessage = message;
|
|
2576
|
+
if (this.shouldUseCommittedSessionWriteAcks()) {
|
|
2577
|
+
const result = await this.emitEncryptedSessionMessageWithAck(message);
|
|
2578
|
+
if (result === "retry") {
|
|
2579
|
+
this.scheduleReliableSessionMessageRetry();
|
|
2580
|
+
break;
|
|
2581
|
+
}
|
|
2582
|
+
} else {
|
|
2583
|
+
this.emitEncryptedSessionMessage(message.encrypted, message.localId);
|
|
2584
|
+
}
|
|
2585
|
+
this.inFlightReliableSessionMessage = null;
|
|
2586
|
+
}
|
|
2587
|
+
} finally {
|
|
2588
|
+
this.reliableSessionMessageFlushInProgress = false;
|
|
2589
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
2590
|
+
}
|
|
2591
|
+
})();
|
|
2592
|
+
}
|
|
2593
|
+
async waitForReliableSessionMessagesToDrain() {
|
|
2594
|
+
if (!this.socket.connected) {
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
this.flushReliableSessionMessages();
|
|
2598
|
+
if (!this.reliableSessionMessageFlushInProgress && this.inFlightReliableSessionMessage === null && this.queuedReliableSessionMessages.length === 0) {
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
await new Promise((resolve) => {
|
|
2602
|
+
this.reliableSessionMessageDrainWaiters.push(resolve);
|
|
2484
2603
|
});
|
|
2485
|
-
|
|
2486
|
-
|
|
2604
|
+
}
|
|
2605
|
+
resolveReliableSessionMessageDrainWaitersIfIdle() {
|
|
2606
|
+
if (!this.socket.connected) {
|
|
2607
|
+
const disconnectedWaiters = this.reliableSessionMessageDrainWaiters.splice(0, this.reliableSessionMessageDrainWaiters.length);
|
|
2608
|
+
for (const waiter of disconnectedWaiters) {
|
|
2609
|
+
waiter();
|
|
2610
|
+
}
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
if (this.reliableSessionMessageFlushInProgress || this.inFlightReliableSessionMessage !== null || this.queuedReliableSessionMessages.length > 0) {
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const waiters = this.reliableSessionMessageDrainWaiters.splice(0, this.reliableSessionMessageDrainWaiters.length);
|
|
2617
|
+
for (const waiter of waiters) {
|
|
2618
|
+
waiter();
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
scheduleReliableSessionMessageRetry() {
|
|
2622
|
+
if (this.reliableSessionMessageRetryTimer || !this.socket.connected || this.inFlightReliableSessionMessage === null) {
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
this.reliableSessionMessageRetryTimer = setTimeout(() => {
|
|
2626
|
+
this.reliableSessionMessageRetryTimer = null;
|
|
2627
|
+
if (!this.socket.connected || this.inFlightReliableSessionMessage === null) {
|
|
2628
|
+
this.resolveReliableSessionMessageDrainWaitersIfIdle();
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
this.flushReliableSessionMessages();
|
|
2632
|
+
}, RELIABLE_SESSION_MESSAGE_RETRY_DELAY_MS);
|
|
2633
|
+
}
|
|
2634
|
+
clearReliableSessionMessageRetryTimer() {
|
|
2635
|
+
if (!this.reliableSessionMessageRetryTimer) {
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
clearTimeout(this.reliableSessionMessageRetryTimer);
|
|
2639
|
+
this.reliableSessionMessageRetryTimer = null;
|
|
2640
|
+
}
|
|
2641
|
+
async emitEncryptedSessionMessageWithAck(message) {
|
|
2642
|
+
try {
|
|
2643
|
+
const response = await this.withAckTimeout(
|
|
2644
|
+
Promise.resolve(this.socket.emitWithAck("message", {
|
|
2645
|
+
sid: this.sessionId,
|
|
2646
|
+
message: message.encrypted,
|
|
2647
|
+
localId: message.localId
|
|
2648
|
+
})),
|
|
2649
|
+
COMMITTED_SESSION_WRITE_ACK_TIMEOUT_MS
|
|
2650
|
+
);
|
|
2651
|
+
if (!response || typeof response !== "object" || !("ok" in response)) {
|
|
2652
|
+
if (this.committedSessionWriteAckMode === "unknown") {
|
|
2653
|
+
logger.debug("[API] Falling back to fire-and-forget session writes because websocket write acknowledgements are unavailable", {
|
|
2654
|
+
sessionId: this.sessionId,
|
|
2655
|
+
type: message.type
|
|
2656
|
+
});
|
|
2657
|
+
this.committedSessionWriteAckMode = "unsupported";
|
|
2658
|
+
this.emitEncryptedSessionMessage(message.encrypted, message.localId);
|
|
2659
|
+
return "committed";
|
|
2660
|
+
}
|
|
2661
|
+
logger.debug("[API] Session write acknowledgement payload was invalid, will retry on reconnect", {
|
|
2662
|
+
sessionId: this.sessionId,
|
|
2663
|
+
type: message.type,
|
|
2664
|
+
response
|
|
2665
|
+
});
|
|
2666
|
+
return "retry";
|
|
2667
|
+
}
|
|
2668
|
+
const ackResponse = response;
|
|
2669
|
+
if (!ackResponse.ok) {
|
|
2670
|
+
logger.debug("[API] Session write was rejected by the server", {
|
|
2671
|
+
sessionId: this.sessionId,
|
|
2672
|
+
type: message.type,
|
|
2673
|
+
localId: message.localId,
|
|
2674
|
+
error: ackResponse.error
|
|
2675
|
+
});
|
|
2676
|
+
this.emit("message-ack", ackResponse);
|
|
2677
|
+
return "committed";
|
|
2678
|
+
}
|
|
2679
|
+
this.committedSessionWriteAckMode = "supported";
|
|
2680
|
+
this.emit("message-ack", ackResponse);
|
|
2681
|
+
return "committed";
|
|
2682
|
+
} catch (error) {
|
|
2683
|
+
if (this.committedSessionWriteAckMode === "unknown" && isAckTimeoutError(error)) {
|
|
2684
|
+
logger.debug("[API] Websocket write acknowledgements timed out before protocol capabilities were confirmed, falling back to fire-and-forget writes", {
|
|
2685
|
+
sessionId: this.sessionId,
|
|
2686
|
+
type: message.type
|
|
2687
|
+
});
|
|
2688
|
+
this.committedSessionWriteAckMode = "unsupported";
|
|
2689
|
+
this.emitEncryptedSessionMessage(message.encrypted, message.localId);
|
|
2690
|
+
return "committed";
|
|
2691
|
+
}
|
|
2692
|
+
logger.debug("[API] Reliable session write acknowledgement failed, will retry when the socket reconnects", {
|
|
2693
|
+
sessionId: this.sessionId,
|
|
2694
|
+
type: message.type,
|
|
2695
|
+
localId: message.localId,
|
|
2696
|
+
error
|
|
2697
|
+
});
|
|
2698
|
+
return "retry";
|
|
2487
2699
|
}
|
|
2488
2700
|
}
|
|
2701
|
+
async withAckTimeout(promise, timeoutMs) {
|
|
2702
|
+
return await new Promise((resolve, reject) => {
|
|
2703
|
+
const timeout = setTimeout(() => {
|
|
2704
|
+
reject(createAckTimeoutError(timeoutMs));
|
|
2705
|
+
}, timeoutMs);
|
|
2706
|
+
promise.then(
|
|
2707
|
+
(value) => {
|
|
2708
|
+
clearTimeout(timeout);
|
|
2709
|
+
resolve(value);
|
|
2710
|
+
},
|
|
2711
|
+
(error) => {
|
|
2712
|
+
clearTimeout(timeout);
|
|
2713
|
+
reject(error);
|
|
2714
|
+
}
|
|
2715
|
+
);
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2489
2718
|
shouldBufferReliableCodexMessage(body) {
|
|
2490
2719
|
switch (body?.type) {
|
|
2491
2720
|
case "message":
|
|
@@ -2532,19 +2761,19 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2532
2761
|
}
|
|
2533
2762
|
trimPendingReliableSessionMessages() {
|
|
2534
2763
|
let dropped = 0;
|
|
2535
|
-
while (this.
|
|
2536
|
-
const removed = this.
|
|
2764
|
+
while (this.queuedReliableSessionMessages.length > MAX_PENDING_RELIABLE_SESSION_MESSAGES || this.queuedReliableSessionMessageBytes > MAX_PENDING_RELIABLE_SESSION_MESSAGE_BYTES) {
|
|
2765
|
+
const removed = this.queuedReliableSessionMessages.shift();
|
|
2537
2766
|
if (!removed) {
|
|
2538
2767
|
break;
|
|
2539
2768
|
}
|
|
2540
|
-
this.
|
|
2769
|
+
this.queuedReliableSessionMessageBytes -= removed.encrypted.length;
|
|
2541
2770
|
dropped += 1;
|
|
2542
2771
|
}
|
|
2543
2772
|
if (dropped > 0) {
|
|
2544
2773
|
logger.debug("[API] Dropped oldest buffered session messages to cap reconnect memory usage", {
|
|
2545
2774
|
dropped,
|
|
2546
|
-
remaining: this.
|
|
2547
|
-
bytes: this.
|
|
2775
|
+
remaining: this.queuedReliableSessionMessages.length,
|
|
2776
|
+
bytes: this.queuedReliableSessionMessageBytes
|
|
2548
2777
|
});
|
|
2549
2778
|
}
|
|
2550
2779
|
}
|
|
@@ -2601,6 +2830,14 @@ function createAbortError() {
|
|
|
2601
2830
|
error.name = "AbortError";
|
|
2602
2831
|
return error;
|
|
2603
2832
|
}
|
|
2833
|
+
function createAckTimeoutError(timeoutMs) {
|
|
2834
|
+
const error = new Error(`Timed out waiting for websocket message acknowledgement after ${timeoutMs}ms`);
|
|
2835
|
+
error.name = "AckTimeoutError";
|
|
2836
|
+
return error;
|
|
2837
|
+
}
|
|
2838
|
+
function isAckTimeoutError(error) {
|
|
2839
|
+
return error instanceof Error && error.name === "AckTimeoutError";
|
|
2840
|
+
}
|
|
2604
2841
|
|
|
2605
2842
|
class ApiMachineClient {
|
|
2606
2843
|
constructor(credentials, machine) {
|