@gotcos/glasses-server 6.99.29 → 7.0.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.
Files changed (80) hide show
  1. package/.env.example +18 -0
  2. package/CHANGELOG.md +475 -0
  3. package/README.md +3 -1
  4. package/managed-runtime-contract.json +3 -0
  5. package/package.json +1 -1
  6. package/server/index.ts +29 -7
  7. package/server/lib/agent-session-output-images.ts +627 -49
  8. package/server/lib/agent-session-store.ts +24 -11
  9. package/server/lib/agent-session-tool-images.ts +224 -0
  10. package/server/lib/agent-session-turns.ts +19 -2
  11. package/server/lib/api-auth.ts +6 -0
  12. package/server/lib/ask-work-plan.ts +3 -1
  13. package/server/lib/calendar-source.ts +491 -0
  14. package/server/lib/codex-bridge.ts +4 -1
  15. package/server/lib/codex-run-ledger.ts +29 -0
  16. package/server/lib/context-builder.ts +2 -1
  17. package/server/lib/drop-p2-runtime.ts +1 -1
  18. package/server/lib/drop-store.ts +10 -4
  19. package/server/lib/learning-needs-you.ts +24 -7
  20. package/server/lib/live-events.ts +533 -0
  21. package/server/lib/local-path-redact.ts +272 -0
  22. package/server/lib/meeting-ask-orientation.ts +40 -6
  23. package/server/lib/meeting-ask-runner.ts +30 -10
  24. package/server/lib/morning-brief-runtime.ts +4 -2
  25. package/server/lib/native-mac-caller.ts +29 -0
  26. package/server/lib/ping-engine.ts +1 -1
  27. package/server/lib/ping-sources.ts +11 -6
  28. package/server/lib/preview-content.ts +172 -0
  29. package/server/lib/preview-error-code.ts +10 -0
  30. package/server/lib/preview-folders-setting.ts +133 -0
  31. package/server/lib/preview-log-throttle.ts +18 -0
  32. package/server/lib/preview-thumbs.ts +139 -0
  33. package/server/lib/secret-path-guard.ts +80 -0
  34. package/server/lib/session-files-setting.ts +88 -0
  35. package/server/lib/session-links.ts +280 -0
  36. package/server/lib/thread-turn-queue.ts +10 -0
  37. package/server/lib/today-brief.ts +119 -28
  38. package/server/lib/web-memories-bridge.ts +3 -1
  39. package/server/lib/work-activity.ts +56 -6
  40. package/server/lib/work-session-activity.ts +260 -0
  41. package/server/lib/your-move-count.ts +9 -3
  42. package/server/routes/agent-session-bindings.ts +13 -0
  43. package/server/routes/agent-session-stream.ts +16 -0
  44. package/server/routes/agent-sessions.ts +146 -11
  45. package/server/routes/ask-work-plan.ts +4 -1
  46. package/server/routes/claude-sessions.ts +5 -1
  47. package/server/routes/health.ts +35 -0
  48. package/server/routes/live-events.ts +329 -0
  49. package/server/routes/permission-broker.ts +69 -5
  50. package/server/routes/session-preview.ts +200 -0
  51. package/server/routes/thread-turn-queue.ts +207 -3
  52. package/server/routes/today-brief.ts +44 -9
  53. package/server/routes/welcome-context.ts +23 -3
  54. package/server/routes/work-board.ts +9 -2
  55. package/server/web-control/app.css +79 -9
  56. package/server/web-control/app.js +1543 -65
  57. package/server/web-control/control.css +37 -6
  58. package/server/web-control/index.html +8 -2
  59. package/server/web-control/memory-review.js +100 -0
  60. package/server/web-control/panes/cos-chat.css +51 -6
  61. package/server/web-control/panes/cos-chat.js +268 -16
  62. package/server/web-control/panes/meetings.css +23 -4
  63. package/server/web-control/panes/meetings.js +25 -9
  64. package/server/web-control/panes/memories.css +18 -3
  65. package/server/web-control/panes/memories.js +130 -50
  66. package/server/web-control/panes/messages.js +21 -2
  67. package/server/web-control/panes/preview.js +318 -0
  68. package/server/web-control/panes/questions.css +20 -4
  69. package/server/web-control/panes/questions.js +127 -26
  70. package/server/web-control/panes/reader-media.js +409 -23
  71. package/server/web-control/panes/sessions.css +50 -10
  72. package/server/web-control/panes/settings.js +96 -5
  73. package/server/web-control/panes/speakers.css +3 -5
  74. package/server/web-control/panes/speakers.js +7 -4
  75. package/server/web-control/panes/threads.js +3 -2
  76. package/server/web-control/panes/today-actions.css +49 -18
  77. package/server/web-control/panes/today-actions.js +217 -50
  78. package/server/web-control/panes/work.css +11 -1
  79. package/server/web-control/panes/work.js +10 -2
  80. package/server/web-control/shell.js +20 -14
