openclaw-voice 1.0.3 → 1.0.5
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 +26 -16
- package/dist/openclaw/client.d.ts +26 -0
- package/dist/openclaw/client.js +102 -0
- package/dist/openclaw/index.d.ts +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Speak -> edit -> send -> stream response.
|
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<video
|
|
15
|
-
src="https://github.com/user-attachments/assets/
|
|
15
|
+
src="https://github.com/user-attachments/assets/17678911-a359-490c-a529-67e9b38ff4bb"
|
|
16
16
|
poster="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/response-light.png"
|
|
17
17
|
controls
|
|
18
18
|
playsinline
|
|
@@ -24,14 +24,14 @@ Speak -> edit -> send -> stream response.
|
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
26
|
<p align="center">
|
|
27
|
-
<a href="https://github.com/user-attachments/assets/
|
|
27
|
+
<a href="https://github.com/user-attachments/assets/17678911-a359-490c-a529-67e9b38ff4bb"><strong>Watch demo video (MP4)</strong></a>
|
|
28
28
|
</p>
|
|
29
29
|
|
|
30
30
|
## Why OpenClaw Voice
|
|
31
31
|
|
|
32
32
|
- Fast voice-to-chat workflow optimized for iOS
|
|
33
33
|
- Reusable `GatewayClient` SDK on npm (`openclaw-voice`)
|
|
34
|
-
- Streaming
|
|
34
|
+
- Streaming + recovery handling for unstable mobile networks
|
|
35
35
|
- Secure device identity signing via Ed25519
|
|
36
36
|
|
|
37
37
|
## Run The App (5 Minutes)
|
|
@@ -111,35 +111,38 @@ Notes:
|
|
|
111
111
|
<td align="center" width="25%">
|
|
112
112
|
<strong>Idle</strong><br>
|
|
113
113
|
<sub>Connected, waiting for input</sub><br><br>
|
|
114
|
-
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/idle.png" width="
|
|
114
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/idle.png" width="230" alt="Idle state" />
|
|
115
115
|
</td>
|
|
116
116
|
<td align="center" width="25%">
|
|
117
117
|
<strong>Ready to Send</strong><br>
|
|
118
118
|
<sub>Transcript ready, tap to send</sub><br><br>
|
|
119
|
-
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/ready-to-send.png" width="
|
|
119
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/ready-to-send.png" width="230" alt="Ready to send state" />
|
|
120
120
|
</td>
|
|
121
121
|
<td align="center" width="25%">
|
|
122
122
|
<strong>Sending</strong><br>
|
|
123
123
|
<sub>Waiting for Gateway response</sub><br><br>
|
|
124
|
-
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/sending.png" width="
|
|
124
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/sending.png" width="230" alt="Sending state" />
|
|
125
125
|
</td>
|
|
126
126
|
<td align="center" width="25%">
|
|
127
|
-
<strong>Response
|
|
127
|
+
<strong>Response</strong><br>
|
|
128
128
|
<sub>Streamed response displayed</sub><br><br>
|
|
129
|
-
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/response-light.png" width="
|
|
129
|
+
<img src="https://raw.githubusercontent.com/kyaukyuai/openclaw-voice/main/docs/screenshots/response-light.png" width="230" alt="Response in light theme" />
|
|
130
130
|
</td>
|
|
131
131
|
</tr>
|
|
132
132
|
</table>
|
|
133
133
|
|
|
134
134
|
## Features
|
|
135
135
|
|
|
136
|
-
-
|
|
137
|
-
- Editable transcript
|
|
138
|
-
-
|
|
136
|
+
- Voice input with hold-to-record (`expo-speech-recognition`)
|
|
137
|
+
- Editable transcript and quick text insert buttons
|
|
138
|
+
- Speech language switch (`ja-JP` / `en-US`)
|
|
139
|
+
- Dedicated **Settings** screen and **Sessions** screen
|
|
140
|
+
- Session management: list, switch, rename, pin/unpin, create
|
|
141
|
+
- Gateway connect/reconnect flow with startup auto-connect retry
|
|
142
|
+
- History sync and manual refresh with status notice
|
|
139
143
|
- Streaming response rendering with per-turn states (`WAIT`, `OK`, `ERR`)
|
|
140
|
-
-
|
|
141
|
-
- Persistent settings
|
|
142
|
-
- Local device identity generation/signing for gateway auth
|
|
144
|
+
- Markdown response rendering with URL linkification
|
|
145
|
+
- Persistent local settings and secure local device identity reuse
|
|
143
146
|
|
|
144
147
|
## Environment Variables
|
|
145
148
|
|
|
@@ -151,9 +154,10 @@ cp .env.example .env
|
|
|
151
154
|
|
|
152
155
|
- `EXPO_PUBLIC_DEFAULT_GATEWAY_URL`
|
|
153
156
|
- `EXPO_PUBLIC_DEFAULT_THEME` (`light` or `dark`)
|
|
157
|
+
- `EXPO_PUBLIC_DEFAULT_SESSION_KEY` (default: `main`)
|
|
154
158
|
- `EXPO_PUBLIC_GATEWAY_CLIENT_ID` (default: `openclaw-ios`)
|
|
155
159
|
- `EXPO_PUBLIC_GATEWAY_DISPLAY_NAME` (default: `OpenClawVoice`)
|
|
156
|
-
- `EXPO_PUBLIC_DEBUG_MODE` (`true` to show warnings
|
|
160
|
+
- `EXPO_PUBLIC_DEBUG_MODE` (`true` to show dev warnings and runtime debug panel, default: `false`)
|
|
157
161
|
|
|
158
162
|
## Connection Defaults
|
|
159
163
|
|
|
@@ -173,7 +177,7 @@ Device identity is generated locally and reused when persistent storage is avail
|
|
|
173
177
|
- `npm run web` - Run web target
|
|
174
178
|
- `npm run typecheck` - Run TypeScript checks
|
|
175
179
|
- `npm run lint` - Run repository lint checks
|
|
176
|
-
- `npm test` - Run manifest switch
|
|
180
|
+
- `npm test` - Run regression tests (runtime logic + manifest switch)
|
|
177
181
|
- `npm run smoke:pack-install` - Pack tarball and verify install/import from a clean temp app
|
|
178
182
|
- `npm run build:package` - Build npm package files to `dist/`
|
|
179
183
|
|
|
@@ -202,6 +206,12 @@ OPENCLAW_SMOKE_SKIP_INSTALL=1 npm run smoke:pack-install
|
|
|
202
206
|
- Do not expose raw Gateway ports publicly.
|
|
203
207
|
- Rotate credentials and keep TLS/server packages up to date.
|
|
204
208
|
|
|
209
|
+
## Funding
|
|
210
|
+
|
|
211
|
+
If this project helps your workflow, you can support maintenance on GitHub Sponsors:
|
|
212
|
+
|
|
213
|
+
- [@kyaukyuai](https://github.com/sponsors/kyaukyuai)
|
|
214
|
+
|
|
205
215
|
## Troubleshooting
|
|
206
216
|
|
|
207
217
|
See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
|
|
@@ -53,6 +53,17 @@ export interface ModelsListResponse {
|
|
|
53
53
|
count: number;
|
|
54
54
|
models: ModelEntry[];
|
|
55
55
|
}
|
|
56
|
+
export interface SessionPatchInput {
|
|
57
|
+
label?: string;
|
|
58
|
+
displayName?: string;
|
|
59
|
+
subject?: string;
|
|
60
|
+
room?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
export interface SessionDeleteTarget {
|
|
64
|
+
key: string;
|
|
65
|
+
sessionId?: string;
|
|
66
|
+
}
|
|
56
67
|
export declare class GatewayError extends Error {
|
|
57
68
|
readonly code: string;
|
|
58
69
|
readonly details?: unknown;
|
|
@@ -165,6 +176,21 @@ export declare class GatewayClient {
|
|
|
165
176
|
limit?: number;
|
|
166
177
|
includeGlobal?: boolean;
|
|
167
178
|
}): Promise<SessionsListResponse>;
|
|
179
|
+
/**
|
|
180
|
+
* Patch session metadata (e.g. label/displayName).
|
|
181
|
+
* Uses a few payload shapes for compatibility with gateway variants.
|
|
182
|
+
*/
|
|
183
|
+
sessionsPatch(sessionKey: string, patch: SessionPatchInput): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Delete a session by key.
|
|
186
|
+
* Uses a few payload shapes for compatibility with gateway variants.
|
|
187
|
+
*/
|
|
188
|
+
sessionsDelete(target: string | SessionDeleteTarget): Promise<void>;
|
|
189
|
+
/**
|
|
190
|
+
* Reset a session by key.
|
|
191
|
+
* Some gateway builds expose reset semantics where delete is unavailable.
|
|
192
|
+
*/
|
|
193
|
+
sessionsReset(target: string | SessionDeleteTarget): Promise<void>;
|
|
168
194
|
/**
|
|
169
195
|
* Health check — returns true if gateway responds.
|
|
170
196
|
*/
|
package/dist/openclaw/client.js
CHANGED
|
@@ -268,6 +268,108 @@ class GatewayClient {
|
|
|
268
268
|
limit: options === null || options === void 0 ? void 0 : options.limit,
|
|
269
269
|
}, 15000);
|
|
270
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Patch session metadata (e.g. label/displayName).
|
|
273
|
+
* Uses a few payload shapes for compatibility with gateway variants.
|
|
274
|
+
*/
|
|
275
|
+
async sessionsPatch(sessionKey, patch) {
|
|
276
|
+
const payloads = [
|
|
277
|
+
{ sessionKey, patch },
|
|
278
|
+
{ key: sessionKey, patch },
|
|
279
|
+
{ sessionKey, ...patch },
|
|
280
|
+
{ key: sessionKey, ...patch },
|
|
281
|
+
];
|
|
282
|
+
let lastError;
|
|
283
|
+
for (const payload of payloads) {
|
|
284
|
+
try {
|
|
285
|
+
await this.request(protocol_1.GatewayMethods.SESSIONS_PATCH, payload, 10000);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
lastError = error;
|
|
290
|
+
if (!(error instanceof GatewayError) || error.code !== "INVALID_REQUEST") {
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
throw lastError instanceof Error
|
|
296
|
+
? lastError
|
|
297
|
+
: new Error("sessions.patch failed");
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Delete a session by key.
|
|
301
|
+
* Uses a few payload shapes for compatibility with gateway variants.
|
|
302
|
+
*/
|
|
303
|
+
async sessionsDelete(target) {
|
|
304
|
+
var _a;
|
|
305
|
+
const sessionKey = typeof target === "string" ? target.trim() : target.key.trim();
|
|
306
|
+
const sessionId = typeof target === "string" ? "" : ((_a = target.sessionId) !== null && _a !== void 0 ? _a : "").trim();
|
|
307
|
+
if (!sessionKey) {
|
|
308
|
+
throw new Error("sessions.delete requires a non-empty session key");
|
|
309
|
+
}
|
|
310
|
+
const payloads = [
|
|
311
|
+
{ sessionKey },
|
|
312
|
+
{ key: sessionKey },
|
|
313
|
+
{ sessionKeys: [sessionKey] },
|
|
314
|
+
{ keys: [sessionKey] },
|
|
315
|
+
];
|
|
316
|
+
if (sessionId) {
|
|
317
|
+
payloads.unshift({ sessionKey, sessionId }, { key: sessionKey, sessionId }, { key: sessionKey, id: sessionId }, { sessionId }, { id: sessionId }, { sessionIds: [sessionId] }, { ids: [sessionId] });
|
|
318
|
+
}
|
|
319
|
+
let lastError;
|
|
320
|
+
for (const payload of payloads) {
|
|
321
|
+
try {
|
|
322
|
+
await this.request(protocol_1.GatewayMethods.SESSIONS_DELETE, payload, 10000);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
lastError = error;
|
|
327
|
+
if (!(error instanceof GatewayError) || error.code !== "INVALID_REQUEST") {
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
throw lastError instanceof Error
|
|
333
|
+
? lastError
|
|
334
|
+
: new Error("sessions.delete failed");
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Reset a session by key.
|
|
338
|
+
* Some gateway builds expose reset semantics where delete is unavailable.
|
|
339
|
+
*/
|
|
340
|
+
async sessionsReset(target) {
|
|
341
|
+
var _a;
|
|
342
|
+
const sessionKey = typeof target === "string" ? target.trim() : target.key.trim();
|
|
343
|
+
const sessionId = typeof target === "string" ? "" : ((_a = target.sessionId) !== null && _a !== void 0 ? _a : "").trim();
|
|
344
|
+
if (!sessionKey) {
|
|
345
|
+
throw new Error("sessions.reset requires a non-empty session key");
|
|
346
|
+
}
|
|
347
|
+
const payloads = [
|
|
348
|
+
{ sessionKey },
|
|
349
|
+
{ key: sessionKey },
|
|
350
|
+
{ sessionKeys: [sessionKey] },
|
|
351
|
+
{ keys: [sessionKey] },
|
|
352
|
+
];
|
|
353
|
+
if (sessionId) {
|
|
354
|
+
payloads.unshift({ sessionKey, sessionId }, { key: sessionKey, sessionId }, { sessionId }, { id: sessionId }, { sessionIds: [sessionId] }, { ids: [sessionId] });
|
|
355
|
+
}
|
|
356
|
+
let lastError;
|
|
357
|
+
for (const payload of payloads) {
|
|
358
|
+
try {
|
|
359
|
+
await this.request(protocol_1.GatewayMethods.SESSIONS_RESET, payload, 10000);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
lastError = error;
|
|
364
|
+
if (!(error instanceof GatewayError) || error.code !== "INVALID_REQUEST") {
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
throw lastError instanceof Error
|
|
370
|
+
? lastError
|
|
371
|
+
: new Error("sessions.reset failed");
|
|
372
|
+
}
|
|
271
373
|
/**
|
|
272
374
|
* Health check — returns true if gateway responds.
|
|
273
375
|
*/
|
package/dist/openclaw/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { GatewayClient, GatewayError, generateIdempotencyKey, type ConnectionState, type GatewayClientOptions, type ModelEntry, type ModelsListResponse, } from './client';
|
|
1
|
+
export { GatewayClient, GatewayError, generateIdempotencyKey, type ConnectionState, type GatewayClientOptions, type ModelEntry, type ModelsListResponse, type SessionPatchInput, } from './client';
|
|
2
2
|
export { loadOrCreateIdentity, signPayload, publicKeyBase64Url, buildSignaturePayload, type StoredDeviceIdentity, } from './device-identity';
|
|
3
3
|
export { type Storage, storage, setStorage } from './storage';
|
|
4
4
|
export { GATEWAY_PROTOCOL_VERSION, GatewayEvents, GatewayMethods, ErrorCode, type GatewayEventName, type RequestFrame, type ResponseFrame, type EventFrame, type GatewayFrame, type ErrorShape, type ConnectChallenge, type ClientInfo, type DeviceIdentity, type ConnectAuth, type ConnectParams, type HelloOk, type HelloOkAuth, type HelloOkPolicy, type TickPayload, type HealthPayload, type ChatEventPayload, type AgentEventPayload, type SeqGapPayload, type ShutdownPayload, type ChatMessageContentType, type ChatMessageContent, type ChatUsage, type ChatUsageCost, type ChatMessage, type ChatAttachmentPayload, type ChatHistoryPayload, type ChatSendResponse, type SessionEntry, type SessionsListResponse, type SessionsDefaults, type SessionPreviewItem, type SessionPreviewEntry, type SessionsPreviewPayload, type StateVersion, type Snapshot, type PresenceEntry, } from './protocol';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-voice",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"main": "./dist/package.js",
|
|
5
5
|
"types": "./dist/package.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"base-64": "^1.0.0",
|
|
40
40
|
"expo": "~54.0.33",
|
|
41
41
|
"expo-crypto": "^15.0.8",
|
|
42
|
+
"expo-haptics": "^15.0.8",
|
|
42
43
|
"expo-secure-store": "^15.0.8",
|
|
43
44
|
"expo-speech-recognition": "^3.1.0",
|
|
44
45
|
"expo-status-bar": "~3.0.9",
|