Skip to content

Commit 8ac129e

Browse files
author
SqlRush
committed
Remember restored image cache paths
1 parent eb37527 commit 8ac129e

4 files changed

Lines changed: 72 additions & 3 deletions

File tree

docs/first-second-parity-audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ M7 progress now includes:
505505
- `internal/session`: prompt-history `LogEntry` loading now accepts project aliases such as `projectPath`/`cwd`/`workingDirectory`/`workspacePath` and timestamp aliases such as `createdAt`/`unixTimestamp`, including RFC3339 timestamp normalization to Unix milliseconds.
506506
- `internal/session`: prompt-history display text loading now accepts aliases such as `prompt`/`text`/`input`/`content`/`value` for runtime and stored history.
507507
- `internal/session`: prompt-history pasted-content containers now accept aliases such as `pastedContent`/`pasted_content`, `pasteContents`/`paste_contents`, `pastes`, and `attachments`/`attachment`, matching interaction script fixture shapes for runtime and stored history.
508-
- `internal/session`: prompt-history pasted image loading now accepts content-block-style `source`/`imageSource` objects, data URL aliases, and source URL/URI/cache-path aliases; stored image entries with inline source data or a current-session image-cache file now restore base64 content into prompt image blocks instead of keeping only metadata, including same-ID cache fallback, media-type inference when history metadata omits the original MIME type, and symlink escape guards before reading cached image bytes.
508+
- `internal/session`: prompt-history pasted image loading now accepts content-block-style `source`/`imageSource` objects, data URL aliases, and source URL/URI/cache-path aliases; stored image entries with inline source data or a current-session image-cache file now restore base64 content into prompt image blocks instead of keeping only metadata, including same-ID cache fallback, media-type inference when history metadata omits the original MIME type, symlink escape guards before reading cached image bytes, and restored path re-indexing for later image metadata/source lookup.
509509
- `internal/compact`: microcompact disk cache loading now accepts broader summary-like direct and provider aliases including `body`, `markdown`, `description`/`details`, `finalSummary`, `summaryContent`, `resultText`, `completionText`, `responseText`, and `messageText`, preserving visible summary recovery through nested cache entries, JSON:API/resource envelopes, and provider-style response wrappers.
510510

511511
Still missing for full M6/M7 parity:

internal/session/history_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -847,11 +847,14 @@ func TestLoadHistoryRestoresExistingImageCacheSourcePath(t *testing.T) {
847847
t.Fatalf("history = %#v", history)
848848
}
849849
image := history[0].PastedContents[4]
850-
if image.SourcePath != imagePath || image.MediaType != "image/webp" || image.Filename != "diagram.webp" {
850+
if image.MediaType != "image/webp" || image.Filename != "diagram.webp" {
851851
t.Fatalf("image = %#v, want source path %q", image, imagePath)
852852
}
853-
if cached, ok := GetStoredImagePath(4); !ok || cached != imagePath {
853+
requireSameFile(t, image.SourcePath, imagePath)
854+
if cached, ok := GetStoredImagePath(4); !ok {
854855
t.Fatalf("cached image path = %q ok=%v, want %q", cached, ok, imagePath)
856+
} else {
857+
requireSameFile(t, cached, imagePath)
855858
}
856859
}
857860

internal/session/image_store.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func RestoreCachedImageContent(sessionID contracts.ID, content PastedContent, so
6969
if mediaType == "" {
7070
mediaType = inferredMediaType
7171
}
72+
rememberImagePath(content.ID, localPath)
7273
return base64.StdEncoding.EncodeToString(data), mediaType, localPath, true
7374
}
7475

internal/session/image_store_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,49 @@ func TestImagePathNormalizesMediaTypeParameters(t *testing.T) {
158158
}
159159
}
160160

161+
func TestRestoreCachedImageContentRemembersResolvedPath(t *testing.T) {
162+
dir := t.TempDir()
163+
t.Setenv("CLAUDE_CONFIG_DIR", dir)
164+
ClearStoredImagePaths()
165+
defer ClearStoredImagePaths()
166+
167+
sessionID := contracts.ID("session-remember")
168+
storedPath, ok := StoreImage(sessionID, PastedContent{
169+
ID: 41,
170+
Type: PastedContentImage,
171+
Content: base64.StdEncoding.EncodeToString([]byte("cached image")),
172+
MediaType: "image/png",
173+
})
174+
if !ok {
175+
t.Fatal("store image failed")
176+
}
177+
ClearStoredImagePaths()
178+
if got, ok := GetStoredImagePath(41); ok {
179+
t.Fatalf("path remained cached after clear: %q", got)
180+
}
181+
182+
content, mediaType, restoredPath, ok := RestoreCachedImageContent(sessionID, PastedContent{
183+
ID: 41,
184+
Type: PastedContentImage,
185+
MediaType: "image/png",
186+
}, "")
187+
if !ok {
188+
t.Fatal("restore cached image failed")
189+
}
190+
if got := string(mustDecodeBase64(t, content)); got != "cached image" {
191+
t.Fatalf("restored content = %q", got)
192+
}
193+
if mediaType != "image/png" {
194+
t.Fatalf("media type = %q", mediaType)
195+
}
196+
if !sameFile(t, restoredPath, storedPath) {
197+
t.Fatalf("restored path = %q, want same file as %q", restoredPath, storedPath)
198+
}
199+
if got, ok := GetStoredImagePath(41); !ok || !sameFile(t, got, restoredPath) {
200+
t.Fatalf("remembered path = %q ok=%v, want same file as %q", got, ok, restoredPath)
201+
}
202+
}
203+
161204
func TestRestoreCachedImageContentRejectsSymlinkEscape(t *testing.T) {
162205
dir := t.TempDir()
163206
t.Setenv("CLAUDE_CONFIG_DIR", dir)
@@ -182,3 +225,25 @@ func TestRestoreCachedImageContentRejectsSymlinkEscape(t *testing.T) {
182225
t.Fatalf("restore symlink escape = content=%q mediaType=%q path=%q ok=%v", content, mediaType, restoredPath, ok)
183226
}
184227
}
228+
229+
func mustDecodeBase64(t *testing.T, value string) []byte {
230+
t.Helper()
231+
data, err := base64.StdEncoding.DecodeString(value)
232+
if err != nil {
233+
t.Fatal(err)
234+
}
235+
return data
236+
}
237+
238+
func sameFile(t *testing.T, a, b string) bool {
239+
t.Helper()
240+
aInfo, err := os.Stat(a)
241+
if err != nil {
242+
t.Fatal(err)
243+
}
244+
bInfo, err := os.Stat(b)
245+
if err != nil {
246+
t.Fatal(err)
247+
}
248+
return os.SameFile(aInfo, bInfo)
249+
}

0 commit comments

Comments
 (0)