@openclaw/voice-call 2026.9.7 → 2026.10.1-beta.1
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/.setup/{cli-D6uwMXyS.mjs → cli-Cn1leykM.mjs} +57 -37
- package/dist/.setup/{config-Ds7xoZbI.mjs → config-CX9J-053.mjs} +18 -119
- package/dist/.setup/{guarded-json-api-Bqc6QS8A.mjs → guarded-json-api-x55yqHvW.mjs} +39 -94
- package/dist/.setup/{mock-ClWW6Ufu.mjs → mock-eLv_Dc4o.mjs} +3 -6
- package/dist/.setup/{plivo-69ObrQPk.mjs → plivo-CpPIENu4.mjs} +48 -101
- package/dist/.setup/{realtime-handler-BgDPcPZt.mjs → realtime-handler-Do7Sz7Gh.mjs} +123 -235
- package/dist/.setup/{response-generator-BzKrJMEb.mjs → response-generator-DAhbXEta.mjs} +6 -25
- package/dist/.setup/{runtime-entry-BNOQRXii.mjs → runtime-entry-CbSKxSKk.mjs} +554 -425
- package/dist/.setup/state-retention-api-ONgqE-_V.mjs +201 -0
- package/dist/.setup/store-path-Cfwff6j7.mjs +33 -0
- package/dist/.setup/{telnyx-DjFGCfy1.mjs → telnyx-DPilutCH.mjs} +10 -29
- package/dist/.setup/{twilio-Bp3gfmUB.mjs → twilio-BWFNGyVx.mjs} +78 -201
- package/dist/.setup/{twilio-region-4fkgz3UG.mjs → twilio-region-wBOx-8sz.mjs} +1 -4
- package/dist/.setup/voice-mapping-B_iAUkER.mjs +31 -0
- package/dist/.setup/{webhook-replay-CCQSoBNF.mjs → webhook-replay-B7RJNWNG.mjs} +2 -5
- package/dist/doctor-contract-api.js +7 -330
- package/dist/index.js +18 -16
- package/dist/runtime-entry.js +1 -1
- package/dist/state-retention-api.js +2 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -6
- package/dist/.setup/utils-B5pnM_Ij.mjs +0 -437
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -22
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -530
- package/node_modules/ws/lib/receiver.js +0 -743
- package/node_modules/ws/lib/sender.js +0 -607
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -562
- package/node_modules/ws/lib/websocket.js +0 -1407
- package/node_modules/ws/package.json +0 -74
- package/node_modules/ws/wrapper.mjs +0 -21
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
5
|
-
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
6
|
-
import os from "node:os";
|
|
7
|
-
//#region extensions/voice-call/src/types.ts
|
|
8
|
-
const ProviderNameSchema = z.enum([
|
|
9
|
-
"telnyx",
|
|
10
|
-
"twilio",
|
|
11
|
-
"plivo",
|
|
12
|
-
"mock"
|
|
13
|
-
]);
|
|
14
|
-
const EndReasonSchema = z.enum([
|
|
15
|
-
"completed",
|
|
16
|
-
"hangup-user",
|
|
17
|
-
"hangup-bot",
|
|
18
|
-
"timeout",
|
|
19
|
-
"error",
|
|
20
|
-
"failed",
|
|
21
|
-
"no-answer",
|
|
22
|
-
"busy",
|
|
23
|
-
"voicemail"
|
|
24
|
-
]);
|
|
25
|
-
const CallStateSchema = z.enum([
|
|
26
|
-
"initiated",
|
|
27
|
-
"ringing",
|
|
28
|
-
"answered",
|
|
29
|
-
"active",
|
|
30
|
-
"speaking",
|
|
31
|
-
"listening",
|
|
32
|
-
...EndReasonSchema.options
|
|
33
|
-
]);
|
|
34
|
-
const TerminalStates = new Set(EndReasonSchema.options);
|
|
35
|
-
const CallDirectionSchema = z.enum(["outbound", "inbound"]);
|
|
36
|
-
const TranscriptEntrySchema = z.object({
|
|
37
|
-
timestamp: z.number(),
|
|
38
|
-
speaker: z.enum(["bot", "user"]),
|
|
39
|
-
text: z.string(),
|
|
40
|
-
isFinal: z.boolean().default(true)
|
|
41
|
-
});
|
|
42
|
-
const CallRecordSchema = z.object({
|
|
43
|
-
callId: z.string(),
|
|
44
|
-
providerCallId: z.string().optional(),
|
|
45
|
-
provider: ProviderNameSchema,
|
|
46
|
-
direction: CallDirectionSchema,
|
|
47
|
-
state: CallStateSchema,
|
|
48
|
-
from: z.string(),
|
|
49
|
-
to: z.string(),
|
|
50
|
-
sessionKey: z.string().optional(),
|
|
51
|
-
/** Agent selected when the call was created. Optional for legacy records. */
|
|
52
|
-
agentId: z.string().optional(),
|
|
53
|
-
startedAt: z.number(),
|
|
54
|
-
answeredAt: z.number().optional(),
|
|
55
|
-
endedAt: z.number().optional(),
|
|
56
|
-
endReason: EndReasonSchema.optional(),
|
|
57
|
-
transcript: z.array(TranscriptEntrySchema).default([]),
|
|
58
|
-
processedEventIds: z.array(z.string()).default([]),
|
|
59
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
60
|
-
});
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region extensions/voice-call/src/runtime-state.ts
|
|
63
|
-
const { setRuntime: setVoiceCallStateRuntime, tryGetRuntime: getOptionalVoiceCallStateRuntime } = createPluginRuntimeStore({
|
|
64
|
-
pluginId: "voice-call-state",
|
|
65
|
-
errorMessage: "Voice Call state runtime not initialized"
|
|
66
|
-
});
|
|
67
|
-
//#endregion
|
|
68
|
-
//#region extensions/voice-call/src/manager/replay-keys.ts
|
|
69
|
-
/** Match the existing voice-call webhook replay-cache cardinality. */
|
|
70
|
-
const MAX_MANAGER_REPLAY_KEYS = 1e4;
|
|
71
|
-
function pruneOldestEntries(keys, maxEntries) {
|
|
72
|
-
while (keys.size > maxEntries) {
|
|
73
|
-
const oldest = keys.keys().next().value;
|
|
74
|
-
if (oldest === void 0) break;
|
|
75
|
-
keys.delete(oldest);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/** Remember one manager replay key without refreshing duplicate insertion order. */
|
|
79
|
-
function rememberManagerReplayKey(keys, key, maxEntries = MAX_MANAGER_REPLAY_KEYS) {
|
|
80
|
-
keys.add(key);
|
|
81
|
-
pruneOldestEntries(keys, maxEntries);
|
|
82
|
-
}
|
|
83
|
-
/** Append one per-call replay key and retain only the newest bounded suffix. */
|
|
84
|
-
function appendCallReplayKey(keys, key, maxEntries = 500) {
|
|
85
|
-
keys.push(key);
|
|
86
|
-
trimCallReplayKeys(keys, maxEntries);
|
|
87
|
-
}
|
|
88
|
-
/** Normalize restored or externally constructed call replay history in place. */
|
|
89
|
-
function trimCallReplayKeys(keys, maxEntries = 500) {
|
|
90
|
-
const overflow = keys.length - maxEntries;
|
|
91
|
-
if (overflow > 0) keys.splice(0, overflow);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Reserve one rejected provider call. The token prevents a stale failed
|
|
95
|
-
* hangup from deleting a newer reservation after bounded eviction and reuse.
|
|
96
|
-
*/
|
|
97
|
-
function reserveRejectedProviderCall(calls, providerCallId, maxEntries = MAX_MANAGER_REPLAY_KEYS) {
|
|
98
|
-
if (calls.has(providerCallId)) return;
|
|
99
|
-
const reservation = Symbol(providerCallId);
|
|
100
|
-
calls.set(providerCallId, reservation);
|
|
101
|
-
pruneOldestEntries(calls, maxEntries);
|
|
102
|
-
return reservation;
|
|
103
|
-
}
|
|
104
|
-
/** Release a rejected-call reservation only when the failing attempt still owns it. */
|
|
105
|
-
function releaseRejectedProviderCall(calls, providerCallId, reservation) {
|
|
106
|
-
if (calls.get(providerCallId) === reservation) calls.delete(providerCallId);
|
|
107
|
-
}
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region extensions/voice-call/src/manager/store.ts
|
|
110
|
-
/** Plugin state namespace for call record event metadata. */
|
|
111
|
-
const CALL_RECORD_EVENTS_NAMESPACE = "call-record-events";
|
|
112
|
-
/** Plugin state namespace for base64 call record event chunks. */
|
|
113
|
-
const CALL_RECORD_EVENT_CHUNKS_NAMESPACE = "call-record-event-chunks";
|
|
114
|
-
/** Maximum retained call record events. */
|
|
115
|
-
const MAX_CALL_RECORD_EVENTS = 1e3;
|
|
116
|
-
/** Extra metadata entries retained so pruning can safely trim oldest rows. */
|
|
117
|
-
const CALL_RECORD_EVENT_META_MAX_ENTRIES = 1100;
|
|
118
|
-
/** Maximum chunks allowed for one persisted call record event. */
|
|
119
|
-
const MAX_CHUNKS_PER_CALL_RECORD_EVENT = 48;
|
|
120
|
-
const CALL_RECORD_CHUNK_MAX_ENTRIES = 48048;
|
|
121
|
-
/** Raw UTF-8 bytes stored per call record chunk before base64 encoding. */
|
|
122
|
-
const RAW_CALL_RECORD_CHUNK_BYTES = 48128;
|
|
123
|
-
const CALL_RECORD_READ_BATCH_KEYS = 128;
|
|
124
|
-
let callRecordEventSequence = 0;
|
|
125
|
-
/** Return the pre-SQLite JSONL call log path for migration/compat checks. */
|
|
126
|
-
function resolveVoiceCallLegacyCallLogPath(storePath) {
|
|
127
|
-
return path.join(storePath, "calls.jsonl");
|
|
128
|
-
}
|
|
129
|
-
/** Build env for plugin state stores rooted at the voice-call store path. */
|
|
130
|
-
function resolvePluginStateEnv(storePath) {
|
|
131
|
-
return {
|
|
132
|
-
...process.env,
|
|
133
|
-
OPENCLAW_STATE_DIR: storePath
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
/** Open the plugin state stores when the runtime is available. */
|
|
137
|
-
function createCallRecordStateStores(storePath, stateRuntime) {
|
|
138
|
-
const runtime = stateRuntime ? { state: stateRuntime } : getOptionalVoiceCallStateRuntime();
|
|
139
|
-
if (!runtime) throw new Error("Voice Call state runtime not initialized");
|
|
140
|
-
const env = resolvePluginStateEnv(storePath);
|
|
141
|
-
return {
|
|
142
|
-
events: runtime.state.openKeyedStore({
|
|
143
|
-
namespace: CALL_RECORD_EVENTS_NAMESPACE,
|
|
144
|
-
maxEntries: CALL_RECORD_EVENT_META_MAX_ENTRIES,
|
|
145
|
-
env
|
|
146
|
-
}),
|
|
147
|
-
chunks: runtime.state.openKeyedStore({
|
|
148
|
-
namespace: CALL_RECORD_EVENT_CHUNKS_NAMESPACE,
|
|
149
|
-
maxEntries: CALL_RECORD_CHUNK_MAX_ENTRIES,
|
|
150
|
-
env
|
|
151
|
-
})
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
/** Open call stores and log failures instead of breaking restore paths. */
|
|
155
|
-
function tryCreateCallRecordStateStores(storePath, stateRuntime) {
|
|
156
|
-
try {
|
|
157
|
-
return createCallRecordStateStores(storePath, stateRuntime);
|
|
158
|
-
} catch (err) {
|
|
159
|
-
console.error("[voice-call] Failed to open SQLite call record store:", err);
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/** Build the stable storage key for one chunk of an event. */
|
|
164
|
-
function buildChunkKey(eventKey, index) {
|
|
165
|
-
return `${eventKey}:chunk:${String(index).padStart(4, "0")}`;
|
|
166
|
-
}
|
|
167
|
-
/** Build a deterministic key for one legacy JSONL line. */
|
|
168
|
-
function buildVoiceCallLegacyJsonlEventKey(line, index) {
|
|
169
|
-
return `jsonl:${String(index).padStart(8, "0")}:${createHash("sha256").update(line).digest("hex")}`;
|
|
170
|
-
}
|
|
171
|
-
/** Allocate monotonic ordering metadata for newly persisted call records. */
|
|
172
|
-
function nextCallRecordOrder() {
|
|
173
|
-
const sequence = callRecordEventSequence;
|
|
174
|
-
callRecordEventSequence = (callRecordEventSequence + 1) % 1e6;
|
|
175
|
-
return {
|
|
176
|
-
persistedAt: Date.now(),
|
|
177
|
-
sequence
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
/** Build a unique event key that preserves timestamp and sequence ordering. */
|
|
181
|
-
function buildNewEventKey(order) {
|
|
182
|
-
return `event:${order.persistedAt.toString(36)}:${String(order.sequence).padStart(6, "0")}:${randomUUID()}`;
|
|
183
|
-
}
|
|
184
|
-
/** Recover the sequence segment from newer event keys. */
|
|
185
|
-
function parseEventKeySequence(key) {
|
|
186
|
-
const sequence = /^event:[^:]+:(\d+):/.exec(key)?.[1];
|
|
187
|
-
return sequence ? Number.parseInt(sequence, 10) : 0;
|
|
188
|
-
}
|
|
189
|
-
/** Parse a stored call record line from v2 envelope or legacy raw-call JSON. */
|
|
190
|
-
function parseVoiceCallRecordLine(line, sequence = 0) {
|
|
191
|
-
if (!line.trim()) return null;
|
|
192
|
-
try {
|
|
193
|
-
const parsed = JSON.parse(line);
|
|
194
|
-
if (parsed && typeof parsed === "object" && parsed.version === 2) {
|
|
195
|
-
const envelope = parsed;
|
|
196
|
-
return {
|
|
197
|
-
call: CallRecordSchema.parse(envelope.call),
|
|
198
|
-
persistedAt: typeof envelope.persistedAt === "number" && Number.isFinite(envelope.persistedAt) ? envelope.persistedAt : 0,
|
|
199
|
-
sequence: typeof envelope.sequence === "number" && Number.isFinite(envelope.sequence) ? envelope.sequence : sequence,
|
|
200
|
-
orderKey: ""
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
call: CallRecordSchema.parse(parsed),
|
|
205
|
-
persistedAt: 0,
|
|
206
|
-
sequence,
|
|
207
|
-
orderKey: ""
|
|
208
|
-
};
|
|
209
|
-
} catch {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
/** Count storage chunks needed for a call record. */
|
|
214
|
-
function countCallRecordChunks(call) {
|
|
215
|
-
return Math.max(1, Math.ceil(Buffer.byteLength(JSON.stringify(call), "utf8") / RAW_CALL_RECORD_CHUNK_BYTES));
|
|
216
|
-
}
|
|
217
|
-
/** Truncate oversized call records to fit the bounded plugin state chunk budget. */
|
|
218
|
-
function prepareVoiceCallRecordForStorage(call) {
|
|
219
|
-
let boundedCall = call;
|
|
220
|
-
if (call.processedEventIds.length > 500) {
|
|
221
|
-
boundedCall = {
|
|
222
|
-
...call,
|
|
223
|
-
processedEventIds: [...call.processedEventIds]
|
|
224
|
-
};
|
|
225
|
-
trimCallReplayKeys(boundedCall.processedEventIds);
|
|
226
|
-
}
|
|
227
|
-
if (countCallRecordChunks(boundedCall) <= MAX_CHUNKS_PER_CALL_RECORD_EVENT) return boundedCall;
|
|
228
|
-
const transcriptEntries = boundedCall.transcript.length;
|
|
229
|
-
const metadata = {
|
|
230
|
-
...boundedCall.metadata,
|
|
231
|
-
voiceCallPersistence: {
|
|
232
|
-
transcriptTruncated: true,
|
|
233
|
-
originalTranscriptEntries: transcriptEntries
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
const candidateInputs = [
|
|
237
|
-
{
|
|
238
|
-
transcript: call.transcript.slice(-20),
|
|
239
|
-
metadata
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
transcript: [],
|
|
243
|
-
metadata
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
transcript: [],
|
|
247
|
-
metadata: { voiceCallPersistence: {
|
|
248
|
-
transcriptTruncated: true,
|
|
249
|
-
originalTranscriptEntries: transcriptEntries,
|
|
250
|
-
metadataTruncated: true
|
|
251
|
-
} }
|
|
252
|
-
}
|
|
253
|
-
];
|
|
254
|
-
for (const candidateInput of candidateInputs) {
|
|
255
|
-
const candidate = CallRecordSchema.parse({
|
|
256
|
-
...boundedCall,
|
|
257
|
-
...candidateInput
|
|
258
|
-
});
|
|
259
|
-
if (countCallRecordChunks(candidate) <= MAX_CHUNKS_PER_CALL_RECORD_EVENT) return candidate;
|
|
260
|
-
}
|
|
261
|
-
return boundedCall;
|
|
262
|
-
}
|
|
263
|
-
/** Encode one bounded record; chunks are produced only when requested by the writer. */
|
|
264
|
-
function encodeCallRecordEvent(call) {
|
|
265
|
-
const serialized = JSON.stringify(prepareVoiceCallRecordForStorage(call));
|
|
266
|
-
const buffer = Buffer.from(serialized, "utf8");
|
|
267
|
-
const chunkCount = Math.max(1, Math.ceil(buffer.byteLength / RAW_CALL_RECORD_CHUNK_BYTES));
|
|
268
|
-
if (chunkCount > MAX_CHUNKS_PER_CALL_RECORD_EVENT) throw new Error(`voice-call record exceeds SQLite chunk limit (${chunkCount}/${MAX_CHUNKS_PER_CALL_RECORD_EVENT})`);
|
|
269
|
-
return {
|
|
270
|
-
meta: {
|
|
271
|
-
chunkCount,
|
|
272
|
-
byteLength: buffer.byteLength
|
|
273
|
-
},
|
|
274
|
-
chunk(index) {
|
|
275
|
-
return {
|
|
276
|
-
index,
|
|
277
|
-
dataBase64: buffer.subarray(index * RAW_CALL_RECORD_CHUNK_BYTES, (index + 1) * RAW_CALL_RECORD_CHUNK_BYTES).toString("base64")
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
/** Register a serialized call record event and its chunks, then prune old events. */
|
|
283
|
-
async function registerCallRecordEvent(stores, eventKey, call, order) {
|
|
284
|
-
const encoded = encodeCallRecordEvent(call);
|
|
285
|
-
for (let index = 0; index < encoded.meta.chunkCount; index += 1) await stores.chunks.register(buildChunkKey(eventKey, index), encoded.chunk(index));
|
|
286
|
-
await stores.events.register(eventKey, {
|
|
287
|
-
...encoded.meta,
|
|
288
|
-
persistedAt: order.persistedAt,
|
|
289
|
-
sequence: order.sequence
|
|
290
|
-
});
|
|
291
|
-
await pruneCallRecordEvents(stores);
|
|
292
|
-
}
|
|
293
|
-
/** Delete metadata and all chunk rows for one call record event. */
|
|
294
|
-
async function deleteCallRecordEventRows(stores, eventKey) {
|
|
295
|
-
const meta = await stores.events.lookup(eventKey);
|
|
296
|
-
await stores.events.delete(eventKey);
|
|
297
|
-
if (!meta) return;
|
|
298
|
-
for (let index = 0; index < meta.chunkCount; index += 1) await stores.chunks.delete(buildChunkKey(eventKey, index));
|
|
299
|
-
}
|
|
300
|
-
/** Keep only the newest bounded call record events. */
|
|
301
|
-
async function pruneCallRecordEvents(stores) {
|
|
302
|
-
if (stores.events.count && await stores.events.count() <= 1e3) return;
|
|
303
|
-
const rows = await stores.events.entries();
|
|
304
|
-
if (rows.length <= 1e3) return;
|
|
305
|
-
const sorted = rows.toSorted((a, b) => a.createdAt - b.createdAt || a.key.localeCompare(b.key));
|
|
306
|
-
for (const row of sorted.slice(0, rows.length - MAX_CALL_RECORD_EVENTS)) await deleteCallRecordEventRows(stores, row.key);
|
|
307
|
-
}
|
|
308
|
-
function isValidCallRecordChunkCount(chunkCount) {
|
|
309
|
-
return Number.isSafeInteger(chunkCount) && chunkCount >= 1 && chunkCount <= MAX_CHUNKS_PER_CALL_RECORD_EVENT;
|
|
310
|
-
}
|
|
311
|
-
/** Read and reassemble one chunked call record event. */
|
|
312
|
-
async function readCallRecordEvent(stores, eventKey, meta, records) {
|
|
313
|
-
if (!isValidCallRecordChunkCount(meta.chunkCount)) return null;
|
|
314
|
-
const chunks = [];
|
|
315
|
-
for (let index = 0; index < meta.chunkCount; index += 1) {
|
|
316
|
-
const result = records?.[index];
|
|
317
|
-
if (result && !result.ok) throw result.error;
|
|
318
|
-
const chunk = records ? result?.value : await stores.chunks.lookup(buildChunkKey(eventKey, index));
|
|
319
|
-
if (!chunk || chunk.index !== index) return null;
|
|
320
|
-
chunks.push(Buffer.from(chunk.dataBase64, "base64"));
|
|
321
|
-
}
|
|
322
|
-
return parseVoiceCallRecordLine(Buffer.concat(chunks, meta.byteLength).toString("utf8"))?.call ?? null;
|
|
323
|
-
}
|
|
324
|
-
/** Read all persisted call records in stable persisted order. */
|
|
325
|
-
async function readCallRecordEvents(stores) {
|
|
326
|
-
const entries = (await stores.events.entries()).toSorted((a, b) => a.createdAt - b.createdAt || a.key.localeCompare(b.key));
|
|
327
|
-
const sqliteCalls = [];
|
|
328
|
-
let batchEnd = 0;
|
|
329
|
-
let chunkOffset = 0;
|
|
330
|
-
let chunkRecords;
|
|
331
|
-
for (const [entryIndex, entry] of entries.entries()) {
|
|
332
|
-
if (entryIndex >= batchEnd && stores.chunks.lookupMany) {
|
|
333
|
-
const keys = [];
|
|
334
|
-
for (let next = entryIndex;; next++) {
|
|
335
|
-
const row = entries[next];
|
|
336
|
-
if (!row) break;
|
|
337
|
-
const chunkCount = row.value?.chunkCount;
|
|
338
|
-
if (!isValidCallRecordChunkCount(chunkCount) || keys.length + chunkCount > CALL_RECORD_READ_BATCH_KEYS) break;
|
|
339
|
-
for (let index = 0; index < chunkCount; index++) keys.push(buildChunkKey(row.key, index));
|
|
340
|
-
batchEnd = next + 1;
|
|
341
|
-
}
|
|
342
|
-
chunkRecords = keys.length > 0 ? await stores.chunks.lookupMany(keys) : void 0;
|
|
343
|
-
chunkOffset = 0;
|
|
344
|
-
}
|
|
345
|
-
const records = chunkRecords?.slice(chunkOffset, chunkOffset + entry.value.chunkCount);
|
|
346
|
-
const call = await readCallRecordEvent(stores, entry.key, entry.value, records);
|
|
347
|
-
if (chunkRecords) chunkOffset += entry.value.chunkCount;
|
|
348
|
-
if (call) sqliteCalls.push({
|
|
349
|
-
call,
|
|
350
|
-
persistedAt: entry.value.persistedAt ?? entry.createdAt,
|
|
351
|
-
sequence: entry.value.sequence ?? parseEventKeySequence(entry.key),
|
|
352
|
-
orderKey: entry.key
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
return sqliteCalls.toSorted((a, b) => a.persistedAt - b.persistedAt || a.sequence - b.sequence || a.orderKey.localeCompare(b.orderKey)).map((entry) => entry.call);
|
|
356
|
-
}
|
|
357
|
-
/** Persist one call record event to plugin state. */
|
|
358
|
-
async function persistCallRecord(storePath, call, stateRuntime) {
|
|
359
|
-
try {
|
|
360
|
-
const stores = createCallRecordStateStores(storePath, stateRuntime);
|
|
361
|
-
const order = nextCallRecordOrder();
|
|
362
|
-
await registerCallRecordEvent(stores, buildNewEventKey(order), call, order);
|
|
363
|
-
} catch (err) {
|
|
364
|
-
console.error("[voice-call] Failed to persist call record:", err);
|
|
365
|
-
throw err;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
/** Restore nonterminal active calls and provider/event indexes from persisted records. */
|
|
369
|
-
async function loadActiveCallsFromStore(storePath, stateRuntime) {
|
|
370
|
-
const stores = tryCreateCallRecordStateStores(storePath, stateRuntime);
|
|
371
|
-
let calls = [];
|
|
372
|
-
try {
|
|
373
|
-
calls = stores ? await readCallRecordEvents(stores) : [];
|
|
374
|
-
} catch (err) {
|
|
375
|
-
console.error("[voice-call] Failed to read SQLite call records:", err);
|
|
376
|
-
}
|
|
377
|
-
const callMap = /* @__PURE__ */ new Map();
|
|
378
|
-
for (const call of calls) {
|
|
379
|
-
callMap.delete(call.callId);
|
|
380
|
-
callMap.set(call.callId, call);
|
|
381
|
-
}
|
|
382
|
-
const activeCalls = /* @__PURE__ */ new Map();
|
|
383
|
-
const providerCallIdMap = /* @__PURE__ */ new Map();
|
|
384
|
-
const processedEventIds = /* @__PURE__ */ new Set();
|
|
385
|
-
for (const [callId, call] of callMap) {
|
|
386
|
-
trimCallReplayKeys(call.processedEventIds);
|
|
387
|
-
for (const eventId of call.processedEventIds) rememberManagerReplayKey(processedEventIds, eventId);
|
|
388
|
-
if (TerminalStates.has(call.state)) continue;
|
|
389
|
-
activeCalls.set(callId, call);
|
|
390
|
-
if (call.providerCallId) providerCallIdMap.set(call.providerCallId, callId);
|
|
391
|
-
}
|
|
392
|
-
return {
|
|
393
|
-
activeCalls,
|
|
394
|
-
providerCallIdMap,
|
|
395
|
-
processedEventIds
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
async function readCallHistoryFromStore(storePath, stateRuntime) {
|
|
399
|
-
const stores = tryCreateCallRecordStateStores(storePath, stateRuntime);
|
|
400
|
-
if (stores) try {
|
|
401
|
-
return await readCallRecordEvents(stores);
|
|
402
|
-
} catch (err) {
|
|
403
|
-
console.error("[voice-call] Failed to read SQLite call history:", err);
|
|
404
|
-
}
|
|
405
|
-
return [];
|
|
406
|
-
}
|
|
407
|
-
/** Resolve an internal ID or retained provider alias to its newest logical call snapshot. */
|
|
408
|
-
async function findCallInStore(storePath, callId, stateRuntime) {
|
|
409
|
-
const calls = await readCallRecordEvents(createCallRecordStateStores(storePath, stateRuntime));
|
|
410
|
-
const match = calls.findLast((call) => call.callId === callId) ?? calls.findLast((call) => call.providerCallId === callId);
|
|
411
|
-
return match ? calls.findLast((call) => call.callId === match.callId) : void 0;
|
|
412
|
-
}
|
|
413
|
-
/** Return the newest persisted call history rows up to the requested limit. */
|
|
414
|
-
async function getCallHistoryFromStore(storePath, limit = 50, stateRuntime) {
|
|
415
|
-
if (limit <= 0) return [];
|
|
416
|
-
return (await readCallHistoryFromStore(storePath, stateRuntime)).slice(-limit);
|
|
417
|
-
}
|
|
418
|
-
//#endregion
|
|
419
|
-
//#region extensions/voice-call/src/store-path.ts
|
|
420
|
-
/** Resolve the plugin-owned store below OpenClaw's canonical state directory. */
|
|
421
|
-
function resolveDefaultVoiceCallStoreDir(env = process.env) {
|
|
422
|
-
return path.join(resolveStateDir(env), "voice-calls");
|
|
423
|
-
}
|
|
424
|
-
//#endregion
|
|
425
|
-
//#region extensions/voice-call/src/utils.ts
|
|
426
|
-
/** Runtime uses the OS home; Doctor supplies the home from its migration environment. */
|
|
427
|
-
function resolveUserPath(input, homedir = os.homedir) {
|
|
428
|
-
const trimmed = input.trim();
|
|
429
|
-
if (!trimmed) return trimmed;
|
|
430
|
-
if (trimmed.startsWith("~")) {
|
|
431
|
-
const expanded = trimmed.replace(/^~(?=$|[\\/])/, homedir);
|
|
432
|
-
return path.resolve(expanded);
|
|
433
|
-
}
|
|
434
|
-
return path.resolve(trimmed);
|
|
435
|
-
}
|
|
436
|
-
//#endregion
|
|
437
|
-
export { TerminalStates as S, appendCallReplayKey as _, CALL_RECORD_EVENT_CHUNKS_NAMESPACE as a, reserveRejectedProviderCall as b, buildChunkKey as c, findCallInStore as d, getCallHistoryFromStore as f, resolveVoiceCallLegacyCallLogPath as g, persistCallRecord as h, CALL_RECORD_EVENTS_NAMESPACE as i, buildVoiceCallLegacyJsonlEventKey as l, parseVoiceCallRecordLine as m, resolveDefaultVoiceCallStoreDir as n, CALL_RECORD_EVENT_META_MAX_ENTRIES as o, loadActiveCallsFromStore as p, CALL_RECORD_CHUNK_MAX_ENTRIES as r, MAX_CALL_RECORD_EVENTS as s, resolveUserPath as t, encodeCallRecordEvent as u, releaseRejectedProviderCall as v, setVoiceCallStateRuntime as x, rememberManagerReplayKey as y };
|
package/node_modules/ws/LICENSE
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
|
|
2
|
-
Copyright (c) 2013 Arnout Kazemier and contributors
|
|
3
|
-
Copyright (c) 2016 Luigi Pinca and contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
-
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
-
the Software without restriction, including without limitation the rights to
|
|
8
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
-
subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|