package/README.md CHANGED
@@ -473,7 +473,9 @@ settled on evidence, never sent twice), `COS_PIN_BOOT_REAP` (default on: a threa
473
473
  run held frees 31 minutes after the restart, once its child is gone), `COS_QUEUE_RESEND`
474
474
  (default on: Send again on a refused or expired queued turn), `COS_MESSAGES_STEER` (default
475
475
  off: steer a running Claude Ask from the glasses), `COS_SESSION_STEER` (default off: steer a
476
- busy Claude desk session through its peer inbox) and `COS_QUERY_JOB_DELTA_JOURNAL` (default
476
+ busy Claude desk session through its peer inbox when a client asks), `COS_STEER_MIDTURN` (default
477
+ on: a message parked at a busy Claude desk session lands inside the running turn at its next tool
478
+ step, through the same peer inbox; `0` makes it wait for the turn to end) and `COS_QUERY_JOB_DELTA_JOURNAL` (default
477
479
  off until the release after 6.78.0: the compact Ask journal writer),
478
480
  `COS_PERMISSION_BROKER` (6.52.0: `0` answers every permission request with
479
481
  the Mac's own dialog, `questions` holds questions but not tool approvals; unset holds
@@ -92,7 +92,10 @@
92
92
  "COS_PIN_BOOT_REAP",
93
93
  "COS_QUEUE_RESEND",
94
94
  "COS_SESSION_STEER",
95
+ "COS_STEER_MIDTURN",
95
96
  "COS_KEY_LANES",
97
+ "COS_WEB_SESSION_FILES",
98
+ "COS_WEB_LIVE_EVENTS",
96
99
  "COS_DICTATION_AUTOCLEAN_MAX_PER_DAY",
97
100
  "COS_DICTATION_AUTOCLEAN_MAX_PER_DAY_MESSAGE",
98
101
  "COS_DICTATION_AUTOCLEAN_MAX_PER_DAY_MEETING",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotcos/glasses-server",
3
- "version": "6.99.29",
3
+ "version": "7.0.7",
4
4
  "description": "COS Glasses: 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": {
package/server/index.ts CHANGED
@@ -25,6 +25,8 @@ import { transcribeRouter } from './routes/transcribe.js'
25
25
  import { sessionIndexRouter } from './routes/session-index.js'
26
26
  import { agentSessionsRouter } from './routes/agent-sessions.js'
27
27
  import { agentSessionStreamRouter } from './routes/agent-session-stream.js'
28
+ import { createLiveEventsRouter, liveEventsEnabled, realLiveSources } from './routes/live-events.js'
29
+ import { LiveEventsHub } from './lib/live-events.js'
28
30
  import { createAttachedTurnStream } from './lib/session-stream-producer.js'
29
31
  import { claudeSessionsRouter } from './routes/claude-sessions.js'
30
32
  import { createSessionHooksRouter } from './routes/session-hooks.js'
@@ -187,6 +189,7 @@ import { createBridgeDropWriters } from './lib/drop-destinations.js'
187
189
  import { dropItEnabled } from './lib/drop-settings.js'
188
190
  import { serverTimezone } from './lib/morning-brief-config.js'
189
191
  import { createYourMoveCounter } from './lib/your-move-count.js'
192
+ import { workSessionActivity } from './lib/work-session-activity.js'
190
193
  import { readWorkJournal } from './lib/work-activity.js'
191
194
  import { createDropP2Boot } from './lib/drop-p2-boot.js'
192
195
  import { createP2SettingsReader } from './lib/drop-p2-settings.js'
@@ -240,7 +243,7 @@ import {
240
243
  } from './lib/maintenance-lifecycle.js'
241
244
 
242
245
  const app = express()
243
- import { createThreadTurnQueueRouter, drainAllThreads, steerAvailabilityFor } from './routes/thread-turn-queue.js'
246
+ import { createThreadTurnQueueRouter, drainAllThreads, midTurnAvailabilityFor, steerAvailabilityFor } from './routes/thread-turn-queue.js'
244
247
  import { createCursorStopFollowupRouter } from './routes/cursor-stop-followup.js'
245
248
  import { queuedThreadKeys, readQueue, readTranscriptTailLines, transcriptTurnVerdict, writeQueue } from './lib/thread-turn-queue-store.js'
246
249
  import { cursorDeskHaltReady, hookHaltReady, readHookToken } from './lib/claude-hooks-installer.js'
@@ -1029,6 +1032,7 @@ app.use('/api', createDropsRouter({
1029
1032
  yourMove: createYourMoveCounter({
1030
1033
  journal: () => readWorkJournal(),
1031
1034
  rows: () => workBoardReader.read(10_000, 3_000).catch(() => null),
1035
+ sessionActivity: ids => workSessionActivity(ids),
1032
1036
  }),
1033
1037
  }))
1034
1038
  // 6.99.16: the COS thread (routes/cos-thread.ts): one size-capped conversation behind the COS chat's Ask, with a
@@ -1074,6 +1078,13 @@ app.use('/api', agentSessionsRouter)
1074
1078
  // router's `/attachability` below. READ-ONLY, and deliberately NOT behind
1075
1079
  // COS_THREAD_ATTACH_ENABLED -- see the header of the route for why.
1076
1080
  app.use('/api', agentSessionStreamRouter)
1081
+ // 7.0.1 live state: ONE change feed per open Control Web page (docs/live-events-contract.md). Ids, states, counts
1082
+ // and times only. COS_WEB_LIVE_EVENTS=0 registers no route (404), so the page polls exactly as before. The hub reads
1083
+ // nothing and ticks never while no stream is open; its reply counter is one Map increment per Stop.
1084
+ if (liveEventsEnabled()) {
1085
+ const liveEventsHub = new LiveEventsHub({ sources: realLiveSources({ broker: () => permissionBroker }) })
1086
+ app.use('/api', createLiveEventsRouter({ hub: liveEventsHub }))
1087
+ }
1077
1088
  // Presence view of Claude Code sessions on this Mac. Dark unless
1078
1089
  // COS_CLAUDE_SESSIONS_ENABLED=1 — it projects another product's 0700 state dir.
1079
1090
  app.use('/api', claudeSessionsRouter)
@@ -1132,6 +1143,13 @@ const steerDeps = {
1132
1143
  waitingOnPrompt: sessionHooksEnabled() ? (signalFor(threadId)?.waiting ?? null) !== null : false,
1133
1144
  }),
1134
1145
  }
