@gotcos/glasses-server 6.48.1 → 6.48.2
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/lib/attached-workspace.ts +53 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 6.48.2
|
|
2
|
+
|
|
3
|
+
Continue and Fork work again on a session whose transcript opens with a big row. Reported from the lens on 6.9.482 (Miles, 2026-09-15): Continue on a Claude session answered "Can't continue. Fork thread instead" while the row said the session was attachable.
|
|
4
|
+
|
|
5
|
+
- The attach route reads the session's working directory out of the transcript's first rows. It read a fixed 512 KiB, and a session whose first prompt carried pasted screenshots opens with one row of about 1 MB, so the window held four bookkeeping rows and a torn fifth, no `cwd`, and every Continue and Fork on that session was refused `target_unresolvable`. The read now widens (512 KiB, 2 MiB, 8 MiB, 16 MiB) while the window keeps ending inside a row with no `cwd` seen, and stops as soon as the whole file has been read or a row boundary shows the transcript records none.
|
|
6
|
+
- The first `cwd` in the transcript is the workspace. Two different values used to refuse the attach as a disagreement; on the release Mac every such transcript was one session whose shell had `cd`-ed into a subfolder inside a turn, and Claude stamps each row with the process cwd of the moment. The directory the session started in keys its project folder and is the only place `--resume` finds it, so it is the answer. On the release Mac 738 of 758 transcripts resolve (before: 703); the 20 that do not are scratch sessions whose temporary directory has since been deleted, which is a refusal by design.
|
|
7
|
+
- Tests: the first-cwd rule for Claude and Codex, the torn-window signal, the widening sequence against the production shape (four bookkeeping rows, a 950 KB image row, then the cwd), a short file read once, a first row past the cap refused at the cap. Mutation gate: the widening and the first-cwd rule.
|
|
8
|
+
|
|
1
9
|
## 6.48.1
|
|
2
10
|
|
|
3
11
|
One derivation everywhere, a Desktop turn ends when the engine says it ended, a queued follow-up drains then instead of on the 30 s idle window, and the live feed carries the same state every row does. No client change is required: the next Control and EHPK builds read the new keys, and today's consumers ignore them.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotcos/glasses-server",
|
|
3
|
-
"version": "6.48.
|
|
3
|
+
"version": "6.48.2",
|
|
4
4
|
"description": "COS Glasses \u2014 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": {
|
|
@@ -61,9 +61,20 @@ export interface AttachedWorkspaceDeps {
|
|
|
61
61
|
cursorSpawnWorkspace?: (threadId: string) => string | null
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* The first window read for the cwd: enough to reach a Claude message row or the Codex
|
|
66
|
+
* session meta row in the common case. NOT enough on its own (6.48.2): a session whose
|
|
67
|
+
* first prompt carries pasted screenshots opens with a single row of ~1 MB, so 512 KiB
|
|
68
|
+
* held four bookkeeping rows and a torn fifth, no cwd, and every Continue on that
|
|
69
|
+
* session was refused `target_unresolvable` (Miles, 2026-09-15, from the lens; 55 of
|
|
70
|
+
* 758 transcripts on the release Mac). When a window ends inside a row before any cwd
|
|
71
|
+
* was seen, the read widens up to WORKSPACE_SCAN_BYTES_MAX.
|
|
72
|
+
*/
|
|
65
73
|
export const WORKSPACE_SCAN_BYTES = 512 * 1024
|
|
66
74
|
|
|
75
|
+
/** The widest head read: a first prompt with ten screenshots fits; a 13 GB rollout does not get read. */
|
|
76
|
+
export const WORKSPACE_SCAN_BYTES_MAX = 16 * 1024 * 1024
|
|
77
|
+
|
|
67
78
|
/** Rows to inspect before giving up. Bounds a pathological single-line file. */
|
|
68
79
|
export const WORKSPACE_SCAN_ROWS = 400
|
|
69
80
|
|
|
@@ -72,19 +83,37 @@ export function fingerprint(value: string): string {
|
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
/**
|
|
75
|
-
* Pull the recorded cwd out of transcript text.
|
|
86
|
+
* Pull the recorded cwd out of transcript text: the FIRST row that records one.
|
|
76
87
|
*
|
|
77
88
|
* Claude puts it at the row's top level; Codex puts it in the session meta row's
|
|
78
89
|
* `payload`. A torn trailing line is NORMAL — these files are appended to — so a
|
|
79
90
|
* parse failure on any single row is skipped rather than fatal.
|
|
80
91
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
92
|
+
* THE FIRST cwd IS THE WORKSPACE (6.48.2). Until 6.48.1 two different values in the
|
|
93
|
+
* window refused the attach as a disagreement. On the release Mac every such
|
|
94
|
+
* transcript was one session whose shell `cd`-ed inside a turn (`operations/scripts`,
|
|
95
|
+
* a weekly folder): Claude stamps each row with the process cwd of the moment, all
|
|
96
|
+
* under the directory the session was started in. That starting directory is the
|
|
97
|
+
* workspace: it keys the transcript's project folder, it is the only place
|
|
98
|
+
* `claude --resume` finds the session, and it is what the first row records.
|
|
99
|
+
*
|
|
100
|
+
* Returns null only when no row in the window records one.
|
|
83
101
|
*/
|
|
84
102
|
export function cwdFromTranscript(text: string): string | null {
|
|
85
|
-
|
|
103
|
+
return scanForCwd(text).cwd
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The scan with its outcome: the cwd, or whether the window ended INSIDE a row before
|
|
108
|
+
* any cwd was seen (a first prompt of pasted screenshots is one row of ~1 MB), which
|
|
109
|
+
* tells the resolver that a wider read can still answer.
|
|
110
|
+
*/
|
|
111
|
+
export function scanForCwd(text: string): { cwd: string | null; endedMidRow: boolean; rows: number } {
|
|
86
112
|
let rows = 0
|
|
87
|
-
|
|
113
|
+
const lines = text.split('\n')
|
|
114
|
+
// A window that does not end with a newline ended inside whatever row came last.
|
|
115
|
+
const endedMidRow = text.length > 0 && !text.endsWith('\n')
|
|
116
|
+
for (const line of lines) {
|
|
88
117
|
if (rows >= WORKSPACE_SCAN_ROWS) break
|
|
89
118
|
const trimmed = line.trim()
|
|
90
119
|
if (!trimmed) continue
|
|
@@ -94,18 +123,14 @@ export function cwdFromTranscript(text: string): string | null {
|
|
|
94
123
|
if (!row || typeof row !== 'object' || Array.isArray(row)) continue
|
|
95
124
|
const record = row as Record<string, unknown>
|
|
96
125
|
const direct = record.cwd
|
|
97
|
-
if (typeof direct === 'string' && direct.length > 0)
|
|
126
|
+
if (typeof direct === 'string' && direct.length > 0) return { cwd: direct, endedMidRow: false, rows }
|
|
98
127
|
const payload = record.payload
|
|
99
128
|
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
100
129
|
const nested = (payload as Record<string, unknown>).cwd
|
|
101
|
-
if (typeof nested === 'string' && nested.length > 0)
|
|
130
|
+
if (typeof nested === 'string' && nested.length > 0) return { cwd: nested, endedMidRow: false, rows }
|
|
102
131
|
}
|
|
103
|
-
// One consistent answer found early is enough; keep scanning only while it is
|
|
104
|
-
// still the only one, so a disagreement is detected rather than short-circuited.
|
|
105
|
-
if (seen.size > 1) return null
|
|
106
132
|
}
|
|
107
|
-
|
|
108
|
-
return [...seen][0]!
|
|
133
|
+
return { cwd: null, endedMidRow, rows }
|
|
109
134
|
}
|
|
110
135
|
|
|
111
136
|
/**
|
|
@@ -145,17 +170,27 @@ export function resolveAttachedWorkspace(
|
|
|
145
170
|
}
|
|
146
171
|
if (provider !== 'claude' && provider !== 'codex') return null
|
|
147
172
|
let path: string | null
|
|
148
|
-
let
|
|
173
|
+
let cwd: string | null = null
|
|
149
174
|
try {
|
|
150
175
|
path = deps.transcriptPath(provider, threadId)
|
|
151
176
|
if (path === null) return null
|
|
152
|
-
|
|
177
|
+
// Widen the head read while the window keeps ending inside a row with no cwd seen:
|
|
178
|
+
// 512 KiB, 2 MiB, 8 MiB, then the cap. A window that ended on a row boundary with
|
|
179
|
+
// no cwd is a transcript that records none, and widening would not change that.
|
|
180
|
+
let window = WORKSPACE_SCAN_BYTES
|
|
181
|
+
for (;;) {
|
|
182
|
+
const text = deps.readHead(path, window)
|
|
183
|
+
if (text === null) return null
|
|
184
|
+
const scan = scanForCwd(text)
|
|
185
|
+
cwd = scan.cwd
|
|
186
|
+
// The whole file came back (shorter than the window): there is nothing wider to read.
|
|
187
|
+
const wholeFile = Buffer.byteLength(text, 'utf8') < window
|
|
188
|
+
if (cwd !== null || wholeFile || !scan.endedMidRow || scan.rows >= WORKSPACE_SCAN_ROWS || window >= WORKSPACE_SCAN_BYTES_MAX) break
|
|
189
|
+
window = Math.min(window * 4, WORKSPACE_SCAN_BYTES_MAX)
|
|
190
|
+
}
|
|
153
191
|
} catch {
|
|
154
192
|
return null
|
|
155
193
|
}
|
|
156
|
-
if (text === null) return null
|
|
157
|
-
|
|
158
|
-
const cwd = cwdFromTranscript(text)
|
|
159
194
|
if (cwd === null) return null
|
|
160
195
|
// A relative cwd would resolve against the SERVER's working directory, which is
|
|
161
196
|
// not a location we control and is never what the user meant.
|