ocuclaw 0.1.0 → 1.3.0
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 +63 -8
- package/dist/config/runtime-config.js +81 -3
- package/dist/domain/activity-status-adapter.js +138 -605
- package/dist/domain/activity-status-arbiter.js +109 -0
- package/dist/domain/activity-status-labels.js +906 -0
- package/dist/domain/code-span-regions.js +103 -0
- package/dist/domain/conversation-state.js +14 -1
- package/dist/domain/debug-store.js +41 -184
- package/dist/domain/glasses-ui-content-summary.js +62 -0
- package/dist/domain/glasses-ui-system-prompt.js +28 -0
- package/dist/domain/message-emoji-allowlist.js +16 -0
- package/dist/domain/message-emoji-filter.js +33 -55
- package/dist/domain/neural-emoji-reactor-system-prompt.js +43 -0
- package/dist/domain/neural-emoji-reactor-tag-config.js +56 -0
- package/dist/domain/neural-pace-modulator-system-prompt.js +32 -0
- package/dist/domain/neural-pace-modulator-tag-config.js +51 -0
- package/dist/domain/tagged-span-parser.js +121 -0
- package/dist/domain/tagged-span-strip.js +38 -0
- package/dist/even-ai/even-ai-endpoint.js +91 -0
- package/dist/even-ai/even-ai-run-waiter.js +14 -0
- package/dist/even-ai/even-ai-settings-store.js +14 -0
- package/dist/gateway/gateway-bridge.js +14 -2
- package/dist/gateway/gateway-timing-ledger.js +457 -0
- package/dist/gateway/openclaw-client.js +462 -38
- package/dist/index.js +28 -1
- package/dist/runtime/downstream-handler.js +909 -68
- package/dist/runtime/downstream-server.js +1004 -512
- package/dist/runtime/ocuclaw-settings-store.js +74 -31
- package/dist/runtime/plugin-update-service.js +216 -0
- package/dist/runtime/protocol-adapter.js +9 -0
- package/dist/runtime/provider-usage-select.js +168 -0
- package/dist/runtime/relay-client-nudge-controller.js +553 -0
- package/dist/runtime/relay-core.js +1357 -210
- package/dist/runtime/relay-health-monitor.js +172 -0
- package/dist/runtime/relay-operation-registry.js +263 -0
- package/dist/runtime/relay-service.js +201 -1
- package/dist/runtime/relay-worker-approval-replay-cache.js +68 -0
- package/dist/runtime/relay-worker-entry.js +32 -0
- package/dist/runtime/relay-worker-health.js +272 -0
- package/dist/runtime/relay-worker-protocol.js +285 -0
- package/dist/runtime/relay-worker-queue.js +202 -0
- package/dist/runtime/relay-worker-supervisor.js +1081 -0
- package/dist/runtime/relay-worker-transport.js +1051 -0
- package/dist/runtime/session-context-service.js +189 -0
- package/dist/runtime/session-service.js +656 -38
- package/dist/runtime/upstream-runtime.js +1167 -60
- package/dist/tools/device-info-tool.js +242 -0
- package/dist/tools/glasses-ui-cron.js +427 -0
- package/dist/tools/glasses-ui-descriptors.js +261 -0
- package/dist/tools/glasses-ui-limits.js +21 -0
- package/dist/tools/glasses-ui-paint-floor.js +99 -0
- package/dist/tools/glasses-ui-recipes.js +746 -0
- package/dist/tools/glasses-ui-surfaces.js +278 -0
- package/dist/tools/glasses-ui-template.js +182 -0
- package/dist/tools/glasses-ui-tool.js +1147 -0
- package/dist/tools/session-title-tool.js +209 -0
- package/dist/version.js +2 -0
- package/openclaw.plugin.json +163 -15
- package/package.json +12 -4
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
normalizeOcuClawDefaultThinking,
|
|
5
5
|
normalizeOcuClawSystemPrompt,
|
|
6
6
|
} from "./ocuclaw-settings-store.js";
|
|
7
|
+
import {
|
|
8
|
+
formatMainOperationReceived,
|
|
9
|
+
formatSendAck,
|
|
10
|
+
} from "./relay-worker-protocol.js";
|
|
7
11
|
|
|
8
12
|
// --- Factory ---
|
|
9
13
|
|
|
@@ -27,7 +31,7 @@ function normalizeLogger(logger) {
|
|
|
27
31
|
* downstream clients (Even App, commander.html). Consumed by relay.js.
|
|
28
32
|
*
|
|
29
33
|
* @param {object} opts
|
|
30
|
-
* @param {(id: string, text: string, sessionKey: string|null, attachment: object|null) => Promise} opts.onSend
|
|
34
|
+
* @param {(id: string, text: string, sessionKey: string|null, attachment: object|null, clientDisplaySignals: object|null) => Promise} opts.onSend
|
|
31
35
|
* Forward a user message to the upstream OpenClaw agent.
|
|
32
36
|
* @param {(sender: string, text: string) => Array} opts.onSimulate
|
|
33
37
|
* Inject a fake message into conversation state; returns pages array.
|
|
@@ -41,10 +45,14 @@ function normalizeLogger(logger) {
|
|
|
41
45
|
* Return cached skills catalog snapshot.
|
|
42
46
|
* @param {() => Promise<{models: Array, fetchedAtMs: number, stale: boolean}>} [opts.onGetSonioxModels]
|
|
43
47
|
* Return cached Soniox model snapshot.
|
|
48
|
+
* @param {() => Promise<object>} [opts.onGetProviderUsageSnapshot]
|
|
49
|
+
* Return the current provider usage snapshot for the active provider.
|
|
44
50
|
* @param {() => Promise<object>} [opts.onGetSessionModelConfig]
|
|
45
51
|
* Return current session model controls.
|
|
46
52
|
* @param {(patch: object) => Promise<{status: string, error?: string}>} [opts.onSetSessionModelConfig]
|
|
47
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.
|
|
48
56
|
* @param {() => Promise<object>} [opts.onGetEvenAiSettings]
|
|
49
57
|
* Return current relay-owned Even AI settings.
|
|
50
58
|
* @param {() => Promise<{sessions: Array, dedicatedKey: string}>} [opts.onGetEvenAiSessions]
|
|
@@ -63,6 +71,8 @@ function normalizeLogger(logger) {
|
|
|
63
71
|
* Returns true if the OpenClaw gateway connection is active.
|
|
64
72
|
* @param {(clientId: string, payload: object) => void} [opts.onEventDebug]
|
|
65
73
|
* Optional structured client debug-event callback.
|
|
74
|
+
* @param {(clientId: string, payload: object) => Promise<object>|object} [opts.onReadinessProbe]
|
|
75
|
+
* Optional dedicated readiness-probe dispatcher.
|
|
66
76
|
* @returns {object} Handler instance
|
|
67
77
|
*/
|
|
68
78
|
function createDownstreamHandler(opts) {
|
|
@@ -79,8 +89,10 @@ function createDownstreamHandler(opts) {
|
|
|
79
89
|
const onGetModelsCatalog = opts.onGetModelsCatalog;
|
|
80
90
|
const onGetSkillsCatalog = opts.onGetSkillsCatalog;
|
|
81
91
|
const onGetSonioxModels = opts.onGetSonioxModels || null;
|
|
92
|
+
const onGetProviderUsageSnapshot = opts.onGetProviderUsageSnapshot || null;
|
|
82
93
|
const onGetSessionModelConfig = opts.onGetSessionModelConfig;
|
|
83
94
|
const onSetSessionModelConfig = opts.onSetSessionModelConfig;
|
|
95
|
+
const onCompactSession = opts.onCompactSession || null;
|
|
84
96
|
const onGetEvenAiSettings = opts.onGetEvenAiSettings;
|
|
85
97
|
const onGetEvenAiSessions = opts.onGetEvenAiSessions;
|
|
86
98
|
const onSetEvenAiSettings = opts.onSetEvenAiSettings;
|
|
@@ -94,8 +106,21 @@ function createDownstreamHandler(opts) {
|
|
|
94
106
|
const onDebugSet = opts.onDebugSet || null;
|
|
95
107
|
const onDebugDump = opts.onDebugDump || null;
|
|
96
108
|
const onEventDebug = opts.onEventDebug || null;
|
|
109
|
+
const onTraceLogSet = opts.onTraceLogSet || null;
|
|
110
|
+
const onTraceLogGet = opts.onTraceLogGet || null;
|
|
97
111
|
const onRemoteControl = opts.onRemoteControl || null;
|
|
112
|
+
const onAutomationState = opts.onAutomationState || null;
|
|
113
|
+
const onReadinessProbe = opts.onReadinessProbe || null;
|
|
114
|
+
const onGlassesUiResult = opts.onGlassesUiResult || null;
|
|
115
|
+
const onGlassesUiRenderInject = opts.onGlassesUiRenderInject || null;
|
|
116
|
+
const onGlassesUiNavEvent = opts.onGlassesUiNavEvent || null;
|
|
117
|
+
const onDeviceInfoResponse = opts.onDeviceInfoResponse || null;
|
|
118
|
+
const onSetUserSessionTitle = opts.onSetUserSessionTitle || null;
|
|
119
|
+
const onSetSessionPinned = opts.onSetSessionPinned || null;
|
|
120
|
+
const onDeleteSessions = opts.onDeleteSessions || null;
|
|
121
|
+
const onSearchTranscripts = opts.onSearchTranscripts || null;
|
|
98
122
|
const getSnapshotRevision = opts.getSnapshotRevision || null;
|
|
123
|
+
const operationRegistry = opts.operationRegistry || null;
|
|
99
124
|
|
|
100
125
|
/** Client IDs subscribed to raw protocol frame forwarding. */
|
|
101
126
|
const protocolSubscribers = new Set();
|
|
@@ -112,6 +137,8 @@ function createDownstreamHandler(opts) {
|
|
|
112
137
|
const approvalResolveCache = new Map();
|
|
113
138
|
const APP_PROTOCOL = {
|
|
114
139
|
activity: "ocuclaw.activity.update",
|
|
140
|
+
automationStateGet: "ocuclaw.automation.state.get",
|
|
141
|
+
automationStateSnapshot: "ocuclaw.automation.state.snapshot",
|
|
115
142
|
approvalRequest: "ocuclaw.approval.request",
|
|
116
143
|
approvalResolve: "ocuclaw.approval.resolve",
|
|
117
144
|
approvalResolveAck: "ocuclaw.approval.resolve.ack",
|
|
@@ -134,11 +161,15 @@ function createDownstreamHandler(opts) {
|
|
|
134
161
|
messageStreamDelta: "ocuclaw.message.stream.delta",
|
|
135
162
|
modelCatalogGet: "ocuclaw.model.catalog.get",
|
|
136
163
|
modelCatalogSnapshot: "ocuclaw.model.catalog.snapshot",
|
|
164
|
+
providerUsageGet: "ocuclaw.provider.usage.get",
|
|
165
|
+
providerUsageSnapshot: "ocuclaw.provider.usage.snapshot",
|
|
137
166
|
skillsCatalogGet: "ocuclaw.skills.catalog.get",
|
|
138
167
|
skillsCatalogSnapshot: "ocuclaw.skills.catalog.snapshot",
|
|
139
168
|
pages: "ocuclaw.view.pages.snapshot",
|
|
140
169
|
protocolSubscribe: "ocuclaw.protocol.tap.subscribe",
|
|
141
170
|
protocolFrame: "ocuclaw.protocol.tap.frame",
|
|
171
|
+
readinessProbeAck: "ocuclaw.readiness.probe.ack",
|
|
172
|
+
readinessProbeRequest: "ocuclaw.readiness.probe.request",
|
|
142
173
|
remoteControl: "ocuclaw.remote.control",
|
|
143
174
|
requestSonioxTemporaryKey: "requestSonioxTemporaryKey",
|
|
144
175
|
sonioxModelsGet: "ocuclaw.voice.soniox.models.get",
|
|
@@ -147,16 +178,20 @@ function createDownstreamHandler(opts) {
|
|
|
147
178
|
sessionConfigSet: "ocuclaw.session.config.set",
|
|
148
179
|
sessionConfigSetAck: "ocuclaw.session.config.set.ack",
|
|
149
180
|
sessionConfigSnapshot: "ocuclaw.session.config.snapshot",
|
|
181
|
+
sessionCompact: "ocuclaw.session.compact",
|
|
182
|
+
sessionCompactAck: "ocuclaw.session.compact.ack",
|
|
150
183
|
sessionCreate: "ocuclaw.session.create",
|
|
151
184
|
sessionList: "ocuclaw.session.list",
|
|
152
185
|
sessionListResult: "ocuclaw.session.list.result",
|
|
153
186
|
sessionReset: "ocuclaw.session.reset",
|
|
154
187
|
sessionSwitch: "ocuclaw.session.switch",
|
|
155
188
|
sessionSwitchApplied: "ocuclaw.session.switch.applied",
|
|
189
|
+
sessionTitleSet: "ocuclaw.session.title.set",
|
|
156
190
|
sonioxTemporaryKey: "sonioxTemporaryKey",
|
|
157
191
|
sonioxTemporaryKeyError: "sonioxTemporaryKeyError",
|
|
158
192
|
status: "ocuclaw.runtime.status",
|
|
159
193
|
statusGet: "ocuclaw.runtime.status.get",
|
|
194
|
+
typingUpdate: "ocuclaw.typing.update",
|
|
160
195
|
};
|
|
161
196
|
|
|
162
197
|
// --- Format helpers ---
|
|
@@ -217,24 +252,52 @@ function createDownstreamHandler(opts) {
|
|
|
217
252
|
return JSON.stringify({ ...activity, type: APP_PROTOCOL.activity });
|
|
218
253
|
}
|
|
219
254
|
|
|
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
|
+
function formatTyping(update) {
|
|
262
|
+
return JSON.stringify({ ...update, type: APP_PROTOCOL.typingUpdate });
|
|
263
|
+
}
|
|
264
|
+
|
|
220
265
|
/**
|
|
221
266
|
* Format an error message for unicast.
|
|
222
267
|
*
|
|
223
268
|
* @param {string} error
|
|
269
|
+
* @param {{code?: string, requestId?: string, op?: string}} [meta]
|
|
224
270
|
* @returns {string} JSON string
|
|
225
271
|
*/
|
|
226
|
-
function formatError(error) {
|
|
227
|
-
|
|
272
|
+
function formatError(error, meta) {
|
|
273
|
+
const msg = {
|
|
228
274
|
type: "error",
|
|
229
275
|
error: error || "Unknown error",
|
|
230
|
-
}
|
|
276
|
+
};
|
|
277
|
+
if (meta && typeof meta === "object" && !Array.isArray(meta)) {
|
|
278
|
+
if (typeof meta.code === "string" && meta.code.trim()) {
|
|
279
|
+
msg.code = meta.code.trim();
|
|
280
|
+
}
|
|
281
|
+
if (typeof meta.requestId === "string" && meta.requestId.trim()) {
|
|
282
|
+
msg.requestId = meta.requestId.trim();
|
|
283
|
+
}
|
|
284
|
+
if (typeof meta.op === "string" && meta.op.trim()) {
|
|
285
|
+
msg.op = meta.op.trim();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return JSON.stringify(msg);
|
|
231
289
|
}
|
|
232
290
|
|
|
233
291
|
function isExternalDebugToolMessageType(messageType) {
|
|
234
292
|
return (
|
|
235
293
|
messageType === "debug-set" ||
|
|
236
294
|
messageType === "debug-dump" ||
|
|
237
|
-
messageType === "
|
|
295
|
+
messageType === "trace-log-set" ||
|
|
296
|
+
messageType === "trace-log-get" ||
|
|
297
|
+
messageType === "remote-control" ||
|
|
298
|
+
messageType === APP_PROTOCOL.automationStateGet ||
|
|
299
|
+
messageType === APP_PROTOCOL.readinessProbeRequest ||
|
|
300
|
+
messageType === "glasses_ui_render"
|
|
238
301
|
);
|
|
239
302
|
}
|
|
240
303
|
|
|
@@ -247,15 +310,12 @@ function createDownstreamHandler(opts) {
|
|
|
247
310
|
* @param {string} [errorCode] - Structured error code (for deterministic client handling)
|
|
248
311
|
* @returns {string} JSON string
|
|
249
312
|
*/
|
|
250
|
-
function
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
msg.errorCode = errorCode;
|
|
257
|
-
}
|
|
258
|
-
return JSON.stringify(msg);
|
|
313
|
+
function formatSendAckCompat(id, status, error, errorCode) {
|
|
314
|
+
return formatSendAck(id, status, error, errorCode);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function formatOperationReceived(data) {
|
|
318
|
+
return formatMainOperationReceived(data);
|
|
259
319
|
}
|
|
260
320
|
|
|
261
321
|
/**
|
|
@@ -277,10 +337,18 @@ function createDownstreamHandler(opts) {
|
|
|
277
337
|
* Format a streaming text message for broadcast during agent runs.
|
|
278
338
|
*
|
|
279
339
|
* @param {string} text - Streaming assistant text
|
|
340
|
+
* @param {Array<{start: number, end: number, emoji: string}>} [spans] - Optional emoji spans
|
|
280
341
|
* @returns {string} JSON string
|
|
281
342
|
*/
|
|
282
|
-
function formatStreaming(text) {
|
|
283
|
-
|
|
343
|
+
function formatStreaming(text, emojiSpans, paceSpans) {
|
|
344
|
+
const payload = { type: APP_PROTOCOL.messageStreamDelta, text };
|
|
345
|
+
if (Array.isArray(emojiSpans) && emojiSpans.length > 0) {
|
|
346
|
+
payload.emojiSpans = emojiSpans;
|
|
347
|
+
}
|
|
348
|
+
if (Array.isArray(paceSpans) && paceSpans.length > 0) {
|
|
349
|
+
payload.paceSpans = paceSpans;
|
|
350
|
+
}
|
|
351
|
+
return JSON.stringify(payload);
|
|
284
352
|
}
|
|
285
353
|
|
|
286
354
|
/**
|
|
@@ -360,6 +428,75 @@ function createDownstreamHandler(opts) {
|
|
|
360
428
|
});
|
|
361
429
|
}
|
|
362
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Format a provider usage snapshot for unicast.
|
|
433
|
+
*
|
|
434
|
+
* @param {object} payload
|
|
435
|
+
* @returns {string}
|
|
436
|
+
*/
|
|
437
|
+
function formatProviderUsageSnapshot(payload) {
|
|
438
|
+
const provider =
|
|
439
|
+
payload && typeof payload.provider === "string" && payload.provider.trim()
|
|
440
|
+
? payload.provider.trim()
|
|
441
|
+
: null;
|
|
442
|
+
const windows = Array.isArray(payload && payload.windows)
|
|
443
|
+
? payload.windows.map((window) => ({
|
|
444
|
+
key:
|
|
445
|
+
window && typeof window.key === "string" && window.key.trim()
|
|
446
|
+
? window.key.trim()
|
|
447
|
+
: null,
|
|
448
|
+
label:
|
|
449
|
+
window && typeof window.label === "string" && window.label.trim()
|
|
450
|
+
? window.label.trim()
|
|
451
|
+
: null,
|
|
452
|
+
usedPercent:
|
|
453
|
+
Number.isFinite(window && window.usedPercent)
|
|
454
|
+
? window.usedPercent
|
|
455
|
+
: 0,
|
|
456
|
+
resetAtMs:
|
|
457
|
+
Number.isFinite(window && window.resetAtMs)
|
|
458
|
+
? Math.floor(window.resetAtMs)
|
|
459
|
+
: null,
|
|
460
|
+
sortOrder:
|
|
461
|
+
Number.isFinite(window && window.sortOrder)
|
|
462
|
+
? Math.floor(window.sortOrder)
|
|
463
|
+
: null,
|
|
464
|
+
}))
|
|
465
|
+
: [];
|
|
466
|
+
return JSON.stringify({
|
|
467
|
+
type: APP_PROTOCOL.providerUsageSnapshot,
|
|
468
|
+
sessionKey:
|
|
469
|
+
payload && typeof payload.sessionKey === "string" && payload.sessionKey.trim()
|
|
470
|
+
? payload.sessionKey.trim()
|
|
471
|
+
: null,
|
|
472
|
+
provider,
|
|
473
|
+
displayName:
|
|
474
|
+
payload && typeof payload.displayName === "string" && payload.displayName.trim()
|
|
475
|
+
? payload.displayName.trim()
|
|
476
|
+
: provider,
|
|
477
|
+
limitingWindowKey:
|
|
478
|
+
payload &&
|
|
479
|
+
typeof payload.limitingWindowKey === "string" &&
|
|
480
|
+
payload.limitingWindowKey.trim()
|
|
481
|
+
? payload.limitingWindowKey.trim()
|
|
482
|
+
: null,
|
|
483
|
+
windows,
|
|
484
|
+
fetchedAtMs:
|
|
485
|
+
Number.isFinite(payload && payload.fetchedAtMs)
|
|
486
|
+
? Math.floor(payload.fetchedAtMs)
|
|
487
|
+
: Date.now(),
|
|
488
|
+
stale: !!(payload && payload.stale),
|
|
489
|
+
poolStatus:
|
|
490
|
+
payload && (payload.poolStatus === "ready" || payload.poolStatus === "exhausted")
|
|
491
|
+
? payload.poolStatus
|
|
492
|
+
: "unknown",
|
|
493
|
+
totalProfileCount:
|
|
494
|
+
Number.isFinite(payload && payload.totalProfileCount) && payload.totalProfileCount >= 0
|
|
495
|
+
? Math.floor(payload.totalProfileCount)
|
|
496
|
+
: null,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
363
500
|
/**
|
|
364
501
|
* Format current session model controls.
|
|
365
502
|
*
|
|
@@ -388,6 +525,11 @@ function createDownstreamHandler(opts) {
|
|
|
388
525
|
payload && typeof payload.verboseLevel === "string"
|
|
389
526
|
? payload.verboseLevel
|
|
390
527
|
: "off",
|
|
528
|
+
fastMode: !!(payload && payload.fastMode === true),
|
|
529
|
+
elevatedLevel:
|
|
530
|
+
payload && typeof payload.elevatedLevel === "string"
|
|
531
|
+
? payload.elevatedLevel
|
|
532
|
+
: "off",
|
|
391
533
|
});
|
|
392
534
|
}
|
|
393
535
|
|
|
@@ -411,6 +553,30 @@ function createDownstreamHandler(opts) {
|
|
|
411
553
|
return JSON.stringify(out);
|
|
412
554
|
}
|
|
413
555
|
|
|
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
|
+
function formatCompactSessionAck(payload) {
|
|
563
|
+
const msg = {
|
|
564
|
+
type: APP_PROTOCOL.sessionCompactAck,
|
|
565
|
+
status:
|
|
566
|
+
payload && payload.status === "accepted" ? "accepted" : "rejected",
|
|
567
|
+
};
|
|
568
|
+
if (payload && payload.requestId) {
|
|
569
|
+
msg.requestId = String(payload.requestId);
|
|
570
|
+
}
|
|
571
|
+
if (msg.status === "rejected") {
|
|
572
|
+
msg.error =
|
|
573
|
+
payload && payload.error
|
|
574
|
+
? String(payload.error)
|
|
575
|
+
: "compact failed";
|
|
576
|
+
}
|
|
577
|
+
return JSON.stringify(msg);
|
|
578
|
+
}
|
|
579
|
+
|
|
414
580
|
/**
|
|
415
581
|
* Format the current relay-owned Even AI settings snapshot.
|
|
416
582
|
*
|
|
@@ -438,6 +604,7 @@ function createDownstreamHandler(opts) {
|
|
|
438
604
|
? payload.defaultThinking
|
|
439
605
|
: "",
|
|
440
606
|
listenEnabled: payload && payload.listenEnabled === true,
|
|
607
|
+
defaultFastMode: !!(payload && payload.defaultFastMode === true),
|
|
441
608
|
});
|
|
442
609
|
}
|
|
443
610
|
|
|
@@ -485,6 +652,7 @@ function createDownstreamHandler(opts) {
|
|
|
485
652
|
? payload.defaultThinking
|
|
486
653
|
: undefined,
|
|
487
654
|
),
|
|
655
|
+
defaultFastMode: !!(payload && payload.defaultFastMode === true),
|
|
488
656
|
});
|
|
489
657
|
}
|
|
490
658
|
|
|
@@ -533,23 +701,52 @@ function createDownstreamHandler(opts) {
|
|
|
533
701
|
*/
|
|
534
702
|
function formatApproval(data) {
|
|
535
703
|
const request = data && data.request ? data.request : {};
|
|
704
|
+
const approvalKind =
|
|
705
|
+
(data && data.approvalKind === "plugin") ||
|
|
706
|
+
(data && typeof data.id === "string" && data.id.startsWith("plugin:")) ||
|
|
707
|
+
(typeof request.title === "string" && request.title.length > 0)
|
|
708
|
+
? "plugin"
|
|
709
|
+
: "exec";
|
|
710
|
+
const isPluginApproval = approvalKind === "plugin";
|
|
711
|
+
const commandText =
|
|
712
|
+
request.command ||
|
|
713
|
+
(request.host === "node" && request.systemRunPlan && typeof request.systemRunPlan.commandText === "string"
|
|
714
|
+
? request.systemRunPlan.commandText
|
|
715
|
+
: "") ||
|
|
716
|
+
(isPluginApproval && typeof request.title === "string" ? request.title : "") ||
|
|
717
|
+
"";
|
|
718
|
+
const pluginDescription =
|
|
719
|
+
isPluginApproval && typeof request.description === "string" && request.description.length > 0
|
|
720
|
+
? request.description
|
|
721
|
+
: null;
|
|
536
722
|
return JSON.stringify({
|
|
537
723
|
type: APP_PROTOCOL.approvalRequest,
|
|
538
724
|
id: data.id,
|
|
725
|
+
...(isPluginApproval ? { approvalKind: "plugin" } : {}),
|
|
539
726
|
requestId:
|
|
540
727
|
(data && typeof data.requestId === "string" && data.requestId) ||
|
|
541
728
|
(request && typeof request.requestId === "string" && request.requestId) ||
|
|
542
729
|
null,
|
|
543
|
-
command:
|
|
544
|
-
cwd: request.cwd || null,
|
|
730
|
+
command: commandText,
|
|
731
|
+
cwd: request.cwd || pluginDescription || null,
|
|
545
732
|
agentId: request.agentId || null,
|
|
546
|
-
host: request.host || null,
|
|
547
|
-
security: request.security || null,
|
|
733
|
+
host: request.host || (isPluginApproval ? "plugin" : null),
|
|
734
|
+
security: request.security || (isPluginApproval && typeof request.severity === "string" ? request.severity : null),
|
|
548
735
|
ask: request.ask || null,
|
|
549
736
|
resolvedPath: request.resolvedPath || null,
|
|
550
737
|
sessionKey: request.sessionKey || null,
|
|
738
|
+
...(isPluginApproval
|
|
739
|
+
? {
|
|
740
|
+
pluginId: typeof request.pluginId === "string" ? request.pluginId : null,
|
|
741
|
+
toolName: typeof request.toolName === "string" ? request.toolName : null,
|
|
742
|
+
description: pluginDescription,
|
|
743
|
+
}
|
|
744
|
+
: {}),
|
|
551
745
|
createdAtMs: data.createdAtMs || 0,
|
|
552
746
|
expiresAtMs: data.expiresAtMs || 0,
|
|
747
|
+
allowedDecisions: Array.isArray(request.allowedDecisions)
|
|
748
|
+
? request.allowedDecisions.filter((d) => typeof d === "string")
|
|
749
|
+
: null,
|
|
553
750
|
});
|
|
554
751
|
}
|
|
555
752
|
|
|
@@ -605,17 +802,6 @@ function createDownstreamHandler(opts) {
|
|
|
605
802
|
});
|
|
606
803
|
}
|
|
607
804
|
|
|
608
|
-
/**
|
|
609
|
-
* Format a transcription update for broadcast during voice mode.
|
|
610
|
-
*
|
|
611
|
-
* @param {string} text - Current transcription text
|
|
612
|
-
* @param {boolean} isFinal - Whether this is a final transcription
|
|
613
|
-
* @returns {string} JSON string
|
|
614
|
-
*/
|
|
615
|
-
function formatTranscription(text, isFinal) {
|
|
616
|
-
return JSON.stringify({ type: "transcription", text, final: isFinal });
|
|
617
|
-
}
|
|
618
|
-
|
|
619
805
|
/**
|
|
620
806
|
* Format a committed listen handoff update for broadcast during voice mode.
|
|
621
807
|
*
|
|
@@ -633,6 +819,13 @@ function createDownstreamHandler(opts) {
|
|
|
633
819
|
});
|
|
634
820
|
}
|
|
635
821
|
|
|
822
|
+
function formatEvenAiListenIntercepted(sessionKey) {
|
|
823
|
+
return JSON.stringify({
|
|
824
|
+
type: "even-ai-listen-intercepted",
|
|
825
|
+
sessionKey: sessionKey ?? null,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
636
829
|
/**
|
|
637
830
|
* Format a listen-ended message for broadcast.
|
|
638
831
|
* @returns {string} JSON string
|
|
@@ -722,6 +915,10 @@ function createDownstreamHandler(opts) {
|
|
|
722
915
|
: "";
|
|
723
916
|
const lowered = message.toLowerCase();
|
|
724
917
|
if (!message) return "soniox_temp_key_request_failed";
|
|
918
|
+
// AbortError from the per-fetch timeout AbortController in mintSonioxTemporaryKey.
|
|
919
|
+
if (err && err.name === "AbortError") {
|
|
920
|
+
return "soniox_temp_key_mint_timeout";
|
|
921
|
+
}
|
|
725
922
|
if (lowered.includes("is not available")) {
|
|
726
923
|
return "soniox_temp_key_unavailable";
|
|
727
924
|
}
|
|
@@ -770,6 +967,27 @@ function createDownstreamHandler(opts) {
|
|
|
770
967
|
});
|
|
771
968
|
}
|
|
772
969
|
|
|
970
|
+
/**
|
|
971
|
+
* Parse a "trace-log-set" message.
|
|
972
|
+
* @returns {{enabled: boolean}}
|
|
973
|
+
*/
|
|
974
|
+
function parseTraceLogSet(msg) {
|
|
975
|
+
if (!msg || typeof msg !== "object") {
|
|
976
|
+
throw new Error("trace-log-set requires an object");
|
|
977
|
+
}
|
|
978
|
+
if (typeof msg.enabled !== "boolean") {
|
|
979
|
+
throw new Error("trace-log-set requires boolean 'enabled'");
|
|
980
|
+
}
|
|
981
|
+
return { enabled: msg.enabled };
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Format a trace-log response for unicast (set + get share one type).
|
|
986
|
+
*/
|
|
987
|
+
function formatTraceLog(data) {
|
|
988
|
+
return JSON.stringify({ type: "trace-log", ...data });
|
|
989
|
+
}
|
|
990
|
+
|
|
773
991
|
/**
|
|
774
992
|
* Format the current relay debug-config snapshot for app/WebUI clients.
|
|
775
993
|
*
|
|
@@ -827,6 +1045,98 @@ function createDownstreamHandler(opts) {
|
|
|
827
1045
|
});
|
|
828
1046
|
}
|
|
829
1047
|
|
|
1048
|
+
function formatAutomationStateRequest(data) {
|
|
1049
|
+
return JSON.stringify({
|
|
1050
|
+
type: APP_PROTOCOL.automationStateGet,
|
|
1051
|
+
requestId:
|
|
1052
|
+
data && typeof data.requestId === "string" && data.requestId
|
|
1053
|
+
? data.requestId
|
|
1054
|
+
: null,
|
|
1055
|
+
sessionKey:
|
|
1056
|
+
data && typeof data.sessionKey === "string" && data.sessionKey
|
|
1057
|
+
? data.sessionKey
|
|
1058
|
+
: null,
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function formatAutomationStateSnapshot(data) {
|
|
1063
|
+
return JSON.stringify({
|
|
1064
|
+
type: APP_PROTOCOL.automationStateSnapshot,
|
|
1065
|
+
ok: data && data.ok !== false,
|
|
1066
|
+
requestId:
|
|
1067
|
+
data && typeof data.requestId === "string" && data.requestId
|
|
1068
|
+
? data.requestId
|
|
1069
|
+
: null,
|
|
1070
|
+
state:
|
|
1071
|
+
data && data.state && typeof data.state === "object" ? data.state : null,
|
|
1072
|
+
reasonCode:
|
|
1073
|
+
data && typeof data.reasonCode === "string" && data.reasonCode
|
|
1074
|
+
? data.reasonCode
|
|
1075
|
+
: null,
|
|
1076
|
+
message:
|
|
1077
|
+
data && typeof data.message === "string" && data.message
|
|
1078
|
+
? data.message
|
|
1079
|
+
: null,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function formatReadinessProbeRequest(data) {
|
|
1084
|
+
return JSON.stringify({
|
|
1085
|
+
type: APP_PROTOCOL.readinessProbeRequest,
|
|
1086
|
+
requestId:
|
|
1087
|
+
data && typeof data.requestId === "string" && data.requestId
|
|
1088
|
+
? data.requestId
|
|
1089
|
+
: null,
|
|
1090
|
+
sinceMs:
|
|
1091
|
+
data && Number.isFinite(Number(data.sinceMs))
|
|
1092
|
+
? Math.max(0, Math.floor(Number(data.sinceMs)))
|
|
1093
|
+
: 0,
|
|
1094
|
+
sessionKey:
|
|
1095
|
+
data && typeof data.sessionKey === "string" && data.sessionKey
|
|
1096
|
+
? data.sessionKey
|
|
1097
|
+
: null,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function formatReadinessProbeAck(data) {
|
|
1102
|
+
return JSON.stringify({
|
|
1103
|
+
type: APP_PROTOCOL.readinessProbeAck,
|
|
1104
|
+
ok: data && data.ok !== false,
|
|
1105
|
+
requestId:
|
|
1106
|
+
data && typeof data.requestId === "string" && data.requestId
|
|
1107
|
+
? data.requestId
|
|
1108
|
+
: null,
|
|
1109
|
+
reasonCode:
|
|
1110
|
+
data && typeof data.reasonCode === "string" && data.reasonCode
|
|
1111
|
+
? data.reasonCode
|
|
1112
|
+
: null,
|
|
1113
|
+
message:
|
|
1114
|
+
data && typeof data.message === "string" && data.message
|
|
1115
|
+
? data.message
|
|
1116
|
+
: null,
|
|
1117
|
+
activeSessionKey:
|
|
1118
|
+
data && typeof data.activeSessionKey === "string" && data.activeSessionKey
|
|
1119
|
+
? data.activeSessionKey
|
|
1120
|
+
: null,
|
|
1121
|
+
emittedAtMs:
|
|
1122
|
+
data && Number.isFinite(Number(data.emittedAtMs))
|
|
1123
|
+
? Math.max(0, Math.floor(Number(data.emittedAtMs)))
|
|
1124
|
+
: null,
|
|
1125
|
+
clientId:
|
|
1126
|
+
data && typeof data.clientId === "string" && data.clientId
|
|
1127
|
+
? data.clientId
|
|
1128
|
+
: null,
|
|
1129
|
+
clientName:
|
|
1130
|
+
data && typeof data.clientName === "string" && data.clientName
|
|
1131
|
+
? data.clientName
|
|
1132
|
+
: null,
|
|
1133
|
+
clientVersion:
|
|
1134
|
+
data && typeof data.clientVersion === "string" && data.clientVersion
|
|
1135
|
+
? data.clientVersion
|
|
1136
|
+
: null,
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
|
|
830
1140
|
function normalizeCategories(raw, fieldName) {
|
|
831
1141
|
if (raw === undefined || raw === null) return [];
|
|
832
1142
|
if (!Array.isArray(raw)) {
|
|
@@ -900,25 +1210,12 @@ function createDownstreamHandler(opts) {
|
|
|
900
1210
|
) {
|
|
901
1211
|
throw new Error("debug-dump untilMs must be a non-negative number");
|
|
902
1212
|
}
|
|
903
|
-
if (msg.redaction !== undefined) {
|
|
904
|
-
if (typeof msg.redaction !== "string") {
|
|
905
|
-
throw new Error("debug-dump redaction must be one of: safe, full");
|
|
906
|
-
}
|
|
907
|
-
const normalized = msg.redaction.trim().toLowerCase();
|
|
908
|
-
if (normalized !== "safe" && normalized !== "full") {
|
|
909
|
-
throw new Error("debug-dump redaction must be one of: safe, full");
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
1213
|
return {
|
|
913
1214
|
categories,
|
|
914
1215
|
limit: msg.limit === undefined ? undefined : Number(msg.limit),
|
|
915
1216
|
sinceMs: msg.sinceMs === undefined ? undefined : Number(msg.sinceMs),
|
|
916
1217
|
sinceAgeMs: msg.sinceAgeMs === undefined ? undefined : Number(msg.sinceAgeMs),
|
|
917
1218
|
untilMs: msg.untilMs === undefined ? undefined : Number(msg.untilMs),
|
|
918
|
-
redaction:
|
|
919
|
-
msg.redaction === undefined
|
|
920
|
-
? undefined
|
|
921
|
-
: msg.redaction.trim().toLowerCase(),
|
|
922
1219
|
};
|
|
923
1220
|
}
|
|
924
1221
|
|
|
@@ -1187,12 +1484,19 @@ function createDownstreamHandler(opts) {
|
|
|
1187
1484
|
|
|
1188
1485
|
const payload = {};
|
|
1189
1486
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1487
|
+
if (Object.prototype.hasOwnProperty.call(msg, "modelRef")) {
|
|
1488
|
+
if (typeof msg.modelRef !== "string") {
|
|
1489
|
+
throw new Error("modelRef must be in provider/id format or blank");
|
|
1490
|
+
}
|
|
1491
|
+
const modelRef = msg.modelRef.trim();
|
|
1492
|
+
if (!modelRef) {
|
|
1493
|
+
payload.modelRef = "";
|
|
1494
|
+
} else {
|
|
1495
|
+
if (!modelRef.includes("/")) {
|
|
1496
|
+
throw new Error("modelRef must be in provider/id format");
|
|
1497
|
+
}
|
|
1498
|
+
payload.modelRef = modelRef;
|
|
1194
1499
|
}
|
|
1195
|
-
payload.modelRef = modelRef;
|
|
1196
1500
|
}
|
|
1197
1501
|
|
|
1198
1502
|
if (Object.prototype.hasOwnProperty.call(msg, "thinkingLevel")) {
|
|
@@ -1230,6 +1534,20 @@ function createDownstreamHandler(opts) {
|
|
|
1230
1534
|
payload.verboseLevel = normalized;
|
|
1231
1535
|
}
|
|
1232
1536
|
|
|
1537
|
+
const fastMode = parseOptionalBoolean(msg.fastMode, "fastMode");
|
|
1538
|
+
if (fastMode !== undefined) {
|
|
1539
|
+
payload.fastMode = fastMode;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
const elevatedLevel = parseOptionalTrimmedString(msg.elevatedLevel);
|
|
1543
|
+
if (elevatedLevel) {
|
|
1544
|
+
const normalized = elevatedLevel.toLowerCase();
|
|
1545
|
+
if (!["off", "on", "ask", "full"].includes(normalized)) {
|
|
1546
|
+
throw new Error("elevatedLevel must be off|on|ask|full");
|
|
1547
|
+
}
|
|
1548
|
+
payload.elevatedLevel = normalized;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1233
1551
|
if (Object.keys(payload).length === 0) {
|
|
1234
1552
|
throw new Error("setSessionModelConfig requires at least one field");
|
|
1235
1553
|
}
|
|
@@ -1301,6 +1619,13 @@ function createDownstreamHandler(opts) {
|
|
|
1301
1619
|
payload.listenEnabled = msg.listenEnabled;
|
|
1302
1620
|
}
|
|
1303
1621
|
|
|
1622
|
+
if (Object.prototype.hasOwnProperty.call(msg, "defaultFastMode")) {
|
|
1623
|
+
if (typeof msg.defaultFastMode !== "boolean") {
|
|
1624
|
+
throw new Error("defaultFastMode must be a boolean");
|
|
1625
|
+
}
|
|
1626
|
+
payload.defaultFastMode = msg.defaultFastMode;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1304
1629
|
if (Object.keys(payload).length === 0) {
|
|
1305
1630
|
throw new Error("setEvenAiSettings requires at least one field");
|
|
1306
1631
|
}
|
|
@@ -1336,6 +1661,13 @@ function createDownstreamHandler(opts) {
|
|
|
1336
1661
|
payload.defaultThinking = normalizeOcuClawDefaultThinking(msg.defaultThinking);
|
|
1337
1662
|
}
|
|
1338
1663
|
|
|
1664
|
+
if (Object.prototype.hasOwnProperty.call(msg, "defaultFastMode")) {
|
|
1665
|
+
if (typeof msg.defaultFastMode !== "boolean") {
|
|
1666
|
+
throw new Error("defaultFastMode must be a boolean");
|
|
1667
|
+
}
|
|
1668
|
+
payload.defaultFastMode = msg.defaultFastMode;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1339
1671
|
if (Object.keys(payload).length === 0) {
|
|
1340
1672
|
throw new Error("setOcuClawSettings requires at least one field");
|
|
1341
1673
|
}
|
|
@@ -1428,6 +1760,20 @@ function createDownstreamHandler(opts) {
|
|
|
1428
1760
|
return payload;
|
|
1429
1761
|
}
|
|
1430
1762
|
|
|
1763
|
+
if (action === "setting-set") {
|
|
1764
|
+
const settingKey = parseOptionalTrimmedString(msg.settingKey);
|
|
1765
|
+
const value = parseOptionalTrimmedString(msg.value);
|
|
1766
|
+
if (!settingKey) {
|
|
1767
|
+
throw new Error("remote-control setting-set requires settingKey");
|
|
1768
|
+
}
|
|
1769
|
+
if (!value) {
|
|
1770
|
+
throw new Error("remote-control setting-set requires value");
|
|
1771
|
+
}
|
|
1772
|
+
payload.settingKey = settingKey;
|
|
1773
|
+
payload.value = value;
|
|
1774
|
+
return payload;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1431
1777
|
if (action === "relay-action") {
|
|
1432
1778
|
payload.relayAction = normalizeRemoteRelayAction(msg.relayAction);
|
|
1433
1779
|
if (
|
|
@@ -1489,6 +1835,39 @@ function createDownstreamHandler(opts) {
|
|
|
1489
1835
|
throw new Error(`unsupported remote-control action: ${actionRaw}`);
|
|
1490
1836
|
}
|
|
1491
1837
|
|
|
1838
|
+
function parseReadinessProbe(msg) {
|
|
1839
|
+
if (!msg || typeof msg !== "object") {
|
|
1840
|
+
throw new Error("readiness probe payload must be an object");
|
|
1841
|
+
}
|
|
1842
|
+
const requestId = parseOptionalTrimmedString(msg.requestId);
|
|
1843
|
+
if (!requestId) {
|
|
1844
|
+
throw new Error("readiness probe requires requestId");
|
|
1845
|
+
}
|
|
1846
|
+
const sinceMs = parseOptionalNonNegativeNumber(msg.sinceMs, "sinceMs");
|
|
1847
|
+
if (sinceMs === undefined) {
|
|
1848
|
+
throw new Error("readiness probe sinceMs must be a non-negative number");
|
|
1849
|
+
}
|
|
1850
|
+
return {
|
|
1851
|
+
requestId,
|
|
1852
|
+
sinceMs,
|
|
1853
|
+
sessionKey: parseOptionalTrimmedString(msg.sessionKey) || null,
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function parseAutomationStateGet(msg) {
|
|
1858
|
+
if (!msg || typeof msg !== "object") {
|
|
1859
|
+
throw new Error("automation state payload must be an object");
|
|
1860
|
+
}
|
|
1861
|
+
const requestId = parseOptionalTrimmedString(msg.requestId);
|
|
1862
|
+
if (!requestId) {
|
|
1863
|
+
throw new Error("automation state request requires requestId");
|
|
1864
|
+
}
|
|
1865
|
+
return {
|
|
1866
|
+
requestId,
|
|
1867
|
+
sessionKey: parseOptionalTrimmedString(msg.sessionKey) || null,
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1492
1871
|
const ATTACHMENT_MAX_DECODED_BYTES = 5_000_000;
|
|
1493
1872
|
const ATTACHMENT_MAX_ENCODED_CHARS =
|
|
1494
1873
|
Math.ceil((ATTACHMENT_MAX_DECODED_BYTES * 4) / 3) + 16;
|
|
@@ -1627,6 +2006,32 @@ function createDownstreamHandler(opts) {
|
|
|
1627
2006
|
};
|
|
1628
2007
|
}
|
|
1629
2008
|
|
|
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
|
+
function parseClientDisplaySignals(raw) {
|
|
2015
|
+
if (raw == null || typeof raw !== "object") return null;
|
|
2016
|
+
const coerceState = (val) => {
|
|
2017
|
+
const s = typeof val === "string" ? val : null;
|
|
2018
|
+
return s === "active" || s === "recently-disabled" || s === "inactive"
|
|
2019
|
+
? s
|
|
2020
|
+
: "inactive";
|
|
2021
|
+
};
|
|
2022
|
+
const state = coerceState(raw.neuralEmojiReactorState);
|
|
2023
|
+
const paceState = coerceState(raw.neuralPaceModulatorState);
|
|
2024
|
+
const enabledRaw = raw.neuralSessionNamesEnabled;
|
|
2025
|
+
const neuralSessionNamesEnabled =
|
|
2026
|
+
typeof enabledRaw === "boolean" ? enabledRaw : true;
|
|
2027
|
+
// readSpeedWpm tolerated but ignored on inbound for old clients.
|
|
2028
|
+
return {
|
|
2029
|
+
neuralEmojiReactorState: state,
|
|
2030
|
+
neuralPaceModulatorState: paceState,
|
|
2031
|
+
neuralSessionNamesEnabled,
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1630
2035
|
// --- Message handlers ---
|
|
1631
2036
|
|
|
1632
2037
|
/**
|
|
@@ -1640,7 +2045,7 @@ function createDownstreamHandler(opts) {
|
|
|
1640
2045
|
const requestId = parseOptionalTrimmedString(msg.requestId);
|
|
1641
2046
|
if (!requestId) {
|
|
1642
2047
|
return {
|
|
1643
|
-
unicast:
|
|
2048
|
+
unicast: formatSendAckCompat(
|
|
1644
2049
|
requestId,
|
|
1645
2050
|
"rejected",
|
|
1646
2051
|
"Missing required field: requestId",
|
|
@@ -1651,7 +2056,7 @@ function createDownstreamHandler(opts) {
|
|
|
1651
2056
|
const parsedAttachment = parseAttachment(msg.attachment);
|
|
1652
2057
|
if (!parsedAttachment.ok) {
|
|
1653
2058
|
return {
|
|
1654
|
-
unicast:
|
|
2059
|
+
unicast: formatSendAckCompat(
|
|
1655
2060
|
requestId,
|
|
1656
2061
|
"rejected",
|
|
1657
2062
|
parsedAttachment.error,
|
|
@@ -1663,7 +2068,7 @@ function createDownstreamHandler(opts) {
|
|
|
1663
2068
|
const text = typeof msg.text === "string" ? msg.text : "";
|
|
1664
2069
|
if (!text.trim() && !parsedAttachment.attachment) {
|
|
1665
2070
|
return {
|
|
1666
|
-
unicast:
|
|
2071
|
+
unicast: formatSendAckCompat(
|
|
1667
2072
|
requestId,
|
|
1668
2073
|
"rejected",
|
|
1669
2074
|
"Missing required field: text",
|
|
@@ -1674,7 +2079,7 @@ function createDownstreamHandler(opts) {
|
|
|
1674
2079
|
// Check upstream connectivity
|
|
1675
2080
|
if (!isUpstreamConnected()) {
|
|
1676
2081
|
return {
|
|
1677
|
-
unicast:
|
|
2082
|
+
unicast: formatSendAckCompat(
|
|
1678
2083
|
requestId,
|
|
1679
2084
|
"rejected",
|
|
1680
2085
|
"OpenClaw disconnected",
|
|
@@ -1682,26 +2087,66 @@ function createDownstreamHandler(opts) {
|
|
|
1682
2087
|
};
|
|
1683
2088
|
}
|
|
1684
2089
|
|
|
2090
|
+
const operation =
|
|
2091
|
+
operationRegistry && typeof operationRegistry.beginMessageSend === "function"
|
|
2092
|
+
? operationRegistry.beginMessageSend({
|
|
2093
|
+
requestId,
|
|
2094
|
+
clientId,
|
|
2095
|
+
sessionKey: msg.sessionKey || null,
|
|
2096
|
+
})
|
|
2097
|
+
: null;
|
|
2098
|
+
|
|
2099
|
+
if (operation && operation.duplicate) {
|
|
2100
|
+
const frames = operation.finalFrame
|
|
2101
|
+
? [operation.receipt, operation.finalFrame]
|
|
2102
|
+
: [operation.receipt];
|
|
2103
|
+
return { unicast: frames };
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
const clientDisplaySignals = parseClientDisplaySignals(msg.clientDisplaySignals);
|
|
2107
|
+
|
|
1685
2108
|
// Forward to upstream — resolves on initial ack (accepted/queued)
|
|
1686
|
-
|
|
2109
|
+
const followup = onSend(
|
|
1687
2110
|
requestId,
|
|
1688
2111
|
text,
|
|
1689
2112
|
msg.sessionKey || null,
|
|
1690
2113
|
parsedAttachment.attachment,
|
|
2114
|
+
clientDisplaySignals,
|
|
1691
2115
|
).then(
|
|
1692
2116
|
(result) => {
|
|
1693
2117
|
const status = (result && result.status) || "accepted";
|
|
1694
|
-
|
|
2118
|
+
const frame = formatSendAckCompat(requestId, status);
|
|
2119
|
+
if (operation && typeof operation.complete === "function") {
|
|
2120
|
+
operation.complete(frame, { status });
|
|
2121
|
+
}
|
|
2122
|
+
return { unicast: frame };
|
|
1695
2123
|
},
|
|
1696
|
-
(err) =>
|
|
1697
|
-
|
|
2124
|
+
(err) => {
|
|
2125
|
+
const frame = formatSendAckCompat(
|
|
1698
2126
|
requestId,
|
|
1699
2127
|
"rejected",
|
|
1700
2128
|
err.message || "Send failed",
|
|
1701
|
-
err.errorCode || undefined,
|
|
1702
|
-
)
|
|
1703
|
-
|
|
2129
|
+
err.errorCode || err.code || undefined,
|
|
2130
|
+
);
|
|
2131
|
+
if (operation && typeof operation.fail === "function") {
|
|
2132
|
+
operation.fail(frame, {
|
|
2133
|
+
errorCode: err.errorCode || err.code || null,
|
|
2134
|
+
message: err.message || "Send failed",
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
return { unicast: frame };
|
|
2138
|
+
},
|
|
1704
2139
|
);
|
|
2140
|
+
|
|
2141
|
+
return operation
|
|
2142
|
+
? {
|
|
2143
|
+
unicast: operation.receipt || formatOperationReceived({
|
|
2144
|
+
requestId,
|
|
2145
|
+
operation: "message.send",
|
|
2146
|
+
}),
|
|
2147
|
+
followup,
|
|
2148
|
+
}
|
|
2149
|
+
: followup;
|
|
1705
2150
|
}
|
|
1706
2151
|
|
|
1707
2152
|
/**
|
|
@@ -1730,7 +2175,7 @@ function createDownstreamHandler(opts) {
|
|
|
1730
2175
|
const id = parseOptionalTrimmedString(msg.id);
|
|
1731
2176
|
if (!id) {
|
|
1732
2177
|
return {
|
|
1733
|
-
unicast:
|
|
2178
|
+
unicast: formatSendAckCompat(
|
|
1734
2179
|
msg.id || null,
|
|
1735
2180
|
"rejected",
|
|
1736
2181
|
"Missing required field: id",
|
|
@@ -1740,7 +2185,7 @@ function createDownstreamHandler(opts) {
|
|
|
1740
2185
|
|
|
1741
2186
|
if (!onSimulateStream) {
|
|
1742
2187
|
return {
|
|
1743
|
-
unicast:
|
|
2188
|
+
unicast: formatSendAckCompat(
|
|
1744
2189
|
id,
|
|
1745
2190
|
"rejected",
|
|
1746
2191
|
"simulateStream not supported by relay",
|
|
@@ -1751,7 +2196,7 @@ function createDownstreamHandler(opts) {
|
|
|
1751
2196
|
const text = typeof msg.text === "string" ? msg.text : "";
|
|
1752
2197
|
if (!text.trim()) {
|
|
1753
2198
|
return {
|
|
1754
|
-
unicast:
|
|
2199
|
+
unicast: formatSendAckCompat(
|
|
1755
2200
|
id,
|
|
1756
2201
|
"rejected",
|
|
1757
2202
|
"simulateStream requires non-empty text",
|
|
@@ -1770,7 +2215,7 @@ function createDownstreamHandler(opts) {
|
|
|
1770
2215
|
thinkingTailMs = parseOptionalNonNegativeNumber(msg.thinkingTailMs, "thinkingTailMs");
|
|
1771
2216
|
} catch (err) {
|
|
1772
2217
|
return {
|
|
1773
|
-
unicast:
|
|
2218
|
+
unicast: formatSendAckCompat(
|
|
1774
2219
|
id,
|
|
1775
2220
|
"rejected",
|
|
1776
2221
|
err && err.message ? err.message : "Invalid simulateStream parameters",
|
|
@@ -1793,10 +2238,10 @@ function createDownstreamHandler(opts) {
|
|
|
1793
2238
|
(result) => {
|
|
1794
2239
|
const status = result && result.status ? result.status : "accepted";
|
|
1795
2240
|
const error = result && result.error ? result.error : undefined;
|
|
1796
|
-
return { unicast:
|
|
2241
|
+
return { unicast: formatSendAckCompat(id, status, error) };
|
|
1797
2242
|
},
|
|
1798
2243
|
(err) => ({
|
|
1799
|
-
unicast:
|
|
2244
|
+
unicast: formatSendAckCompat(
|
|
1800
2245
|
id,
|
|
1801
2246
|
"rejected",
|
|
1802
2247
|
err && err.message ? err.message : "simulateStream failed",
|
|
@@ -2059,6 +2504,41 @@ function createDownstreamHandler(opts) {
|
|
|
2059
2504
|
);
|
|
2060
2505
|
}
|
|
2061
2506
|
|
|
2507
|
+
/**
|
|
2508
|
+
* Handle "getProviderUsageSnapshot": return the current provider usage snapshot.
|
|
2509
|
+
*
|
|
2510
|
+
* @param {string} clientId
|
|
2511
|
+
* @returns {Promise<{ unicast: string }>|{ unicast: string }}
|
|
2512
|
+
*/
|
|
2513
|
+
function handleGetProviderUsageSnapshot(clientId) {
|
|
2514
|
+
const emptySnapshot = () => ({
|
|
2515
|
+
sessionKey: null,
|
|
2516
|
+
provider: null,
|
|
2517
|
+
displayName: null,
|
|
2518
|
+
limitingWindowKey: null,
|
|
2519
|
+
windows: [],
|
|
2520
|
+
fetchedAtMs: Date.now(),
|
|
2521
|
+
stale: true,
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2524
|
+
if (!onGetProviderUsageSnapshot) {
|
|
2525
|
+
return {
|
|
2526
|
+
unicast: formatProviderUsageSnapshot(emptySnapshot()),
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
return Promise.resolve(onGetProviderUsageSnapshot()).then(
|
|
2530
|
+
(payload) => ({
|
|
2531
|
+
unicast: formatProviderUsageSnapshot(payload || {}),
|
|
2532
|
+
}),
|
|
2533
|
+
(err) => {
|
|
2534
|
+
logger.error(`[downstream] getProviderUsageSnapshot failed: ${err.message}`);
|
|
2535
|
+
return {
|
|
2536
|
+
unicast: formatProviderUsageSnapshot(emptySnapshot()),
|
|
2537
|
+
};
|
|
2538
|
+
},
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2062
2542
|
/**
|
|
2063
2543
|
* Handle "getStatus": return the current status snapshot.
|
|
2064
2544
|
*
|
|
@@ -2143,6 +2623,53 @@ function createDownstreamHandler(opts) {
|
|
|
2143
2623
|
);
|
|
2144
2624
|
}
|
|
2145
2625
|
|
|
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
|
+
function handleCompactSession(clientId, msg) {
|
|
2634
|
+
if (!onCompactSession) {
|
|
2635
|
+
return Promise.resolve({
|
|
2636
|
+
unicast: formatCompactSessionAck({
|
|
2637
|
+
status: "rejected",
|
|
2638
|
+
requestId: msg && msg.requestId,
|
|
2639
|
+
error: "compactSession is not available",
|
|
2640
|
+
}),
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
const sessionKey =
|
|
2644
|
+
msg && typeof msg.sessionKey === "string" && msg.sessionKey
|
|
2645
|
+
? msg.sessionKey
|
|
2646
|
+
: null;
|
|
2647
|
+
if (!sessionKey) {
|
|
2648
|
+
return Promise.resolve({
|
|
2649
|
+
unicast: formatCompactSessionAck({
|
|
2650
|
+
status: "rejected",
|
|
2651
|
+
requestId: msg && msg.requestId,
|
|
2652
|
+
error: "sessionKey is required",
|
|
2653
|
+
}),
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
return Promise.resolve(onCompactSession({ sessionKey })).then(
|
|
2657
|
+
(result) => ({
|
|
2658
|
+
unicast: formatCompactSessionAck({
|
|
2659
|
+
...(result || { status: "accepted" }),
|
|
2660
|
+
requestId: msg.requestId,
|
|
2661
|
+
}),
|
|
2662
|
+
}),
|
|
2663
|
+
(err) => ({
|
|
2664
|
+
unicast: formatCompactSessionAck({
|
|
2665
|
+
status: "rejected",
|
|
2666
|
+
requestId: msg.requestId,
|
|
2667
|
+
error: err && err.message ? err.message : "compactSession failed",
|
|
2668
|
+
}),
|
|
2669
|
+
}),
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2146
2673
|
/**
|
|
2147
2674
|
* Handle "getEvenAiSettings": read relay-owned Even AI settings.
|
|
2148
2675
|
*
|
|
@@ -2338,6 +2865,65 @@ function createDownstreamHandler(opts) {
|
|
|
2338
2865
|
);
|
|
2339
2866
|
}
|
|
2340
2867
|
|
|
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
|
+
function handleSetUserSessionTitle(clientId, msg) {
|
|
2877
|
+
if (typeof onSetUserSessionTitle !== "function") return null;
|
|
2878
|
+
const sessionKey =
|
|
2879
|
+
typeof msg.sessionKey === "string" ? msg.sessionKey.trim() : "";
|
|
2880
|
+
const title = typeof msg.title === "string" ? msg.title.trim() : "";
|
|
2881
|
+
if (!sessionKey || !title) return null;
|
|
2882
|
+
if (title.length > 55) return null;
|
|
2883
|
+
onSetUserSessionTitle(sessionKey, title);
|
|
2884
|
+
return null;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
function handleSetSessionPinned(clientId, msg) {
|
|
2888
|
+
if (typeof onSetSessionPinned !== "function") return null;
|
|
2889
|
+
const sessionKey =
|
|
2890
|
+
typeof msg.sessionKey === "string" ? msg.sessionKey.trim() : "";
|
|
2891
|
+
const pinned = msg.pinned === true;
|
|
2892
|
+
const kind = msg.kind;
|
|
2893
|
+
if (!sessionKey || (kind !== "ocuclaw" && kind !== "evenai")) {
|
|
2894
|
+
return { unicast: formatError("invalid_session_pin_request") };
|
|
2895
|
+
}
|
|
2896
|
+
const result = onSetSessionPinned(sessionKey, pinned, kind);
|
|
2897
|
+
if (result && result.ok === false) {
|
|
2898
|
+
const code = result.reason === "cap" ? "pin_cap_reached" : "invalid_session_pin_request";
|
|
2899
|
+
return { unicast: formatError(code) };
|
|
2900
|
+
}
|
|
2901
|
+
return null;
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
function handleDeleteSessions(clientId, msg) {
|
|
2905
|
+
if (typeof onDeleteSessions !== "function") return null;
|
|
2906
|
+
const sessionKeys = Array.isArray(msg.sessionKeys) ? msg.sessionKeys.filter((k) => typeof k === "string" && k) : [];
|
|
2907
|
+
const kind = msg.kind;
|
|
2908
|
+
const switchBeforeDelete = msg.switchBeforeDelete === true;
|
|
2909
|
+
if (sessionKeys.length === 0 || (kind !== "ocuclaw" && kind !== "evenai")) {
|
|
2910
|
+
return { unicast: formatError("invalid_session_delete_request") };
|
|
2911
|
+
}
|
|
2912
|
+
onDeleteSessions(sessionKeys, kind, switchBeforeDelete);
|
|
2913
|
+
return null;
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
function handleSearchTranscripts(clientId, msg) {
|
|
2917
|
+
if (typeof onSearchTranscripts !== "function") return null;
|
|
2918
|
+
const query = typeof msg.query === "string" ? msg.query : "";
|
|
2919
|
+
const kind = msg.kind;
|
|
2920
|
+
if (!query.trim() || (kind !== "ocuclaw" && kind !== "evenai")) {
|
|
2921
|
+
return { unicast: formatError("invalid_transcript_search_request") };
|
|
2922
|
+
}
|
|
2923
|
+
onSearchTranscripts(clientId, query, kind);
|
|
2924
|
+
return null;
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2341
2927
|
/**
|
|
2342
2928
|
* Handle a "slashCommand" message: forward a slash command to OpenClaw.
|
|
2343
2929
|
*
|
|
@@ -2541,6 +3127,36 @@ function createDownstreamHandler(opts) {
|
|
|
2541
3127
|
}
|
|
2542
3128
|
}
|
|
2543
3129
|
|
|
3130
|
+
function handleTraceLogSet(clientId, msg) {
|
|
3131
|
+
if (!onTraceLogSet) {
|
|
3132
|
+
return { unicast: formatError("trace-log-set is not available") };
|
|
3133
|
+
}
|
|
3134
|
+
let payload;
|
|
3135
|
+
try {
|
|
3136
|
+
payload = parseTraceLogSet(msg);
|
|
3137
|
+
} catch (err) {
|
|
3138
|
+
return { unicast: formatError(err.message) };
|
|
3139
|
+
}
|
|
3140
|
+
try {
|
|
3141
|
+
const result = onTraceLogSet(clientId, payload) || { ok: true };
|
|
3142
|
+
return { unicast: formatTraceLog(result) };
|
|
3143
|
+
} catch (err) {
|
|
3144
|
+
return { unicast: formatError(err.message || "trace-log-set failed") };
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
function handleTraceLogGet(clientId) {
|
|
3149
|
+
if (!onTraceLogGet) {
|
|
3150
|
+
return { unicast: formatError("trace-log-get is not available") };
|
|
3151
|
+
}
|
|
3152
|
+
try {
|
|
3153
|
+
const result = onTraceLogGet(clientId) || { ok: true };
|
|
3154
|
+
return { unicast: formatTraceLog(result) };
|
|
3155
|
+
} catch (err) {
|
|
3156
|
+
return { unicast: formatError(err.message || "trace-log-get failed") };
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
|
|
2544
3160
|
/**
|
|
2545
3161
|
* Handle a "remote-control" message: dispatch remote actions to app clients.
|
|
2546
3162
|
*/
|
|
@@ -2591,6 +3207,139 @@ function createDownstreamHandler(opts) {
|
|
|
2591
3207
|
}
|
|
2592
3208
|
}
|
|
2593
3209
|
|
|
3210
|
+
function handleReadinessProbe(clientId, msg) {
|
|
3211
|
+
if (!onReadinessProbe) {
|
|
3212
|
+
return { unicast: formatError("readiness probe is not available") };
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
let payload;
|
|
3216
|
+
try {
|
|
3217
|
+
payload = parseReadinessProbe(msg);
|
|
3218
|
+
} catch (err) {
|
|
3219
|
+
return { unicast: formatError(err.message) };
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
const finalize = (result) => {
|
|
3223
|
+
const resolved = result || {};
|
|
3224
|
+
const requestId = resolved.requestId || payload.requestId;
|
|
3225
|
+
if (
|
|
3226
|
+
resolved.ok === false ||
|
|
3227
|
+
!resolved.targetClientId ||
|
|
3228
|
+
!resolved.probe
|
|
3229
|
+
) {
|
|
3230
|
+
return {
|
|
3231
|
+
unicast: formatReadinessProbeAck({
|
|
3232
|
+
ok: false,
|
|
3233
|
+
requestId,
|
|
3234
|
+
reasonCode: resolved.reasonCode || null,
|
|
3235
|
+
message: resolved.message || "readiness probe was not dispatched",
|
|
3236
|
+
activeSessionKey: resolved.activeSessionKey || null,
|
|
3237
|
+
emittedAtMs: resolved.emittedAtMs || null,
|
|
3238
|
+
}),
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
return {
|
|
3243
|
+
readinessProbe: {
|
|
3244
|
+
requestId,
|
|
3245
|
+
targetClientId: resolved.targetClientId,
|
|
3246
|
+
message: formatReadinessProbeRequest(resolved.probe),
|
|
3247
|
+
},
|
|
3248
|
+
};
|
|
3249
|
+
};
|
|
3250
|
+
|
|
3251
|
+
try {
|
|
3252
|
+
const result = onReadinessProbe(clientId, payload);
|
|
3253
|
+
if (result && typeof result.then === "function") {
|
|
3254
|
+
return result.then(
|
|
3255
|
+
(resolved) => finalize(resolved),
|
|
3256
|
+
(err) => ({
|
|
3257
|
+
unicast: formatError(err.message || "readiness probe failed"),
|
|
3258
|
+
}),
|
|
3259
|
+
);
|
|
3260
|
+
}
|
|
3261
|
+
return finalize(result);
|
|
3262
|
+
} catch (err) {
|
|
3263
|
+
return { unicast: formatError(err.message || "readiness probe failed") };
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
function handleAutomationState(clientId, msg) {
|
|
3268
|
+
let payload;
|
|
3269
|
+
try {
|
|
3270
|
+
payload = parseAutomationStateGet(msg);
|
|
3271
|
+
} catch (err) {
|
|
3272
|
+
return {
|
|
3273
|
+
unicast: formatAutomationStateSnapshot({
|
|
3274
|
+
ok: false,
|
|
3275
|
+
requestId:
|
|
3276
|
+
msg && typeof msg.requestId === "string" ? msg.requestId : null,
|
|
3277
|
+
reasonCode: "snapshot_unavailable",
|
|
3278
|
+
message: err.message,
|
|
3279
|
+
}),
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
if (!onAutomationState) {
|
|
3284
|
+
return null;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
const finalize = (result) => {
|
|
3288
|
+
const resolved = result || {};
|
|
3289
|
+
const requestId = resolved.requestId || payload.requestId;
|
|
3290
|
+
if (
|
|
3291
|
+
resolved.ok === false ||
|
|
3292
|
+
!resolved.targetClientId ||
|
|
3293
|
+
!resolved.request
|
|
3294
|
+
) {
|
|
3295
|
+
return {
|
|
3296
|
+
unicast: formatAutomationStateSnapshot({
|
|
3297
|
+
ok: false,
|
|
3298
|
+
requestId,
|
|
3299
|
+
reasonCode: resolved.reasonCode || "snapshot_unavailable",
|
|
3300
|
+
message:
|
|
3301
|
+
resolved.message || "automation state request was not dispatched",
|
|
3302
|
+
}),
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
return {
|
|
3307
|
+
automationStateRequest: {
|
|
3308
|
+
requestId,
|
|
3309
|
+
targetClientId: resolved.targetClientId,
|
|
3310
|
+
message: formatAutomationStateRequest(resolved.request),
|
|
3311
|
+
},
|
|
3312
|
+
};
|
|
3313
|
+
};
|
|
3314
|
+
|
|
3315
|
+
try {
|
|
3316
|
+
const result = onAutomationState(clientId, payload);
|
|
3317
|
+
if (result && typeof result.then === "function") {
|
|
3318
|
+
return result.then(
|
|
3319
|
+
(resolved) => finalize(resolved),
|
|
3320
|
+
(err) => ({
|
|
3321
|
+
unicast: formatAutomationStateSnapshot({
|
|
3322
|
+
ok: false,
|
|
3323
|
+
requestId: payload.requestId,
|
|
3324
|
+
reasonCode: "snapshot_unavailable",
|
|
3325
|
+
message: err.message || "automation state request failed",
|
|
3326
|
+
}),
|
|
3327
|
+
}),
|
|
3328
|
+
);
|
|
3329
|
+
}
|
|
3330
|
+
return finalize(result);
|
|
3331
|
+
} catch (err) {
|
|
3332
|
+
return {
|
|
3333
|
+
unicast: formatAutomationStateSnapshot({
|
|
3334
|
+
ok: false,
|
|
3335
|
+
requestId: payload.requestId,
|
|
3336
|
+
reasonCode: "snapshot_unavailable",
|
|
3337
|
+
message: err.message || "automation state request failed",
|
|
3338
|
+
}),
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
|
|
2594
3343
|
// --- Public API ---
|
|
2595
3344
|
|
|
2596
3345
|
return {
|
|
@@ -2639,6 +3388,14 @@ function createDownstreamHandler(opts) {
|
|
|
2639
3388
|
return handleSwitchSession(clientId, msg);
|
|
2640
3389
|
case APP_PROTOCOL.sessionCreate:
|
|
2641
3390
|
return handleNewSession(clientId);
|
|
3391
|
+
case APP_PROTOCOL.sessionTitleSet:
|
|
3392
|
+
return handleSetUserSessionTitle(clientId, msg);
|
|
3393
|
+
case "ocuclaw.session.pinned.set":
|
|
3394
|
+
return handleSetSessionPinned(clientId, msg);
|
|
3395
|
+
case "ocuclaw.session.delete":
|
|
3396
|
+
return handleDeleteSessions(clientId, msg);
|
|
3397
|
+
case "ocuclaw.session.transcripts.search":
|
|
3398
|
+
return handleSearchTranscripts(clientId, msg);
|
|
2642
3399
|
case APP_PROTOCOL.modelCatalogGet:
|
|
2643
3400
|
return handleGetModelsCatalog(clientId);
|
|
2644
3401
|
case APP_PROTOCOL.skillsCatalogGet:
|
|
@@ -2647,6 +3404,9 @@ function createDownstreamHandler(opts) {
|
|
|
2647
3404
|
case APP_PROTOCOL.sonioxModelsGet:
|
|
2648
3405
|
case "getSonioxModels":
|
|
2649
3406
|
return handleGetSonioxModels(clientId);
|
|
3407
|
+
case APP_PROTOCOL.providerUsageGet:
|
|
3408
|
+
case "getProviderUsageSnapshot":
|
|
3409
|
+
return handleGetProviderUsageSnapshot(clientId);
|
|
2650
3410
|
case APP_PROTOCOL.statusGet:
|
|
2651
3411
|
case "getStatus":
|
|
2652
3412
|
return handleGetStatus(clientId);
|
|
@@ -2654,6 +3414,8 @@ function createDownstreamHandler(opts) {
|
|
|
2654
3414
|
return handleGetSessionModelConfig(clientId);
|
|
2655
3415
|
case APP_PROTOCOL.sessionConfigSet:
|
|
2656
3416
|
return handleSetSessionModelConfig(clientId, msg);
|
|
3417
|
+
case APP_PROTOCOL.sessionCompact:
|
|
3418
|
+
return handleCompactSession(clientId, msg);
|
|
2657
3419
|
case APP_PROTOCOL.evenAiSettingsGet:
|
|
2658
3420
|
return handleGetEvenAiSettings(clientId);
|
|
2659
3421
|
case APP_PROTOCOL.evenAiSessionList:
|
|
@@ -2679,10 +3441,83 @@ function createDownstreamHandler(opts) {
|
|
|
2679
3441
|
return handleDebugSet(clientId, msg);
|
|
2680
3442
|
case "debug-dump":
|
|
2681
3443
|
return handleDebugDump(clientId, msg);
|
|
3444
|
+
case "trace-log-set":
|
|
3445
|
+
return handleTraceLogSet(clientId, msg);
|
|
3446
|
+
case "trace-log-get":
|
|
3447
|
+
return handleTraceLogGet(clientId);
|
|
2682
3448
|
case "remote-control":
|
|
2683
3449
|
return handleRemoteControl(clientId, msg);
|
|
3450
|
+
case APP_PROTOCOL.automationStateGet:
|
|
3451
|
+
return handleAutomationState(clientId, msg);
|
|
3452
|
+
case APP_PROTOCOL.readinessProbeRequest:
|
|
3453
|
+
return handleReadinessProbe(clientId, msg);
|
|
2684
3454
|
case APP_PROTOCOL.debugEvent:
|
|
2685
3455
|
return handleEventDebug(clientId, msg);
|
|
3456
|
+
case "glasses_ui_result":
|
|
3457
|
+
if (typeof onGlassesUiResult === "function") {
|
|
3458
|
+
try {
|
|
3459
|
+
onGlassesUiResult({
|
|
3460
|
+
surfaceId: typeof msg.surfaceId === "string" ? msg.surfaceId : "",
|
|
3461
|
+
outcome: msg.outcome,
|
|
3462
|
+
});
|
|
3463
|
+
} catch (err) {
|
|
3464
|
+
logger.warn(
|
|
3465
|
+
`[downstream] glasses_ui_result handler threw: ${err && err.message ? err.message : err}`,
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
return null;
|
|
3470
|
+
case "glasses_ui_nav_event":
|
|
3471
|
+
// Client-reported nav-stack transition (push reports the PARENT
|
|
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.
|
|
3475
|
+
if (typeof onGlassesUiNavEvent === "function") {
|
|
3476
|
+
try {
|
|
3477
|
+
onGlassesUiNavEvent({
|
|
3478
|
+
surfaceId: typeof msg.surfaceId === "string" ? msg.surfaceId : "",
|
|
3479
|
+
depth: Number.isFinite(msg.depth) ? Math.max(1, Math.floor(msg.depth)) : 1,
|
|
3480
|
+
});
|
|
3481
|
+
} catch (err) {
|
|
3482
|
+
logger.warn(
|
|
3483
|
+
`[downstream] glasses_ui_nav_event handler threw: ${err && err.message ? err.message : err}`,
|
|
3484
|
+
);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
return null;
|
|
3488
|
+
case "glasses_ui_render":
|
|
3489
|
+
// Debug inject path used by tools/debugctl.js glasses-ui render to
|
|
3490
|
+
// exercise the simulator paint loop without spinning up an agent.
|
|
3491
|
+
if (typeof onGlassesUiRenderInject === "function") {
|
|
3492
|
+
try {
|
|
3493
|
+
onGlassesUiRenderInject({
|
|
3494
|
+
surfaceId: typeof msg.surfaceId === "string" ? msg.surfaceId : "",
|
|
3495
|
+
depth: Number.isFinite(msg.depth) ? Math.max(1, Math.floor(msg.depth)) : 1,
|
|
3496
|
+
spec: msg.spec,
|
|
3497
|
+
});
|
|
3498
|
+
} catch (err) {
|
|
3499
|
+
logger.warn(
|
|
3500
|
+
`[downstream] glasses_ui_render inject handler threw: ${err && err.message ? err.message : err}`,
|
|
3501
|
+
);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
return null;
|
|
3505
|
+
case "device_info_response":
|
|
3506
|
+
if (typeof onDeviceInfoResponse === "function") {
|
|
3507
|
+
try {
|
|
3508
|
+
onDeviceInfoResponse({
|
|
3509
|
+
requestId: typeof msg.requestId === "string" ? msg.requestId : "",
|
|
3510
|
+
ok: msg.ok === true,
|
|
3511
|
+
code: typeof msg.code === "string" ? msg.code : undefined,
|
|
3512
|
+
data: msg.data && typeof msg.data === "object" ? msg.data : undefined,
|
|
3513
|
+
});
|
|
3514
|
+
} catch (err) {
|
|
3515
|
+
logger.warn(
|
|
3516
|
+
`[downstream] device_info_response handler threw: ${err && err.message ? err.message : err}`,
|
|
3517
|
+
);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
return null;
|
|
2686
3521
|
default:
|
|
2687
3522
|
return null;
|
|
2688
3523
|
}
|
|
@@ -2691,7 +3526,8 @@ function createDownstreamHandler(opts) {
|
|
|
2691
3526
|
formatPages,
|
|
2692
3527
|
formatStatus,
|
|
2693
3528
|
formatActivity,
|
|
2694
|
-
|
|
3529
|
+
formatTyping,
|
|
3530
|
+
formatSendAck: formatSendAckCompat,
|
|
2695
3531
|
formatProtocol,
|
|
2696
3532
|
formatStreaming,
|
|
2697
3533
|
formatSessions,
|
|
@@ -2699,6 +3535,7 @@ function createDownstreamHandler(opts) {
|
|
|
2699
3535
|
formatModelsCatalog,
|
|
2700
3536
|
formatSkillsCatalog,
|
|
2701
3537
|
formatSonioxModels,
|
|
3538
|
+
formatProviderUsageSnapshot,
|
|
2702
3539
|
formatSessionModelConfig,
|
|
2703
3540
|
formatSessionModelConfigAck,
|
|
2704
3541
|
formatEvenAiSettings,
|
|
@@ -2709,8 +3546,8 @@ function createDownstreamHandler(opts) {
|
|
|
2709
3546
|
formatApproval,
|
|
2710
3547
|
formatApprovalResolved,
|
|
2711
3548
|
formatApprovalResponseAck,
|
|
2712
|
-
formatTranscription,
|
|
2713
3549
|
formatListenCommitted,
|
|
3550
|
+
formatEvenAiListenIntercepted,
|
|
2714
3551
|
formatListenEnded,
|
|
2715
3552
|
formatListenError,
|
|
2716
3553
|
formatListenReady,
|
|
@@ -2721,6 +3558,10 @@ function createDownstreamHandler(opts) {
|
|
|
2721
3558
|
formatDebugConfigSnapshot,
|
|
2722
3559
|
formatRemoteControl,
|
|
2723
3560
|
formatRemoteControlAck,
|
|
3561
|
+
formatAutomationStateRequest,
|
|
3562
|
+
formatAutomationStateSnapshot,
|
|
3563
|
+
formatReadinessProbeRequest,
|
|
3564
|
+
formatReadinessProbeAck,
|
|
2724
3565
|
formatError,
|
|
2725
3566
|
|
|
2726
3567
|
/**
|