- 1
//! `doc_read` parses Office packages only in the broker worker - 2
//! (docs/design/72-openxml-documents.md, F3; invariant 14). - 3
- 4
#![allow(clippy::unwrap_used, clippy::expect_used)] - 5
- 6
use std::path::PathBuf; - 7
- 8
use serde_json::json; - 9
use vak_tools::ToolContext; - 10
- 11
fn doc_read( - 12
tools: Vec<std::sync::Arc<dyn vak_tools::Tool>>, - 13
) -> std::sync::Arc<dyn vak_tools::Tool> { - 14
tools - 15
.into_iter() - 16
.find(|tool| tool.name() == "doc_read") - 17
.expect("doc_read is a built-in tool") - 18
} - 19
- 20
#[tokio::test] - 21
async fn doc_read_reads_a_workbook_through_the_real_worker() { - 22
let dir = tempfile::tempdir().unwrap(); - 23
std::fs::write(dir.path().join("budget.xlsx"), vak_ooxml::fixtures::xlsx()).unwrap(); - 24
let worker = PathBuf::from(env!("CARGO_BIN_EXE_vak-tool-worker")); - 25
let tool = doc_read(vak_tools::brokered_default_tools(worker)); - 26
let output = tool - 27
.execute( - 28
&json!({"path": "budget.xlsx", "section": "Budget"}), - 29
&ToolContext::new(dir.path().to_path_buf()), - 30
) - 31
.await; - 32
assert!(!output.is_error, "{}", output.content); - 33
assert!( - 34
output - 35
.content - 36
.contains("[Budget!B4:C4] B4: =SUM(B2:B3) [cached: 120] | C4: TRUE"), - 37
"{}", - 38
output.content - 39
); - 40
} - 41
- 42
#[tokio::test] - 43
async fn core_never_runs_doc_read_in_its_own_process() { - 44
vak_config::paths::isolate_home_for_tests(); - 45
let dir = tempfile::tempdir().unwrap(); - 46
std::fs::write(dir.path().join("deck.pptx"), vak_ooxml::fixtures::pptx()).unwrap(); - 47
let core = vak_core::Core::new_with_trust(dir.path().to_path_buf(), true).unwrap(); - 48
core.set_tool_worker_exe(PathBuf::from("/nonexistent/vak-tool-worker")); - 49
let tools = core.agent_tools(); - 50
assert_eq!( - 51
tools - 52
.iter() - 53
.filter(|tool| tool.name() == "doc_read") - 54
.count(), - 55
1, - 56
"exactly one doc_read, the brokered one" - 57
); - 58
let output = doc_read(tools) - 59
.execute( - 60
&json!({"path": "deck.pptx"}), - 61
&ToolContext::new(dir.path().to_path_buf()), - 62
) - 63
.await; - 64
assert!(output.is_error); - 65
assert!( - 66
output.content.contains("tool broker unavailable"), - 67
"without a worker the read must fail, not fall back in-process: {}", - 68
output.content - 69
); - 70
let read_only = core.agent_read_only_tools(); - 71
assert!(read_only.iter().any(|tool| tool.name() == "doc_read")); - 72
} - 73
- 74
#[tokio::test] - 75
async fn office_apply_edits_through_the_worker_as_the_calling_agent() { - 76
use sha2::Digest as _; - 77
let dir = tempfile::tempdir().unwrap(); - 78
let file = dir.path().join("memo.docx"); - 79
std::fs::write(&file, vak_ooxml::fixtures::docx()).unwrap(); - 80
let digest: String = sha2::Sha256::digest(std::fs::read(&file).unwrap()) - 81
.iter() - 82
.take(8) - 83
.map(|byte| format!("{byte:02x}")) - 84
.collect(); - 85
let worker = PathBuf::from(env!("CARGO_BIN_EXE_vak-tool-worker")); - 86
let tool = vak_tools::brokered_default_tools(worker) - 87
.into_iter() - 88
.find(|tool| tool.name() == "office_apply") - 89
.expect("office_apply is a built-in tool"); - 90
let output = tool - 91
.execute( - 92
&json!({ - 93
"path": "memo.docx", - 94
"base_digest": digest, - 95
"ops": [{"op": "replace_paragraph_text", "anchor": "p@11", "text": "Up."}] - 96
}), - 97
&ToolContext::new(dir.path().to_path_buf()).with_agent_id("mira"), - 98
) - 99
.await; - 100
assert!(!output.is_error, "{}", output.content); - 101
assert!( - 102
output.content.contains("tracked changes by mira"), - 103
"the worker received the calling Agent's id: {}", - 104
output.content - 105
); - 106
assert_eq!( - 107
std::fs::read(&file).unwrap(), - 108
vak_ooxml::fixtures::docx(), - 109
"the workspace file waits for review" - 110
); - 111
let draft = output - 112
.content - 113
.split("written to ") - 114
.nth(1) - 115
.and_then(|rest| rest.split(". ").next()) - 116
.unwrap(); - 117
assert!(draft.starts_with(".vak/scratch/mira/"), "{draft}"); - 118
let bytes = std::fs::read(dir.path().join(draft)).unwrap(); - 119
let document = - 120
vak_ooxml::read::read(std::io::Cursor::new(bytes), vak_ooxml::Limits::default()).unwrap(); - 121
assert!( - 122
document - 123
.lines() - 124
.join("\n") - 125
.contains("[deleted by mira: Steady][inserted by mira: Up].") - 126
); - 127
} - 128
- 129
/// A revision's task copy holds a new document under its own name; the - 130
/// runtime tells the worker so, and its Word edits stay clean - 131
/// (docs/design/72, R7). The list travels with the call, never from the model. - 132
#[tokio::test] - 133
async fn the_worker_writes_a_new_document_clean_when_the_runtime_says_so() { - 134
use sha2::Digest as _; - 135
let dir = tempfile::tempdir().unwrap(); - 136
let file = dir.path().join("memo.docx"); - 137
std::fs::write(&file, vak_ooxml::fixtures::docx()).unwrap(); - 138
let digest: String = sha2::Sha256::digest(std::fs::read(&file).unwrap()) - 139
.iter() - 140
.take(8) - 141
.map(|byte| format!("{byte:02x}")) - 142
.collect(); - 143
let worker = PathBuf::from(env!("CARGO_BIN_EXE_vak-tool-worker")); - 144
let body = |tools: Vec<std::sync::Arc<dyn vak_tools::Tool>>| { - 145
let digest = digest.clone(); - 146
let dir = dir.path().to_path_buf(); - 147
async move { - 148
let tool = tools - 149
.into_iter() - 150
.find(|tool| tool.name() == "office_apply") - 151
.expect("office_apply is a built-in tool"); - 152
let output = tool - 153
.execute( - 154
&json!({ - 155
"path": "memo.docx", - 156
"base_digest": digest, - 157
"ops": [{"op": "replace_paragraph_text", "anchor": "p@11", "text": "Up."}] - 158
}), - 159
&ToolContext::new(dir.clone()).with_agent_id("mira"), - 160
) - 161
.await; - 162
assert!(!output.is_error, "{}", output.content); - 163
let draft = output - 164
.content - 165
.split("written to ") - 166
.nth(1) - 167
.and_then(|rest| rest.split(". ").next()) - 168
.unwrap() - 169
.to_string(); - 170
let mut package = vak_ooxml::Package::open( - 171
std::io::Cursor::new(std::fs::read(dir.join(draft)).unwrap()), - 172
vak_ooxml::Limits::default(), - 173
) - 174
.unwrap(); - 175
String::from_utf8(package.read_part("word/document.xml").unwrap()).unwrap() - 176
} - 177
}; - 178
let tracked = body(vak_tools::brokered_default_tools(worker.clone())).await; - 179
assert!( - 180
tracked.contains(r#"w:author="mira""#), - 181
"an existing file is tracked" - 182
); - 183
let clean = body(vak_tools::brokered_tools( - 184
worker, - 185
&["memo.docx".to_string()], - 186
)) - 187
.await; - 188
assert!( - 189
!clean.contains(r#"w:author="mira""#), - 190
"a file the runtime names as new is written clean" - 191
); - 192
assert!(clean.contains(">Up</w:t>"), "{clean}"); - 193
} - 194
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.