ocuclaw 1.3.1 → 1.3.3
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/dist/config/runtime-config-session-title-model.test.js +22 -0
- package/dist/config/runtime-config.js +7 -1
- package/dist/domain/glasses-display-system-prompt.js +52 -0
- package/dist/domain/glasses-display-system-prompt.test.js +44 -0
- package/dist/domain/glasses-ui-system-prompt.js +6 -22
- package/dist/domain/glasses-ui-system-prompt.test.js +13 -0
- package/dist/domain/prompt-channel-fragments.js +32 -0
- package/dist/domain/prompt-channel-fragments.test.js +70 -0
- package/dist/gateway/gateway-timing-ledger.js +15 -3
- package/dist/gateway/openclaw-client.js +80 -3
- package/dist/index.js +22 -0
- package/dist/runtime/channel-two-hook.js +36 -0
- package/dist/runtime/container-env.js +41 -0
- package/dist/runtime/display-toggle-states.js +98 -0
- package/dist/runtime/glasses-backpressure-latch.js +115 -0
- package/dist/runtime/register-session-title-distiller.js +100 -0
- package/dist/runtime/relay-core.js +284 -33
- package/dist/runtime/relay-service.js +152 -13
- package/dist/runtime/relay-worker-entry.js +26 -0
- package/dist/runtime/relay-worker-supervisor.js +51 -2
- package/dist/runtime/relay-worker-transport.js +51 -1
- package/dist/runtime/session-service.js +136 -12
- package/dist/runtime/session-title-distiller-budget.js +36 -0
- package/dist/runtime/session-title-distiller-helpers.js +130 -0
- package/dist/runtime/session-title-distiller.js +354 -0
- package/dist/runtime/session-title-record.js +21 -0
- package/dist/runtime/stable-prompt-snapshot.js +119 -0
- package/dist/tools/glasses-ui-cron.js +59 -3
- package/dist/tools/glasses-ui-paint-floor.js +33 -4
- package/dist/tools/glasses-ui-surfaces.js +369 -35
- package/dist/tools/glasses-ui-tool-description.test.js +16 -0
- package/dist/tools/glasses-ui-tool.js +662 -80
- package/dist/tools/glasses-ui-voicemail.js +299 -0
- package/dist/tools/glasses-ui-wake.js +262 -0
- package/dist/tools/session-title-tool.js +14 -76
- package/dist/tools/session-title-tool.test.js +53 -0
- package/dist/version.js +2 -2
- package/openclaw.plugin.json +9 -0
- package/package.json +4 -3
- package/skills/glasses-ui/SKILL.md +26 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createSessionTitleToolHandler, TOOL_DESCRIPTION } from "./session-title-tool.ts";
|
|
4
|
+
|
|
5
|
+
function deps(over = {}) {
|
|
6
|
+
const calls = [];
|
|
7
|
+
return {
|
|
8
|
+
calls,
|
|
9
|
+
peekSessionKey: () => "ocuclaw:123",
|
|
10
|
+
setSessionTitle: (k, t, o) => { calls.push({ k, t, o }); return { ok: true }; },
|
|
11
|
+
...over,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("description is explicit-rename-only", () => {
|
|
16
|
+
assert.match(TOOL_DESCRIPTION, /explicitly asks to rename|user explicitly/i);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("explicit rename passes origin user_tool", async () => {
|
|
20
|
+
const d = deps();
|
|
21
|
+
const h = createSessionTitleToolHandler(d);
|
|
22
|
+
await h.setSessionTitle({ title: "Trip Planning" });
|
|
23
|
+
assert.equal(d.calls[0].o.origin, "user_tool");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("a user-locked session can STILL be renamed via the tool", async () => {
|
|
27
|
+
const d = deps({
|
|
28
|
+
setSessionTitle: (k, t, o) => {
|
|
29
|
+
// service-layer would allow user_tool over a lock; tool must not pre-block
|
|
30
|
+
return { ok: true };
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
const h = createSessionTitleToolHandler(d);
|
|
34
|
+
const r = await h.setSessionTitle({ title: "New Name" });
|
|
35
|
+
assert.deepEqual(r, { ok: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("feature-disabled does NOT block an explicit rename", async () => {
|
|
39
|
+
const d = deps({ isNeuralSessionNamesEnabled: () => false });
|
|
40
|
+
const h = createSessionTitleToolHandler(d);
|
|
41
|
+
const r = await h.setSessionTitle({ title: "Anything" });
|
|
42
|
+
assert.deepEqual(r, { ok: true });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("still rejects an empty title", async () => {
|
|
46
|
+
const h = createSessionTitleToolHandler(deps());
|
|
47
|
+
await assert.rejects(() => h.setSessionTitle({ title: " " }), /title_empty/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("still rejects when no active session", async () => {
|
|
51
|
+
const h = createSessionTitleToolHandler(deps({ peekSessionKey: () => "" }));
|
|
52
|
+
await assert.rejects(() => h.setSessionTitle({ title: "X" }), /no_active_session/);
|
|
53
|
+
});
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PLUGIN_VERSION = "1.3.
|
|
2
|
-
export const REQUIRES_CLIENT_VERSION = "1.3.
|
|
1
|
+
export const PLUGIN_VERSION = "1.3.3";
|
|
2
|
+
export const REQUIRES_CLIENT_VERSION = "1.3.3";
|
package/openclaw.plugin.json
CHANGED
|
@@ -43,6 +43,11 @@
|
|
|
43
43
|
"help": "Optional extra system prompt appended to Even AI runs only.",
|
|
44
44
|
"advanced": true
|
|
45
45
|
},
|
|
46
|
+
"sessionTitleModel": {
|
|
47
|
+
"label": "Session-title model",
|
|
48
|
+
"help": "Optional model override (\"provider/model\") for the background session-title distiller. Leave blank to use your normal model.",
|
|
49
|
+
"advanced": true
|
|
50
|
+
},
|
|
46
51
|
"evenAiRoutingMode": {
|
|
47
52
|
"label": "Even AI routing mode",
|
|
48
53
|
"help": "active = current session; background = dedicated background session; background_new = fresh background session per request.",
|
|
@@ -226,6 +231,10 @@
|
|
|
226
231
|
"maximum": 8000,
|
|
227
232
|
"default": 5000,
|
|
228
233
|
"description": "How long (ms) a fresh agent summary stays preferred over a tool label in the glasses activity status. Clamped 3000-8000."
|
|
234
|
+
},
|
|
235
|
+
"sessionTitleModel": {
|
|
236
|
+
"type": "string",
|
|
237
|
+
"description": "Optional model override (\"provider/model\") for the background session-title distiller. When absent, the user's normal model is used."
|
|
229
238
|
}
|
|
230
239
|
},
|
|
231
240
|
"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.3",
|
|
4
|
+
"requiresClientVersion": "1.3.3",
|
|
5
5
|
"description": "OcuClaw for Even Realities G2 smart glasses.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node ./scripts/build.mjs",
|
|
43
|
-
"prepare": "node ./scripts/build.mjs"
|
|
43
|
+
"prepare": "node ./scripts/build.mjs",
|
|
44
|
+
"test:unit": "node --import ./scripts/test-loader.mjs --test --test-force-exit \"src/**/*.test.ts\""
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -6,7 +6,14 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
# Authoring glasses surfaces with `render_glasses_ui`
|
|
8
8
|
|
|
9
|
-
`render_glasses_ui` paints an interactive surface on the user's Even G2 HUD instead of a text reply.
|
|
9
|
+
`render_glasses_ui` paints an interactive surface on the user's Even G2 HUD instead of a text reply. Think in two verbs: the render **paints** (the surface lives on glass, ticking, until the user exits or its refresh budget ends) and the call carries **one one-shot listen** (a bounded window in which a tap resolves your call live). The paint outlives the listen by design. This skill is the source of truth for **authoring** surfaces; the tool description is deliberately lean.
|
|
10
|
+
|
|
11
|
+
## Before you author: is the tool loaded?
|
|
12
|
+
|
|
13
|
+
`render_glasses_ui` is a plugin tool, and depending on the host runtime it may not sit in your initial tool list:
|
|
14
|
+
|
|
15
|
+
1. **Not listed but searchable** — some runtimes (e.g. the Codex harness) defer OpenClaw dynamic tools behind tool search. Search your available/deferred tools for `render_glasses_ui` (it surfaces under the `openclaw` namespace), load it, and proceed.
|
|
16
|
+
2. **Not findable at all** — the host's tool policy is filtering plugin tools. Newer OpenClaw versions (2026.6+) default `tools.profile` to `"coding"`, a base allowlist that strips plugin-owned tools; skills are not policy-filtered, which is why you can read this guide for a tool you cannot call. Don't improvise a workaround: tell the user to run `openclaw config set tools.alsoAllow '["ocuclaw"]' --strict-json` (merging `"ocuclaw"` into any existing `alsoAllow` list rather than overwriting) and restart the gateway, then try again.
|
|
10
17
|
|
|
11
18
|
## Surface kinds
|
|
12
19
|
|
|
@@ -135,6 +142,20 @@ render_glasses_ui({
|
|
|
135
142
|
})
|
|
136
143
|
```
|
|
137
144
|
|
|
145
|
+
## The interaction window (the listen)
|
|
146
|
+
|
|
147
|
+
Every call carries **one one-shot listen**: the host waits **90 seconds by default** for the user to act, **up to 600000 ms (10 min) via the optional `timeoutMs` param**. Pass `timeoutMs: 300000–600000` when you expect the user to read or decide; omit it for fire-and-forget paints; never go below 60000 for anything interactive. The listen is **never renewed automatically** — re-rendering opens a fresh one.
|
|
148
|
+
|
|
149
|
+
When the listen ends without a tap you get a **non-terminal** `{ result: "window_expired", surface_still_live: true }`. **This is not an error and not a paint event** — the surface stays on glass and keeps ticking. From there:
|
|
150
|
+
|
|
151
|
+
- Taps now **park** (the user sees their tap acknowledged; nothing is lost). Re-render the same surface (`update: "patch"`) to collect parked taps in this run — chain a couple of these listens if you are actively waiting, then stop.
|
|
152
|
+
- Or simply **end your turn** — parked taps **wake you** (one agent turn per real parked gesture, delivered as a refs-only plugin notification; re-render to collect) or ride your next turn. Ending your turn with a surface parked is a normal, cheap state, not an abandonment.
|
|
153
|
+
- **Silence-as-consent**: the window doubles as a default-action deadline. Put the deadline in the body copy ("Merging in 5 min unless you stop me"), give it a matching `timeoutMs`, and treat `window_expired` as consent.
|
|
154
|
+
|
|
155
|
+
Parked deliveries arrive annotated: `surfaceUuid`, `eventId`, `origin`, `actor`, `queuedAtMs`, `parkedForMs`. For taps that **actuate** something (sell/approve/unlock), declare `staleAfterMs` per render — a tap parked longer arrives with `stale: true`: treat it as a re-confirm prompt, **never** execute it as-is.
|
|
156
|
+
|
|
157
|
+
(The `await`/listen-without-repaint verb namespace is reserved for a future version — don't repurpose those words in surface copy or tooling.)
|
|
158
|
+
|
|
138
159
|
## Outcomes (the `result` you get back)
|
|
139
160
|
|
|
140
161
|
| result | meaning |
|
|
@@ -142,15 +163,17 @@ render_glasses_ui({
|
|
|
142
163
|
| `selected` | user picked a list item; `selected_index` + `selected_text` returned |
|
|
143
164
|
| `back` | user double-tapped above the root; they want to revise — re-render the previous step or pivot |
|
|
144
165
|
| `dismissed` | dismissed at root, or no selection made |
|
|
145
|
-
| `
|
|
166
|
+
| `window_expired` | **non-terminal** — the listen ended, the surface is still live; taps park (see the interaction-window section) |
|
|
167
|
+
| `timeout` | terminal hygiene cap (rare); a refresh surface ends at `maxDurationMs` |
|
|
146
168
|
| `recipe_failed` | refresh only — initial smoke tick failed, the consecutive-failure breaker fired, or `onError:stop`; `failureReason` carries the last error |
|
|
147
169
|
| `glasses_disconnected` | refresh only — the glasses client dropped mid-cron |
|
|
148
170
|
|
|
149
|
-
Refresh results also carry: `ticks: { count, succeeded, failed, lastSuccessAt, lastFailureAt? }`, `lastBody`, `lastItems`, and `failureReason` (on `recipe_failed`).
|
|
171
|
+
Every delivery carries the surface's durable `surfaceUuid` plus `origin` (`gesture` for wearer actions, `system` for plugin-initiated outcomes) and an `actor` slot. Refresh results also carry: `ticks: { count, succeeded, failed, lastSuccessAt, lastFailureAt? }`, `lastBody`, `lastItems`, and `failureReason` (on `recipe_failed`).
|
|
150
172
|
|
|
151
173
|
## Quick reference
|
|
152
174
|
|
|
153
175
|
- Pick the **lowest tier**: host metrics → `system-stats`; pure data API → `http` (not enabled yet). Needs interpretation → render once from your turn.
|
|
154
176
|
- `update` default is `replace` (in-place). Use `push` to drill in without losing the parent; `patch` to edit fields while the cron keeps ticking.
|
|
177
|
+
- The listen is one-shot: default 90 s, `timeoutMs` up to 600000 (use 300000–600000 for read-or-decide). `window_expired` ≠ error — re-render to collect parked taps, or end your turn (they wake you / ride the next turn).
|
|
155
178
|
- `intervalMs` ≥ 1000. Read `failureReason` on `recipe_failed` and fix the recipe.
|
|
156
179
|
- After a surface resolves, a short text reply exits to chat; another render replaces; silence lets it linger.
|