@gotcos/glasses-server 6.44.9 → 6.44.10
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 +13 -0
- package/package.json +1 -1
- package/server/lib/cos-context-browser.ts +32 -0
- package/server/lib/python-bridge.ts +2 -0
- package/server/routes/memory.ts +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 6.44.10
|
|
2
|
+
|
|
3
|
+
Ingest progress for COS Control.
|
|
4
|
+
|
|
5
|
+
- `GET /api/context/graph/ingest/progress` (the bridge's `graph-ingest-progress`,
|
|
6
|
+
read-only): where the current or last Control-started run stands. The run's
|
|
7
|
+
total, how many are indexed and failed, the document in flight with its
|
|
8
|
+
estimated calls, the last eight outcomes with their seconds, the budget line,
|
|
9
|
+
the queue's pending count, and the log's last lines. A run started somewhere
|
|
10
|
+
else (a Claude session) holds the lock but writes no log here, so it reports
|
|
11
|
+
as `external` with the counts only. COS Control 0.5.195's Sync card polls it
|
|
12
|
+
every five seconds while the ingest lock is held.
|
|
13
|
+
|
|
1
14
|
## 6.44.9
|
|
2
15
|
|
|
3
16
|
Knowledge from zero: the setup path behind COS Control's Knowledge tab.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotcos/glasses-server",
|
|
3
|
-
"version": "6.44.
|
|
3
|
+
"version": "6.44.10",
|
|
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": {
|
|
@@ -923,6 +923,38 @@ export function normalizeGraphAnswer(value: unknown): { question: string; mode:
|
|
|
923
923
|
return { question: stringOrAbsent(s.question, KNOWLEDGE_ASK_MAX_CHARS) ?? '', mode: stringOrAbsent(s.mode, 16) ?? 'hybrid', answer, elapsed_s: typeof s.elapsed_s === 'number' && Number.isFinite(s.elapsed_s) ? s.elapsed_s : null }
|
|
924
924
|
}
|
|
925
925
|
|
|
926
|
+
export interface IngestProgressItem { id: string; outcome: 'indexed' | 'failed' | 'unknown'; seconds: number | null; reason: string | null }
|
|
927
|
+
|
|
928
|
+
/** `graph-ingest-progress`: where the current or last Control-started run stands. */
|
|
929
|
+
export function normalizeIngestProgress(value: unknown): Record<string, unknown> {
|
|
930
|
+
const s = asRecord(value) ?? {}
|
|
931
|
+
const current = asRecord(s.current)
|
|
932
|
+
const budget = asRecord(s.budget)
|
|
933
|
+
const items: IngestProgressItem[] = (Array.isArray(s.items) ? s.items : []).flatMap((row) => {
|
|
934
|
+
const r = asRecord(row); const id = r ? stringOrAbsent(r.id, 200) : undefined
|
|
935
|
+
if (!id) return []
|
|
936
|
+
const outcome: IngestProgressItem['outcome'] = r!.outcome === 'indexed' ? 'indexed' : r!.outcome === 'failed' ? 'failed' : 'unknown'
|
|
937
|
+
return [{ id, outcome, seconds: typeof r!.seconds === 'number' && Number.isFinite(r!.seconds) ? r!.seconds : null, reason: stringOrAbsent(r!.reason, 200) ?? null }]
|
|
938
|
+
}).slice(-8)
|
|
939
|
+
return {
|
|
940
|
+
running: s.running === true,
|
|
941
|
+
pid: integerOrAbsent(s.pid) ?? null,
|
|
942
|
+
external: s.external === true,
|
|
943
|
+
pending: integerOrAbsent(s.pending) ?? null,
|
|
944
|
+
total: integerOrAbsent(s.total) ?? null,
|
|
945
|
+
done: integerOrAbsent(s.done) ?? 0,
|
|
946
|
+
failed: integerOrAbsent(s.failed) ?? 0,
|
|
947
|
+
current: current && stringOrAbsent(current.id, 200) ? { id: stringOrAbsent(current.id, 200)!, est_calls: integerOrAbsent(current.est_calls) ?? null } : null,
|
|
948
|
+
items,
|
|
949
|
+
remaining_calls: integerOrAbsent(s.remaining_calls) ?? null,
|
|
950
|
+
budget: budget && integerOrAbsent(budget.used) !== undefined && integerOrAbsent(budget.cap) !== undefined ? { used: integerOrAbsent(budget.used)!, cap: integerOrAbsent(budget.cap)! } : null,
|
|
951
|
+
ended: s.ended === true,
|
|
952
|
+
log_tail: (Array.isArray(s.log_tail) ? s.log_tail : []).filter((l): l is string => typeof l === 'string').map(l => l.slice(0, 300)).slice(-24),
|
|
953
|
+
log_age_s: typeof s.log_age_s === 'number' && Number.isFinite(s.log_age_s) ? s.log_age_s : null,
|
|
954
|
+
lock: normalizeIngestKickoff({ lock: s.lock }).lock,
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
926
958
|
export function normalizeContextBrowserStatus(value: unknown): ContextBrowserStatus {
|
|
927
959
|
const source = value && typeof value === 'object' && !Array.isArray(value)
|
|
928
960
|
? value as Record<string, unknown> : {}
|
|
@@ -64,6 +64,7 @@ export const LEARNING_COMMANDS = [
|
|
|
64
64
|
'graph-ingest-sample',
|
|
65
65
|
'graph-ask',
|
|
66
66
|
'graph-schedule',
|
|
67
|
+
'graph-ingest-progress',
|
|
67
68
|
] as const
|
|
68
69
|
|
|
69
70
|
// The optional Python bridge is available only when the user points us at a real
|
|
@@ -244,6 +245,7 @@ function standaloneNoop(args: string[]): unknown {
|
|
|
244
245
|
case 'graph-ingest-sample':
|
|
245
246
|
case 'graph-ask':
|
|
246
247
|
case 'graph-schedule':
|
|
248
|
+
case 'graph-ingest-progress':
|
|
247
249
|
return { error: 'cos_pipeline_not_configured' }
|
|
248
250
|
case 'task-rows':
|
|
249
251
|
case 'task-capture':
|
package/server/routes/memory.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { normalizeReviewDecision, LEARNING_REVIEW_LIMIT,
|
|
|
29
29
|
normalizeKnowledgeSources,
|
|
30
30
|
normalizeSampleKickoff,
|
|
31
31
|
normalizeGraphAnswer,
|
|
32
|
+
normalizeIngestProgress,
|
|
32
33
|
KNOWLEDGE_SETUP_SAMPLE_MAX,
|
|
33
34
|
KNOWLEDGE_ASK_MAX_CHARS,
|
|
34
35
|
normalizeLearningEventDetail,
|
|
@@ -345,6 +346,21 @@ memoryRouter.post('/context/graph/ingest', async (req, res) => {
|
|
|
345
346
|
}
|
|
346
347
|
})
|
|
347
348
|
|
|
349
|
+
/** Where the current or last Control-started ingest stands (6.44.10). Read-only. */
|
|
350
|
+
memoryRouter.get('/context/graph/ingest/progress', async (_req, res) => {
|
|
351
|
+
noStore(res)
|
|
352
|
+
if (!contextConfigured()) { res.status(503).json({ error: pythonBridgeState() }); return }
|
|
353
|
+
try {
|
|
354
|
+
const data = await callPython(['graph-ingest-progress'], 10_000)
|
|
355
|
+
const code = bridgeErrorCode(data)
|
|
356
|
+
if (code) { res.status(503).json({ error: code }); return }
|
|
357
|
+
res.json(normalizeIngestProgress(data))
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.warn('[context] progress bridge failure:', (error as Error).message)
|
|
360
|
+
res.status(503).json({ error: 'graph_unavailable' })
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
|
|
348
364
|
// ── Knowledge setup (6.44.9): from zero to a first index, in COS Control ──
|
|
349
365
|
//
|
|
350
366
|
// Six bridge commands behind one guided path: the readiness checklist, the
|