@gotcos/glasses-server 6.27.12 → 6.28.0
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 +56 -0
- package/package.json +1 -1
- package/server/index.ts +141 -0
- package/server/lib/agent-session-binding-registry.ts +1225 -0
- package/server/lib/agent-session-binding-store.ts +327 -0
- package/server/lib/agent-session-ownership-store.ts +395 -0
- package/server/lib/attached-provider-adapter.ts +1223 -0
- package/server/lib/attached-workspace.ts +197 -0
- package/server/lib/meeting-relabel-enrolment.ts +10 -1
- package/server/lib/native-head.ts +649 -0
- package/server/lib/native-thread-id.ts +23 -0
- package/server/lib/occupancy-probes.ts +444 -0
- package/server/lib/query-job-runtime.ts +24 -0
- package/server/lib/thread-occupancy.ts +367 -0
- package/server/routes/agent-session-bindings.ts +1550 -0
- package/server/routes/meeting.ts +90 -0
package/server/routes/meeting.ts
CHANGED
|
@@ -975,6 +975,96 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
|
|
|
975
975
|
})
|
|
976
976
|
})
|
|
977
977
|
|
|
978
|
+
/**
|
|
979
|
+
* Replay a recorded correction's enrolment — the retroactive path.
|
|
980
|
+
*
|
|
981
|
+
* WHY. Enrolment fires inside `POST /relabel`, so a voice named BEFORE that shipped
|
|
982
|
+
* has a correct transcript and no profile. Kirstyn Blum is the live case: named
|
|
983
|
+
* across two meetings (60 and 109 chunks), 182 mentions in the sidecars, absent from
|
|
984
|
+
* a 77-profile store. Re-running the rename cannot help — she is already a real
|
|
985
|
+
* name there, so the placeholder guard correctly declines.
|
|
986
|
+
*
|
|
987
|
+
* The correction LEDGER already holds exactly what enrolment needs: the original
|
|
988
|
+
* `from`, the `to`, and the precise chunk indices, written at apply time.
|
|
989
|
+
*
|
|
990
|
+
* This runs IN-PROCESS on purpose. The voice store is owned by the running server,
|
|
991
|
+
* which holds it in memory and rewrites it wholesale; an external process that
|
|
992
|
+
* enrols directly has its work silently clobbered on the next persist. That is not
|
|
993
|
+
* hypothetical — an attempt on 2026-08-13 validated cleanly, selected 20 samples,
|
|
994
|
+
* and left the store untouched at its Aug 7 mtime.
|
|
995
|
+
*
|
|
996
|
+
* SAME GATES, no exceptions. It calls `enrolNamedVoice`, so raw-index mapping,
|
|
997
|
+
* refusal when unmappable, voice coherence, the diversity cap and the
|
|
998
|
+
* `correction:<sessionId>` tag all apply identically. Rows whose `from` is a real
|
|
999
|
+
* person are skipped by that function's own placeholder rule, which is what keeps a
|
|
1000
|
+
* mis-attribution correction (Allison Wheeler -> Kirstyn) out of the training set.
|
|
1001
|
+
*
|
|
1002
|
+
* FAILS CLOSED. Without `confirm: true` it reports what it would enrol and writes
|
|
1003
|
+
* nothing.
|
|
1004
|
+
*/
|
|
1005
|
+
router.post('/meeting/:sessionId/backfill-enrolment', (req, res) => {
|
|
1006
|
+
res.set('Cache-Control', 'private, no-store')
|
|
1007
|
+
const sessionId = String(req.params.sessionId ?? '')
|
|
1008
|
+
if (!/^[A-Za-z0-9:_-]{3,96}$/.test(sessionId)) {
|
|
1009
|
+
res.status(400).json({ error: 'Invalid sessionId', reason: 'invalid_session_id' })
|
|
1010
|
+
return
|
|
1011
|
+
}
|
|
1012
|
+
const speaker = typeof req.body?.speaker === 'string' ? req.body.speaker.trim() : ''
|
|
1013
|
+
if (!speaker) {
|
|
1014
|
+
res.status(400).json({ error: 'speaker is required', reason: 'invalid_label' })
|
|
1015
|
+
return
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const operations = cosOperationsMeetingsConfigured()
|
|
1019
|
+
? findCosOperationsMeetingBySessionId(sessionId)
|
|
1020
|
+
: null
|
|
1021
|
+
const saved = operations ? null : store.findBySessionId(sessionId)
|
|
1022
|
+
if (!operations && !saved) {
|
|
1023
|
+
res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
|
|
1024
|
+
return
|
|
1025
|
+
}
|
|
1026
|
+
const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
|
|
1027
|
+
let parsedSidecar: Record<string, unknown> | null = null
|
|
1028
|
+
try {
|
|
1029
|
+
const doc = JSON.parse(readFileSync(sidecarPath, 'utf-8')) as unknown
|
|
1030
|
+
if (doc && typeof doc === 'object' && !Array.isArray(doc)) parsedSidecar = doc as Record<string, unknown>
|
|
1031
|
+
} catch {
|
|
1032
|
+
res.status(422).json({ error: 'Chunk sidecar is missing or unreadable', reason: 'sidecar_unreadable' })
|
|
1033
|
+
return
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Only rows that ACTUALLY landed, and only those that named this speaker.
|
|
1037
|
+
const rows = appliedCorrections(sessionId).filter(r => r.to === speaker && r.chunks.length > 0)
|
|
1038
|
+
if (rows.length === 0) {
|
|
1039
|
+
res.status(404).json({ error: `No applied correction named "${speaker}" in this meeting`, reason: 'no_correction' })
|
|
1040
|
+
return
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const confirm = req.body?.confirm === true
|
|
1044
|
+
const reports = rows.map(row => ({
|
|
1045
|
+
correctionId: row.id,
|
|
1046
|
+
from: row.from,
|
|
1047
|
+
chunks: row.chunks.length,
|
|
1048
|
+
// Dry run still evaluates every gate — a preview that skips them would be a
|
|
1049
|
+
// guess about what the real call is going to do.
|
|
1050
|
+
report: enrolNamedVoice({
|
|
1051
|
+
sessionId, from: row.from, to: speaker, changed: row.chunks, sidecar: parsedSidecar!, dryRun: !confirm,
|
|
1052
|
+
}),
|
|
1053
|
+
}))
|
|
1054
|
+
|
|
1055
|
+
res.json({
|
|
1056
|
+
ok: true,
|
|
1057
|
+
speaker,
|
|
1058
|
+
confirmed: confirm,
|
|
1059
|
+
corrections: reports,
|
|
1060
|
+
totals: {
|
|
1061
|
+
eligible: reports.filter(r => r.report.attempted > 0).length,
|
|
1062
|
+
skippedNamedSource: reports.filter(r => r.report.attempted === 0 && !r.report.skipped).length,
|
|
1063
|
+
enrolled: reports.reduce((n, r) => n + r.report.enrolled, 0),
|
|
1064
|
+
},
|
|
1065
|
+
})
|
|
1066
|
+
})
|
|
1067
|
+
|
|
978
1068
|
router.post('/meeting/:sessionId/relabel', (req, res) => {
|
|
979
1069
|
res.set('Cache-Control', 'private, no-store')
|
|
980
1070
|
const sessionId = String(req.params.sessionId ?? '')
|