@gotcos/glasses-server 6.78.0 → 6.78.1
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 +8 -0
- package/package.json +1 -1
- package/server/index.ts +4 -1
- package/server/lib/native-head.ts +65 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 6.78.1 - Glasses messages reach open Claude sessions again, whatever the size of ~/.claude/projects
|
|
2
|
+
|
|
3
|
+
Hotfix on 6.78.0. No companion or COS Control change.
|
|
4
|
+
|
|
5
|
+
- **What broke.** To decide whether an open Claude session is idle, the server finds its transcript by walking `~/.claude/projects`, and it refused the walk past 2,048 folders. Every Claude transcript then read as missing, so occupancy answered `live_desktop_process` for every open session: queued glasses messages waited until they expired (the lens said "Waiting for the current turn to end"), and live delivery through the peer inbox could not confirm either. On Miles's Mac the count passed 2,048 on 2026-10-17 12:33, almost all of it empty folders left by COS's own stateless `claude -p` calls (fixed in the COS scripts separately). The ceiling dates from 6.36; 6.77.0 has the same refusal.
|
|
6
|
+
- **The fix.** The transcript path Claude Code reports in every hook event (`transcript_path`, already in the session signal store) is used first, registered at boot so every reader of Claude transcripts gets it: no walk, no folder limit. It is used only when it is `<projects>/<one folder>/<session id>.jsonl` and the file exists; anything else falls back to the walk, unchanged. The walk ceiling rises to 8,192, and a refusal now logs one `[native-head]` line at most once an hour instead of failing silently.
|
|
7
|
+
- **Checks.** Full suite 7,715 passed, 2 skipped. 9 of 9 guard mutants killed by named tests.
|
|
8
|
+
|
|
1
9
|
## 6.78.0 - Steer from the glasses, Retry and Send again, and crash safety for Asks and desk turns (Pi Durable parity)
|
|
2
10
|
|
|
3
11
|
Pairs with COS Glasses companion 6.10.645. Includes 6.77.0. No COS Control change is required (0.5.322 and 0.5.323 work; see the client matrix). Miles, 2026-10-18: steer from the glasses and retry, for Messages and Sessions; no regressions; better offline to online. Plan: Pi Durable parity build v3.1.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotcos/glasses-server",
|
|
3
|
-
"version": "6.78.
|
|
3
|
+
"version": "6.78.1",
|
|
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
|
@@ -70,7 +70,7 @@ import { nativeQueueControl, plainQueueText } from './lib/codex-queue-control.js
|
|
|
70
70
|
import { makeCodexLiveDeliverer } from './lib/codex-live-queue-deps.js'
|
|
71
71
|
import { forkThread, realForkDeps } from './lib/fork-thread.js'
|
|
72
72
|
import { ForkJobLedger, fileForkJobPersistence } from './lib/fork-job-ledger.js'
|
|
73
|
-
import { nativeHead, realNativeHeadDeps } from './lib/native-head.js'
|
|
73
|
+
import { nativeHead, realNativeHeadDeps, registerClaudeTranscriptHint } from './lib/native-head.js'
|
|
74
74
|
import { threadOccupancy, holderActivity } from './lib/thread-occupancy.js'
|
|
75
75
|
import { displayRouter } from './routes/display.js'
|
|
76
76
|
import { transcribeStreamRouter, getActiveTranscriptionSessionCount } from './routes/transcribe-stream.js'
|
|
@@ -536,6 +536,9 @@ bindingReapTimer.unref()
|
|
|
536
536
|
// re-deriving roots per request.
|
|
537
537
|
const occupancyDirs = realOccupancyDirs()
|
|
538
538
|
const nativeHeadDeps = realNativeHeadDeps()
|
|
539
|
+
// 6.78.1: Claude Code's own transcript path from the hooks, so finding a session's transcript
|
|
540
|
+
// needs no walk of ~/.claude/projects (which refused past its folder limit; see native-head.ts).
|
|
541
|
+
registerClaudeTranscriptHint(sessionId => signalFor(sessionId)?.transcriptPath ?? null)
|
|
539
542
|
const attachedWorkspaceDeps = realAttachedWorkspaceDeps(nativeHeadDeps)
|
|
540
543
|
// 6.62.0: one set of readers for the Continue plan, rows, detail and the attach verdict.
|
|
541
544
|
const continueFactsDeps = realContinueFactsDeps()
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
|
|
120
120
|
import { createHash } from 'node:crypto'
|
|
121
121
|
import { closeSync, constants as fsConstants, fstatSync, openSync, readSync, readdirSync, statSync } from 'node:fs'
|
|
122
|
-
import { join } from 'node:path'
|
|
122
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
123
123
|
import { agentSessionRoots, idFromCodexFilename } from './agent-session-store.js'
|
|
124
124
|
import { cursorTranscriptPath } from './cursor-agent-store.js'
|
|
125
125
|
import { isValidNativeThreadId } from './native-thread-id.js'
|
|
@@ -177,8 +177,16 @@ export const TAIL_WINDOW_BYTES_BY_PROVIDER: Record<NativeHeadProvider, number> =
|
|
|
177
177
|
*/
|
|
178
178
|
export const HEAD_ROW_COUNT = 3
|
|
179
179
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Ceilings that keep a pathological tree from turning attach into a directory walk.
|
|
182
|
+
*
|
|
183
|
+
* 6.78.1: Claude raised from 2048. COS's own stateless `claude -p` calls each left an empty
|
|
184
|
+
* project folder behind (about 100 to 270 a day on Miles's Mac); at 2,049 the walk refused,
|
|
185
|
+
* every Claude transcript read as missing, and every glasses message to an open Claude session
|
|
186
|
+
* waited from 2026-10-17 12:33 until it expired. A session whose hooks name its transcript
|
|
187
|
+
* no longer walks at all (`hintedClaudeTranscript`); this ceiling now bounds only the rest.
|
|
188
|
+
*/
|
|
189
|
+
export const MAX_CLAUDE_PROJECT_DIRS = 8192
|
|
182
190
|
export const MAX_CODEX_DIR_ENTRIES = 512
|
|
183
191
|
export const MAX_CODEX_FILES_SCANNED = 20_000
|
|
184
192
|
|
|
@@ -224,6 +232,13 @@ export interface NativeHeadDeps {
|
|
|
224
232
|
* as `agentSessionLines`' injectable head/tail sizes.
|
|
225
233
|
*/
|
|
226
234
|
tailWindowBytes?: number
|
|
235
|
+
/**
|
|
236
|
+
* 6.78.1: where Claude Code itself says this session's transcript is (the `transcript_path`
|
|
237
|
+
* its hook events carry), or null. Optional: absent, or null, means walk the project folders
|
|
238
|
+
* as before. Validated before use (`hintedClaudeTranscript`), so a wrong hint can only fall
|
|
239
|
+
* back to the walk, never name another file.
|
|
240
|
+
*/
|
|
241
|
+
claudeTranscriptHint?: (sessionId: string) => string | null
|
|
227
242
|
}
|
|
228
243
|
|
|
229
244
|
/**
|
|
@@ -406,6 +421,41 @@ export function transcriptPathFor(
|
|
|
406
421
|
}
|
|
407
422
|
}
|
|
408
423
|
|
|
424
|
+
/**
|
|
425
|
+
* The hook's transcript path, only if it is exactly `<root>/<one folder>/<threadId>.jsonl` and
|
|
426
|
+
* the file exists. The walk's two-copies refusal is not repeated here: this is the file Claude
|
|
427
|
+
* Code reported writing for this very session, which is stronger evidence than a name match.
|
|
428
|
+
*/
|
|
429
|
+
function hintedClaudeTranscript(filename: string, root: string, threadId: string, deps: NativeHeadDeps): string | null {
|
|
430
|
+
if (typeof deps.claudeTranscriptHint !== 'function') return null
|
|
431
|
+
let hint: string | null
|
|
432
|
+
try {
|
|
433
|
+
hint = deps.claudeTranscriptHint(threadId)
|
|
434
|
+
} catch {
|
|
435
|
+
return null
|
|
436
|
+
}
|
|
437
|
+
if (typeof hint !== 'string') return null
|
|
438
|
+
const path = resolve(hint)
|
|
439
|
+
if (basename(path) !== filename) return null
|
|
440
|
+
if (dirname(dirname(path)) !== resolve(root)) return null
|
|
441
|
+
return deps.fileExists(path) ? path : null
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Said at most once an hour: the walk refusing is otherwise silent, and it was for 26 hours. */
|
|
445
|
+
let lastOverflowWarnAt = 0
|
|
446
|
+
function warnClaudeProjectsOverflow(root: string, count: number): void {
|
|
447
|
+
const now = Date.now()
|
|
448
|
+
if (now - lastOverflowWarnAt < 60 * 60_000) return
|
|
449
|
+
lastOverflowWarnAt = now
|
|
450
|
+
console.warn(`[native-head] ${count} folders in ${root}, over the ${MAX_CLAUDE_PROJECT_DIRS} walk limit: Claude sessions without a hook-reported transcript read as missing`)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Where index.ts registers the hook lookup, so every `realNativeHeadDeps()` gets it. */
|
|
454
|
+
let claudeTranscriptHintSource: ((sessionId: string) => string | null) | null = null
|
|
455
|
+
export function registerClaudeTranscriptHint(read: ((sessionId: string) => string | null) | null): void {
|
|
456
|
+
claudeTranscriptHintSource = read
|
|
457
|
+
}
|
|
458
|
+
|
|
409
459
|
function cursorAgentTranscriptPath(threadId: string, deps: NativeHeadDeps): string | null {
|
|
410
460
|
return cursorTranscriptPath(threadId, deps.dirs.cursorProjectsDir)
|
|
411
461
|
}
|
|
@@ -414,10 +464,16 @@ function claudeTranscriptPath(threadId: string, deps: NativeHeadDeps): string |
|
|
|
414
464
|
const root = deps.dirs.claudeProjectsDir
|
|
415
465
|
if (!deps.dirExists(root)) return null
|
|
416
466
|
|
|
467
|
+
const filename = `${threadId}.jsonl`
|
|
468
|
+
const hinted = hintedClaudeTranscript(filename, root, threadId, deps)
|
|
469
|
+
if (hinted !== null) return hinted
|
|
470
|
+
|
|
417
471
|
const folders = deps.readDir(root)
|
|
418
|
-
if (folders.length > MAX_CLAUDE_PROJECT_DIRS)
|
|
472
|
+
if (folders.length > MAX_CLAUDE_PROJECT_DIRS) {
|
|
473
|
+
warnClaudeProjectsOverflow(root, folders.length)
|
|
474
|
+
return null
|
|
475
|
+
}
|
|
419
476
|
|
|
420
|
-
const filename = `${threadId}.jsonl`
|
|
421
477
|
let found: string | null = null
|
|
422
478
|
for (const folder of folders) {
|
|
423
479
|
const candidate = join(root, folder, filename)
|
|
@@ -660,5 +716,8 @@ function readTail(path: string, maxBytes: number): TailRead | null {
|
|
|
660
716
|
}
|
|
661
717
|
|
|
662
718
|
export function realNativeHeadDeps(dirs: NativeHeadDirs = realNativeHeadDirs()): NativeHeadDeps {
|
|
663
|
-
return {
|
|
719
|
+
return {
|
|
720
|
+
dirs, dirExists, readDir, fileExists, readTail,
|
|
721
|
+
claudeTranscriptHint: sessionId => (claudeTranscriptHintSource === null ? null : claudeTranscriptHintSource(sessionId)),
|
|
722
|
+
}
|
|
664
723
|
}
|