clawconnect-agent 0.1.7 → 0.1.8
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/README.md +62 -30
- package/README.zh-CN.md +62 -30
- package/dist/commands/pair.d.ts +4 -0
- package/dist/commands/pair.js +63 -6
- package/dist/commands/pair.js.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/send-file.d.ts +2 -25
- package/dist/commands/send-file.js +22 -455
- package/dist/commands/send-file.js.map +1 -1
- package/dist/core/command-types.d.ts +18 -0
- package/dist/core/command-types.js +2 -0
- package/dist/core/command-types.js.map +1 -0
- package/dist/core/relay/attachment-staging.js.map +1 -0
- package/dist/{relay → core/relay}/chat-payload.js +17 -3
- package/dist/core/relay/chat-payload.js.map +1 -0
- package/dist/{commands/send-file-utils.js → core/relay/file-upload-utils.js} +1 -1
- package/dist/core/relay/file-upload-utils.js.map +1 -0
- package/dist/core/relay/file-upload.d.ts +40 -0
- package/dist/core/relay/file-upload.js +401 -0
- package/dist/core/relay/file-upload.js.map +1 -0
- package/dist/core/relay/mobile-chat-run-bridge.d.ts +201 -0
- package/dist/core/relay/mobile-chat-run-bridge.js +192 -0
- package/dist/core/relay/mobile-chat-run-bridge.js.map +1 -0
- package/dist/core/relay/office-payload.js.map +1 -0
- package/dist/core/relay/reconnect.js.map +1 -0
- package/dist/core/relay/relay-server-connection.d.ts +6 -0
- package/dist/core/relay/relay-server-connection.js +32 -0
- package/dist/core/relay/relay-server-connection.js.map +1 -0
- package/dist/{relay/slash-command-catalog.d.ts → core/relay/slash-command-types.d.ts} +0 -1
- package/dist/core/relay/slash-command-types.js +2 -0
- package/dist/core/relay/slash-command-types.js.map +1 -0
- package/dist/core/relay/timeline-event-builder.d.ts +86 -0
- package/dist/core/relay/timeline-event-builder.js +199 -0
- package/dist/core/relay/timeline-event-builder.js.map +1 -0
- package/dist/core/relay/timeline-event-log.d.ts +81 -0
- package/dist/core/relay/timeline-event-log.js +254 -0
- package/dist/core/relay/timeline-event-log.js.map +1 -0
- package/dist/{relay → core/relay}/voice-input.js +5 -3
- package/dist/core/relay/voice-input.js.map +1 -0
- package/dist/hermes/hermes-relay-manager.d.ts +14 -4
- package/dist/hermes/hermes-relay-manager.js +159 -41
- package/dist/hermes/hermes-relay-manager.js.map +1 -1
- package/dist/hermes/hermes-runtime.d.ts +9 -8
- package/dist/hermes/hermes-runtime.js +8 -7
- package/dist/hermes/hermes-runtime.js.map +1 -1
- package/dist/hermes/hermes-session-store.js +52 -30
- package/dist/hermes/hermes-session-store.js.map +1 -1
- package/dist/hermes/hermes-voice-input.js +1 -1
- package/dist/hermes/hermes-voice-input.js.map +1 -1
- package/dist/hermes/runtime/hermes-runtime-artifacts.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-backups.d.ts +1 -1
- package/dist/hermes/runtime/hermes-runtime-backups.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-chat.d.ts +2 -1
- package/dist/hermes/{models → runtime}/hermes-runtime-chat.js +127 -27
- package/dist/hermes/runtime/hermes-runtime-chat.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-command-router.d.ts +1 -1
- package/dist/hermes/{models → runtime}/hermes-runtime-command-router.js +3 -0
- package/dist/hermes/runtime/hermes-runtime-command-router.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-command-utils.d.ts +1 -1
- package/dist/hermes/runtime/hermes-runtime-command-utils.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-cron.d.ts +1 -1
- package/dist/hermes/runtime/hermes-runtime-cron.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-history.d.ts +2 -0
- package/dist/hermes/runtime/hermes-runtime-history.js +249 -0
- package/dist/hermes/runtime/hermes-runtime-history.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-lifecycle.d.ts +1 -1
- package/dist/hermes/runtime/hermes-runtime-lifecycle.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-models.d.ts +1 -1
- package/dist/hermes/runtime/hermes-runtime-models.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-process.d.ts +2 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-process.js +19 -2
- package/dist/hermes/runtime/hermes-runtime-process.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-sessions.d.ts +1 -1
- package/dist/hermes/{models → runtime}/hermes-runtime-sessions.js +26 -9
- package/dist/hermes/runtime/hermes-runtime-sessions.js.map +1 -0
- package/dist/hermes/{models → runtime}/hermes-runtime-skills.d.ts +1 -1
- package/dist/hermes/runtime/hermes-runtime-skills.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-types.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-usage.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-values.js.map +1 -0
- package/dist/openclaw/backups/backup-manager.js.map +1 -0
- package/dist/{commands → openclaw/config}/provider-config.js +1 -1
- package/dist/openclaw/config/provider-config.js.map +1 -0
- package/dist/openclaw/config/provider-registry.js.map +1 -0
- package/dist/openclaw/gateway-client.js.map +1 -0
- package/dist/{commands → openclaw/handlers}/local-handlers.d.ts +1 -1
- package/dist/{commands → openclaw/handlers}/local-handlers.js +2 -2
- package/dist/openclaw/handlers/local-handlers.js.map +1 -0
- package/dist/{commands → openclaw/handlers}/provider-handlers.d.ts +1 -1
- package/dist/{commands → openclaw/handlers}/provider-handlers.js +3 -3
- package/dist/openclaw/handlers/provider-handlers.js.map +1 -0
- package/dist/openclaw/relay/chat-history.d.ts +51 -0
- package/dist/openclaw/relay/chat-history.js +347 -0
- package/dist/openclaw/relay/chat-history.js.map +1 -0
- package/dist/{relay → openclaw/relay}/chat-send-attachments.js +12 -1
- package/dist/openclaw/relay/chat-send-attachments.js.map +1 -0
- package/dist/{relay → openclaw/relay}/openclaw-voice-input.js +1 -1
- package/dist/openclaw/relay/openclaw-voice-input.js.map +1 -0
- package/dist/openclaw/relay/outgoing-media-relay.d.ts +11 -0
- package/dist/openclaw/relay/outgoing-media-relay.js +120 -0
- package/dist/openclaw/relay/outgoing-media-relay.js.map +1 -0
- package/dist/{relay → openclaw/relay}/session-context.d.ts +6 -0
- package/dist/{relay → openclaw/relay}/session-context.js +42 -2
- package/dist/openclaw/relay/session-context.js.map +1 -0
- package/dist/openclaw/relay/slash-command-catalog.d.ts +2 -0
- package/dist/openclaw/relay/slash-command-catalog.generated.js.map +1 -0
- package/dist/openclaw/relay/slash-command-catalog.js.map +1 -0
- package/dist/openclaw/relay/transcript-dedupe.d.ts +21 -0
- package/dist/openclaw/relay/transcript-dedupe.js +211 -0
- package/dist/openclaw/relay/transcript-dedupe.js.map +1 -0
- package/dist/{relay → openclaw}/relay-manager.d.ts +1 -1
- package/dist/{relay → openclaw}/relay-manager.js +373 -99
- package/dist/openclaw/relay-manager.js.map +1 -0
- package/dist/{commands → openclaw/runtime}/local-runtime.d.ts +1 -17
- package/dist/openclaw/runtime/local-runtime.js.map +1 -0
- package/dist/openclaw/session-store.d.ts +1 -0
- package/dist/openclaw/session-store.js +70 -0
- package/dist/openclaw/session-store.js.map +1 -0
- package/dist/runtime-adapters.js +1 -1
- package/dist/runtime-adapters.js.map +1 -1
- package/package.json +2 -2
- package/dist/commands/backup-manager.js.map +0 -1
- package/dist/commands/local-handlers.js.map +0 -1
- package/dist/commands/local-runtime.js.map +0 -1
- package/dist/commands/provider-config.js.map +0 -1
- package/dist/commands/provider-handlers.js.map +0 -1
- package/dist/commands/provider-registry.js.map +0 -1
- package/dist/commands/send-file-utils.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-artifacts.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-backups.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-chat.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-command-router.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-command-utils.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-cron.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-lifecycle.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-models.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-process.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-sessions.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-skills.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-types.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-usage.js.map +0 -1
- package/dist/hermes/models/hermes-runtime-values.js.map +0 -1
- package/dist/relay/attachment-staging.js.map +0 -1
- package/dist/relay/chat-history.d.ts +0 -29
- package/dist/relay/chat-history.js +0 -87
- package/dist/relay/chat-history.js.map +0 -1
- package/dist/relay/chat-payload.js.map +0 -1
- package/dist/relay/chat-send-attachments.js.map +0 -1
- package/dist/relay/gateway-client.js.map +0 -1
- package/dist/relay/mobile-chat-run-bridge.d.ts +0 -93
- package/dist/relay/mobile-chat-run-bridge.js +0 -68
- package/dist/relay/mobile-chat-run-bridge.js.map +0 -1
- package/dist/relay/office-payload.js.map +0 -1
- package/dist/relay/openclaw-voice-input.js.map +0 -1
- package/dist/relay/reconnect.js.map +0 -1
- package/dist/relay/relay-manager.js.map +0 -1
- package/dist/relay/session-context.js.map +0 -1
- package/dist/relay/slash-command-catalog.generated.js.map +0 -1
- package/dist/relay/slash-command-catalog.js.map +0 -1
- package/dist/relay/voice-input.js.map +0 -1
- /package/dist/{relay → core/relay}/attachment-staging.d.ts +0 -0
- /package/dist/{relay → core/relay}/attachment-staging.js +0 -0
- /package/dist/{relay → core/relay}/chat-payload.d.ts +0 -0
- /package/dist/{commands/send-file-utils.d.ts → core/relay/file-upload-utils.d.ts} +0 -0
- /package/dist/{relay → core/relay}/office-payload.d.ts +0 -0
- /package/dist/{relay → core/relay}/office-payload.js +0 -0
- /package/dist/{relay → core/relay}/reconnect.d.ts +0 -0
- /package/dist/{relay → core/relay}/reconnect.js +0 -0
- /package/dist/{relay → core/relay}/voice-input.d.ts +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-artifacts.d.ts +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-artifacts.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-backups.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-command-utils.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-cron.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-lifecycle.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-models.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-skills.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-types.d.ts +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-types.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-usage.d.ts +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-usage.js +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-values.d.ts +0 -0
- /package/dist/hermes/{models → runtime}/hermes-runtime-values.js +0 -0
- /package/dist/{commands → openclaw/backups}/backup-manager.d.ts +0 -0
- /package/dist/{commands → openclaw/backups}/backup-manager.js +0 -0
- /package/dist/{commands → openclaw/config}/provider-config.d.ts +0 -0
- /package/dist/{commands → openclaw/config}/provider-registry.d.ts +0 -0
- /package/dist/{commands → openclaw/config}/provider-registry.js +0 -0
- /package/dist/{relay → openclaw}/gateway-client.d.ts +0 -0
- /package/dist/{relay → openclaw}/gateway-client.js +0 -0
- /package/dist/{relay → openclaw/relay}/chat-send-attachments.d.ts +0 -0
- /package/dist/{relay → openclaw/relay}/openclaw-voice-input.d.ts +0 -0
- /package/dist/{relay → openclaw/relay}/slash-command-catalog.generated.d.ts +0 -0
- /package/dist/{relay → openclaw/relay}/slash-command-catalog.generated.js +0 -0
- /package/dist/{relay → openclaw/relay}/slash-command-catalog.js +0 -0
- /package/dist/{commands → openclaw/runtime}/local-runtime.js +0 -0
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { WebSocket } from "ws";
|
|
2
2
|
import { OpenClawGatewayClient } from "./gateway-client.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
3
|
+
import { bindRelayAbortSignal, buildRelayUrl, parseRelayFrame, sendRelayJson, shouldRetryRelayClose, } from "../core/relay/relay-server-connection.js";
|
|
4
|
+
import { handleLocalCommand } from "./handlers/local-handlers.js";
|
|
5
|
+
import { handleProviderCommand } from "./handlers/provider-handlers.js";
|
|
6
|
+
import { DEFAULT_GATEWAY_SESSION_DEFAULTS, buildContextUsageFingerprint, readContextUsageSnapshot, canonicalizeRelayParams, canonicalizeSessionKey, extractGatewaySessionDefaults, } from "./relay/session-context.js";
|
|
7
|
+
import { appendUniqueSuffix, extractChatRole, extractChatText, normalizeChatEventPayload, normalizeChatState, withMessageText, } from "../core/relay/chat-payload.js";
|
|
8
|
+
import { extractHistoryOutcome, readOpenClawTranscriptChatHistory, withTimeout, } from "./relay/chat-history.js";
|
|
9
|
+
import { dedupeOpenClawChatSendUserMirrorTranscript, } from "./relay/transcript-dedupe.js";
|
|
10
|
+
import { buildMobileAssistantErrorPayload, buildMobileAssistantFinalPayload, } from "../core/relay/mobile-chat-run-bridge.js";
|
|
11
|
+
import { buildOfficeEventPayload } from "../core/relay/office-payload.js";
|
|
12
|
+
import { prepareChatSendParams } from "./relay/chat-send-attachments.js";
|
|
13
|
+
import { relayOutgoingMediaInHistoryResponse, relayOutgoingMediaInPayload } from "./relay/outgoing-media-relay.js";
|
|
14
|
+
import { prepareOpenClawVoiceInputCommand } from "./relay/openclaw-voice-input.js";
|
|
15
|
+
import { voiceInputSetupMessage } from "../core/relay/voice-input.js";
|
|
13
16
|
import { gatewayCapabilitiesForType } from "../gateway-profiles.js";
|
|
14
|
-
import { OPENCLAW_SLASH_COMMAND_CATALOG
|
|
17
|
+
import { OPENCLAW_SLASH_COMMAND_CATALOG } from "./relay/slash-command-catalog.js";
|
|
18
|
+
const CHAT_HISTORY_FETCH_TIMEOUT_MS = 3000;
|
|
19
|
+
const CHAT_HISTORY_FALLBACK_INITIAL_DELAY_MS = 1200;
|
|
20
|
+
const CHAT_HISTORY_FALLBACK_RETRY_DELAY_MS = 1800;
|
|
21
|
+
const CHAT_HISTORY_FALLBACK_MAX_ATTEMPTS = 120;
|
|
22
|
+
const CHAT_HISTORY_FINAL_RETRY_DELAY_MS = 750;
|
|
23
|
+
const CHAT_SEND_MIRROR_DEDUPE_RETRY_DELAYS_MS = [250, 1000, 2500, 5000, 10000, 20000, 30000];
|
|
15
24
|
// ---------------------------------------------------------------------------
|
|
16
25
|
// Main entry point
|
|
17
26
|
// ---------------------------------------------------------------------------
|
|
@@ -35,20 +44,18 @@ export async function runRelayManager(opts) {
|
|
|
35
44
|
resolve(true);
|
|
36
45
|
return;
|
|
37
46
|
}
|
|
38
|
-
|
|
39
|
-
if (opts.signal) {
|
|
40
|
-
if (opts.signal.aborted) {
|
|
41
|
-
relayWs.close(1001, "shutdown");
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
opts.signal.addEventListener("abort", () => relayWs.close(1001, "shutdown"), { once: true });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
+
bindRelayAbortSignal(relayWs, opts.signal);
|
|
47
48
|
let gatewayClient = null;
|
|
48
49
|
let sessionDefaults = { ...DEFAULT_GATEWAY_SESSION_DEFAULTS };
|
|
49
50
|
const chatBuffers = new Map();
|
|
50
51
|
const chatFallbacks = new Map();
|
|
51
52
|
const chatRunContexts = new Map();
|
|
53
|
+
const chatSendDedupeRequests = new Map();
|
|
54
|
+
const chatSendDedupeRunKeys = new Map();
|
|
55
|
+
const chatSendDedupeTimers = new Map();
|
|
56
|
+
const chatSendDedupeAttempts = new Map();
|
|
57
|
+
const chatSendDedupeRunning = new Map();
|
|
58
|
+
const outgoingMediaUploadCache = new Map();
|
|
52
59
|
const contextUsageRefreshes = new Map();
|
|
53
60
|
const contextUsageFingerprints = new Map();
|
|
54
61
|
const clearChatFallback = (runId) => {
|
|
@@ -58,6 +65,139 @@ export async function runRelayManager(opts) {
|
|
|
58
65
|
chatFallbacks.delete(runId);
|
|
59
66
|
}
|
|
60
67
|
};
|
|
68
|
+
const clearChatSendDedupe = (clientRunId) => {
|
|
69
|
+
const timer = chatSendDedupeTimers.get(clientRunId);
|
|
70
|
+
if (timer) {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
chatSendDedupeTimers.delete(clientRunId);
|
|
73
|
+
}
|
|
74
|
+
chatSendDedupeRequests.delete(clientRunId);
|
|
75
|
+
chatSendDedupeAttempts.delete(clientRunId);
|
|
76
|
+
for (const [runId, mappedClientRunId] of chatSendDedupeRunKeys.entries()) {
|
|
77
|
+
if (mappedClientRunId === clientRunId) {
|
|
78
|
+
chatSendDedupeRunKeys.delete(runId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const registerChatSendDedupe = (request, runId) => {
|
|
83
|
+
chatSendDedupeRequests.set(request.clientRunId, request);
|
|
84
|
+
if (runId) {
|
|
85
|
+
chatSendDedupeRunKeys.set(runId, request.clientRunId);
|
|
86
|
+
}
|
|
87
|
+
scheduleChatSendDedupe(request.clientRunId);
|
|
88
|
+
};
|
|
89
|
+
function scheduleChatSendDedupe(clientRunId, delayOverrideMs) {
|
|
90
|
+
if (!chatSendDedupeRequests.has(clientRunId)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const existing = chatSendDedupeTimers.get(clientRunId);
|
|
94
|
+
if (existing) {
|
|
95
|
+
clearTimeout(existing);
|
|
96
|
+
}
|
|
97
|
+
const attempt = chatSendDedupeAttempts.get(clientRunId) ?? 0;
|
|
98
|
+
const delayMs = delayOverrideMs ?? CHAT_SEND_MIRROR_DEDUPE_RETRY_DELAYS_MS[Math.min(attempt, CHAT_SEND_MIRROR_DEDUPE_RETRY_DELAYS_MS.length - 1)];
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
chatSendDedupeTimers.delete(clientRunId);
|
|
101
|
+
void runChatSendDedupeAttempt(clientRunId).then((changed) => {
|
|
102
|
+
if (changed || !chatSendDedupeRequests.has(clientRunId)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const nextAttempt = attempt + 1;
|
|
106
|
+
if (nextAttempt >= CHAT_SEND_MIRROR_DEDUPE_RETRY_DELAYS_MS.length) {
|
|
107
|
+
clearChatSendDedupe(clientRunId);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
chatSendDedupeAttempts.set(clientRunId, nextAttempt);
|
|
111
|
+
scheduleChatSendDedupe(clientRunId);
|
|
112
|
+
});
|
|
113
|
+
}, delayMs);
|
|
114
|
+
timer.unref?.();
|
|
115
|
+
chatSendDedupeTimers.set(clientRunId, timer);
|
|
116
|
+
}
|
|
117
|
+
function scheduleChatSendDedupeForRun(runId, delayMs = 100) {
|
|
118
|
+
const clientRunId = chatSendDedupeRunKeys.get(runId);
|
|
119
|
+
if (!clientRunId) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
scheduleChatSendDedupe(clientRunId, delayMs);
|
|
123
|
+
}
|
|
124
|
+
async function runChatSendDedupeAttempt(clientRunId) {
|
|
125
|
+
const running = chatSendDedupeRunning.get(clientRunId);
|
|
126
|
+
if (running) {
|
|
127
|
+
return running;
|
|
128
|
+
}
|
|
129
|
+
const promise = (async () => {
|
|
130
|
+
const request = chatSendDedupeRequests.get(clientRunId);
|
|
131
|
+
if (!request) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const result = await dedupeOpenClawChatSendUserMirrorTranscript(request, sessionDefaults, { maxRetries: 2 });
|
|
136
|
+
if (result.changed) {
|
|
137
|
+
console.log(`[relay] removed ${result.removedCount} duplicate OpenClaw prompt mirror(s) from ${result.transcriptPath ?? "transcript"}`);
|
|
138
|
+
clearChatSendDedupe(clientRunId);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.warn(`[relay] chat.send transcript dedupe failed runId=${clientRunId}: ${String(error)}`);
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
})().finally(() => {
|
|
147
|
+
chatSendDedupeRunning.delete(clientRunId);
|
|
148
|
+
});
|
|
149
|
+
chatSendDedupeRunning.set(clientRunId, promise);
|
|
150
|
+
return promise;
|
|
151
|
+
}
|
|
152
|
+
async function dedupePendingChatSendMirrorsForSession(rawParams) {
|
|
153
|
+
const sessionKey = resolveRawParamsSessionKey(rawParams);
|
|
154
|
+
const pending = Array.from(chatSendDedupeRequests.entries()).filter(([, request]) => {
|
|
155
|
+
const requestSessionKey = canonicalizeSessionKey(request.sessionKey ?? sessionDefaults.mainSessionKey, sessionDefaults);
|
|
156
|
+
return requestSessionKey === sessionKey;
|
|
157
|
+
});
|
|
158
|
+
for (const [clientRunId] of pending) {
|
|
159
|
+
await runChatSendDedupeAttempt(clientRunId);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function buildChatSendDedupeRequest(paramsRecord) {
|
|
163
|
+
if (!paramsRecord) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const message = typeof paramsRecord.message === "string" && paramsRecord.message.trim().length > 0
|
|
167
|
+
? paramsRecord.message.trim()
|
|
168
|
+
: "";
|
|
169
|
+
const idempotencyKey = typeof paramsRecord.idempotencyKey === "string" && paramsRecord.idempotencyKey.trim().length > 0
|
|
170
|
+
? paramsRecord.idempotencyKey.trim()
|
|
171
|
+
: "";
|
|
172
|
+
if (!message || !idempotencyKey) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const clientRunId = idempotencyKey.endsWith(":user")
|
|
176
|
+
? idempotencyKey.slice(0, -":user".length)
|
|
177
|
+
: idempotencyKey;
|
|
178
|
+
if (!clientRunId) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
clientRunId,
|
|
183
|
+
message,
|
|
184
|
+
sessionKey: resolveRawParamsSessionKey(paramsRecord),
|
|
185
|
+
senderId: "openclaw-macos",
|
|
186
|
+
senderName: "ClawConnect Agent",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function resolveRawParamsSessionKey(rawParams) {
|
|
190
|
+
const record = rawParams && typeof rawParams === "object" && !Array.isArray(rawParams)
|
|
191
|
+
? rawParams
|
|
192
|
+
: {};
|
|
193
|
+
const rawSessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
|
|
194
|
+
? record.sessionKey.trim()
|
|
195
|
+
: sessionDefaults.mainSessionKey;
|
|
196
|
+
const normalized = canonicalizeSessionKey(rawSessionKey, sessionDefaults);
|
|
197
|
+
return typeof normalized === "string" && normalized.trim().length > 0
|
|
198
|
+
? normalized.trim()
|
|
199
|
+
: sessionDefaults.mainSessionKey;
|
|
200
|
+
}
|
|
61
201
|
const publishContextUsageSnapshot = async (sessionKey, force = false) => {
|
|
62
202
|
const normalizedSessionKey = canonicalizeSessionKey(sessionKey, sessionDefaults);
|
|
63
203
|
if (typeof normalizedSessionKey !== "string" || normalizedSessionKey.trim().length === 0) {
|
|
@@ -118,11 +258,11 @@ export async function runRelayManager(opts) {
|
|
|
118
258
|
chatFallbacks.delete(runId);
|
|
119
259
|
return;
|
|
120
260
|
}
|
|
121
|
-
const fetchHistory = () =>
|
|
122
|
-
withTimeout(fetchHistory(),
|
|
261
|
+
const fetchHistory = () => requestChatHistoryFromClawConnect({ sessionKey: context.sessionKey, limit: 10 });
|
|
262
|
+
withTimeout(fetchHistory(), CHAT_HISTORY_FETCH_TIMEOUT_MS, "chat.history fallback")
|
|
123
263
|
.then(async (history) => {
|
|
124
264
|
const outcome = extractHistoryOutcome(history, context);
|
|
125
|
-
if (!outcome && attempt <
|
|
265
|
+
if (!outcome && attempt < CHAT_HISTORY_FALLBACK_MAX_ATTEMPTS) {
|
|
126
266
|
scheduleChatHistoryFallback(runId, context, attempt + 1);
|
|
127
267
|
return;
|
|
128
268
|
}
|
|
@@ -134,27 +274,22 @@ export async function runRelayManager(opts) {
|
|
|
134
274
|
clearChatFallback(runId);
|
|
135
275
|
chatRunContexts.delete(runId);
|
|
136
276
|
if (outcome.kind === "final") {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
run: { runId, sessionKey: context.sessionKey },
|
|
142
|
-
text: outcome.text,
|
|
143
|
-
}),
|
|
277
|
+
const basePayload = buildMobileAssistantFinalPayload({
|
|
278
|
+
run: { runId, sessionKey: context.sessionKey },
|
|
279
|
+
text: outcome.text,
|
|
280
|
+
includeTimelineEvents: true,
|
|
144
281
|
});
|
|
282
|
+
await publishAndSendGatewayEvent("chat", buildFinalPayloadFromHistoryOutcome(basePayload, outcome), true);
|
|
145
283
|
return;
|
|
146
284
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
errorMessage: outcome.errorMessage,
|
|
153
|
-
}),
|
|
154
|
-
});
|
|
285
|
+
await publishAndSendGatewayEvent("chat", buildMobileAssistantErrorPayload({
|
|
286
|
+
run: { runId, sessionKey: context.sessionKey },
|
|
287
|
+
errorMessage: outcome.errorMessage,
|
|
288
|
+
includeTimelineEvents: true,
|
|
289
|
+
}), true);
|
|
155
290
|
})
|
|
156
291
|
.catch((err) => {
|
|
157
|
-
if (attempt <
|
|
292
|
+
if (attempt < CHAT_HISTORY_FALLBACK_MAX_ATTEMPTS) {
|
|
158
293
|
scheduleChatHistoryFallback(runId, context, attempt + 1);
|
|
159
294
|
return;
|
|
160
295
|
}
|
|
@@ -162,7 +297,7 @@ export async function runRelayManager(opts) {
|
|
|
162
297
|
chatFallbacks.delete(runId);
|
|
163
298
|
chatRunContexts.delete(runId);
|
|
164
299
|
});
|
|
165
|
-
}, attempt === 0 ?
|
|
300
|
+
}, attempt === 0 ? CHAT_HISTORY_FALLBACK_INITIAL_DELAY_MS : CHAT_HISTORY_FALLBACK_RETRY_DELAY_MS);
|
|
166
301
|
timer.unref?.();
|
|
167
302
|
chatFallbacks.set(runId, timer);
|
|
168
303
|
};
|
|
@@ -183,9 +318,7 @@ export async function runRelayManager(opts) {
|
|
|
183
318
|
}
|
|
184
319
|
};
|
|
185
320
|
function send(msg) {
|
|
186
|
-
|
|
187
|
-
relayWs.send(JSON.stringify(msg));
|
|
188
|
-
}
|
|
321
|
+
sendRelayJson(relayWs, msg);
|
|
189
322
|
}
|
|
190
323
|
function publishOfficeSnapshot(eventName, payload) {
|
|
191
324
|
const officePayload = buildOfficeEventPayload(eventName, payload, () => new Date().toISOString());
|
|
@@ -194,6 +327,149 @@ export async function runRelayManager(opts) {
|
|
|
194
327
|
}
|
|
195
328
|
send({ type: "event", event: "office", payload: officePayload });
|
|
196
329
|
}
|
|
330
|
+
async function publishAndSendGatewayEvent(eventName, payload, publishOffice) {
|
|
331
|
+
const outgoingPayload = eventName === "chat"
|
|
332
|
+
? await relayOutgoingMediaInPayload(payload, {
|
|
333
|
+
relayServerUrl: opts.relayServerUrl,
|
|
334
|
+
relaySecret: opts.relaySecret,
|
|
335
|
+
gatewayId: opts.gatewayId,
|
|
336
|
+
senderDisplayName: "OpenClaw",
|
|
337
|
+
cache: outgoingMediaUploadCache,
|
|
338
|
+
})
|
|
339
|
+
: payload;
|
|
340
|
+
if (publishOffice) {
|
|
341
|
+
publishOfficeSnapshot(eventName, outgoingPayload);
|
|
342
|
+
}
|
|
343
|
+
send({ type: "event", event: eventName, payload: outgoingPayload });
|
|
344
|
+
}
|
|
345
|
+
async function relayOutgoingMediaForResponse(method, payload) {
|
|
346
|
+
if (method !== "chat.history") {
|
|
347
|
+
return payload;
|
|
348
|
+
}
|
|
349
|
+
return relayOutgoingMediaInHistoryResponse(payload, {
|
|
350
|
+
relayServerUrl: opts.relayServerUrl,
|
|
351
|
+
relaySecret: opts.relaySecret,
|
|
352
|
+
gatewayId: opts.gatewayId,
|
|
353
|
+
senderDisplayName: "OpenClaw",
|
|
354
|
+
cache: outgoingMediaUploadCache,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
async function requestChatHistoryFromClawConnect(params) {
|
|
358
|
+
try {
|
|
359
|
+
await dedupePendingChatSendMirrorsForSession(params);
|
|
360
|
+
const transcriptHistory = await readOpenClawTranscriptChatHistory(params, sessionDefaults);
|
|
361
|
+
if (transcriptHistory) {
|
|
362
|
+
return transcriptHistory;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
console.warn(`[relay] transcript chat.history unavailable: ${String(err)}`);
|
|
367
|
+
}
|
|
368
|
+
if (hasHistoryCursor(params)) {
|
|
369
|
+
return buildEmptyHistoryPage(params);
|
|
370
|
+
}
|
|
371
|
+
if (!shouldUseLegacyOpenClawHistoryFallback(params)) {
|
|
372
|
+
return buildEmptyHistoryPage(params);
|
|
373
|
+
}
|
|
374
|
+
if (!gatewayClient) {
|
|
375
|
+
throw new Error("gateway not connected");
|
|
376
|
+
}
|
|
377
|
+
return gatewayClient.request("chat.history", buildLegacyOpenClawHistoryParams(params));
|
|
378
|
+
}
|
|
379
|
+
function shouldUseLegacyOpenClawHistoryFallback(params) {
|
|
380
|
+
const record = params && typeof params === "object" && !Array.isArray(params)
|
|
381
|
+
? params
|
|
382
|
+
: {};
|
|
383
|
+
const rawSessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
|
|
384
|
+
? record.sessionKey.trim()
|
|
385
|
+
: sessionDefaults.mainSessionKey;
|
|
386
|
+
const normalized = canonicalizeSessionKey(rawSessionKey, sessionDefaults);
|
|
387
|
+
return typeof normalized === "string" && normalized === sessionDefaults.mainSessionKey;
|
|
388
|
+
}
|
|
389
|
+
function buildLegacyOpenClawHistoryParams(params) {
|
|
390
|
+
const record = params && typeof params === "object" && !Array.isArray(params)
|
|
391
|
+
? params
|
|
392
|
+
: {};
|
|
393
|
+
const rawSessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
|
|
394
|
+
? record.sessionKey.trim()
|
|
395
|
+
: sessionDefaults.mainSessionKey;
|
|
396
|
+
const sessionKey = canonicalizeSessionKey(rawSessionKey, sessionDefaults);
|
|
397
|
+
const legacyParams = {
|
|
398
|
+
sessionKey: typeof sessionKey === "string" && sessionKey.trim().length > 0
|
|
399
|
+
? sessionKey.trim()
|
|
400
|
+
: sessionDefaults.mainSessionKey,
|
|
401
|
+
};
|
|
402
|
+
const limit = normalizePositiveInteger(record.limit);
|
|
403
|
+
if (limit !== undefined) {
|
|
404
|
+
legacyParams.limit = limit;
|
|
405
|
+
}
|
|
406
|
+
const maxChars = normalizePositiveInteger(record.maxChars);
|
|
407
|
+
if (maxChars !== undefined) {
|
|
408
|
+
legacyParams.maxChars = maxChars;
|
|
409
|
+
}
|
|
410
|
+
return legacyParams;
|
|
411
|
+
}
|
|
412
|
+
function buildEmptyHistoryPage(params) {
|
|
413
|
+
const record = params && typeof params === "object" && !Array.isArray(params)
|
|
414
|
+
? params
|
|
415
|
+
: {};
|
|
416
|
+
const rawSessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
|
|
417
|
+
? record.sessionKey.trim()
|
|
418
|
+
: sessionDefaults.mainSessionKey;
|
|
419
|
+
const sessionKey = canonicalizeSessionKey(rawSessionKey, sessionDefaults);
|
|
420
|
+
return {
|
|
421
|
+
sessionKey: typeof sessionKey === "string" && sessionKey.trim().length > 0
|
|
422
|
+
? sessionKey.trim()
|
|
423
|
+
: sessionDefaults.mainSessionKey,
|
|
424
|
+
messages: [],
|
|
425
|
+
hasMore: false,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function hasHistoryCursor(params) {
|
|
429
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
const cursor = params.cursor;
|
|
433
|
+
return typeof cursor === "string" && cursor.trim().length > 0;
|
|
434
|
+
}
|
|
435
|
+
function normalizePositiveInteger(value) {
|
|
436
|
+
const parsed = typeof value === "number" && Number.isFinite(value)
|
|
437
|
+
? Math.round(value)
|
|
438
|
+
: typeof value === "string" && value.trim().length > 0
|
|
439
|
+
? Number.parseInt(value.trim(), 10)
|
|
440
|
+
: Number.NaN;
|
|
441
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
442
|
+
}
|
|
443
|
+
function buildFinalPayloadFromHistoryOutcome(basePayload, outcome) {
|
|
444
|
+
const payload = outcome.text.trim()
|
|
445
|
+
? withMessageText(basePayload, outcome.text)
|
|
446
|
+
: basePayload && typeof basePayload === "object" && !Array.isArray(basePayload)
|
|
447
|
+
? { ...basePayload }
|
|
448
|
+
: {};
|
|
449
|
+
const historyMessage = outcome.message;
|
|
450
|
+
const content = Array.isArray(historyMessage.content) ? historyMessage.content : [];
|
|
451
|
+
if (content.length === 0) {
|
|
452
|
+
return payload;
|
|
453
|
+
}
|
|
454
|
+
const payloadRecord = payload && typeof payload === "object" && !Array.isArray(payload)
|
|
455
|
+
? payload
|
|
456
|
+
: {};
|
|
457
|
+
const existingMessage = payloadRecord.message && typeof payloadRecord.message === "object" && !Array.isArray(payloadRecord.message)
|
|
458
|
+
? payloadRecord.message
|
|
459
|
+
: {};
|
|
460
|
+
return {
|
|
461
|
+
...payloadRecord,
|
|
462
|
+
message: {
|
|
463
|
+
...existingMessage,
|
|
464
|
+
...stripUndefinedHistoryMessageFields(historyMessage),
|
|
465
|
+
role: "assistant",
|
|
466
|
+
content,
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function stripUndefinedHistoryMessageFields(message) {
|
|
471
|
+
return Object.fromEntries(Object.entries(message).filter(([, value]) => value !== undefined));
|
|
472
|
+
}
|
|
197
473
|
relayWs.on("open", () => {
|
|
198
474
|
console.log(`Connected to relay server (gatewayId=${opts.gatewayId})`);
|
|
199
475
|
opts.onConnected?.();
|
|
@@ -235,13 +511,21 @@ export async function runRelayManager(opts) {
|
|
|
235
511
|
const resolvedSessionKey = sessionKey ?? runContext?.sessionKey;
|
|
236
512
|
const currentText = extractChatText(normalizedPayload);
|
|
237
513
|
const role = extractChatRole(normalizedPayload);
|
|
514
|
+
let realtimePayload = normalizedPayload;
|
|
238
515
|
if (runId) {
|
|
516
|
+
if (state === "final" || state === "error" || state === "failed" || state === "fail" || state === "aborted") {
|
|
517
|
+
scheduleChatSendDedupeForRun(runId, 100);
|
|
518
|
+
}
|
|
239
519
|
if (role === "assistant" && (state === "delta" || state === "final" || state === "error" || state === "failed" || state === "fail")) {
|
|
240
520
|
clearChatFallback(runId);
|
|
241
521
|
}
|
|
242
522
|
if (state === "delta" || state === "streaming" || state === "in_progress") {
|
|
243
523
|
const previousText = chatBuffers.get(runId) ?? "";
|
|
244
|
-
|
|
524
|
+
const bufferedText = appendUniqueSuffix(previousText, currentText);
|
|
525
|
+
chatBuffers.set(runId, bufferedText);
|
|
526
|
+
if (role === "assistant" && bufferedText.trim()) {
|
|
527
|
+
realtimePayload = withMessageText(normalizedPayload, bufferedText);
|
|
528
|
+
}
|
|
245
529
|
}
|
|
246
530
|
else if (state === "error" || state === "failed" || state === "fail" || state === "aborted") {
|
|
247
531
|
chatBuffers.delete(runId);
|
|
@@ -259,31 +543,25 @@ export async function runRelayManager(opts) {
|
|
|
259
543
|
chatRunContexts.delete(runId);
|
|
260
544
|
}
|
|
261
545
|
const outgoingPayload = withMessageText(normalizedPayload, resolvedText);
|
|
262
|
-
|
|
263
|
-
publishOfficeSnapshot(event, outgoingPayload);
|
|
264
|
-
}
|
|
265
|
-
send({ type: "event", event, payload: outgoingPayload });
|
|
546
|
+
void publishAndSendGatewayEvent(event, outgoingPayload, shouldPublishOffice);
|
|
266
547
|
return;
|
|
267
548
|
}
|
|
268
|
-
const fetchHistory = () =>
|
|
269
|
-
withTimeout(fetchHistory(),
|
|
549
|
+
const fetchHistory = () => requestChatHistoryFromClawConnect({ sessionKey: resolvedSessionKey, limit: 10 });
|
|
550
|
+
withTimeout(fetchHistory(), CHAT_HISTORY_FETCH_TIMEOUT_MS, "chat.history")
|
|
270
551
|
.then(async (history) => {
|
|
271
552
|
let outcome = runContext ? extractHistoryOutcome(history, runContext) : null;
|
|
272
|
-
// Retry once
|
|
553
|
+
// Retry once if OpenClaw has emitted final before the transcript is committed.
|
|
273
554
|
if (!outcome) {
|
|
274
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
275
|
-
const retryHistory = await withTimeout(fetchHistory(),
|
|
555
|
+
await new Promise((resolve) => setTimeout(resolve, CHAT_HISTORY_FINAL_RETRY_DELAY_MS));
|
|
556
|
+
const retryHistory = await withTimeout(fetchHistory(), CHAT_HISTORY_FETCH_TIMEOUT_MS, "chat.history retry");
|
|
276
557
|
outcome = runContext ? extractHistoryOutcome(retryHistory, runContext) : null;
|
|
277
558
|
}
|
|
278
|
-
if (runId) {
|
|
559
|
+
if (runId && outcome) {
|
|
279
560
|
chatRunContexts.delete(runId);
|
|
280
561
|
}
|
|
281
562
|
if (outcome?.kind === "final") {
|
|
282
|
-
const outgoingPayload =
|
|
283
|
-
|
|
284
|
-
publishOfficeSnapshot(event, outgoingPayload);
|
|
285
|
-
}
|
|
286
|
-
send({ type: "event", event, payload: outgoingPayload });
|
|
563
|
+
const outgoingPayload = buildFinalPayloadFromHistoryOutcome(normalizedPayload, outcome);
|
|
564
|
+
await publishAndSendGatewayEvent(event, outgoingPayload, shouldPublishOffice);
|
|
287
565
|
return;
|
|
288
566
|
}
|
|
289
567
|
if (outcome?.kind === "error") {
|
|
@@ -292,30 +570,22 @@ export async function runRelayManager(opts) {
|
|
|
292
570
|
state: "error",
|
|
293
571
|
errorMessage: outcome.errorMessage,
|
|
294
572
|
};
|
|
295
|
-
|
|
296
|
-
publishOfficeSnapshot(event, outgoingPayload);
|
|
297
|
-
}
|
|
298
|
-
send({
|
|
299
|
-
type: "event",
|
|
300
|
-
event,
|
|
301
|
-
payload: outgoingPayload,
|
|
302
|
-
});
|
|
573
|
+
await publishAndSendGatewayEvent(event, outgoingPayload, shouldPublishOffice);
|
|
303
574
|
return;
|
|
304
575
|
}
|
|
305
|
-
if (
|
|
306
|
-
|
|
576
|
+
if (runId && runContext) {
|
|
577
|
+
scheduleChatHistoryFallback(runId, runContext, 0);
|
|
578
|
+
return;
|
|
307
579
|
}
|
|
308
|
-
|
|
580
|
+
await publishAndSendGatewayEvent(event, normalizedPayload, shouldPublishOffice);
|
|
309
581
|
})
|
|
310
582
|
.catch((err) => {
|
|
311
583
|
console.error(`[relay] chat.history fetch failed: ${err}`);
|
|
312
|
-
if (runId) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (shouldPublishOffice) {
|
|
316
|
-
publishOfficeSnapshot(event, normalizedPayload);
|
|
584
|
+
if (runId && runContext) {
|
|
585
|
+
scheduleChatHistoryFallback(runId, runContext, 0);
|
|
586
|
+
return;
|
|
317
587
|
}
|
|
318
|
-
|
|
588
|
+
void publishAndSendGatewayEvent(event, normalizedPayload, shouldPublishOffice);
|
|
319
589
|
});
|
|
320
590
|
return;
|
|
321
591
|
}
|
|
@@ -323,12 +593,10 @@ export async function runRelayManager(opts) {
|
|
|
323
593
|
chatRunContexts.delete(runId);
|
|
324
594
|
scheduleContextUsageRefresh(p?.sessionKey, 450);
|
|
325
595
|
}
|
|
326
|
-
|
|
327
|
-
publishOfficeSnapshot(event, normalizedPayload);
|
|
328
|
-
}
|
|
329
|
-
send({ type: "event", event, payload: normalizedPayload });
|
|
596
|
+
void publishAndSendGatewayEvent(event, realtimePayload, shouldPublishOffice);
|
|
330
597
|
return;
|
|
331
598
|
}
|
|
599
|
+
void publishAndSendGatewayEvent(event, normalizedPayload, shouldPublishOffice);
|
|
332
600
|
},
|
|
333
601
|
});
|
|
334
602
|
gatewayClient.start();
|
|
@@ -338,11 +606,8 @@ export async function runRelayManager(opts) {
|
|
|
338
606
|
let methodForLog;
|
|
339
607
|
let voiceInputRun;
|
|
340
608
|
try {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
msg = JSON.parse(raw.toString());
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
609
|
+
const msg = parseRelayFrame(raw);
|
|
610
|
+
if (!msg) {
|
|
346
611
|
return;
|
|
347
612
|
}
|
|
348
613
|
if (msg.type === "heartbeat") {
|
|
@@ -405,12 +670,17 @@ export async function runRelayManager(opts) {
|
|
|
405
670
|
const paramsRecord = params && typeof params === "object" && !Array.isArray(params)
|
|
406
671
|
? params
|
|
407
672
|
: undefined;
|
|
408
|
-
|
|
673
|
+
const chatSendDedupeRequest = msg.method === "chat.send"
|
|
674
|
+
? buildChatSendDedupeRequest(paramsRecord)
|
|
675
|
+
: undefined;
|
|
676
|
+
if (!gatewayClient && msg.method !== "chat.history") {
|
|
409
677
|
throw new Error("gateway not connected");
|
|
410
678
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
.
|
|
679
|
+
const commandPromise = msg.method === "chat.history"
|
|
680
|
+
? requestChatHistoryFromClawConnect(params)
|
|
681
|
+
: gatewayClient.request(msg.method, params);
|
|
682
|
+
commandPromise
|
|
683
|
+
.then(async (result) => {
|
|
414
684
|
let resolvedRunId;
|
|
415
685
|
if ((msg.method === "chat.send" || msg.method === "agent") && params && typeof params === "object" && !Array.isArray(params)) {
|
|
416
686
|
const paramsRecord = params;
|
|
@@ -435,12 +705,16 @@ export async function runRelayManager(opts) {
|
|
|
435
705
|
};
|
|
436
706
|
scheduleChatHistoryFallback(runId, runContext);
|
|
437
707
|
}
|
|
708
|
+
if (msg.method === "chat.send" && chatSendDedupeRequest) {
|
|
709
|
+
registerChatSendDedupe(chatSendDedupeRequest, runId);
|
|
710
|
+
}
|
|
438
711
|
// Let a model switch settle before publishing context usage again.
|
|
439
712
|
// Forced refreshes can replay a stale model snapshot and overwrite the new selection.
|
|
440
713
|
scheduleContextUsageRefresh(sessionKey, 1200);
|
|
441
714
|
}
|
|
442
715
|
if (requestId) {
|
|
443
|
-
|
|
716
|
+
const responsePayload = await relayOutgoingMediaForResponse(msg.method, result);
|
|
717
|
+
send({ type: "res", id: requestId, ok: true, payload: responsePayload });
|
|
444
718
|
}
|
|
445
719
|
})
|
|
446
720
|
.catch((err) => {
|
|
@@ -461,6 +735,7 @@ export async function runRelayManager(opts) {
|
|
|
461
735
|
payload: buildMobileAssistantErrorPayload({
|
|
462
736
|
run: voiceInputRun,
|
|
463
737
|
errorMessage: setupMessage,
|
|
738
|
+
includeTimelineEvents: true,
|
|
464
739
|
}),
|
|
465
740
|
});
|
|
466
741
|
if (requestId) {
|
|
@@ -481,15 +756,18 @@ export async function runRelayManager(opts) {
|
|
|
481
756
|
clearTimeout(timer);
|
|
482
757
|
}
|
|
483
758
|
chatFallbacks.clear();
|
|
759
|
+
for (const timer of chatSendDedupeTimers.values()) {
|
|
760
|
+
clearTimeout(timer);
|
|
761
|
+
}
|
|
762
|
+
chatSendDedupeTimers.clear();
|
|
763
|
+
chatSendDedupeRequests.clear();
|
|
764
|
+
chatSendDedupeRunKeys.clear();
|
|
765
|
+
chatSendDedupeAttempts.clear();
|
|
484
766
|
for (const timer of contextUsageRefreshes.values()) {
|
|
485
767
|
clearTimeout(timer);
|
|
486
768
|
}
|
|
487
769
|
contextUsageRefreshes.clear();
|
|
488
|
-
|
|
489
|
-
// Stop retrying so the two instances don't bounce each other forever.
|
|
490
|
-
// Also stop retrying when the shutdown signal was received.
|
|
491
|
-
const intentional = opts.signal?.aborted || code === 4000;
|
|
492
|
-
resolve(!intentional);
|
|
770
|
+
resolve(shouldRetryRelayClose(code, opts.signal));
|
|
493
771
|
});
|
|
494
772
|
relayWs.on("error", (err) => {
|
|
495
773
|
console.error("Relay WebSocket error:", err.message);
|
|
@@ -506,8 +784,4 @@ export function buildRelayHelloMessage(opts) {
|
|
|
506
784
|
slashCommands: OPENCLAW_SLASH_COMMAND_CATALOG,
|
|
507
785
|
};
|
|
508
786
|
}
|
|
509
|
-
function buildRelayUrl(serverUrl, gatewayId, relaySecret) {
|
|
510
|
-
const base = serverUrl.replace(/\/+$/, "").replace(/^http/, "ws");
|
|
511
|
-
return `${base}/relay/${gatewayId}?secret=${encodeURIComponent(relaySecret)}`;
|
|
512
|
-
}
|
|
513
787
|
//# sourceMappingURL=relay-manager.js.map
|