Generated images survive moves and renames
Agents tidy up folders after generating images (sort into subfolders, rename revisions); the image records kept the old path, so /api/images/<id>/file answered 404 and only the requesting device still showed them from its cache. - Generated images get a copy in the image store (images.blob, kept by the blob GC); existing ones are copied once on start-up. - Records follow: after bash commands with mv/rename (missing images of the owner from the last two weeks, found by name or by content hash), when moved with the file manager (file or folder), and on access (served from the copy, relocated in the background; without a copy found by file name). - Chat gallery falls back to the preview the model saw when the file is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
90d65ec654
commit
b99f71526a
9 files changed
+260
-13
No files matched your search
+22
-1
@@ -563,7 +563,15 @@ async fn execute_call(
|
||||
// Pass depth through the agent name channel: subagents run with depth+1.
|
||||
run_task(app, &ctx, input, depth).await
|
||||
} else {
|
||||
tool.run(input, &ctx).await
|
||||
let moves = name == "bash" && input["command"].as_str().is_some_and(moves_files);
|
||||
let out = tool.run(input, &ctx).await;
|
||||
if moves {
|
||||
// Generated images the command moved or renamed keep their entries.
|
||||
let a = app.clone();
|
||||
let owner = session.owner_id.clone();
|
||||
tokio::task::spawn_blocking(move || crate::images::relocate_missing(&a, owner.as_deref()));
|
||||
}
|
||||
out
|
||||
};
|
||||
// Diagnostics: report new errors of the changed file right away.
|
||||
let res = match res {
|
||||
@@ -609,6 +617,11 @@ async fn execute_call(
|
||||
}
|
||||
}
|
||||
|
||||
/// A shell command that may move or rename files (`mv`, `git mv`, `rename`).
|
||||
fn moves_files(cmd: &str) -> bool {
|
||||
cmd.split(|c: char| c.is_whitespace() || ";&|()`$".contains(c)).any(|w| w == "mv" || w == "rename")
|
||||
}
|
||||
|
||||
async fn run_task(app: &Arc<App>, ctx: &ToolCtx, input: Value, depth: usize) -> Result<tools::ToolOutput> {
|
||||
let prompt = tools::arg_str(&input, "prompt")?.to_string();
|
||||
let description = input["description"].as_str().unwrap_or("subagent").to_string();
|
||||
@@ -1301,6 +1314,14 @@ async fn compact_inner(app: Arc<App>, session_id: &str, cancel: CancellationToke
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn detects_commands_that_move_files() {
|
||||
assert!(moves_files("mkdir -p art/old && mv art/*.png art/old/"));
|
||||
assert!(moves_files("git mv a.png b.png"));
|
||||
assert!(moves_files("cd art;mv x y"));
|
||||
assert!(!moves_files("ls -la movies && cat remove.txt"));
|
||||
}
|
||||
|
||||
fn call(id: &str) -> Part {
|
||||
Part::ToolCall { id: id.into(), name: "bash".into(), input: serde_json::json!({}), extra: None }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user