@gotcos/glasses-server 6.95.0 → 6.98.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.
- package/CHANGELOG.md +166 -0
- package/README.md +4 -0
- package/managed-runtime-contract.json +13 -0
- package/package.json +3 -1
- package/server/index.ts +12 -0
- package/server/lib/attached-provider-adapter.ts +35 -1
- package/server/lib/claude-bridge.ts +47 -0
- package/server/lib/codex-bridge.ts +11 -1
- package/server/lib/codex-rollout-watermark.ts +42 -0
- package/server/lib/cursor-bridge.ts +2 -1
- package/server/lib/decision-provider.ts +4 -2
- package/server/lib/dictation-clean-models.ts +2 -0
- package/server/lib/dictation-clean.ts +2 -2
- package/server/lib/dictation-polish-pipeline.ts +22 -2
- package/server/lib/dictation-prewarm.ts +16 -0
- package/server/lib/direct-bridge.ts +14 -2
- package/server/lib/direct-sse.ts +6 -1
- package/server/lib/display-bus.ts +1 -1
- package/server/lib/fork-thread.ts +32 -1
- package/server/lib/held-naming-batches.ts +44 -7
- package/server/lib/held-voice-groups.ts +5 -4
- package/server/lib/job-audit-caller.ts +20 -0
- package/server/lib/keyed-sessions.ts +21 -1
- package/server/lib/lens-gist-engines.ts +17 -5
- package/server/lib/live-cos-ask.ts +29 -6
- package/server/lib/live-cue-verdicts.ts +43 -8
- package/server/lib/live-cues-engine.ts +4 -1
- package/server/lib/live-cues-filter.ts +50 -0
- package/server/lib/live-cues-general-prompts.ts +137 -0
- package/server/lib/live-cues-general-topic.ts +245 -0
- package/server/lib/live-cues-general.ts +715 -0
- package/server/lib/live-cues-v2-data.ts +763 -0
- package/server/lib/live-cues-v2-grounded.ts +262 -0
- package/server/lib/live-cues-v2-judge-b.ts +59 -0
- package/server/lib/live-cues-v2-names.ts +49 -0
- package/server/lib/live-cues-v2-notes.ts +8 -4
- package/server/lib/live-cues-v2-open-items.ts +132 -0
- package/server/lib/live-cues-v2-pacing.ts +45 -0
- package/server/lib/live-cues-v2-prompts-kinds.ts +9 -4
- package/server/lib/live-cues-v2-settings.ts +89 -10
- package/server/lib/live-cues-v2.ts +572 -47
- package/server/lib/mac-recording-asr.ts +6 -3
- package/server/lib/mac-speaker-call-human.ts +371 -0
- package/server/lib/mac-speaker-call-interval-human.ts +499 -0
- package/server/lib/mac-speaker-call-interval-projection.ts +233 -0
- package/server/lib/mac-speaker-call-original-view.ts +328 -0
- package/server/lib/mac-speaker-call-producer-worker.ts +615 -0
- package/server/lib/mac-speaker-call-producer.ts +539 -0
- package/server/lib/mac-speaker-call-store.ts +264 -0
- package/server/lib/mac-speaker-call-view.ts +373 -0
- package/server/lib/mac-speaker-live-compute.ts +72 -0
- package/server/lib/mac-speaker-live-memory.ts +414 -0
- package/server/lib/mac-speaker-live-projection.ts +126 -0
- package/server/lib/mac-speaker-live-save.ts +90 -0
- package/server/lib/mac-speaker-live-store.ts +71 -0
- package/server/lib/mac-speaker-live-worker.ts +58 -0
- package/server/lib/mac-speaker-live.ts +175 -0
- package/server/lib/mac-speaker-profile-extractor.ts +15 -10
- package/server/lib/mac-speaker-shadow-repair.ts +307 -0
- package/server/lib/mac-voice-assignment.ts +46 -27
- package/server/lib/mac-voice-display.ts +195 -0
- package/server/lib/mac-voice-naming-batch.ts +1067 -0
- package/server/lib/mac-voice-suggestions.ts +548 -0
- package/server/lib/maintenance-lifecycle.ts +2 -0
- package/server/lib/media-store.ts +2 -1
- package/server/lib/meeting-ask-pasted.ts +135 -0
- package/server/lib/meeting-ask-review.ts +8 -1
- package/server/lib/meeting-ask-runner.ts +4 -1
- package/server/lib/meeting-ask-runtime.ts +189 -17
- package/server/lib/meeting-ask-store.ts +2 -0
- package/server/lib/meeting-ask-web.ts +284 -13
- package/server/lib/meeting-batch-progress.ts +51 -5
- package/server/lib/meeting-batch-transcribe.ts +35 -2
- package/server/lib/meeting-corrections.ts +2 -1
- package/server/lib/meeting-fact-check.ts +3 -0
- package/server/lib/meeting-finalization-jobs.ts +69 -2
- package/server/lib/meeting-public-topic.ts +6 -1
- package/server/lib/meeting-relabel-enrolment.ts +2 -1
- package/server/lib/meeting-relabel.ts +17 -10
- package/server/lib/meeting-speaker-review.ts +187 -21
- package/server/lib/meeting-store.ts +6 -1
- package/server/lib/parakeet.ts +51 -5
- package/server/lib/pending-batch-retention.ts +58 -0
- package/server/lib/private-reference.ts +38 -0
- package/server/lib/provider-usage.ts +561 -0
- package/server/lib/public-https-url.ts +32 -0
- package/server/lib/query-job-runtime.ts +3 -0
- package/server/lib/rich-media-safety.ts +23 -2
- package/server/lib/session-find-ask.ts +288 -0
- package/server/lib/session-find-collect.ts +715 -0
- package/server/lib/session-find-redact.ts +254 -0
- package/server/lib/session-find.ts +345 -0
- package/server/lib/speaker-calibration-log.ts +3 -2
- package/server/lib/speaker-embeddings.ts +46 -35
- package/server/lib/speaker-name-nfc.ts +37 -0
- package/server/lib/speaker-rename-fanout.ts +7 -6
- package/server/lib/speaker-shadow-decide.ts +59 -22
- package/server/lib/speaker-shadow-pair-source.ts +91 -0
- package/server/lib/speaker-shadow-paired.ts +268 -0
- package/server/lib/speaker-shadow-report.ts +2 -2
- package/server/lib/speaker-shadow-worker.ts +50 -15
- package/server/lib/speaker-shadow.ts +80 -19
- package/server/lib/speaker-trainer.ts +2 -1
- package/server/lib/token-audit.ts +36 -6
- package/server/lib/transcript-cleanup.ts +3 -2
- package/server/lib/voice-directory.ts +10 -5
- package/server/lib/voice-profile-store.ts +24 -14
- package/server/lib/web-lookup-ledger.ts +151 -0
- package/server/lib/work-review-runtime.ts +2 -0
- package/server/lib/work-search.ts +3 -1
- package/server/lib/xlsx-text.ts +277 -0
- package/server/routes/decision-provider.ts +4 -1
- package/server/routes/jev.ts +5 -1
- package/server/routes/live-cues.ts +36 -14
- package/server/routes/meeting.ts +515 -61
- package/server/routes/session-find.ts +73 -0
- package/server/routes/transcribe-stream.ts +114 -33
- package/server/routes/voice.ts +28 -18
- package/server/scripts/live-cues-v3-mutation-gate.mjs +311 -0
- package/server/scripts/mac-voice-assignment-6930-mutation-gate.mjs +5 -5
- package/shared/live-cues-v2.ts +38 -5
- package/shared/media-attachment.ts +2 -0
- package/shared/meeting-ask.ts +11 -0
package/server/lib/parakeet.ts
CHANGED
|
@@ -454,8 +454,49 @@ export function sweepParakeetStaging(): { removed: number; restored: boolean } {
|
|
|
454
454
|
// ── Inference ─────────────────────────────────────────────────────────────────
|
|
455
455
|
|
|
456
456
|
export type ParakeetFailure = 'not_installed' | 'sandbox_unavailable' | 'busy' | 'timeout' | 'cli_failed' | 'bad_output' | 'spawn_failed' | 'aborted'
|
|
457
|
+
export interface ParakeetCliDiagnostics {
|
|
458
|
+
exitCode: number | null
|
|
459
|
+
signal: string | null
|
|
460
|
+
/** Safe categories extracted from stderr, never arbitrary native output. */
|
|
461
|
+
stderr: string
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function safeCliDiagnostics(message: string, diagnostics?: ParakeetCliDiagnostics): ParakeetCliDiagnostics {
|
|
465
|
+
const raw = (diagnostics?.stderr ?? message).slice(-32_768)
|
|
466
|
+
// Native stderr can contain paths, credentials, or recognized speech. Keep
|
|
467
|
+
// only fixed diagnostic vocabulary; redacting a few secret patterns would
|
|
468
|
+
// still leave arbitrary transcript text in the durable save error/log.
|
|
469
|
+
const categories = [
|
|
470
|
+
/unknown option/i, /not supported/i, /transcription failed/i,
|
|
471
|
+
/invalidAudioData/i, /initializationFailed/i, /modelNotFound/i,
|
|
472
|
+
/modelLoadingFailed/i, /predictionFailed/i, /invalidModel/i,
|
|
473
|
+
/failed to load model/i, /failed to initialize/i, /CoreML/i,
|
|
474
|
+
/out of memory/i, /allocation failed/i, /operation not permitted/i,
|
|
475
|
+
/permission denied/i, /no such file or directory/i, /library not loaded/i,
|
|
476
|
+
/sandbox.*deny/i, /fatal error/i, /EXC_BAD_ACCESS/, /SIGABRT/, /SIGSEGV/,
|
|
477
|
+
/NSCocoaErrorDomain/, /MLModelErrorDomain/,
|
|
478
|
+
].filter(pattern => pattern.test(raw)).map(pattern => {
|
|
479
|
+
// The pattern itself is trusted text; never copy the matching stderr.
|
|
480
|
+
return pattern.source.replace('.*', ' ').replace(/\\/g, '')
|
|
481
|
+
})
|
|
482
|
+
const legacyExit = /\bexit\s+(-?\d+|SIG[A-Z0-9]+)\b/.exec(message.slice(0, 100))?.[1]
|
|
483
|
+
const code = diagnostics?.exitCode ?? (legacyExit && /^-?\d+$/.test(legacyExit) ? Number(legacyExit) : null)
|
|
484
|
+
const signal = diagnostics?.signal ?? (legacyExit?.startsWith('SIG') ? legacyExit : null)
|
|
485
|
+
return {
|
|
486
|
+
exitCode: Number.isSafeInteger(code) ? code : null,
|
|
487
|
+
signal: typeof signal === 'string' && /^SIG[A-Z0-9]{1,12}$/.test(signal) ? signal : null,
|
|
488
|
+
stderr: categories.join(' | ').slice(0, 240) || 'unrecognized stderr omitted',
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
457
492
|
export class ParakeetError extends Error {
|
|
458
|
-
|
|
493
|
+
readonly cliDiagnostics?: ParakeetCliDiagnostics
|
|
494
|
+
constructor(readonly reason: ParakeetFailure, message: string, diagnostics?: ParakeetCliDiagnostics) {
|
|
495
|
+
const safe = reason === 'cli_failed' ? safeCliDiagnostics(message, diagnostics) : undefined
|
|
496
|
+
super(safe ? `exit code=${safe.exitCode ?? 'unknown'} signal=${safe.signal ?? 'none'}; stderr=${safe.stderr}` : message)
|
|
497
|
+
this.name = 'ParakeetError'
|
|
498
|
+
this.cliDiagnostics = safe
|
|
499
|
+
}
|
|
459
500
|
}
|
|
460
501
|
|
|
461
502
|
export interface ParakeetResult {
|
|
@@ -721,9 +762,14 @@ function runParakeetChecked(wav: Buffer, options: ParakeetRunOptions): Promise<P
|
|
|
721
762
|
if (child.pid && options.lane !== 'preview' && options.lane !== 'interactive') {
|
|
722
763
|
try { setPriority(child.pid, 10) } catch { /* runs at normal priority */ }
|
|
723
764
|
}
|
|
724
|
-
let stderr = ''
|
|
765
|
+
let stderr = '', stderrTail = ''
|
|
725
766
|
child.stdout?.on('data', () => { /* the JSON file is the output */ })
|
|
726
|
-
child.stderr?.on('data', (d: Buffer) => {
|
|
767
|
+
child.stderr?.on('data', (d: Buffer) => {
|
|
768
|
+
// Retain the existing leading-output option-failure check; keep a
|
|
769
|
+
// separate bounded tail for diagnostics from a nonzero native exit.
|
|
770
|
+
if (stderr.length < 32_768) stderr = (stderr + d.subarray(0, 32_768).toString()).slice(0, 32_768)
|
|
771
|
+
stderrTail = (stderrTail + d.subarray(-32_768).toString()).slice(-32_768)
|
|
772
|
+
})
|
|
727
773
|
timer = setTimeout(() => {
|
|
728
774
|
// SIGKILL: a CoreML compile does not stop for TERM.
|
|
729
775
|
try { child.kill('SIGKILL') } catch { /* gone */ }
|
|
@@ -733,8 +779,8 @@ function runParakeetChecked(wav: Buffer, options: ParakeetRunOptions): Promise<P
|
|
|
733
779
|
child.on('error', error => finish(new ParakeetError('spawn_failed', error.message)))
|
|
734
780
|
child.on('close', (code, signal) => {
|
|
735
781
|
if (settled) return
|
|
736
|
-
if (code !== 0) { finish(new ParakeetError('cli_failed',
|
|
737
|
-
if (/Unknown option|not supported|transcription failed/i.test(stderr)) { finish(new ParakeetError('cli_failed', '
|
|
782
|
+
if (code !== 0) { finish(new ParakeetError('cli_failed', '', { exitCode: code, signal, stderr: stderrTail })); return }
|
|
783
|
+
if (/Unknown option|not supported|transcription failed/i.test(stderr)) { finish(new ParakeetError('cli_failed', '', { exitCode: code, signal, stderr })); return }
|
|
738
784
|
try {
|
|
739
785
|
const parsed = parseParakeetJson(readFileSync(output, 'utf8'))
|
|
740
786
|
finish(null, { model: options.model, ...parsed, wallMs: Date.now() - started })
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// How long a saved meeting's pending HQ audio stays in pending-batch.
|
|
2
|
+
//
|
|
3
|
+
// A directory is stale 12 hours after its _batch_pending.marker was last refreshed, and normally
|
|
4
|
+
// goes then. A saved meeting still waiting on HQ is the exception: deleting its audio finishes the
|
|
5
|
+
// job on streaming text the next time it resumes, which made "audio is preserved for retry" untrue
|
|
6
|
+
// (2026-10-16 and 2026-10-29). It keeps its audio until a person or a fix can finish it, and for a
|
|
7
|
+
// bounded time, so one job that can never finish does not hold disk forever.
|
|
8
|
+
|
|
9
|
+
import { existsSync, readdirSync, rmSync, statSync } from 'node:fs'
|
|
10
|
+
import { basename, resolve } from 'node:path'
|
|
11
|
+
import { MeetingFinalizationJobStore, type MeetingFinalizationJob } from './meeting-finalization-jobs.js'
|
|
12
|
+
|
|
13
|
+
export const PENDING_BATCH_STALE_MS = 12 * 60 * 60 * 1000
|
|
14
|
+
export const WAITING_JOB_AUDIO_HOLD_MS = 30 * 24 * 60 * 60 * 1000
|
|
15
|
+
|
|
16
|
+
/** Directory names of meetings still waiting on HQ, inside the hold. */
|
|
17
|
+
export function pendingBatchDirsHeldForJobs(jobs: readonly MeetingFinalizationJob[], now: number): Set<string> {
|
|
18
|
+
const held = new Set<string>()
|
|
19
|
+
for (const job of jobs) {
|
|
20
|
+
if (job.phase !== 'batch_pending') continue
|
|
21
|
+
// An unreadable date cannot prove the hold ended, so the audio stays.
|
|
22
|
+
const created = Date.parse(job.createdAt)
|
|
23
|
+
if (Number.isFinite(created) && now - created > WAITING_JOB_AUDIO_HOLD_MS) continue
|
|
24
|
+
held.add(job.sessionId)
|
|
25
|
+
if (job.audioDir) held.add(basename(job.audioDir))
|
|
26
|
+
}
|
|
27
|
+
return held
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function sweepStalePendingBatch(root: string, options: {
|
|
31
|
+
now: number
|
|
32
|
+
batchBusy: boolean
|
|
33
|
+
jobs?: readonly MeetingFinalizationJob[]
|
|
34
|
+
}): void {
|
|
35
|
+
const held = pendingBatchDirsHeldForJobs(options.jobs ?? new MeetingFinalizationJobStore().list(), options.now)
|
|
36
|
+
for (const dir of readdirSync(root)) {
|
|
37
|
+
const dirPath = resolve(root, dir)
|
|
38
|
+
try {
|
|
39
|
+
const files = readdirSync(dirPath)
|
|
40
|
+
if (files.length === 0) { rmSync(dirPath, { recursive: true, force: true }); continue }
|
|
41
|
+
// Prefer the marker file mtime over first chunk mtime
|
|
42
|
+
const markerPath = resolve(dirPath, '_batch_pending.marker')
|
|
43
|
+
// Fallback for pre-v5.4.3 dirs: use directory ctime (changes on rename)
|
|
44
|
+
const ageSource = existsSync(markerPath) ? statSync(markerPath).mtimeMs : statSync(dirPath).ctimeMs
|
|
45
|
+
if (options.now - ageSource <= PENDING_BATCH_STALE_MS) continue
|
|
46
|
+
if (held.has(dir)) {
|
|
47
|
+
console.warn(`[cleanup] Retaining pending-batch for a saved meeting waiting on HQ: ${dir}`)
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
if (options.batchBusy) {
|
|
51
|
+
console.warn(`[cleanup] Retaining stale pending-batch while batch lease held: ${dir}`)
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
rmSync(dirPath, { recursive: true, force: true })
|
|
55
|
+
console.log(`[cleanup] Purged stale pending-batch: ${dir}`)
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// "Private" as a PRIVACY reference, not as an ordinary adjective.
|
|
2
|
+
//
|
|
3
|
+
// A blocklist of private nouns (notes, pricing, ...) lets every other private thing through ("private funding round from Blackstone",
|
|
4
|
+
// "my private hospital stay", "Acme's private valuation"), so this is inverted: the bare word "private" (and privately, privacy) blocks,
|
|
5
|
+
// EXCEPT inside a short list of public phrases: private aviation, jet(s), plane(s), pilot(s), equity, school(s), sector, label,
|
|
6
|
+
// market(s), company/companies, credit, and "privately held". Those are the subjects of public statistics and news.
|
|
7
|
+
// Even a public phrase blocks when the question also
|
|
8
|
+
// - has a privacy phrase anywhere: stays private, keep ... private, in private, private for now, privately told (all caught because
|
|
9
|
+
// they carry a bare "private"), or off the record, between us; or
|
|
10
|
+
// - has a possessive or a person on it: my, our, his, her, their, your, <Name>'s ("Stephen's private plane crash rate",
|
|
11
|
+
// "private equity ... our CEO owns").
|
|
12
|
+
// One helper, used by every gate that read the bare word (the Ask search gate, the public-topic and fact-check query filter, the
|
|
13
|
+
// public-resource request check). The live-cue deny list keeps the bare word (live-cues-general-topic.ts): its queries are chosen by a
|
|
14
|
+
// model, not typed by the user.
|
|
15
|
+
const PUBLIC_PHRASE = 'aviation|jets?|planes?|pilots?|equity|schools?|sector|label|markets?|compan(?:y|ies)|credit'
|
|
16
|
+
const PUBLIC_PHRASES = new RegExp(`\\bprivate\\s+(?:${PUBLIC_PHRASE})\\b|\\bprivately\\s+held\\b`, 'gi')
|
|
17
|
+
const MARKER = /\bprivate(?:ly)?\b|\bprivacy\b/i
|
|
18
|
+
// Phrases that make a question private WITHOUT the word "private" (every phrase with the word is already caught by the bare-word rule below).
|
|
19
|
+
const PRIVACY_PHRASES: readonly RegExp[] = [
|
|
20
|
+
/\boff\s+the\s+record\b/i,
|
|
21
|
+
/\bbetween\s+(?:us|you\s+and\s+me)\b/i,
|
|
22
|
+
]
|
|
23
|
+
const POSSESSIVE = /\b(?:my|our|his|her|their|your|mine|ours|hers|theirs)\b/i
|
|
24
|
+
// <Name>'s, except the contractions that are not possessives.
|
|
25
|
+
const NOT_POSSESSIVE = new Set(['what', 'it', 'that', 'there', 'here', 'who', 'how', 'let', 'he', 'she', 'where', 'when', 'why', 'one', 'this'])
|
|
26
|
+
function hasPossessive(text: string): boolean {
|
|
27
|
+
if (POSSESSIVE.test(text)) return true
|
|
28
|
+
for (const m of text.matchAll(/\b([\p{L}][\p{L}\p{N}&.-]*)['’]s\b/gu)) if (!NOT_POSSESSIVE.has(m[1].toLowerCase())) return true
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
/** True when `text` uses "private" as a privacy marker. "private aviation", "private equity", "private jet" alone are not. */
|
|
32
|
+
export function privateReference(text: string): boolean {
|
|
33
|
+
if (!MARKER.test(text)) return false
|
|
34
|
+
if (PRIVACY_PHRASES.some(p => p.test(text))) return true
|
|
35
|
+
const rest = text.replace(PUBLIC_PHRASES, ' ')
|
|
36
|
+
if (MARKER.test(rest)) return true // any other "private": notes, funding round, valuation, dinner, hospital ...
|
|
37
|
+
return hasPossessive(text) // only public phrases are left: still private if it is someone's
|
|
38
|
+
}
|