@gotcos/glasses-server 6.71.5 → 6.71.11
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/.env.example +4 -0
- package/CHANGELOG.md +36 -0
- package/README.md +31 -14
- package/bin/hooks/cos-session-hook +15 -3
- package/docs/test-plan-6.71.10.md +41 -0
- package/docs/test-plan-6.71.11.md +12 -0
- package/docs/test-plan-6.71.5.md +11 -0
- package/docs/test-plan-6.71.7.md +56 -0
- package/docs/test-plan-6.71.8.md +123 -0
- package/docs/test-plan-6.71.9.md +61 -0
- package/package.json +8 -2
- package/server/index.ts +5 -1
- package/server/lib/claude-hooks-installer.ts +1 -0
- package/server/lib/live-cos-ask.ts +36 -25
- package/server/lib/meeting-ask-confidence.ts +2 -2
- package/server/lib/meeting-ask-evidence.ts +76 -0
- package/server/lib/meeting-ask-follow-up.ts +61 -0
- package/server/lib/meeting-ask-followup.ts +27 -0
- package/server/lib/meeting-ask-grounding.ts +57 -0
- package/server/lib/meeting-ask-links.ts +34 -0
- package/server/lib/meeting-ask-runtime.ts +61 -11
- package/server/lib/meeting-ask-web.ts +22 -1
- package/server/lib/meeting-image-ocr.ts +57 -0
- package/server/lib/native/meeting-image-ocr.js +23 -0
- package/server/lib/permission-broker.ts +76 -19
- package/server/lib/work-intake-store.ts +21 -0
- package/server/routes/meeting-ask.ts +2 -2
- package/server/routes/memory.ts +27 -2
- package/server/routes/permission-broker.ts +6 -2
- package/server/routes/work-intake.ts +11 -1
- package/shared/meeting-ask.ts +3 -0
package/server/routes/memory.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express'
|
|
2
2
|
import { callPython, contextSourceAvailable, pythonBridgeState } from '../lib/python-bridge.js'
|
|
3
|
+
import { resolveContextFilesRoot, readFileMemoryById } from '../lib/context-files.js'
|
|
3
4
|
import { searchMemories } from '../lib/context-library-search.js'
|
|
4
5
|
import { getOwnerName, setProfileOwnerName } from '../lib/profile.js'
|
|
5
6
|
import { resetDecoderCaches } from '../lib/whisper-local.js'
|
|
@@ -857,18 +858,29 @@ memoryRouter.get('/memory/overview', async (_req, res) => {
|
|
|
857
858
|
}
|
|
858
859
|
})
|
|
859
860
|
|
|
860
|
-
|
|
861
|
+
// A distinct route fails closed on older servers; they cannot silently run meaning search.
|
|
862
|
+
memoryRouter.get(['/memory/search', '/memory/search/keyword'], async (req, res) => {
|
|
861
863
|
const query = typeof req.query.q === 'string' ? req.query.q.trim() : ''
|
|
862
|
-
if (query.length < 2) {
|
|
864
|
+
if (query.length < 2 || query.length > 300) {
|
|
863
865
|
res.status(400).json({ error: 'q must be at least 2 characters', reason: 'invalid_query' })
|
|
864
866
|
return
|
|
865
867
|
}
|
|
868
|
+
const mode = req.path.endsWith('/keyword') ? 'keyword' : req.query.mode
|
|
869
|
+
if (mode !== undefined && mode !== 'keyword' && mode !== 'meaning') {
|
|
870
|
+
res.status(400).json({ error: 'invalid_search_mode', reason: 'invalid_search_mode' })
|
|
871
|
+
return
|
|
872
|
+
}
|
|
873
|
+
if (mode === 'keyword' && !contextConfigured()) {
|
|
874
|
+
res.status(503).json({ error: 'memory_search_unavailable', reason: 'memory_source_unavailable' })
|
|
875
|
+
return
|
|
876
|
+
}
|
|
866
877
|
const rawLimit = typeof req.query.limit === 'string' ? Number.parseInt(req.query.limit, 10) : 20
|
|
867
878
|
res.set('Cache-Control', 'private, no-store')
|
|
868
879
|
try {
|
|
869
880
|
const result = await searchMemories({
|
|
870
881
|
query,
|
|
871
882
|
limit: Number.isFinite(rawLimit) ? rawLimit : 20,
|
|
883
|
+
...(mode === 'keyword' ? { semantic: false } : {}),
|
|
872
884
|
})
|
|
873
885
|
res.json(result)
|
|
874
886
|
} catch {
|
|
@@ -876,6 +888,19 @@ memoryRouter.get('/memory/search', async (req, res) => {
|
|
|
876
888
|
}
|
|
877
889
|
})
|
|
878
890
|
|
|
891
|
+
// Detail stays in the same file tier as keyword results, even if a Python bridge is configured.
|
|
892
|
+
memoryRouter.get('/memory/search/keyword/:id', (req, res) => {
|
|
893
|
+
res.set('Cache-Control', 'private, no-store')
|
|
894
|
+
if (!MEMORY_ID_PATTERN.test(req.params.id)) return res.status(400).json({ error: 'invalid_memory_id' })
|
|
895
|
+
const root = resolveContextFilesRoot()
|
|
896
|
+
if (!root) return res.status(503).json({ error: 'memory_source_unavailable' })
|
|
897
|
+
try {
|
|
898
|
+
const memory = readFileMemoryById(root, req.params.id)
|
|
899
|
+
if (!memory) return res.status(404).json({ error: 'memory_not_found' })
|
|
900
|
+
return res.json(memory)
|
|
901
|
+
} catch { return res.status(503).json({ error: 'memory_unavailable' }) }
|
|
902
|
+
})
|
|
903
|
+
|
|
879
904
|
memoryRouter.get('/memory/:id', async (req, res) => {
|
|
880
905
|
if (!MEMORY_ID_PATTERN.test(req.params.id)) {
|
|
881
906
|
res.status(400).json({ error: 'invalid_memory_id' })
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// 6.71.7: new-hook stamped interactive questions also arrive while the desk is
|
|
2
|
+
// active. The broker keeps their lens card hidden until the desk is idle and
|
|
3
|
+
// then latches it. Native desk answers/cancels settle the same request. Legacy
|
|
4
|
+
// hooks and approvals keep the gates below. GET polling is 3 s while pending.
|
|
5
|
+
//
|
|
1
6
|
// The permission broker's two doors (6.52.0). See `lib/permission-broker.ts` for the why.
|
|
2
7
|
//
|
|
3
8
|
// POST /api/permission-requests/ask -- the COS session hook, on a PermissionRequest at a
|
|
@@ -35,7 +40,6 @@ import { timingSafeTokenEqual } from '../lib/token-auth.js'
|
|
|
35
40
|
import {
|
|
36
41
|
ANSWERING_CLIENTS,
|
|
37
42
|
CLIENT_LIVE_WINDOW_MS,
|
|
38
|
-
QUESTIONS_POLL_INTERVAL_MS,
|
|
39
43
|
SESSION_QUESTIONS_PROTOCOL_VERSION,
|
|
40
44
|
type BrokerAdmission,
|
|
41
45
|
type HookReplyChannel,
|
|
@@ -182,7 +186,7 @@ export function createSessionQuestionsRouter(deps: SessionQuestionsRouterDeps):
|
|
|
182
186
|
enabled: health.enabled,
|
|
183
187
|
mode: health.mode,
|
|
184
188
|
protocolVersion: SESSION_QUESTIONS_PROTOCOL_VERSION,
|
|
185
|
-
pollIntervalMs:
|
|
189
|
+
pollIntervalMs: deps.broker.questionsPollIntervalMs(),
|
|
186
190
|
liveWindowMs: CLIENT_LIVE_WINDOW_MS,
|
|
187
191
|
pending: deps.broker.pendingCount(),
|
|
188
192
|
stats: deps.broker.stats(),
|
|
@@ -5,6 +5,8 @@ import { captureWorkItem, linkTaskMeeting, listBoard, workIntakeCapabilities, Ta
|
|
|
5
5
|
import { resolveSavedMeetingDetail } from './meetings.js'
|
|
6
6
|
import type { MeetingDescriptor } from '../lib/work-review-store.js'
|
|
7
7
|
import type { MeetingDetail } from '../lib/meeting-store.js'
|
|
8
|
+
import { saveAskFollowUp } from '../lib/meeting-ask-follow-up.js'
|
|
9
|
+
import { meetingAskRuntime } from '../lib/meeting-ask-runtime.js'
|
|
8
10
|
|
|
9
11
|
export interface WorkIntakeDependencies {
|
|
10
12
|
store: WorkIntakeStore | null
|
|
@@ -13,6 +15,8 @@ export interface WorkIntakeDependencies {
|
|
|
13
15
|
link: typeof linkTaskMeeting
|
|
14
16
|
capture: typeof captureWorkItem
|
|
15
17
|
resolveMeeting: (descriptor: MeetingDescriptor) => MeetingDetail | Promise<MeetingDetail>
|
|
18
|
+
askGet: typeof meetingAskRuntime.get
|
|
19
|
+
askIdentity: typeof meetingAskRuntime.identity
|
|
16
20
|
}
|
|
17
21
|
const MAX_MEETING_REFS = 8 // task_work_metadata.MAX_REFS
|
|
18
22
|
export const LINK_RACE_RETRIES = 2
|
|
@@ -32,7 +36,8 @@ export function intakeCardText(item: Pick<WorkIntakeItem, 'text' | 'owner' | 'ow
|
|
|
32
36
|
/** Auth is supplied by the parent /api mount. Nothing here calls a model or starts an agent. */
|
|
33
37
|
export function createWorkIntakeRouter(overrides: Partial<WorkIntakeDependencies> & Pick<WorkIntakeDependencies, 'store'>): Router {
|
|
34
38
|
const deps: WorkIntakeDependencies = { list: listBoard, capabilities: workIntakeCapabilities, link: linkTaskMeeting,
|
|
35
|
-
capture: captureWorkItem, resolveMeeting: resolveSavedMeetingDetail,
|
|
39
|
+
capture: captureWorkItem, resolveMeeting: resolveSavedMeetingDetail,
|
|
40
|
+
askGet: id => meetingAskRuntime.get(id), askIdentity: value => meetingAskRuntime.identity(value), ...overrides }
|
|
36
41
|
const router = Router()
|
|
37
42
|
router.use('/work-intake', (_req, res, next) => {
|
|
38
43
|
res.set('Cache-Control', 'private, no-store')
|
|
@@ -54,6 +59,11 @@ export function createWorkIntakeRouter(overrides: Partial<WorkIntakeDependencies
|
|
|
54
59
|
return res.status(503).json({ error: { code: 'work_intake_unavailable', message: 'Work intake is unavailable. Refresh before trying again.' } })
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
router.post('/work-intake/meeting-ask', async (req, res) => {
|
|
63
|
+
try { res.json({ ok: true, ...await saveAskFollowUp(req.body, { store: deps.store, identity: deps.askIdentity,
|
|
64
|
+
get: deps.askGet, resolveMeeting: deps.resolveMeeting }) }) }
|
|
65
|
+
catch (e) { fail(res, e, 'meeting-ask') }
|
|
66
|
+
})
|
|
57
67
|
router.get('/work-intake', async (_req, res) => {
|
|
58
68
|
try {
|
|
59
69
|
const items = deps.store!.open()
|
package/shared/meeting-ask.ts
CHANGED
|
@@ -181,6 +181,7 @@ export interface MeetingAskCapabilities {
|
|
|
181
181
|
/** 6.71.1: config carries cosSearch and PUT config {cosSearch} changes it. */
|
|
182
182
|
cosSearchMode?: true;
|
|
183
183
|
meetingContext?: true;
|
|
184
|
+
followUpReview?: true;
|
|
184
185
|
}
|
|
185
186
|
/**
|
|
186
187
|
* How Meeting Ask searches COS memory and earlier meetings (6.71.1). 'meaning' (default) adds the embedding
|
|
@@ -214,6 +215,8 @@ export interface MeetingAskConfig {
|
|
|
214
215
|
* PUT files {sessionId,serverInstanceId,files:MeetingAskFile[]} -> {files}. Upsert only; removed is explicit.
|
|
215
216
|
* POST files/media {sessionId,serverInstanceId,media:[{id,name?}]} -> {files} (6.71.1, capabilities.phoneFiles):
|
|
216
217
|
* media already uploaded through /api/media joins the meeting's file list as id "phone_<mediaId>", origin "phone".
|
|
218
|
+
* 6.71.9 on macOS: images containing text use local OCR and revisioned F evidence; no-word images
|
|
219
|
+
* remain unsupported and extraction errors are explicit failed rows. Historical evidence is immutable.
|
|
217
220
|
* GET export?sessionId=...&serverInstanceId=... -> {text}; composes persisted Ask/file context,
|
|
218
221
|
* client prepends its current canonical transcript. All operations use X-Cos-Token.
|
|
219
222
|
* GET config is the capability probe (404 means old server); health.capabilities.meetingAsk mirrors capabilities.
|