@gotcos/glasses-server 6.95.0 → 6.98.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/README.md +4 -0
  3. package/managed-runtime-contract.json +13 -0
  4. package/package.json +3 -1
  5. package/server/index.ts +12 -0
  6. package/server/lib/attached-provider-adapter.ts +35 -1
  7. package/server/lib/claude-bridge.ts +47 -0
  8. package/server/lib/codex-bridge.ts +11 -1
  9. package/server/lib/codex-rollout-watermark.ts +42 -0
  10. package/server/lib/cursor-bridge.ts +2 -1
  11. package/server/lib/decision-provider.ts +4 -2
  12. package/server/lib/dictation-clean-models.ts +2 -0
  13. package/server/lib/dictation-clean.ts +2 -2
  14. package/server/lib/dictation-polish-pipeline.ts +22 -2
  15. package/server/lib/dictation-prewarm.ts +16 -0
  16. package/server/lib/direct-bridge.ts +14 -2
  17. package/server/lib/direct-sse.ts +6 -1
  18. package/server/lib/display-bus.ts +1 -1
  19. package/server/lib/fork-thread.ts +32 -1
  20. package/server/lib/held-naming-batches.ts +44 -7
  21. package/server/lib/held-voice-groups.ts +5 -4
  22. package/server/lib/job-audit-caller.ts +20 -0
  23. package/server/lib/keyed-sessions.ts +21 -1
  24. package/server/lib/lens-gist-engines.ts +17 -5
  25. package/server/lib/live-cos-ask.ts +29 -6
  26. package/server/lib/live-cue-verdicts.ts +43 -8
  27. package/server/lib/live-cues-engine.ts +4 -1
  28. package/server/lib/live-cues-filter.ts +50 -0
  29. package/server/lib/live-cues-general-prompts.ts +137 -0
  30. package/server/lib/live-cues-general-topic.ts +245 -0
  31. package/server/lib/live-cues-general.ts +715 -0
  32. package/server/lib/live-cues-v2-data.ts +453 -0
  33. package/server/lib/live-cues-v2-grounded.ts +262 -0
  34. package/server/lib/live-cues-v2-judge-b.ts +59 -0
  35. package/server/lib/live-cues-v2-names.ts +49 -0
  36. package/server/lib/live-cues-v2-notes.ts +8 -4
  37. package/server/lib/live-cues-v2-open-items.ts +132 -0
  38. package/server/lib/live-cues-v2-pacing.ts +45 -0
  39. package/server/lib/live-cues-v2-prompts-kinds.ts +9 -4
  40. package/server/lib/live-cues-v2-settings.ts +89 -10
  41. package/server/lib/live-cues-v2.ts +570 -47
  42. package/server/lib/mac-recording-asr.ts +6 -3
  43. package/server/lib/mac-speaker-call-human.ts +371 -0
  44. package/server/lib/mac-speaker-call-interval-human.ts +499 -0
  45. package/server/lib/mac-speaker-call-interval-projection.ts +233 -0
  46. package/server/lib/mac-speaker-call-original-view.ts +328 -0
  47. package/server/lib/mac-speaker-call-producer-worker.ts +615 -0
  48. package/server/lib/mac-speaker-call-producer.ts +539 -0
  49. package/server/lib/mac-speaker-call-store.ts +264 -0
  50. package/server/lib/mac-speaker-call-view.ts +373 -0
  51. package/server/lib/mac-speaker-live-compute.ts +72 -0
  52. package/server/lib/mac-speaker-live-memory.ts +414 -0
  53. package/server/lib/mac-speaker-live-projection.ts +126 -0
  54. package/server/lib/mac-speaker-live-save.ts +90 -0
  55. package/server/lib/mac-speaker-live-store.ts +71 -0
  56. package/server/lib/mac-speaker-live-worker.ts +58 -0
  57. package/server/lib/mac-speaker-live.ts +175 -0
  58. package/server/lib/mac-speaker-profile-extractor.ts +15 -10
  59. package/server/lib/mac-speaker-shadow-repair.ts +307 -0
  60. package/server/lib/mac-voice-assignment.ts +46 -27
  61. package/server/lib/mac-voice-display.ts +195 -0
  62. package/server/lib/mac-voice-naming-batch.ts +1067 -0
  63. package/server/lib/mac-voice-suggestions.ts +548 -0
  64. package/server/lib/maintenance-lifecycle.ts +2 -0
  65. package/server/lib/media-store.ts +2 -1
  66. package/server/lib/meeting-ask-pasted.ts +135 -0
  67. package/server/lib/meeting-ask-review.ts +8 -1
  68. package/server/lib/meeting-ask-runner.ts +4 -1
  69. package/server/lib/meeting-ask-runtime.ts +189 -17
  70. package/server/lib/meeting-ask-store.ts +2 -0
  71. package/server/lib/meeting-ask-web.ts +284 -13
  72. package/server/lib/meeting-batch-progress.ts +51 -5
  73. package/server/lib/meeting-batch-transcribe.ts +35 -2
  74. package/server/lib/meeting-corrections.ts +2 -1
  75. package/server/lib/meeting-fact-check.ts +3 -0
  76. package/server/lib/meeting-finalization-jobs.ts +69 -2
  77. package/server/lib/meeting-public-topic.ts +6 -1
  78. package/server/lib/meeting-relabel-enrolment.ts +2 -1
  79. package/server/lib/meeting-relabel.ts +17 -10
  80. package/server/lib/meeting-speaker-review.ts +187 -21
  81. package/server/lib/meeting-store.ts +6 -1
  82. package/server/lib/parakeet.ts +51 -5
  83. package/server/lib/pending-batch-retention.ts +58 -0
  84. package/server/lib/private-reference.ts +38 -0
  85. package/server/lib/provider-usage.ts +561 -0
  86. package/server/lib/public-https-url.ts +32 -0
  87. package/server/lib/query-job-runtime.ts +3 -0
  88. package/server/lib/rich-media-safety.ts +23 -2
  89. package/server/lib/session-find-ask.ts +288 -0
  90. package/server/lib/session-find-collect.ts +715 -0
  91. package/server/lib/session-find-redact.ts +254 -0
  92. package/server/lib/session-find.ts +345 -0
  93. package/server/lib/speaker-calibration-log.ts +3 -2
  94. package/server/lib/speaker-embeddings.ts +46 -35
  95. package/server/lib/speaker-name-nfc.ts +37 -0
  96. package/server/lib/speaker-rename-fanout.ts +7 -6
  97. package/server/lib/speaker-shadow-decide.ts +59 -22
  98. package/server/lib/speaker-shadow-pair-source.ts +91 -0
  99. package/server/lib/speaker-shadow-paired.ts +268 -0
  100. package/server/lib/speaker-shadow-report.ts +2 -2
  101. package/server/lib/speaker-shadow-worker.ts +50 -15
  102. package/server/lib/speaker-shadow.ts +80 -19
  103. package/server/lib/speaker-trainer.ts +2 -1
  104. package/server/lib/token-audit.ts +36 -6
  105. package/server/lib/transcript-cleanup.ts +3 -2
  106. package/server/lib/voice-directory.ts +10 -5
  107. package/server/lib/voice-profile-store.ts +24 -14
  108. package/server/lib/web-lookup-ledger.ts +151 -0
  109. package/server/lib/work-review-runtime.ts +2 -0
  110. package/server/lib/work-search.ts +3 -1
  111. package/server/lib/xlsx-text.ts +277 -0
  112. package/server/routes/decision-provider.ts +4 -1
  113. package/server/routes/jev.ts +5 -1
  114. package/server/routes/live-cues.ts +36 -14
  115. package/server/routes/meeting.ts +515 -61
  116. package/server/routes/session-find.ts +73 -0
  117. package/server/routes/transcribe-stream.ts +114 -33
  118. package/server/routes/voice.ts +28 -18
  119. package/server/scripts/live-cues-v3-mutation-gate.mjs +311 -0
  120. package/server/scripts/mac-voice-assignment-6930-mutation-gate.mjs +5 -5
  121. package/shared/live-cues-v2.ts +38 -5
  122. package/shared/media-attachment.ts +2 -0
  123. package/shared/meeting-ask.ts +11 -0
