ocuclaw 1.3.2 → 1.3.4
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 +29 -1
- package/dist/config/runtime-config-session-title-model.test.js +0 -3
- package/dist/config/runtime-config.js +22 -33
- package/dist/domain/activity-status-adapter.js +0 -7
- package/dist/domain/activity-status-arbiter.js +3 -27
- package/dist/domain/activity-status-labels.js +8 -38
- package/dist/domain/code-span-regions.js +4 -24
- package/dist/domain/constant-time-equal.js +9 -0
- package/dist/domain/constant-time-equal.test.js +28 -0
- package/dist/domain/conversation-state.js +27 -138
- package/dist/domain/debug-bundle-cache.js +52 -0
- package/dist/domain/debug-bundle-format.js +60 -0
- package/dist/domain/debug-bundle-preview.js +123 -0
- package/dist/domain/debug-bundle-redaction.js +182 -0
- package/dist/domain/debug-bundle-save.js +11 -0
- package/dist/domain/debug-bundle-zip.js +15 -0
- package/dist/domain/debug-bundle.js +97 -0
- package/dist/domain/debug-store.js +6 -17
- package/dist/domain/debug-upload-preset.js +27 -0
- package/dist/domain/glasses-display-system-prompt.js +0 -5
- package/dist/domain/glasses-display-system-prompt.test.js +1 -1
- package/dist/domain/glasses-ui-content-summary.js +0 -6
- package/dist/domain/glasses-ui-system-prompt.test.js +1 -2
- package/dist/domain/message-emoji-allowlist.js +0 -7
- package/dist/domain/message-emoji-filter.js +3 -9
- package/dist/domain/neural-emoji-reactor-tag-config.js +3 -3
- package/dist/domain/prompt-channel-fragments.js +1 -10
- package/dist/domain/tagged-span-parser.js +3 -26
- package/dist/domain/tagged-span-strip.js +0 -7
- package/dist/even-ai/even-ai-endpoint.js +77 -24
- package/dist/even-ai/even-ai-run-waiter.js +0 -1
- package/dist/even-ai/even-ai-settings-store.js +11 -0
- package/dist/gateway/gateway-bridge.js +8 -9
- package/dist/gateway/gateway-timing-ledger.js +8 -6
- package/dist/gateway/openclaw-client.js +97 -297
- package/dist/gateway/sanitize-connect-reason.js +10 -0
- package/dist/gateway/sanitize-connect-reason.test.js +34 -0
- package/dist/index.js +3 -3
- package/dist/runtime/channel-two-hook.js +1 -6
- package/dist/runtime/container-env.js +1 -5
- package/dist/runtime/debug-bundle-handler.js +159 -0
- package/dist/runtime/display-toggle-states.js +6 -17
- package/dist/runtime/downstream-handler.js +682 -508
- package/dist/runtime/glasses-backpressure-latch.js +93 -0
- package/dist/runtime/ocuclaw-settings-store.js +10 -1
- package/dist/runtime/openclaw-host-version.js +5 -0
- package/dist/runtime/plugin-version-service.js +13 -6
- package/dist/runtime/provider-usage-select.js +0 -6
- package/dist/runtime/register-session-title-distiller.js +14 -16
- package/dist/runtime/relay-core.js +657 -271
- package/dist/runtime/relay-service.js +40 -36
- package/dist/runtime/relay-worker-approval-replay-cache.js +1 -1
- package/dist/runtime/relay-worker-entry.js +1 -2
- package/dist/runtime/relay-worker-health.js +2 -10
- package/dist/runtime/relay-worker-protocol.js +6 -1
- package/dist/runtime/relay-worker-supervisor.js +109 -39
- package/dist/runtime/relay-worker-transport.js +157 -15
- package/dist/runtime/session-context-service.js +5 -45
- package/dist/runtime/session-service.js +157 -175
- package/dist/runtime/session-title-distiller-budget.js +1 -5
- package/dist/runtime/session-title-distiller-helpers.js +14 -24
- package/dist/runtime/session-title-distiller.js +109 -122
- package/dist/runtime/session-title-record.js +0 -6
- package/dist/runtime/stable-prompt-snapshot.js +3 -14
- package/dist/runtime/upstream-runtime.js +600 -103
- package/dist/tools/device-info-tool.js +4 -21
- package/dist/tools/glasses-ui-cron.js +58 -63
- package/dist/tools/glasses-ui-descriptors.js +4 -33
- package/dist/tools/glasses-ui-limits.js +0 -13
- package/dist/tools/glasses-ui-paint-floor.js +22 -34
- package/dist/tools/glasses-ui-recipes.js +92 -101
- package/dist/tools/glasses-ui-surfaces.js +295 -100
- package/dist/tools/glasses-ui-template.js +7 -22
- package/dist/tools/glasses-ui-tool-description.test.js +2 -2
- package/dist/tools/glasses-ui-tool.js +475 -331
- package/dist/tools/glasses-ui-voicemail.js +242 -0
- package/dist/tools/glasses-ui-wake.js +195 -0
- package/dist/tools/session-title-tool.js +2 -7
- package/dist/tools/session-title-tool.test.js +1 -1
- package/dist/version.js +3 -2
- package/openclaw.plugin.json +60 -13
- package/package.json +3 -2
- package/skills/glasses-ui/SKILL.md +19 -3
- package/dist/runtime/protocol-adapter.js +0 -387
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
normalizeEvenAiRoutingMode,
|
|
3
|
+
normalizeEvenAiDefaultAgent,
|
|
4
|
+
} from "../even-ai/even-ai-settings-store.js";
|
|
2
5
|
import {
|
|
3
6
|
normalizeOcuClawDefaultModel,
|
|
4
7
|
normalizeOcuClawDefaultThinking,
|
|
5
8
|
normalizeOcuClawSystemPrompt,
|
|
9
|
+
normalizeOcuClawDefaultAgent,
|
|
6
10
|
} from "./ocuclaw-settings-store.js";
|
|
7
11
|
import {
|
|
8
12
|
formatMainOperationReceived,
|
|
9
13
|
formatSendAck,
|
|
10
14
|
} from "./relay-worker-protocol.js";
|
|
11
15
|
|
|
12
|
-
// --- Factory ---
|
|
13
|
-
|
|
14
16
|
function normalizeLogger(logger) {
|
|
15
17
|
if (!logger || typeof logger !== "object") {
|
|
16
18
|
return console;
|
|
@@ -24,61 +26,12 @@ function normalizeLogger(logger) {
|
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
/**
|
|
28
|
-
* Create a downstream message handler.
|
|
29
|
-
*
|
|
30
|
-
* Transport-agnostic protocol logic for processing messages from
|
|
31
|
-
* downstream clients (Even App, commander.html). Consumed by relay.js.
|
|
32
|
-
*
|
|
33
|
-
* @param {object} opts
|
|
34
|
-
* @param {(id: string, text: string, sessionKey: string|null, attachment: object|null, clientDisplaySignals: object|null) => Promise} opts.onSend
|
|
35
|
-
* Forward a user message to the upstream OpenClaw agent.
|
|
36
|
-
* @param {(sender: string, text: string) => Array} opts.onSimulate
|
|
37
|
-
* Inject a fake message into conversation state; returns pages array.
|
|
38
|
-
* @param {(request: object) => Promise<object>|object} [opts.onSimulateStream]
|
|
39
|
-
* Inject deterministic streaming payload (relay-local), then finalize pages.
|
|
40
|
-
* @param {() => Promise<Array>} opts.onNewChat
|
|
41
|
-
* Clear conversation and reset the OpenClaw session; returns empty pages array.
|
|
42
|
-
* @param {() => Promise<{models: Array, fetchedAtMs: number, stale: boolean}>} [opts.onGetModelsCatalog]
|
|
43
|
-
* Return cached model catalog snapshot.
|
|
44
|
-
* @param {() => Promise<{skills: Array, fetchedAtMs: number, stale: boolean}>} [opts.onGetSkillsCatalog]
|
|
45
|
-
* Return cached skills catalog snapshot.
|
|
46
|
-
* @param {() => Promise<{models: Array, fetchedAtMs: number, stale: boolean}>} [opts.onGetSonioxModels]
|
|
47
|
-
* Return cached Soniox model snapshot.
|
|
48
|
-
* @param {() => Promise<object>} [opts.onGetProviderUsageSnapshot]
|
|
49
|
-
* Return the current provider usage snapshot for the active provider.
|
|
50
|
-
* @param {() => Promise<object>} [opts.onGetSessionModelConfig]
|
|
51
|
-
* Return current session model controls.
|
|
52
|
-
* @param {(patch: object) => Promise<{status: string, error?: string}>} [opts.onSetSessionModelConfig]
|
|
53
|
-
* Patch current session model controls.
|
|
54
|
-
* @param {({sessionKey: string}) => Promise<{status: string, error?: string}>} [opts.onCompactSession]
|
|
55
|
-
* Trigger gateway-side compaction for a session key.
|
|
56
|
-
* @param {() => Promise<object>} [opts.onGetEvenAiSettings]
|
|
57
|
-
* Return current relay-owned Even AI settings.
|
|
58
|
-
* @param {() => Promise<{sessions: Array, dedicatedKey: string}>} [opts.onGetEvenAiSessions]
|
|
59
|
-
* Return the Even AI session-browser payload.
|
|
60
|
-
* @param {(patch: object) => Promise<{status: string, error?: string, settings?: object}>} [opts.onSetEvenAiSettings]
|
|
61
|
-
* Patch relay-owned Even AI settings.
|
|
62
|
-
* @param {() => Promise<object>} [opts.onGetOcuClawSettings]
|
|
63
|
-
* Return current relay-owned OcuClaw settings.
|
|
64
|
-
* @param {(patch: object) => Promise<{status: string, error?: string, settings?: object}>} [opts.onSetOcuClawSettings]
|
|
65
|
-
* Patch relay-owned OcuClaw settings.
|
|
66
|
-
* @param {(clientId: string, payload: object) => Promise<object>|object} [opts.onRequestSonioxTemporaryKey]
|
|
67
|
-
* Mint a short-lived Soniox temporary key for the provided voiceSessionId.
|
|
68
|
-
* @param {() => object} [opts.onGetStatus]
|
|
69
|
-
* Return the current relay status snapshot.
|
|
70
|
-
* @param {() => boolean} opts.isUpstreamConnected
|
|
71
|
-
* Returns true if the OpenClaw gateway connection is active.
|
|
72
|
-
* @param {(clientId: string, payload: object) => void} [opts.onEventDebug]
|
|
73
|
-
* Optional structured client debug-event callback.
|
|
74
|
-
* @param {(clientId: string, payload: object) => Promise<object>|object} [opts.onReadinessProbe]
|
|
75
|
-
* Optional dedicated readiness-probe dispatcher.
|
|
76
|
-
* @returns {object} Handler instance
|
|
77
|
-
*/
|
|
78
29
|
function createDownstreamHandler(opts) {
|
|
79
30
|
const logger = normalizeLogger(opts.logger);
|
|
80
31
|
const externalDebugToolsEnabled = opts.externalDebugToolsEnabled !== false;
|
|
81
32
|
const onSend = opts.onSend;
|
|
33
|
+
const onAbortSession = opts.onAbortSession || null;
|
|
34
|
+
const onSteerSession = opts.onSteerSession || null;
|
|
82
35
|
const onSimulate = opts.onSimulate;
|
|
83
36
|
const onSimulateStream = opts.onSimulateStream || null;
|
|
84
37
|
const onNewChat = opts.onNewChat;
|
|
@@ -88,10 +41,12 @@ function createDownstreamHandler(opts) {
|
|
|
88
41
|
const onSlashCommand = opts.onSlashCommand;
|
|
89
42
|
const onGetModelsCatalog = opts.onGetModelsCatalog;
|
|
90
43
|
const onGetSkillsCatalog = opts.onGetSkillsCatalog;
|
|
44
|
+
const onGetAgentsCatalog = opts.onGetAgentsCatalog;
|
|
91
45
|
const onGetSonioxModels = opts.onGetSonioxModels || null;
|
|
92
46
|
const onGetProviderUsageSnapshot = opts.onGetProviderUsageSnapshot || null;
|
|
93
47
|
const onGetSessionModelConfig = opts.onGetSessionModelConfig;
|
|
94
48
|
const onSetSessionModelConfig = opts.onSetSessionModelConfig;
|
|
49
|
+
const onSetSessionAgent = opts.onSetSessionAgent;
|
|
95
50
|
const onCompactSession = opts.onCompactSession || null;
|
|
96
51
|
const onGetEvenAiSettings = opts.onGetEvenAiSettings;
|
|
97
52
|
const onGetEvenAiSessions = opts.onGetEvenAiSessions;
|
|
@@ -99,6 +54,7 @@ function createDownstreamHandler(opts) {
|
|
|
99
54
|
const onGetOcuClawSettings = opts.onGetOcuClawSettings;
|
|
100
55
|
const onSetOcuClawSettings = opts.onSetOcuClawSettings;
|
|
101
56
|
const onRequestSonioxTemporaryKey = opts.onRequestSonioxTemporaryKey || null;
|
|
57
|
+
const onRequestCartesiaAccessToken = opts.onRequestCartesiaAccessToken || null;
|
|
102
58
|
const onGetStatus = opts.onGetStatus || null;
|
|
103
59
|
const isUpstreamConnected = opts.isUpstreamConnected;
|
|
104
60
|
const onConsoleLog = opts.onConsoleLog || null;
|
|
@@ -119,10 +75,12 @@ function createDownstreamHandler(opts) {
|
|
|
119
75
|
const onSetSessionPinned = opts.onSetSessionPinned || null;
|
|
120
76
|
const onDeleteSessions = opts.onDeleteSessions || null;
|
|
121
77
|
const onSearchTranscripts = opts.onSearchTranscripts || null;
|
|
78
|
+
const onDebugBundleRequest = opts.onDebugBundleRequest || null;
|
|
79
|
+
const onDebugBundleSave = opts.onDebugBundleSave || null;
|
|
80
|
+
const onDebugBundleFetch = opts.onDebugBundleFetch || null;
|
|
122
81
|
const getSnapshotRevision = opts.getSnapshotRevision || null;
|
|
123
82
|
const operationRegistry = opts.operationRegistry || null;
|
|
124
83
|
|
|
125
|
-
/** Client IDs subscribed to raw protocol frame forwarding. */
|
|
126
84
|
const protocolSubscribers = new Set();
|
|
127
85
|
const APPROVAL_DECISIONS = new Set(["allow-once", "allow-always", "deny"]);
|
|
128
86
|
const approvalResolveCacheTtlMs = Number.isFinite(opts.approvalResolveCacheTtlMs)
|
|
@@ -133,7 +91,7 @@ function createDownstreamHandler(opts) {
|
|
|
133
91
|
: 500;
|
|
134
92
|
const EXTERNAL_DEBUG_TOOLS_DISABLED_ERROR =
|
|
135
93
|
"external debug tools are disabled by plugin config";
|
|
136
|
-
|
|
94
|
+
|
|
137
95
|
const approvalResolveCache = new Map();
|
|
138
96
|
const APP_PROTOCOL = {
|
|
139
97
|
activity: "ocuclaw.activity.update",
|
|
@@ -165,6 +123,8 @@ function createDownstreamHandler(opts) {
|
|
|
165
123
|
providerUsageSnapshot: "ocuclaw.provider.usage.snapshot",
|
|
166
124
|
skillsCatalogGet: "ocuclaw.skills.catalog.get",
|
|
167
125
|
skillsCatalogSnapshot: "ocuclaw.skills.catalog.snapshot",
|
|
126
|
+
agentsCatalogGet: "ocuclaw.agent.catalog.get",
|
|
127
|
+
agentsCatalogSnapshot: "ocuclaw.agent.catalog.snapshot",
|
|
168
128
|
pages: "ocuclaw.view.pages.snapshot",
|
|
169
129
|
protocolSubscribe: "ocuclaw.protocol.tap.subscribe",
|
|
170
130
|
protocolFrame: "ocuclaw.protocol.tap.frame",
|
|
@@ -172,37 +132,38 @@ function createDownstreamHandler(opts) {
|
|
|
172
132
|
readinessProbeRequest: "ocuclaw.readiness.probe.request",
|
|
173
133
|
remoteControl: "ocuclaw.remote.control",
|
|
174
134
|
requestSonioxTemporaryKey: "requestSonioxTemporaryKey",
|
|
135
|
+
requestCartesiaAccessToken: "requestCartesiaAccessToken",
|
|
175
136
|
sonioxModelsGet: "ocuclaw.voice.soniox.models.get",
|
|
176
137
|
sonioxModelsSnapshot: "ocuclaw.voice.soniox.models.snapshot",
|
|
177
138
|
sessionConfigGet: "ocuclaw.session.config.get",
|
|
178
139
|
sessionConfigSet: "ocuclaw.session.config.set",
|
|
179
140
|
sessionConfigSetAck: "ocuclaw.session.config.set.ack",
|
|
180
141
|
sessionConfigSnapshot: "ocuclaw.session.config.snapshot",
|
|
142
|
+
sessionAgentSet: "ocuclaw.session.agent.set",
|
|
143
|
+
sessionAgentSetAck: "ocuclaw.session.agent.set.ack",
|
|
144
|
+
sessionAbort: "ocuclaw.session.abort",
|
|
145
|
+
sessionAbortAck: "ocuclaw.session.abort.ack",
|
|
181
146
|
sessionCompact: "ocuclaw.session.compact",
|
|
182
147
|
sessionCompactAck: "ocuclaw.session.compact.ack",
|
|
183
148
|
sessionCreate: "ocuclaw.session.create",
|
|
184
149
|
sessionList: "ocuclaw.session.list",
|
|
150
|
+
sessionListDiff: "ocuclaw.session.list.diff",
|
|
151
|
+
sessionListDiffResult: "ocuclaw.session.list.diff.result",
|
|
185
152
|
sessionListResult: "ocuclaw.session.list.result",
|
|
186
153
|
sessionReset: "ocuclaw.session.reset",
|
|
154
|
+
sessionSteer: "ocuclaw.session.steer",
|
|
187
155
|
sessionSwitch: "ocuclaw.session.switch",
|
|
188
156
|
sessionSwitchApplied: "ocuclaw.session.switch.applied",
|
|
189
157
|
sessionTitleSet: "ocuclaw.session.title.set",
|
|
190
158
|
sonioxTemporaryKey: "sonioxTemporaryKey",
|
|
191
159
|
sonioxTemporaryKeyError: "sonioxTemporaryKeyError",
|
|
160
|
+
cartesiaAccessToken: "cartesiaAccessToken",
|
|
161
|
+
cartesiaAccessTokenError: "cartesiaAccessTokenError",
|
|
192
162
|
status: "ocuclaw.runtime.status",
|
|
193
163
|
statusGet: "ocuclaw.runtime.status.get",
|
|
194
164
|
typingUpdate: "ocuclaw.typing.update",
|
|
195
165
|
};
|
|
196
166
|
|
|
197
|
-
// --- Format helpers ---
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Format a pages message for broadcast.
|
|
201
|
-
*
|
|
202
|
-
* @param {Array<{content: string, subPage: [number,number]|null}>} pages
|
|
203
|
-
* @param {{revision?: number}} [meta]
|
|
204
|
-
* @returns {string} JSON string
|
|
205
|
-
*/
|
|
206
167
|
function formatPages(pages, meta) {
|
|
207
168
|
const msg = { type: APP_PROTOCOL.pages, pages };
|
|
208
169
|
const fallbackRevision = getSnapshotRevision
|
|
@@ -219,13 +180,6 @@ function createDownstreamHandler(opts) {
|
|
|
219
180
|
return JSON.stringify(msg);
|
|
220
181
|
}
|
|
221
182
|
|
|
222
|
-
/**
|
|
223
|
-
* Format a status message for broadcast.
|
|
224
|
-
*
|
|
225
|
-
* @param {object} status - Status fields (openclaw, agent, session, etc.)
|
|
226
|
-
* @param {{revision?: number}} [meta]
|
|
227
|
-
* @returns {string} JSON string
|
|
228
|
-
*/
|
|
229
183
|
function formatStatus(status, meta) {
|
|
230
184
|
const msg = { ...status, type: APP_PROTOCOL.status };
|
|
231
185
|
const fallbackRevision = getSnapshotRevision
|
|
@@ -242,33 +196,14 @@ function createDownstreamHandler(opts) {
|
|
|
242
196
|
return JSON.stringify(msg);
|
|
243
197
|
}
|
|
244
198
|
|
|
245
|
-
/**
|
|
246
|
-
* Format an activity message for broadcast.
|
|
247
|
-
*
|
|
248
|
-
* @param {object} activity - Activity fields (state, tool, etc.)
|
|
249
|
-
* @returns {string} JSON string
|
|
250
|
-
*/
|
|
251
199
|
function formatActivity(activity) {
|
|
252
200
|
return JSON.stringify({ ...activity, type: APP_PROTOCOL.activity });
|
|
253
201
|
}
|
|
254
202
|
|
|
255
|
-
/**
|
|
256
|
-
* Format a typing update message for broadcast.
|
|
257
|
-
*
|
|
258
|
-
* @param {object} update - Typing fields (state, runId, sessionKey, etc.)
|
|
259
|
-
* @returns {string} JSON string
|
|
260
|
-
*/
|
|
261
203
|
function formatTyping(update) {
|
|
262
204
|
return JSON.stringify({ ...update, type: APP_PROTOCOL.typingUpdate });
|
|
263
205
|
}
|
|
264
206
|
|
|
265
|
-
/**
|
|
266
|
-
* Format an error message for unicast.
|
|
267
|
-
*
|
|
268
|
-
* @param {string} error
|
|
269
|
-
* @param {{code?: string, requestId?: string, op?: string}} [meta]
|
|
270
|
-
* @returns {string} JSON string
|
|
271
|
-
*/
|
|
272
207
|
function formatError(error, meta) {
|
|
273
208
|
const msg = {
|
|
274
209
|
type: "error",
|
|
@@ -301,30 +236,25 @@ function createDownstreamHandler(opts) {
|
|
|
301
236
|
);
|
|
302
237
|
}
|
|
303
238
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
239
|
+
function formatSendAckCompat(id, status, error, errorCode, data) {
|
|
240
|
+
return formatSendAck(id, status, error, errorCode, data);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function formatSessionAbortAck(data = {}) {
|
|
244
|
+
const msg = {
|
|
245
|
+
type: APP_PROTOCOL.sessionAbortAck,
|
|
246
|
+
requestId: parseOptionalTrimmedString(data.requestId),
|
|
247
|
+
status: data.status || "accepted",
|
|
248
|
+
};
|
|
249
|
+
if (data.error !== undefined) msg.error = data.error;
|
|
250
|
+
if (data.errorCode !== undefined) msg.errorCode = data.errorCode;
|
|
251
|
+
return JSON.stringify(msg);
|
|
315
252
|
}
|
|
316
253
|
|
|
317
254
|
function formatOperationReceived(data) {
|
|
318
255
|
return formatMainOperationReceived(data);
|
|
319
256
|
}
|
|
320
257
|
|
|
321
|
-
/**
|
|
322
|
-
* Format a protocol frame for forwarding to subscribers.
|
|
323
|
-
*
|
|
324
|
-
* @param {string} direction - "in" or "out"
|
|
325
|
-
* @param {object} frame - Raw protocol frame
|
|
326
|
-
* @returns {string} JSON string
|
|
327
|
-
*/
|
|
328
258
|
function formatProtocol(direction, frame) {
|
|
329
259
|
return JSON.stringify({
|
|
330
260
|
type: APP_PROTOCOL.protocolFrame,
|
|
@@ -333,13 +263,6 @@ function createDownstreamHandler(opts) {
|
|
|
333
263
|
});
|
|
334
264
|
}
|
|
335
265
|
|
|
336
|
-
/**
|
|
337
|
-
* Format a streaming text message for broadcast during agent runs.
|
|
338
|
-
*
|
|
339
|
-
* @param {string} text - Streaming assistant text
|
|
340
|
-
* @param {Array<{start: number, end: number, emoji: string}>} [spans] - Optional emoji spans
|
|
341
|
-
* @returns {string} JSON string
|
|
342
|
-
*/
|
|
343
266
|
function formatStreaming(text, emojiSpans, paceSpans) {
|
|
344
267
|
const payload = { type: APP_PROTOCOL.messageStreamDelta, text };
|
|
345
268
|
if (Array.isArray(emojiSpans) && emojiSpans.length > 0) {
|
|
@@ -351,22 +274,135 @@ function createDownstreamHandler(opts) {
|
|
|
351
274
|
return JSON.stringify(payload);
|
|
352
275
|
}
|
|
353
276
|
|
|
354
|
-
/**
|
|
355
|
-
* Format a sessions list message for unicast response.
|
|
356
|
-
*
|
|
357
|
-
* @param {Array<{key: string, updatedAt: number, preview: string, firstUserMessage?: string}>} sessions
|
|
358
|
-
* @returns {string} JSON string
|
|
359
|
-
*/
|
|
360
277
|
function formatSessions(sessions) {
|
|
361
278
|
return JSON.stringify({ type: APP_PROTOCOL.sessionListResult, sessions });
|
|
362
279
|
}
|
|
363
280
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
281
|
+
function sessionInfoFingerprint(session) {
|
|
282
|
+
const row = session && typeof session === "object" ? session : {};
|
|
283
|
+
const raw = [
|
|
284
|
+
row.key || "",
|
|
285
|
+
Number.isFinite(Number(row.updatedAt)) ? String(Math.floor(Number(row.updatedAt))) : "0",
|
|
286
|
+
row.preview || "",
|
|
287
|
+
row.firstUserMessage || "",
|
|
288
|
+
row.title || "",
|
|
289
|
+
row.pinned === true ? "true" : "false",
|
|
290
|
+
Number.isFinite(Number(row.pinnedAtMs)) ? String(Math.floor(Number(row.pinnedAtMs))) : "",
|
|
291
|
+
row.agentId || "",
|
|
292
|
+
row.agentName || "",
|
|
293
|
+
].join("\u001f");
|
|
294
|
+
return fnv1a32Hex(raw);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function fnv1a32Hex(text) {
|
|
298
|
+
let hash = 0x811c9dc5;
|
|
299
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
300
|
+
hash ^= text.charCodeAt(i);
|
|
301
|
+
hash = Math.imul(hash, 0x01000193);
|
|
302
|
+
}
|
|
303
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function normalizeSessionDiffLimit(limit) {
|
|
307
|
+
if (!Number.isFinite(Number(limit)) || Number(limit) <= 0) return 80;
|
|
308
|
+
return Math.min(200, Math.max(1, Math.floor(Number(limit))));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function normalizeSessionDiffKind(kind) {
|
|
312
|
+
return String(kind || "").trim().toLowerCase() === "evenai"
|
|
313
|
+
? "evenai"
|
|
314
|
+
: "ocuclaw";
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function parseKnownSessionRows(msg) {
|
|
318
|
+
if (!msg || !Array.isArray(msg.known)) return [];
|
|
319
|
+
const out = [];
|
|
320
|
+
for (const item of msg.known) {
|
|
321
|
+
if (!item || typeof item !== "object") continue;
|
|
322
|
+
const key = typeof item.key === "string" ? item.key.trim() : "";
|
|
323
|
+
if (!key) continue;
|
|
324
|
+
out.push({
|
|
325
|
+
key,
|
|
326
|
+
updatedAt: Number.isFinite(Number(item.updatedAt))
|
|
327
|
+
? Math.floor(Number(item.updatedAt))
|
|
328
|
+
: 0,
|
|
329
|
+
fingerprint:
|
|
330
|
+
typeof item.fingerprint === "string" ? item.fingerprint.trim() : "",
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function buildSessionDiff({ kind, sessions, known, limit, dedicatedKey }) {
|
|
337
|
+
const normalizedKind = normalizeSessionDiffKind(kind);
|
|
338
|
+
const normalizedLimit = normalizeSessionDiffLimit(limit);
|
|
339
|
+
const rows = Array.isArray(sessions) ? sessions : [];
|
|
340
|
+
const limitedRows = rows
|
|
341
|
+
.slice()
|
|
342
|
+
.sort((left, right) => (Number(right && right.updatedAt) || 0) - (Number(left && left.updatedAt) || 0))
|
|
343
|
+
.slice(0, normalizedLimit);
|
|
344
|
+
const knownByKey = new Map();
|
|
345
|
+
for (const row of Array.isArray(known) ? known : []) {
|
|
346
|
+
const key = typeof row.key === "string" ? row.key.trim().toLowerCase() : "";
|
|
347
|
+
if (!key) continue;
|
|
348
|
+
knownByKey.set(key, row);
|
|
349
|
+
}
|
|
350
|
+
const liveKeys = new Set();
|
|
351
|
+
const changed = [];
|
|
352
|
+
for (const row of limitedRows) {
|
|
353
|
+
const key = typeof row.key === "string" ? row.key.trim().toLowerCase() : "";
|
|
354
|
+
if (!key) continue;
|
|
355
|
+
liveKeys.add(key);
|
|
356
|
+
const knownRow = knownByKey.get(key);
|
|
357
|
+
const updatedAt = Number.isFinite(Number(row.updatedAt))
|
|
358
|
+
? Math.floor(Number(row.updatedAt))
|
|
359
|
+
: 0;
|
|
360
|
+
const fingerprint = sessionInfoFingerprint(row);
|
|
361
|
+
if (
|
|
362
|
+
!knownRow ||
|
|
363
|
+
knownRow.updatedAt !== updatedAt ||
|
|
364
|
+
knownRow.fingerprint !== fingerprint
|
|
365
|
+
) {
|
|
366
|
+
changed.push(row);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const deletedKeys = [];
|
|
370
|
+
for (const row of Array.isArray(known) ? known : []) {
|
|
371
|
+
const rawKey = typeof row.key === "string" ? row.key.trim() : "";
|
|
372
|
+
const key = rawKey.toLowerCase();
|
|
373
|
+
if (key && !liveKeys.has(key)) deletedKeys.push(rawKey);
|
|
374
|
+
}
|
|
375
|
+
const out = {
|
|
376
|
+
type: APP_PROTOCOL.sessionListDiffResult,
|
|
377
|
+
kind: normalizedKind,
|
|
378
|
+
sessions: changed,
|
|
379
|
+
deletedKeys,
|
|
380
|
+
limit: normalizedLimit,
|
|
381
|
+
};
|
|
382
|
+
if (typeof dedicatedKey === "string" && dedicatedKey) {
|
|
383
|
+
out.dedicatedKey = dedicatedKey;
|
|
384
|
+
}
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function formatSessionDiff(payload) {
|
|
389
|
+
return JSON.stringify(buildSessionDiff(payload || {}));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function formatEmptySessionDiff(kind, limit, dedicatedKey) {
|
|
393
|
+
const out = {
|
|
394
|
+
type: APP_PROTOCOL.sessionListDiffResult,
|
|
395
|
+
kind: normalizeSessionDiffKind(kind),
|
|
396
|
+
sessions: [],
|
|
397
|
+
deletedKeys: [],
|
|
398
|
+
limit: normalizeSessionDiffLimit(limit),
|
|
399
|
+
};
|
|
400
|
+
if (typeof dedicatedKey === "string" && dedicatedKey) {
|
|
401
|
+
out.dedicatedKey = dedicatedKey;
|
|
402
|
+
}
|
|
403
|
+
return JSON.stringify(out);
|
|
404
|
+
}
|
|
405
|
+
|
|
370
406
|
function formatSessionSwitched(sessionKey) {
|
|
371
407
|
return JSON.stringify({
|
|
372
408
|
type: APP_PROTOCOL.sessionSwitchApplied,
|
|
@@ -374,12 +410,6 @@ function createDownstreamHandler(opts) {
|
|
|
374
410
|
});
|
|
375
411
|
}
|
|
376
412
|
|
|
377
|
-
/**
|
|
378
|
-
* Format a model catalog snapshot for unicast.
|
|
379
|
-
*
|
|
380
|
-
* @param {{models: Array, fetchedAtMs: number, stale: boolean}} payload
|
|
381
|
-
* @returns {string}
|
|
382
|
-
*/
|
|
383
413
|
function formatModelsCatalog(payload) {
|
|
384
414
|
return JSON.stringify({
|
|
385
415
|
type: APP_PROTOCOL.modelCatalogSnapshot,
|
|
@@ -392,12 +422,6 @@ function createDownstreamHandler(opts) {
|
|
|
392
422
|
});
|
|
393
423
|
}
|
|
394
424
|
|
|
395
|
-
/**
|
|
396
|
-
* Format a cached skills catalog snapshot for unicast.
|
|
397
|
-
*
|
|
398
|
-
* @param {{skills?: Array, fetchedAtMs?: number, stale?: boolean}} payload
|
|
399
|
-
* @returns {string}
|
|
400
|
-
*/
|
|
401
425
|
function formatSkillsCatalog(payload) {
|
|
402
426
|
return JSON.stringify({
|
|
403
427
|
type: APP_PROTOCOL.skillsCatalogSnapshot,
|
|
@@ -410,12 +434,27 @@ function createDownstreamHandler(opts) {
|
|
|
410
434
|
});
|
|
411
435
|
}
|
|
412
436
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
437
|
+
function formatAgentsCatalog(payload) {
|
|
438
|
+
return JSON.stringify({
|
|
439
|
+
type: APP_PROTOCOL.agentsCatalogSnapshot,
|
|
440
|
+
agents: Array.isArray(payload && payload.agents) ? payload.agents : [],
|
|
441
|
+
defaultId:
|
|
442
|
+
payload && typeof payload.defaultId === "string"
|
|
443
|
+
? payload.defaultId
|
|
444
|
+
: null,
|
|
445
|
+
mainKey:
|
|
446
|
+
payload && typeof payload.mainKey === "string" ? payload.mainKey : null,
|
|
447
|
+
scope:
|
|
448
|
+
payload && typeof payload.scope === "string" ? payload.scope : null,
|
|
449
|
+
fetchedAtMs:
|
|
450
|
+
Number.isFinite(payload && payload.fetchedAtMs)
|
|
451
|
+
? Math.floor(payload.fetchedAtMs)
|
|
452
|
+
: Date.now(),
|
|
453
|
+
stale: !!(payload && payload.stale),
|
|
454
|
+
unsupported: !!(payload && payload.unsupported),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
419
458
|
function formatSonioxModels(payload) {
|
|
420
459
|
return JSON.stringify({
|
|
421
460
|
type: APP_PROTOCOL.sonioxModelsSnapshot,
|
|
@@ -428,12 +467,6 @@ function createDownstreamHandler(opts) {
|
|
|
428
467
|
});
|
|
429
468
|
}
|
|
430
469
|
|
|
431
|
-
/**
|
|
432
|
-
* Format a provider usage snapshot for unicast.
|
|
433
|
-
*
|
|
434
|
-
* @param {object} payload
|
|
435
|
-
* @returns {string}
|
|
436
|
-
*/
|
|
437
470
|
function formatProviderUsageSnapshot(payload) {
|
|
438
471
|
const provider =
|
|
439
472
|
payload && typeof payload.provider === "string" && payload.provider.trim()
|
|
@@ -497,12 +530,6 @@ function createDownstreamHandler(opts) {
|
|
|
497
530
|
});
|
|
498
531
|
}
|
|
499
532
|
|
|
500
|
-
/**
|
|
501
|
-
* Format current session model controls.
|
|
502
|
-
*
|
|
503
|
-
* @param {object} payload
|
|
504
|
-
* @returns {string}
|
|
505
|
-
*/
|
|
506
533
|
function formatSessionModelConfig(payload) {
|
|
507
534
|
return JSON.stringify({
|
|
508
535
|
type: APP_PROTOCOL.sessionConfigSnapshot,
|
|
@@ -530,15 +557,11 @@ function createDownstreamHandler(opts) {
|
|
|
530
557
|
payload && typeof payload.elevatedLevel === "string"
|
|
531
558
|
? payload.elevatedLevel
|
|
532
559
|
: "off",
|
|
560
|
+
agentId:
|
|
561
|
+
payload && typeof payload.agentId === "string" ? payload.agentId : "",
|
|
533
562
|
});
|
|
534
563
|
}
|
|
535
564
|
|
|
536
|
-
/**
|
|
537
|
-
* Format a session model config write acknowledgement.
|
|
538
|
-
*
|
|
539
|
-
* @param {{status: string, error?: string}} payload
|
|
540
|
-
* @returns {string}
|
|
541
|
-
*/
|
|
542
565
|
function formatSessionModelConfigAck(payload) {
|
|
543
566
|
const out = {
|
|
544
567
|
type: APP_PROTOCOL.sessionConfigSetAck,
|
|
@@ -553,12 +576,6 @@ function createDownstreamHandler(opts) {
|
|
|
553
576
|
return JSON.stringify(out);
|
|
554
577
|
}
|
|
555
578
|
|
|
556
|
-
/**
|
|
557
|
-
* Format a compactSession ack message for unicast.
|
|
558
|
-
*
|
|
559
|
-
* @param {{status: string, error?: string, requestId?: string}} payload
|
|
560
|
-
* @returns {string} JSON string
|
|
561
|
-
*/
|
|
562
579
|
function formatCompactSessionAck(payload) {
|
|
563
580
|
const msg = {
|
|
564
581
|
type: APP_PROTOCOL.sessionCompactAck,
|
|
@@ -577,12 +594,6 @@ function createDownstreamHandler(opts) {
|
|
|
577
594
|
return JSON.stringify(msg);
|
|
578
595
|
}
|
|
579
596
|
|
|
580
|
-
/**
|
|
581
|
-
* Format the current relay-owned Even AI settings snapshot.
|
|
582
|
-
*
|
|
583
|
-
* @param {object} payload
|
|
584
|
-
* @returns {string}
|
|
585
|
-
*/
|
|
586
597
|
function formatEvenAiSettings(payload) {
|
|
587
598
|
return JSON.stringify({
|
|
588
599
|
type: APP_PROTOCOL.evenAiSettingsSnapshot,
|
|
@@ -605,15 +616,14 @@ function createDownstreamHandler(opts) {
|
|
|
605
616
|
: "",
|
|
606
617
|
listenEnabled: payload && payload.listenEnabled === true,
|
|
607
618
|
defaultFastMode: !!(payload && payload.defaultFastMode === true),
|
|
619
|
+
defaultAgent: normalizeEvenAiDefaultAgent(
|
|
620
|
+
payload && typeof payload.defaultAgent === "string"
|
|
621
|
+
? payload.defaultAgent
|
|
622
|
+
: undefined,
|
|
623
|
+
),
|
|
608
624
|
});
|
|
609
625
|
}
|
|
610
626
|
|
|
611
|
-
/**
|
|
612
|
-
* Format an Even AI settings write acknowledgement.
|
|
613
|
-
*
|
|
614
|
-
* @param {{status: string, error?: string}} payload
|
|
615
|
-
* @returns {string}
|
|
616
|
-
*/
|
|
617
627
|
function formatEvenAiSettingsAck(payload) {
|
|
618
628
|
const out = {
|
|
619
629
|
type: APP_PROTOCOL.evenAiSettingsSetAck,
|
|
@@ -628,12 +638,6 @@ function createDownstreamHandler(opts) {
|
|
|
628
638
|
return JSON.stringify(out);
|
|
629
639
|
}
|
|
630
640
|
|
|
631
|
-
/**
|
|
632
|
-
* Format the current relay-owned OcuClaw settings snapshot.
|
|
633
|
-
*
|
|
634
|
-
* @param {object} payload
|
|
635
|
-
* @returns {string}
|
|
636
|
-
*/
|
|
637
641
|
function formatOcuClawSettings(payload) {
|
|
638
642
|
return JSON.stringify({
|
|
639
643
|
type: APP_PROTOCOL.ocuClawSettingsSnapshot,
|
|
@@ -653,15 +657,14 @@ function createDownstreamHandler(opts) {
|
|
|
653
657
|
: undefined,
|
|
654
658
|
),
|
|
655
659
|
defaultFastMode: !!(payload && payload.defaultFastMode === true),
|
|
660
|
+
defaultAgent: normalizeOcuClawDefaultAgent(
|
|
661
|
+
payload && typeof payload.defaultAgent === "string"
|
|
662
|
+
? payload.defaultAgent
|
|
663
|
+
: undefined,
|
|
664
|
+
),
|
|
656
665
|
});
|
|
657
666
|
}
|
|
658
667
|
|
|
659
|
-
/**
|
|
660
|
-
* Format an OcuClaw settings write acknowledgement.
|
|
661
|
-
*
|
|
662
|
-
* @param {{status: string, error?: string}} payload
|
|
663
|
-
* @returns {string}
|
|
664
|
-
*/
|
|
665
668
|
function formatOcuClawSettingsAck(payload) {
|
|
666
669
|
const out = {
|
|
667
670
|
type: APP_PROTOCOL.ocuClawSettingsSetAck,
|
|
@@ -676,12 +679,6 @@ function createDownstreamHandler(opts) {
|
|
|
676
679
|
return JSON.stringify(out);
|
|
677
680
|
}
|
|
678
681
|
|
|
679
|
-
/**
|
|
680
|
-
* Format the Even AI session-browser result payload.
|
|
681
|
-
*
|
|
682
|
-
* @param {{sessions?: Array, dedicatedKey?: string}} payload
|
|
683
|
-
* @returns {string}
|
|
684
|
-
*/
|
|
685
682
|
function formatEvenAiSessions(payload) {
|
|
686
683
|
return JSON.stringify({
|
|
687
684
|
type: APP_PROTOCOL.evenAiSessionListResult,
|
|
@@ -693,12 +690,6 @@ function createDownstreamHandler(opts) {
|
|
|
693
690
|
});
|
|
694
691
|
}
|
|
695
692
|
|
|
696
|
-
/**
|
|
697
|
-
* Format an exec approval request for broadcast.
|
|
698
|
-
*
|
|
699
|
-
* @param {object} data - Approval payload from gateway
|
|
700
|
-
* @returns {string} JSON string
|
|
701
|
-
*/
|
|
702
693
|
function formatApproval(data) {
|
|
703
694
|
const request = data && data.request ? data.request : {};
|
|
704
695
|
const approvalKind =
|
|
@@ -750,12 +741,6 @@ function createDownstreamHandler(opts) {
|
|
|
750
741
|
});
|
|
751
742
|
}
|
|
752
743
|
|
|
753
|
-
/**
|
|
754
|
-
* Format an exec approval resolution for broadcast.
|
|
755
|
-
*
|
|
756
|
-
* @param {object} data - Resolution payload from gateway
|
|
757
|
-
* @returns {string} JSON string
|
|
758
|
-
*/
|
|
759
744
|
function formatApprovalResolved(data) {
|
|
760
745
|
return JSON.stringify({
|
|
761
746
|
type: APP_PROTOCOL.approvalResolved,
|
|
@@ -768,12 +753,6 @@ function createDownstreamHandler(opts) {
|
|
|
768
753
|
});
|
|
769
754
|
}
|
|
770
755
|
|
|
771
|
-
/**
|
|
772
|
-
* Format an approval-response acknowledgement for unicast.
|
|
773
|
-
*
|
|
774
|
-
* @param {object} data
|
|
775
|
-
* @returns {string}
|
|
776
|
-
*/
|
|
777
756
|
function formatApprovalResponseAck(data) {
|
|
778
757
|
return JSON.stringify({
|
|
779
758
|
type: APP_PROTOCOL.approvalResolveAck,
|
|
@@ -802,14 +781,6 @@ function createDownstreamHandler(opts) {
|
|
|
802
781
|
});
|
|
803
782
|
}
|
|
804
783
|
|
|
805
|
-
/**
|
|
806
|
-
* Format a committed listen handoff update for broadcast during voice mode.
|
|
807
|
-
*
|
|
808
|
-
* @param {string} text - Final committed text sent upstream
|
|
809
|
-
* @param {"manual"|"endpoint"} source - Commit source path
|
|
810
|
-
* @param {string|null} sessionKey - Active relay session key for diagnostics
|
|
811
|
-
* @returns {string} JSON string
|
|
812
|
-
*/
|
|
813
784
|
function formatListenCommitted(text, source, sessionKey) {
|
|
814
785
|
return JSON.stringify({
|
|
815
786
|
type: "listen-committed",
|
|
@@ -826,20 +797,10 @@ function createDownstreamHandler(opts) {
|
|
|
826
797
|
});
|
|
827
798
|
}
|
|
828
799
|
|
|
829
|
-
/**
|
|
830
|
-
* Format a listen-ended message for broadcast.
|
|
831
|
-
* @returns {string} JSON string
|
|
832
|
-
*/
|
|
833
800
|
function formatListenEnded() {
|
|
834
801
|
return JSON.stringify({ type: "listen-ended" });
|
|
835
802
|
}
|
|
836
803
|
|
|
837
|
-
/**
|
|
838
|
-
* Format a listen-error message for broadcast.
|
|
839
|
-
* @param {string} error - Error description
|
|
840
|
-
* @param {string|null} [code] - Structured error code
|
|
841
|
-
* @returns {string} JSON string
|
|
842
|
-
*/
|
|
843
804
|
function formatListenError(error, code = null) {
|
|
844
805
|
const msg = { type: "listen-error", error };
|
|
845
806
|
if (typeof code === "string" && code.trim()) {
|
|
@@ -848,20 +809,10 @@ function createDownstreamHandler(opts) {
|
|
|
848
809
|
return JSON.stringify(msg);
|
|
849
810
|
}
|
|
850
811
|
|
|
851
|
-
/**
|
|
852
|
-
* Format a listen-ready message for broadcast (agent done, client can auto-restart).
|
|
853
|
-
* @returns {string} JSON string
|
|
854
|
-
*/
|
|
855
812
|
function formatListenReady() {
|
|
856
813
|
return JSON.stringify({ type: "listen-ready" });
|
|
857
814
|
}
|
|
858
815
|
|
|
859
|
-
/**
|
|
860
|
-
* Format a temporary Soniox key lease for unicast back to the requesting client.
|
|
861
|
-
*
|
|
862
|
-
* @param {{voiceSessionId: string, temporaryKey: string, expiresAtMs: number}} payload
|
|
863
|
-
* @returns {string}
|
|
864
|
-
*/
|
|
865
816
|
function formatSonioxTemporaryKey(payload) {
|
|
866
817
|
return JSON.stringify({
|
|
867
818
|
type: APP_PROTOCOL.sonioxTemporaryKey,
|
|
@@ -880,12 +831,6 @@ function createDownstreamHandler(opts) {
|
|
|
880
831
|
});
|
|
881
832
|
}
|
|
882
833
|
|
|
883
|
-
/**
|
|
884
|
-
* Format a Soniox temporary-key failure for unicast back to the requesting client.
|
|
885
|
-
*
|
|
886
|
-
* @param {{voiceSessionId: string, error: string, code?: string|null}} payload
|
|
887
|
-
* @returns {string}
|
|
888
|
-
*/
|
|
889
834
|
function formatSonioxTemporaryKeyError(payload) {
|
|
890
835
|
const msg = {
|
|
891
836
|
type: APP_PROTOCOL.sonioxTemporaryKeyError,
|
|
@@ -908,6 +853,88 @@ function createDownstreamHandler(opts) {
|
|
|
908
853
|
return JSON.stringify(msg);
|
|
909
854
|
}
|
|
910
855
|
|
|
856
|
+
function parseRequestCartesiaAccessToken(msg) {
|
|
857
|
+
if (!msg || typeof msg !== "object") {
|
|
858
|
+
throw new Error("requestCartesiaAccessToken payload must be an object");
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const voiceSessionId = parseOptionalTrimmedString(msg.voiceSessionId);
|
|
862
|
+
if (!voiceSessionId) {
|
|
863
|
+
throw new Error("voiceSessionId is required");
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return {
|
|
867
|
+
voiceSessionId,
|
|
868
|
+
sessionKey: parseOptionalTrimmedString(msg.sessionKey),
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function normalizeCartesiaAccessTokenErrorCode(err) {
|
|
873
|
+
|
|
874
|
+
const explicit = err && typeof err.code === "string" ? err.code.trim() : "";
|
|
875
|
+
if (explicit) return explicit;
|
|
876
|
+
|
|
877
|
+
const message =
|
|
878
|
+
err && typeof err.message === "string" && err.message.trim()
|
|
879
|
+
? err.message.trim()
|
|
880
|
+
: "";
|
|
881
|
+
const lowered = message.toLowerCase();
|
|
882
|
+
if (!message) return "cartesia_access_token_failed";
|
|
883
|
+
|
|
884
|
+
if (err && err.name === "AbortError") {
|
|
885
|
+
return "cartesia_access_token_mint_timeout";
|
|
886
|
+
}
|
|
887
|
+
if (lowered.includes("api key is not configured")) {
|
|
888
|
+
return "cartesia_access_token_not_configured";
|
|
889
|
+
}
|
|
890
|
+
if (lowered.includes("fetch is not available")) {
|
|
891
|
+
return "cartesia_access_token_fetch_unavailable";
|
|
892
|
+
}
|
|
893
|
+
if (lowered.includes("voicesessionid is required")) {
|
|
894
|
+
return "cartesia_access_token_invalid_request";
|
|
895
|
+
}
|
|
896
|
+
if (lowered.includes("missing token")) {
|
|
897
|
+
return "cartesia_access_token_invalid_response";
|
|
898
|
+
}
|
|
899
|
+
const statusMatch = lowered.match(/\((\d{3})\)/);
|
|
900
|
+
if (statusMatch) {
|
|
901
|
+
return `cartesia_access_token_http_${statusMatch[1]}`;
|
|
902
|
+
}
|
|
903
|
+
return "cartesia_access_token_failed";
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function formatCartesiaAccessToken(payload) {
|
|
907
|
+
return JSON.stringify({
|
|
908
|
+
type: APP_PROTOCOL.cartesiaAccessToken,
|
|
909
|
+
voiceSessionId:
|
|
910
|
+
payload && typeof payload.voiceSessionId === "string" ? payload.voiceSessionId : "",
|
|
911
|
+
accessToken:
|
|
912
|
+
payload && typeof payload.accessToken === "string" ? payload.accessToken : "",
|
|
913
|
+
expiresAtMs:
|
|
914
|
+
payload && Number.isFinite(payload.expiresAtMs) ? Math.floor(payload.expiresAtMs) : 0,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function formatCartesiaAccessTokenError(payload) {
|
|
919
|
+
const msg = {
|
|
920
|
+
type: APP_PROTOCOL.cartesiaAccessTokenError,
|
|
921
|
+
voiceSessionId:
|
|
922
|
+
payload && typeof payload.voiceSessionId === "string" ? payload.voiceSessionId : "",
|
|
923
|
+
error:
|
|
924
|
+
payload && typeof payload.error === "string" && payload.error.trim()
|
|
925
|
+
? payload.error.trim()
|
|
926
|
+
: "Cartesia access-token request failed",
|
|
927
|
+
};
|
|
928
|
+
const code =
|
|
929
|
+
payload && typeof payload.code === "string" && payload.code.trim()
|
|
930
|
+
? payload.code.trim()
|
|
931
|
+
: "";
|
|
932
|
+
if (code) {
|
|
933
|
+
msg.code = code;
|
|
934
|
+
}
|
|
935
|
+
return JSON.stringify(msg);
|
|
936
|
+
}
|
|
937
|
+
|
|
911
938
|
function normalizeSonioxTemporaryKeyErrorCode(err) {
|
|
912
939
|
const message =
|
|
913
940
|
err && typeof err.message === "string" && err.message.trim()
|
|
@@ -915,7 +942,7 @@ function createDownstreamHandler(opts) {
|
|
|
915
942
|
: "";
|
|
916
943
|
const lowered = message.toLowerCase();
|
|
917
944
|
if (!message) return "soniox_temp_key_request_failed";
|
|
918
|
-
|
|
945
|
+
|
|
919
946
|
if (err && err.name === "AbortError") {
|
|
920
947
|
return "soniox_temp_key_mint_timeout";
|
|
921
948
|
}
|
|
@@ -941,12 +968,6 @@ function createDownstreamHandler(opts) {
|
|
|
941
968
|
return "soniox_temp_key_request_failed";
|
|
942
969
|
}
|
|
943
970
|
|
|
944
|
-
/**
|
|
945
|
-
* Format a debug-set response for unicast.
|
|
946
|
-
*
|
|
947
|
-
* @param {object} data
|
|
948
|
-
* @returns {string}
|
|
949
|
-
*/
|
|
950
971
|
function formatDebugSet(data) {
|
|
951
972
|
return JSON.stringify({
|
|
952
973
|
type: "debug-set",
|
|
@@ -954,12 +975,6 @@ function createDownstreamHandler(opts) {
|
|
|
954
975
|
});
|
|
955
976
|
}
|
|
956
977
|
|
|
957
|
-
/**
|
|
958
|
-
* Format a debug-dump response for unicast.
|
|
959
|
-
*
|
|
960
|
-
* @param {object} data
|
|
961
|
-
* @returns {string}
|
|
962
|
-
*/
|
|
963
978
|
function formatDebugDump(data) {
|
|
964
979
|
return JSON.stringify({
|
|
965
980
|
type: "debug-dump",
|
|
@@ -967,10 +982,6 @@ function createDownstreamHandler(opts) {
|
|
|
967
982
|
});
|
|
968
983
|
}
|
|
969
984
|
|
|
970
|
-
/**
|
|
971
|
-
* Parse a "trace-log-set" message.
|
|
972
|
-
* @returns {{enabled: boolean}}
|
|
973
|
-
*/
|
|
974
985
|
function parseTraceLogSet(msg) {
|
|
975
986
|
if (!msg || typeof msg !== "object") {
|
|
976
987
|
throw new Error("trace-log-set requires an object");
|
|
@@ -981,19 +992,10 @@ function createDownstreamHandler(opts) {
|
|
|
981
992
|
return { enabled: msg.enabled };
|
|
982
993
|
}
|
|
983
994
|
|
|
984
|
-
/**
|
|
985
|
-
* Format a trace-log response for unicast (set + get share one type).
|
|
986
|
-
*/
|
|
987
995
|
function formatTraceLog(data) {
|
|
988
996
|
return JSON.stringify({ type: "trace-log", ...data });
|
|
989
997
|
}
|
|
990
998
|
|
|
991
|
-
/**
|
|
992
|
-
* Format the current relay debug-config snapshot for app/WebUI clients.
|
|
993
|
-
*
|
|
994
|
-
* @param {{serverNowMs: number, enabled: Array<{cat: string, expiresAtMs: number}>}} data
|
|
995
|
-
* @returns {string}
|
|
996
|
-
*/
|
|
997
999
|
function formatDebugConfigSnapshot(data) {
|
|
998
1000
|
const enabled = Array.isArray(data && data.enabled)
|
|
999
1001
|
? data.enabled
|
|
@@ -1019,12 +1021,6 @@ function createDownstreamHandler(opts) {
|
|
|
1019
1021
|
});
|
|
1020
1022
|
}
|
|
1021
1023
|
|
|
1022
|
-
/**
|
|
1023
|
-
* Format a remote-control command for broadcast to app clients.
|
|
1024
|
-
*
|
|
1025
|
-
* @param {object} data
|
|
1026
|
-
* @returns {string}
|
|
1027
|
-
*/
|
|
1028
1024
|
function formatRemoteControl(data) {
|
|
1029
1025
|
return JSON.stringify({
|
|
1030
1026
|
type: APP_PROTOCOL.remoteControl,
|
|
@@ -1032,12 +1028,6 @@ function createDownstreamHandler(opts) {
|
|
|
1032
1028
|
});
|
|
1033
1029
|
}
|
|
1034
1030
|
|
|
1035
|
-
/**
|
|
1036
|
-
* Format a remote-control acknowledgement for unicast.
|
|
1037
|
-
*
|
|
1038
|
-
* @param {object} data
|
|
1039
|
-
* @returns {string}
|
|
1040
|
-
*/
|
|
1041
1031
|
function formatRemoteControlAck(data) {
|
|
1042
1032
|
return JSON.stringify({
|
|
1043
1033
|
type: "remote-control-ack",
|
|
@@ -1626,6 +1616,13 @@ function createDownstreamHandler(opts) {
|
|
|
1626
1616
|
payload.defaultFastMode = msg.defaultFastMode;
|
|
1627
1617
|
}
|
|
1628
1618
|
|
|
1619
|
+
if (Object.prototype.hasOwnProperty.call(msg, "defaultAgent")) {
|
|
1620
|
+
if (typeof msg.defaultAgent !== "string") {
|
|
1621
|
+
throw new Error("defaultAgent must be a string");
|
|
1622
|
+
}
|
|
1623
|
+
payload.defaultAgent = normalizeEvenAiDefaultAgent(msg.defaultAgent);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1629
1626
|
if (Object.keys(payload).length === 0) {
|
|
1630
1627
|
throw new Error("setEvenAiSettings requires at least one field");
|
|
1631
1628
|
}
|
|
@@ -1668,6 +1665,13 @@ function createDownstreamHandler(opts) {
|
|
|
1668
1665
|
payload.defaultFastMode = msg.defaultFastMode;
|
|
1669
1666
|
}
|
|
1670
1667
|
|
|
1668
|
+
if (Object.prototype.hasOwnProperty.call(msg, "defaultAgent")) {
|
|
1669
|
+
if (typeof msg.defaultAgent !== "string") {
|
|
1670
|
+
throw new Error("defaultAgent must be a string");
|
|
1671
|
+
}
|
|
1672
|
+
payload.defaultAgent = normalizeOcuClawDefaultAgent(msg.defaultAgent);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1671
1675
|
if (Object.keys(payload).length === 0) {
|
|
1672
1676
|
throw new Error("setOcuClawSettings requires at least one field");
|
|
1673
1677
|
}
|
|
@@ -2006,11 +2010,6 @@ function createDownstreamHandler(opts) {
|
|
|
2006
2010
|
};
|
|
2007
2011
|
}
|
|
2008
2012
|
|
|
2009
|
-
/**
|
|
2010
|
-
* Validate and normalize an inbound clientDisplaySignals envelope.
|
|
2011
|
-
* Returns null if absent or unparseable. Falls back to safe defaults
|
|
2012
|
-
* for individual fields rather than rejecting the whole envelope.
|
|
2013
|
-
*/
|
|
2014
2013
|
function parseClientDisplaySignals(raw) {
|
|
2015
2014
|
if (raw == null || typeof raw !== "object") return null;
|
|
2016
2015
|
const coerceState = (val) => {
|
|
@@ -2024,7 +2023,7 @@ function createDownstreamHandler(opts) {
|
|
|
2024
2023
|
const enabledRaw = raw.neuralSessionNamesEnabled;
|
|
2025
2024
|
const neuralSessionNamesEnabled =
|
|
2026
2025
|
typeof enabledRaw === "boolean" ? enabledRaw : true;
|
|
2027
|
-
|
|
2026
|
+
|
|
2028
2027
|
return {
|
|
2029
2028
|
neuralEmojiReactorState: state,
|
|
2030
2029
|
neuralPaceModulatorState: paceState,
|
|
@@ -2032,15 +2031,6 @@ function createDownstreamHandler(opts) {
|
|
|
2032
2031
|
};
|
|
2033
2032
|
}
|
|
2034
2033
|
|
|
2035
|
-
// --- Message handlers ---
|
|
2036
|
-
|
|
2037
|
-
/**
|
|
2038
|
-
* Handle a "send" message: forward user text to upstream agent.
|
|
2039
|
-
*
|
|
2040
|
-
* @param {string} clientId
|
|
2041
|
-
* @param {object} msg - Parsed message with id, text, sessionKey, and optional attachment
|
|
2042
|
-
* @returns {{ unicast: string }|Promise<{ unicast: string }>}
|
|
2043
|
-
*/
|
|
2044
2034
|
function handleSend(clientId, msg) {
|
|
2045
2035
|
const requestId = parseOptionalTrimmedString(msg.requestId);
|
|
2046
2036
|
if (!requestId) {
|
|
@@ -2076,7 +2066,6 @@ function createDownstreamHandler(opts) {
|
|
|
2076
2066
|
};
|
|
2077
2067
|
}
|
|
2078
2068
|
|
|
2079
|
-
// Check upstream connectivity
|
|
2080
2069
|
if (!isUpstreamConnected()) {
|
|
2081
2070
|
return {
|
|
2082
2071
|
unicast: formatSendAckCompat(
|
|
@@ -2105,7 +2094,6 @@ function createDownstreamHandler(opts) {
|
|
|
2105
2094
|
|
|
2106
2095
|
const clientDisplaySignals = parseClientDisplaySignals(msg.clientDisplaySignals);
|
|
2107
2096
|
|
|
2108
|
-
// Forward to upstream — resolves on initial ack (accepted/queued)
|
|
2109
2097
|
const followup = onSend(
|
|
2110
2098
|
requestId,
|
|
2111
2099
|
text,
|
|
@@ -2115,9 +2103,18 @@ function createDownstreamHandler(opts) {
|
|
|
2115
2103
|
).then(
|
|
2116
2104
|
(result) => {
|
|
2117
2105
|
const status = (result && result.status) || "accepted";
|
|
2118
|
-
const frame = formatSendAckCompat(
|
|
2106
|
+
const frame = formatSendAckCompat(
|
|
2107
|
+
requestId,
|
|
2108
|
+
status,
|
|
2109
|
+
undefined,
|
|
2110
|
+
undefined,
|
|
2111
|
+
{ runId: result && result.runId },
|
|
2112
|
+
);
|
|
2119
2113
|
if (operation && typeof operation.complete === "function") {
|
|
2120
|
-
operation.complete(frame, {
|
|
2114
|
+
operation.complete(frame, {
|
|
2115
|
+
status,
|
|
2116
|
+
runId: result && result.runId ? result.runId : null,
|
|
2117
|
+
});
|
|
2121
2118
|
}
|
|
2122
2119
|
return { unicast: frame };
|
|
2123
2120
|
},
|
|
@@ -2149,13 +2146,149 @@ function createDownstreamHandler(opts) {
|
|
|
2149
2146
|
: followup;
|
|
2150
2147
|
}
|
|
2151
2148
|
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2149
|
+
function handleAbortSession(clientId, msg) {
|
|
2150
|
+
const requestId = parseOptionalTrimmedString(msg.requestId);
|
|
2151
|
+
if (!requestId) {
|
|
2152
|
+
return {
|
|
2153
|
+
unicast: formatSessionAbortAck({
|
|
2154
|
+
requestId,
|
|
2155
|
+
status: "rejected",
|
|
2156
|
+
error: "Missing required field: requestId",
|
|
2157
|
+
}),
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
const sessionKey = parseOptionalTrimmedString(msg.sessionKey);
|
|
2161
|
+
if (!sessionKey) {
|
|
2162
|
+
return {
|
|
2163
|
+
unicast: formatSessionAbortAck({
|
|
2164
|
+
requestId,
|
|
2165
|
+
status: "rejected",
|
|
2166
|
+
error: "Missing required field: sessionKey",
|
|
2167
|
+
}),
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
if (!onAbortSession) {
|
|
2171
|
+
return {
|
|
2172
|
+
unicast: formatSessionAbortAck({
|
|
2173
|
+
requestId,
|
|
2174
|
+
status: "rejected",
|
|
2175
|
+
error: "session abort is not available",
|
|
2176
|
+
}),
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
if (!isUpstreamConnected()) {
|
|
2180
|
+
return {
|
|
2181
|
+
unicast: formatSessionAbortAck({
|
|
2182
|
+
requestId,
|
|
2183
|
+
status: "rejected",
|
|
2184
|
+
error: "OpenClaw disconnected",
|
|
2185
|
+
}),
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
return Promise.resolve(onAbortSession({ requestId, sessionKey })).then(
|
|
2189
|
+
(result) => ({
|
|
2190
|
+
unicast: formatSessionAbortAck({
|
|
2191
|
+
requestId,
|
|
2192
|
+
...(result || { status: "accepted" }),
|
|
2193
|
+
}),
|
|
2194
|
+
}),
|
|
2195
|
+
(err) => ({
|
|
2196
|
+
unicast: formatSessionAbortAck({
|
|
2197
|
+
requestId,
|
|
2198
|
+
status: "rejected",
|
|
2199
|
+
error: err && err.message ? err.message : "session abort failed",
|
|
2200
|
+
errorCode: err && (err.errorCode || err.code) ? (err.errorCode || err.code) : undefined,
|
|
2201
|
+
}),
|
|
2202
|
+
}),
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function handleSteerSession(clientId, msg) {
|
|
2207
|
+
const requestId = parseOptionalTrimmedString(msg.requestId);
|
|
2208
|
+
if (!requestId) {
|
|
2209
|
+
return {
|
|
2210
|
+
unicast: formatSendAckCompat(
|
|
2211
|
+
requestId,
|
|
2212
|
+
"rejected",
|
|
2213
|
+
"Missing required field: requestId",
|
|
2214
|
+
),
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
const sessionKey = parseOptionalTrimmedString(msg.sessionKey);
|
|
2218
|
+
if (!sessionKey) {
|
|
2219
|
+
return {
|
|
2220
|
+
unicast: formatSendAckCompat(
|
|
2221
|
+
requestId,
|
|
2222
|
+
"rejected",
|
|
2223
|
+
"Missing required field: sessionKey",
|
|
2224
|
+
),
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
const parsedAttachment = parseAttachment(msg.attachment);
|
|
2228
|
+
if (!parsedAttachment.ok) {
|
|
2229
|
+
return {
|
|
2230
|
+
unicast: formatSendAckCompat(
|
|
2231
|
+
requestId,
|
|
2232
|
+
"rejected",
|
|
2233
|
+
parsedAttachment.error,
|
|
2234
|
+
parsedAttachment.errorCode,
|
|
2235
|
+
),
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
const message = typeof msg.message === "string" ? msg.message : "";
|
|
2239
|
+
if (!message.trim() && !parsedAttachment.attachment) {
|
|
2240
|
+
return {
|
|
2241
|
+
unicast: formatSendAckCompat(
|
|
2242
|
+
requestId,
|
|
2243
|
+
"rejected",
|
|
2244
|
+
"Missing required field: message",
|
|
2245
|
+
),
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
if (!onSteerSession) {
|
|
2249
|
+
return {
|
|
2250
|
+
unicast: formatSendAckCompat(
|
|
2251
|
+
requestId,
|
|
2252
|
+
"rejected",
|
|
2253
|
+
"session steer is not available",
|
|
2254
|
+
),
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
if (!isUpstreamConnected()) {
|
|
2258
|
+
return {
|
|
2259
|
+
unicast: formatSendAckCompat(
|
|
2260
|
+
requestId,
|
|
2261
|
+
"rejected",
|
|
2262
|
+
"OpenClaw disconnected",
|
|
2263
|
+
),
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
return Promise.resolve(onSteerSession({
|
|
2267
|
+
requestId,
|
|
2268
|
+
sessionKey,
|
|
2269
|
+
message,
|
|
2270
|
+
attachment: parsedAttachment.attachment,
|
|
2271
|
+
})).then(
|
|
2272
|
+
(result) => ({
|
|
2273
|
+
unicast: formatSendAckCompat(
|
|
2274
|
+
requestId,
|
|
2275
|
+
(result && result.status) || "accepted",
|
|
2276
|
+
undefined,
|
|
2277
|
+
undefined,
|
|
2278
|
+
{ runId: result && result.runId },
|
|
2279
|
+
),
|
|
2280
|
+
}),
|
|
2281
|
+
(err) => ({
|
|
2282
|
+
unicast: formatSendAckCompat(
|
|
2283
|
+
requestId,
|
|
2284
|
+
"rejected",
|
|
2285
|
+
err && err.message ? err.message : "session steer failed",
|
|
2286
|
+
err && (err.errorCode || err.code) ? (err.errorCode || err.code) : undefined,
|
|
2287
|
+
),
|
|
2288
|
+
}),
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2159
2292
|
function handleSimulate(clientId, msg) {
|
|
2160
2293
|
const pages = onSimulate(
|
|
2161
2294
|
msg.sender || "Simulator",
|
|
@@ -2164,13 +2297,6 @@ function createDownstreamHandler(opts) {
|
|
|
2164
2297
|
return { broadcast: formatPages(pages) };
|
|
2165
2298
|
}
|
|
2166
2299
|
|
|
2167
|
-
/**
|
|
2168
|
-
* Handle a "simulateStream" message: relay-local deterministic stream replay.
|
|
2169
|
-
*
|
|
2170
|
-
* @param {string} clientId
|
|
2171
|
-
* @param {object} msg - Parsed message with id, text, sender, and timing controls.
|
|
2172
|
-
* @returns {{ unicast: string }|Promise<{ unicast: string }>}
|
|
2173
|
-
*/
|
|
2174
2300
|
function handleSimulateStream(clientId, msg) {
|
|
2175
2301
|
const id = parseOptionalTrimmedString(msg.id);
|
|
2176
2302
|
if (!id) {
|
|
@@ -2250,24 +2376,11 @@ function createDownstreamHandler(opts) {
|
|
|
2250
2376
|
);
|
|
2251
2377
|
}
|
|
2252
2378
|
|
|
2253
|
-
/**
|
|
2254
|
-
* Handle a "subscribeProtocol" message: mark client for protocol forwarding.
|
|
2255
|
-
*
|
|
2256
|
-
* @param {string} clientId
|
|
2257
|
-
* @returns {null}
|
|
2258
|
-
*/
|
|
2259
2379
|
function handleSubscribeProtocol(clientId) {
|
|
2260
2380
|
protocolSubscribers.add(clientId);
|
|
2261
2381
|
return null;
|
|
2262
2382
|
}
|
|
2263
2383
|
|
|
2264
|
-
/**
|
|
2265
|
-
* Handle an "approvalResponse" message: forward decision to upstream gateway.
|
|
2266
|
-
*
|
|
2267
|
-
* @param {string} clientId
|
|
2268
|
-
* @param {object} msg - Parsed message with id and decision
|
|
2269
|
-
* @returns {{ unicast: string }|Promise<{ unicast: string }>}
|
|
2270
|
-
*/
|
|
2271
2384
|
function handleApprovalResponse(clientId, msg) {
|
|
2272
2385
|
let payload;
|
|
2273
2386
|
try {
|
|
@@ -2373,12 +2486,6 @@ function createDownstreamHandler(opts) {
|
|
|
2373
2486
|
});
|
|
2374
2487
|
}
|
|
2375
2488
|
|
|
2376
|
-
/**
|
|
2377
|
-
* Handle a "newChat" message: clear conversation and reset the session.
|
|
2378
|
-
*
|
|
2379
|
-
* @param {string} clientId
|
|
2380
|
-
* @returns {Promise<{ broadcast: string }>}
|
|
2381
|
-
*/
|
|
2382
2489
|
function handleNewChat(clientId) {
|
|
2383
2490
|
return onNewChat().then(
|
|
2384
2491
|
(pages) => ({ broadcast: formatPages(pages) }),
|
|
@@ -2389,12 +2496,6 @@ function createDownstreamHandler(opts) {
|
|
|
2389
2496
|
);
|
|
2390
2497
|
}
|
|
2391
2498
|
|
|
2392
|
-
/**
|
|
2393
|
-
* Handle a "getSessions" message: fetch session list from upstream.
|
|
2394
|
-
*
|
|
2395
|
-
* @param {string} clientId
|
|
2396
|
-
* @returns {Promise<{ unicast: string }>}
|
|
2397
|
-
*/
|
|
2398
2499
|
function handleGetSessions(clientId) {
|
|
2399
2500
|
return onGetSessions().then(
|
|
2400
2501
|
(sessions) => ({ unicast: formatSessions(sessions) }),
|
|
@@ -2405,12 +2506,46 @@ function createDownstreamHandler(opts) {
|
|
|
2405
2506
|
);
|
|
2406
2507
|
}
|
|
2407
2508
|
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2509
|
+
function handleGetSessionDiff(clientId, msg) {
|
|
2510
|
+
const kind = normalizeSessionDiffKind(msg && msg.kind);
|
|
2511
|
+
const known = parseKnownSessionRows(msg);
|
|
2512
|
+
const limit = normalizeSessionDiffLimit(msg && msg.limit);
|
|
2513
|
+
if (kind === "evenai") {
|
|
2514
|
+
if (!onGetEvenAiSessions) {
|
|
2515
|
+
return Promise.resolve({
|
|
2516
|
+
unicast: formatEmptySessionDiff(kind, limit, "ocuclaw:even-ai"),
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
return Promise.resolve(onGetEvenAiSessions()).then(
|
|
2520
|
+
(payload) => ({
|
|
2521
|
+
unicast: formatSessionDiff({
|
|
2522
|
+
kind,
|
|
2523
|
+
sessions: payload && payload.sessions,
|
|
2524
|
+
known,
|
|
2525
|
+
limit,
|
|
2526
|
+
dedicatedKey:
|
|
2527
|
+
payload && typeof payload.dedicatedKey === "string"
|
|
2528
|
+
? payload.dedicatedKey
|
|
2529
|
+
: "ocuclaw:even-ai",
|
|
2530
|
+
}),
|
|
2531
|
+
}),
|
|
2532
|
+
(err) => {
|
|
2533
|
+
logger.error(`[downstream] getEvenAiSessionDiff failed: ${err.message}`);
|
|
2534
|
+
return { unicast: formatEmptySessionDiff(kind, limit, "ocuclaw:even-ai") };
|
|
2535
|
+
},
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
return onGetSessions().then(
|
|
2539
|
+
(sessions) => ({
|
|
2540
|
+
unicast: formatSessionDiff({ kind, sessions, known, limit }),
|
|
2541
|
+
}),
|
|
2542
|
+
(err) => {
|
|
2543
|
+
logger.error(`[downstream] getSessionDiff failed: ${err.message}`);
|
|
2544
|
+
return { unicast: formatEmptySessionDiff(kind, limit) };
|
|
2545
|
+
},
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2414
2549
|
function handleGetModelsCatalog(clientId) {
|
|
2415
2550
|
if (!onGetModelsCatalog) {
|
|
2416
2551
|
return {
|
|
@@ -2438,12 +2573,6 @@ function createDownstreamHandler(opts) {
|
|
|
2438
2573
|
);
|
|
2439
2574
|
}
|
|
2440
2575
|
|
|
2441
|
-
/**
|
|
2442
|
-
* Handle "getSkills": return cached skills catalog snapshot.
|
|
2443
|
-
*
|
|
2444
|
-
* @param {string} clientId
|
|
2445
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2446
|
-
*/
|
|
2447
2576
|
function handleGetSkillsCatalog(clientId) {
|
|
2448
2577
|
if (!onGetSkillsCatalog) {
|
|
2449
2578
|
return {
|
|
@@ -2471,12 +2600,34 @@ function createDownstreamHandler(opts) {
|
|
|
2471
2600
|
);
|
|
2472
2601
|
}
|
|
2473
2602
|
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2603
|
+
function handleGetAgentsCatalog(clientId) {
|
|
2604
|
+
if (!onGetAgentsCatalog) {
|
|
2605
|
+
return {
|
|
2606
|
+
unicast: formatAgentsCatalog({
|
|
2607
|
+
agents: [],
|
|
2608
|
+
fetchedAtMs: Date.now(),
|
|
2609
|
+
stale: true,
|
|
2610
|
+
unsupported: true,
|
|
2611
|
+
}),
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
return Promise.resolve(onGetAgentsCatalog()).then(
|
|
2615
|
+
(payload) => ({
|
|
2616
|
+
unicast: formatAgentsCatalog(payload || {}),
|
|
2617
|
+
}),
|
|
2618
|
+
(err) => {
|
|
2619
|
+
logger.error(`[downstream] getAgentsCatalog failed: ${err.message}`);
|
|
2620
|
+
return {
|
|
2621
|
+
unicast: formatAgentsCatalog({
|
|
2622
|
+
agents: [],
|
|
2623
|
+
fetchedAtMs: Date.now(),
|
|
2624
|
+
stale: true,
|
|
2625
|
+
}),
|
|
2626
|
+
};
|
|
2627
|
+
},
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2480
2631
|
function handleGetSonioxModels(clientId) {
|
|
2481
2632
|
if (!onGetSonioxModels) {
|
|
2482
2633
|
return {
|
|
@@ -2504,12 +2655,6 @@ function createDownstreamHandler(opts) {
|
|
|
2504
2655
|
);
|
|
2505
2656
|
}
|
|
2506
2657
|
|
|
2507
|
-
/**
|
|
2508
|
-
* Handle "getProviderUsageSnapshot": return the current provider usage snapshot.
|
|
2509
|
-
*
|
|
2510
|
-
* @param {string} clientId
|
|
2511
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2512
|
-
*/
|
|
2513
2658
|
function handleGetProviderUsageSnapshot(clientId) {
|
|
2514
2659
|
const emptySnapshot = () => ({
|
|
2515
2660
|
sessionKey: null,
|
|
@@ -2539,12 +2684,6 @@ function createDownstreamHandler(opts) {
|
|
|
2539
2684
|
);
|
|
2540
2685
|
}
|
|
2541
2686
|
|
|
2542
|
-
/**
|
|
2543
|
-
* Handle "getStatus": return the current status snapshot.
|
|
2544
|
-
*
|
|
2545
|
-
* @param {string} clientId
|
|
2546
|
-
* @returns {{ unicast: string }}
|
|
2547
|
-
*/
|
|
2548
2687
|
function handleGetStatus(clientId) {
|
|
2549
2688
|
if (!onGetStatus) {
|
|
2550
2689
|
return { unicast: formatError("getStatus is not available") };
|
|
@@ -2558,12 +2697,6 @@ function createDownstreamHandler(opts) {
|
|
|
2558
2697
|
}
|
|
2559
2698
|
}
|
|
2560
2699
|
|
|
2561
|
-
/**
|
|
2562
|
-
* Handle "getSessionModelConfig": read controls for current session key.
|
|
2563
|
-
*
|
|
2564
|
-
* @param {string} clientId
|
|
2565
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2566
|
-
*/
|
|
2567
2700
|
function handleGetSessionModelConfig(clientId) {
|
|
2568
2701
|
if (!onGetSessionModelConfig) {
|
|
2569
2702
|
return { unicast: formatError("getSessionModelConfig is not available") };
|
|
@@ -2579,13 +2712,66 @@ function createDownstreamHandler(opts) {
|
|
|
2579
2712
|
);
|
|
2580
2713
|
}
|
|
2581
2714
|
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2715
|
+
function formatSessionAgentAck(payload) {
|
|
2716
|
+
const out = {
|
|
2717
|
+
type: APP_PROTOCOL.sessionAgentSetAck,
|
|
2718
|
+
status:
|
|
2719
|
+
payload && typeof payload.status === "string"
|
|
2720
|
+
? payload.status
|
|
2721
|
+
: "rejected",
|
|
2722
|
+
};
|
|
2723
|
+
if (payload && payload.error !== undefined) {
|
|
2724
|
+
out.error = payload.error;
|
|
2725
|
+
}
|
|
2726
|
+
return JSON.stringify(out);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
function parseSetSessionAgent(msg) {
|
|
2730
|
+
if (!msg || typeof msg !== "object") {
|
|
2731
|
+
throw new Error("setSessionAgent payload must be an object");
|
|
2732
|
+
}
|
|
2733
|
+
if (!Object.prototype.hasOwnProperty.call(msg, "agentId")) {
|
|
2734
|
+
throw new Error("setSessionAgent requires an agentId field");
|
|
2735
|
+
}
|
|
2736
|
+
if (msg.agentId !== null && typeof msg.agentId !== "string") {
|
|
2737
|
+
throw new Error("agentId must be a string or null");
|
|
2738
|
+
}
|
|
2739
|
+
return { agentId: typeof msg.agentId === "string" ? msg.agentId.trim() : "" };
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
function handleSetSessionAgent(clientId, msg) {
|
|
2743
|
+
if (!onSetSessionAgent) {
|
|
2744
|
+
return {
|
|
2745
|
+
unicast: formatSessionAgentAck({
|
|
2746
|
+
status: "rejected",
|
|
2747
|
+
error: "setSessionAgent is not available",
|
|
2748
|
+
}),
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
let payload;
|
|
2752
|
+
try {
|
|
2753
|
+
payload = parseSetSessionAgent(msg);
|
|
2754
|
+
} catch (err) {
|
|
2755
|
+
return {
|
|
2756
|
+
unicast: formatSessionAgentAck({
|
|
2757
|
+
status: "rejected",
|
|
2758
|
+
error: err && err.message ? err.message : "invalid setSessionAgent payload",
|
|
2759
|
+
}),
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
return Promise.resolve(onSetSessionAgent(payload)).then(
|
|
2763
|
+
(result) => ({
|
|
2764
|
+
unicast: formatSessionAgentAck(result || { status: "accepted" }),
|
|
2765
|
+
}),
|
|
2766
|
+
(err) => ({
|
|
2767
|
+
unicast: formatSessionAgentAck({
|
|
2768
|
+
status: "rejected",
|
|
2769
|
+
error: err && err.message ? err.message : "setSessionAgent failed",
|
|
2770
|
+
}),
|
|
2771
|
+
}),
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2589
2775
|
function handleSetSessionModelConfig(clientId, msg) {
|
|
2590
2776
|
if (!onSetSessionModelConfig) {
|
|
2591
2777
|
return {
|
|
@@ -2623,13 +2809,6 @@ function createDownstreamHandler(opts) {
|
|
|
2623
2809
|
);
|
|
2624
2810
|
}
|
|
2625
2811
|
|
|
2626
|
-
/**
|
|
2627
|
-
* Handle "compactSession": trigger gateway-side compaction for a session key.
|
|
2628
|
-
*
|
|
2629
|
-
* @param {string} clientId
|
|
2630
|
-
* @param {object} msg - { sessionKey, requestId }
|
|
2631
|
-
* @returns {Promise<{ unicast: string }>}
|
|
2632
|
-
*/
|
|
2633
2812
|
function handleCompactSession(clientId, msg) {
|
|
2634
2813
|
if (!onCompactSession) {
|
|
2635
2814
|
return Promise.resolve({
|
|
@@ -2670,12 +2849,6 @@ function createDownstreamHandler(opts) {
|
|
|
2670
2849
|
);
|
|
2671
2850
|
}
|
|
2672
2851
|
|
|
2673
|
-
/**
|
|
2674
|
-
* Handle "getEvenAiSettings": read relay-owned Even AI settings.
|
|
2675
|
-
*
|
|
2676
|
-
* @param {string} clientId
|
|
2677
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2678
|
-
*/
|
|
2679
2852
|
function handleGetEvenAiSettings(clientId) {
|
|
2680
2853
|
if (!onGetEvenAiSettings) {
|
|
2681
2854
|
return { unicast: formatError("getEvenAiSettings is not available") };
|
|
@@ -2691,12 +2864,6 @@ function createDownstreamHandler(opts) {
|
|
|
2691
2864
|
);
|
|
2692
2865
|
}
|
|
2693
2866
|
|
|
2694
|
-
/**
|
|
2695
|
-
* Handle "getEvenAiSessions": return tracked Even AI sessions.
|
|
2696
|
-
*
|
|
2697
|
-
* @param {string} clientId
|
|
2698
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2699
|
-
*/
|
|
2700
2867
|
function handleGetEvenAiSessions(clientId) {
|
|
2701
2868
|
if (!onGetEvenAiSessions) {
|
|
2702
2869
|
return { unicast: formatError("getEvenAiSessions is not available") };
|
|
@@ -2712,13 +2879,6 @@ function createDownstreamHandler(opts) {
|
|
|
2712
2879
|
);
|
|
2713
2880
|
}
|
|
2714
2881
|
|
|
2715
|
-
/**
|
|
2716
|
-
* Handle "setEvenAiSettings": patch relay-owned Even AI settings.
|
|
2717
|
-
*
|
|
2718
|
-
* @param {string} clientId
|
|
2719
|
-
* @param {object} msg
|
|
2720
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2721
|
-
*/
|
|
2722
2882
|
function handleSetEvenAiSettings(clientId, msg) {
|
|
2723
2883
|
if (!onSetEvenAiSettings) {
|
|
2724
2884
|
return {
|
|
@@ -2754,12 +2914,6 @@ function createDownstreamHandler(opts) {
|
|
|
2754
2914
|
);
|
|
2755
2915
|
}
|
|
2756
2916
|
|
|
2757
|
-
/**
|
|
2758
|
-
* Handle "getOcuClawSettings": read relay-owned OcuClaw settings.
|
|
2759
|
-
*
|
|
2760
|
-
* @param {string} clientId
|
|
2761
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2762
|
-
*/
|
|
2763
2917
|
function handleGetOcuClawSettings(clientId) {
|
|
2764
2918
|
if (!onGetOcuClawSettings) {
|
|
2765
2919
|
return { unicast: formatError("getOcuClawSettings is not available") };
|
|
@@ -2775,13 +2929,6 @@ function createDownstreamHandler(opts) {
|
|
|
2775
2929
|
);
|
|
2776
2930
|
}
|
|
2777
2931
|
|
|
2778
|
-
/**
|
|
2779
|
-
* Handle "setOcuClawSettings": patch relay-owned OcuClaw settings.
|
|
2780
|
-
*
|
|
2781
|
-
* @param {string} clientId
|
|
2782
|
-
* @param {object} msg
|
|
2783
|
-
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2784
|
-
*/
|
|
2785
2932
|
function handleSetOcuClawSettings(clientId, msg) {
|
|
2786
2933
|
if (!onSetOcuClawSettings) {
|
|
2787
2934
|
return {
|
|
@@ -2817,13 +2964,6 @@ function createDownstreamHandler(opts) {
|
|
|
2817
2964
|
);
|
|
2818
2965
|
}
|
|
2819
2966
|
|
|
2820
|
-
/**
|
|
2821
|
-
* Handle a "switchSession" message: switch to a different session.
|
|
2822
|
-
*
|
|
2823
|
-
* @param {string} clientId
|
|
2824
|
-
* @param {object} msg - Parsed message with sessionKey
|
|
2825
|
-
* @returns {Promise<{ broadcast: string[] }|null>}
|
|
2826
|
-
*/
|
|
2827
2967
|
function handleSwitchSession(clientId, msg) {
|
|
2828
2968
|
if (!msg.sessionKey) return null;
|
|
2829
2969
|
return onSwitchSession(msg.sessionKey).then(
|
|
@@ -2840,12 +2980,6 @@ function createDownstreamHandler(opts) {
|
|
|
2840
2980
|
);
|
|
2841
2981
|
}
|
|
2842
2982
|
|
|
2843
|
-
/**
|
|
2844
|
-
* Handle a "newSession" message: create a new session.
|
|
2845
|
-
*
|
|
2846
|
-
* @param {string} clientId
|
|
2847
|
-
* @returns {Promise<{ broadcast: string[] }|null>}
|
|
2848
|
-
*/
|
|
2849
2983
|
function handleNewSession(clientId) {
|
|
2850
2984
|
return onNewSession().then(
|
|
2851
2985
|
(result) => {
|
|
@@ -2865,14 +2999,6 @@ function createDownstreamHandler(opts) {
|
|
|
2865
2999
|
);
|
|
2866
3000
|
}
|
|
2867
3001
|
|
|
2868
|
-
/**
|
|
2869
|
-
* Handle a "session.title.set" message: user-edited session title from
|
|
2870
|
-
* phone UI. Sticky-locks the agent against retitling.
|
|
2871
|
-
*
|
|
2872
|
-
* @param {string} clientId
|
|
2873
|
-
* @param {object} msg - Parsed message with sessionKey and title
|
|
2874
|
-
* @returns {null}
|
|
2875
|
-
*/
|
|
2876
3002
|
function handleSetUserSessionTitle(clientId, msg) {
|
|
2877
3003
|
if (typeof onSetUserSessionTitle !== "function") return null;
|
|
2878
3004
|
const sessionKey =
|
|
@@ -2924,13 +3050,6 @@ function createDownstreamHandler(opts) {
|
|
|
2924
3050
|
return null;
|
|
2925
3051
|
}
|
|
2926
3052
|
|
|
2927
|
-
/**
|
|
2928
|
-
* Handle a "slashCommand" message: forward a slash command to OpenClaw.
|
|
2929
|
-
*
|
|
2930
|
-
* @param {string} clientId
|
|
2931
|
-
* @param {object} msg - Parsed message with command
|
|
2932
|
-
* @returns {Promise<null>}
|
|
2933
|
-
*/
|
|
2934
3053
|
function handleSlashCommand(clientId, msg) {
|
|
2935
3054
|
if (!msg.command) return null;
|
|
2936
3055
|
return onSlashCommand(msg.command).then(
|
|
@@ -2942,13 +3061,6 @@ function createDownstreamHandler(opts) {
|
|
|
2942
3061
|
);
|
|
2943
3062
|
}
|
|
2944
3063
|
|
|
2945
|
-
/**
|
|
2946
|
-
* Handle a "console" message: forward browser console output to log.
|
|
2947
|
-
*
|
|
2948
|
-
* @param {string} clientId
|
|
2949
|
-
* @param {object} msg - Parsed message with level, message
|
|
2950
|
-
* @returns {null}
|
|
2951
|
-
*/
|
|
2952
3064
|
function handleConsole(clientId, msg) {
|
|
2953
3065
|
if (onConsoleLog) {
|
|
2954
3066
|
onConsoleLog(msg.level || "log", msg.message || "");
|
|
@@ -2956,11 +3068,6 @@ function createDownstreamHandler(opts) {
|
|
|
2956
3068
|
return null;
|
|
2957
3069
|
}
|
|
2958
3070
|
|
|
2959
|
-
/**
|
|
2960
|
-
* Handle a structured "eventDebug" message.
|
|
2961
|
-
*
|
|
2962
|
-
* Legacy payloads (without cat/event) fall back to onConsoleLog.
|
|
2963
|
-
*/
|
|
2964
3071
|
function handleEventDebug(clientId, msg) {
|
|
2965
3072
|
const parsed = parseEventDebug(msg);
|
|
2966
3073
|
if (parsed && onEventDebug) {
|
|
@@ -2983,9 +3090,6 @@ function createDownstreamHandler(opts) {
|
|
|
2983
3090
|
};
|
|
2984
3091
|
}
|
|
2985
3092
|
|
|
2986
|
-
/**
|
|
2987
|
-
* Handle a "requestSonioxTemporaryKey" message.
|
|
2988
|
-
*/
|
|
2989
3093
|
function handleRequestSonioxTemporaryKey(clientId, msg) {
|
|
2990
3094
|
let payload;
|
|
2991
3095
|
try {
|
|
@@ -3043,9 +3147,57 @@ function createDownstreamHandler(opts) {
|
|
|
3043
3147
|
}
|
|
3044
3148
|
}
|
|
3045
3149
|
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3150
|
+
function handleRequestCartesiaAccessToken(clientId, msg) {
|
|
3151
|
+
let payload;
|
|
3152
|
+
try {
|
|
3153
|
+
payload = parseRequestCartesiaAccessToken(msg);
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
return {
|
|
3156
|
+
unicast: formatCartesiaAccessTokenError({
|
|
3157
|
+
voiceSessionId:
|
|
3158
|
+
msg && typeof msg.voiceSessionId === "string" ? msg.voiceSessionId : "",
|
|
3159
|
+
error: err && err.message ? err.message : "requestCartesiaAccessToken failed",
|
|
3160
|
+
code: normalizeCartesiaAccessTokenErrorCode(err),
|
|
3161
|
+
}),
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
if (!onRequestCartesiaAccessToken) {
|
|
3166
|
+
return {
|
|
3167
|
+
unicast: formatCartesiaAccessTokenError({
|
|
3168
|
+
voiceSessionId: payload.voiceSessionId,
|
|
3169
|
+
error: "requestCartesiaAccessToken is not available",
|
|
3170
|
+
code: "cartesia_access_token_unavailable",
|
|
3171
|
+
}),
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
try {
|
|
3176
|
+
const result = onRequestCartesiaAccessToken(clientId, payload);
|
|
3177
|
+
if (result && typeof result.then === "function") {
|
|
3178
|
+
return result.then(
|
|
3179
|
+
(resolved) => ({ unicast: formatCartesiaAccessToken(resolved || payload) }),
|
|
3180
|
+
(err) => ({
|
|
3181
|
+
unicast: formatCartesiaAccessTokenError({
|
|
3182
|
+
voiceSessionId: payload.voiceSessionId,
|
|
3183
|
+
error: err && err.message ? err.message : "requestCartesiaAccessToken failed",
|
|
3184
|
+
code: normalizeCartesiaAccessTokenErrorCode(err),
|
|
3185
|
+
}),
|
|
3186
|
+
}),
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
return { unicast: formatCartesiaAccessToken(result || payload) };
|
|
3190
|
+
} catch (err) {
|
|
3191
|
+
return {
|
|
3192
|
+
unicast: formatCartesiaAccessTokenError({
|
|
3193
|
+
voiceSessionId: payload.voiceSessionId,
|
|
3194
|
+
error: err && err.message ? err.message : "requestCartesiaAccessToken failed",
|
|
3195
|
+
code: normalizeCartesiaAccessTokenErrorCode(err),
|
|
3196
|
+
}),
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3049
3201
|
function handleDebugSet(clientId, msg) {
|
|
3050
3202
|
if (!onDebugSet) {
|
|
3051
3203
|
return { unicast: formatError("debug-set is not available") };
|
|
@@ -3098,9 +3250,6 @@ function createDownstreamHandler(opts) {
|
|
|
3098
3250
|
}
|
|
3099
3251
|
}
|
|
3100
3252
|
|
|
3101
|
-
/**
|
|
3102
|
-
* Handle a "debug-dump" message: fetch structured debug events.
|
|
3103
|
-
*/
|
|
3104
3253
|
function handleDebugDump(clientId, msg) {
|
|
3105
3254
|
if (!onDebugDump) {
|
|
3106
3255
|
return { unicast: formatError("debug-dump is not available") };
|
|
@@ -3157,9 +3306,6 @@ function createDownstreamHandler(opts) {
|
|
|
3157
3306
|
}
|
|
3158
3307
|
}
|
|
3159
3308
|
|
|
3160
|
-
/**
|
|
3161
|
-
* Handle a "remote-control" message: dispatch remote actions to app clients.
|
|
3162
|
-
*/
|
|
3163
3309
|
function handleRemoteControl(clientId, msg) {
|
|
3164
3310
|
if (!onRemoteControl) {
|
|
3165
3311
|
return { unicast: formatError("remote-control is not available") };
|
|
@@ -3340,16 +3486,8 @@ function createDownstreamHandler(opts) {
|
|
|
3340
3486
|
}
|
|
3341
3487
|
}
|
|
3342
3488
|
|
|
3343
|
-
// --- Public API ---
|
|
3344
|
-
|
|
3345
3489
|
return {
|
|
3346
|
-
|
|
3347
|
-
* Process an incoming downstream message.
|
|
3348
|
-
*
|
|
3349
|
-
* @param {string} clientId - Identifier of the sending client
|
|
3350
|
-
* @param {string} raw - Raw JSON string
|
|
3351
|
-
* @returns {{ unicast: string }|{ broadcast: string }|null|Promise}
|
|
3352
|
-
*/
|
|
3490
|
+
|
|
3353
3491
|
handleMessage(clientId, raw) {
|
|
3354
3492
|
let msg;
|
|
3355
3493
|
try {
|
|
@@ -3372,6 +3510,10 @@ function createDownstreamHandler(opts) {
|
|
|
3372
3510
|
switch (msg.type) {
|
|
3373
3511
|
case APP_PROTOCOL.messageSend:
|
|
3374
3512
|
return handleSend(clientId, msg);
|
|
3513
|
+
case APP_PROTOCOL.sessionAbort:
|
|
3514
|
+
return handleAbortSession(clientId, msg);
|
|
3515
|
+
case APP_PROTOCOL.sessionSteer:
|
|
3516
|
+
return handleSteerSession(clientId, msg);
|
|
3375
3517
|
case "simulate":
|
|
3376
3518
|
return handleSimulate(clientId, msg);
|
|
3377
3519
|
case "simulateStream":
|
|
@@ -3384,6 +3526,8 @@ function createDownstreamHandler(opts) {
|
|
|
3384
3526
|
return handleNewChat(clientId);
|
|
3385
3527
|
case APP_PROTOCOL.sessionList:
|
|
3386
3528
|
return handleGetSessions(clientId);
|
|
3529
|
+
case APP_PROTOCOL.sessionListDiff:
|
|
3530
|
+
return handleGetSessionDiff(clientId, msg);
|
|
3387
3531
|
case APP_PROTOCOL.sessionSwitch:
|
|
3388
3532
|
return handleSwitchSession(clientId, msg);
|
|
3389
3533
|
case APP_PROTOCOL.sessionCreate:
|
|
@@ -3401,6 +3545,9 @@ function createDownstreamHandler(opts) {
|
|
|
3401
3545
|
case APP_PROTOCOL.skillsCatalogGet:
|
|
3402
3546
|
case "getSkills":
|
|
3403
3547
|
return handleGetSkillsCatalog(clientId);
|
|
3548
|
+
case APP_PROTOCOL.agentsCatalogGet:
|
|
3549
|
+
case "getAgentsCatalog":
|
|
3550
|
+
return handleGetAgentsCatalog(clientId);
|
|
3404
3551
|
case APP_PROTOCOL.sonioxModelsGet:
|
|
3405
3552
|
case "getSonioxModels":
|
|
3406
3553
|
return handleGetSonioxModels(clientId);
|
|
@@ -3414,6 +3561,9 @@ function createDownstreamHandler(opts) {
|
|
|
3414
3561
|
return handleGetSessionModelConfig(clientId);
|
|
3415
3562
|
case APP_PROTOCOL.sessionConfigSet:
|
|
3416
3563
|
return handleSetSessionModelConfig(clientId, msg);
|
|
3564
|
+
case APP_PROTOCOL.sessionAgentSet:
|
|
3565
|
+
case "setSessionAgent":
|
|
3566
|
+
return handleSetSessionAgent(clientId, msg);
|
|
3417
3567
|
case APP_PROTOCOL.sessionCompact:
|
|
3418
3568
|
return handleCompactSession(clientId, msg);
|
|
3419
3569
|
case APP_PROTOCOL.evenAiSettingsGet:
|
|
@@ -3437,10 +3587,46 @@ function createDownstreamHandler(opts) {
|
|
|
3437
3587
|
return handleRemovedListenAction(msg.type);
|
|
3438
3588
|
case APP_PROTOCOL.requestSonioxTemporaryKey:
|
|
3439
3589
|
return handleRequestSonioxTemporaryKey(clientId, msg);
|
|
3590
|
+
case APP_PROTOCOL.requestCartesiaAccessToken:
|
|
3591
|
+
return handleRequestCartesiaAccessToken(clientId, msg);
|
|
3440
3592
|
case "debug-set":
|
|
3441
3593
|
return handleDebugSet(clientId, msg);
|
|
3442
3594
|
case "debug-dump":
|
|
3443
3595
|
return handleDebugDump(clientId, msg);
|
|
3596
|
+
case "debug-bundle-request":
|
|
3597
|
+
|
|
3598
|
+
if (typeof onDebugBundleRequest === "function") {
|
|
3599
|
+
try {
|
|
3600
|
+
onDebugBundleRequest(clientId, msg);
|
|
3601
|
+
} catch (err) {
|
|
3602
|
+
logger.warn(
|
|
3603
|
+
`[downstream] debug-bundle-request handler threw: ${err && err.message ? err.message : err}`,
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
return null;
|
|
3608
|
+
case "debug-bundle-save":
|
|
3609
|
+
if (typeof onDebugBundleSave === "function") {
|
|
3610
|
+
try {
|
|
3611
|
+
onDebugBundleSave(clientId, msg);
|
|
3612
|
+
} catch (err) {
|
|
3613
|
+
logger.warn(
|
|
3614
|
+
`[downstream] debug-bundle-save handler threw: ${err && err.message ? err.message : err}`,
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
return null;
|
|
3619
|
+
case "debug-bundle-fetch":
|
|
3620
|
+
if (typeof onDebugBundleFetch === "function") {
|
|
3621
|
+
try {
|
|
3622
|
+
onDebugBundleFetch(clientId, msg);
|
|
3623
|
+
} catch (err) {
|
|
3624
|
+
logger.warn(
|
|
3625
|
+
`[downstream] debug-bundle-fetch handler threw: ${err && err.message ? err.message : err}`,
|
|
3626
|
+
);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
return null;
|
|
3444
3630
|
case "trace-log-set":
|
|
3445
3631
|
return handleTraceLogSet(clientId, msg);
|
|
3446
3632
|
case "trace-log-get":
|
|
@@ -3468,10 +3654,7 @@ function createDownstreamHandler(opts) {
|
|
|
3468
3654
|
}
|
|
3469
3655
|
return null;
|
|
3470
3656
|
case "glasses_ui_nav_event":
|
|
3471
|
-
|
|
3472
|
-
// surfaceId; pop reports the post-pop depth) so the plugin can
|
|
3473
|
-
// pause/resume the right surface's cron. Not a debug tool — stays out
|
|
3474
|
-
// of isExternalDebugToolMessageType.
|
|
3657
|
+
|
|
3475
3658
|
if (typeof onGlassesUiNavEvent === "function") {
|
|
3476
3659
|
try {
|
|
3477
3660
|
onGlassesUiNavEvent({
|
|
@@ -3486,8 +3669,7 @@ function createDownstreamHandler(opts) {
|
|
|
3486
3669
|
}
|
|
3487
3670
|
return null;
|
|
3488
3671
|
case "glasses_ui_render":
|
|
3489
|
-
|
|
3490
|
-
// exercise the simulator paint loop without spinning up an agent.
|
|
3672
|
+
|
|
3491
3673
|
if (typeof onGlassesUiRenderInject === "function") {
|
|
3492
3674
|
try {
|
|
3493
3675
|
onGlassesUiRenderInject({
|
|
@@ -3531,9 +3713,12 @@ function createDownstreamHandler(opts) {
|
|
|
3531
3713
|
formatProtocol,
|
|
3532
3714
|
formatStreaming,
|
|
3533
3715
|
formatSessions,
|
|
3716
|
+
formatSessionDiff,
|
|
3717
|
+
sessionInfoFingerprint,
|
|
3534
3718
|
formatSessionSwitched,
|
|
3535
3719
|
formatModelsCatalog,
|
|
3536
3720
|
formatSkillsCatalog,
|
|
3721
|
+
formatAgentsCatalog,
|
|
3537
3722
|
formatSonioxModels,
|
|
3538
3723
|
formatProviderUsageSnapshot,
|
|
3539
3724
|
formatSessionModelConfig,
|
|
@@ -3564,21 +3749,10 @@ function createDownstreamHandler(opts) {
|
|
|
3564
3749
|
formatReadinessProbeAck,
|
|
3565
3750
|
formatError,
|
|
3566
3751
|
|
|
3567
|
-
/**
|
|
3568
|
-
* Check whether a client is subscribed to protocol frame forwarding.
|
|
3569
|
-
*
|
|
3570
|
-
* @param {string} clientId
|
|
3571
|
-
* @returns {boolean}
|
|
3572
|
-
*/
|
|
3573
3752
|
isProtocolSubscriber(clientId) {
|
|
3574
3753
|
return protocolSubscribers.has(clientId);
|
|
3575
3754
|
},
|
|
3576
3755
|
|
|
3577
|
-
/**
|
|
3578
|
-
* Clean up state for a disconnected client.
|
|
3579
|
-
*
|
|
3580
|
-
* @param {string} clientId
|
|
3581
|
-
*/
|
|
3582
3756
|
removeClient(clientId) {
|
|
3583
3757
|
protocolSubscribers.delete(clientId);
|
|
3584
3758
|
},
|