@gotcos/glasses-server 6.44.6 → 6.44.7
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 +10 -0
- package/package.json +1 -1
- package/server/lib/cos-context-browser.ts +17 -0
- package/server/lib/python-bridge.ts +2 -0
- package/server/routes/memory.ts +21 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 6.44.7
|
|
2
|
+
|
|
3
|
+
One learning write: a review decision.
|
|
4
|
+
|
|
5
|
+
- `POST /api/context/learning/:id/review` with `{ "decision": "dismissed" | "reopened" }`
|
|
6
|
+
appends a review-ledger row through the bridge's `learning-decide`. A
|
|
7
|
+
dismissed proposal leaves the To review set on the next read; reopened puts
|
|
8
|
+
it back. Nothing else is written: no skill, no memory, no graph. COS Control
|
|
9
|
+
0.5.191's Memories tab uses it for Dismiss and Restore proposal.
|
|
10
|
+
|
|
1
11
|
## 6.44.6
|
|
2
12
|
|
|
3
13
|
What /qa found in 6.44.5 before anyone installed it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotcos/glasses-server",
|
|
3
|
-
"version": "6.44.
|
|
3
|
+
"version": "6.44.7",
|
|
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": {
|
|
@@ -776,6 +776,23 @@ export function normalizeGraphPassages(value: unknown): Record<string, unknown>
|
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
+
/** The review decision the bridge wrote back: lesson, decision, stamp, id, who. */
|
|
780
|
+
export function normalizeReviewDecision(value: unknown): Record<string, unknown> | null {
|
|
781
|
+
const source = asRecord(value)
|
|
782
|
+
const row = asRecord(source?.decision)
|
|
783
|
+
if (!row) return null
|
|
784
|
+
const decision = stringOrAbsent(row.decision, 16)
|
|
785
|
+
const lessonId = stringOrAbsent(row.lesson_id, 200)
|
|
786
|
+
if (!lessonId || (decision !== 'dismissed' && decision !== 'reopened')) return null
|
|
787
|
+
return {
|
|
788
|
+
lesson_id: lessonId,
|
|
789
|
+
decision,
|
|
790
|
+
ts: isoOrAbsent(row.ts) ?? null,
|
|
791
|
+
event_id: LEARNING_EVENT_ID_PATTERN.test(String(row.event_id ?? '')) ? String(row.event_id) : null,
|
|
792
|
+
by: stringOrAbsent(row.by, 32) ?? null,
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
779
796
|
export function normalizeIndexBuildKickoff(value: unknown): { started: boolean; already_running: boolean; pid: number | null; receipt: Record<string, unknown> | null } {
|
|
780
797
|
const source = asRecord(value) ?? {}
|
|
781
798
|
return {
|
|
@@ -56,6 +56,7 @@ export const LEARNING_COMMANDS = [
|
|
|
56
56
|
'graph-passages',
|
|
57
57
|
'graph-index-build',
|
|
58
58
|
'learning-to-review',
|
|
59
|
+
'learning-decide',
|
|
59
60
|
] as const
|
|
60
61
|
|
|
61
62
|
// The optional Python bridge is available only when the user points us at a real
|
|
@@ -228,6 +229,7 @@ function standaloneNoop(args: string[]): unknown {
|
|
|
228
229
|
case 'graph-passages':
|
|
229
230
|
case 'graph-index-build':
|
|
230
231
|
case 'learning-to-review':
|
|
232
|
+
case 'learning-decide':
|
|
231
233
|
return { error: 'cos_pipeline_not_configured' }
|
|
232
234
|
case 'task-rows':
|
|
233
235
|
case 'task-capture':
|
package/server/routes/memory.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { searchMemories } from '../lib/context-library-search.js'
|
|
|
13
13
|
function contextConfigured(): boolean {
|
|
14
14
|
return contextSourceAvailable() !== null
|
|
15
15
|
}
|
|
16
|
-
import { LEARNING_REVIEW_LIMIT,
|
|
16
|
+
import { normalizeReviewDecision, LEARNING_REVIEW_LIMIT,
|
|
17
17
|
GRAPH_ENTITY_ID_LIMIT,
|
|
18
18
|
LEARNING_EVENT_ID_PATTERN,
|
|
19
19
|
MEMORY_ID_PATTERN,
|
|
@@ -167,6 +167,26 @@ memoryRouter.get('/context/learning/review', async (req, res) => {
|
|
|
167
167
|
}
|
|
168
168
|
})
|
|
169
169
|
|
|
170
|
+
memoryRouter.post('/context/learning/:id/review', async (req, res) => {
|
|
171
|
+
noStore(res)
|
|
172
|
+
// The one learning write (6.44.7): a review decision on a lesson, appended to
|
|
173
|
+
// the review ledger by the bridge. Dismissed leaves To review, reopened
|
|
174
|
+
// returns; nothing else is touched. The lesson id is a store id, not an event id.
|
|
175
|
+
const lessonId = String(req.params.id)
|
|
176
|
+
const decision = typeof req.body?.decision === 'string' ? req.body.decision : ''
|
|
177
|
+
if (!lessonId || lessonId.length > 200 || CONTROL_CHARACTER.test(lessonId)) { res.status(400).json({ error: 'invalid_lesson_id' }); return }
|
|
178
|
+
if (decision !== 'dismissed' && decision !== 'reopened') { res.status(400).json({ error: 'invalid_decision' }); return }
|
|
179
|
+
if (!contextConfigured()) { res.status(503).json({ error: pythonBridgeState() }); return }
|
|
180
|
+
try {
|
|
181
|
+
const note = typeof req.body?.note === 'string' ? req.body.note.slice(0, 400) : ''
|
|
182
|
+
const answer = await callPython(['learning-decide', `--id=${lessonId}`, `--decision=${decision}`, ...(note ? [`--note=${note}`] : [])], 8_000)
|
|
183
|
+
sendBridgeAnswer(res, answer, value => normalizeReviewDecision(value))
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.warn('[context] learning decide bridge failure:', (error as Error).message)
|
|
186
|
+
res.status(503).json({ error: 'learning_unavailable' })
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
170
190
|
memoryRouter.get('/context/learning/:id', async (req, res) => {
|
|
171
191
|
noStore(res)
|
|
172
192
|
if (!LEARNING_EVENT_ID_PATTERN.test(req.params.id)) { res.status(400).json({ error: 'invalid_event_id' }); return }
|