clawconnect-agent 0.1.11 → 0.1.13

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.
Files changed (102) hide show
  1. package/README.md +16 -0
  2. package/README.zh-CN.md +16 -0
  3. package/dist/commands/install.js +3 -2
  4. package/dist/commands/install.js.map +1 -1
  5. package/dist/commands/pair.js +2 -1
  6. package/dist/commands/pair.js.map +1 -1
  7. package/dist/commands/profile-hints.d.ts +7 -0
  8. package/dist/commands/profile-hints.js +71 -0
  9. package/dist/commands/profile-hints.js.map +1 -0
  10. package/dist/commands/send-file.js +25 -5
  11. package/dist/commands/send-file.js.map +1 -1
  12. package/dist/core/relay/timeline-event-builder.d.ts +5 -1
  13. package/dist/core/relay/timeline-event-builder.js +4 -0
  14. package/dist/core/relay/timeline-event-builder.js.map +1 -1
  15. package/dist/core/relay/timeline-event-log.d.ts +3 -0
  16. package/dist/core/relay/timeline-event-log.js +4 -0
  17. package/dist/core/relay/timeline-event-log.js.map +1 -1
  18. package/dist/hermes/hermes-relay-manager.d.ts +27 -24
  19. package/dist/hermes/hermes-relay-manager.js +126 -343
  20. package/dist/hermes/hermes-relay-manager.js.map +1 -1
  21. package/dist/hermes/hermes-runtime-test-support.d.ts +27 -0
  22. package/dist/hermes/hermes-runtime-test-support.js +703 -0
  23. package/dist/hermes/hermes-runtime-test-support.js.map +1 -0
  24. package/dist/hermes/hermes-runtime.d.ts +1 -2
  25. package/dist/hermes/hermes-runtime.js +1 -2
  26. package/dist/hermes/hermes-runtime.js.map +1 -1
  27. package/dist/hermes/relay/active-hermes-chat-runs.d.ts +12 -0
  28. package/dist/hermes/relay/active-hermes-chat-runs.js +42 -0
  29. package/dist/hermes/relay/active-hermes-chat-runs.js.map +1 -0
  30. package/dist/hermes/relay/hermes-slash-command-catalog.d.ts +22 -0
  31. package/dist/hermes/relay/hermes-slash-command-catalog.js +248 -0
  32. package/dist/hermes/relay/hermes-slash-command-catalog.js.map +1 -0
  33. package/dist/hermes/relay/hermes-state-db-realtime.d.ts +69 -0
  34. package/dist/hermes/relay/hermes-state-db-realtime.js +444 -0
  35. package/dist/hermes/relay/hermes-state-db-realtime.js.map +1 -0
  36. package/dist/hermes/runtime/hermes-runtime-api-client.d.ts +15 -0
  37. package/dist/hermes/runtime/hermes-runtime-api-client.js +510 -0
  38. package/dist/hermes/runtime/hermes-runtime-api-client.js.map +1 -0
  39. package/dist/hermes/runtime/hermes-runtime-chat.d.ts +4 -14
  40. package/dist/hermes/runtime/hermes-runtime-chat.js +205 -436
  41. package/dist/hermes/runtime/hermes-runtime-chat.js.map +1 -1
  42. package/dist/hermes/runtime/hermes-runtime-command-utils.d.ts +1 -0
  43. package/dist/hermes/runtime/hermes-runtime-command-utils.js +10 -1
  44. package/dist/hermes/runtime/hermes-runtime-command-utils.js.map +1 -1
  45. package/dist/hermes/runtime/hermes-runtime-history-completion.d.ts +12 -0
  46. package/dist/hermes/runtime/hermes-runtime-history-completion.js +162 -0
  47. package/dist/hermes/runtime/hermes-runtime-history-completion.js.map +1 -0
  48. package/dist/hermes/runtime/hermes-runtime-history.js +85 -22
  49. package/dist/hermes/runtime/hermes-runtime-history.js.map +1 -1
  50. package/dist/hermes/runtime/hermes-runtime-preloaded-skills.d.ts +11 -0
  51. package/dist/hermes/runtime/hermes-runtime-preloaded-skills.js +67 -0
  52. package/dist/hermes/runtime/hermes-runtime-preloaded-skills.js.map +1 -0
  53. package/dist/hermes/runtime/hermes-runtime-process.d.ts +3 -1
  54. package/dist/hermes/runtime/hermes-runtime-process.js +35 -6
  55. package/dist/hermes/runtime/hermes-runtime-process.js.map +1 -1
  56. package/dist/hermes/runtime/hermes-runtime-sessions.js +28 -3
  57. package/dist/hermes/runtime/hermes-runtime-sessions.js.map +1 -1
  58. package/dist/hermes/runtime/hermes-runtime-slash-command.d.ts +8 -0
  59. package/dist/hermes/runtime/hermes-runtime-slash-command.js +144 -0
  60. package/dist/hermes/runtime/hermes-runtime-slash-command.js.map +1 -0
  61. package/dist/hermes/runtime/hermes-runtime-state-db.d.ts +4 -0
  62. package/dist/hermes/runtime/hermes-runtime-state-db.js +193 -0
  63. package/dist/hermes/runtime/hermes-runtime-state-db.js.map +1 -0
  64. package/dist/hermes/runtime/hermes-runtime-tool-log-watcher.d.ts +8 -0
  65. package/dist/hermes/runtime/hermes-runtime-tool-log-watcher.js +121 -0
  66. package/dist/hermes/runtime/hermes-runtime-tool-log-watcher.js.map +1 -0
  67. package/dist/hermes/runtime/hermes-runtime-usage.d.ts +2 -0
  68. package/dist/hermes/runtime/hermes-runtime-usage.js +59 -5
  69. package/dist/hermes/runtime/hermes-runtime-usage.js.map +1 -1
  70. package/dist/i18n/index.js +4 -0
  71. package/dist/i18n/index.js.map +1 -1
  72. package/dist/index.js +22 -3
  73. package/dist/index.js.map +1 -1
  74. package/dist/openclaw/handlers/local-handlers.js +66 -20
  75. package/dist/openclaw/handlers/local-handlers.js.map +1 -1
  76. package/dist/openclaw/relay/chat-send-dedupe-coordinator.d.ts +20 -0
  77. package/dist/openclaw/relay/chat-send-dedupe-coordinator.js +161 -0
  78. package/dist/openclaw/relay/chat-send-dedupe-coordinator.js.map +1 -0
  79. package/dist/openclaw/relay/relay-manager-hello.d.ts +6 -0
  80. package/dist/openclaw/relay/relay-manager-hello.js +11 -0
  81. package/dist/openclaw/relay/relay-manager-hello.js.map +1 -0
  82. package/dist/openclaw/relay/relay-manager-history-timing.d.ts +5 -0
  83. package/dist/openclaw/relay/relay-manager-history-timing.js +6 -0
  84. package/dist/openclaw/relay/relay-manager-history-timing.js.map +1 -0
  85. package/dist/openclaw/relay/relay-manager-office-events.d.ts +2 -0
  86. package/dist/openclaw/relay/relay-manager-office-events.js +9 -0
  87. package/dist/openclaw/relay/relay-manager-office-events.js.map +1 -0
  88. package/dist/openclaw/relay/relay-manager-payload-helpers.d.ts +18 -0
  89. package/dist/openclaw/relay/relay-manager-payload-helpers.js +213 -0
  90. package/dist/openclaw/relay/relay-manager-payload-helpers.js.map +1 -0
  91. package/dist/openclaw/relay/relay-manager-protocol.d.ts +49 -0
  92. package/dist/openclaw/relay/relay-manager-protocol.js +2 -0
  93. package/dist/openclaw/relay/relay-manager-protocol.js.map +1 -0
  94. package/dist/openclaw/relay-manager.d.ts +3 -27
  95. package/dist/openclaw/relay-manager.js +19 -402
  96. package/dist/openclaw/relay-manager.js.map +1 -1
  97. package/dist/openclaw/session-store.js +42 -1
  98. package/dist/openclaw/session-store.js.map +1 -1
  99. package/package.json +1 -1
  100. package/dist/hermes/runtime/hermes-runtime-artifacts.d.ts +0 -3
  101. package/dist/hermes/runtime/hermes-runtime-artifacts.js +0 -75
  102. package/dist/hermes/runtime/hermes-runtime-artifacts.js.map +0 -1