@@ -11,6 +11,12 @@
11
11
  // var for a limit wins over the saved value and locks it. A file holding only `enabled` is still written as schema 1, so
12
12
  // a 6.79.1 server reads it exactly as before; any limit makes it schema 2, which 6.79.1 also reads (it looks only at
13
13
  // `enabled`).
14
+ //
15
+ // Live cues v3: the same file also holds the cue mode (focused | general | all), grounded cues (classic | grounded) and the
16
+ // six general-cue limits. A file holding any of them is schema 3; an older server that rewrites the file drops them, which
17
+ // reads as focused and classic, the safe values. A saved value that is malformed or outside its enum or bounds reads as
18
+ // unset. Env can only force the SAFE value (COS_LIVE_CUES_MODE=focused, COS_LIVE_CUES_GROUNDING=classic); any other value
19
+ // defers to the setting, so a managed env can never switch general or grounded cues on behind COS Control's back.
14
20
 
15
21
  import { existsSync, mkdirSync, readFileSync } from 'node:fs'
16
22
  import { dirname } from 'node:path'
@@ -30,25 +36,63 @@ export type LiveCuesV2LimitName = keyof typeof LIVE_CUES_V2_LIMIT_BOUNDS
30
36
  export const LIVE_CUES_V2_LIMIT_NAMES = Object.keys(LIVE_CUES_V2_LIMIT_BOUNDS) as LiveCuesV2LimitName[]
