@gotcos/glasses-server 6.27.9 → 6.27.11
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/CHANGELOG.md +54 -0
- package/package.json +1 -1
- package/server/routes/meeting.ts +65 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
## Unreleased
|
|
2
2
|
|
|
3
|
+
## 6.27.11
|
|
4
|
+
- **SAFETY REVERT: the 6.27.10 relabel enrolment is disabled.** It joined two
|
|
5
|
+
different index spaces. `plan.value.changed` are positions in the COMPACTED sidecar
|
|
6
|
+
array (`meeting-relabel.ts:119`, over rows already filtered to those carrying text)
|
|
7
|
+
while the chunk-embedding store is keyed on the RAW capture index
|
|
8
|
+
(`transcribe-stream.ts:1868`). They diverge at the first text-less chunk.
|
|
9
|
+
- Measured on a live session: naming ONE voice enrolled **73 of 103 rows belonging to
|
|
10
|
+
other people**, including **22 chunks of the owner**, plus five colleagues. It
|
|
11
|
+
reported success because rows do come back — the wrong ones. 73 of 74 live sessions
|
|
12
|
+
with embeddings have gaps, so this was the normal case, not an edge case.
|
|
13
|
+
- `attachRawChunkIndices` is the conversion for exactly this. It was already imported
|
|
14
|
+
in the file, used correctly by the review path, and carries a comment three hundred
|
|
15
|
+
lines above the change reading "`chunks` is NOT the WAV number".
|
|
16
|
+
- **Anyone on 6.27.10 should update.** Naming a voice in the review panel could write
|
|
17
|
+
other people — including the owner — into that person's profile, permanently and
|
|
18
|
+
across every future meeting. The relabel itself was always correct; only the
|
|
19
|
+
enrolment side effect was wrong.
|
|
20
|
+
|
|
21
|
+
Re-enabling requires all of: the `attachRawChunkIndices` mapping with a refusal when
|
|
22
|
+
it is unavailable; a coherence gate (an `Ext` bucket is many voices — 98% of its
|
|
23
|
+
pairwise cosines fall below the identifier's own 0.55 accept threshold); a
|
|
24
|
+
`correction:<sessionId>` source tag so samples are human-tier, quota-protected and
|
|
25
|
+
retractable by the existing de-attribute path (a bare `meeting-relabel` tag matches
|
|
26
|
+
none of the three prefixes `isSampleFromSession` accepts, so "Not in this meeting"
|
|
27
|
+
silently retracts nothing); `greedyDiversitySelect` to bound the loop (~41ms per
|
|
28
|
+
cycle against a 7.9 MB store x 109 chunks blocks the event loop past the helper's
|
|
29
|
+
30s timeout); and honest reporting so a zero enrolment is distinguishable from
|
|
30
|
+
success. The COS Control scope-picker copy also still reads "Corrects this meeting
|
|
31
|
+
only. Other meetings are left alone."
|
|
32
|
+
|
|
33
|
+
## 6.27.10
|
|
34
|
+
- **Naming an unidentified voice now creates a real profile.** `POST /api/meeting/:id/relabel`
|
|
35
|
+
was TEXT ONLY: it rewrote `speaker` strings in the sidecar and never touched the
|
|
36
|
+
voice store. Saving "Kirstyn Blum" over 109 segments labelled that one meeting and
|
|
37
|
+
taught the system nothing — she never appeared in `/api/voice/profiles`, the review
|
|
38
|
+
panel still offered her as `new name` inside the SAME meeting, no later meeting
|
|
39
|
+
could match her, and there was no profile to add further chunks against.
|
|
40
|
+
Verified on the live store: 77 profiles, no Kirstyn Blum, while both sidecars
|
|
41
|
+
carried her name.
|
|
42
|
+
- The server already had `/api/voice/enroll-ext` for exactly this and the naming flow
|
|
43
|
+
never called it. Relabel now enrols the embeddings of the chunks it actually
|
|
44
|
+
changed, tagged `source: meeting-relabel` so a bad batch can be retracted wholesale.
|
|
45
|
+
- **Scoped deliberately.** Enrolment runs ONLY when a placeholder (`Ext`,
|
|
46
|
+
`Unidentified N`, `Speaker N`, `Unknown`) becomes a real name. Correcting one real
|
|
47
|
+
name to another is left alone: moving a voice between existing people is
|
|
48
|
+
`merge-profiles`, which is explicit and confirmation-gated. A cross-roster sweep of
|
|
49
|
+
this store put two DISTINCT people at 0.85 similarity, so implicit re-pointing would
|
|
50
|
+
poison profiles.
|
|
51
|
+
- Enrolment happens after the sidecar and ledger are durable, and a throwing voice
|
|
52
|
+
store cannot undo the rename the user asked for. `enrolledEmbeddings` is returned so
|
|
53
|
+
the panel can confirm a profile was created.
|
|
54
|
+
- Four tests, both directions mutation-checked: reverting to text-only fails the
|
|
55
|
+
enrolment test; dropping the placeholder guard fails both safety tests.
|
|
56
|
+
|
|
3
57
|
## 6.27.9
|
|
4
58
|
- **Large sessions open instead of 413ing.** `GET /api/agent-sessions/:provider/:id`
|
|
5
59
|
answered `413 Session too large to open` for any transcript over 32 MiB, so the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotcos/glasses-server",
|
|
3
|
-
"version": "6.27.
|
|
3
|
+
"version": "6.27.11",
|
|
4
4
|
"description": "COS Glasses \u2014 self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, or Cursor Agent CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server/routes/meeting.ts
CHANGED
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
meetingAudioChunkPath,
|
|
24
24
|
meetingAudioRetentionDays,
|
|
25
25
|
} from '../lib/meeting-audio-archive.js'
|
|
26
|
-
import { readVoiceProfiles, retractEmbeddingsBySource } from '../lib/speaker-embeddings.js'
|
|
26
|
+
import { enrollEmbedding, readVoiceProfiles, retractEmbeddingsBySource } from '../lib/speaker-embeddings.js'
|
|
27
|
+
import { chunkEmbeddingsForIndices } from '../lib/chunk-embedding-store.js'
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* The label a de-attributed voice takes, numbered within its meeting.
|
|
@@ -974,6 +975,46 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
974
975
|
})
|
|
975
976
|
})
|
|
976
977
|
|
|
978
|
+
/** Labels the diariser invents when it does not know who is speaking. Naming one
|
|
979
|
+
* of these is a FIRST TRAINING RUN for a new person, not a correction. */
|
|
980
|
+
const PLACEHOLDER_LABEL = /^(ext|unknown|unidentified(\s+\d+)?|speaker\s*\d+)$/i
|
|
981
|
+
const isPlaceholderLabel = (label: string): boolean => PLACEHOLDER_LABEL.test(label.trim())
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Turn a named placeholder into a real voice profile.
|
|
985
|
+
*
|
|
986
|
+
* WHY THIS EXISTS. Relabelling was TEXT ONLY: it rewrote `speaker` strings in the
|
|
987
|
+
* meeting sidecar and never touched the voice store. So naming an unidentified
|
|
988
|
+
* voice "Kirstyn Blum" labelled that one meeting and taught the system nothing —
|
|
989
|
+
* she never appeared in `/api/voice/profiles`, the review panel still offered her
|
|
990
|
+
* as `new name` inside the SAME meeting, no later meeting could match her, and
|
|
991
|
+
* there was no profile to accumulate further chunks against. The server already
|
|
992
|
+
* had `/api/voice/enroll-ext` for exactly this and the naming flow never called it.
|
|
993
|
+
*
|
|
994
|
+
* Enrolment is ADDITIVE and scoped: it runs only when a placeholder becomes a real
|
|
995
|
+
* name. Correcting one real name to another is left alone deliberately — moving a
|
|
996
|
+
* voice between existing people is `merge-profiles`, which is explicit and
|
|
997
|
+
* confirmation-gated, and doing it implicitly here would poison profiles.
|
|
998
|
+
*
|
|
999
|
+
* `enrollEmbedding` owns the diversity gate and the FIFO cap, so feeding it the
|
|
1000
|
+
* relabelled chunks cannot bloat a profile.
|
|
1001
|
+
*/
|
|
1002
|
+
const enrolNamedVoice = (sessionId: string, from: string, to: string, changed: number[]): number => {
|
|
1003
|
+
if (!isPlaceholderLabel(from) || isPlaceholderLabel(to) || changed.length === 0) return 0
|
|
1004
|
+
let enrolled = 0
|
|
1005
|
+
try {
|
|
1006
|
+
for (const row of chunkEmbeddingsForIndices(sessionId, changed)) {
|
|
1007
|
+
if (!row.embedding) continue
|
|
1008
|
+
if (enrollEmbedding(to, row.embedding, 'meeting-relabel').success) enrolled += 1
|
|
1009
|
+
}
|
|
1010
|
+
} catch {
|
|
1011
|
+
// Enrolment is a bonus on top of the relabel. A voice store that refuses must
|
|
1012
|
+
// not fail the rename the user actually asked for.
|
|
1013
|
+
return enrolled
|
|
1014
|
+
}
|
|
1015
|
+
return enrolled
|
|
1016
|
+
}
|
|
1017
|
+
|
|
977
1018
|
router.post('/meeting/:sessionId/relabel', (req, res) => {
|
|
978
1019
|
res.set('Cache-Control', 'private, no-store')
|
|
979
1020
|
const sessionId = String(req.params.sessionId ?? '')
|
|
@@ -1150,7 +1191,29 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
1150
1191
|
proseStale: preview.proseStale,
|
|
1151
1192
|
})
|
|
1152
1193
|
|
|
1153
|
-
|
|
1194
|
+
// Enrol AFTER the sidecar and ledger are durable: the rename is the thing the
|
|
1195
|
+
// user asked for, and a voice store that refuses must not undo it. Reported so
|
|
1196
|
+
// the panel can say a profile was created rather than leaving the user to
|
|
1197
|
+
// discover, in another meeting, that it was not.
|
|
1198
|
+
// DISABLED in 6.27.11. 6.27.10 shipped this joining the WRONG INDEX SPACE:
|
|
1199
|
+
// `plan.value.changed` are positions in the COMPACTED sidecar array
|
|
1200
|
+
// (meeting-relabel.ts:119, over rows already filtered to those with text) while
|
|
1201
|
+
// the embedding store is keyed on the RAW capture index
|
|
1202
|
+
// (transcribe-stream.ts:1868). They diverge at the first text-less chunk.
|
|
1203
|
+
//
|
|
1204
|
+
// Measured on a live session: naming one voice enrolled 73 of 103 rows belonging
|
|
1205
|
+
// to OTHER people, including 22 chunks of the owner. It reported success because
|
|
1206
|
+
// rows do come back — just the wrong ones. 73 of 74 live sessions have gaps, so
|
|
1207
|
+
// this was the normal case.
|
|
1208
|
+
//
|
|
1209
|
+
// `attachRawChunkIndices` is the conversion for this and is imported at line 126,
|
|
1210
|
+
// used correctly by the review path at 755 and 856. Re-enable only WITH that
|
|
1211
|
+
// mapping, a refusal when the mapping is unavailable, a coherence gate (an `Ext`
|
|
1212
|
+
// bucket is many voices — 98% of its pairwise cosines fall below the identifier's
|
|
1213
|
+
// own 0.55 accept threshold), and a `correction:<sessionId>` source tag so the
|
|
1214
|
+
// samples are human-tier, quota-protected and retractable.
|
|
1215
|
+
const enrolled = 0
|
|
1216
|
+
res.json({ ok: true, correctionId: id, enrolledEmbeddings: enrolled, ...preview })
|
|
1154
1217
|
})
|
|
1155
1218
|
|
|
1156
1219
|
|