@@ -1,25 +1,6 @@
1
- import type { RelaySlashCommandDescriptor } from "../core/relay/slash-command-types.js";
2
- /** Messages the relay client sends to the relay server. */
3
- export type RelayHelloMessage = {
4
- type: "hello";
5
- platform: string;
6
- agentVersion: string;
7
- capabilities?: string[];
8
- slashCommands?: readonly RelaySlashCommandDescriptor[];
9
- };
10
- export interface RelayManagerOptions {
11
- relayServerUrl: string;
12
- gatewayId: string;
13
- relaySecret: string;
14
- gatewayUrl: string | (() => string);
15
- gatewayToken?: string;
16
- gatewayPassword?: string;
17
- onConnected?: () => void;
18
- onDisconnected?: () => void;
19
- /** Optional abort signal. When aborted the relay WebSocket is closed
20
- * cleanly (code 1001) and the retry loop stops. */
21
- signal?: AbortSignal;
22
- }
1
+ import type { RelayManagerOptions } from "./relay/relay-manager-protocol.js";
2
+ export { buildRelayHelloMessage } from "./relay/relay-manager-hello.js";
3
+ export type { RelayHelloMessage, RelayManagerOptions } from "./relay/relay-manager-protocol.js";
23
4
  /**
24
5
  * Connects to the cloud relay server and the local OpenClaw Gateway,
25
6
  * then bridges messages between them indefinitely.
@@ -29,8 +10,3 @@ export interface RelayManagerOptions {
29
10
  * connection closes.
30
11
  */