31
37
  export type LiveCuesV2StoredLimits = Partial<Record<LiveCuesV2LimitName, number>>
32
38
 
39
+ /** Live cues v3 (cue mode). `focused` is today's cues; `general` adds nothing to focused cues but runs only insight cues; `all` runs both. */
40
+ export const LIVE_CUES_CUE_MODES = ['focused', 'general', 'all'] as const
41
+ export type LiveCuesCueMode = typeof LIVE_CUES_CUE_MODES[number]
42
+ export const LIVE_CUES_GROUNDINGS = ['classic', 'grounded'] as const
43
+ export type LiveCuesGrounding = typeof LIVE_CUES_GROUNDINGS[number]
44
+ export const isCueMode = (v: unknown): v is LiveCuesCueMode => typeof v === 'string' && (LIVE_CUES_CUE_MODES as readonly string[]).includes(v)
45
+ export const isGrounding = (v: unknown): v is LiveCuesGrounding => typeof v === 'string' && (LIVE_CUES_GROUNDINGS as readonly string[]).includes(v)
46
+
47
+ /** The six general-cue limits. `field` is the name on the wire (POST body and settings file); the key is the name in the
48
+ * `general.limits` view. Bounds are inclusive; integer fields take whole numbers only. */
49
+ export const LIVE_CUES_GENERAL_LIMIT_BOUNDS = {
50
+ intervalSeconds: { field: 'generalIntervalSeconds', min: 120, max: 1800, default: 300, integer: true, env: 'COS_LIVE_CUES_GENERAL_INTERVAL_S' },
51
+ trigger: { field: 'generalTrigger', min: 0.4, max: 0.9, default: 0.6, integer: false, env: 'COS_LIVE_CUES_GENERAL_TRIGGER' },
52
+ attemptsPerMeeting: { field: 'generalAttemptsPerMeeting', min: 1, max: 30, default: 8, integer: true, env: 'COS_LIVE_CUES_GENERAL_ATTEMPTS' },
53
+ maxCuesPerMeeting: { field: 'generalMaxCuesPerMeeting', min: 1, max: 12, default: 4, integer: true, env: 'COS_LIVE_CUES_GENERAL_MAX_PER_MEETING' },
54
+ dailyTopicCalls: { field: 'generalDailyTopicCalls', min: 1, max: 200, default: 60, integer: true, env: 'COS_LIVE_CUES_GENERAL_DAILY_TOPIC_CALLS' },
55
+ dailyWebSearches: { field: 'generalDailyWebSearches', min: 1, max: 100, default: 30, integer: true, env: 'COS_LIVE_CUES_GENERAL_DAILY_WEB_SEARCHES' },
56
+ } as const
57
+ export type LiveCuesGeneralLimitName = keyof typeof LIVE_CUES_GENERAL_LIMIT_BOUNDS
58
+ export const LIVE_CUES_GENERAL_LIMIT_NAMES = Object.keys(LIVE_CUES_GENERAL_LIMIT_BOUNDS) as LiveCuesGeneralLimitName[]
59
+ export type LiveCuesGeneralStoredLimits = Partial<Record<LiveCuesGeneralLimitName, number>>
60
+ export function generalLimitInBounds(name: LiveCuesGeneralLimitName, value: unknown): value is number {
61
+ return withinBounds(LIVE_CUES_GENERAL_LIMIT_BOUNDS[name], value)
62
+ }
63
+
33
64
  /** Inside the bounds, and whole where the field is whole. */
