use super::*;
pub(super) mod agent_teams;
pub(super) mod bug_monitor;
pub(super) mod capabilities;
pub(super) mod channels;
pub(super) mod coder;
pub(super) mod context_packs;
pub(super) mod context_run_ledger;
pub(super) mod context_run_mutation_checkpoints;
pub(super) mod context_runs;
pub(super) mod enterprise;
pub(super) mod global;
pub(super) mod marketplace;
pub(super) mod mcp;
pub(super) mod memory;
pub(super) mod mission_builder;
pub(super) mod missions;
pub(super) mod optimizations;
pub(super) mod pack_builder;
pub(super) mod packs;
pub(super) mod permissions;
pub(super) mod presets;
pub(super) mod providers;
pub(super) mod resources;
pub(super) mod routines;
pub(super) mod sessions;
pub(super) mod setup_understanding;
pub(super) mod task_intake;
pub(super) mod workflow_planner;
pub(super) mod workflows;
use std::sync::Arc;
use axum::body::{to_bytes, Body};
use axum::http::Request;
use std::time::Duration;
use tandem_core::{
AgentRegistry, CancellationRegistry, ConfigStore, EngineLoop, EventBus, PermissionManager,
PluginRegistry, Storage, ToolPolicyContext, ToolPolicyHook,
};
use tandem_providers::ProviderRegistry;
use tandem_runtime::{LspManager, McpRegistry, PtyManager, WorkspaceIndex};
use tandem_tools::ToolRegistry;
use tokio::sync::broadcast;
use tower::ServiceExt;
use uuid::Uuid;
use crate::http::global::sanitize_relative_subpath;
pub(super) async fn test_state() -> AppState {
let root = std::env::temp_dir().join(format!("tandem-http-test-{}", Uuid::new_v4()));
let global = root.join("global-config.json");
let tandem_home = root.join("tandem-home");
let mcp_state = root.join("mcp.json");
std::env::set_var("TANDEM_GLOBAL_CONFIG", &global);
std::env::set_var("TANDEM_HOME", &tandem_home);
let seeded_mcp = json!({
"github": {
"name": "github",
"transport": "memory://github",
"enabled": true,
"connected": false,
"headers": {},
"tool_cache": [
{
"tool_name": "list_repository_issues",
"description": "List repository issues",
"input_schema": {"type":"object"},
"fetched_at_ms": 1,
"schema_hash": "tool-github-list-issues"
},
{
"tool_name": "get_issue",
"description": "Get a GitHub issue",
"input_schema": {"type":"object"},
"fetched_at_ms": 1,
"schema_hash": "tool-github-get-issue"
},
{
"tool_name": "mcp.github.list_pull_requests",
"description": "List repository pull requests",
"input_schema": {"type":"object"},
"fetched_at_ms": 1,
"schema_hash": "tool-github-list-pulls"
},
{
"tool_name": "mcp.github.get_pull_request",
"description": "Get a GitHub pull request",
"input_schema": {"type":"object"},
"fetched_at_ms": 1,
"schema_hash": "tool-github-get-pull"
},
{
"tool_name": "mcp.github.create_pull_request",
"description": "Create a GitHub pull request",
"input_schema": {"type":"object"},
"fetched_at_ms": 1,
"schema_hash": "tool-github-create-pull"
}
],
"tools_fetched_at_ms": 1,
"pending_auth_by_tool": {}
}
});
if let Some(parent) = mcp_state.parent() {
std::fs::create_dir_all(parent).expect("mcp state dir");
}
std::fs::write(
&mcp_state,
serde_json::to_string_pretty(&seeded_mcp).expect("seeded mcp json"),
)
.expect("write mcp state");
let storage = Arc::new(Storage::new(root.join("storage")).await.expect("storage"));
let config = ConfigStore::new(root.join("config.json"), None)
.await
.expect("config");
let event_bus = EventBus::new();
let app_config = config.get().await;
let browser = crate::BrowserSubsystem::new(app_config.browser.clone());
let _ = browser.refresh_status().await;
let providers = ProviderRegistry::new(app_config.into());
let plugins = PluginRegistry::new(".").await.expect("plugins");
let agents = AgentRegistry::new(".").await.expect("agents");
let tools = ToolRegistry::new();
let permissions = PermissionManager::new(event_bus.clone());
let mcp = McpRegistry::new_with_state_file(mcp_state);
let pty = PtyManager::new();
let lsp = LspManager::new(".");
let auth = Arc::new(tokio::sync::RwLock::new(HashMap::new()));
let logs = Arc::new(tokio::sync::RwLock::new(Vec::new()));
let workspace_index = WorkspaceIndex::new(".").await;
let cancellations = CancellationRegistry::new();
let host_runtime_context = crate::detect_host_runtime_context();
let engine_loop = EngineLoop::new(
storage.clone(),
event_bus.clone(),
providers.clone(),
plugins.clone(),
agents.clone(),
permissions.clone(),
tools.clone(),
cancellations.clone(),
host_runtime_context.clone(),
);
let mut state = AppState::new_starting(Uuid::new_v4().to_string(), false);
state.shared_resources_path = root.join("shared_resources.json");
state.memory_audit_path = root.join("memory").join("audit.log.jsonl");
state.protected_audit_path = root.join("audit").join("protected_events.log.jsonl");
state
.mark_ready(crate::RuntimeState {
storage,
config,
event_bus,
providers,
plugins,
agents,
tools,
permissions,
mcp,
pty,
lsp,
auth,
logs,
workspace_index,
cancellations,
engine_loop,
host_runtime_context,
browser,
})
.await
.expect("runtime ready");
assert!(state.mcp.connect("github").await);
state
}
pub(super) fn write_pack_zip(path: &std::path::Path, manifest: &str) {
write_pack_zip_with_entries(path, manifest, &[("README.md", "# pack")]);
}
pub(super) fn write_pack_zip_with_entries(
path: &std::path::Path,
manifest: &str,
extra_entries: &[(&str, &str)],
) {
let file = std::fs::File::create(path).expect("create zip");
let mut zip = zip::ZipWriter::new(file);
let opts = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated);
zip.start_file("tandempack.yaml", opts)
.expect("start marker");
std::io::Write::write_all(&mut zip, manifest.as_bytes()).expect("write marker");
for (name, body) in extra_entries {
zip.start_file(*name, opts).expect("start extra entry");
std::io::Write::write_all(&mut zip, body.as_bytes()).expect("write extra entry");
}
zip.finish().expect("finish zip");
}
pub(super) fn write_plain_zip_without_marker(path: &std::path::Path) {
let file = std::fs::File::create(path).expect("create zip");
let mut zip = zip::ZipWriter::new(file);
let opts = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated);
zip.start_file("README.md", opts).expect("start readme");
std::io::Write::write_all(&mut zip, b"# not a pack").expect("write readme");
zip.start_file("agents/a.txt", opts)
.expect("start agents file");
std::io::Write::write_all(&mut zip, b"agent body").expect("write agents file");
zip.finish().expect("finish zip");
}
pub(super) async fn next_event_of_type(
rx: &mut broadcast::Receiver<EngineEvent>,
expected_type: &str,
) -> EngineEvent {
tokio::time::timeout(Duration::from_secs(5), async {
loop {
let event = rx.recv().await.expect("event");
if event.event_type == expected_type {
return event;
}
}
})
.await
.expect("event timeout")
}