31
12
  export declare function runRelayManager(opts: RelayManagerOptions): Promise<boolean>;
32
- export declare function buildRelayHelloMessage(opts: {
33
- platform: string;
34
- agentVersion: string;
35
- capabilities?: string[];
36
- }): RelayHelloMessage;
@@ -6,21 +6,18 @@ import { handleProviderCommand } from "./handlers/provider-handlers.js";
6
6
  import { DEFAULT_GATEWAY_SESSION_DEFAULTS, buildContextUsageFingerprint, readContextUsageSnapshot, canonicalizeRelayParams, canonicalizeSessionKey, extractGatewaySessionDefaults, } from "./relay/session-context.js";
7
7
  import { appendUniqueSuffix, extractChatRole, extractChatText, normalizeChatEventPayload, normalizeChatState, withMessageText, } from "../core/relay/chat-payload.js";
8
8
  import { extractHistoryOutcome, readOpenClawTranscriptChatHistory, withTimeout, } from "./relay/chat-history.js";
9
- import { dedupeOpenClawChatSendUserMirrorTranscript, } from "./relay/transcript-dedupe.js";
9
+ import { OpenClawChatSendDedupeCoordinator, } from "./relay/chat-send-dedupe-coordinator.js";
10
10
  import { buildMobileAssistantDeltaPayload, buildMobileAssistantErrorPayload, buildMobileAssistantFinalPayload, } from "../core/relay/mobile-chat-run-bridge.js";
11
- import { buildOfficeEventPayload } from "../core/relay/office-payload.js";
12
11
  import { prepareChatSendParams } from "./relay/chat-send-attachments.js";
13
12
  import { relayOutgoingMediaInHistoryResponse, relayOutgoingMediaInPayload } from "./relay/outgoing-media-relay.js";
14
13
  import { prepareOpenClawVoiceInputCommand } from "./relay/openclaw-voice-input.js";
15
14
  import { voiceInputSetupMessage } from "../core/relay/voice-input.js";
16
15
  import { gatewayCapabilitiesForType } from "../gateway-profiles.js";
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];
16
+ import { buildRelayHelloMessage } from "./relay/relay-manager-hello.js";
17
+ import { CHAT_HISTORY_FALLBACK_INITIAL_DELAY_MS, CHAT_HISTORY_FALLBACK_MAX_ATTEMPTS, CHAT_HISTORY_FALLBACK_RETRY_DELAY_MS, CHAT_HISTORY_FETCH_TIMEOUT_MS, CHAT_HISTORY_FINAL_RETRY_DELAY_MS, } from "./relay/relay-manager-history-timing.js";
18
+ import { publishOfficeSnapshot } from "./relay/relay-manager-office-events.js";
19
+ export { buildRelayHelloMessage } from "./relay/relay-manager-hello.js";
20
+ import { buildEmptyHistoryPage, buildFinalPayloadFromHistoryOutcome, buildLegacyOpenClawHistoryParams, extractChatErrorMessage, hasHistoryCursor, mergeCanonicalChatPayload, mobileAssistantUsageFromPayload, nonTextContentBlocks, nonTextContentBlocksFromHistory, resolveChatPayloadSeq, resolveChatPayloadTimestamp, shouldUseLegacyOpenClawHistoryFallback, } from "./relay/relay-manager-payload-helpers.js";
24
21
  // ---------------------------------------------------------------------------
