@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.
@@ -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
- memoryRouter.get('/memory/search', async (req, res) => {
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: QUESTIONS_POLL_INTERVAL_MS,
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, ...overrides }
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()
@@ -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.