@remnic/capture-audio 9.54.2 → 9.54.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/dist/{chunk-BRVXKUZY.js → chunk-MFWH245M.js} +603 -87
- package/dist/chunk-MFWH245M.js.map +1 -0
- package/dist/cli-bin.js +1 -1
- package/dist/index.d.ts +136 -6
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-BRVXKUZY.js.map +0 -1
package/dist/cli-bin.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ declare const CAPTURE_AUDIO_VERSION = "9.14.0";
|
|
|
12
12
|
declare const DEFAULT_HOST = "127.0.0.1";
|
|
13
13
|
declare const DEFAULT_PORT = 4340;
|
|
14
14
|
/** Spool schema version, persisted in the `meta` table. */
|
|
15
|
-
declare const SPOOL_SCHEMA_VERSION =
|
|
15
|
+
declare const SPOOL_SCHEMA_VERSION = 2;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Error taxonomy for @remnic/capture-audio.
|
|
@@ -68,6 +68,15 @@ interface DaemonConfig {
|
|
|
68
68
|
chunkSeconds: number;
|
|
69
69
|
captureChannel: "mic" | "system" | "both";
|
|
70
70
|
conversationGapMinutes: number;
|
|
71
|
+
/**
|
|
72
|
+
* Bounded reorder window, in seconds, for cross-channel arrival skew
|
|
73
|
+
* (issue #2145). Chunks are held until the newest observed chunk end is
|
|
74
|
+
* this far past their own end, then released oldest-first, so a delayed
|
|
75
|
+
* system chunk is grouped with the conversation it belongs to instead of
|
|
76
|
+
* a later mic chunk's. 0 disables buffering: every chunk is released on
|
|
77
|
+
* arrival, which is the pre-#2145 behavior.
|
|
78
|
+
*/
|
|
79
|
+
reorderWindowSeconds: number;
|
|
71
80
|
rawRetentionHours: number;
|
|
72
81
|
spoolRetentionDays: number;
|
|
73
82
|
vad: VadConfig;
|
|
@@ -158,6 +167,13 @@ type ChunkStatus = "pending" | "transcribed" | "failed" | "deleted";
|
|
|
158
167
|
interface SegmentInput {
|
|
159
168
|
speakerCluster?: string | null;
|
|
160
169
|
isWearer?: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Speaker embedding for this segment, persisted as a JSON BLOB (issue
|
|
172
|
+
* #2145). Clustering runs at finalize over the segments that SURVIVE
|
|
173
|
+
* cross-channel dedup, so a pruned loopback duplicate never inflates a
|
|
174
|
+
* cluster's centroid or count.
|
|
175
|
+
*/
|
|
176
|
+
embedding?: readonly number[] | null;
|
|
161
177
|
channel: string;
|
|
162
178
|
text: string;
|
|
163
179
|
startUtc: string;
|
|
@@ -287,8 +303,82 @@ declare class Spool {
|
|
|
287
303
|
* segment_count in sync. Returns the number actually removed.
|
|
288
304
|
*/
|
|
289
305
|
deleteSegments(ids: readonly string[]): number;
|
|
306
|
+
/**
|
|
307
|
+
* Segments of one conversation that still need a speaker, chronological.
|
|
308
|
+
*
|
|
309
|
+
* Only rows with a stored embedding and no cluster yet: clustering runs at
|
|
310
|
+
* finalize over the segments that SURVIVED dedup (issue #2145), and skipping
|
|
311
|
+
* already-assigned rows keeps a repeated finalize from double-counting a
|
|
312
|
+
* centroid.
|
|
313
|
+
*/
|
|
314
|
+
conversationSegmentsForDiarization(conversationId: string): Array<{
|
|
315
|
+
id: string;
|
|
316
|
+
channel: string;
|
|
317
|
+
embedding: number[];
|
|
318
|
+
}>;
|
|
319
|
+
/**
|
|
320
|
+
* Commit one conversation's diarization: cluster snapshots and the segment
|
|
321
|
+
* assignments that produced them, in ONE transaction.
|
|
322
|
+
*
|
|
323
|
+
* Splitting the two lets a crash persist an updated `embedding_count` while
|
|
324
|
+
* its segments stay unassigned; the next finalize would select the same rows
|
|
325
|
+
* and count the same embeddings again (issue #2145). Atomicity is what makes
|
|
326
|
+
* the repeated-finalize idempotency claim true.
|
|
327
|
+
*/
|
|
328
|
+
commitDiarization(input: {
|
|
329
|
+
clusters: readonly SpeakerInput[];
|
|
330
|
+
assignments: ReadonlyArray<{
|
|
331
|
+
id: string;
|
|
332
|
+
speakerCluster: string;
|
|
333
|
+
isWearer: boolean;
|
|
334
|
+
}>;
|
|
335
|
+
}): number;
|
|
290
336
|
/** Ids of every still-`capturing` conversation (dedup-before-finalize sweep). */
|
|
291
337
|
capturingConversationIds(): string[];
|
|
338
|
+
/**
|
|
339
|
+
* Record a bare idempotency marker (no segments).
|
|
340
|
+
*
|
|
341
|
+
* Used to persist facts a later replay cannot re-derive — such as how many
|
|
342
|
+
* segments a chunk's transcript produced, which is the only way to tell a
|
|
343
|
+
* legitimately shorter retranscription from a missing tail (issue #2145).
|
|
344
|
+
*/
|
|
345
|
+
markApplied(idempotencyKey: string, conversationId: string): void;
|
|
346
|
+
/**
|
|
347
|
+
* Whether ANY idempotency key for this chunk was applied.
|
|
348
|
+
*
|
|
349
|
+
* Only a SILENT replay needs this — a chunk partially applied by a binary
|
|
350
|
+
* predating the transcript manifest has no manifest to compare, and a
|
|
351
|
+
* zero-segment replay has no per-segment key to look up exactly. Speech
|
|
352
|
+
* chunks use the indexed manifest lookup below, so continuous capture never
|
|
353
|
+
* pays for this scan (issue #2145).
|
|
354
|
+
*/
|
|
355
|
+
hasAppliedChunkPrefix(chunkIdPrefix: string): boolean;
|
|
356
|
+
/**
|
|
357
|
+
* Conversations a chunk actually contributed stored segments to.
|
|
358
|
+
*
|
|
359
|
+
* Used to scope a rebuilt replay hold to the prefix that chunk belongs to,
|
|
360
|
+
* rather than to every conversation that happens to be capturing (#2145).
|
|
361
|
+
* Matches the bare chunk id and every per-segment or per-group derivative
|
|
362
|
+
* (`<chunkId>:h<hash>`, and the pre-manifest `<chunkId>:<n>`).
|
|
363
|
+
*/
|
|
364
|
+
conversationIdsForChunk(chunkId: string): string[];
|
|
365
|
+
/**
|
|
366
|
+
* Chunks whose transcript manifest is recorded but which never completed.
|
|
367
|
+
*
|
|
368
|
+
* A restart loses the in-memory record of which chunks are still awaiting a
|
|
369
|
+
* replay, so it is re-derived from these two durable markers: the manifest is
|
|
370
|
+
* written before any append, `:done` only after every segment is stored
|
|
371
|
+
* (issue #2145).
|
|
372
|
+
*/
|
|
373
|
+
incompleteChunkIds(): string[];
|
|
374
|
+
/**
|
|
375
|
+
* The value stored alongside an idempotency marker, or `undefined`.
|
|
376
|
+
*
|
|
377
|
+
* `markApplied` uses this column to carry a fact a replay cannot re-derive —
|
|
378
|
+
* the chunk's transcript manifest hash — and this is the exact, primary-key
|
|
379
|
+
* lookup that reads it back (issue #2145).
|
|
380
|
+
*/
|
|
381
|
+
appliedChunkValue(idempotencyKey: string): string | undefined;
|
|
292
382
|
/** Whether a chunk with this idempotency key was already durably applied. */
|
|
293
383
|
isChunkApplied(idempotencyKey: string): boolean;
|
|
294
384
|
/**
|
|
@@ -307,6 +397,12 @@ declare class Spool {
|
|
|
307
397
|
startedAtUtc: string;
|
|
308
398
|
endedAtUtc: string;
|
|
309
399
|
} | null;
|
|
400
|
+
/** One capturing conversation by id, for resuming a specific prefix. */
|
|
401
|
+
capturingConversationById(id: string): {
|
|
402
|
+
id: string;
|
|
403
|
+
startedAtUtc: string;
|
|
404
|
+
endedAtUtc: string;
|
|
405
|
+
} | null;
|
|
310
406
|
upsertSpeaker(input: SpeakerInput): void;
|
|
311
407
|
/** Read every speaker cluster with decoded centroid + examples (diarization restart seed). */
|
|
312
408
|
readSpeakerClusters(): SpeakerClusterRow[];
|
|
@@ -686,6 +782,27 @@ declare class ConversationAssembler {
|
|
|
686
782
|
startedAtUtc: string;
|
|
687
783
|
endedAtUtc: string;
|
|
688
784
|
}): void;
|
|
785
|
+
/**
|
|
786
|
+
* Drop finalized conversations the caller no longer needs.
|
|
787
|
+
*
|
|
788
|
+
* A long-running daemon would otherwise retain every conversation and every
|
|
789
|
+
* segment forever, which makes the rollback snapshot below O(capture
|
|
790
|
+
* history) and the daemon's per-chunk work quadratic (issue #2145). Only the
|
|
791
|
+
* open conversation can still be mutated, so nothing else needs keeping.
|
|
792
|
+
*/
|
|
793
|
+
pruneFinalized(): number;
|
|
794
|
+
/**
|
|
795
|
+
* Deep snapshot for rollback (issue #2145).
|
|
796
|
+
*
|
|
797
|
+
* `add` mutates the open conversation in place. A caller that fails BEFORE
|
|
798
|
+
* anything was persisted must be able to rewind, or the retry feeds earlier
|
|
799
|
+
* timestamps into an advanced assembler and collapses conversations the
|
|
800
|
+
* first attempt had split. A caller that already persisted something must
|
|
801
|
+
* NOT rewind: the durable ids would then diverge from the in-memory ones.
|
|
802
|
+
*/
|
|
803
|
+
checkpoint(): AssembledConversation[];
|
|
804
|
+
/** Rewind to a {@link checkpoint}. */
|
|
805
|
+
rewind(snapshot: readonly AssembledConversation[]): void;
|
|
689
806
|
/** Ordered snapshot; segments are cloned so callers cannot mutate internal state. */
|
|
690
807
|
conversations(): AssembledConversation[];
|
|
691
808
|
/**
|
|
@@ -733,6 +850,15 @@ declare class SpeakerClusterer {
|
|
|
733
850
|
enrollSelf(embedding: Embedding): void;
|
|
734
851
|
/** Match `embedding` to an existing cluster or create a new `spk_<n>`. */
|
|
735
852
|
assign(embedding: Embedding): string;
|
|
853
|
+
/**
|
|
854
|
+
* Replace every cluster with `snapshot` (issue #2145).
|
|
855
|
+
*
|
|
856
|
+
* `assign` mutates centroids and counts in place, so a diarization commit
|
|
857
|
+
* that rolls back in SQLite must roll back here too — otherwise the retry
|
|
858
|
+
* counts the same embeddings twice. Deep-copied, so the caller's snapshot
|
|
859
|
+
* cannot alias internal state.
|
|
860
|
+
*/
|
|
861
|
+
restore(snapshot: readonly SpeakerCluster[]): void;
|
|
736
862
|
/** Snapshot for persistence. */
|
|
737
863
|
clusters(): SpeakerCluster[];
|
|
738
864
|
}
|
|
@@ -927,6 +1053,15 @@ interface ChunkProcessorDeps {
|
|
|
927
1053
|
diarizer?: SpeakerClusterer;
|
|
928
1054
|
/** Cross-channel dedup window in ms; defaults to the dedup module's tolerance. */
|
|
929
1055
|
dedupWindowMs?: number;
|
|
1056
|
+
/**
|
|
1057
|
+
* Bounded reorder window in ms for cross-channel arrival skew (issue
|
|
1058
|
+
* #2145). A transcribed chunk is HELD until the newest observed chunk end
|
|
1059
|
+
* is this far past its own end, then released oldest-first, so a delayed
|
|
1060
|
+
* system chunk is assembled into the conversation it temporally belongs to
|
|
1061
|
+
* rather than joined to a later mic chunk's. 0 (the default here, so
|
|
1062
|
+
* existing callers are unchanged) releases every chunk on arrival.
|
|
1063
|
+
*/
|
|
1064
|
+
reorderWindowMs?: number;
|
|
930
1065
|
/** Reports a per-chunk failure; the chain keeps running afterwards. */
|
|
931
1066
|
onError?: (error: Error, event: ChunkEvent) => void;
|
|
932
1067
|
}
|
|
@@ -938,11 +1073,6 @@ interface ChunkProcessor {
|
|
|
938
1073
|
/** Drain, then flip open conversations to `final`; returns the count closed. */
|
|
939
1074
|
finalize(): Promise<number>;
|
|
940
1075
|
}
|
|
941
|
-
/**
|
|
942
|
-
* Stable chunk identity derived purely from the WAV path. Because it never
|
|
943
|
-
* depends on a freshly-generated conversation id, the same chunk yields the
|
|
944
|
-
* same idempotency key across process restarts.
|
|
945
|
-
*/
|
|
946
1076
|
declare function chunkStableId(event: ChunkEvent): string;
|
|
947
1077
|
declare function createChunkProcessor(deps: ChunkProcessorDeps): ChunkProcessor;
|
|
948
1078
|
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/capture-audio",
|
|
3
|
-
"version": "9.54.
|
|
3
|
+
"version": "9.54.4",
|
|
4
4
|
"description": "Desktop audio capture daemon for Remnic — local spool, loopback HTTP API, and replay ingestion for the wearable source `desktop` (à-la-carte)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"tsup": "^8.0.0",
|
|
30
30
|
"typescript": "^5.7.0",
|
|
31
31
|
"tsx": "^4.0.0",
|
|
32
|
-
"@remnic/core": "9.54.
|
|
32
|
+
"@remnic/core": "9.54.4"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@remnic/core": "^9.54.
|
|
35
|
+
"@remnic/core": "^9.54.4",
|
|
36
36
|
"sherpa-onnx-node": "*",
|
|
37
|
-
"@remnic/capture-native-darwin-arm64": "^9.54.
|
|
38
|
-
"@remnic/capture-native-darwin-x64": "^9.54.
|
|
37
|
+
"@remnic/capture-native-darwin-arm64": "^9.54.4",
|
|
38
|
+
"@remnic/capture-native-darwin-x64": "^9.54.4"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"sherpa-onnx-node": {
|