1146
+ /** The attempt journal (the binding registry): Sessions steer, steer mid-turn and the drain stamp. */
1147
+ const queueAttemptJournal = {
1148
+ begin: (provider: string, threadId: string, clientTurnId: string, mode: 'turn' | 'steer', now: number) =>
1149
+ agentSessionBindingRegistry.beginAttempt(provider, threadId, clientTurnId, mode, now),
1150
+ settle: (provider: string, threadId: string, clientTurnId: string, exit: 'sent' | 'not_sent' | 'unknown', now: number) =>
1151
+ agentSessionBindingRegistry.settleAttempt(provider, threadId, clientTurnId, exit, now),
1152
+ }
1135
1153
  /** The bounded transcript tail the reconcile and the receipts read evidence from. */
1136
1154
  const QUEUE_EVIDENCE_TAIL_BYTES = 4 * 1024 * 1024
1137
1155
  const queueEvidenceLines = (provider: string, threadId: string): readonly string[] | null =>
@@ -1157,6 +1175,10 @@ const queueTurnEvidence = makeQueueTurnEvidence({
1157
1175
  now: () => Date.now(),
1158
1176
  })
1159
1177
 
1178
+ // Steer mid-turn (COS_STEER_MIDTURN): the park route asks for a drain pass at once. The kick is
1179
+ // made below, after the deps it drains with; until then (and with attach off) this does nothing.
1180
+ let kickQueueDrain: ((threadId: string) => void) | null = null
1181
+
1160
1182
  if (threadAttachEnabled()) {
1161
1183
  const queueDeps = {
1162
1184
  // TWO GATES, ONE ANSWER. `threadOccupancy` sees FOREIGN holders -- another app on
@@ -1235,16 +1257,12 @@ if (threadAttachEnabled()) {
1235
1257
  .filter((text): text is string => typeof text === 'string'),
1236
1258
  }),
1237
1259
  ...steerDeps,
1238
- attempts: {
1239
- begin: (provider: string, threadId: string, clientTurnId: string, mode: 'turn' | 'steer', now: number) =>
1240
- agentSessionBindingRegistry.beginAttempt(provider, threadId, clientTurnId, mode, now),
1241
- settle: (provider: string, threadId: string, clientTurnId: string, exit: 'sent' | 'not_sent' | 'unknown', now: number) =>
1242
- agentSessionBindingRegistry.settleAttempt(provider, threadId, clientTurnId, exit, now),
1243
- },
1260
+ attempts: queueAttemptJournal,
1244
1261
  // 6.53.0: parked turns hold for two minutes after a cancel on their thread.
1245
1262
  cancelHoldUntil: (provider: string, threadId: string) => cancelHoldUntilFor(provider, threadId),
1246
1263
  // 6.62.0 (W14): a busy Agent CLI chat's parked turn drains here (no Stop hook fires for `-p`).
1247
1264
  cursorCliChat: (threadId: string) => continueFactsDeps.cursorChatDir(threadId) !== null,
1265
+ kickDrain: (threadId: string) => { kickQueueDrain?.(threadId) },
1248
1266
  now: () => Date.now(),
1249
1267
  }
