@gotcos/glasses-server 6.46.0 → 6.46.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 +8 -0
- package/package.json +1 -1
- package/server/lib/g2-ops-handoff.ts +15 -1
- package/server/routes/meetings.ts +50 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 6.46.1
|
|
2
|
+
|
|
3
|
+
G2 recordings show in a multi-folder Meetings library that has no COS pipeline to file them. No Control or glasses update is needed.
|
|
4
|
+
|
|
5
|
+
- A Meetings library on a multi-folder operations tree (COS_OPERATIONS_DIR), on a Mac without a working COS pipeline (no COS_SCRIPTS_DIR, or no venv Python or sync_meetings.py in it), listed only that tree. G2 recordings saved there never reach it, so Control's Meetings window showed an empty month while the recordings sat in the server's own recordings store. GET /api/meetings now lists them beside the operations rows, with their months and day counts.
|
|
6
|
+
- A recording that also reached the operations tree is listed once, as the operations copy matched by the session id in its sidecar, and its day is counted once.
|
|
7
|
+
- Where the COS pipeline can run, the list is unchanged: sync_meetings.py decides which recordings join the operations tree. Search and meeting detail already read the recordings store in every layout.
|
|
8
|
+
|
|
1
9
|
## 6.46.0
|
|
2
10
|
|
|
3
11
|
Name held voice samples and preview the meeting labels they will change. Paired Control build: 0.5.223.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotcos/glasses-server",
|
|
3
|
-
"version": "6.46.
|
|
3
|
+
"version": "6.46.1",
|
|
4
4
|
"description": "COS Glasses \u2014 self-hosted AI heads-up-display server for Even G2 smart glasses, powered by Claude Code, Codex, Cursor Agent CLI, or local Ollama",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// 3. Run sync_meetings.py --g2-only --g2-file with the private-app retry helper.
|
|
12
12
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync } from 'node:fs'
|
|
14
|
-
import { basename, dirname, join } from 'node:path'
|
|
14
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
15
15
|
import { durableAtomicWriteFileSync } from './atomic-fs.js'
|
|
16
16
|
import { resolveCosOperationsDir } from './cos-operations-meetings.js'
|
|
17
17
|
import { runG2EnrichmentWithRetry } from './g2-enrichment-runner.js'
|
|
@@ -222,6 +222,20 @@ export function stageRecordingIntoOperations(
|
|
|
222
222
|
return destPath
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Whether a G2 recording saved now can reach operations/ at all. The save path skips the pipeline when
|
|
227
|
+
* COS_SCRIPTS_DIR is unset, and runOperationsSync throws when the venv Python or sync_meetings.py is missing, so in
|
|
228
|
+
* each of those cases the recording stays only in the server's own store. GET /api/meetings reads this to decide
|
|
229
|
+
* whether a multi-folder library must list that store itself. Reads env live, like cosOpsPipelineConfigured in
|
|
230
|
+
* routes/meeting.ts.
|
|
231
|
+
*/
|
|
232
|
+
export function g2RecordingsReachOperations(): boolean {
|
|
233
|
+
const raw = process.env.COS_SCRIPTS_DIR?.trim()
|
|
234
|
+
if (!raw) return false
|
|
235
|
+
const scriptsDir = resolve(raw)
|
|
236
|
+
return existsSync(resolve(scriptsDir, 'venv/bin/python3')) && existsSync(join(scriptsDir, 'sync_meetings.py'))
|
|
237
|
+
}
|
|
238
|
+
|
|
225
239
|
async function runOperationsSync(localMeetingPath: string, claimOnly: boolean): Promise<void> {
|
|
226
240
|
if (!COS_SCRIPTS_DIR) {
|
|
227
241
|
console.log('[meeting/save] Standalone mode — skipping G2 sync pipeline')
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import type { MeetingMeta } from '../lib/meeting-store.js'
|
|
18
18
|
import { meetingListLimit } from '../lib/meeting-store.js'
|
|
19
19
|
import { searchMeetingLibrary } from '../lib/meeting-library-search.js'
|
|
20
|
+
import { g2RecordingsReachOperations } from '../lib/g2-ops-handoff.js'
|
|
20
21
|
|
|
21
22
|
const MONTH_QUERY = /^\d{4}-(0[1-9]|1[0-2])$/
|
|
22
23
|
const DAY_QUERY = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/
|
|
@@ -87,6 +88,10 @@ function mergeDayCounts(
|
|
|
87
88
|
.map(([date, count]) => ({ date, count }))
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
function dayCountsOf(rows: MeetingMeta[]): Array<{ date: string; count: number }> {
|
|
92
|
+
return mergeDayCounts([rows.map(row => ({ date: row.date, count: 1 }))])
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
function uniqueSortedMonths(groups: string[][]): string[] {
|
|
91
96
|
return [...new Set(groups.flat())].sort().reverse()
|
|
92
97
|
}
|
|
@@ -161,17 +166,53 @@ export function createMeetingsRouter(store: MeetingStore = getMeetingStore()): R
|
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
if (library.layout === 'multi_domain') {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
if (g2RecordingsReachOperations()) {
|
|
170
|
+
const meetings = listCosOperationsMeetings({
|
|
171
|
+
limit,
|
|
172
|
+
domain,
|
|
173
|
+
month: filters.month,
|
|
174
|
+
day: filters.day,
|
|
175
|
+
})
|
|
176
|
+
res.json({
|
|
177
|
+
meetings,
|
|
178
|
+
months: listCosOperationsMeetingMonths(domain),
|
|
179
|
+
days: filters.month ? listCosOperationsMeetingDays(filters.month, domain) : [],
|
|
180
|
+
source: 'cos_operations',
|
|
181
|
+
layout: 'multi_domain',
|
|
182
|
+
root: library.root,
|
|
183
|
+
rootFingerprint: library.rootFingerprint,
|
|
184
|
+
meetingCount: meetings.length,
|
|
185
|
+
warnings: library.warnings,
|
|
186
|
+
})
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// No COS pipeline can file this Mac's G2 recordings into the operations folder (a Starter Kit library
|
|
191
|
+
// without COS_SCRIPTS_DIR, 2026-09-14), so they live only in this server's store. List them beside the
|
|
192
|
+
// operations rows. Operations rows go first, so a copy that did reach operations wins by sessionId, and
|
|
193
|
+
// the day counts skip the store row it covers. Day counts always describe the whole month.
|
|
194
|
+
const operations = listCosOperationsMeetings(listOptions)
|
|
195
|
+
const standalone = store.list(listOptions).map(withStandaloneIdentity)
|
|
196
|
+
const meetings = mergeMeetingSources([operations, standalone], limit, sourceLimit)
|
|
197
|
+
let days: Array<{ date: string; count: number }> = []
|
|
198
|
+
if (filters.month) {
|
|
199
|
+
const monthOperations = filters.day
|
|
200
|
+
? listCosOperationsMeetings({ limit: sourceLimit, domain, month: filters.month })
|
|
201
|
+
: operations
|
|
202
|
+
const monthStore = filters.day
|
|
203
|
+
? store.list({ limit: sourceLimit, domain, month: filters.month })
|
|
204
|
+
: standalone
|
|
205
|
+
const covered = new Set(monthOperations.flatMap(row => (row.sessionId ? [row.sessionId] : [])))
|
|
206
|
+
days = mergeDayCounts([
|
|
207
|
+
listCosOperationsMeetingDays(filters.month, domain),
|
|
208
|
+
dayCountsOf(monthStore.filter(row => !row.sessionId || !covered.has(row.sessionId))),
|
|
209
|
+
])
|
|
210
|
+
}
|
|
170
211
|
res.json({
|
|
171
212
|
meetings,
|
|
172
|
-
months: listCosOperationsMeetingMonths(domain),
|
|
173
|
-
days
|
|
174
|
-
source: 'cos_operations',
|
|
213
|
+
months: uniqueSortedMonths([listCosOperationsMeetingMonths(domain), store.listMonths()]),
|
|
214
|
+
days,
|
|
215
|
+
source: standalone.length > 0 ? 'mixed_library' : 'cos_operations',
|
|
175
216
|
layout: 'multi_domain',
|
|
176
217
|
root: library.root,
|
|
177
218
|
rootFingerprint: library.rootFingerprint,
|