@gotcos/glasses-server 6.27.13 → 6.28.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.
@@ -0,0 +1,197 @@
1
+ // Where does a resumed provider turn actually run?
2
+ //
3
+ // The attach route resolves this server-side because plan 4.2 forbids the client
4
+ // sending a path, and the adapter needs a real cwd to spawn in. It is the missing
5
+ // half of `TargetResolution`, which carries non-identifying fingerprints for the
6
+ // wire but no directory to run in.
7
+ //
8
+ // WHY NOT DECODE THE PROJECT SLUG. Claude files transcripts under
9
+ // `~/.claude/projects/<slug>/<id>.jsonl` where the slug is the cwd with separators
10
+ // replaced. Decoding it back is LOSSY and wrong on this very machine:
11
+ // slug -Users-ukaoma-Documents-GitHub-Ukaoma-Chief-Of-Staff-MU-Chief-Staff
12
+ // naive /Users/ukaoma/Documents/GitHub/Ukaoma/Chief/Of/Staff/MU/Chief/Staff
13
+ // real /Users/ukaoma/Documents/GitHub/Ukaoma Chief Of Staff/MU-Chief-Staff
14
+ // The real path contains spaces AND hyphens, so the mapping is not invertible.
15
+ // Resuming in the wrong cwd is not a harmless error: Claude associates a session
16
+ // with its project, so a mismatched cwd risks writing a NEW session instead of
17
+ // appending to the target — a silent fork, which is precisely the outcome the
18
+ // adapter's id-equality check exists to catch and which is better never caused.
19
+ //
20
+ // So the cwd is read from the transcript itself, which records it verbatim.
21
+ // Verified 2026-08-16: Claude message rows carry a top-level `cwd` (234 of the
22
+ // first 300 rows of a live transcript, all identical); Codex records it once in the
23
+ // session meta row as `payload.cwd`.
24
+ //
25
+ // EVERYTHING HERE FAILS CLOSED. Ambiguity, an unreadable transcript, a missing cwd,
26
+ // a relative path, or a directory that no longer exists all return null, and a null
27
+ // makes attach REFUSE. A wrong cwd is worse than no attach.
28
+
29
+ import { createHash } from 'node:crypto'
30
+ import { closeSync, constants as fsConstants, openSync, readSync, statSync } from 'node:fs'
31
+ import { isAbsolute } from 'node:path'
32
+ import { realNativeHeadDeps, transcriptPathFor, type NativeHeadDeps } from './native-head.js'
33
+
34
+ export type AttachedWorkspaceProvider = 'claude' | 'codex'
35
+
36
+ export interface ResolvedWorkspace {
37
+ /**
38
+ * Real directory to spawn in. SERVER-SIDE ONLY — never goes on the wire.
39
+ * Plan 3.3 requires the client-visible reference to carry no filesystem path.
40
+ */
41
+ path: string
42
+ /** Non-identifying, stable. Safe to persist in a binding and compare. */
43
+ workspaceFingerprint: string
44
+ /** Which transcript the cwd came from, fingerprinted the same way. */
45
+ sourceFingerprint: string
46
+ }
47
+
48
+ export interface AttachedWorkspaceDeps {
49
+ /** Absolute path of the transcript for this thread, or null when not exactly one. */
50
+ transcriptPath: (provider: AttachedWorkspaceProvider, threadId: string) => string | null
51
+ /** First bytes of the transcript. Null when unreadable. */
52
+ readHead: (path: string, maxBytes: number) => string | null
53
+ /** Does this directory exist right now? MUST THROW on "cannot tell". */
54
+ dirExists: (path: string) => boolean
55
+ }
56
+
57
+ /** Enough to reach a Claude message row or the Codex session meta row. */
58
+ export const WORKSPACE_SCAN_BYTES = 512 * 1024
59
+
60
+ /** Rows to inspect before giving up. Bounds a pathological single-line file. */
61
+ export const WORKSPACE_SCAN_ROWS = 400
62
+
63
+ export function fingerprint(value: string): string {
64
+ return createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32)
65
+ }
66
+
67
+ /**
68
+ * Pull the recorded cwd out of transcript text.
69
+ *
70
+ * Claude puts it at the row's top level; Codex puts it in the session meta row's
71
+ * `payload`. A torn trailing line is NORMAL — these files are appended to — so a
72
+ * parse failure on any single row is skipped rather than fatal.
73
+ *
74
+ * Returns null when no row records one, and DISAGREEMENT is also null: a transcript
75
+ * whose rows claim two different working directories is not something to guess at.
76
+ */
77
+ export function cwdFromTranscript(text: string): string | null {
78
+ const seen = new Set<string>()
79
+ let rows = 0
80
+ for (const line of text.split('\n')) {
81
+ if (rows >= WORKSPACE_SCAN_ROWS) break
82
+ const trimmed = line.trim()
83
+ if (!trimmed) continue
84
+ rows++
85
+ let row: unknown
86
+ try { row = JSON.parse(trimmed) } catch { continue }
87
+ if (!row || typeof row !== 'object' || Array.isArray(row)) continue
88
+ const record = row as Record<string, unknown>
89
+ const direct = record.cwd
90
+ if (typeof direct === 'string' && direct.length > 0) seen.add(direct)
91
+ const payload = record.payload
92
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
93
+ const nested = (payload as Record<string, unknown>).cwd
94
+ if (typeof nested === 'string' && nested.length > 0) seen.add(nested)
95
+ }
96
+ // One consistent answer found early is enough; keep scanning only while it is
97
+ // still the only one, so a disagreement is detected rather than short-circuited.
98
+ if (seen.size > 1) return null
99
+ }
100
+ if (seen.size !== 1) return null
101
+ return [...seen][0]!
102
+ }
103
+
104
+ /**
105
+ * Resolve where an attached turn for this thread must run.
106
+ *
107
+ * Null means REFUSE THE ATTACH. Callers must not substitute a default, and must
108
+ * not fall back to the server's own cwd — that would run the user's turn against
109
+ * whatever directory the LaunchAgent happened to start in.
110
+ */
111
+ export function resolveAttachedWorkspace(
112
+ provider: string,
113
+ threadId: string,
114
+ deps: AttachedWorkspaceDeps,
115
+ ): ResolvedWorkspace | null {
116
+ if (provider !== 'claude' && provider !== 'codex') return null
117
+ let path: string | null
118
+ let text: string | null
119
+ try {
120
+ path = deps.transcriptPath(provider, threadId)
121
+ if (path === null) return null
122
+ text = deps.readHead(path, WORKSPACE_SCAN_BYTES)
123
+ } catch {
124
+ return null
125
+ }
126
+ if (text === null) return null
127
+
128
+ const cwd = cwdFromTranscript(text)
129
+ if (cwd === null) return null
130
+ // A relative cwd would resolve against the SERVER's working directory, which is
131
+ // not a location we control and is never what the user meant.
132
+ if (!isAbsolute(cwd)) return null
133
+
134
+ let exists: boolean
135
+ try {
136
+ exists = deps.dirExists(cwd)
137
+ } catch {
138
+ return null
139
+ }
140
+ // The workspace was deleted or moved since the thread was last used. Spawning
141
+ // there fails or, worse, succeeds somewhere unintended.
142
+ if (!exists) return null
143
+
144
+ return {
145
+ path: cwd,
146
+ workspaceFingerprint: fingerprint(cwd),
147
+ sourceFingerprint: fingerprint(path),
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Real filesystem wiring.
153
+ *
154
+ * `readHead`, not a tail read: the cwd lives in the EARLY rows (Claude's first
155
+ * message row, Codex's session meta row), and the tail of a 13 GB rollout does not
156
+ * contain it.
157
+ *
158
+ * O_NONBLOCK for the same reason `occupancy-probes.readFile` needs it — `openSync`
159
+ * on a writer-less FIFO never returns, and it is a synchronous syscall on Node's
160
+ * single thread, so one planted path would wedge the whole server rather than this
161
+ * request. The `isFile` check runs after the open and cannot prevent that alone.
162
+ */
163
+ export function realAttachedWorkspaceDeps(
164
+ headDeps: NativeHeadDeps = realNativeHeadDeps(),
165
+ ): AttachedWorkspaceDeps {
166
+ return {
167
+ transcriptPath: (provider, threadId) => transcriptPathFor(provider, threadId, headDeps),
168
+
169
+ readHead: (path, maxBytes) => {
170
+ let fd: number | null = null
171
+ try {
172
+ const noFollow = typeof fsConstants.O_NOFOLLOW === 'number' ? fsConstants.O_NOFOLLOW : 0
173
+ const nonBlock = typeof fsConstants.O_NONBLOCK === 'number' ? fsConstants.O_NONBLOCK : 0
174
+ fd = openSync(path, fsConstants.O_RDONLY | noFollow | nonBlock)
175
+ const stat = statSync(path, { throwIfNoEntry: false })
176
+ if (stat === undefined || !stat.isFile()) return null
177
+ const size = Math.min(maxBytes, stat.size)
178
+ if (size <= 0) return null
179
+ const buffer = Buffer.allocUnsafe(size)
180
+ const read = readSync(fd, buffer, 0, size, 0)
181
+ return buffer.subarray(0, read).toString('utf8')
182
+ } catch {
183
+ return null
184
+ } finally {
185
+ if (fd !== null) { try { closeSync(fd) } catch { /* already gone */ } }
186
+ }
187
+ },
188
+
189
+ // ENOENT is absent; every other errno is "cannot tell" and throws, which the
190
+ // resolver turns into a refusal. A bare catch here would let an unreadable
191
+ // workspace look like a deleted one.
192
+ dirExists: (path) => {
193
+ const stat = statSync(path, { throwIfNoEntry: false })
194
+ return stat !== undefined && stat.isDirectory()
195
+ },
196
+ }
197
+ }