@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.
- package/CHANGELOG.md +29 -0
- package/package.json +1 -1
- package/server/index.ts +141 -0
- package/server/lib/agent-session-binding-registry.ts +1225 -0
- package/server/lib/agent-session-binding-store.ts +327 -0
- package/server/lib/agent-session-ownership-store.ts +395 -0
- package/server/lib/attached-provider-adapter.ts +1223 -0
- package/server/lib/attached-workspace.ts +197 -0
- package/server/lib/native-head.ts +649 -0
- package/server/lib/native-thread-id.ts +23 -0
- package/server/lib/occupancy-probes.ts +444 -0
- package/server/lib/query-job-runtime.ts +24 -0
- package/server/lib/thread-occupancy.ts +367 -0
- package/server/routes/agent-session-bindings.ts +1550 -0
|
@@ -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
|
+
}
|