@gotcos/glasses-server 6.8.0 → 6.9.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.9.0
4
+
5
+ Live recoverable prompt transcription for COS Glasses builds 200+.
6
+
7
+ - **Words appear while speaking.** After each audio chunk is durably acknowledged,
8
+ its sanitized fast/local transcript is published on the existing authenticated,
9
+ replayable display stream as `prompt_transcript`; the phone/G2 client can fill
10
+ the Listening body without adding another recorder, polling loop, or ASR job.
11
+ - **Recovery remains authoritative.** The event is optional presentation state.
12
+ Stored WAV chunks, final HQ transcription, glossary cleanup, editing, retry,
13
+ and send behavior remain unchanged and continue even if no display client is
14
+ connected.
15
+ - **Stale retries cannot repaint.** The server rechecks the exact draft, chunk
16
+ index, and audio bytes after warm transcription. Replaced audio never emits
17
+ its obsolete words, while client-side draft scoping, ordering, and replay
18
+ deduplication handle reconnects safely.
19
+ - **Public boundary retained.** This release adds no private COS paths, personal
20
+ data, LaunchAgent controls, remote restart authority, or machine-management
21
+ endpoints.
22
+
3
23
  ## 6.8.0
4
24
 
5
25
  Public-safe meeting finalization for COS Glasses build 199.
package/README.md CHANGED
@@ -64,7 +64,9 @@ The built-in IP allowlist blocks public-internet traffic regardless.
64
64
  - Send phone photos with queued prompts, and review assistant-selected generated,
65
65
  research, or explicitly used email images in Messages and on the G2 lens
66
66
  - Recover long voice prompts after phone, network, or server interruptions. Audio
67
- chunks are saved before transcription and retained locally for 72 hours.
67
+ chunks are saved before transcription and retained locally for 72 hours. On
68
+ compatible app builds, their warm transcript also appears live while speaking;
69
+ final HQ transcription remains authoritative.
68
70
  - Live voice capture + transcription during meetings
69
71
  - Local whisper.cpp transcription (free) with OpenAI fallback (optional)
70
72
  - Tasks / calendar / people context **if** you run the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.8.0",
3
+ "version": "6.9.0",
4
4
  "description": "COS Glasses — self-hosted AI heads-up-display server for Even G2 smart glasses, powered by your local Claude Code or Codex CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@ const bus = new EventEmitter()
8
8
  bus.setMaxListeners(20) // Multiple glasses clients
9
9
 
10
10
  export interface DisplayEvent {
11
- type: 'chunk' | 'done' | 'error' | 'tool_status' | 'start' | 'session_restore' | 'transcript_chunk' | 'recording_start' | 'recording_stop' | 'coaching_nudge'
11
+ type: 'chunk' | 'done' | 'error' | 'tool_status' | 'start' | 'session_restore' | 'transcript_chunk' | 'prompt_transcript' | 'recording_start' | 'recording_stop' | 'coaching_nudge'
12
12
  data: Record<string, unknown>
13
13
  }
14
14
 
@@ -38,6 +38,7 @@ import { createBreaker } from '../lib/claude-circuit.js'
38
38
  import { logTokenAudit } from '../lib/token-audit.js'
39
39
  import { atomicWriteFileSync } from '../lib/atomic-fs.js'
40
40
  import { dataPath } from '../lib/data-dir.js'
41
+ import { emitDisplay } from '../lib/display-bus.js'
41
42
 
42
43
  export const promptDraftsRouter = Router()
43
44
 
@@ -234,7 +235,17 @@ promptDraftsRouter.post('/prompt-drafts/:draftId/chunks', async (req, res) => {
234
235
  const nextTotal = Object.values(before.chunkBytes).reduce((sum, bytes) => sum + bytes, 0) - existingBytes + audio.length
235
236
  if (nextTotal > MAX_DRAFT_BYTES) return res.status(413).json({ error: 'prompt draft too large' })
236
237
  const meta = await savePromptDraftChunk(req.params.draftId, chunkIndex, audio)
237
- warmTail = warmTail.then(() => transcribeChunk(req.params.draftId, chunkIndex, audio, 'fast', 'warm').then(() => undefined)).catch(err => {
238
+ warmTail = warmTail.then(async () => {
239
+ const text = await transcribeChunk(req.params.draftId, chunkIndex, audio, 'fast', 'warm')
240
+ // The durability ACK above remains immediate. Publish the optional warm
241
+ // transcript only after rechecking that this exact audio still owns the
242
+ // chunk index; a retry/replacement must never paint stale words.
243
+ if (!isCurrentChunk(req.params.draftId, chunkIndex, audio)) return
244
+ emitDisplay({
245
+ type: 'prompt_transcript',
246
+ data: { draftId: req.params.draftId, chunkIndex, text },
247
+ })
248
+ }).catch(err => {
238
249
  console.warn(`[prompt-draft] warm transcription failed ${req.params.draftId}/${chunkIndex}: ${err.message}`)
239
250
  })
240
251
  res.json({ draftId: meta.draftId, chunkIndex, acked: true, receivedChunkIndexes: meta.receivedChunkIndexes, chunkBytes: meta.chunkBytes[String(chunkIndex)] ?? audio.length, transcriptPending: true, expiresAt: meta.expiresAt })