@gotcos/glasses-server 6.50.4 → 6.52.0

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.
Files changed (41) hide show
  1. package/.env.example +16 -0
  2. package/CHANGELOG.md +78 -0
  3. package/README.md +56 -3
  4. package/bin/hooks/cos-session-hook +24 -1
  5. package/managed-runtime-contract.json +1 -0
  6. package/package.json +1 -1
  7. package/server/index.ts +83 -5
  8. package/server/lib/activity-preview.ts +45 -2
  9. package/server/lib/agent-session-search.ts +3 -1
  10. package/server/lib/agent-session-store.ts +35 -5
  11. package/server/lib/agent-session-turns.ts +4 -1
  12. package/server/lib/claude-bridge.ts +9 -0
  13. package/server/lib/client-liveness.ts +50 -0
  14. package/server/lib/codex-bridge.ts +6 -0
  15. package/server/lib/codex-live-queue-deps.ts +135 -0
  16. package/server/lib/codex-live-queue.ts +232 -0
  17. package/server/lib/codex-turn-clock.ts +36 -0
  18. package/server/lib/cursor-bridge.ts +6 -0
  19. package/server/lib/cursor-stop-followup.ts +134 -0
  20. package/server/lib/job-trail.ts +758 -0
  21. package/server/lib/occupancy-probes.ts +52 -0
  22. package/server/lib/ollama-bridge.ts +17 -0
  23. package/server/lib/permission-broker.ts +1156 -0
  24. package/server/lib/query-job-coordinator.ts +17 -1
  25. package/server/lib/query-job-feature.ts +10 -0
  26. package/server/lib/query-job-runtime.ts +9 -1
  27. package/server/lib/query-job-store.ts +214 -5
  28. package/server/lib/query-job-types.ts +36 -0
  29. package/server/lib/session-signal-store.ts +42 -10
  30. package/server/lib/session-state-derive.ts +14 -4
  31. package/server/lib/session-stream-events.ts +600 -15
  32. package/server/lib/session-stream-producer.ts +25 -6
  33. package/server/lib/session-transcript-watcher.ts +120 -3
  34. package/server/lib/thread-occupancy.ts +30 -7
  35. package/server/routes/agent-session-bindings.ts +98 -6
  36. package/server/routes/agent-sessions.ts +24 -2
  37. package/server/routes/cursor-stop-followup.ts +74 -0
  38. package/server/routes/health.ts +34 -1
  39. package/server/routes/permission-broker.ts +176 -0
  40. package/server/routes/query-jobs.ts +84 -13
  41. package/server/routes/thread-turn-queue.ts +15 -5
@@ -834,6 +834,58 @@ export function withHookTurnClock(
834
834
  }
835
835
  }
836
836
 
837
+ /**
838
+ * Add the Codex turn clock (6.51.0) to a probe set, keeping whatever the set already
839
+ * answers for other providers.
840
+ *
841
+ * `codexTurnEndedAtMs` reads the thread's rollout tail and answers a time only when the
842
+ * newest turn marker CLOSED a turn (`task_complete`/`turn_complete`); a `task_started`
843
+ * after it, an unreadable file, or a tail with no marker all answer null, which keeps
844
+ * the 30 s transcript window. Measured on codex-cli 0.155: the app starts a turn queued
845
+ * in its own queue 10 ms after the `task_complete`, and that `task_started` is what the
846
+ * next read sees.
847
+ */
848
+ export function withCodexTurnClock(
849
+ probes: OccupancyProbes,
850
+ codexTurnEndedAtMs: (threadId: string) => number | null,
851
+ ): OccupancyProbes {
852
+ const inner = probes.holderTurnEndedAtMs
853
+ return {
854
+ ...probes,
855
+ holderTurnEndedAtMs: (provider, threadId) => {
856
+ if (provider === 'codex') {
857
+ if (!NATIVE_THREAD_ID_RE.test(threadId)) return null
858
+ return codexTurnEndedAtMs(threadId)
859
+ }
860
+ return typeof inner === 'function' ? inner(provider, threadId) : null
861
+ },
862
+ }
863
+ }
864
+
865
+ /**
866
+ * Add the Cursor composer turn signal (6.51.0). `signalFor` is the hook signal store's
867
+ * reader; Cursor's hook events land there under the composer's conversation id. Open
868
+ * means: a turn was opened (prompt or tool) and no Stop has closed it, nothing ended the
869
+ * session, and the newest event is inside the open-turn ceiling -- a composer that died
870
+ * mid-turn must not read working for ever.
871
+ */
872
+ export function withCursorComposerTurn(
873
+ probes: OccupancyProbes,
874
+ signalFor: (sessionId: string) => { turnOpen: boolean; ended: { at: number; reason: string } | null; lastEventAt: number } | undefined,
875
+ now: () => number,
876
+ ceilingMs: number,
877
+ ): OccupancyProbes {
878
+ return {
879
+ ...probes,
880
+ cursorComposerTurnOpen: (threadId: string) => {
881
+ if (!NATIVE_THREAD_ID_RE.test(threadId)) return null
882
+ const signal = signalFor(threadId)
883
+ if (!signal) return null
884
+ return signal.turnOpen === true && signal.ended === null && now() - signal.lastEventAt <= ceilingMs
885
+ },
886
+ }
887
+ }
888
+
837
889
  /** The facts the hook turn clock reads off a session signal. */
