@gotcos/glasses-server 6.27.1 → 6.27.2

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,3 +1,18 @@
1
+ ## 6.27.2
2
+
3
+ ### One finalizer for phone and Mac dictation
4
+
5
+ - Added authenticated `POST /api/dictation/finalize` for already-transcribed
6
+ Moonshine text. It reuses the exact glossary, negative rules, Haiku/Sonnet
7
+ polish, circuit breaker, token audit, and daily cap already used by recovered
8
+ server prompt drafts.
9
+ - The route accepts bounded text only. Phone-local audio and rolling preview
10
+ audio do not leave the iPhone, and a finalizer failure falls back to the
11
+ deterministic glossary result instead of losing the transcription.
12
+ - Both Message commits and sealed Meeting preview phrases can use the same
13
+ final quality pass while canonical Large-v3 meeting transcription remains
14
+ unchanged.
15
+
1
16
  ## 6.27.1
2
17
 
3
18
  ### The 16-frame video from 6.27.0 now actually works end to end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.27.1",
3
+ "version": "6.27.2",
4
4
  "description": "COS Glasses \u2014 self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, or Cursor Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -124,6 +124,34 @@ async function cleanOutboundDictation(text: string, opts: AutoCleanRequest & { s
124
124
  }
125
125
  }
126
126
 
127
+ /** Finalize already-transcribed phone text through the same glossary + optional
128
+ * AI cleanup used by server-owned prompt drafts. This route accepts text only:
129
+ * Moonshine audio and rolling preview audio never leave the phone. */
130
+ promptDraftsRouter.post('/dictation/finalize', async (req, res) => {
131
+ const text = typeof req.body?.text === 'string' ? req.body.text.trim() : ''
132
+ const surface = req.body?.surface
133
+ const model = req.body?.autocleanModel
134
+ if (!text) return res.status(400).json({ error: 'text_required' })
135
+ if (text.length > AUTOCLEAN_MAX_CHARS) {
136
+ return res.status(413).json({ error: 'text_too_large', maxChars: AUTOCLEAN_MAX_CHARS })
137
+ }
138
+ if (surface !== 'message' && surface !== 'meeting') {
139
+ return res.status(400).json({ error: 'invalid_surface' })
140
+ }
141
+ if (model !== undefined && model !== 'haiku' && model !== 'sonnet') {
142
+ return res.status(400).json({ error: 'invalid_autoclean_model' })
143
+ }
144
+
145
+ const abort = new AbortController()
146
+ res.on('close', () => { if (!res.writableEnded) abort.abort() })
147
+ const finalText = await cleanOutboundDictation(text, {
148
+ enabled: true,
149
+ model,
150
+ signal: abort.signal,
151
+ })
152
+ res.json({ text: finalText, surface, polished: finalText !== text })
153
+ })
154
+
127
155
  async function readRawBody(req: AsyncIterable<Buffer | Uint8Array | string>): Promise<Buffer> {
128
156
  const chunks: Buffer[] = []
129
157
  let total = 0