34
- export function limitInBounds(name: LiveCuesV2LimitName, value: unknown): value is number {
35
- const b = LIVE_CUES_V2_LIMIT_BOUNDS[name]
65
+ function withinBounds(b: { min: number; max: number; integer: boolean }, value: unknown): value is number {
36
66
  return typeof value === 'number' && Number.isFinite(value) && value >= b.min && value <= b.max && (!b.integer || Number.isInteger(value))
37
67
  }
68
+ export function limitInBounds(name: LiveCuesV2LimitName, value: unknown): value is number {
69
+ return withinBounds(LIVE_CUES_V2_LIMIT_BOUNDS[name], value)
70
+ }
38
71
 
39
72
  /** What the engine and the routes need from the store: the saved choice, or null when there is none, and (6.79.2) the
40
73
  * saved limits. `limits` is optional so a reader without it means "no saved limits". */
41
- export interface LiveCuesV2SettingReader { stored(): boolean | null; limits?(): LiveCuesV2StoredLimits }
74
+ export interface LiveCuesV2SettingReader {
75
+ stored(): boolean | null
76
+ limits?(): LiveCuesV2StoredLimits
77
+ /** Live cues v3, all optional: a reader without them means focused, classic and the default general limits. */
78
+ cueMode?(): LiveCuesCueMode | null
79
+ grounding?(): LiveCuesGrounding | null
80
+ generalLimits?(): LiveCuesGeneralStoredLimits
81
+ }
82
+
83
+ interface Loaded { value: boolean | null; limits: LiveCuesV2StoredLimits; cueMode: LiveCuesCueMode | null; grounding: LiveCuesGrounding | null; general: LiveCuesGeneralStoredLimits }
42
84
 
43
85
  export class LiveCuesV2Settings implements LiveCuesV2SettingReader {
44
86
  /** Loaded once; only this server writes the file, and every write goes through update(). */
45
- private cache: { value: boolean | null; limits: LiveCuesV2StoredLimits } | null = null
87
+ private cache: Loaded | null = null
46
88
  constructor(readonly file: string) {}
47
89
 
48
- private load(): { value: boolean | null; limits: LiveCuesV2StoredLimits } {
90
+ private load(): Loaded {
49
91
  if (this.cache) return this.cache
50
92
  let value: boolean | null = null
51
93
  const limits: LiveCuesV2StoredLimits = {}
94
+ let cueMode: LiveCuesCueMode | null = null, grounding: LiveCuesGrounding | null = null
95
+ const general: LiveCuesGeneralStoredLimits = {}
52
96
  try {
53
97
  if (existsSync(this.file)) {
54
98
  const raw = JSON.parse(readFileSync(this.file, 'utf8')) as unknown
@@ -56,29 +100,46 @@ export class LiveCuesV2Settings implements LiveCuesV2SettingReader {
56
100
  const r = raw as Record<string, unknown>
57
101
  if (typeof r.enabled === 'boolean') value = r.enabled
58
102
  for (const name of LIVE_CUES_V2_LIMIT_NAMES) if (limitInBounds(name, r[name])) limits[name] = r[name] as number
103
+ if (isCueMode(r.cueMode)) cueMode = r.cueMode
104
+ if (isGrounding(r.grounding)) grounding = r.grounding
105
+ for (const name of LIVE_CUES_GENERAL_LIMIT_NAMES) {
106
+ const field = LIVE_CUES_GENERAL_LIMIT_BOUNDS[name].field
107
+ if (generalLimitInBounds(name, r[field])) general[name] = r[field] as number
108
+ }
59
109
  }
60
110
  }
61
111
  } catch { value = null }
62
- this.cache = { value, limits }
112
+ this.cache = { value, limits, cueMode, grounding, general }
63
113
  return this.cache
64
114
  }
65
115
 
66
116
  stored(): boolean | null { return this.load().value }
67
117
  limits(): LiveCuesV2StoredLimits { return { ...this.load().limits } }
118
+ cueMode(): LiveCuesCueMode | null { return this.load().cueMode }
119
+ grounding(): LiveCuesGrounding | null { return this.load().grounding }
120
+ generalLimits(): LiveCuesGeneralStoredLimits { return { ...this.load().general } }
68
121
 
69
122
  /** Atomic and durable. The in-memory value changes only after the file is written, so a failed write changes nothing. */
70
123
  save(enabled: boolean, now: Date = new Date()): void { this.update({ enabled }, now) }
71
124
 
72
- /** One write for any mix of the switch and the limits; unnamed fields keep their saved values. */
73
- update(patch: { enabled?: boolean; limits?: LiveCuesV2StoredLimits }, now: Date = new Date()): void {
125
+ /** One write for any mix of the switch, the limits and (v3) the cue mode, grounding and general limits; unnamed fields
126
+ * keep their saved values, the v3 ones included. */
127
+ update(patch: { enabled?: boolean; limits?: LiveCuesV2StoredLimits; cueMode?: LiveCuesCueMode; grounding?: LiveCuesGrounding; general?: LiveCuesGeneralStoredLimits },
128
+ now: Date = new Date()): void {
74
129
  const current = this.load()
75
130
  const value = patch.enabled ?? current.value
76
131
  const limits = { ...current.limits, ...patch.limits }
132
+ const cueMode = patch.cueMode ?? current.cueMode
133
+ const grounding = patch.grounding ?? current.grounding
134
+ const general = { ...current.general, ...patch.general }
77
135
  const hasLimits = Object.keys(limits).length > 0
78
- const body = { schema: hasLimits ? 2 : 1, ...(value === null ? {} : { enabled: value }), ...limits, updatedAt: now.toISOString() }
136
+ const hasV3 = cueMode !== null || grounding !== null || Object.keys(general).length > 0
137
+ const generalFields = Object.fromEntries(LIVE_CUES_GENERAL_LIMIT_NAMES.filter(n => general[n] !== undefined).map(n => [LIVE_CUES_GENERAL_LIMIT_BOUNDS[n].field, general[n]]))
138
+ const body = { schema: hasV3 ? 3 : hasLimits ? 2 : 1, ...(value === null ? {} : { enabled: value }), ...limits,
139
+ ...(cueMode === null ? {} : { cueMode }), ...(grounding === null ? {} : { grounding }), ...generalFields, updatedAt: now.toISOString() }
79
140
  mkdirSync(dirname(this.file), { recursive: true, mode: 0o700 })
80
141
  durableAtomicWriteFileSync(this.file, JSON.stringify(body) + '\n', { mode: 0o600 })
81
- this.cache = { value, limits }
142
+ this.cache = { value, limits, cueMode, grounding, general }
82
143
  }
83
144
  }
84
145
 
@@ -94,6 +155,24 @@ export function resolveLiveCuesV2(env: NodeJS.ProcessEnv, settings: LiveCuesV2Se
94
155
  return { enabled: false, source: 'default' }
95
156
  }
96
157
 
158
+ /** Where a v3 mode came from. `env` only ever means the safe value was forced, so it is locked. */
159
+ export interface LiveCuesV2Mode<T extends string> { value: T; source: LiveCuesV2SettingSource; locked: boolean }
160
+ /** COS_LIVE_CUES_MODE=focused forces focused; any other env value, a missing or malformed saved value, or a reader that
161
+ * throws, reads through to the saved mode, then to focused. */
162
+ export function resolveLiveCuesCueMode(env: NodeJS.ProcessEnv, settings: LiveCuesV2SettingReader | null): LiveCuesV2Mode<LiveCuesCueMode> {
163
+ if (env.COS_LIVE_CUES_MODE === 'focused') return { value: 'focused', source: 'env', locked: true }
164
+ let stored: LiveCuesCueMode | null = null
165
+ try { const v = settings?.cueMode?.(); stored = isCueMode(v) ? v : null } catch { stored = null }
166
+ return stored !== null ? { value: stored, source: 'setting', locked: false } : { value: 'focused', source: 'default', locked: false }
167
+ }
168
+ /** COS_LIVE_CUES_GROUNDING=classic forces classic, the same way. */
169
+ export function resolveLiveCuesGrounding(env: NodeJS.ProcessEnv, settings: LiveCuesV2SettingReader | null): LiveCuesV2Mode<LiveCuesGrounding> {
170
+ if (env.COS_LIVE_CUES_GROUNDING === 'classic') return { value: 'classic', source: 'env', locked: true }
171
+ let stored: LiveCuesGrounding | null = null
172
+ try { const v = settings?.grounding?.(); stored = isGrounding(v) ? v : null } catch { stored = null }
173
+ return stored !== null ? { value: stored, source: 'setting', locked: false } : { value: 'classic', source: 'default', locked: false }
174
+ }
175
+
97
176
  let singleton: LiveCuesV2Settings | null = null
98
177
  export function liveCuesV2Settings(): LiveCuesV2Settings {
99
178
  if (!singleton) singleton = new LiveCuesV2Settings(dataPath('live-cues-v2-settings.json'))