838
890
  export interface HookTurnSignal {
839
891
  lastEvent: string
@@ -37,6 +37,7 @@ import {
37
37
  ollamaToolStatusLabel,
38
38
  parseToolArguments,
39
39
  } from './ollama-tools.js'
40
+ import { ollamaTrailOutcome, ollamaTrailStep, type JobTrailDraft } from './job-trail.js'
40
41
 
41
42
  const INACTIVITY_MS = 60_000
42
43
  const WALL_MAX_MS = 180_000
@@ -347,6 +348,14 @@ export async function callOllamaStreaming(
347
348
  let postCount = 0
348
349
  let toolsRetried = false
349
350
 
351
+ // 6.52.0, the Messages trail. This bridge owns its loop, so it reports each round's words
352
+ // and each tool call itself (there is no stdout to tee). A no-op without `onTrail`, and
353
+ // it never throws into the loop.
354
+ const trail = (draft: JobTrailDraft) => {
355
+ if (!callbacks.onTrail) return
356
+ try { callbacks.onTrail(draft) } catch { /* observation only */ }
357
+ }
358
+
350
359
  /** One POST + its stream. Returns what the round produced. */
351
360
  const runChatPost = async (sendTools: boolean): Promise<
352
361
  | { kind: 'text' }
@@ -446,8 +455,14 @@ export async function callOllamaStreaming(
446
455
 
447
456
  try {
448
457
  for (;;) {
458
+ const roundStart = fullText.length
449
459
  const round = await runChatPost(toolDefs.length > 0)
450
460
  if (round.kind === 'handled') return sid
461
+ // One prose entry per round: what the model wrote before its calls, or its answer.
462
+ if (callbacks.onTrail && (round.kind === 'text' || round.kind === 'tools')) {
463
+ const roundText = fullText.slice(roundStart)
464
+ if (roundText.trim()) trail({ kind: 'prose', text: roundText })
465
+ }
451
466
  if (round.kind === 'text') { await finalizeDone(); return sid }
452
467
 
453
468
  if (round.kind === 'empty') {
@@ -472,6 +487,7 @@ export async function callOllamaStreaming(
472
487
  if (finalized || abort.signal.aborted) break
473
488
  const name = call.function.name
474
489
  callbacks.onToolStatus?.(name)
490
+ if (callbacks.onTrail) trail(ollamaTrailStep(name, parseToolArguments(call.function.arguments)))
475
491
  toolHeartbeat = setInterval(bumpInactivity, TOOL_HEARTBEAT_MS)
476
492
  let result: string
477
493
  try {
@@ -483,6 +499,7 @@ export async function callOllamaStreaming(
483
499
  await finalizeError('ollama-bridge: tool call aborted.')
484
500
  return sid
485
501
  }
502
+ if (callbacks.onTrail) trail(ollamaTrailOutcome(result))
486
503
  messages.push({
487
504
  role: 'tool',
488
505
  content: result,