@gotcos/glasses-server 6.98.1 → 6.99.16
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 +227 -0
- package/package.json +2 -1
- package/server/index.ts +170 -11
- package/server/lib/api-auth.ts +31 -14
- package/server/lib/claude-bridge.ts +21 -2
- package/server/lib/codex-bridge.ts +21 -2
- package/server/lib/cos-thread.ts +582 -0
- package/server/lib/display-bus.ts +3 -0
- package/server/lib/drop-attachments.ts +55 -0
- package/server/lib/drop-destinations.ts +134 -0
- package/server/lib/drop-history-search.ts +97 -0
- package/server/lib/drop-p2-boot.ts +60 -0
- package/server/lib/drop-p2-runtime.ts +237 -0
- package/server/lib/drop-p2-settings.ts +174 -0
- package/server/lib/drop-resolution.ts +168 -0
- package/server/lib/drop-router.ts +325 -0
- package/server/lib/drop-service.ts +830 -0
- package/server/lib/drop-settings.ts +56 -0
- package/server/lib/drop-store.ts +450 -0
- package/server/lib/drop-when.ts +265 -0
- package/server/lib/glasses-pairing.ts +15 -0
- package/server/lib/learning-needs-you.ts +111 -0
- package/server/lib/live-cues-v2-settings.ts +2 -1
- package/server/lib/live-cues-v2.ts +6 -4
- package/server/lib/media-store.ts +80 -0
- package/server/lib/meeting-batch-transcribe.ts +15 -2
- package/server/lib/meeting-consolidation-run.ts +169 -0
- package/server/lib/meeting-store.ts +61 -0
- package/server/lib/meeting-title-steer.ts +200 -0
- package/server/lib/network-policy.ts +84 -6
- package/server/lib/permission-broker.ts +13 -0
- package/server/lib/phone-recording.ts +1 -1
- package/server/lib/ping-budget.ts +149 -0
- package/server/lib/ping-engine.ts +245 -0
- package/server/lib/ping-feed-sink.ts +33 -0
- package/server/lib/ping-sources.ts +192 -0
- package/server/lib/query-job-coordinator.ts +5 -2
- package/server/lib/query-job-runtime.ts +45 -4
- package/server/lib/query-job-types.ts +10 -1
- package/server/lib/reminder-scheduler.ts +153 -0
- package/server/lib/tailnet-self-name.ts +103 -0
- package/server/lib/tailscale-nudge.ts +40 -2
- package/server/lib/task-store.ts +32 -0
- package/server/lib/web-ask-admission.ts +56 -0
- package/server/lib/web-live-chunks.ts +273 -0
- package/server/lib/web-memories-bridge.ts +323 -0
- package/server/lib/web-session-auth.ts +492 -0
- package/server/lib/web-usage-bridge.ts +154 -0
- package/server/lib/work-search.ts +14 -1
- package/server/lib/your-move-count.ts +58 -0
- package/server/routes/cos-thread.ts +79 -0
- package/server/routes/display.ts +11 -1
- package/server/routes/drops.ts +262 -0
- package/server/routes/health.ts +16 -0
- package/server/routes/live-cues.ts +5 -4
- package/server/routes/meeting.ts +61 -5
- package/server/routes/permission-broker.ts +23 -3
- package/server/routes/transcribe-stream.ts +43 -4
- package/server/routes/web-control.ts +569 -0
- package/server/routes/welcome-context.ts +2 -1
- package/server/scripts/web-live-cls/cdp.mjs +57 -0
- package/server/scripts/web-live-cls/measure.mjs +80 -0
- package/server/scripts/web-live-cls/mock.mjs +97 -0
- package/server/scripts/web-live-cls/render.mjs +26 -0
- package/server/web-control/app.css +519 -0
- package/server/web-control/app.js +3643 -0
- package/server/web-control/control.css +325 -0
- package/server/web-control/fonts/DMSans.ttf +0 -0
- package/server/web-control/fonts/Fraunces.ttf +0 -0
- package/server/web-control/fonts/JetBrainsMono.ttf +0 -0
- package/server/web-control/index.html +275 -0
- package/server/web-control/mark.svg +1 -0
- package/server/web-control/memories/d3.min.js +2 -0
- package/server/web-control/memories/graph-explorer.css +200 -0
- package/server/web-control/memories/graph-explorer.js +748 -0
- package/server/web-control/memories/memories-app.js +1561 -0
- package/server/web-control/memories/memories-theme.css +73 -0
- package/server/web-control/memories/memories.html +72 -0
- package/server/web-control/memories/memory-stewardship.js +63 -0
- package/server/web-control/memories/memory-workspace.js +216 -0
- package/server/web-control/panes/cos-chat.css +174 -0
- package/server/web-control/panes/cos-chat.js +1118 -0
- package/server/web-control/panes/dictation.js +173 -0
- package/server/web-control/panes/meetings.css +314 -0
- package/server/web-control/panes/meetings.js +2568 -0
- package/server/web-control/panes/messages.js +718 -0
- package/server/web-control/panes/questions.css +32 -0
- package/server/web-control/panes/questions.js +275 -0
- package/server/web-control/panes/sessions.css +452 -0
- package/server/web-control/panes/settings.js +292 -0
- package/server/web-control/panes/speakers.css +269 -0
- package/server/web-control/panes/speakers.js +2755 -0
- package/server/web-control/panes/threads.js +200 -0
- package/server/web-control/panes/work.css +261 -0
- package/server/web-control/panes/work.js +1667 -0
- package/server/web-control/shell.js +294 -0
- package/server/web-control/theme.js +25 -0
- package/server/web-control/vendor/qrcode-encoder.js +10 -0
package/server/lib/api-auth.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestHandler } from 'express'
|
|
2
2
|
import { timingSafeTokenEqual } from './token-auth.js'
|
|
3
3
|
import { PAIRING_NONCE_BODY } from './glasses-pairing.js'
|
|
4
|
+
import { isWebSessionAuthorized } from './web-session-auth.js'
|
|
4
5
|
|
|
5
6
|
// Recovery/setup clients need these availability surfaces before they have a
|
|
6
7
|
// usable token. Keep private provider state and every mutation route out.
|
|
@@ -47,6 +48,11 @@ export function isPublicApiRequest(method: string, path: string): boolean {
|
|
|
47
48
|
if (PUBLIC_API_PATHS.has(path)) return true
|
|
48
49
|
if (method === 'POST' && path === '/pairing/claim') return true
|
|
49
50
|
if (method === 'GET' && PAIRING_POLL_PATH.test(path)) return true
|
|
51
|
+
// COS Control Web (browser on the tailnet). Login takes the token or a one-time handoff
|
|
52
|
+
// ticket and answers with the HttpOnly cookie; it is rate limited in the route. Status
|
|
53
|
+
// says only whether this browser is signed in. Everything else stays behind the gate.
|
|
54
|
+
if (method === 'POST' && (path === '/web-session/login' || path === '/web-session/logout')) return true
|
|
55
|
+
if (method === 'GET' && path === '/web-session/status') return true
|
|
50
56
|
if (method !== 'GET' && method !== 'HEAD') return false
|
|
51
57
|
return TTS_PLAYBACK_CAPABILITY_PATH.test(path)
|
|
52
58
|
|| DISPLAY_STREAM_CAPABILITY_PATH.test(path)
|
|
@@ -54,29 +60,40 @@ export function isPublicApiRequest(method: string, path: string): boolean {
|
|
|
54
60
|
|
|
55
61
|
export interface RequireApiTokenOptions {
|
|
56
62
|
/**
|
|
57
|
-
* Called once per request that passed the
|
|
58
|
-
*
|
|
59
|
-
*
|
|
63
|
+
* Called once per request that passed the X-COS-Token header check (never for a
|
|
64
|
+
* public path and never for a browser cookie session so web login cannot disturb
|
|
65
|
+
* the glasses pairing state machine).
|
|
60
66
|
*/
|
|
61
67
|
onAuthenticated?: (req: Parameters<RequestHandler>[0]) => void
|
|
68
|
+
/** Verified Tailscale `Self.DNSName` for validating browser cookie Origins. */
|
|
69
|
+
selfDnsName?: () => string | null
|
|
70
|
+
/** Allowed browser-facing server ports (e.g. `[3141, 3143]`) for cookie Origin port binding. */
|
|
71
|
+
allowedPorts?: () => readonly number[]
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
/** Global /api authentication boundary. Mount before all body parsers. */
|
|
65
75
|
export function requireApiToken(apiToken: string, options: RequireApiTokenOptions = {}): RequestHandler {
|
|
66
76
|
return (req, res, next) => {
|
|
67
77
|
if (isPublicApiRequest(req.method, req.path)) return next()
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Older COS Glasses builds, which cannot scan, show this message word for word.
|
|
74
|
-
message: 'Paste the pairing token from COS Control, or scan its code with COS Glasses 6.10.621 or newer.',
|
|
75
|
-
})
|
|
78
|
+
if (timingSafeTokenEqual(req.headers['x-cos-token'], apiToken)) {
|
|
79
|
+
if (options.onAuthenticated) {
|
|
80
|
+
try { options.onAuthenticated(req) } catch { /* observer only */ }
|
|
81
|
+
}
|
|
82
|
+
return next()
|
|
76
83
|
}
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
const cookieAuth = isWebSessionAuthorized(req, apiToken, {
|
|
85
|
+
selfDnsName: options.selfDnsName?.() ?? null,
|
|
86
|
+
allowedPorts: options.allowedPorts?.(),
|
|
87
|
+
})
|
|
88
|
+
if (cookieAuth) {
|
|
89
|
+
return next()
|
|
79
90
|
}
|
|
80
|
-
|
|
91
|
+
return res.status(401).json({
|
|
92
|
+
error: 'unauthorized',
|
|
93
|
+
// Stable on purpose: clients key off this value, not the message.
|
|
94
|
+
reason: 'pairing_token_rejected',
|
|
95
|
+
// Older COS Glasses builds, which cannot scan, show this message word for word.
|
|
96
|
+
message: 'Paste the pairing token from COS Control, or scan its code with COS Glasses 6.10.621 or newer.',
|
|
97
|
+
})
|
|
81
98
|
}
|
|
82
99
|
}
|
|
@@ -418,6 +418,8 @@ const PHASE_LABELS: Record<Phase, string> = {
|
|
|
418
418
|
* Returns the session ID for multi-turn tracking.
|
|
419
419
|
*/
|
|
420
420
|
export interface CallOptions {
|
|
421
|
+
/** Internal checkpoint runner only; timeout is never a successful answer. */
|
|
422
|
+
maintenancePass?: true
|
|
421
423
|
lightweight?: boolean // Skip async context fetch — use cached context instantly (G2 speed path)
|
|
422
424
|
abortSignal?: AbortSignal
|
|
423
425
|
effort?: EffortPreference
|
|
@@ -700,7 +702,7 @@ export async function callClaudeStreaming(
|
|
|
700
702
|
const effortWallMax = resolvedEffort === 'max' || resolvedEffort === 'ultracode'
|
|
701
703
|
? Math.max(defaultWallMax, WALL_MAX_DEEP_EFFORT_MS)
|
|
702
704
|
: defaultWallMax
|
|
703
|
-
const wallMax = isExtendedQuery(query) ? Math.max(WALL_MAX_EXTENDED_MS, effortWallMax) : effortWallMax
|
|
705
|
+
const wallMax = options?.maintenancePass ? 900_000 : isExtendedQuery(query) ? Math.max(WALL_MAX_EXTENDED_MS, effortWallMax) : effortWallMax
|
|
704
706
|
// Key lanes: one nonce per turn from the relay (P2 contract), then the child env points at it.
|
|
705
707
|
// Only the child's copy is written. A relay that cannot issue ends the turn before spawn.
|
|
706
708
|
let keyTurn: RelayTurnHandle | null = null
|
|
@@ -850,8 +852,16 @@ export async function callClaudeStreaming(
|
|
|
850
852
|
})
|
|
851
853
|
}
|
|
852
854
|
|
|
855
|
+
let maintenanceClosed = false
|
|
853
856
|
async function finalize(text: string) {
|
|
854
857
|
if (finalized) return
|
|
858
|
+
// A next maintenance pass may start only after the entire provider tree
|
|
859
|
+
// has closed. Normal chat retains its existing result-event behavior.
|
|
860
|
+
if (options?.maintenancePass && !maintenanceClosed) {
|
|
861
|
+
if (terminationRequested) return
|
|
862
|
+
await terminateForTerminal('maintenance checkpoint', () => finalize(text))
|
|
863
|
+
return
|
|
864
|
+
}
|
|
855
865
|
const responseAuthFailure = terminalProviderAuthFailure('claude', text)
|
|
856
866
|
const authFailure = responseAuthFailure
|
|
857
867
|
?? (!text.trim() ? terminalTextError ?? terminalProviderAuthFailure('claude', stderr) : null)
|
|
@@ -1010,6 +1020,11 @@ export async function callClaudeStreaming(
|
|
|
1010
1020
|
status: Exclude<ClaudeRunStatus, 'running'> = 'failed',
|
|
1011
1021
|
) {
|
|
1012
1022
|
if (finalized) return
|
|
1023
|
+
if (options?.maintenancePass && !maintenanceClosed) {
|
|
1024
|
+
if (terminationRequested) return
|
|
1025
|
+
await terminateForTerminal('maintenance error', () => finalizeError(msg, exitCode, status))
|
|
1026
|
+
return
|
|
1027
|
+
}
|
|
1013
1028
|
finalized = true
|
|
1014
1029
|
cleanup()
|
|
1015
1030
|
armSteerReaper()
|
|
@@ -1041,6 +1056,7 @@ export async function callClaudeStreaming(
|
|
|
1041
1056
|
console.error(`[claude-bridge] provider did not close after SIGKILL (${reason}); retaining lifecycle ownership`)
|
|
1042
1057
|
return
|
|
1043
1058
|
}
|
|
1059
|
+
maintenanceClosed = true
|
|
1044
1060
|
await onClosed(result)
|
|
1045
1061
|
}
|
|
1046
1062
|
|
|
@@ -1117,6 +1133,7 @@ export async function callClaudeStreaming(
|
|
|
1117
1133
|
|
|
1118
1134
|
const wallTimer = setTimeout(() => {
|
|
1119
1135
|
void terminateForTerminal('wall timeout', result => {
|
|
1136
|
+
if (options?.maintenancePass) return finalizeError('maintenance_pass_timeout', result.code)
|
|
1120
1137
|
// 6.78.0 steer: a steered run past its first whole answer completes with that answer.
|
|
1121
1138
|
if (steerCheckpoint !== null) return finalize(steerCheckpoint)
|
|
1122
1139
|
if (fullText) {
|
|
@@ -1338,7 +1355,9 @@ export async function callClaudeStreaming(
|
|
|
1338
1355
|
} catch { /* ignore */ }
|
|
1339
1356
|
}
|
|
1340
1357
|
|
|
1341
|
-
if (
|
|
1358
|
+
if (options?.maintenancePass) {
|
|
1359
|
+
void finalizeError('maintenance_pass_incomplete_exit', code)
|
|
1360
|
+
} else if (fullText) {
|
|
1342
1361
|
// If we got text but no explicit result event, still finalize. A sole,
|
|
1343
1362
|
// machine-shaped auth response is classified inside finalize.
|
|
1344
1363
|
void finalize(fullText)
|
|
@@ -722,8 +722,16 @@ export async function callCodexStreaming(
|
|
|
722
722
|
callbacks.onChunk(text)
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
+
let maintenanceClosed = false
|
|
725
726
|
async function finalize(text: string) {
|
|
726
727
|
if (finalized) return
|
|
728
|
+
// A next maintenance pass may start only after the entire provider tree
|
|
729
|
+
// has closed. Normal chat retains its existing result-event behavior.
|
|
730
|
+
if (options?.maintenancePass && !maintenanceClosed) {
|
|
731
|
+
if (terminationRequested) return
|
|
732
|
+
await terminateForTerminal('maintenance checkpoint', () => finalize(text))
|
|
733
|
+
return
|
|
734
|
+
}
|
|
727
735
|
const responseAuthFailure = terminalProviderAuthFailure('codex', text)
|
|
728
736
|
const authFailure = responseAuthFailure
|
|
729
737
|
?? (!text.trim() ? terminalTextError ?? terminalProviderAuthFailure('codex', stderr) : null)
|
|
@@ -861,6 +869,11 @@ export async function callCodexStreaming(
|
|
|
861
869
|
|
|
862
870
|
async function finalizeError(msg: string, exitCode?: number | null, status: Exclude<CodexRunStatus, 'running'> = 'failed') {
|
|
863
871
|
if (finalized) return
|
|
872
|
+
if (options?.maintenancePass && !maintenanceClosed) {
|
|
873
|
+
if (terminationRequested) return
|
|
874
|
+
await terminateForTerminal('maintenance error', () => finalizeError(msg, exitCode, status))
|
|
875
|
+
return
|
|
876
|
+
}
|
|
864
877
|
finalized = true
|
|
865
878
|
// Canary R14: a Codex child keeps retrying after the bridge ends the turn; its nonce dies now.
|
|
866
879
|
keyTurn?.revoke('cancel')
|
|
@@ -876,7 +889,7 @@ export async function callCodexStreaming(
|
|
|
876
889
|
exitCode,
|
|
877
890
|
})
|
|
878
891
|
try {
|
|
879
|
-
await callbacks.onError(safeCodexUserError(msg))
|
|
892
|
+
await callbacks.onError(options?.maintenancePass && msg === 'maintenance_pass_timeout' ? msg : safeCodexUserError(msg))
|
|
880
893
|
} catch (error) {
|
|
881
894
|
console.error('[codex-bridge] terminal error callback failed:', error)
|
|
882
895
|
}
|
|
@@ -910,6 +923,7 @@ export async function callCodexStreaming(
|
|
|
910
923
|
console.error(`[codex-bridge] provider did not close after SIGKILL (${reason}); retaining lifecycle ownership`)
|
|
911
924
|
return
|
|
912
925
|
}
|
|
926
|
+
maintenanceClosed = true
|
|
913
927
|
await onClosed(result)
|
|
914
928
|
}
|
|
915
929
|
|
|
@@ -949,6 +963,7 @@ export async function callCodexStreaming(
|
|
|
949
963
|
|
|
950
964
|
const wallTimer = setTimeout(() => {
|
|
951
965
|
void terminateForTerminal('wall timeout', result => {
|
|
966
|
+
if (options?.maintenancePass) return finalizeError('maintenance_pass_timeout', result.code)
|
|
952
967
|
if (fullText) return finalize(fullText)
|
|
953
968
|
return finalizeError(`Wall clock limit reached (${WALL_MAX_MS / 1000}s). Codex process killed.`, result.code)
|
|
954
969
|
})
|
|
@@ -1046,7 +1061,11 @@ export async function callCodexStreaming(
|
|
|
1046
1061
|
try { handleEvent(JSON.parse(buffer.trim())) } catch { /* ignore */ }
|
|
1047
1062
|
}
|
|
1048
1063
|
runUsage.settle()
|
|
1049
|
-
if (finalized) return
|
|
1064
|
+
if (finalized || terminationRequested) return
|
|
1065
|
+
if (options?.maintenancePass) {
|
|
1066
|
+
finalizeError('maintenance_pass_incomplete_exit', code)
|
|
1067
|
+
return
|
|
1068
|
+
}
|
|
1050
1069
|
if (fullText) {
|
|
1051
1070
|
void finalize(fullText)
|
|
1052
1071
|
return
|