@gotcos/glasses-server 6.36.28 → 6.37.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.
@@ -61,6 +61,14 @@ import {
61
61
  appendChunkEmbedding,
62
62
  sweepExpiredChunkEmbeddings,
63
63
  } from '../lib/chunk-embedding-store.js'
64
+ import {
65
+ evenSpeakerRoleMode,
66
+ formatEvenRoleAgreement,
67
+ parseEvenHubSpeakerRoleBody,
68
+ parseEvenHubSpeakerRoleQuery,
69
+ warnEvenSpeakerRoleApplyNotImplemented,
70
+ type EvenSpeakerRoleHistogram,
71
+ } from '../lib/even-hub-speaker-role.js'
64
72
  import { archiveSessionAudio, runMeetingAudioRetention } from '../lib/meeting-audio-archive.js'
65
73
  import {
66
74
  countChunkWavs,
@@ -276,6 +284,7 @@ export interface TranscriptChunk {
276
284
  latencyMs?: number
277
285
  audioSha256?: string
278
286
  canonical?: boolean
287
+ evenHubSpeakerRole?: EvenSpeakerRoleHistogram
279
288
  }
280
289
 
281
290
  export interface ProviderCandidateRecord {
@@ -1945,8 +1954,11 @@ async function processStreamChunk(opts: {
1945
1954
  clientElapsed?: number
1946
1955
  /** Original client recording start, applied only before canonical chunks. */
1947
1956
  startTimeOverride?: number
1957
+ evenHubSpeakerRole?: EvenSpeakerRoleHistogram
1948
1958
  }): Promise<StreamChunkCompletionResponse> {
1949
1959
  const { sessionId, chunkIndex, clientSpeaker, audioBuffer, candidate } = opts
1960
+ const evenHubSpeakerRole = evenSpeakerRoleMode() === 'off' ? undefined : opts.evenHubSpeakerRole
1961
+ if (evenHubSpeakerRole) warnEvenSpeakerRoleApplyNotImplemented()
1950
1962
  const tReq = performance.now()
1951
1963
  validateSessionId(sessionId)
1952
1964
  validateChunkIndex(chunkIndex)
@@ -2066,6 +2078,15 @@ async function processStreamChunk(opts: {
2066
2078
  }
2067
2079
 
2068
2080
  const { speaker, similarity } = await speakerPromise
2081
+ if (evenHubSpeakerRole) {
2082
+ console.log(formatEvenRoleAgreement({
2083
+ chunkIndex,
2084
+ even: evenHubSpeakerRole,
2085
+ amp: clientSpeaker,
2086
+ emb: speaker,
2087
+ similarity,
2088
+ }))
2089
+ }
2069
2090
  // Client time is authoritative for live network jitter and deferred replay.
2070
2091
  const elapsed = Number.isFinite(opts.clientElapsed) && (opts.clientElapsed as number) >= 0
2071
2092
  ? Math.round(opts.clientElapsed as number)
@@ -2110,6 +2131,7 @@ async function processStreamChunk(opts: {
2110
2131
  latencyMs,
2111
2132
  audioSha256,
2112
2133
  canonical: true,
2134
+ evenHubSpeakerRole,
2113
2135
  }
2114
2136
  const finalExisting = session.chunks[chunkIndex]
2115
2137
  if (finalExisting?.text) {
@@ -2263,6 +2285,7 @@ transcribeStreamRouter.post('/transcribe-stream', async (req, res) => {
2263
2285
  audioBuffer,
2264
2286
  clientElapsed,
2265
2287
  startTimeOverride,
2288
+ evenHubSpeakerRole: parseEvenHubSpeakerRoleQuery(req.query.eh),
2266
2289
  }))
2267
2290
  } catch (err: unknown) {
2268
2291
  sendStreamError(res, err)
@@ -2327,6 +2350,7 @@ transcribeStreamRouter.post('/transcribe-stream/offline-sessions/:sessionId/chun
2327
2350
  clientSpeaker: String(body.clientSpeaker ?? 'Unknown'),
2328
2351
  audioBuffer,
2329
2352
  clientElapsed: typeof body.elapsed === 'number' ? body.elapsed : Number(body.elapsed ?? 0),
2353
+ evenHubSpeakerRole: parseEvenHubSpeakerRoleBody(body.evenHubSpeakerRole),
2330
2354
  candidate: {
2331
2355
  provider: 'iphone-whisperkit-beta',
2332
2356
  text: normalizeCandidateText(candidate.text),
@@ -2405,6 +2429,7 @@ transcribeStreamRouter.post('/transcribe-stream/candidates', async (req, res) =>
2405
2429
  clientSpeaker: String(body.clientSpeaker ?? 'Unknown'),
2406
2430
  audioBuffer,
2407
2431
  clientElapsed: typeof body.elapsed === 'number' ? body.elapsed : Number(body.elapsed ?? 0),
2432
+ evenHubSpeakerRole: parseEvenHubSpeakerRoleBody(body.evenHubSpeakerRole),
2408
2433
  candidate: {
2409
2434
  provider: 'iphone-whisperkit-beta',
2410
2435
  text: normalizeCandidateText(candidate.text),
@@ -4,6 +4,7 @@ import { Router } from 'express'
4
4
  import { errMsg } from '../lib/utils.js'
5
5
  import { readdirSync, readFileSync, unlinkSync, existsSync, rmdirSync, rmSync } from 'node:fs'
6
6
  import { resolve } from 'node:path'
7
+ import { checkSpeakerName } from '../lib/speaker-name.js'
7
8
  import { enrollSpeaker, isEnrolled, getAllSpeakerNames, identifySpeaker, extractEmbedding, enrollEmbedding, getEmbeddingCount, removeSpeakerProfile, readVoiceProfiles, mergeSpeakerProfiles } from '../lib/speaker-embeddings.js'
8
9
  import { statSync } from 'node:fs'
9
10
  import { trainFromFireflies, getTrainingStatus } from '../lib/speaker-trainer.js'
@@ -48,6 +49,20 @@ voiceRouter.post('/voice/enroll', async (req, res) => {
48
49
  // under a stranger's name.
49
50
  const name = (req.query.name as string) || getOwnerSpeakerLabel()
50
51
 
52
+ // An old client can still send a whole spoken sentence as the name (the
53
+ // "enroll my voice" fall-through). Refuse it here: a junk profile can
54
+ // never match owner_speaker_label, so /voice/status would report
55
+ // enrolled:false forever, and the store cannot be repaired by editing
56
+ // voice-profiles.json — the server rewrites it from memory.
57
+ const nameCheck = checkSpeakerName(name, { ownerLabel: getOwnerSpeakerLabel() })
58
+ if (!nameCheck.ok) {
59
+ return res.status(400).json({
60
+ success: false,
61
+ error: nameCheck.message,
62
+ reason: nameCheck.reason,
63
+ })
64
+ }
65
+
51
66
  // Collect raw audio body
52
67
  const buffers: Buffer[] = []
53
68
  for await (const chunk of req) {