1250
1268
  app.use('/api', createThreadTurnQueueRouter(queueDeps))
@@ -1266,6 +1284,8 @@ if (threadAttachEnabled()) {
1266
1284
  queuedThreadIds: () => queuedThreadKeys().map(k => k.threadId),
1267
1285
  })
1268
1286
  registerDrainKickStats(() => drainKick.stats())
1287
+ // The same in-flight guard as the timer and the Stop hooks: a park never starts a second pass.
1288
+ kickQueueDrain = threadId => { drainKick.kick(threadId) }
1269
1289
  const queueDrainTimer = setInterval(() => { void drainKick.sweep() }, 20_000)
1270
1290
  queueDrainTimer.unref()
1271
1291
  if (sessionHooksEnabled()) {
@@ -1375,6 +1395,8 @@ app.use('/api', createAgentSessionBindingsRouter({
1375
1395
  bootId: serverMetrics.bootId,
1376
1396
  }),
1377
1397
  sessionSteer: (provider, threadId, gateReason) => (sessionSteerEnabled() ? steerAvailabilityFor(steerDeps, provider, threadId, gateReason) : null),
1398
+ // Steer mid-turn: the attachability answer's `midTurn` (null, and absent, with COS_STEER_MIDTURN off).
1399
+ sessionMidTurn: (provider, threadId, gateReason) => midTurnAvailabilityFor({ ...steerDeps, attempts: queueAttemptJournal }, provider, threadId, gateReason),
1378
1400
  // 6.53.0: cancel a session run from the lens. The route's own in-flight map answers for
1379
1401
  // a COS turn; these answer for a run at the desk. Read per request, so Install hooks in
1380
1402
  // Control flips `hooks_outdated` to `desk_run` within the status cache's five seconds.