25
22
  // Main entry point
26
23
  // ---------------------------------------------------------------------------
@@ -50,11 +47,7 @@ export async function runRelayManager(opts) {
50
47
  const chatBuffers = new Map();
51
48
  const chatFallbacks = new Map();
52
49
  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();
50
+ const chatSendDedupe = new OpenClawChatSendDedupeCoordinator(() => sessionDefaults);
58
51
  const outgoingMediaUploadCache = new Map();
59
52
  const contextUsageRefreshes = new Map();
60
53
  const contextUsageFingerprints = new Map();
@@ -65,139 +58,6 @@ export async function runRelayManager(opts) {
65
58
  chatFallbacks.delete(runId);
66
59
  }
67
60
  };
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
- }
201
61
  const publishContextUsageSnapshot = async (sessionKey, force = false) => {
202
62
  const normalizedSessionKey = canonicalizeSessionKey(sessionKey, sessionDefaults);
203
63
  if (typeof normalizedSessionKey !== "string" || normalizedSessionKey.trim().length === 0) {
@@ -320,13 +180,6 @@ export async function runRelayManager(opts) {
320
180
  function send(msg) {
321
181
  sendRelayJson(relayWs, msg);
322
182
  }
323
- function publishOfficeSnapshot(eventName, payload) {
324
- const officePayload = buildOfficeEventPayload(eventName, payload, () => new Date().toISOString());
325
- if (!officePayload) {
326
- return;
327
- }
328
- send({ type: "event", event: "office", payload: officePayload });
329
- }
330
183
  async function publishAndSendGatewayEvent(eventName, payload, publishOffice, userMessage) {
331
184
  const outgoingPayload = eventName === "chat"
332
185
  ? await relayOutgoingMediaInPayload(payload, {
@@ -339,7 +192,7 @@ export async function runRelayManager(opts) {
339
192
  })
340
193
  : payload;
341
194
  if (publishOffice) {
342
- publishOfficeSnapshot(eventName, outgoingPayload);
195
+ publishOfficeSnapshot(send, eventName, outgoingPayload);
343
196
  }
344
197
  send({ type: "event", event: eventName, payload: outgoingPayload });
345
198
  }
@@ -357,7 +210,7 @@ export async function runRelayManager(opts) {
357
210
  }
358
211
  async function requestChatHistoryFromClawConnect(params) {
359
212
  try {
360
- await dedupePendingChatSendMirrorsForSession(params);
213
+ await chatSendDedupe.dedupePendingForSession(params);
361
214
  const transcriptHistory = await readOpenClawTranscriptChatHistory(params, sessionDefaults);
362
215
  if (transcriptHistory) {
363
216
  return transcriptHistory;
@@ -367,236 +220,15 @@ export async function runRelayManager(opts) {
367
220
  console.warn(`[relay] transcript chat.history unavailable: ${String(err)}`);
368
221
  }
369
222
  if (hasHistoryCursor(params)) {
370
- return buildEmptyHistoryPage(params);
223
+ return buildEmptyHistoryPage(params, sessionDefaults);
371
224
  }
372
- if (!shouldUseLegacyOpenClawHistoryFallback(params)) {
373
- return buildEmptyHistoryPage(params);
225
+ if (!shouldUseLegacyOpenClawHistoryFallback(params, sessionDefaults)) {
226
+ return buildEmptyHistoryPage(params, sessionDefaults);
374
227
  }
375
228
  if (!gatewayClient) {
376
229
  throw new Error("gateway not connected");
377
230
  }
378
- return gatewayClient.request("chat.history", buildLegacyOpenClawHistoryParams(params));
379
- }
380
- function shouldUseLegacyOpenClawHistoryFallback(params) {
381
- const record = params && typeof params === "object" && !Array.isArray(params)
382
- ? params
383
- : {};
384
- const rawSessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
385
- ? record.sessionKey.trim()
386
- : sessionDefaults.mainSessionKey;
387
- const normalized = canonicalizeSessionKey(rawSessionKey, sessionDefaults);
388
- return typeof normalized === "string" && normalized === sessionDefaults.mainSessionKey;
389
- }
390
- function buildLegacyOpenClawHistoryParams(params) {
391
- const record = params && typeof params === "object" && !Array.isArray(params)
392
- ? params
393
- : {};
394
- const rawSessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
395
- ? record.sessionKey.trim()
396
- : sessionDefaults.mainSessionKey;
397
- const sessionKey = canonicalizeSessionKey(rawSessionKey, sessionDefaults);
398
- const legacyParams = {
399
- sessionKey: typeof sessionKey === "string" && sessionKey.trim().length > 0
400
- ? sessionKey.trim()
401
- : sessionDefaults.mainSessionKey,
402
- };
403
- const limit = normalizePositiveInteger(record.limit);
404
- if (limit !== undefined) {
405
- legacyParams.limit = limit;
406
- }
407
- const maxChars = normalizePositiveInteger(record.maxChars);
408
- if (maxChars !== undefined) {
409
- legacyParams.maxChars = maxChars;
410
- }
411
- return legacyParams;
412
- }
413
- function buildEmptyHistoryPage(params) {
414
- const record = params && typeof params === "object" && !Array.isArray(params)
415
- ? params
416
- : {};
417
- const rawSessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
418
- ? record.sessionKey.trim()
419
- : sessionDefaults.mainSessionKey;
420
- const sessionKey = canonicalizeSessionKey(rawSessionKey, sessionDefaults);
421
- return {
422
- sessionKey: typeof sessionKey === "string" && sessionKey.trim().length > 0
423
- ? sessionKey.trim()
424
- : sessionDefaults.mainSessionKey,
425
- messages: [],
426
- hasMore: false,
427
- };
428
- }
429
- function hasHistoryCursor(params) {
430
- if (!params || typeof params !== "object" || Array.isArray(params)) {
431
- return false;
432
- }
433
- const cursor = params.cursor;
434
- return typeof cursor === "string" && cursor.trim().length > 0;
435
- }
436
- function normalizePositiveInteger(value) {
437
- const parsed = typeof value === "number" && Number.isFinite(value)
438
- ? Math.round(value)
439
- : typeof value === "string" && value.trim().length > 0
440
- ? Number.parseInt(value.trim(), 10)
441
- : Number.NaN;
442
- return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
443
- }
444
- function buildFinalPayloadFromHistoryOutcome(basePayload, outcome) {
445
- const payload = outcome.text.trim()
446
- ? withMessageText(basePayload, outcome.text)
447
- : basePayload && typeof basePayload === "object" && !Array.isArray(basePayload)
448
- ? { ...basePayload }
449
- : {};
450
- const historyMessage = outcome.message;
451
- const content = Array.isArray(historyMessage.content) ? historyMessage.content : [];
452
- if (content.length === 0) {
453
- return payload;
454
- }
455
- const payloadRecord = payload && typeof payload === "object" && !Array.isArray(payload)
456
- ? payload
457
- : {};
458
- const existingMessage = payloadRecord.message && typeof payloadRecord.message === "object" && !Array.isArray(payloadRecord.message)
459
- ? payloadRecord.message
460
- : {};
461
- return {
462
- ...payloadRecord,
463
- message: {
464
- ...existingMessage,
465
- ...stripUndefinedHistoryMessageFields(historyMessage),
466
- role: "assistant",
467
- content,
468
- },
469
- };
470
- }
471
- function stripUndefinedHistoryMessageFields(message) {
472
- return Object.fromEntries(Object.entries(message).filter(([, value]) => value !== undefined));
473
- }
474
- function mergeCanonicalChatPayload(basePayload, canonicalPayload) {
475
- const base = asRecord(basePayload);
476
- const canonical = asRecord(canonicalPayload);
477
- if (!base) {
478
- return canonicalPayload;
479
- }
480
- if (!canonical) {
481
- return basePayload;
482
- }
483
- const baseMessage = asRecord(base.message);
484
- const canonicalMessage = asRecord(canonical.message);
485
- const merged = {
486
- ...base,
487
- ...canonical,
488
- };
489
- return baseMessage || canonicalMessage
490
- ? {
491
- ...merged,
492
- message: {
493
- ...(baseMessage ?? {}),
494
- ...(canonicalMessage ?? {}),
495
- },
496
- }
497
- : merged;
498
- }
499
- function resolveChatPayloadSeq(payload) {
500
- return normalizeFiniteNumber(deepField(payload, ["seq", "sequence", "index"]))
501
- ?? resolveChatPayloadTimestamp(payload);
502
- }
503
- function resolveChatPayloadTimestamp(payload) {
504
- const value = normalizeFiniteNumber(deepField(payload, ["ts", "timestamp", "createdAt", "created_at", "time"]));
505
- if (value !== undefined) {
506
- return value > 10_000_000_000 ? Math.round(value) : Math.round(value * 1000);
507
- }
508
- return Date.now();
509
- }
510
- function extractChatErrorMessage(payload) {
511
- const direct = firstNonEmptyString(deepField(payload, ["errorMessage", "error_message", "message", "text"]));
512
- if (direct) {
513
- return direct;
514
- }
515
- const error = asRecord(asRecord(payload)?.error);
516
- const nested = firstNonEmptyString(error?.message, error?.userMessage, error?.detail);
517
- return nested ?? "Request failed";
518
- }
519
- function mobileAssistantUsageFromPayload(payload) {
520
- const record = asRecord(payload);
521
- const usage = asRecord(record?.usage);
522
- return stripUndefined({
523
- currentModel: firstNonEmptyString(record?.currentModel, record?.model, usage?.currentModel, usage?.model),
524
- provider: firstNonEmptyString(record?.provider, usage?.provider),
525
- contextUsage: normalizeNonNegativeInteger(record?.contextUsage)
526
- ?? normalizeNonNegativeInteger(record?.promptTokens)
527
- ?? normalizeNonNegativeInteger(record?.inputTokens)
528
- ?? normalizeNonNegativeInteger(usage?.contextUsage)
529
- ?? normalizeNonNegativeInteger(usage?.promptTokens)
530
- ?? normalizeNonNegativeInteger(usage?.inputTokens),
531
- contextLimit: normalizeNonNegativeInteger(record?.contextLimit)
532
- ?? normalizeNonNegativeInteger(record?.maxInputTokens)
533
- ?? normalizeNonNegativeInteger(usage?.contextLimit)
534
- ?? normalizeNonNegativeInteger(usage?.maxInputTokens),
535
- });
536
- }
537
- function nonTextContentBlocks(payload) {
538
- const payloadRecord = asRecord(payload);
539
- const message = asRecord(payloadRecord?.message);
540
- const topLevelContent = payloadRecord?.content;
541
- const content = Array.isArray(message?.content)
542
- ? message.content
543
- : Array.isArray(topLevelContent)
544
- ? topLevelContent
545
- : [];
546
- return content.filter((block) => {
547
- const record = asRecord(block);
548
- return Boolean(record?.type) && record?.type !== "text";
549
- });
550
- }
551
- function nonTextContentBlocksFromHistory(message) {
552
- return Array.isArray(message.content)
553
- ? message.content.filter((block) => {
554
- const record = asRecord(block);
555
- return Boolean(record?.type) && record?.type !== "text";
556
- })
557
- : [];
558
- }
559
- function deepField(payload, keys) {
560
- const record = asRecord(payload);
561
- const message = asRecord(record?.message);
562
- const data = asRecord(record?.data);
563
- for (const key of keys) {
564
- if (record && record[key] !== undefined)
565
- return record[key];
566
- if (message && message[key] !== undefined)
567
- return message[key];
568
- if (data && data[key] !== undefined)
569
- return data[key];
570
- }
571
- return undefined;
572
- }
573
- function firstNonEmptyString(...values) {
574
- for (const value of values) {
575
- if (typeof value === "string" && value.trim().length > 0) {
576
- return value.trim();
577
- }
578
- }
579
- return undefined;
580
- }
581
- function normalizeFiniteNumber(value) {
582
- const number = typeof value === "number" && Number.isFinite(value)
583
- ? value
584
- : typeof value === "string" && value.trim().length > 0
585
- ? Number(value.trim())
586
- : Number.NaN;
587
- return Number.isFinite(number) ? number : undefined;
588
- }
589
- function normalizeNonNegativeInteger(value) {
590
- const number = normalizeFiniteNumber(value);
591
- return number !== undefined && number >= 0 ? Math.round(number) : undefined;
592
- }
593
- function stripUndefined(value) {
594
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
595
- }
596
- function asRecord(value) {
597
- return value && typeof value === "object" && !Array.isArray(value)
598
- ? value
599
- : undefined;
231
+ return gatewayClient.request("chat.history", buildLegacyOpenClawHistoryParams(params, sessionDefaults));
600
232
  }
601
233
  relayWs.on("open", () => {
602
234
  console.log(`Connected to relay server (gatewayId=${opts.gatewayId})`);
@@ -615,7 +247,7 @@ export async function runRelayManager(opts) {
615
247
  onConnected: () => {
616
248
  console.log("Gateway connected.");
617
249
  send({ type: "gateway_connected" });
618
- publishOfficeSnapshot("gateway_connected", {
250
+ publishOfficeSnapshot(send, "gateway_connected", {
619
251
  sessionKey: sessionDefaults.mainSessionKey,
620
252
  });
621
253
  void refreshSessionDefaults();
@@ -623,7 +255,7 @@ export async function runRelayManager(opts) {
623
255
  onDisconnected: (reason) => {
624
256
  console.log(`Gateway disconnected: ${reason}`);
625
257
  send({ type: "gateway_disconnected", reason });
626
- publishOfficeSnapshot("gateway_disconnected", {
258
+ publishOfficeSnapshot(send, "gateway_disconnected", {
627
259
  reason,
628
260
  });
629
261
  },
@@ -642,7 +274,7 @@ export async function runRelayManager(opts) {
642
274
  let realtimePayload = normalizedPayload;
643
275
  if (runId) {
644
276
  if (state === "final" || state === "error" || state === "failed" || state === "fail" || state === "aborted") {
645
- scheduleChatSendDedupeForRun(runId, 100);
277
+ chatSendDedupe.scheduleForRun(runId, 100);
646
278
  }
647
279
  if (role === "assistant" && (state === "delta" || state === "final" || state === "error" || state === "failed" || state === "fail")) {
648
280
  clearChatFallback(runId);
@@ -837,7 +469,7 @@ export async function runRelayManager(opts) {
837
469
  ? params
838
470
  : undefined;
839
471
  const chatSendDedupeRequest = msg.method === "chat.send"
840
- ? buildChatSendDedupeRequest(paramsRecord)
472
+ ? chatSendDedupe.buildRequest(paramsRecord)
841
473
  : undefined;
842
474
  if (!gatewayClient && msg.method !== "chat.history") {
843
475
  throw new Error("gateway not connected");
@@ -872,7 +504,7 @@ export async function runRelayManager(opts) {
872
504
  scheduleChatHistoryFallback(runId, runContext);
873
505
  }
874
506
  if (msg.method === "chat.send" && chatSendDedupeRequest) {
875
- registerChatSendDedupe(chatSendDedupeRequest, runId);
507
+ chatSendDedupe.register(chatSendDedupeRequest, runId);
876
508
  }
877
509
  // Let a model switch settle before publishing context usage again.
878
510
  // Forced refreshes can replay a stale model snapshot and overwrite the new selection.
@@ -922,13 +554,7 @@ export async function runRelayManager(opts) {
922
554
  clearTimeout(timer);
923
555
  }
924
556
  chatFallbacks.clear();
925
- for (const timer of chatSendDedupeTimers.values()) {
926
- clearTimeout(timer);
927
- }
928
- chatSendDedupeTimers.clear();
929
- chatSendDedupeRequests.clear();
930
- chatSendDedupeRunKeys.clear();
931
- chatSendDedupeAttempts.clear();
557
+ chatSendDedupe.clearAll();
932
558
  for (const timer of contextUsageRefreshes.values()) {
933
559
  clearTimeout(timer);
934
560
  }
@@ -941,13 +567,4 @@ export async function runRelayManager(opts) {
941
567
  });
942
568
  });
943
569
  }
944
- export function buildRelayHelloMessage(opts) {
945
- return {
946
- type: "hello",
947
- platform: opts.platform,
948
- agentVersion: opts.agentVersion,
949
- capabilities: opts.capabilities,
950
- slashCommands: OPENCLAW_SLASH_COMMAND_CATALOG,
951
- };
952
- }
953
570
  //# sourceMappingURL=relay-manager.js.map