@gotcos/glasses-server 6.21.31 → 6.21.33

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.
@@ -11,6 +11,7 @@ import { durableAtomicWriteFileSync } from '../lib/atomic-fs.js'
11
11
  import { appendCorrection, appliedCorrections, pendingCorrections } from '../lib/meeting-corrections.js'
12
12
  import { isSampleFromSession, untraceableSampleCount } from '../lib/training-audio-provenance.js'
13
13
  import { sendAudioFile } from '../lib/send-audio.js'
14
+ import { adaptivePlaybackAudio } from '../lib/adaptive-playback-audio.js'
14
15
  import { chunkDiagnostics } from '../lib/chunk-embedding-diagnostics.js'
15
16
  import { errMsg } from '../lib/utils.js'
16
17
  import { confirmedLabels } from '../lib/meeting-corrections.js'
@@ -100,6 +101,7 @@ import {
100
101
  getSessionStartTime,
101
102
  getSessionTranscript,
102
103
  getMeetingSessionStatus,
104
+ getTranscriptionSessionLiveness,
103
105
  hasSessionAudio,
104
106
  moveSessionAudioToPending,
105
107
  type IndexedTranscriptChunk,
@@ -110,6 +112,7 @@ import {
110
112
  import { getServerInstanceId } from '../lib/server-instance-id.js'
111
113
  import {
112
114
  cosOperationsMeetingsConfigured,
115
+ findDirectLibraryMeetingBySessionId,
113
116
  findCosOperationsMeetingBySessionId,
114
117
  resolveCosOperationsDir,
115
118
  } from '../lib/cos-operations-meetings.js'
@@ -154,6 +157,10 @@ function cosOpsPipelineConfigured(): boolean {
154
157
  return Boolean(process.env.COS_SCRIPTS_DIR?.trim())
155
158
  }
156
159
 
160
+ function requestedRecordMatches(requested: unknown, expected: string): boolean {
161
+ return requested == null || requested === '' || requested === expected
162
+ }
163
+
157
164
  interface MeetingSessionSource {
158
165
  getTranscript(sessionId: string): string | null
159
166
  getStartTime(sessionId: string): number | null
@@ -710,15 +717,16 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
710
717
  const operations = cosOperationsMeetingsConfigured()
711
718
  ? findCosOperationsMeetingBySessionId(sessionId)
712
719
  : null
713
- const saved = operations ? null : store.findBySessionId(sessionId)
714
- if (!operations && !saved) {
720
+ const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
721
+ const saved = operations || direct ? null : store.findBySessionId(sessionId)
722
+ if (!operations && !direct && !saved) {
715
723
  res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
716
724
  return
717
725
  }
718
- const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
719
- const title = operations?.title ?? saved!.title
720
- const domain = operations?.domain ?? saved!.domain
721
- const filename = operations?.filename ?? saved!.filename
726
+ const sidecarPath = operations?.sidecarPath ?? direct?.sidecarPath ?? saved!.sidecarPath
727
+ const title = operations?.title ?? direct?.title ?? saved!.title
728
+ const domain = operations?.domain ?? direct?.domain ?? saved!.domain
729
+ const filename = operations?.filename ?? direct?.filename ?? saved!.filename
722
730
 
723
731
  let chunks: unknown
724
732
  try {
@@ -768,7 +776,11 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
768
776
  title,
769
777
  domain,
770
778
  filename,
771
- source: operations ? 'cos_operations' : 'standalone_recordings',
779
+ source: operations ? 'cos_operations' : direct ? 'direct_library' : 'standalone_recordings',
780
+ recordId: operations
781
+ ? `ops:${operations.domain}:${operations.month}:${operations.filename}`
782
+ : direct?.recordId ?? `standalone:${sessionId}`,
783
+ mutable: direct == null,
772
784
  ...(saved ? { durationMin: saved.durationMin } : {}),
773
785
  ...review,
774
786
  })
@@ -810,14 +822,15 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
810
822
  const operations = cosOperationsMeetingsConfigured()
811
823
  ? findCosOperationsMeetingBySessionId(sessionId)
812
824
  : null
813
- const saved = operations ? null : store.findBySessionId(sessionId)
814
- if (!operations && !saved) {
825
+ const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
826
+ const saved = operations || direct ? null : store.findBySessionId(sessionId)
827
+ if (!operations && !direct && !saved) {
815
828
  res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
816
829
  return
817
830
  }
818
- const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
819
- const title = operations?.title ?? saved!.title
820
- const mdPath = operations?.meetingPath ?? sidecarPath.replace(/\.g2-chunks\.json$/, '.md')
831
+ const sidecarPath = operations?.sidecarPath ?? direct?.sidecarPath ?? saved!.sidecarPath
832
+ const title = operations?.title ?? direct?.title ?? saved!.title
833
+ const mdPath = operations?.meetingPath ?? direct?.meetingPath ?? sidecarPath.replace(/\.g2-chunks\.json$/, '.md')
821
834
 
822
835
  let sidecar: Record<string, unknown>
823
836
  try {
@@ -907,7 +920,7 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
907
920
  // Which business this is. The sibling /speakers route has always carried
908
921
  // this and this one dropped it, so a personal 1:1 about someone's
909
922
  // compensation was byte-identical to a marketing sync.
910
- domain: operations?.domain ?? saved?.domain ?? '',
923
+ domain: operations?.domain ?? direct?.domain ?? saved?.domain ?? '',
911
924
  // Transcript actually captured, whether or not a write-up exists yet. 140
912
925
  // of 399 real sidecars have no .md, and the fallback text claimed there was
913
926
  // no transcript while holding one.
@@ -941,6 +954,11 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
941
954
  // no write-up yet.
942
955
  capturedChars: clip.capturedChars,
943
956
  domain: clip.domain,
957
+ source: operations ? 'cos_operations' : direct ? 'direct_library' : 'standalone_recordings',
958
+ recordId: operations
959
+ ? `ops:${operations.domain}:${operations.month}:${operations.filename}`
960
+ : direct?.recordId ?? `standalone:${sessionId}`,
961
+ mutable: direct == null,
944
962
  // So the panel can warn above the write-up, not just the clipboard.
945
963
  removedNames: clip.removed,
946
964
  coverage,
@@ -988,11 +1006,32 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
988
1006
  const operations = cosOperationsMeetingsConfigured()
989
1007
  ? findCosOperationsMeetingBySessionId(sessionId)
990
1008
  : null
1009
+ const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
1010
+ if (direct) {
1011
+ if (!requestedRecordMatches(req.body?.recordId, direct.recordId || '')) {
1012
+ res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
1013
+ return
1014
+ }
1015
+ res.status(409).json({
1016
+ error: 'This meeting comes from a read-only library',
1017
+ reason: 'direct_library_read_only',
1018
+ recordId: direct.recordId,
1019
+ mutable: false,
1020
+ })
1021
+ return
1022
+ }
991
1023
  const saved = operations ? null : store.findBySessionId(sessionId)
992
1024
  if (!operations && !saved) {
993
1025
  res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
994
1026
  return
995
1027
  }
1028
+ const selectedRecordId = operations
1029
+ ? `ops:${operations.domain}:${operations.month}:${operations.filename}`
1030
+ : `standalone:${sessionId}`
1031
+ if (!requestedRecordMatches(req.body?.recordId, selectedRecordId)) {
1032
+ res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
1033
+ return
1034
+ }
996
1035
  const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
997
1036
  const meetingPath = operations?.meetingPath ?? saved!.filepath
998
1037
  const title = operations?.title ?? saved!.title
@@ -1162,11 +1201,32 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
1162
1201
  const operations = cosOperationsMeetingsConfigured()
1163
1202
  ? findCosOperationsMeetingBySessionId(sessionId)
1164
1203
  : null
1204
+ const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
1205
+ if (direct) {
1206
+ if (!requestedRecordMatches(req.body?.recordId, direct.recordId || '')) {
1207
+ res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
1208
+ return
1209
+ }
1210
+ res.status(409).json({
1211
+ error: 'This meeting comes from a read-only library',
1212
+ reason: 'direct_library_read_only',
1213
+ recordId: direct.recordId,
1214
+ mutable: false,
1215
+ })
1216
+ return
1217
+ }
1165
1218
  const saved = operations ? null : store.findBySessionId(sessionId)
1166
1219
  if (!operations && !saved) {
1167
1220
  res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
1168
1221
  return
1169
1222
  }
1223
+ const selectedRecordId = operations
1224
+ ? `ops:${operations.domain}:${operations.month}:${operations.filename}`
1225
+ : `standalone:${sessionId}`
1226
+ if (!requestedRecordMatches(req.body?.recordId, selectedRecordId)) {
1227
+ res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
1228
+ return
1229
+ }
1170
1230
  const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
1171
1231
 
1172
1232
  // Refuse to confirm a label the meeting does not actually carry. Otherwise
@@ -1240,11 +1300,32 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
1240
1300
  const operations = cosOperationsMeetingsConfigured()
1241
1301
  ? findCosOperationsMeetingBySessionId(sessionId)
1242
1302
  : null
1303
+ const direct = operations ? null : findDirectLibraryMeetingBySessionId(sessionId)
1304
+ if (direct) {
1305
+ if (!requestedRecordMatches(req.body?.recordId, direct.recordId || '')) {
1306
+ res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
1307
+ return
1308
+ }
1309
+ res.status(409).json({
1310
+ error: 'This meeting comes from a read-only library',
1311
+ reason: 'direct_library_read_only',
1312
+ recordId: direct.recordId,
1313
+ mutable: false,
1314
+ })
1315
+ return
1316
+ }
1243
1317
  const saved = operations ? null : store.findBySessionId(sessionId)
1244
1318
  if (!operations && !saved) {
1245
1319
  res.status(404).json({ error: 'No saved meeting for this session', reason: 'meeting_not_found' })
1246
1320
  return
1247
1321
  }
1322
+ const selectedRecordId = operations
1323
+ ? `ops:${operations.domain}:${operations.month}:${operations.filename}`
1324
+ : `standalone:${sessionId}`
1325
+ if (!requestedRecordMatches(req.body?.recordId, selectedRecordId)) {
1326
+ res.status(409).json({ error: 'Meeting source changed; reopen the meeting', reason: 'record_source_mismatch' })
1327
+ return
1328
+ }
1248
1329
  const sidecarPath = operations?.sidecarPath ?? saved!.sidecarPath
1249
1330
  const meetingPath = operations?.meetingPath ?? saved!.filepath
1250
1331
  const title = operations?.title ?? saved!.title
@@ -1423,7 +1504,7 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
1423
1504
  // voice. Retention is 7 days (see meeting-audio-archive), so this answers with
1424
1505
  // 404 + a reason once the window has passed rather than pretending the audio
1425
1506
  // was never there.
1426
- router.get('/meeting/:sessionId/audio/:chunkIndex', (req, res) => {
1507
+ router.get('/meeting/:sessionId/audio/:chunkIndex', async (req, res) => {
1427
1508
  res.set('Cache-Control', 'private, no-store')
1428
1509
  const sessionId = String(req.params.sessionId ?? '')
1429
1510
  if (!/^[A-Za-z0-9:_-]{3,96}$/.test(sessionId)) {
@@ -1440,8 +1521,8 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
1440
1521
  // fallback there is nothing to play on any meeting predating 6.21.18 — while
1441
1522
  // 72 hours of unidentified-voice audio is sitting right there, and an
1442
1523
  // unidentified voice is exactly what a reviewer needs to hear.
1443
- const path = meetingAudioChunkPath(sessionId, chunkIndex)
1444
- ?? extAudioChunkPath(sessionId, chunkIndex)
1524
+ const archivedPath = meetingAudioChunkPath(sessionId, chunkIndex)
1525
+ const path = archivedPath ?? extAudioChunkPath(sessionId, chunkIndex)
1445
1526
  if (!path) {
1446
1527
  const retained = [...new Set([
1447
1528
  ...listMeetingAudioChunks(sessionId),
@@ -1460,7 +1541,30 @@ export function createMeetingRouter(deps: MeetingRouteDependencies = {}): Router
1460
1541
  })
1461
1542
  return
1462
1543
  }
1463
- sendAudioFile(res, path)
1544
+ // Canary scope is intentionally narrow: only the week-retained immutable
1545
+ // archive gets a derived playback copy. Legacy ext-audio remains byte-for-
1546
+ // byte raw. `?raw=1` is the authenticated A/B and emergency per-request
1547
+ // escape hatch; the COS Control toggle is the machine-wide rollback.
1548
+ const liveRecording = getTranscriptionSessionLiveness().live > 0
1549
+ const playback = archivedPath && req.query.raw !== '1' && !liveRecording
1550
+ ? await adaptivePlaybackAudio(archivedPath, {
1551
+ // Admission is not enough: if a meeting starts while FFmpeg is
1552
+ // working, the replay job is preempted and this request hears raw.
1553
+ shouldAbort: () => getTranscriptionSessionLiveness().live > 0,
1554
+ })
1555
+ : { path, mode: 'raw' as const, profile: null }
1556
+ res.set('X-COS-Audio-Playback', playback.mode)
1557
+ const bypassReason = 'reason' in playback ? playback.reason : undefined
1558
+ const bypass = liveRecording
1559
+ ? 'live_recording'
1560
+ : bypassReason === 'live_recording'
1561
+ ? 'live_recording'
1562
+ : bypassReason === 'cleanup_busy'
1563
+ ? 'cleanup_busy'
1564
+ : null
1565
+ if (bypass) res.set('X-COS-Audio-Bypass', bypass)
1566
+ if (playback.profile) res.set('X-COS-Audio-Profile', playback.profile)
1567
+ sendAudioFile(res, playback.path)
1464
1568
  })
1465
1569
 
1466
1570
  /**
@@ -4,10 +4,44 @@ import { Router } from 'express'
4
4
  import { getMeetingStore, MeetingStore, MeetingStoreError } from '../lib/meeting-store.js'
5
5
  import {
6
6
  cosOperationsMeetingsConfigured,
7
+ getDirectLibraryMeetingDetail,
7
8
  getCosOperationsMeetingDetail,
9
+ listDirectLibraryMeetings,
8
10
  listCosOperationsMeetings,
9
- resolveCosOperationsDir,
11
+ resolveMeetingLibrary,
10
12
  } from '../lib/cos-operations-meetings.js'
13
+ import type { MeetingMeta } from '../lib/meeting-store.js'
14
+
15
+ function withStandaloneIdentity(meeting: MeetingMeta): MeetingMeta {
16
+ return {
17
+ ...meeting,
18
+ librarySource: 'standalone_recordings',
19
+ recordId: `standalone:${meeting.sessionId || `${meeting.domain}:${meeting.month}:${meeting.filename}`}`,
20
+ mutable: true,
21
+ }
22
+ }
23
+
24
+ function mergeMeetingSources(groups: MeetingMeta[][], limit: number): MeetingMeta[] {
25
+ const seenSessions = new Set<string>()
26
+ const seenExact = new Set<string>()
27
+ const merged: MeetingMeta[] = []
28
+ for (const group of groups) {
29
+ for (const meeting of group) {
30
+ if (meeting.sessionId) {
31
+ if (seenSessions.has(meeting.sessionId)) continue
32
+ seenSessions.add(meeting.sessionId)
33
+ } else {
34
+ const exact = `${meeting.librarySource || ''}:${meeting.domain}:${meeting.month}:${meeting.filename}`
35
+ if (seenExact.has(exact)) continue
36
+ seenExact.add(exact)
37
+ }
38
+ merged.push(meeting)
39
+ }
40
+ }
41
+ merged.sort((a, b) => `${b.date}T${b.time || '00:00'}`.localeCompare(`${a.date}T${a.time || '00:00'}`)
42
+ || b.filename.localeCompare(a.filename))
43
+ return merged.slice(0, Math.min(Math.max(limit, 1), 50))
44
+ }
11
45
 
12
46
  export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): Router {
13
47
  const router = Router()
@@ -20,17 +54,54 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
20
54
  const domain = typeof req.query.domain === 'string' ? req.query.domain : 'all'
21
55
  res.set('Cache-Control', 'private, no-store')
22
56
 
23
- if (cosOperationsMeetingsConfigured()) {
57
+ const library = resolveMeetingLibrary()
58
+ if (library.layout === 'invalid_explicit_root') {
59
+ res.status(409).json({
60
+ error: 'Configured meetings library is unavailable or malformed',
61
+ reason: 'invalid_explicit_root',
62
+ layout: library.layout,
63
+ warnings: library.warnings,
64
+ })
65
+ return
66
+ }
67
+
68
+ if (library.layout === 'direct') {
69
+ const operations = cosOperationsMeetingsConfigured()
70
+ ? listCosOperationsMeetings({ limit: 50, domain })
71
+ : []
72
+ const direct = domain === 'all' || domain === 'library'
73
+ ? listDirectLibraryMeetings({ limit: 50 })
74
+ : []
75
+ const standalone = store.list({ limit: 50, domain }).map(withStandaloneIdentity)
76
+ const meetings = mergeMeetingSources([operations, direct, standalone], limit)
77
+ res.json({
78
+ meetings,
79
+ source: operations.length > 0 ? 'mixed_library' : 'direct_library',
80
+ layout: 'direct',
81
+ root: library.root,
82
+ rootFingerprint: library.rootFingerprint,
83
+ meetingCount: meetings.length,
84
+ warnings: library.warnings,
85
+ })
86
+ return
87
+ }
88
+
89
+ if (library.layout === 'multi_domain') {
24
90
  const meetings = listCosOperationsMeetings({ limit, domain })
25
91
  res.json({
26
92
  meetings,
27
93
  source: 'cos_operations',
28
- operationsDir: resolveCosOperationsDir(),
94
+ layout: 'multi_domain',
95
+ root: library.root,
96
+ rootFingerprint: library.rootFingerprint,
97
+ meetingCount: meetings.length,
98
+ warnings: library.warnings,
29
99
  })
30
100
  return
31
101
  }
32
102
 
33
- res.json({ meetings: store.list({ limit, domain }), source: 'standalone_recordings' })
103
+ const meetings = store.list({ limit, domain }).map(withStandaloneIdentity)
104
+ res.json({ meetings, source: 'standalone_recordings', layout: 'standalone', meetingCount: meetings.length })
34
105
  } catch (error) {
35
106
  sendMeetingStoreError(res, error)
36
107
  }
@@ -49,6 +120,14 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
49
120
  }
50
121
  res.set('Cache-Control', 'private, no-store')
51
122
 
123
+ if (domain === 'library') {
124
+ const detail = getDirectLibraryMeetingDetail(month, filename)
125
+ if (detail) {
126
+ res.json(detail)
127
+ return
128
+ }
129
+ }
130
+
52
131
  if (cosOperationsMeetingsConfigured()) {
53
132
  const detail = getCosOperationsMeetingDetail(domain, month, filename)
54
133
  if (detail) {
@@ -71,6 +150,14 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
71
150
  try {
72
151
  res.set('Cache-Control', 'private, no-store')
73
152
 
153
+ if (req.params.domain === 'library') {
154
+ const detail = getDirectLibraryMeetingDetail(req.params.month, req.params.filename)
155
+ if (detail) {
156
+ res.json(detail)
157
+ return
158
+ }
159
+ }
160
+
74
161
  if (cosOperationsMeetingsConfigured()) {
75
162
  const detail = getCosOperationsMeetingDetail(
76
163
  req.params.domain,