ocuclaw 1.3.3 → 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 +2 -24
- 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 +601 -290
- package/dist/runtime/relay-service.js +19 -47
- 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 +103 -41
- package/dist/runtime/relay-worker-transport.js +150 -17
- 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 +22 -77
- 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 +5 -39
- package/dist/tools/glasses-ui-recipes.js +92 -101
- package/dist/tools/glasses-ui-surfaces.js +31 -163
- 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 +87 -451
- package/dist/tools/glasses-ui-voicemail.js +6 -63
- package/dist/tools/glasses-ui-wake.js +9 -76
- 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/dist/runtime/protocol-adapter.js +0 -387
|
@@ -1,43 +1,14 @@
|
|
|
1
|
-
// Voicemail delivery for parked glasses events (roadmap 7b, §2.6 W+P+L).
|
|
2
|
-
//
|
|
3
|
-
// A parked, ✓-acked event whose wake could not run (no dispatch lane, or the
|
|
4
|
-
// dispatch failed past its retry — the 6f wake outbox) or whose surface was
|
|
5
|
-
// destructively reaped (the 6a per-session dead-letter) must not wait in
|
|
6
|
-
// silence for the wearer to speak first. This module drains both sources into
|
|
7
|
-
// a refs-only system-context fragment for the session's NEXT genuine turn —
|
|
8
|
-
// the before_prompt_build hook registered by registerGlassesUiTool calls
|
|
9
|
-
// buildInjection(sessionKey) and returns {appendSystemContext} (Channel-2
|
|
10
|
-
// class: hash-exempt, per-turn).
|
|
11
|
-
//
|
|
12
|
-
// Locked rules carried over from the 6f security review (§2.6 amendments):
|
|
13
|
-
// - REFS ONLY: surfaceUuid is pattern-validated against the plugin-mint shape
|
|
14
|
-
// and REPLACED WHOLESALE when off-pattern; result is enum-allowlisted; ints
|
|
15
|
-
// are Number.isFinite-coerced. Outcome/label text (selected_text etc.)
|
|
16
|
-
// NEVER reaches the prompt — tapped content arrives only through a surface
|
|
17
|
-
// collect, as tool-result data.
|
|
18
|
-
// - Explicit non-wearer provenance header.
|
|
19
|
-
// - Idempotency: an entry injects at most once (keyed on the wake
|
|
20
|
-
// idempotencyKey / a minted voicemail key), so re-entering entries and
|
|
21
|
-
// double-firing hooks cannot duplicate a delivery.
|
|
22
|
-
// - TTL: stale voicemail drops LOUDLY (voicemail_expired lifecycle), never
|
|
23
|
-
// silently; dead-letter staleAfterMs (6b) additionally flags entries the
|
|
24
|
-
// agent should re-confirm before acting on.
|
|
25
|
-
//
|
|
26
|
-
// Leaf module (CJS emitter constraint): all state injected, no runtime deps.
|
|
27
|
-
|
|
28
1
|
import { sanitizeWakeToken } from "./glasses-ui-wake.js";
|
|
29
2
|
import { normalizeGlassesSessionKey } from "./glasses-ui-surfaces.js";
|
|
30
3
|
|
|
31
4
|
export const DEFAULT_VOICEMAIL_TTL_MS = 30 * 60_000;
|
|
32
5
|
export const VOICEMAIL_MAX_ENTRIES_PER_INJECTION = 8;
|
|
33
|
-
|
|
34
|
-
// disconnected session that never speaks again must not grow memory forever
|
|
35
|
-
// (review P3). Newest kept, eviction is loud.
|
|
6
|
+
|
|
36
7
|
export const VOICEMAIL_PENDING_CAP_PER_SESSION = 32;
|
|
37
8
|
const DELIVERED_KEY_CAP = 256;
|
|
38
9
|
|
|
39
10
|
const RESULT_ENUM = new Set(["selected", "back"]);
|
|
40
|
-
|
|
11
|
+
|
|
41
12
|
const REAP_REASON_ENUM = new Set(["drain_session", "drain_all", "exit", "pop_back"]);
|
|
42
13
|
|
|
43
14
|
function coerceInt(value) {
|
|
@@ -48,8 +19,6 @@ function sanitizeResult(value) {
|
|
|
48
19
|
return RESULT_ENUM.has(value) ? value : "event";
|
|
49
20
|
}
|
|
50
21
|
|
|
51
|
-
// idempotencyKey reaches the prompt verbatim-ish; constrain to the same safe
|
|
52
|
-
// charset family as the minted forms and replace wholesale when off-pattern.
|
|
53
22
|
const IDEMPOTENCY_KEY_PATTERN = /^[a-z0-9:._-]{1,80}$/i;
|
|
54
23
|
function sanitizeIdempotencyKey(value) {
|
|
55
24
|
const raw = String(value == null ? "" : value);
|
|
@@ -69,15 +38,8 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
69
38
|
const emitLifecycle =
|
|
70
39
|
typeof deps.emitLifecycle === "function" ? deps.emitLifecycle : () => {};
|
|
71
40
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// destroys another session's owed voicemail. Bounded per session and
|
|
75
|
-
// TTL-checked AT INGESTION (review P3) — buckets for silent sessions
|
|
76
|
-
// cannot grow without bound, and empty buckets are deleted.
|
|
77
|
-
const pendingBySession = new Map(); // normalized sessionKey -> [entry]
|
|
78
|
-
// Injected-once guard (bounded FIFO), keyed on the CANONICAL per-event key
|
|
79
|
-
// (surfaceUuid:eventId) so the same parked tap arriving via BOTH the wake
|
|
80
|
-
// outbox and the dead-letter injects exactly once (review P2).
|
|
41
|
+
const pendingBySession = new Map();
|
|
42
|
+
|
|
81
43
|
const deliveredKeys = new Set();
|
|
82
44
|
|
|
83
45
|
function rememberDelivered(key) {
|
|
@@ -92,9 +54,6 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
92
54
|
return `${entry.surfaceUuid}:${entry.eventId === null ? 0 : entry.eventId}`;
|
|
93
55
|
}
|
|
94
56
|
|
|
95
|
-
// Route a batch of normalized entries into their sessions' pending buffers:
|
|
96
|
-
// TTL-expired entries drop here (loudly), and each bucket trims to the cap
|
|
97
|
-
// (newest kept, loudly). One emit per session per batch.
|
|
98
57
|
function ingest(entries, nowMs) {
|
|
99
58
|
const bySession = new Map();
|
|
100
59
|
for (const entry of entries) {
|
|
@@ -125,7 +84,6 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
125
84
|
}
|
|
126
85
|
}
|
|
127
86
|
|
|
128
|
-
// Normalize an owed wake (outbox shape) into the voicemail entry form.
|
|
129
87
|
function fromOutbox(record) {
|
|
130
88
|
const surfaceUuid = sanitizeWakeToken(record && record.surfaceUuid);
|
|
131
89
|
const eventId = coerceInt(record && record.eventId);
|
|
@@ -144,7 +102,6 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
144
102
|
};
|
|
145
103
|
}
|
|
146
104
|
|
|
147
|
-
// Normalize a dead-letter record's events (store shape) into entries.
|
|
148
105
|
function fromDeadLetter(sessionKey, record) {
|
|
149
106
|
const surfaceUuid = sanitizeWakeToken(record && record.surfaceUuid);
|
|
150
107
|
const reason =
|
|
@@ -192,11 +149,6 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
192
149
|
return parts.join(" ");
|
|
193
150
|
}
|
|
194
151
|
|
|
195
|
-
// Sweep ALL buffered buckets for TTL expiry on every build (review round 2):
|
|
196
|
-
// session keys rotate on relay restarts/reconnects, so abandoned sessions
|
|
197
|
-
// are a recurring shape — without this, their buckets (each capped, but
|
|
198
|
-
// unbounded in NUMBER) would outlive them forever. After a sweep, total
|
|
199
|
-
// buffered memory is bounded by sessions active within one TTL window.
|
|
200
152
|
function sweepExpired(nowMs) {
|
|
201
153
|
for (const [key, list] of pendingBySession) {
|
|
202
154
|
const fresh = list.filter((entry) => {
|
|
@@ -225,8 +177,6 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
225
177
|
const nowMs = now();
|
|
226
178
|
sweepExpired(nowMs);
|
|
227
179
|
|
|
228
|
-
// Pull both sources through the bounded ingest. The outbox drain returns
|
|
229
|
-
// every session's entries — non-target ones land in their own buffers.
|
|
230
180
|
ingest(
|
|
231
181
|
[
|
|
232
182
|
...drainWakeOutbox().map((record) => fromOutbox(record)),
|
|
@@ -237,15 +187,11 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
237
187
|
|
|
238
188
|
const pending = pendingBySession.get(sessionKey);
|
|
239
189
|
if (!pending || pending.length === 0) {
|
|
240
|
-
pendingBySession.delete(sessionKey);
|
|
190
|
+
pendingBySession.delete(sessionKey);
|
|
241
191
|
return null;
|
|
242
192
|
}
|
|
243
193
|
pendingBySession.delete(sessionKey);
|
|
244
194
|
|
|
245
|
-
// In-batch collapse by the canonical per-event key (review P2): the same
|
|
246
|
-
// parked tap can arrive via the wake outbox AND the dead-letter in one
|
|
247
|
-
// pass — the dead-letter entry wins (the surface is genuinely gone, so a
|
|
248
|
-
// "may still be live" line for it would be false).
|
|
249
195
|
const byEvent = new Map();
|
|
250
196
|
for (const entry of pending) {
|
|
251
197
|
const key = dedupeKeyOf(entry);
|
|
@@ -266,13 +212,11 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
266
212
|
deliverable.push(entry);
|
|
267
213
|
}
|
|
268
214
|
if (dropped > 0) {
|
|
269
|
-
|
|
215
|
+
|
|
270
216
|
emitLifecycle("voicemail_expired", "warn", { sessionKey, dropped, ttlMs });
|
|
271
217
|
}
|
|
272
218
|
if (deliverable.length === 0) return null;
|
|
273
219
|
|
|
274
|
-
// Newest entries are the wearer's most recent intent — keep those when
|
|
275
|
-
// capping, in chronological order.
|
|
276
220
|
const shown = deliverable.slice(-maxEntries);
|
|
277
221
|
const overflow = deliverable.length - shown.length;
|
|
278
222
|
const lines = [
|
|
@@ -295,5 +239,4 @@ export function createGlassesVoicemail(deps = {}) {
|
|
|
295
239
|
return { buildInjection, pendingSessionCount };
|
|
296
240
|
}
|
|
297
241
|
|
|
298
|
-
// Single line: the CJS emitter strips only `^export default .*;$` (one line).
|
|
299
242
|
export default { createGlassesVoicemail, DEFAULT_VOICEMAIL_TTL_MS, VOICEMAIL_MAX_ENTRIES_PER_INJECTION };
|
|
@@ -1,59 +1,11 @@
|
|
|
1
|
-
// Tap-to-wake for parked glasses surfaces (roadmap 6f, §2.6 W).
|
|
2
|
-
//
|
|
3
|
-
// A REAL parked gesture buys ONE agent turn via the plugin's own gateway
|
|
4
|
-
// client (the voice-send lane — request("agent", ...)). This module is a
|
|
5
|
-
// dependency-free LEAF (CJS emitter constraint — see glasses-ui-limits.ts):
|
|
6
|
-
// it builds the wake envelope and owns the arbitration policy; transport is
|
|
7
|
-
// injected (the relay facade's dispatchGlassesWake).
|
|
8
|
-
//
|
|
9
|
-
// Locked contract (§2.6, panel amendments 3-4):
|
|
10
|
-
// - wake payload = STRUCTURED REFERENCES ONLY with explicit non-wearer
|
|
11
|
-
// provenance framing; never interpolated label text. SECURITY (review
|
|
12
|
-
// pinned to this ship): every token that reaches the prompt is either
|
|
13
|
-
// numeric-coerced or charset-filtered ([a-zA-Z0-9._:-], <=64 chars), so a
|
|
14
|
-
// hostile client cannot smuggle instruction text through the wake lane —
|
|
15
|
-
// tapped CONTENT only ever reaches the agent through the surface collect
|
|
16
|
-
// (onReattached delivery), where it arrives as data in a tool result, not
|
|
17
|
-
// as a user-role message.
|
|
18
|
-
// - origin-typed envelope: ONLY "gesture" enabled at launch. schedule/
|
|
19
|
-
// threshold/system are reserved categories (event-to-wake is deferred,
|
|
20
|
-
// not foreclosed) — enabling one later is a policy edit here, not a
|
|
21
|
-
// schema migration.
|
|
22
|
-
// - voice absorbs wake: an in-flight/imminent genuine turn (voice send,
|
|
23
|
-
// user send, or an earlier wake's run) suppresses the dispatch; the
|
|
24
|
-
// parked tap rides that turn's collect instead.
|
|
25
|
-
// - taps during an in-flight wake COALESCE into its delivery — never a
|
|
26
|
-
// second submission. A per-session cooldown additionally bounds gesture
|
|
27
|
-
// storms (hostile/buggy client spamming taps) to <=1 turn per window;
|
|
28
|
-
// suppressed taps stay parked in the 6a event log — bounded, never silent.
|
|
29
|
-
// - failed submission: one retry (same idempotencyKey — the gateway honors
|
|
30
|
-
// it as the runId, so a double-delivery dedupes), then the durable wake
|
|
31
|
-
// outbox + a warn lifecycle. Never silent after the ✓-ack (amendment 1);
|
|
32
|
-
// the parked event itself survives in the surface log / dead-letter
|
|
33
|
-
// regardless, so the worst case is delayed collect, not loss. The 7b
|
|
34
|
-
// voicemail leg drains the outbox via enqueueNextTurnInjection.
|
|
35
|
-
|
|
36
1
|
export const GLASSES_WAKE_ENABLED_ORIGINS = ["gesture"];
|
|
37
2
|
|
|
38
3
|
export const DEFAULT_WAKE_COOLDOWN_MS = 5_000;
|
|
39
4
|
|
|
40
|
-
// Outbox bound (review P3 sibling): the no-lane branch pushes per parked tap
|
|
41
|
-
// with no cooldown gate, so a tap storm on a legacy host would otherwise grow
|
|
42
|
-
// this without limit. Newest kept; eviction is loud. The parked events
|
|
43
|
-
// themselves still survive in the surface log / dead-letter — eviction here
|
|
44
|
-
// only forfeits the voicemail NOTICE for the oldest entries.
|
|
45
5
|
export const WAKE_OUTBOX_CAP = 64;
|
|
46
6
|
|
|
47
|
-
// Busy-signal decay: if no activity update arrives for this long, treat the
|
|
48
|
-
// session as idle again (fail open — a stuck-busy would suppress wakes
|
|
49
|
-
// forever, while a wrongly-idle wake merely queues as the next turn).
|
|
50
7
|
export const DEFAULT_AGENT_TURN_BUSY_DECAY_MS = 180_000;
|
|
51
8
|
|
|
52
|
-
// SECURITY: charset-FILTERING is not enough — stripping separators from
|
|
53
|
-
// hostile input still concatenates readable instruction words into the
|
|
54
|
-
// prompt ("su-x\" IGNORE ALL..." -> "su-xIGNOREALL..."). Tokens are instead
|
|
55
|
-
// VALIDATED against the exact plugin-minted shape / a closed enum and
|
|
56
|
-
// replaced wholesale when off-pattern, so hostile bytes never pass through.
|
|
57
9
|
const SURFACE_UUID_PATTERN = /^su-[a-z0-9]{4,24}$/i;
|
|
58
10
|
const WAKE_RESULT_ENUM = new Set(["selected", "back"]);
|
|
59
11
|
|
|
@@ -85,24 +37,13 @@ export function buildWakeMessage(ref) {
|
|
|
85
37
|
].join(" ");
|
|
86
38
|
}
|
|
87
39
|
|
|
88
|
-
// Per-session "an agent turn is in flight or imminent" signal. Fed by the
|
|
89
|
-
// relay: markBusy on every dispatched send (voice/user/wake) and onActivity
|
|
90
|
-
// from the gateway activity stream (normalized phase: start/update = busy
|
|
91
|
-
// refresh, end = idle). Lives here (leaf) so it is unit-testable and shared
|
|
92
|
-
// with the relay without a runtime-module cycle.
|
|
93
40
|
export function createAgentTurnTracker(deps = {}) {
|
|
94
41
|
const now = typeof deps.now === "function" ? deps.now : Date.now;
|
|
95
42
|
const busyDecayMs = Number.isFinite(deps.busyDecayMs)
|
|
96
43
|
? deps.busyDecayMs
|
|
97
44
|
: DEFAULT_AGENT_TURN_BUSY_DECAY_MS;
|
|
98
|
-
const lastSeenBySession = new Map();
|
|
45
|
+
const lastSeenBySession = new Map();
|
|
99
46
|
|
|
100
|
-
// One session arrives under TWO key forms: the relay send path marks busy
|
|
101
|
-
// with the stripped relay key ("ocuclaw:<ts>") while the surface store /
|
|
102
|
-
// gateway hook contexts use the canonical "agent:<id>:<key>". Normalize so
|
|
103
|
-
// both name the same busy slot — without this, a tap in the send→run-start
|
|
104
|
-
// window dispatches a second agent submission during an in-flight genuine
|
|
105
|
-
// turn (live-proven, 2026-06-12 Wave-1 e2e leg 3a).
|
|
106
47
|
function normalizeKey(sessionKey) {
|
|
107
48
|
return sessionKey.replace(/^agent:[^:]+:/, "");
|
|
108
49
|
}
|
|
@@ -147,9 +88,9 @@ export function createGlassesWakeController(deps = {}) {
|
|
|
147
88
|
? deps.wakeCooldownMs
|
|
148
89
|
: DEFAULT_WAKE_COOLDOWN_MS;
|
|
149
90
|
|
|
150
|
-
const inFlightBySession = new Map();
|
|
151
|
-
const lastWakeAtBySession = new Map();
|
|
152
|
-
const outbox = [];
|
|
91
|
+
const inFlightBySession = new Map();
|
|
92
|
+
const lastWakeAtBySession = new Map();
|
|
93
|
+
const outbox = [];
|
|
153
94
|
|
|
154
95
|
function pushOutbox(entry) {
|
|
155
96
|
outbox.push(entry);
|
|
@@ -166,8 +107,7 @@ export function createGlassesWakeController(deps = {}) {
|
|
|
166
107
|
eventId: coerceInt(ref.eventId),
|
|
167
108
|
result: sanitizeWakeResult(ref.result),
|
|
168
109
|
itemIndex: coerceInt(ref.itemIndex),
|
|
169
|
-
|
|
170
|
-
// origin can never dispatch; keep the raw string for the gate.
|
|
110
|
+
|
|
171
111
|
origin: typeof ref.origin === "string" ? ref.origin : "gesture",
|
|
172
112
|
queuedAtMs: coerceInt(ref.queuedAtMs),
|
|
173
113
|
};
|
|
@@ -181,11 +121,7 @@ export function createGlassesWakeController(deps = {}) {
|
|
|
181
121
|
function onParkedGesture(ref) {
|
|
182
122
|
const refs = refsOnly(ref || {});
|
|
183
123
|
if (!dispatchWake) {
|
|
184
|
-
|
|
185
|
-
// keeps its collect-on-next-render semantics AND is owed to the 7b
|
|
186
|
-
// voicemail leg so the next genuine turn hears about it — no longer a
|
|
187
|
-
// silent early-return. Same gates as a dispatch: enabled origin + a
|
|
188
|
-
// session to deliver to.
|
|
124
|
+
|
|
189
125
|
if (GLASSES_WAKE_ENABLED_ORIGINS.includes(refs.origin) && refs.sessionKey) {
|
|
190
126
|
if (refs.queuedAtMs === null) refs.queuedAtMs = now();
|
|
191
127
|
const idempotencyKey = `glasses-wake:${refs.surfaceUuid}:${refs.eventId === null ? 0 : refs.eventId}`;
|
|
@@ -205,8 +141,7 @@ export function createGlassesWakeController(deps = {}) {
|
|
|
205
141
|
const sessionKey = refs.sessionKey;
|
|
206
142
|
if (!sessionKey) return suppress("no_session", refs);
|
|
207
143
|
if (isAgentTurnBusy(sessionKey)) {
|
|
208
|
-
|
|
209
|
-
// collect render or the next genuine turn — no second submission.
|
|
144
|
+
|
|
210
145
|
return suppress("absorbed_by_active_turn", refs);
|
|
211
146
|
}
|
|
212
147
|
if (inFlightBySession.has(sessionKey)) {
|
|
@@ -225,13 +160,12 @@ export function createGlassesWakeController(deps = {}) {
|
|
|
225
160
|
lastWakeAtBySession.set(sessionKey, now());
|
|
226
161
|
const attempt = () => Promise.resolve(dispatchWake(payload));
|
|
227
162
|
const flight = attempt()
|
|
228
|
-
.catch(() => attempt())
|
|
163
|
+
.catch(() => attempt())
|
|
229
164
|
.then(() => {
|
|
230
165
|
emitLifecycle("wake_dispatched", "debug", { ...refs, idempotencyKey });
|
|
231
166
|
})
|
|
232
167
|
.catch((err) => {
|
|
233
|
-
|
|
234
|
-
// outbox for the 7b voicemail leg, loudly.
|
|
168
|
+
|
|
235
169
|
pushOutbox({
|
|
236
170
|
...refs,
|
|
237
171
|
idempotencyKey,
|
|
@@ -258,5 +192,4 @@ export function createGlassesWakeController(deps = {}) {
|
|
|
258
192
|
return { onParkedGesture, peekWakeOutbox, drainWakeOutbox };
|
|
259
193
|
}
|
|
260
194
|
|
|
261
|
-
// Single line: the CJS emitter strips only `^export default .*;$` (one line).
|
|
262
195
|
export default { createGlassesWakeController, createAgentTurnTracker, buildWakeMessage, sanitizeWakeToken, GLASSES_WAKE_ENABLED_ORIGINS };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const SESSION_TITLE_LIMITS = {
|
|
2
|
-
titleMax: 55,
|
|
2
|
+
titleMax: 55,
|
|
3
3
|
};
|
|
4
4
|
|
|
5
5
|
export const sessionTitleParametersSchema = {
|
|
@@ -51,12 +51,7 @@ function isEvenAiDedicatedKey(sessionKey) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function gateReason(sessionKey, deps) {
|
|
54
|
-
|
|
55
|
-
// titling (the distiller), not user-requested renames, so feature_disabled is
|
|
56
|
-
// gone. session_user_locked is gone too — a user who already named a session
|
|
57
|
-
// must be able to rename it again (the lock only blocks the distiller).
|
|
58
|
-
// The structural no_active_session / EvenAI-renamable guards live in the
|
|
59
|
-
// handler body. Only the no-user-message guard remains here.
|
|
54
|
+
|
|
60
55
|
if (
|
|
61
56
|
typeof deps.hasRecordedUserMessage === "function" &&
|
|
62
57
|
!deps.hasRecordedUserMessage(sessionKey)
|
|
@@ -26,7 +26,7 @@ test("explicit rename passes origin user_tool", async () => {
|
|
|
26
26
|
test("a user-locked session can STILL be renamed via the tool", async () => {
|
|
27
27
|
const d = deps({
|
|
28
28
|
setSessionTitle: (k, t, o) => {
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
return { ok: true };
|
|
31
31
|
},
|
|
32
32
|
});
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export const PLUGIN_VERSION = "1.3.
|
|
2
|
-
export const REQUIRES_CLIENT_VERSION = "1.3.
|
|
1
|
+
export const PLUGIN_VERSION = "1.3.4";
|
|
2
|
+
export const REQUIRES_CLIENT_VERSION = "1.3.4";
|
|
3
|
+
export const BUILD_INPUT_HASH = "sha256:2e70aa3160e383e95d494d26249e07351ccc4d5372ff13e40c532a5c408e21d0";
|
package/openclaw.plugin.json
CHANGED
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"help": "Optional. Enables Soniox speech-to-text for voice input.",
|
|
29
29
|
"sensitive": true
|
|
30
30
|
},
|
|
31
|
+
"cartesiaApiKey": {
|
|
32
|
+
"label": "Cartesia API key",
|
|
33
|
+
"help": "Optional. Enables Cartesia Ink-2 speech-to-text for voice input.",
|
|
34
|
+
"sensitive": true
|
|
35
|
+
},
|
|
31
36
|
"evenAiEnabled": {
|
|
32
37
|
"label": "Enable Even AI",
|
|
33
38
|
"help": "Routes Even AI agent requests through OcuClaw. Requires evenAiToken when enabled."
|
|
@@ -45,7 +50,7 @@
|
|
|
45
50
|
},
|
|
46
51
|
"sessionTitleModel": {
|
|
47
52
|
"label": "Session-title model",
|
|
48
|
-
"help": "Optional model override (\"provider/model\") for the background session-title distiller. Leave blank to use your normal model.",
|
|
53
|
+
"help": "Optional model override (\"provider/model\") for the background session-title distiller. This is a lightweight background task, so a small, fast, inexpensive model is a good choice (e.g. anthropic/claude-haiku-4-5). Leave blank to use your normal model.",
|
|
49
54
|
"advanced": true
|
|
50
55
|
},
|
|
51
56
|
"evenAiRoutingMode": {
|
|
@@ -64,13 +69,8 @@
|
|
|
64
69
|
"advanced": true
|
|
65
70
|
},
|
|
66
71
|
"sessionLimit": {
|
|
67
|
-
"label": "
|
|
68
|
-
"help": "
|
|
69
|
-
"advanced": true
|
|
70
|
-
},
|
|
71
|
-
"debugPayloadMaxBytes": {
|
|
72
|
-
"label": "Debug payload size limit",
|
|
73
|
-
"help": "Maximum byte size for debug payloads recorded for debugctl.",
|
|
72
|
+
"label": "WebUI session list size",
|
|
73
|
+
"help": "Recent sessions fetched for the WebUI switcher/search list. Glasses clamp to their own item-count cap.",
|
|
74
74
|
"advanced": true
|
|
75
75
|
},
|
|
76
76
|
"debugNoisyPolicies": {
|
|
@@ -83,6 +83,26 @@
|
|
|
83
83
|
"help": "Allow debugctl-style external tools to call debug-set, debug-dump, and remote-control.",
|
|
84
84
|
"advanced": true
|
|
85
85
|
},
|
|
86
|
+
"allowDebugUpload": {
|
|
87
|
+
"label": "Allow debug-trace upload",
|
|
88
|
+
"help": "Allow the relay to assemble user-initiated debug-trace bundles and hand them to the phone for upload. Requires External debug tools. Off by default.",
|
|
89
|
+
"advanced": true
|
|
90
|
+
},
|
|
91
|
+
"debugUploadMaxZipBytes": {
|
|
92
|
+
"label": "Debug upload max zip size (bytes)",
|
|
93
|
+
"help": "Maximum compressed (uploaded) bundle size; the binding upload cap. Oldest events are trimmed until the zip fits.",
|
|
94
|
+
"advanced": true
|
|
95
|
+
},
|
|
96
|
+
"debugUploadCapturePreset": {
|
|
97
|
+
"label": "Debug upload capture preset (override)",
|
|
98
|
+
"help": "Optional override of the always-armed debug-upload capture category list.",
|
|
99
|
+
"advanced": true
|
|
100
|
+
},
|
|
101
|
+
"debugBundleSaveDir": {
|
|
102
|
+
"label": "Debug bundle save directory",
|
|
103
|
+
"help": "Directory for locally-saved debug bundles. Empty = ~/.openclaw/ocuclaw-debug-bundles.",
|
|
104
|
+
"advanced": true
|
|
105
|
+
},
|
|
86
106
|
"evenAiRequestTimeoutMs": {
|
|
87
107
|
"label": "Even AI request timeout (ms) (deprecated)",
|
|
88
108
|
"advanced": true
|
|
@@ -136,14 +156,13 @@
|
|
|
136
156
|
"sessionLimit": {
|
|
137
157
|
"type": "integer",
|
|
138
158
|
"minimum": 1,
|
|
139
|
-
"default":
|
|
140
|
-
"description": "
|
|
159
|
+
"default": 80,
|
|
160
|
+
"description": "Number of recent sessions fetched for the WebUI session switcher/search list (glasses clamp to their own item-count cap)."
|
|
141
161
|
},
|
|
142
162
|
"debugPayloadMaxBytes": {
|
|
143
163
|
"type": "integer",
|
|
144
164
|
"minimum": 1,
|
|
145
|
-
"
|
|
146
|
-
"description": "Maximum byte size for individual debug payloads recorded for debugctl."
|
|
165
|
+
"description": "Deprecated and ignored: debug payload truncation was removed (2026-05-13); this key has no effect. Retained in the schema only so existing configs that still set it continue to validate."
|
|
147
166
|
},
|
|
148
167
|
"debugNoisyPolicies": {
|
|
149
168
|
"anyOf": [
|
|
@@ -164,10 +183,38 @@
|
|
|
164
183
|
"default": false,
|
|
165
184
|
"description": "Allow debugctl-style external debug tools to use debug-set, debug-dump, and remote-control."
|
|
166
185
|
},
|
|
186
|
+
"allowDebugUpload": {
|
|
187
|
+
"type": "boolean",
|
|
188
|
+
"default": false,
|
|
189
|
+
"description": "Allow the relay to assemble user-initiated debug-trace upload bundles and hand them to the phone (requires externalDebugToolsEnabled). Off by default."
|
|
190
|
+
},
|
|
191
|
+
"debugUploadMaxZipBytes": {
|
|
192
|
+
"type": "integer",
|
|
193
|
+
"minimum": 100000,
|
|
194
|
+
"maximum": 4300000,
|
|
195
|
+
"default": 4000000,
|
|
196
|
+
"description": "Maximum compressed (uploaded) bundle size (bytes); the binding upload cap, kept under the upload backend's limit. Oldest events are trimmed until the zip fits."
|
|
197
|
+
},
|
|
198
|
+
"debugUploadCapturePreset": {
|
|
199
|
+
"type": "array",
|
|
200
|
+
"items": {
|
|
201
|
+
"type": "string"
|
|
202
|
+
},
|
|
203
|
+
"description": "Optional override of the always-armed debug-upload capture preset (list of category names)."
|
|
204
|
+
},
|
|
205
|
+
"debugBundleSaveDir": {
|
|
206
|
+
"type": "string",
|
|
207
|
+
"default": "",
|
|
208
|
+
"description": "Directory for locally-saved debug bundles; empty = ~/.openclaw/ocuclaw-debug-bundles."
|
|
209
|
+
},
|
|
167
210
|
"sonioxApiKey": {
|
|
168
211
|
"type": "string",
|
|
169
212
|
"description": "Optional Soniox API key. Enables Soniox speech-to-text for voice input."
|
|
170
213
|
},
|
|
214
|
+
"cartesiaApiKey": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"description": "Optional Cartesia API key. Enables Cartesia Ink-2 speech-to-text for voice input."
|
|
217
|
+
},
|
|
171
218
|
"evenAiEnabled": {
|
|
172
219
|
"type": "boolean",
|
|
173
220
|
"default": false,
|
|
@@ -234,7 +281,7 @@
|
|
|
234
281
|
},
|
|
235
282
|
"sessionTitleModel": {
|
|
236
283
|
"type": "string",
|
|
237
|
-
"description": "Optional model override (\"provider/model\") for the background session-title distiller. When absent, the user's normal model is used."
|
|
284
|
+
"description": "Optional model override (\"provider/model\") for the background session-title distiller. This is a lightweight background task, so a small, fast, inexpensive model is a good choice (e.g. anthropic/claude-haiku-4-5). When absent, the user's normal model is used."
|
|
238
285
|
}
|
|
239
286
|
},
|
|
240
287
|
"if": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ocuclaw",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"requiresClientVersion": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
|
+
"requiresClientVersion": "1.3.4",
|
|
5
5
|
"description": "OcuClaw for Even Realities G2 smart glasses.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"fflate": "^0.8.3",
|
|
37
38
|
"marked": "^17.0.2",
|
|
38
39
|
"undici": "^6.26.0",
|
|
39
40
|
"ws": "^8.19.0"
|