vv-agent 0.8.0

VectorVein agent runtime, SDK, CLI, tools, and workspace backends
Documentation
use super::*;

fn fallback_name_matches(name: &str) -> bool {
    let Some(hex) = name
        .strip_prefix("tool_result_")
        .and_then(|name| name.strip_suffix(".txt"))
    else {
        return false;
    };
    hex.len() == 32 && hex.chars().all(|character| character.is_ascii_hexdigit())
}

fn artifact_messages(tool_call_id: &str, contents: &[&str]) -> Vec<Message> {
    let calls = contents
        .iter()
        .map(|_| ToolCall::new(tool_call_id, "read_file", BTreeMap::new()))
        .collect::<Vec<_>>();
    let mut messages = vec![
        Message::system("system"),
        Message::user("read files"),
        Message {
            tool_calls: calls,
            ..Message::assistant("reading")
        },
    ];
    messages.extend(
        contents
            .iter()
            .map(|content| Message::tool(*content, tool_call_id)),
    );
    messages.push(Message::assistant("continue"));
    messages
}

fn compact_artifacts(
    workspace: &Path,
    artifact_dir: &str,
    tool_call_id: &str,
    contents: &[&str],
) -> Vec<Message> {
    let mut manager = MemoryManager::new(MemoryManagerConfig {
        compact_threshold: 10,
        model_context_window: 80,
        reserved_output_tokens: 10,
        autocompact_buffer_tokens: 0,
        tool_result_compact_threshold: 10,
        tool_result_keep_last: 0,
        tool_result_artifact_dir: artifact_dir.into(),
        workspace: Some(workspace.to_path_buf()),
        ..MemoryManagerConfig::default()
    });
    manager
        .compact_for_cycle(&artifact_messages(tool_call_id, contents), 4, false)
        .0
}

#[test]
fn artifact_fallbacks_are_unique_and_fail_open_at_workspace_boundary() {
    let contract = contract();
    let expected = &contract["artifacts"];
    assert_eq!(
        expected["fallback_pattern"].as_str().unwrap(),
        "^tool_result_[0-9a-f]{32}\\.txt$"
    );
    let root = tempfile::tempdir().expect("test root");
    let workspace = root.path().join("workspace");
    std::fs::create_dir(&workspace).expect("workspace");
    let compacted = compact_artifacts(
        &workspace,
        ".memory/tool_results",
        "/",
        &["first artifact payload", "second artifact payload"],
    );
    assert!(compacted
        .iter()
        .any(|message| message.content.contains("<Persisted Artifacts>")));
    let artifact_dir = workspace.join(".memory/tool_results/cycle_4");
    let mut fallback_names = std::fs::read_dir(&artifact_dir)
        .expect("artifact directory")
        .map(|entry| {
            entry
                .expect("artifact entry")
                .file_name()
                .to_string_lossy()
                .to_string()
        })
        .collect::<Vec<_>>();
    fallback_names.sort();
    assert_eq!(
        fallback_names.len() as u64,
        expected["fallback_count"].as_u64().unwrap()
    );
    assert!(fallback_names
        .iter()
        .all(|name| fallback_name_matches(name)));
    assert_ne!(fallback_names[0], fallback_names[1]);

    let blocked = workspace.join("blocked");
    std::fs::write(&blocked, "not a directory").expect("blocked path");
    let failed = compact_artifacts(
        &workspace,
        "blocked/nested",
        "call",
        &["write failure payload"],
    );
    assert!(failed
        .iter()
        .any(|message| message.content.contains("artifact_path: N/A")));
    assert_eq!(expected["write_failure_path"].as_str().unwrap(), "N/A");

    let escaped = compact_artifacts(&workspace, "../outside", "call", &["escape payload"]);
    assert!(escaped
        .iter()
        .any(|message| message.content.contains("artifact_path: N/A")));
    assert_eq!(expected["escape_path"].as_str().unwrap(), "N/A");
    assert!(!root.path().join("outside").exists());
}